chiark / gitweb /
REORG Delete everything that's not innduct or build system or changed for innduct
[innduct.git] / innd / inndstart.c
diff --git a/innd/inndstart.c b/innd/inndstart.c
deleted file mode 100644 (file)
index 56ac6e6..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-/*  $Id: inndstart.c 7749 2008-04-06 14:15:04Z iulius $
-**
-**  Open the privileged port, then exec innd.
-**
-**  inndstart, in a normal INN installation, is installed setuid root and
-**  executable only by users in the news group.  Because it is setuid root,
-**  it's very important to ensure that it be as simple and secure as
-**  possible so that news access can't be leveraged into root access.
-**
-**  Fighting against this desire, as much of INN's operation as possible
-**  should be configurable at run-time using inn.conf, and the news system
-**  should be able to an alternate inn.conf by setting INNCONF to the path
-**  to that file before starting any programs.  The configuration data
-**  therefore can't be trusted.
-**
-**  Our security model is therefore:
-**
-**   - The only three operations performed while privileged are determining
-**     the UID and GID of NEWSUSER and NEWSGRP, setting system limits, and
-**     opening the privileged port we're binding to.
-**
-**   - We can only be executed by the NEWSUSER and NEWSGRP, both compile-
-**     time constants; otherwise, we exit.  Similarly, we will only setuid()
-**     to the NEWSUSER.  This is to prevent someone other than the NEWSUSER
-**     but still able to execute inndstart for whatever reason from using it
-**     to run innd as the news user with bogus configuration information,
-**     thereby possibly compromising the news account.
-**
-**   - The only ports < 1024 that we'll bind to are 119 and 433, or a port
-**     given at configure time with --with-innd-port.  This is to prevent
-**     the news user from taking over a service such as telnet or POP and
-**     potentially gaining access to user passwords.
-**
-**  This program therefore gives the news user the ability to revoke system
-**  file descriptor limits and bind to the news port, and nothing else.
-**
-**  Note that we do use getpwnam() to determine the UID of NEWSUSER, which
-**  potentially opens an exploitable hole on those systems that don't
-**  correctly prevent a user running a setuid program from interfering with
-**  the running process (replacing system calls, for example, or using
-**  things like LD_PRELOAD).
-*/
-
-#include "config.h"
-#include "clibrary.h"
-#include "portable/socket.h"
-#include <errno.h>
-#include <fcntl.h>
-#include <grp.h>
-#include <pwd.h>
-#include <syslog.h>
-
-#ifdef HAVE_INET6
-# include <netdb.h>
-#endif
-
-#include "inn/innconf.h"
-#include "inn/messages.h"
-#include "libinn.h"
-#include "paths.h"
-
-/* Fake up a do-nothing setgroups for Cygwin. */
-#if !HAVE_SETGROUPS
-# define setgroups(n, list)     0
-#endif
-
-/* To run innd under the debugger, uncomment this and fix the path. */
-/* #define DEBUGGER "/usr/ucb/dbx" */
-
-
-int
-main(int argc, char *argv[])
-{
-    struct passwd *pwd;
-    struct group *grp;
-    uid_t news_uid, real_uid;
-    gid_t news_gid;
-    int snum = 0;
-    int port, s[MAX_SOCKETS + 1], i, j;
-#ifdef HAVE_INET6
-    struct in6_addr address6;
-    bool addr6_specified = false;
-#endif
-    struct in_addr address;
-    bool addr_specified = false;
-    char *p;
-    char **innd_argv;
-    char pflag[SMBUF];
-#ifdef PURIFY
-    char *innd_env[11];
-#else
-    char *innd_env[9];
-#endif
-
-    /* Set up the error handlers.  Always print to stderr, and for warnings
-       also syslog with a priority of LOG_ERR.  For fatal errors, also
-       syslog with a priority of LOG_CRIT.  These priority levels are a
-       little high, but they're chosen to match innd. */
-    openlog("inndstart", LOG_CONS, LOG_INN_PROG);
-    message_handlers_warn(2, message_log_stderr, message_log_syslog_err);
-    message_handlers_die(2, message_log_stderr, message_log_syslog_crit);
-    message_program_name = "inndstart";
-
-    /* Convert NEWSUSER and NEWSGRP to a UID and GID.  getpwnam() and
-       getgrnam() don't set errno normally, so don't print strerror() on
-       failure; it probably contains garbage.*/
-    pwd = getpwnam(NEWSUSER);
-    if (!pwd)
-        die("can't getpwnam(%s)", NEWSUSER);
-    news_uid = pwd->pw_uid;
-    grp = getgrnam(NEWSGRP);
-    if (!grp)
-        die("can't getgrnam(%s)", NEWSGRP);
-    news_gid = grp->gr_gid;
-
-    /* Exit if run by any other user or group. */
-    real_uid = getuid();
-    if (real_uid != news_uid)
-        die("must be run by user %s (%lu), not %lu", NEWSUSER,
-                (unsigned long)news_uid, (unsigned long)real_uid);
-
-    /* Drop all supplemental groups and drop privileges to read inn.conf.
-       setgroups() can only be invoked by root, so if inndstart isn't setuid
-       root this is where we fail. */
-    if (setgroups(1, &news_gid) < 0)
-        syswarn("can't setgroups (is inndstart setuid root?)");
-    if (seteuid(news_uid) < 0)
-        sysdie("can't seteuid to %lu", (unsigned long)news_uid);
-    if (!innconf_read(NULL))
-        exit(1);
-
-    /* Check for a bind address specified in inn.conf.  "any" or "all" will
-       cause inndstart to bind to INADDR_ANY. */
-    address.s_addr = htonl(INADDR_ANY);
-    p = innconf->bindaddress;
-    if (p && strcmp(p, "all") != 0 && strcmp(p, "any") != 0) {
-        if (!inet_aton(p, &address))
-            die("invalid bindaddress in inn.conf (%s)", p);
-        addr_specified = true;
-    }
-#ifdef HAVE_INET6
-    address6 = in6addr_any;
-    p = innconf->bindaddress6;
-    if (p && strcmp(p, "all") != 0 && strcmp(p, "any") != 0) {
-       if (inet_pton(AF_INET6, p, &address6) < 1)
-           die("invalid bindaddress6 in inn.conf (%s)", p);
-        addr6_specified = true;
-    }
-#endif
-
-    /* Parse our command-line options.  The only options we take are -P,
-       which specifies what port number to bind to, and -I, which specifies
-       what IP address to bind to.  Both override inn.conf.  Support both
-       "-P <port>" and "-P<port>".  All other options are passed through to
-       innd. */
-    port = innconf->port;
-    for (i = 1; i < argc; i++) {
-        if (strncmp("-P", argv[i], 2) == 0) {
-            if (strlen(argv[i]) > 2) {
-                port = atoi(&argv[i][2]);
-            } else {
-                i++;
-                if (argv[i] == NULL)
-                    die("missing port after -P");
-                port = atoi(argv[i]);
-            }
-            if (port == 0)
-                die("invalid port %s (must be a number)", argv[i]);
-#ifdef HAVE_INET6
-        } else if (strncmp("-6", argv[i], 2) == 0) {
-            if (strlen(argv[i]) > 2) {
-                p = &argv[i][2];
-            } else {
-                i++;
-                if (argv[i] == NULL)
-                    die("missing address after -6");
-                p = argv[i];
-            }
-            if (inet_pton(AF_INET6, p, &address6) < 1)
-                die("invalid address %s", p);
-           addr6_specified = true;
-#endif
-        } else if (strncmp("-I", argv[i], 2) == 0) {
-            if (strlen(argv[i]) > 2) {
-                p = &argv[i][2];
-            } else {
-                i++;
-                if (argv[i] == NULL)
-                    die("missing address after -I");
-                p = argv[i];
-            }
-            if (!inet_aton(p, &address))
-                die("invalid address %s", p);
-           addr_specified = true;
-        }
-    }
-            
-    /* Make sure that the requested port is legitimate. */
-    if (port < 1024 && port != 119
-#ifdef INND_PORT
-        && port != INND_PORT
-#endif
-        && port != 433)
-        die("can't bind to restricted port %d", port);
-
-    /* Now, regain privileges so that we can change system limits and bind
-       to our desired port. */
-    if (seteuid(0) < 0)
-        sysdie("can't seteuid to 0");
-
-    /* innconf->rlimitnofile <= 0 says to leave it alone. */
-    if (innconf->rlimitnofile > 0 && setfdlimit(innconf->rlimitnofile) < 0)
-        syswarn("can't set file descriptor limit to %ld",
-                innconf->rlimitnofile);
-
-#if defined(HAVE_INET6) && defined(IPV6_V6ONLY)
-    /* If we have the IPV6_V6ONLY socket option, and it works,
-       always open separate IPv4 and IPv6 sockets. */
-    if (addr_specified == 0 && addr6_specified == 0) {
-       j = socket(PF_INET6, SOCK_STREAM, 0);
-       if (j >= 0) {
-           i = 1;
-            if (setsockopt (j, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i,
-                            sizeof i) == 0) {
-               addr_specified = 1;
-               addr6_specified = 1;
-           }
-           close (j);
-       }
-    }
-#endif
-
-    /* Create a socket and name it. */
-#ifdef HAVE_INET6
-    if( ! (addr_specified || addr6_specified) ) {
-       struct addrinfo hints, *addr, *ressave;
-       char service[16];
-       int error;
-
-       memset(&hints, 0, sizeof hints);
-       hints.ai_family = PF_UNSPEC;
-       hints.ai_flags = AI_PASSIVE;
-       hints.ai_socktype = SOCK_STREAM;
-       snprintf(service, sizeof(service), "%d", port);
-       error = getaddrinfo(NULL, service, &hints, &addr);
-       if (error < 0)
-           die("getaddrinfo: %s", gai_strerror(error));
-
-       for (ressave = addr; addr; addr = addr->ai_next) {
-           if ((i = socket(addr->ai_family, addr->ai_socktype,
-                           addr->ai_protocol)) < 0)
-               continue; /* ignore */
-#ifdef SO_REUSEADDR
-           j = 1;
-           if (setsockopt(i, SOL_SOCKET, SO_REUSEADDR, (char *)&j,
-                       sizeof j) < 0)
-               syswarn("can't set SO_REUSEADDR");
-#endif
-           if (bind(i, addr->ai_addr, addr->ai_addrlen) < 0) {
-               j = errno;
-               close(i);
-               errno = j;
-               continue; /* ignore */
-           }
-           s[snum++] = i;
-           if (snum == MAX_SOCKETS)
-               break;
-       }
-       freeaddrinfo(ressave);
-
-       if (snum == 0)
-           sysdie("can't bind socket");
-    } else {
-       if ( addr6_specified ) {
-           struct sockaddr_in6 server6;
-
-           s[snum] = socket(PF_INET6, SOCK_STREAM, 0);
-           if (s[snum] < 0)
-               sysdie("can't open inet6 socket");
-#ifdef SO_REUSEADDR
-           i = 1;
-           if (setsockopt(s[snum], SOL_SOCKET, SO_REUSEADDR, (char *)&i,
-                       sizeof i) < 0)
-               syswarn("can't set SO_REUSEADDR");
-#endif
-#ifdef IPV6_V6ONLY
-           i = 1;
-            if (setsockopt(s[snum], IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i,
-                       sizeof i) < 0)
-               syswarn("can't set IPV6_V6ONLY");
-#endif
-           memset(&server6, 0, sizeof server6);
-           server6.sin6_port = htons(port);
-           server6.sin6_family = AF_INET6;
-           server6.sin6_addr = address6;
-#ifdef HAVE_SOCKADDR_LEN
-           server6.sin6_len = sizeof server6;
-#endif
-           if (bind(s[snum], (struct sockaddr *)&server6, sizeof server6) < 0)
-               sysdie("can't bind inet6 socket");
-           snum++;
-       }
-       if ( addr_specified )
-#endif /* HAVE_INET6 */
-       {
-           struct sockaddr_in server;
-
-           s[snum] = socket(PF_INET, SOCK_STREAM, 0);
-           if (s[snum] < 0)
-               sysdie("can't open inet socket");
-#ifdef SO_REUSEADDR
-           i = 1;
-           if (setsockopt(s[snum], SOL_SOCKET, SO_REUSEADDR, (char *) &i,
-                       sizeof i) < 0)
-               syswarn("can't set SO_REUSEADDR");
-#endif
-           memset(&server, 0, sizeof server);
-           server.sin_port = htons(port);
-           server.sin_family = AF_INET;
-#ifdef HAVE_SOCKADDR_LEN
-           server.sin_len = sizeof server;
-#endif
-           server.sin_addr = address;
-           if (bind(s[snum], (struct sockaddr *)&server, sizeof server) < 0)
-               sysdie("can't bind inet socket");
-           snum++;
-       }
-#ifdef HAVE_INET6
-    }
-#endif
-    s[snum] = -1;
-
-    /* Now, permanently drop privileges. */
-    if (setgid(news_gid) < 0 || getgid() != news_gid)
-        sysdie("can't setgid to %lu", (unsigned long)news_gid);
-    if (setuid(news_uid) < 0 || getuid() != news_uid)
-        sysdie("can't setuid to %lu", (unsigned long)news_uid);
-
-    /* Build the argument vector for innd.  Pass -p<port> to innd to tell it
-       what port we just created and bound to for it. */
-    innd_argv = xmalloc((1 + argc + 1) * sizeof(char *));
-    i = 0;
-    strlcpy(pflag, "-p ", sizeof(pflag));
-    for (j = 0; s[j] > 0; j++) {
-       char temp[16];
-
-       snprintf(temp, sizeof(temp), "%d,", s[j]);
-       strlcat(pflag, temp, sizeof(pflag));
-    }
-    /* chop off the trailing , */
-    j = strlen(pflag) - 1;
-    pflag[j] = '\0';
-#ifdef DEBUGGER
-    innd_argv[i++] = DEBUGGER;
-    innd_argv[i++] = concatpath(innconf->pathbin, "innd");
-    innd_argv[i] = 0;
-    printf("When starting innd, use -d %s\n", s, pflag);
-#else /* DEBUGGER */
-    innd_argv[i++] = concatpath(innconf->pathbin, "innd");
-    innd_argv[i++] = pflag;
-
-    /* Don't pass along -p, -P, or -I.  Check the length of the argument
-       string, and if it == 2 (meaning there's nothing after the -p or -P or
-       -I), skip the next argument too, to support leaving a space between
-       the argument and the value. */
-    for (j = 1; j < argc; j++) {
-        if (argv[j][0] == '-' && strchr("pP6I", argv[j][1])) {
-            if (strlen(argv[j]) == 2)
-                j++;
-            continue;
-        } else {
-            innd_argv[i++] = argv[j];
-        }
-    }
-    innd_argv[i] = 0;
-#endif /* !DEBUGGER */
-
-    /* Set up the environment.  Note that we're trusting BIND_INADDR and TZ;
-       everything else is either from inn.conf or from configure.  These
-       should be sanity-checked before being propagated, but that requires
-       knowledge of the range of possible values.  Just limiting their
-       length doesn't necessarily do anything to prevent exploits and may
-       stop things from working that should.  We have to pass BIND_INADDR so
-       that it's set for programs, such as innfeed, that innd may spawn. */
-    innd_env[0] = concat("PATH=", innconf->pathbin, ":", innconf->pathetc,
-                         ":/bin:/usr/bin:/usr/ucb", (char *) 0);
-    innd_env[1] = concat( "TMPDIR=", innconf->pathtmp,  (char *) 0);
-    innd_env[2] = concat(  "SHELL=", _PATH_SH,          (char *) 0);
-    innd_env[3] = concat("LOGNAME=", NEWSMASTER,        (char *) 0);
-    innd_env[4] = concat(   "USER=", NEWSMASTER,        (char *) 0);
-    innd_env[5] = concat(   "HOME=", innconf->pathnews, (char *) 0);
-    i = 6;
-    p = getenv("BIND_INADDR");
-    if (p != NULL)
-        innd_env[i++] = concat("BIND_INADDR=", p, (char *) 0);
-    p = getenv("TZ");
-    if (p != NULL)
-        innd_env[i++] = concat("TZ=", p, (char *) 0);
-#ifdef PURIFY
-    /* you have to compile with `purify cc -DPURIFY' to get this */
-    p = getenv("DISPLAY");
-    if (p != NULL)
-        innd_env[i++] = concat("DISPLAY=", p, (char *) 0);
-    p = getenv("PURIFYOPTIONS");
-    if (p != NULL)
-        innd_env[i++] = concat("PURIFYOPTIONS=", p, (char *) 0);
-#endif
-    innd_env[i] = 0;
-
-    /* Go exec innd. */
-    execve(innd_argv[0], innd_argv, innd_env);
-    sysdie("can't exec %s", innd_argv[0]);
-
-    /* Not reached. */
-    return 1;
-}