X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=innduct.git;a=blobdiff_plain;f=innd%2Finndstart.c;fp=innd%2Finndstart.c;h=0000000000000000000000000000000000000000;hp=56ac6e6fb452e1a6db31c76816373e9f0cf8ee7b;hb=b7a32e2d73e3ab1add8208d3e157f7269a31ef4d;hpb=ac902a8299ff4469b356836f431ead31c3377377 diff --git a/innd/inndstart.c b/innd/inndstart.c deleted file mode 100644 index 56ac6e6..0000000 --- a/innd/inndstart.c +++ /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 -#include -#include -#include -#include - -#ifdef HAVE_INET6 -# include -#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 " and "-P". 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 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; -}