initial commit
authorJann Horn <jann@thejh.net>
Sun, 16 Jun 2013 19:41:59 +0000 (21:41 +0200)
committerJann Horn <jann@thejh.net>
Sun, 16 Jun 2013 19:41:59 +0000 (21:41 +0200)
.gitignore [new file with mode: 0644]
common.c [new file with mode: 0644]
common.h [new file with mode: 0644]
loadmap.c [new file with mode: 0644]
showmaps.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e6515f3
--- /dev/null
@@ -0,0 +1,3 @@
+loadmap
+showmaps
+
diff --git a/common.c b/common.c
new file mode 100644 (file)
index 0000000..739d365
--- /dev/null
+++ b/common.c
@@ -0,0 +1,79 @@
+#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);
+}
diff --git a/common.h b/common.h
new file mode 100644 (file)
index 0000000..3a055a7
--- /dev/null
+++ b/common.h
@@ -0,0 +1,11 @@
+#define CONFIG_PATH "/etc/quakecontrol/config.ini"
+
+#include <jh.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void senderr(char *errmsg, bool myfault);
+struct inifile *get_config(void);
+char *getcval(char *key);
+void rcon_send(char *data);
diff --git a/loadmap.c b/loadmap.c
new file mode 100644 (file)
index 0000000..82b3f6d
--- /dev/null
+++ b/loadmap.c
@@ -0,0 +1,29 @@
+#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
diff --git a/showmaps.c b/showmaps.c
new file mode 100644 (file)
index 0000000..6ea884e
--- /dev/null
@@ -0,0 +1,50 @@
+#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);
+}