ChangeLog update.
[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         setpgid(0, 0);
308         signal(SIGHUP, SIG_IGN);
309         errno = 0;
310 #ifdef HAVE_KEYUTILS
311         keyctl_join_session_keyring(NULL);
312         keyctl_chown(KEY_SPEC_SESSION_KEYRING, pwent->pw_uid, pwent->pw_gid);
313 #endif
314         if((authopensess(auth)) != AUTH_SUCCESS)
315         {
316             flog(LOG_WARNING, "could not open session for user %s: %s", pwent->pw_name, (errno == 0)?"Unknown error - should be logged above":strerror(errno));
317             exit(127);
318         }
319         if((pid = fork()) < 0)
320         {
321             authclosesess(auth);
322             exit(127);
323         }
324         if(pid == 0)
325         {
326             if(geteuid() == 0)
327             {
328                 if(initgroups(pwent->pw_name, pwent->pw_gid))
329                 {
330                     flog(LOG_WARNING, "could not initgroups: %s", strerror(errno));
331                     exit(127);
332                 }
333                 if(setgid(pwent->pw_gid))
334                 {
335                     flog(LOG_WARNING, "could not setgid: %s", strerror(errno));
336                     exit(127);
337                 }
338                 if(setuid(pwent->pw_uid))
339                 {
340                     flog(LOG_WARNING, "could not setuid: %s", strerror(errno));
341                     exit(127);
342                 }
343                 putenv(sprintf2("HOME=%s", pwent->pw_dir));
344                 putenv(sprintf2("SHELL=%s", pwent->pw_shell));
345                 putenv(sprintf2("PATH=%s/bin:/usr/local/bin:/bin:/usr/bin", pwent->pw_dir));
346             }
347             putenv(sprintf2("USER=%s", pwent->pw_name));
348             putenv(sprintf2("LOGNAME=%s", pwent->pw_name));
349             chdir(pwent->pw_dir);
350             return(0);
351         }
352         for(i = 0; i < numfiles; i++)
353             close(files[i].fd);
354         while(((ret = waitpid(pid, &status, 0)) != pid) && (ret >= 0));
355         authclosesess(auth);
356         if(ret < 0)
357         {
358             flog(LOG_WARNING, "waitpid(%i) said \"%s\"", pid, strerror(errno));
359             exit(127);
360         }
361         if(!WIFEXITED(status))
362             exit(127);
363         exit(WEXITSTATUS(status));
364     }
365     for(i = 0; i < numfiles; i++)
366         close(files[i].tfd);
367     if(files != NULL)
368         free(files);
369     if(ccbfunc != NULL)
370         childcallback(pid, ccbfunc, data);
371     sigprocmask(SIG_UNBLOCK, &sigset, NULL);
372     return(pid);
373 }
374
375 int main(int argc, char **argv)
376 {
377     int c;
378     int nofork;
379     char *configfile;
380     char *pidfile;
381     FILE *pfstream, *confstream;
382     int delay, immsyslog;
383     struct module *mod;
384     struct timer *timer;
385     struct child *child;
386     double now;
387     
388     now = ntime();
389     immsyslog = nofork = 0;
390     syslogfac = LOG_DAEMON;
391     configfile = NULL;
392     pidfile = NULL;
393     while((c = getopt(argc, argv, "p:C:f:hnsV")) != -1)
394     {
395         switch(c)
396         {
397         case 'p':
398             pidfile = optarg;
399             break;
400         case 'C':
401             configfile = optarg;
402             break;
403         case 'f':
404             if(!strcmp(optarg, "auth"))
405                 syslogfac = LOG_AUTH;
406             else if(!strcmp(optarg, "authpriv"))
407                 syslogfac = LOG_AUTHPRIV;
408             else if(!strcmp(optarg, "cron"))
409                 syslogfac = LOG_CRON;
410             else if(!strcmp(optarg, "daemon"))
411                 syslogfac = LOG_DAEMON;
412             else if(!strcmp(optarg, "ftp"))
413                 syslogfac = LOG_FTP;
414             else if(!strcmp(optarg, "kern"))
415                 syslogfac = LOG_KERN;
416             else if(!strcmp(optarg, "lpr"))
417                 syslogfac = LOG_LPR;
418             else if(!strcmp(optarg, "mail"))
419                 syslogfac = LOG_MAIL;
420             else if(!strcmp(optarg, "news"))
421                 syslogfac = LOG_NEWS;
422             else if(!strcmp(optarg, "syslog"))
423                 syslogfac = LOG_SYSLOG;
424             else if(!strcmp(optarg, "user"))
425                 syslogfac = LOG_USER;
426             else if(!strcmp(optarg, "uucp"))
427                 syslogfac = LOG_UUCP;
428             else if(!strncmp(optarg, "local", 5) && (strlen(optarg) == 6))
429                 syslogfac = LOG_LOCAL0 + (optarg[5] - '0');
430             else
431                 fprintf(stderr, "unknown syslog facility %s, using daemon\n", optarg);
432             break;
433         case 'n':
434             nofork = 1;
435             break;
436         case 's':
437             immsyslog = 1;
438             break;
439         case 'V':
440             printf("%s", RELEASEINFO);
441             exit(0);
442         case 'h':
443         case ':':
444         case '?':
445         default:
446             printf("usage: doldacond [-hnsV] [-C configfile] [-p pidfile] [-f facility]\n");
447             exit(c != 'h');
448         }
449     }
450     setlocale(LC_ALL, "");
451     initlog();
452     if(immsyslog)
453     {
454         logtosyslog = 1;
455         logtostderr = 0;
456     }
457     signal(SIGPIPE, SIG_IGN);
458     signal(SIGHUP, handler);
459     signal(SIGINT, handler);
460     signal(SIGTERM, handler);
461     signal(SIGCHLD, handler);
462     signal(SIGUSR1, handler);
463     signal(SIGUSR2, handler);
464     preinit(0);
465     if(configfile == NULL)
466     {
467         if((configfile = findfile("doldacond.conf", NULL, 0)) == NULL)
468         {
469             flog(LOG_CRIT, "could not find a configuration file");
470             exit(1);
471         }
472     }
473     pfstream = NULL;
474     if(pidfile != NULL)
475     {
476         if((pfstream = fopen(pidfile, "w")) == NULL)
477         {
478             flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno));
479             exit(1);
480         }
481     }
482     if((confstream = fopen(configfile, "r")) == NULL)
483     {
484         flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno));
485         exit(1);
486     }
487     readconfig(confstream);
488     fclose(confstream);
489     init(0);
490     if(!nofork)
491     {
492         logtosyslog = 1;
493         daemon(0, 0);
494         flog(LOG_INFO, "daemonized");
495         logtostderr = 0;
496     }
497     if(pfstream != NULL) {
498         fprintf(pfstream, "%i\n", getpid());
499         fclose(pfstream);
500     }
501     flog(LOG_INFO, "startup took %f seconds", ntime() - now);
502     running = 1;
503     reinit = 0;
504     while(running)
505     {
506         if(reinit)
507         {
508             if((confstream = fopen(configfile, "r")) == NULL)
509             {
510                 flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno));
511             } else {
512                 preinit(1);
513                 readconfig(confstream);
514                 fclose(confstream);
515                 init(1);
516             }
517             reinit = 0;
518         }
519         delay = 1000; /* -1; */
520         for(mod = modchain; mod != NULL; mod = mod->next)
521         {
522             if(mod->run && mod->run())
523                 delay = 0;
524         }
525         if(!running)
526             delay = 0;
527         if(delay != 0)
528         {
529             now = ntime();
530             for(timer = timers; timer != NULL; timer = timer->next)
531             {
532                 if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay))
533                     delay = (int)((timer->at - now) * 1000.0);
534             }
535         }
536         if(childrendone)
537         {
538             delay = 0;
539             childrendone = 0;
540         }
541         pollsocks(delay);
542         now = ntime();
543         do
544         {
545             for(timer = timers; timer != NULL; timer = timer->next)
546             {
547                 if(now < timer->at)
548                     continue;
549                 if(timer->prev != NULL)
550                     timer->prev->next = timer->next;
551                 if(timer->next != NULL)
552                     timer->next->prev = timer->prev;
553                 if(timer == timers)
554                     timers = timer->next;
555                 timer->func(0, timer->data);
556                 free(timer);
557                 break;
558             }
559         } while(timer != NULL);
560         do
561         {
562             for(child = children; child != NULL; child = child->next)
563             {
564                 if(child->finished)
565                 {
566                     child->callback(child->pid, child->status, child->data);
567                     if(child == children)
568                         children = child->next;
569                     if(child->prev != NULL)
570                         child->prev->next = child->next;
571                     if(child->next != NULL)
572                         child->next->prev = child->prev;
573                     free(child);
574                     break;
575                 }
576             }
577         } while(child != NULL);
578     }
579     flog(LOG_INFO, "terminating...");
580     terminate();
581     if(pidfile != NULL)
582         unlink(pidfile);
583     return(0);
584 }