chiark / gitweb /
cgi-fcgi-perl: wip, convert to err.h; do mkdir
[chiark-utils.git] / cprogs / cgi-fcgi-perl.c
index 037bd8644e271675b6817b019c7af4d23cc97dac..58a858ae41fede971bc4372001c2427a2ff222e9 100644 (file)
@@ -37,6 +37,9 @@
  * used.  The substring is chosen so that the whole path is 10 bytes
  * shorter than sizeof(sun_path).  But always at least 33 characters.
  *
+ * <node> is truncated at the first `.' and after the first 32
+ * characters.
+ *
  * Algorithm:
  *  - see if /var/run/user exists
  *       if so, lstat /var/run/user/<UID> and check that
  *  - run  cgi-fcgi -connect SOCKET SCRIPT
  */
 
+#include "common.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <stdbool.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <err.h>
+
+#include <nettle/sha.h>
 
 #include "myopt.h"
 
+#define die  common_die
+#define diee common_diee
+
+#define MINHEXHASH 33
+
 static const char *ident;
 static int numservers;
 
-static void diee(const char *m) {
-  fprintf(stderr,"cgi-fcgi-: error: %s failed: %s\n", m, strerror(errno));
-  exit(127);
+void diee(const char *m) {
+  err(127, "error: %s failed", m);
 }
 
 static void fusagemessage(FILE *f) {
@@ -89,7 +110,120 @@ static const struct cmdinfo cmdinfos[]= {
   { 0 }
 };
 
+static uid_t us;
+static const char *run_base, *command, *socket_path;
+
+static bool find_run_base_var_run(void) {
+  struct stat stab;
+  char *try;
+  int r;
+
+  try = m_asprintf("%s/%lu", "/var/run/user", us);
+  r = lstat(try, &stab);
+  if (r<0) {
+    if (errno == ENOENT ||
+       errno == ENOTDIR ||
+       errno == EACCES ||
+       errno == EPERM)
+      return 0; /* oh well */
+    diee("stat /var/run/user/UID");
+  }
+  if (!S_ISDIR(stab.st_mode)) {
+    warnx("%s not a directory, falling back to ~\n", try);
+    return 0;
+  }
+  if (stab.st_uid != us) {
+    warnx("%s not owned by uid %lu, falling back to ~\n", try,
+         (unsigned long)us);
+    return 0;
+  }
+  if (stab.st_mode & 0077) {
+    warnx("%s writeable by group or other, falling back to ~\n", try);
+    return 0;
+  }
+  run_base = m_asprintf("%s/%s", try, "cgi-fcgi-perl");
+  return 1;
+}
+
+static bool find_run_base_home(void) {
+  struct passwd *pw;
+  struct utsname ut;
+  char *dot, *try;
+  int r;
+
+  pw = getpwuid(us);  if (!pw) diee("getpwent(uid)");
+
+  r = uname(&ut);   if (r) diee("uname(2)");
+  dot = strchr(ut.nodename, '.');
+  if (dot) *dot = 0;
+  if (sizeof(ut.nodename) > 32)
+    ut.nodename[32] = 0;
+
+  try = m_asprintf("%s/%s/%s", pw->pw_dir, ".cgi-fcgi-perl", ut.nodename);
+  run_base = try;
+  return 1;
+}
+
+static void find_socket_path(void) {
+  struct sockaddr_un sun;
+  int r;
+
+  us = getuid();  if (us==(uid_t)-1) diee("getuid");
+
+  find_run_base_var_run() ||
+    find_run_base_home() ||
+    (abort(),0);
+
+  int maxidentlen = sizeof(sun.sun_path) - strlen(run_base) - 10 - 2;
+
+  if (!ident) {
+    if (maxidentlen < MINHEXHASH)
+      errx(127,"cgi-fcgi-perl: base directory `%s'"
+          " leaves only %d characters for command name hash"
+          " which is too little (<%d)",
+          run_base, maxidentlen, MINHEXHASH);
+
+    int identlen = maxidentlen > 64 ? 64 : maxidentlen;
+    char *hexident = xmalloc(identlen + 2);
+    struct sha256_ctx sc;
+    unsigned char bbuf[32];
+    int i;
+
+    sha256_init(&sc);
+    sha256_update(&sc,strlen(command)+1,command);
+    sha256_digest(&sc,sizeof(bbuf),bbuf);
+
+    for (i=0; i<identlen; i += 2)
+      sprintf(hexident+i, "%02x", bbuf[i/2]);
+
+    hexident[identlen] = 0;
+    ident = hexident;
+  }
+
+  if (strlen(ident) > maxidentlen)
+    errx(127, "base directory `%s' plus ident `%s' too long"
+        " (with spare) for socket (max ident %d)\n",
+        run_base, ident, maxidentlen);
+
+  r = mkdir(run_base, 0700);
+  if (r) {
+    if (!(errno == EEXIST))
+      err(127,"mkdir %s",run_base);
+  }
+
+  socket_path = m_asprintf("%s/g%s",run_base,ident);
+}  
+
 int main(int argc, const char *const *argv) {
   myopt(&argv, cmdinfos);
+
+  command = *argv++;
+  if (!command) errx(127,"need command argument");
+  if (*argv) errx(127,"too many arguments");
+
+  find_socket_path();
+
+  printf(">%s<\n",socket_path);
+
   exit(0);
 }