Warning: In an inconsistent state since undertaking major modifications release-20040406
authorMatthew Mondor <mmondor@pulsar-zone.net>
Tue, 6 Apr 2004 16:35:23 +0000 (16:35 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Tue, 6 Apr 2004 16:35:23 +0000 (16:35 +0000)
mmsoftware/mmspawnd/mmspawnd.c

index 28709b9..1044433 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmspawnd.c,v 1.18 2004/03/14 09:26:13 mmondor Exp $ */
+/* $Id: mmspawnd.c,v 1.19 2004/04/06 16:35:23 mmondor Exp $ */
 
 /*
  * Copyright (C) 2003, Matthew Mondor
  *   a library like libevent or libio which could take adventage of features
  *   like kqueue and epoll where available. Using poll(2) wouldn't be too
  *   bad however...
+ * - Think properly. We have a recursion prevention lock, which should use
+ *   a lock path outside of the chroot(2) if any. We have the ALOCK_PATH,
+ *   which can also be outside the chroot(2), since only the parent locks it.
+ *   However, there is the LOCK_PATH which should be inside the chroot(2) if
+ *   any, because both the parent, cache manager and children processes are
+ *   locking it. These files also need to be readable by the user or group
+ *   under which mmspawnd(8) runs, so that shlocks_reopen() works. Hmm these
+ *   are not used in the children processes however, only by the cache manager
+ *   process after launch... Either we have chroot(2) called in each, in which
+ *   case the files can be anywhere on the filesystem, or we cause those lock
+ *   files to be created after chroot(2). Also, because we drop privileges
+ *   after this, both can write and open the locks fine permission-wise.
+ *   So plz hlp wut 2 doo 2 nut teh errer kthx!!11111 u teh their!?1//
+ *   Argh!!! children processes call client_resolve()! Hence they also need
+ *   to reopen lock file(s)... So LOCK_PATH HAS to be within the chroot(2),
+ *   and chroot(2) needs to be called before calling shlocks_init()!
+ * - Fix client_resolve() so that getnameinfo(3) can be called concurrently.
+ * - Update man page about lock paths requirements etc!
  */
 
 
 
 MMCOPYRIGHT("@(#) Copyright (c) 2003\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmspawnd.c,v 1.18 2004/03/14 09:26:13 mmondor Exp $");
+MMRCSID("$Id: mmspawnd.c,v 1.19 2004/04/06 16:35:23 mmondor Exp $");
 
 
 
 /* DEFINITIONS */
 
 #define DAEMON_NAME            "mmspawnd"
-#define DAEMON_VERSION         "0.0.1/mmondor"
+#define DAEMON_VERSION         "0.0.2/mmondor"
 
 struct configuration {
-    char CHROOT_DIR[256], LOCK_PATH[256], PID_PATH[256],
-       USER[32], GROUPS[256], LOG_FACILITY[32], LISTEN_ADDRESSES[1024],
-       COMMAND[256], ALOCK_PATH[256], ALOCK_USER[32], ALOCK_GROUP[32],
-       ALOCK_MODE[4], PROCTITLE[64], STDERR_FILE[256];
+    char CHROOT_DIR[256], LOCKS_PATH[256], LOCKS_USER[32], LOCKS_GROUP[32],
+       LOCKS_MODE[4], PID_PATH[256], USER[32], GROUPS[256], LOG_FACILITY[32],
+       LISTEN_ADDRESSES[1024], COMMAND[256], ALOCK_PATH[256], ALOCK_USER[32],
+       ALOCK_GROUP[32], ALOCK_MODE[4], PROCTITLE[64], STDERR_FILE[256];
     long LISTEN_PORT, MAX_CONNECTIONS, MAX_ADDRESSES, MAX_PER_ADDRESS,
        CONNECTION_RATE, CONNECTION_PERIOD, MAX_ARGUMENTS, COMMAND_TIMEOUT,
        LIMIT_CORE, LIMIT_CPU, LIMIT_DATA, LIMIT_FSIZE, LIMIT_MEMLOCK,
@@ -202,7 +220,7 @@ struct ipaddr_expire_process_iterator_udata {
            syslog(LOG_NOTICE, "* setrlimit(%s, %ld) - (%s)", \
                    (restxt), (limit), strerror(errno)); \
     } \
-} while (/* CONSTCOND */0) ;
+} while (/* CONSTCOND */0)
 
 
 
