1 /* $Id: inndstart.c 7749 2008-04-06 14:15:04Z iulius $
3 ** Open the privileged port, then exec innd.
5 ** inndstart, in a normal INN installation, is installed setuid root and
6 ** executable only by users in the news group. Because it is setuid root,
7 ** it's very important to ensure that it be as simple and secure as
8 ** possible so that news access can't be leveraged into root access.
10 ** Fighting against this desire, as much of INN's operation as possible
11 ** should be configurable at run-time using inn.conf, and the news system
12 ** should be able to an alternate inn.conf by setting INNCONF to the path
13 ** to that file before starting any programs. The configuration data
14 ** therefore can't be trusted.
16 ** Our security model is therefore:
18 ** - The only three operations performed while privileged are determining
19 ** the UID and GID of NEWSUSER and NEWSGRP, setting system limits, and
20 ** opening the privileged port we're binding to.
22 ** - We can only be executed by the NEWSUSER and NEWSGRP, both compile-
23 ** time constants; otherwise, we exit. Similarly, we will only setuid()
24 ** to the NEWSUSER. This is to prevent someone other than the NEWSUSER
25 ** but still able to execute inndstart for whatever reason from using it
26 ** to run innd as the news user with bogus configuration information,
27 ** thereby possibly compromising the news account.
29 ** - The only ports < 1024 that we'll bind to are 119 and 433, or a port
30 ** given at configure time with --with-innd-port. This is to prevent
31 ** the news user from taking over a service such as telnet or POP and
32 ** potentially gaining access to user passwords.
34 ** This program therefore gives the news user the ability to revoke system
35 ** file descriptor limits and bind to the news port, and nothing else.
37 ** Note that we do use getpwnam() to determine the UID of NEWSUSER, which
38 ** potentially opens an exploitable hole on those systems that don't
39 ** correctly prevent a user running a setuid program from interfering with
40 ** the running process (replacing system calls, for example, or using
41 ** things like LD_PRELOAD).
46 #include "portable/socket.h"
57 #include "inn/innconf.h"
58 #include "inn/messages.h"
62 /* Fake up a do-nothing setgroups for Cygwin. */
64 # define setgroups(n, list) 0
67 /* To run innd under the debugger, uncomment this and fix the path. */
68 /* #define DEBUGGER "/usr/ucb/dbx" */
72 main(int argc, char *argv[])
76 uid_t news_uid, real_uid;
79 int port, s[MAX_SOCKETS + 1], i, j;
81 struct in6_addr address6;
82 bool addr6_specified = false;
84 struct in_addr address;
85 bool addr_specified = false;
95 /* Set up the error handlers. Always print to stderr, and for warnings
96 also syslog with a priority of LOG_ERR. For fatal errors, also
97 syslog with a priority of LOG_CRIT. These priority levels are a
98 little high, but they're chosen to match innd. */
99 openlog("inndstart", LOG_CONS, LOG_INN_PROG);
100 message_handlers_warn(2, message_log_stderr, message_log_syslog_err);
101 message_handlers_die(2, message_log_stderr, message_log_syslog_crit);
102 message_program_name = "inndstart";
104 /* Convert NEWSUSER and NEWSGRP to a UID and GID. getpwnam() and
105 getgrnam() don't set errno normally, so don't print strerror() on
106 failure; it probably contains garbage.*/
107 pwd = getpwnam(NEWSUSER);
109 die("can't getpwnam(%s)", NEWSUSER);
110 news_uid = pwd->pw_uid;
111 grp = getgrnam(NEWSGRP);
113 die("can't getgrnam(%s)", NEWSGRP);
114 news_gid = grp->gr_gid;
116 /* Exit if run by any other user or group. */
118 if (real_uid != news_uid)
119 die("must be run by user %s (%lu), not %lu", NEWSUSER,
120 (unsigned long)news_uid, (unsigned long)real_uid);
122 /* Drop all supplemental groups and drop privileges to read inn.conf.
123 setgroups() can only be invoked by root, so if inndstart isn't setuid
124 root this is where we fail. */
125 if (setgroups(1, &news_gid) < 0)
126 syswarn("can't setgroups (is inndstart setuid root?)");
127 if (seteuid(news_uid) < 0)
128 sysdie("can't seteuid to %lu", (unsigned long)news_uid);
129 if (!innconf_read(NULL))
132 /* Check for a bind address specified in inn.conf. "any" or "all" will
133 cause inndstart to bind to INADDR_ANY. */
134 address.s_addr = htonl(INADDR_ANY);
135 p = innconf->bindaddress;
136 if (p && strcmp(p, "all") != 0 && strcmp(p, "any") != 0) {
137 if (!inet_aton(p, &address))
138 die("invalid bindaddress in inn.conf (%s)", p);
139 addr_specified = true;
142 address6 = in6addr_any;
143 p = innconf->bindaddress6;
144 if (p && strcmp(p, "all") != 0 && strcmp(p, "any") != 0) {
145 if (inet_pton(AF_INET6, p, &address6) < 1)
146 die("invalid bindaddress6 in inn.conf (%s)", p);
147 addr6_specified = true;
151 /* Parse our command-line options. The only options we take are -P,
152 which specifies what port number to bind to, and -I, which specifies
153 what IP address to bind to. Both override inn.conf. Support both
154 "-P <port>" and "-P<port>". All other options are passed through to
156 port = innconf->port;
157 for (i = 1; i < argc; i++) {
158 if (strncmp("-P", argv[i], 2) == 0) {
159 if (strlen(argv[i]) > 2) {
160 port = atoi(&argv[i][2]);
164 die("missing port after -P");
165 port = atoi(argv[i]);
168 die("invalid port %s (must be a number)", argv[i]);
170 } else if (strncmp("-6", argv[i], 2) == 0) {
171 if (strlen(argv[i]) > 2) {
176 die("missing address after -6");
179 if (inet_pton(AF_INET6, p, &address6) < 1)
180 die("invalid address %s", p);
181 addr6_specified = true;
183 } else if (strncmp("-I", argv[i], 2) == 0) {
184 if (strlen(argv[i]) > 2) {
189 die("missing address after -I");
192 if (!inet_aton(p, &address))
193 die("invalid address %s", p);
194 addr_specified = true;
198 /* Make sure that the requested port is legitimate. */
199 if (port < 1024 && port != 119
204 die("can't bind to restricted port %d", port);
206 /* Now, regain privileges so that we can change system limits and bind
207 to our desired port. */
209 sysdie("can't seteuid to 0");
211 /* innconf->rlimitnofile <= 0 says to leave it alone. */
212 if (innconf->rlimitnofile > 0 && setfdlimit(innconf->rlimitnofile) < 0)
213 syswarn("can't set file descriptor limit to %ld",
214 innconf->rlimitnofile);
216 #if defined(HAVE_INET6) && defined(IPV6_V6ONLY)
217 /* If we have the IPV6_V6ONLY socket option, and it works,
218 always open separate IPv4 and IPv6 sockets. */
219 if (addr_specified == 0 && addr6_specified == 0) {
220 j = socket(PF_INET6, SOCK_STREAM, 0);
223 if (setsockopt (j, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i,
233 /* Create a socket and name it. */
235 if( ! (addr_specified || addr6_specified) ) {
236 struct addrinfo hints, *addr, *ressave;
240 memset(&hints, 0, sizeof hints);
241 hints.ai_family = PF_UNSPEC;
242 hints.ai_flags = AI_PASSIVE;
243 hints.ai_socktype = SOCK_STREAM;
244 snprintf(service, sizeof(service), "%d", port);
245 error = getaddrinfo(NULL, service, &hints, &addr);
247 die("getaddrinfo: %s", gai_strerror(error));
249 for (ressave = addr; addr; addr = addr->ai_next) {
250 if ((i = socket(addr->ai_family, addr->ai_socktype,
251 addr->ai_protocol)) < 0)
252 continue; /* ignore */
255 if (setsockopt(i, SOL_SOCKET, SO_REUSEADDR, (char *)&j,
257 syswarn("can't set SO_REUSEADDR");
259 if (bind(i, addr->ai_addr, addr->ai_addrlen) < 0) {
263 continue; /* ignore */
266 if (snum == MAX_SOCKETS)
269 freeaddrinfo(ressave);
272 sysdie("can't bind socket");
274 if ( addr6_specified ) {
275 struct sockaddr_in6 server6;
277 s[snum] = socket(PF_INET6, SOCK_STREAM, 0);
279 sysdie("can't open inet6 socket");
282 if (setsockopt(s[snum], SOL_SOCKET, SO_REUSEADDR, (char *)&i,
284 syswarn("can't set SO_REUSEADDR");
288 if (setsockopt(s[snum], IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i,
290 syswarn("can't set IPV6_V6ONLY");
292 memset(&server6, 0, sizeof server6);
293 server6.sin6_port = htons(port);
294 server6.sin6_family = AF_INET6;
295 server6.sin6_addr = address6;
296 #ifdef HAVE_SOCKADDR_LEN
297 server6.sin6_len = sizeof server6;
299 if (bind(s[snum], (struct sockaddr *)&server6, sizeof server6) < 0)
300 sysdie("can't bind inet6 socket");
303 if ( addr_specified )
304 #endif /* HAVE_INET6 */
306 struct sockaddr_in server;
308 s[snum] = socket(PF_INET, SOCK_STREAM, 0);
310 sysdie("can't open inet socket");
313 if (setsockopt(s[snum], SOL_SOCKET, SO_REUSEADDR, (char *) &i,
315 syswarn("can't set SO_REUSEADDR");
317 memset(&server, 0, sizeof server);
318 server.sin_port = htons(port);
319 server.sin_family = AF_INET;
320 #ifdef HAVE_SOCKADDR_LEN
321 server.sin_len = sizeof server;
323 server.sin_addr = address;
324 if (bind(s[snum], (struct sockaddr *)&server, sizeof server) < 0)
325 sysdie("can't bind inet socket");
333 /* Now, permanently drop privileges. */
334 if (setgid(news_gid) < 0 || getgid() != news_gid)
335 sysdie("can't setgid to %lu", (unsigned long)news_gid);
336 if (setuid(news_uid) < 0 || getuid() != news_uid)
337 sysdie("can't setuid to %lu", (unsigned long)news_uid);
339 /* Build the argument vector for innd. Pass -p<port> to innd to tell it
340 what port we just created and bound to for it. */
341 innd_argv = xmalloc((1 + argc + 1) * sizeof(char *));
343 strlcpy(pflag, "-p ", sizeof(pflag));
344 for (j = 0; s[j] > 0; j++) {
347 snprintf(temp, sizeof(temp), "%d,", s[j]);
348 strlcat(pflag, temp, sizeof(pflag));
350 /* chop off the trailing , */
351 j = strlen(pflag) - 1;
354 innd_argv[i++] = DEBUGGER;
355 innd_argv[i++] = concatpath(innconf->pathbin, "innd");
357 printf("When starting innd, use -d %s\n", s, pflag);
359 innd_argv[i++] = concatpath(innconf->pathbin, "innd");
360 innd_argv[i++] = pflag;
362 /* Don't pass along -p, -P, or -I. Check the length of the argument
363 string, and if it == 2 (meaning there's nothing after the -p or -P or
364 -I), skip the next argument too, to support leaving a space between
365 the argument and the value. */
366 for (j = 1; j < argc; j++) {
367 if (argv[j][0] == '-' && strchr("pP6I", argv[j][1])) {
368 if (strlen(argv[j]) == 2)
372 innd_argv[i++] = argv[j];
376 #endif /* !DEBUGGER */
378 /* Set up the environment. Note that we're trusting BIND_INADDR and TZ;
379 everything else is either from inn.conf or from configure. These
380 should be sanity-checked before being propagated, but that requires
381 knowledge of the range of possible values. Just limiting their
382 length doesn't necessarily do anything to prevent exploits and may
383 stop things from working that should. We have to pass BIND_INADDR so
384 that it's set for programs, such as innfeed, that innd may spawn. */
385 innd_env[0] = concat("PATH=", innconf->pathbin, ":", innconf->pathetc,
386 ":/bin:/usr/bin:/usr/ucb", (char *) 0);
387 innd_env[1] = concat( "TMPDIR=", innconf->pathtmp, (char *) 0);
388 innd_env[2] = concat( "SHELL=", _PATH_SH, (char *) 0);
389 innd_env[3] = concat("LOGNAME=", NEWSMASTER, (char *) 0);
390 innd_env[4] = concat( "USER=", NEWSMASTER, (char *) 0);
391 innd_env[5] = concat( "HOME=", innconf->pathnews, (char *) 0);
393 p = getenv("BIND_INADDR");
395 innd_env[i++] = concat("BIND_INADDR=", p, (char *) 0);
398 innd_env[i++] = concat("TZ=", p, (char *) 0);
400 /* you have to compile with `purify cc -DPURIFY' to get this */
401 p = getenv("DISPLAY");
403 innd_env[i++] = concat("DISPLAY=", p, (char *) 0);
404 p = getenv("PURIFYOPTIONS");
406 innd_env[i++] = concat("PURIFYOPTIONS=", p, (char *) 0);
411 execve(innd_argv[0], innd_argv, innd_env);
412 sysdie("can't exec %s", innd_argv[0]);