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