@@ -231,8 +249,13 @@ static int key_cmp32(const void *, const void *, size_t);
 static int key_pidcmp(const void *, const void *, size_t);
 static u_int32_t key_pidhash(const void *, size_t);
 
+void launch_server_child_init(const char *, uid_t, gid_t *, int);
+
+static pid_t launch_server_process(uid_t, gid_t *, int);
+static void server_process_main(void);
+
 static pid_t launch_ipaddr_expire_process(uid_t, gid_t *, int);
-static void ipaddr_expire_process(void);
+static void ipaddr_expire_process_main(void);
 static bool ipaddr_expire_process_iterator(hashnode_t *, void *);
 
 
@@ -277,7 +300,10 @@ int main(int argc, char **argv)
     carg_t *cargp;
     carg_t cargs[] = {
        {CAT_STR, CAF_NONE, 0, 255, "CHROOT_DIR", CONF.CHROOT_DIR},
-       {CAT_STR, CAF_NONE, 1, 255, "LOCK_PATH", CONF.LOCK_PATH},
+       {CAT_STR, CAF_NONE, 1, 255, "LOCKS_PATH", CONF.LOCKS_PATH},
+       {CAT_STR, CAF_NONE, 1, 31, "LOCKS_USER", CONF.LOCKS_USER},
+       {CAT_STR, CAF_NONE, 1, 31, "LOCKS_GROUP", CONF.LOCKS_GROUP},
+       {CAT_STR, CAF_NONE, 1, 3, "LOCKS_MODE", CONF.LOCKS_MODE},
        {CAT_STR, CAF_NONE, 1, 255, "PID_PATH", CONF.PID_PATH},
        {CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
        {CAT_STR, CAF_NONE, 1, 255, "GROUPS", CONF.GROUPS},
@@ -340,7 +366,10 @@ int main(int argc, char **argv)
 
     /* Set harmless defaults */
     *CONF.CHROOT_DIR = '\0';
-    (void) mm_strcpy(CONF.LOCK_PATH, "/var/run/mmspawnd.lock");
+    (void) mm_strcpy(CONF.LOCKS_PATH, "/var/run/mmspawnd.lock");
+    (void) mm_strcpy(CONF.LOCKS_USER, "mmspawnd");
+    (void) mm_strcpy(CONF.LOCKS_GROUP, "mmspawnd");
+    (void) mm_strcpy(CONF.LOCKS_MODE, "400");
     (void) mm_strcpy(CONF.PID_PATH, "/var/run/mmspawnd.pid");
     (void) mm_strcpy(CONF.USER, "mmspawnd");
     (void) mm_strcpy(CONF.GROUPS, "mmspawnd");
@@ -409,6 +438,7 @@ int main(int argc, char **argv)
                       CONF.GROUPS);
        exit(EXIT_FAILURE);
     }
+    /* Initialize alock */
     if (*CONF.ALOCK_PATH != '\0') {
        uid_t u;
        gid_t g;
@@ -442,6 +472,29 @@ int main(int argc, char **argv)
        }
     } else
        server.alock = -1;
+    /* Initialize shlocks */
+    if (CONF.SHLOCKS_PATH != '\0') { /* Actually should always be true */
+       uid_t u;
+       gid_t g;
+       mode_t m;
+
+       if ((u = mmgetuid(CONF.LOCKS_USER)) == -1) {
+           (void) fprintf(stderr, "\nUnknown LOCKS_USER '%s'\n\n",
+                          CONF.ALOCK_USER);
+           exit(EXIT_FAILURE);
+       }
+       if ((g = mmgetgid(CONF.LOCKS_GROUP)) == -1) {
+           (void) fprintf(stderr, "\nUnknown LOCKS_GROUP '%s'\n\n",
+                          CONF.ALOCK_GROUP);
+           exit(EXIT_FAILURE);
+       }
+       (void) sscanf(CONF.LOCKS_MODE, "%o", &m);
+       if (m == 0 || m > 0770) {
+           (void) fprintf(stderr, "\nIllegal LOCKS_MODE '%03o'\n\n", m);
+           exit(EXIT_FAILURE);
+       }
+       /* XXX */
+    }
     if ((ifaces = ifaces_parse(CONF.LISTEN_ADDRESSES)) == NULL) {
        (void) fprintf(stderr, "\nInvalid LISTEN_ADDRESSES\n\n");
        exit(EXIT_FAILURE);
@@ -506,6 +559,9 @@ MAX_ARGUMENTS)\n\n");
     /* Create our locks for shared memory synchronization. These lock files
      * (LOCK_PATH) should also be available under the new root since they
      * need to be reopened by our children.
+     * XXX Should be done after chroot(2), with proper permissions so that
+     * we may reopen the files in children processes, Moreover, the
+     * lock_check() lock path should be independent (since outside the chroot)?
      */
     if (!shlocks_init(CONF.LOCK_PATH, locks, SHLOCK_MAX, TRUE)) {
        syslog(LOG_NOTICE, "main() - shlocks_init()");
@@ -600,7 +656,7 @@ MAX_ARGUMENTS)\n\n");
      * Note that the following function also causes the child to become
      * unprivileged.
      */
