Fixed possible child wait race condition.
[doldaconnect.git] / daemon / main.c
CommitLineData
d3372da9 1/*
2 * Dolda Connect - Modular multiuser Direct Connect-style client
302a2600 3 * Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
d3372da9 4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18*/
19#include <stdio.h>
20#include <unistd.h>
21#include <stdlib.h>
22#include <string.h>
23#include <errno.h>
24#include <locale.h>
25#include <signal.h>
26#include <getopt.h>
27#include <time.h>
28#include <pwd.h>
29#include <grp.h>
30#include <sys/wait.h>
31#include <stdarg.h>
32#include <fcntl.h>
e304af88 33#include <sys/select.h>
d3372da9 34
35#ifdef HAVE_CONFIG_H
36#include <config.h>
37#endif
38#include "utils.h"
39#include "log.h"
40#include "conf.h"
41#include "module.h"
42#include "net.h"
43#include "client.h"
44#include "sysevents.h"
45#include "auth.h"
46
5feb6be7 47#ifdef HAVE_KEYUTILS
48#include <keyutils.h>
49#endif
50
d3372da9 51struct module *modchain = NULL;
52static struct timer *timers = NULL;
53static struct child *children = NULL;
54volatile int running;
55volatile int reinit;
56static volatile int childrendone = 0;
57
58struct timer *timercallback(double at, void (*func)(int, void *), void *data)
59{
60 struct timer *new;
61
62 new = smalloc(sizeof(*new));
63 new->at = at;
64 new->func = func;
65 new->data = data;
66 new->next = timers;
67 new->prev = NULL;
68 if(timers != NULL)
69 timers->prev = new;
70 timers = new;
71 return(new);
72}
73
74void canceltimer(struct timer *timer)
75{
76 if(timer->next != NULL)
77 timer->next->prev = timer->prev;
78 if(timer->prev != NULL)
79 timer->prev->next = timer->next;
80 if(timer == timers)
81 timers = timer->next;
82 timer->func(1, timer->data);
83 free(timer);
84}
85
86void childcallback(pid_t pid, void (*func)(pid_t, int, void *), void *data)
87{
88 struct child *new;
89
90 new = smalloc(sizeof(*new));
91 new->pid = pid;
92 new->callback = func;
93 new->data = data;
d3372da9 94 new->prev = NULL;
95 new->next = children;
96 if(children != NULL)
97 children->prev = new;
98 children = new;
99}
100
101static void preinit(int hup)
102{
103 struct module *mod;
104
105 for(mod = modchain; mod != NULL; mod = mod->next)
106 {
107 if(mod->preinit)
108 mod->preinit(hup);
109 if(!hup && ((mod->conf.vars != NULL) || (mod->conf.cmds != NULL)))
110 confregmod(&mod->conf);
111 }
112}
113
114static void init(int hup)
115{
116 struct module *mod;
117
118 for(mod = modchain; mod != NULL; mod = mod->next)
119 {
120 if(mod->init && mod->init(hup))
121 {
122 flog(LOG_CRIT, "initialization of \"%s\" failed", mod->name);
123 exit(1);
124 }
125 }
126}
127
128static void terminate(void)
129{
130 struct module *mod;
131
132 for(mod = modchain; mod != NULL; mod = mod->next)
133 {
134 if(mod->terminate)
135 mod->terminate();
136 }
137}
138
139static void handler(int signum)
140{
d3372da9 141 FILE *dumpfile;
142 extern int numfnetnodes, numtransfers, numdcpeers;
143
144 switch(signum)
145 {
146 case SIGHUP:
147 reinit = 1;
148 break;
149 case SIGINT:
150 case SIGTERM:
151 running = 0;
152 break;
153 case SIGCHLD:
bc222c94 154 childrendone = 1;
d3372da9 155 break;
156 case SIGUSR1:
157 flog(LOG_NOTICE, "forking and dumping core upon SIGUSR1");
158 if(fork() == 0)
159 abort();
160 break;
161 case SIGUSR2:
162 flog(LOG_NOTICE, "dumping memstats to /tmp/dc-mem upon SIGUSR2");
163 if((dumpfile = fopen("/tmp/dc-mem", "w")) == NULL) {
164 flog(LOG_ERR, "could not dump stats: %s", strerror(errno));
165 break;
166 }
167 fprintf(dumpfile, "%i %i %i\n", numfnetnodes, numtransfers, numdcpeers);
168 fclose(dumpfile);
169 break;
170 }
171}
172
bc222c94
FT
173static void checkchildren(void)
174{
175 pid_t pid;
176 int status;
177 struct child *child;
178
179 while((pid = waitpid(-1, &status, WNOHANG)) > 0)
180 {
181 for(child = children; child != NULL; child = child->next)
182 {
183 if(child->pid == pid)
184 {
185 child->callback(pid, status, child->data);
186 if(child == children)
187 children = child->next;
188 if(child->prev != NULL)
189 child->prev->next = child->next;
190 if(child->next != NULL)
191 child->next->prev = child->prev;
192 free(child);
193 break;
194 }
195 }
196 }
197}
198
d3372da9 199pid_t forksess(uid_t user, struct authhandle *auth, void (*ccbfunc)(pid_t, int, void *), void *data, ...)
200{
201 int i, o;
202 int cpipe[2];
203 struct
204 {
205 int tfd;
206 int fd;
207 } *files;
208 int maxfd, type, numfiles;
209 int acc;
210 int *ibuf;
211 struct passwd *pwent;
212 pid_t pid;
213 char *buf;
214 va_list args;
215 sigset_t sigset;
216 int ret, status;
217
218 if((pwent = getpwuid(user)) == NULL)
219 {
220 flog(LOG_WARNING, "no passwd entry for uid %i, cannot fork session", user);
221 errno = EACCES;
222 return(-1);
223 }
224 if((geteuid() != 0) && (user != geteuid()))
225 {
226 flog(LOG_WARNING, "cannot fork non-owning session when not running as root (EUID is %i, target UID is %i)", geteuid(), user);
227 errno = EPERM;
228 return(-1);
229 }
230 va_start(args, data);
231 numfiles = 0;
232 files = NULL;
233 maxfd = 0;
234 while((type = va_arg(args, int)) != FD_END)
235 {
236 files = srealloc(files, sizeof(*files) * (numfiles + 1));
237 files[numfiles].fd = va_arg(args, int);
238 if(files[numfiles].fd > maxfd)
239 maxfd = files[numfiles].fd;
240 acc = va_arg(args, int);
241 if(type == FD_PIPE)
242 {
243 if(pipe(cpipe) < 0)
244 {
245 flog(LOG_CRIT, "could not create pipe(!): %s", strerror(errno));
246 for(i = 0; i < numfiles; i++)
247 close(files[i].tfd);
248 return(-1);
249 }
250 ibuf = va_arg(args, int *);
251 if(acc == O_WRONLY)
252 {
253 *ibuf = cpipe[1];
254 files[numfiles].tfd = cpipe[0];
255 } else {
256 *ibuf = cpipe[0];
257 files[numfiles].tfd = cpipe[1];
258 }
259 } else if(type == FD_FILE) {
260 buf = va_arg(args, char *);
261 if((files[numfiles].tfd = open(buf, acc)) < 0)
262 {
263 flog(LOG_CRIT, "could not open file \"%s\": %s", buf, strerror(errno));
264 for(i = 0; i < numfiles; i++)
265 close(files[i].tfd);
266 return(-1);
267 }
268 }
269 if(files[numfiles].tfd > maxfd)
270 maxfd = files[numfiles].tfd;
271 numfiles++;
272 }
273 va_end(args);
274 sigemptyset(&sigset);
275 sigaddset(&sigset, SIGCHLD);
276 sigprocmask(SIG_BLOCK, &sigset, NULL);
277 if((pid = fork()) < 0)
278 {
279 flog(LOG_WARNING, "could not fork(!) in forksess(): %s", strerror(errno));
280 for(i = 0; i < numfiles; i++)
281 close(files[i].tfd);
282 sigprocmask(SIG_UNBLOCK, &sigset, NULL);
283 }
284 if(pid == 0)
285 {
286 sigprocmask(SIG_UNBLOCK, &sigset, NULL);
287 signal(SIGPIPE, SIG_DFL);
288 signal(SIGCHLD, SIG_DFL);
289 signal(SIGINT, SIG_DFL);
290 signal(SIGTERM, SIG_DFL);
291 signal(SIGHUP, SIG_DFL);
292 for(i = 0; i < numfiles; i++)
293 {
294 if(dup2(files[i].tfd, maxfd + i + 1) < 0)
295 exit(127);
296 files[i].tfd = maxfd + i + 1;
297 }
298 for(i = 0; i < numfiles; i++)
299 {
300 if(dup2(files[i].tfd, files[i].fd) < 0)
301 exit(127);
302 }
303 initlog();
304 for(i = 0; i < FD_SETSIZE; i++)
305 {
306 if(i <= maxfd)
307 {
308 for(o = 0; o < numfiles; o++)
309 {
310 if(i == files[o].fd)
311 break;
312 }
313 if(o == numfiles)
314 close(i);
315 } else {
316 close(i);
317 }
318 }
1001e996 319 setpgid(0, 0);
d3372da9 320 signal(SIGHUP, SIG_IGN);
321 errno = 0;
5feb6be7 322#ifdef HAVE_KEYUTILS
323 keyctl_join_session_keyring(NULL);
4a68468f 324 keyctl_chown(KEY_SPEC_SESSION_KEYRING, pwent->pw_uid, pwent->pw_gid);
5feb6be7 325#endif
d3372da9 326 if((authopensess(auth)) != AUTH_SUCCESS)
327 {
328 flog(LOG_WARNING, "could not open session for user %s: %s", pwent->pw_name, (errno == 0)?"Unknown error - should be logged above":strerror(errno));
329 exit(127);
330 }
331 if((pid = fork()) < 0)
332 {
333 authclosesess(auth);
334 exit(127);
335 }
336 if(pid == 0)
337 {
338 if(geteuid() == 0)
339 {
340 if(initgroups(pwent->pw_name, pwent->pw_gid))
341 {
342 flog(LOG_WARNING, "could not initgroups: %s", strerror(errno));
343 exit(127);
344 }
345 if(setgid(pwent->pw_gid))
346 {
347 flog(LOG_WARNING, "could not setgid: %s", strerror(errno));
348 exit(127);
349 }
350 if(setuid(pwent->pw_uid))
351 {
352 flog(LOG_WARNING, "could not setuid: %s", strerror(errno));
353 exit(127);
354 }
5b50bfcd
FT
355 putenv(sprintf2("HOME=%s", pwent->pw_dir));
356 putenv(sprintf2("SHELL=%s", pwent->pw_shell));
357 putenv(sprintf2("PATH=%s/bin:/usr/local/bin:/bin:/usr/bin", pwent->pw_dir));
d3372da9 358 }
d3372da9 359 putenv(sprintf2("USER=%s", pwent->pw_name));
360 putenv(sprintf2("LOGNAME=%s", pwent->pw_name));
d3372da9 361 chdir(pwent->pw_dir);
362 return(0);
363 }
364 for(i = 0; i < numfiles; i++)
365 close(files[i].fd);
366 while(((ret = waitpid(pid, &status, 0)) != pid) && (ret >= 0));
367 authclosesess(auth);
368 if(ret < 0)
369 {
370 flog(LOG_WARNING, "waitpid(%i) said \"%s\"", pid, strerror(errno));
371 exit(127);
372 }
373 if(!WIFEXITED(status))
374 exit(127);
375 exit(WEXITSTATUS(status));
376 }
377 for(i = 0; i < numfiles; i++)
378 close(files[i].tfd);
379 if(files != NULL)
380 free(files);
381 if(ccbfunc != NULL)
382 childcallback(pid, ccbfunc, data);
383 sigprocmask(SIG_UNBLOCK, &sigset, NULL);
384 return(pid);
385}
386
387int main(int argc, char **argv)
388{
389 int c;
390 int nofork;
391 char *configfile;
392 char *pidfile;
393 FILE *pfstream, *confstream;
5cccc2d5 394 int delay, immsyslog;
d3372da9 395 struct module *mod;
dae3c8b4 396 struct timer *timer;
d3372da9 397 double now;
398
2559e077 399 now = ntime();
5cccc2d5 400 immsyslog = nofork = 0;
d3372da9 401 syslogfac = LOG_DAEMON;
402 configfile = NULL;
403 pidfile = NULL;
8af979f3 404 while((c = getopt(argc, argv, "p:C:f:hnsV")) != -1)
d3372da9 405 {
406 switch(c)
407 {
408 case 'p':
409 pidfile = optarg;
410 break;
411 case 'C':
412 configfile = optarg;
413 break;
414 case 'f':
415 if(!strcmp(optarg, "auth"))
416 syslogfac = LOG_AUTH;
417 else if(!strcmp(optarg, "authpriv"))
418 syslogfac = LOG_AUTHPRIV;
419 else if(!strcmp(optarg, "cron"))
420 syslogfac = LOG_CRON;
421 else if(!strcmp(optarg, "daemon"))
422 syslogfac = LOG_DAEMON;
423 else if(!strcmp(optarg, "ftp"))
424 syslogfac = LOG_FTP;
425 else if(!strcmp(optarg, "kern"))
426 syslogfac = LOG_KERN;
427 else if(!strcmp(optarg, "lpr"))
428 syslogfac = LOG_LPR;
429 else if(!strcmp(optarg, "mail"))
430 syslogfac = LOG_MAIL;
431 else if(!strcmp(optarg, "news"))
432 syslogfac = LOG_NEWS;
433 else if(!strcmp(optarg, "syslog"))
434 syslogfac = LOG_SYSLOG;
435 else if(!strcmp(optarg, "user"))
436 syslogfac = LOG_USER;
437 else if(!strcmp(optarg, "uucp"))
438 syslogfac = LOG_UUCP;
439 else if(!strncmp(optarg, "local", 5) && (strlen(optarg) == 6))
440 syslogfac = LOG_LOCAL0 + (optarg[5] - '0');
441 else
442 fprintf(stderr, "unknown syslog facility %s, using daemon\n", optarg);
443 break;
444 case 'n':
445 nofork = 1;
446 break;
9a0b3ef8 447 case 's':
5cccc2d5 448 immsyslog = 1;
58b3dc7b 449 break;
8af979f3
FT
450 case 'V':
451 printf("%s", RELEASEINFO);
452 exit(0);
d3372da9 453 case 'h':
454 case ':':
455 case '?':
456 default:
8af979f3 457 printf("usage: doldacond [-hnsV] [-C configfile] [-p pidfile] [-f facility]\n");
d3372da9 458 exit(c != 'h');
459 }
460 }
461 setlocale(LC_ALL, "");
462 initlog();
5cccc2d5 463 if(immsyslog)
464 {
465 logtosyslog = 1;
466 logtostderr = 0;
467 }
d3372da9 468 signal(SIGPIPE, SIG_IGN);
469 signal(SIGHUP, handler);
470 signal(SIGINT, handler);
471 signal(SIGTERM, handler);
472 signal(SIGCHLD, handler);
473 signal(SIGUSR1, handler);
474 signal(SIGUSR2, handler);
475 preinit(0);
476 if(configfile == NULL)
477 {
c5d23632 478 if((configfile = findfile("doldacond.conf", NULL, 0)) == NULL)
d3372da9 479 {
480 flog(LOG_CRIT, "could not find a configuration file");
481 exit(1);
482 }
483 }
484 pfstream = NULL;
485 if(pidfile != NULL)
486 {
487 if((pfstream = fopen(pidfile, "w")) == NULL)
488 {
489 flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno));
490 exit(1);
491 }
492 }
493 if((confstream = fopen(configfile, "r")) == NULL)
494 {
495 flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno));
496 exit(1);
497 }
498 readconfig(confstream);
499 fclose(confstream);
500 init(0);
501 if(!nofork)
502 {
503 logtosyslog = 1;
504 daemon(0, 0);
505 flog(LOG_INFO, "daemonized");
506 logtostderr = 0;
507 }
508 if(pfstream != NULL) {
509 fprintf(pfstream, "%i\n", getpid());
510 fclose(pfstream);
511 }
2559e077 512 flog(LOG_INFO, "startup took %f seconds", ntime() - now);
d3372da9 513 running = 1;
514 reinit = 0;
515 while(running)
516 {
517 if(reinit)
518 {
519 if((confstream = fopen(configfile, "r")) == NULL)
520 {
521 flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno));
522 } else {
523 preinit(1);
524 readconfig(confstream);
525 fclose(confstream);
526 init(1);
527 }
528 reinit = 0;
529 }
530 delay = 1000; /* -1; */
531 for(mod = modchain; mod != NULL; mod = mod->next)
532 {
533 if(mod->run && mod->run())
534 delay = 0;
535 }
536 if(!running)
537 delay = 0;
538 if(delay != 0)
539 {
540 now = ntime();
541 for(timer = timers; timer != NULL; timer = timer->next)
542 {
543 if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay))
544 delay = (int)((timer->at - now) * 1000.0);
545 }
546 }
bc222c94
FT
547 /* Of course, there's a race condition here that should be
548 * solved with pselect, but it doesn't matter a lot. */
d3372da9 549 if(childrendone)
d3372da9 550 delay = 0;
d3372da9 551 pollsocks(delay);
552 now = ntime();
dae3c8b4 553 do
d3372da9 554 {
dae3c8b4 555 for(timer = timers; timer != NULL; timer = timer->next)
556 {
557 if(now < timer->at)
558 continue;
559 if(timer->prev != NULL)
560 timer->prev->next = timer->next;
561 if(timer->next != NULL)
562 timer->next->prev = timer->prev;
563 if(timer == timers)
564 timers = timer->next;
565 timer->func(0, timer->data);
566 free(timer);
567 break;
568 }
569 } while(timer != NULL);
bc222c94 570 if(childrendone)
d3372da9 571 {
bc222c94
FT
572 childrendone = 0;
573 checkchildren();
574 }
d3372da9 575 }
576 flog(LOG_INFO, "terminating...");
577 terminate();
267c4a93 578 if(pidfile != NULL)
579 unlink(pidfile);
d3372da9 580 return(0);
581}