--- /dev/null
+#include "common.h"
+
+#include <unistd.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+extern char *rcon_host, *rcon_port, *rcon_pass, *map;
+
+void senderr(char *errmsg, bool myfault) {
+ if (myfault) {
+ puts("Status: 500 CGI error");
+ } else {
+ puts("Status: 400 CGI error");
+ }
+ puts("Content-Type: text/plain;charset=utf8");
+ puts("X-Frame-Options: DENY");
+ puts("");
+ puts("An error occured while processing your request:");
+ puts(errmsg);
+ exit(0);
+}
+
+struct inifile *get_config(void) {
+ static struct inifile *res = NULL;
+ if (!res) res = slurp_inifile(CONFIG_PATH);
+ if (!res) {
+ // error!
+ senderr("can't fetch config", true);
+ }
+ return res;
+}
+
+char *getcval(char *key) {
+ char *r = inifile_lookup(get_config(), key);
+ if (!r) {
+ // error!
+ senderr("config value missing", true);
+ }
+ return r;
+}
+
+void rcon_send(char *data) {
+ // get config
+ char *host = getcval("rcon_host");
+ char *port = getcval("rcon_port");
+ char *pass = getcval("rcon_pass");
+
+ // construct packet
+ size_t pass_len = strlen(pass);
+ size_t data_len = strlen(data);
+ size_t packet_len = 4+4+1+pass_len+1+data_len +1;
+ char buf[packet_len];
+ char *p = buf;
+ memcpy(p, "\xff\xff\xff\xffrcon ", 9); p += 9;
+ memcpy(p, pass, pass_len); p += pass_len;
+ *(p++) = ' ';
+ memcpy(p, data, data_len); p += data_len;
+ *(p++) = '\0';
+
+ // connect
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd == -1) senderr("can't create UDP socket", true);
+ struct sockaddr_in addr;
+ bzero(&addr, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(atoi(port));
+ if (!inet_aton(host, &addr.sin_addr)) senderr("bad ipv4 addr in config", true);
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)))
+ senderr("can't connect UDP socket", true);
+
+ // send
+ if (write(fd, buf, packet_len) != packet_len)
+ senderr("incomplete packet sent or write error", true);
+
+ close(fd);
+}
--- /dev/null
+#include "common.h"
+
+char *map;
+
+void check_map_name(void) {
+ if (map == NULL) senderr("missing query string", false);
+ for (char *p = map; *p; p++) {
+ if (*p >= 'a' && *p <= 'z') continue;
+ if (*p >= 'A' && *p <= 'Z') continue;
+ if (*p >= '0' && *p <= '9') continue;
+ if (*p == '_') continue;
+ senderr("bad map name", false);
+ }
+}
+
+int main(void) {
+ map = getenv("QUERY_STRING");
+ check_map_name();
+
+ char cmd[4+strlen(map)+1];
+ memcpy(cmd, "map ", 4);
+ strcpy(cmd+4, map);
+ rcon_send(cmd);
+
+ puts("Status: 204 changed map"
+ "\nX-Frame-Options: DENY"
+ "\n");
+ exit(0);
+}
\ No newline at end of file
--- /dev/null
+#include "common.h"
+
+static char *maps_url;
+static char *maps_dir;
+
+int print_map(struct dirent *dent, void *data) {
+ if (!ends_with(dent->d_name, ".png")) return 0;
+
+ size_t dent_name_len = strlen(dent->d_name);
+ char name_nopng[dent_name_len-4+1];
+ memcpyn(name_nopng, dent->d_name, dent_name_len-4);
+
+ printf(" <div class=\"map\">"
+ "\n <a href=\"loadmap?%s\">"
+ "\n <div class=\"mapimg\"><img src=\"%s/%s\"></div>"
+ "\n <div class=\"mapname\">%s</div>"
+ "\n </a>"
+ "\n </div>"
+ "\n",
+ name_nopng, getcval("maps_url"), dent->d_name, name_nopng
+ );
+
+ return 0;
+}
+
+int main(void) {
+ maps_url = getcval("maps_url");
+ maps_dir = getcval("maps_dir");
+
+ puts("Status: 200 graphical listing coming up..."
+ "\nContent-Type: text/html;charset=utf8"
+ "\nX-Frame-Options: DENY"
+ "\n"
+ "\n<!DOCTYPE html>"
+ "\n<html>"
+ "\n <head>"
+ "\n <title>Quake Control</title>"
+ "\n <style>"
+ "\n .map {float: left; padding: 5px; }"
+ "\n </style>"
+ "\n </head>"
+ "\n <body>");
+
+ dir_foreach(maps_dir, print_map, NULL);
+
+ puts(" </body>"
+ "\n</html>");
+
+ exit(0);
+}