-    if (launch_ipaddr_expire_process(uid, gids, ngids) < 1) {
+    if (launch_ipaddr_expire_process(uid, gids, ngids, CONF.CHROOT_PATH) < 1) {
        syslog(LOG_NOTICE, "main() - launch_ipaddr_expire_process()");
        exit(EXIT_FAILURE);
     }
@@ -768,7 +824,7 @@ listening for connections on port %d. Service is '%s'.",
 
                            /* We create our own process group so that the
                             * process after execve(2) could not kill(0)
-                            * causing the listerner process to exit.
+                            * causing the listener process to exit.
                             * It also allows us to optionally not interrupt
                             * currently served client when we restart.
                             */
@@ -791,7 +847,8 @@ listening for connections on port %d. Service is '%s'.",
                            (void) sigaction(SIGPIPE, &act, NULL);
 
                            /* Close bound sockets which this process shouldn't
-                            * have access to
+                            * have access to. XXX We could use fcntl(3) to
+                            * change socket inheritance at execve(2)...
                             */
                            for (tif = ifaces; *(tif->address); tif++) {
                                if (tif->sock != -1) {
@@ -811,7 +868,7 @@ listening for connections on port %d. Service is '%s'.",
                            /* sock always >2 */
                            (void) close(sock);
 
-                           /* Let children know and remember these */
+                           /* Let child know and remember these */
                            client.ipn = ipn;
                            client.saddr = addr;
                            client.ipaddr = (const char *)ipaddr;
@@ -828,12 +885,12 @@ listening for connections on port %d. Service is '%s'.",
                            /* We do not need to hold the lock files any
                             * longer, and in fact we do not want to let
                             * the process access them, especially if the
-                            * adminstrator sets up the service to not kill
+                            * administrator sets up the service to not kill
                             * currently running children if the service is
                             * shut down.
                             */
-                           shlocks_destroy(CONF.LOCK_PATH, locks,
-                                   (enum shlocks)SHLOCK_MAX, FALSE);
+                           (void) shlocks_destroy(CONF.LOCK_PATH, locks,
+                                       (enum shlocks)SHLOCK_MAX, FALSE);
                            (void) close(server.runlock);
                            (void) close(server.alock);
 
@@ -1426,45 +1483,6 @@ static void ipaddr_close(struct ipaddr_node *ipn)
 }
 
 
-/* Launches the ipaddress-hostname-rate cache entry expiration process,
- * a kind of asynchroneous garbage collector.
- */
-static pid_t launch_ipaddr_expire_process(uid_t uid, gid_t *gids, int ngids)
-{
-    pid_t pid = -1;
-
-    if ((pid = fork()) == 0) {
-       struct sigaction act;
-
-       /* Reset default signal action */
-       act.sa_handler = SIG_DFL;
-       act.sa_flags = SA_NOCLDSTOP;
-       (void) sigemptyset(&act.sa_mask);
-
-       (void) sigaction(SIGTERM, &act, NULL);
-       (void) sigaction(SIGINT, &act, NULL);
-       (void) sigaction(SIGCHLD, &act, NULL);
-       (void) sigaction(SIGSEGV, &act, NULL);
-
-       /* Release runlock which only our parent should hold */
-       (void) close(server.runlock);
-
-       /* Drop privileges */
-       if (!mmdropprivs(uid, gids, ngids)) {
-           syslog(LOG_NOTICE, "launch_ipaddr_expire_process() - Cannot \
-change uid and gids to safe privs");
-           (void) kill(0, SIGTERM);
-           (void) exit(EXIT_FAILURE);
-       }
-       ipaddr_expire_process();
-       /* NOTREACHED */
-    }
-
-    /* Parent */
-    return pid;
-}
-
-
 /* A quick hashtable_hash() and memcmp() replacements which already deal with
  * unique 32-bit values.
  */
@@ -1498,10 +1516,93 @@ static int key_pidcmp(const void *src, const void *dst, size_t len)
 }
 
 
-/* This process can run independantly and manage the ipaddr cache entry
+/* Useful function for use by launch_server_process() and
+ * launch_ipaddress_expire_process() initialization
+ */
+void launch_server_child_init(const char *func, uid_t uid, gid_t *gids,
+       int ngids)
+{
+    /* Reopen lock files as needed for flock(2) */
+    if (shlocks_reopen(CONF.LOCK_PATH, locks, SHLOCK_MAX) == -1) {
+       syslog(LOG_NOTICE, "%s - Could not reopen lock files - %s",
+               func, strerror(errno));
+       (void) kill(getppid(), SIGTERM);
+       exit(EXIT_FAILURE);
+    }
+
+    /* chroot(2) if necessary */
+    if (CONF.CHROOT_PATH != '\0') {
+       if ((chroot(chroot_path)) == -1) {
+           syslog(LOG_NOTICE, "%s - Could not chroot(2) to '%s' - %s",
+                   func, CONF.CHROOT_PATH, strerror(errno));
+           (void) kill(getppid(), SIGTERM);
+           exit(EXIT_FAILURE);
+       }
+       (void) chdir("/");
+    }
+
+    /* Drop privileges */
+    if (!mmdropprivs(uid, gids, ngids)) {
+       syslog(LOG_NOTICE, "%s - Cannot change uid and gids to safe privs",
+               func);
+       (void) kill(getppid(), SIGTERM);
+       (void) exit(EXIT_FAILURE);
+    }
+}
+
+
+/* Launches the main server program, listening for connections and dispatching
+ * them to children processes.
+ */
+static pid_t launch_server_process(uid_t uid, gid_t *gids, int ngids)
+{
+}
+
+static void server_process_main(void)
+{
+}
+
+
+/* Launches the ipaddress-hostname-rate cache entry expiration process,
+ * a kind of asynchroneous garbage collector.
+ */
+static pid_t launch_ipaddr_expire_process(uid_t uid, gid_t *gids, int ngids)
+{
+    pid_t pid = -1;
+
+    if ((pid = fork()) == 0) {
+       struct sigaction act;
+
+       /* Reset default signal action */
+       act.sa_handler = SIG_DFL;
+       act.sa_flags = SA_NOCLDSTOP;
+       (void) sigemptyset(&act.sa_mask);
+
+       (void) sigaction(SIGTERM, &act, NULL);
+       (void) sigaction(SIGINT, &act, NULL);
+       (void) sigaction(SIGCHLD, &act, NULL);
+       (void) sigaction(SIGSEGV, &act, NULL);
+
+       /* Release runlock which only our parent should hold */
+       (void) close(server.runlock);
+
+       /* Will reopen lock files, chroot(2) and drop privileges */
+       launch_server_child_init("launch_ipaddr_expire_process()",
+               uid, gids, ngids);
+
+       /* Finally lanch main process loop */
+       ipaddr_expire_process_main();
+       /* NOTREACHED */
+    }
+
+    /* Parent */
+    return pid;
+}
+
+/* This process can run independently and manage the ipaddr cache entry
  * expiration events.
  */
-static void ipaddr_expire_process(void)
+static void ipaddr_expire_process_main(void)
 {
     struct ipaddr_expire_process_iterator_udata data;
 
@@ -1509,9 +1610,6 @@ static void ipaddr_expire_process(void)
     setproctitle("%s Cache service process", CONF.PROCTITLE);
 #endif /* __GLIBC__ */
 
-    /* Reopen lock files as needed for flock(2) */
-    (void) shlocks_init(CONF.LOCK_PATH, locks, SHLOCK_MAX, FALSE);
-
     /* Set initial timeout to maximum allowed */
     data.soonest = CONF.CONNECTION_PERIOD;
     for (;;) {
@@ -1539,7 +1637,6 @@ static void ipaddr_expire_process(void)
     /* NOTREACHED */
 }
 
-
 /* Internally used by the cache events expiration process */
 static bool ipaddr_expire_process_iterator(hashnode_t *hnod, void *udata)
 {