New --managed option for use when running under a daemon supervisor.
authorRichard Kettlewell <rjk@terraraq.org.uk>
Sat, 10 Dec 2011 22:35:47 +0000 (22:35 +0000)
committerRichard Kettlewell <rjk@terraraq.org.uk>
Sat, 10 Dec 2011 22:35:47 +0000 (22:35 +0000)
The effect is secnet is told that it is running as a daemon right from
the start, so it knows to follow the logging rules for daemons but not
to fork.

The conflation of daemonization with dropping privilege is also
unpicked by this patch.  Most importantly this ensures that errors
from PHASE_GETRESOURCES operations such as 'route' commands is sent to
the logfile (or syslog).

Signed-off-by: Richard Kettlewell <rjk@terraraq.org.uk>
log.c
secnet.c
secnet.h
uk.org.greenend.secnet.plist
util.c

diff --git a/log.c b/log.c
index 6f4b738..3a50a27 100644 (file)
--- a/log.c
+++ b/log.c
@@ -22,7 +22,7 @@ static void vMessage(uint32_t class, const char *message, va_list args)
     size_t bp;
     char *nlp;
 
-    if (secnet_is_daemon) {
+    if (system_log) {
        /* Messages go to the system log interface */
        bp=strlen(buff);
        assert(bp < MESSAGE_BUFLEN);
index 7759834..465a93f 100644 (file)
--- a/secnet.c
+++ b/secnet.c
@@ -62,6 +62,7 @@ static void parse_options(int argc, char **argv)
            {"help", 0, 0, 2},
            {"version", 0, 0, 1},
            {"nodetach", 0, 0, 'n'},
+           {"managed", 0, 0, 'm'},
            {"silent", 0, 0, 'f'},
            {"quiet", 0, 0, 'f'},
            {"debug", 0, 0, 'd'},
@@ -71,7 +72,7 @@ static void parse_options(int argc, char **argv)
            {0,0,0,0}
        };
 
-       c=getopt_long(argc, argv, "vwdnjc:ft:s:",
+       c=getopt_long(argc, argv, "vwdnjc:ft:s:m",
                      long_options, &option_index);
        if (c==-1)
            break;
@@ -89,6 +90,7 @@ static void parse_options(int argc, char **argv)
                   "  -s, --sites-key=name    configuration key that "
                   "specifies active sites\n"
                   "  -n, --nodetach          do not run in background\n"
+                  "  -m, --managed           running under a supervisor\n"
                   "  -d, --debug             output debug messages\n"
                   "      --help              display this help and exit\n"
                   "      --version           output version information "
@@ -124,6 +126,10 @@ static void parse_options(int argc, char **argv)
            background=False;
            break;
 
+       case 'm':
+           secnet_is_daemon=True;
+           break;
+
        case 'c':
            if (optarg)
                configfile=safe_strdup(optarg,"config_filename");
@@ -184,7 +190,7 @@ static void setup(dict_t *config)
     /* Who are we supposed to run as? */
     userid=dict_read_string(system,"userid",False,"system",loc);
     if (userid) {
-       if(!(pw=getpwnam(userid)))
+       if (!(pw=getpwnam(userid)))
            fatal("userid \"%s\" not found",userid);
        uid=pw->pw_uid;
        gid=pw->pw_gid;
@@ -349,32 +355,13 @@ static void run(void)
     free(fds);
 }
 
+/* Surrender privileges, if necessary */
 static void droppriv(void)
 {
-    FILE *pf=NULL;
-    pid_t p;
-    int errfds[2];
-
-    add_hook(PHASE_SHUTDOWN,system_phase_hook,NULL);
-
-    /* Open the pidfile for writing now: we may be unable to do so
-       once we drop privileges. */
-    if (pidfile) {
-       pf=fopen(pidfile,"w");
-       if (!pf) {
-           fatal_perror("cannot open pidfile \"%s\"",pidfile);
-       }
-    }
-    if (!background && pf) {
-       fprintf(pf,"%d\n",getpid());
-       fclose(pf);
-    }
-
-    /* Now drop privileges */
     if (userid) {
        if (setgid(gid)!=0)
            fatal_perror("can't set gid to %ld",(long)gid);
-       if(initgroups(userid, gid) < 0)
+       if (initgroups(userid, gid) < 0)
            fatal_perror("initgroups"); 
        if (setuid(uid)!=0) {
            fatal_perror("can't set uid to \"%s\"",userid);
@@ -384,39 +371,60 @@ static void droppriv(void)
        assert(getgid() == gid);
        assert(getegid() == gid);
     }
-    if (background) {
+}
+
+/* Become a daemon, if necessary */
+static void become_daemon(void)
+{
+    FILE *pf=NULL;
+    pid_t p;
+    int errfds[2];
+
+    add_hook(PHASE_SHUTDOWN,system_phase_hook,NULL);
+
+    /* We only want to become a daemon if we are not one
+     already */
+    if (background && !secnet_is_daemon) {
        p=fork();
        if (p>0) {
-           if (pf) {
-               /* Parent process - write pidfile, exit */
-               fprintf(pf,"%d\n",p);
-               fclose(pf);
-           }
-           exit(0);
+           /* Parent process - just exit */
+           _exit(0);
        } else if (p==0) {
            /* Child process - all done, just carry on */
-           if (pf) fclose(pf);
-           /* Close stdin and stdout; we don't need them any more.
-               stderr is redirected to the system/log facility */
-           if (pipe(errfds)!=0) {
-               fatal_perror("can't create pipe for stderr");
-           }
-           close(0);
-           close(1);
-           close(2);
-           dup2(errfds[1],0);
-           dup2(errfds[1],1);
-           dup2(errfds[1],2);
            secnet_is_daemon=True;
-           setsid();
-           log_from_fd(errfds[0],"stderr",system_log);
+           if (setsid() < 0)
+               fatal_perror("setsid");
        } else {
            /* Error */
            fatal_perror("cannot fork");
            exit(1);
        }
     }
+    if (secnet_is_daemon) {
+       /* stderr etc are redirected to the system/log facility */
+       if (pipe(errfds)!=0) {
+           fatal_perror("can't create pipe for stderr");
+       }
+       if (dup2(errfds[1],0) < 0
+           || dup2(errfds[1],1) < 0
+           || dup2(errfds[1],2) < 0)
+           fatal_perror("can't dup2 pipe");
+       if (close(errfds[1]) < 0)
+           fatal_perror("can't close redundant pipe endpoint");
+       log_from_fd(errfds[0],"stderr",system_log);
+    }
     secnet_pid=getpid();
+    
+    /* Now we can write the pidfile */
+    if (pidfile) {
+       pf=fopen(pidfile,"w");
+       if (!pf) {
+           fatal_perror("cannot open pidfile \"%s\"",pidfile);
+       }
+       if (fprintf(pf,"%ld\n",(long)secnet_pid) < 0
+           || fclose(pf) < 0)
+           fatal_perror("cannot write to pidfile \"%s\"",pidfile);
+    }
 }
 
 static signal_notify_fn finish,ignore_hup;
@@ -449,6 +457,9 @@ int main(int argc, char **argv)
        exit(0);
     }
 
+    enter_phase(PHASE_DAEMONIZE);
+    become_daemon();
+    
     enter_phase(PHASE_GETRESOURCES);
     /* Appropriate phase hooks will have been run */
     
index e7eb161..06beb05 100644 (file)
--- a/secnet.h
+++ b/secnet.h
@@ -184,13 +184,17 @@ extern void register_for_poll(void *st, beforepoll_fn *before,
 
 /* The secnet program goes through a number of phases in its lifetime.
    Module code may arrange to be called just as various phases are
-   entered. */
+   entered.
+   Remember to update the table in util.c if changing the set of
+   phases. */
 
 enum phase {
     PHASE_INIT,
     PHASE_GETOPTS,             /* Process command-line arguments */
     PHASE_READCONFIG,          /* Parse and process configuration file */
     PHASE_SETUP,               /* Process information in configuration */
+    PHASE_DAEMONIZE,           /* Become a daemon (if necessary) */
     PHASE_GETRESOURCES,        /* Obtain all external resources */
     PHASE_DROPPRIV,            /* Last chance for privileged operations */
     PHASE_RUN,
index f80184b..1de6ca4 100644 (file)
@@ -14,7 +14,7 @@
        <key>ProgramArguments</key>
        <array>
                <string>/usr/local/sbin/secnet</string>
-               <string>-n</string>
+               <string>-m</string>
        </array>
        <key>WorkingDirectory</key>
        <string>/</string>
diff --git a/util.c b/util.c
index fff5b6d..63fe76f 100644 (file)
--- a/util.c
+++ b/util.c
@@ -166,6 +166,7 @@ static const char *phases[NR_PHASES]={
     "PHASE_GETOPTS",
     "PHASE_READCONFIG",
     "PHASE_SETUP",
+    "PHASE_DAEMONIZE",
     "PHASE_GETRESOURCES",
     "PHASE_DROPPRIV",
     "PHASE_RUN",