Fixed possible child wait race condition.
[doldaconnect.git] / daemon / main.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
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>
33 #include <sys/select.h>
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
47 #ifdef HAVE_KEYUTILS
48 #include <keyutils.h>
49 #endif
50
51 struct module *modchain = NULL;
52 static struct timer *timers = NULL;
53 static struct child *children = NULL;
54 volatile int running;
55 volatile int reinit;
56 static volatile int childrendone = 0;
57
58 struct 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
74 void 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
86 void 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;
94     new->prev = NULL;
95     new->next = children;
96     if(children != NULL)
97         children->prev = new;
98     children = new;
99 }
100
101 static 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
114 static 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
128 static 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
139 static void handler(int signum)
140 {
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:
154         childrendone = 1;
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
173 static 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
199 pid_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         }
319         setpgid(0, 0);
320         signal(SIGHUP, SIG_IGN);
321         errno = 0;
322 #ifdef HAVE_KEYUTILS
323         keyctl_join_session_keyring(NULL);
324         keyctl_chown(KEY_SPEC_SESSION_KEYRING, pwent->pw_uid, pwent->pw_gid);
325 #endif
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                 }
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));
358             }
359             putenv(sprintf2("USER=%s", pwent->pw_name));
360             putenv(sprintf2("LOGNAME=%s", pwent->pw_name));
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
387 int main(int argc, char **argv)
388 {
389     int c;
390     int nofork;
391     char *configfile;
392     char *pidfile;
393     FILE *pfstream, *confstream;
394     int delay, immsyslog;
395     struct module *mod;
396     struct timer *timer;
397     double now;
398     
399     now = ntime();
400     immsyslog = nofork = 0;
401     syslogfac = LOG_DAEMON;
402     configfile = NULL;
403     pidfile = NULL;
404     while((c = getopt(argc, argv, "p:C:f:hnsV")) != -1)
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;
447         case 's':
448             immsyslog = 1;
449             break;
450         case 'V':
451             printf("%s", RELEASEINFO);
452             exit(0);
453         case 'h':
454         case ':':
455         case '?':
456         default:
457             printf("usage: doldacond [-hnsV] [-C configfile] [-p pidfile] [-f facility]\n");
458             exit(c != 'h');
459         }
460     }
461     setlocale(LC_ALL, "");
462     initlog();
463     if(immsyslog)
464     {
465         logtosyslog = 1;
466         logtostderr = 0;
467     }
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     {
478         if((configfile = findfile("doldacond.conf", NULL, 0)) == NULL)
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     }
512     flog(LOG_INFO, "startup took %f seconds", ntime() - now);
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         }
547         /* Of course, there's a race condition here that should be
548          * solved with pselect, but it doesn't matter a lot. */
549         if(childrendone)
550             delay = 0;
551         pollsocks(delay);
552         now = ntime();
553         do
554         {
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);
570         if(childrendone)
571         {
572             childrendone = 0;
573             checkchildren();
574         }
575     }
576     flog(LOG_INFO, "terminating...");
577     terminate();
578     if(pidfile != NULL)
579         unlink(pidfile);
580     return(0);
581 }