2c6f08d0d0ee5d79984a94600f58117560a65af5
[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
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37 #include "utils.h"
38 #include "log.h"
39 #include "conf.h"
40 #include "module.h"
41 #include "net.h"
42 #include "client.h"
43 #include "sysevents.h"
44 #include "auth.h"
45
46 #ifdef HAVE_KEYUTILS
47 #include <keyutils.h>
48 #endif
49
50 struct module *modchain = NULL;
51 static struct timer *timers = NULL;
52 static struct child *children = NULL;
53 volatile int running;
54 volatile int reinit;
55 static volatile int childrendone = 0;
56
57 struct timer *timercallback(double at, void (*func)(int, void *), void *data)
58 {
59     struct timer *new;
60     
61     new = smalloc(sizeof(*new));
62     new->at = at;
63     new->func = func;
64     new->data = data;
65     new->next = timers;
66     new->prev = NULL;
67     if(timers != NULL)
68         timers->prev = new;
69     timers = new;
70     return(new);
71 }
72
73 void canceltimer(struct timer *timer)
74 {
75     if(timer->next != NULL)
76         timer->next->prev = timer->prev;
77     if(timer->prev != NULL)
78         timer->prev->next = timer->next;
79     if(timer == timers)
80         timers = timer->next;
81     timer->func(1, timer->data);
82     free(timer);
83 }
84
85 void childcallback(pid_t pid, void (*func)(pid_t, int, void *), void *data)
86 {
87     struct child *new;
88     
89     new = smalloc(sizeof(*new));
90     new->pid = pid;
91     new->callback = func;
92     new->data = data;
93     new->finished = 0;
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     pid_t pid;
142     int status;
143     struct child *child;
144     FILE *dumpfile;
145     extern int numfnetnodes, numtransfers, numdcpeers;
146     
147     switch(signum)
148     {
149     case SIGHUP:
150         reinit = 1;
151         break;
152     case SIGINT:
153     case SIGTERM:
154         running = 0;
155         break;
156     case SIGCHLD:
157         while((pid = waitpid(-1, &status, WNOHANG)) > 0)
158         {
159             for(child = children; child != NULL; child = child->next)
160             {
161                 if(child->pid == pid)
162                 {
163                     child->finished = 1;
164                     child->status = status;
165                 }
166             }
167             childrendone = 1;
168         }
169         break;
170     case SIGUSR1:
171         flog(LOG_NOTICE, "forking and dumping core upon SIGUSR1");
172         if(fork() == 0)
173             abort();
174         break;
175     case SIGUSR2:
176         flog(LOG_NOTICE, "dumping memstats to /tmp/dc-mem upon SIGUSR2");
177         if((dumpfile = fopen("/tmp/dc-mem", "w")) == NULL) {
178             flog(LOG_ERR, "could not dump stats: %s", strerror(errno));
179             break;
180         }
181         fprintf(dumpfile, "%i %i %i\n", numfnetnodes, numtransfers, numdcpeers);
182         fclose(dumpfile);
183         break;
184     }
185 }
186
187 pid_t forksess(uid_t user, struct authhandle *auth, void (*ccbfunc)(pid_t, int, void *), void *data, ...)
188 {
189     int i, o;
190     int cpipe[2];
191     struct
192     {
193         int tfd;
194         int fd;
195     } *files;
196     int maxfd, type, numfiles;
197     int acc;
198     int *ibuf;
199     struct passwd *pwent;
200     pid_t pid;
201     char *buf;
202     va_list args;
203     sigset_t sigset;
204     int ret, status;
205     
206     if((pwent = getpwuid(user)) == NULL)
207     {
208         flog(LOG_WARNING, "no passwd entry for uid %i, cannot fork session", user);
209         errno = EACCES;
210         return(-1);
211     }
212     if((geteuid() != 0) && (user != geteuid()))
213     {
214         flog(LOG_WARNING, "cannot fork non-owning session when not running as root (EUID is %i, target UID is %i)", geteuid(), user);
215         errno = EPERM;
216         return(-1);
217     }
218     va_start(args, data);
219     numfiles = 0;
220     files = NULL;
221     maxfd = 0;
222     while((type = va_arg(args, int)) != FD_END)
223     {
224         files = srealloc(files, sizeof(*files) * (numfiles + 1));
225         files[numfiles].fd = va_arg(args, int);
226         if(files[numfiles].fd > maxfd)
227             maxfd = files[numfiles].fd;
228         acc = va_arg(args, int);
229         if(type == FD_PIPE)
230         {
231             if(pipe(cpipe) < 0)
232             {
233                 flog(LOG_CRIT, "could not create pipe(!): %s", strerror(errno));
234                 for(i = 0; i < numfiles; i++)
235                     close(files[i].tfd);
236                 return(-1);
237             }
238             ibuf = va_arg(args, int *);
239             if(acc == O_WRONLY)
240             {
241                 *ibuf = cpipe[1];
242                 files[numfiles].tfd = cpipe[0];
243             } else {
244                 *ibuf = cpipe[0];
245                 files[numfiles].tfd = cpipe[1];
246             }
247         } else if(type == FD_FILE) {
248             buf = va_arg(args, char *);
249             if((files[numfiles].tfd = open(buf, acc)) < 0)
250             {
251                 flog(LOG_CRIT, "could not open file \"%s\": %s", buf, strerror(errno));
252                 for(i = 0; i < numfiles; i++)
253                     close(files[i].tfd);
254                 return(-1);
255             }
256         }
257         if(files[numfiles].tfd > maxfd)
258             maxfd = files[numfiles].tfd;
259         numfiles++;
260     }
261     va_end(args);
262     sigemptyset(&sigset);
263     sigaddset(&sigset, SIGCHLD);
264     sigprocmask(SIG_BLOCK, &sigset, NULL);
265     if((pid = fork()) < 0)
266     {
267         flog(LOG_WARNING, "could not fork(!) in forksess(): %s", strerror(errno));
268         for(i = 0; i < numfiles; i++)
269             close(files[i].tfd);
270         sigprocmask(SIG_UNBLOCK, &sigset, NULL);
271     }
272     if(pid == 0)
273     {
274         sigprocmask(SIG_UNBLOCK, &sigset, NULL);
275         signal(SIGPIPE, SIG_DFL);
276         signal(SIGCHLD, SIG_DFL);
277         signal(SIGINT, SIG_DFL);
278         signal(SIGTERM, SIG_DFL);
279         signal(SIGHUP, SIG_DFL);
280         for(i = 0; i < numfiles; i++)
281         {
282             if(dup2(files[i].tfd, maxfd + i + 1) < 0)
283                 exit(127);
284             files[i].tfd = maxfd + i + 1;
285         }
286         for(i = 0; i < numfiles; i++)
287         {
288             if(dup2(files[i].tfd, files[i].fd) < 0)
289                 exit(127);
290         }
291         initlog();
292         for(i = 0; i < FD_SETSIZE; i++)
293         {
294             if(i <= maxfd)
295             {
296                 for(o = 0; o < numfiles; o++)
297                 {
298                     if(i == files[o].fd)
299                         break;
300                 }
301                 if(o == numfiles)
302                     close(i);
303             } else {
304                 close(i);
305             }
306         }
307         setpgrp();
308         signal(SIGHUP, SIG_IGN);
309         errno = 0;
310 #ifdef HAVE_KEYUTILS
311         keyctl_join_session_keyring(NULL);
312 #endif
313         if((authopensess(auth)) != AUTH_SUCCESS)
314         {
315             flog(LOG_WARNING, "could not open session for user %s: %s", pwent->pw_name, (errno == 0)?"Unknown error - should be logged above":strerror(errno));
316             exit(127);
317         }
318         if((pid = fork()) < 0)
319         {
320             authclosesess(auth);
321             exit(127);
322         }
323         if(pid == 0)
324         {
325             if(geteuid() == 0)
326             {
327                 if(initgroups(pwent->pw_name, pwent->pw_gid))
328                 {
329                     flog(LOG_WARNING, "could not initgroups: %s", strerror(errno));
330                     exit(127);
331                 }
332                 if(setgid(pwent->pw_gid))
333                 {
334                     flog(LOG_WARNING, "could not setgid: %s", strerror(errno));
335                     exit(127);
336                 }
337                 if(setuid(pwent->pw_uid))
338                 {
339                     flog(LOG_WARNING, "could not setuid: %s", strerror(errno));
340                     exit(127);
341                 }
342             }
343             putenv(sprintf2("HOME=%s", pwent->pw_dir));
344             putenv(sprintf2("SHELL=%s", pwent->pw_shell));
345             putenv(sprintf2("USER=%s", pwent->pw_name));
346             putenv(sprintf2("LOGNAME=%s", pwent->pw_name));
347             putenv(sprintf2("PATH=%s/bin:/usr/local/bin:/bin:/usr/bin", pwent->pw_dir));
348             chdir(pwent->pw_dir);
349             return(0);
350         }
351         for(i = 0; i < numfiles; i++)
352             close(files[i].fd);
353         while(((ret = waitpid(pid, &status, 0)) != pid) && (ret >= 0));
354         authclosesess(auth);
355         if(ret < 0)
356         {
357             flog(LOG_WARNING, "waitpid(%i) said \"%s\"", pid, strerror(errno));
358             exit(127);
359         }
360         if(!WIFEXITED(status))
361             exit(127);
362         exit(WEXITSTATUS(status));
363     }
364     for(i = 0; i < numfiles; i++)
365         close(files[i].tfd);
366     if(files != NULL)
367         free(files);
368     if(ccbfunc != NULL)
369         childcallback(pid, ccbfunc, data);
370     sigprocmask(SIG_UNBLOCK, &sigset, NULL);
371     return(pid);
372 }
373
374 int main(int argc, char **argv)
375 {
376     int c;
377     int nofork;
378     char *configfile;
379     char *pidfile;
380     FILE *pfstream, *confstream;
381     int delay, immsyslog;
382     struct module *mod;
383     struct timer *timer;
384     struct child *child;
385     double now;
386     
387     immsyslog = nofork = 0;
388     syslogfac = LOG_DAEMON;
389     configfile = NULL;
390     pidfile = NULL;
391     while((c = getopt(argc, argv, "p:C:f:hns")) != -1)
392     {
393         switch(c)
394         {
395         case 'p':
396             pidfile = optarg;
397             break;
398         case 'C':
399             configfile = optarg;
400             break;
401         case 'f':
402             if(!strcmp(optarg, "auth"))
403                 syslogfac = LOG_AUTH;
404             else if(!strcmp(optarg, "authpriv"))
405                 syslogfac = LOG_AUTHPRIV;
406             else if(!strcmp(optarg, "cron"))
407                 syslogfac = LOG_CRON;
408             else if(!strcmp(optarg, "daemon"))
409                 syslogfac = LOG_DAEMON;
410             else if(!strcmp(optarg, "ftp"))
411                 syslogfac = LOG_FTP;
412             else if(!strcmp(optarg, "kern"))
413                 syslogfac = LOG_KERN;
414             else if(!strcmp(optarg, "lpr"))
415                 syslogfac = LOG_LPR;
416             else if(!strcmp(optarg, "mail"))
417                 syslogfac = LOG_MAIL;
418             else if(!strcmp(optarg, "news"))
419                 syslogfac = LOG_NEWS;
420             else if(!strcmp(optarg, "syslog"))
421                 syslogfac = LOG_SYSLOG;
422             else if(!strcmp(optarg, "user"))
423                 syslogfac = LOG_USER;
424             else if(!strcmp(optarg, "uucp"))
425                 syslogfac = LOG_UUCP;
426             else if(!strncmp(optarg, "local", 5) && (strlen(optarg) == 6))
427                 syslogfac = LOG_LOCAL0 + (optarg[5] - '0');
428             else
429                 fprintf(stderr, "unknown syslog facility %s, using daemon\n", optarg);
430             break;
431         case 'n':
432             nofork = 1;
433             break;
434         case 's':
435             immsyslog = 1;
436             break;
437         case 'h':
438         case ':':
439         case '?':
440         default:
441             printf("usage: doldacond [-hns] [-C configfile] [-p pidfile] [-f facility]\n");
442             exit(c != 'h');
443         }
444     }
445     setlocale(LC_ALL, "");
446     initlog();
447     if(immsyslog)
448     {
449         logtosyslog = 1;
450         logtostderr = 0;
451     }
452     signal(SIGPIPE, SIG_IGN);
453     signal(SIGHUP, handler);
454     signal(SIGINT, handler);
455     signal(SIGTERM, handler);
456     signal(SIGCHLD, handler);
457     signal(SIGUSR1, handler);
458     signal(SIGUSR2, handler);
459     preinit(0);
460     if(configfile == NULL)
461     {
462         if((configfile = findconfigfile()) == NULL)
463         {
464             flog(LOG_CRIT, "could not find a configuration file");
465             exit(1);
466         }
467     }
468     pfstream = NULL;
469     if(pidfile != NULL)
470     {
471         if((pfstream = fopen(pidfile, "w")) == NULL)
472         {
473             flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno));
474             exit(1);
475         }
476     }
477     if((confstream = fopen(configfile, "r")) == NULL)
478     {
479         flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno));
480         exit(1);
481     }
482     readconfig(confstream);
483     fclose(confstream);
484     init(0);
485     if(!nofork)
486     {
487         logtosyslog = 1;
488         daemon(0, 0);
489         flog(LOG_INFO, "daemonized");
490         logtostderr = 0;
491     }
492     if(pfstream != NULL) {
493         fprintf(pfstream, "%i\n", getpid());
494         fclose(pfstream);
495     }
496     running = 1;
497     reinit = 0;
498     while(running)
499     {
500         if(reinit)
501         {
502             if((confstream = fopen(configfile, "r")) == NULL)
503             {
504                 flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno));
505             } else {
506                 preinit(1);
507                 readconfig(confstream);
508                 fclose(confstream);
509                 init(1);
510             }
511             reinit = 0;
512         }
513         delay = 1000; /* -1; */
514         for(mod = modchain; mod != NULL; mod = mod->next)
515         {
516             if(mod->run && mod->run())
517                 delay = 0;
518         }
519         if(!running)
520             delay = 0;
521         if(delay != 0)
522         {
523             now = ntime();
524             for(timer = timers; timer != NULL; timer = timer->next)
525             {
526                 if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay))
527                     delay = (int)((timer->at - now) * 1000.0);
528             }
529         }
530         if(childrendone)
531         {
532             delay = 0;
533             childrendone = 0;
534         }
535         pollsocks(delay);
536         now = ntime();
537         do
538         {
539             for(timer = timers; timer != NULL; timer = timer->next)
540             {
541                 if(now < timer->at)
542                     continue;
543                 if(timer->prev != NULL)
544                     timer->prev->next = timer->next;
545                 if(timer->next != NULL)
546                     timer->next->prev = timer->prev;
547                 if(timer == timers)
548                     timers = timer->next;
549                 timer->func(0, timer->data);
550                 free(timer);
551                 break;
552             }
553         } while(timer != NULL);
554         do
555         {
556             for(child = children; child != NULL; child = child->next)
557             {
558                 if(child->finished)
559                 {
560                     child->callback(child->pid, child->status, child->data);
561                     if(child == children)
562                         children = child->next;
563                     if(child->prev != NULL)
564                         child->prev->next = child->next;
565                     if(child->next != NULL)
566                         child->next->prev = child->prev;
567                     free(child);
568                     break;
569                 }
570             }
571         } while(child != NULL);
572     }
573     flog(LOG_INFO, "terminating...");
574     terminate();
575     if(pidfile != NULL)
576         unlink(pidfile);
577     return(0);
578 }