More translations.
[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         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             }
344             putenv(sprintf2("HOME=%s", pwent->pw_dir));
345             putenv(sprintf2("SHELL=%s", pwent->pw_shell));
346             putenv(sprintf2("USER=%s", pwent->pw_name));
347             putenv(sprintf2("LOGNAME=%s", pwent->pw_name));
348             putenv(sprintf2("PATH=%s/bin:/usr/local/bin:/bin:/usr/bin", pwent->pw_dir));
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     immsyslog = nofork = 0;
389     syslogfac = LOG_DAEMON;
390     configfile = NULL;
391     pidfile = NULL;
392     while((c = getopt(argc, argv, "p:C:f:hns")) != -1)
393     {
394         switch(c)
395         {
396         case 'p':
397             pidfile = optarg;
398             break;
399         case 'C':
400             configfile = optarg;
401             break;
402         case 'f':
403             if(!strcmp(optarg, "auth"))
404                 syslogfac = LOG_AUTH;
405             else if(!strcmp(optarg, "authpriv"))
406                 syslogfac = LOG_AUTHPRIV;
407             else if(!strcmp(optarg, "cron"))
408                 syslogfac = LOG_CRON;
409             else if(!strcmp(optarg, "daemon"))
410                 syslogfac = LOG_DAEMON;
411             else if(!strcmp(optarg, "ftp"))
412                 syslogfac = LOG_FTP;
413             else if(!strcmp(optarg, "kern"))
414                 syslogfac = LOG_KERN;
415             else if(!strcmp(optarg, "lpr"))
416                 syslogfac = LOG_LPR;
417             else if(!strcmp(optarg, "mail"))
418                 syslogfac = LOG_MAIL;
419             else if(!strcmp(optarg, "news"))
420                 syslogfac = LOG_NEWS;
421             else if(!strcmp(optarg, "syslog"))
422                 syslogfac = LOG_SYSLOG;
423             else if(!strcmp(optarg, "user"))
424                 syslogfac = LOG_USER;
425             else if(!strcmp(optarg, "uucp"))
426                 syslogfac = LOG_UUCP;
427             else if(!strncmp(optarg, "local", 5) && (strlen(optarg) == 6))
428                 syslogfac = LOG_LOCAL0 + (optarg[5] - '0');
429             else
430                 fprintf(stderr, "unknown syslog facility %s, using daemon\n", optarg);
431             break;
432         case 'n':
433             nofork = 1;
434             break;
435         case 's':
436             immsyslog = 1;
437             break;
438         case 'h':
439         case ':':
440         case '?':
441         default:
442             printf("usage: doldacond [-hns] [-C configfile] [-p pidfile] [-f facility]\n");
443             exit(c != 'h');
444         }
445     }
446     setlocale(LC_ALL, "");
447     initlog();
448     if(immsyslog)
449     {
450         logtosyslog = 1;
451         logtostderr = 0;
452     }
453     signal(SIGPIPE, SIG_IGN);
454     signal(SIGHUP, handler);
455     signal(SIGINT, handler);
456     signal(SIGTERM, handler);
457     signal(SIGCHLD, handler);
458     signal(SIGUSR1, handler);
459     signal(SIGUSR2, handler);
460     preinit(0);
461     if(configfile == NULL)
462     {
463         if((configfile = findconfigfile()) == NULL)
464         {
465             flog(LOG_CRIT, "could not find a configuration file");
466             exit(1);
467         }
468     }
469     pfstream = NULL;
470     if(pidfile != NULL)
471     {
472         if((pfstream = fopen(pidfile, "w")) == NULL)
473         {
474             flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno));
475             exit(1);
476         }
477     }
478     if((confstream = fopen(configfile, "r")) == NULL)
479     {
480         flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno));
481         exit(1);
482     }
483     readconfig(confstream);
484     fclose(confstream);
485     init(0);
486     if(!nofork)
487     {
488         logtosyslog = 1;
489         daemon(0, 0);
490         flog(LOG_INFO, "daemonized");
491         logtostderr = 0;
492     }
493     if(pfstream != NULL) {
494         fprintf(pfstream, "%i\n", getpid());
495         fclose(pfstream);
496     }
497     running = 1;
498     reinit = 0;
499     while(running)
500     {
501         if(reinit)
502         {
503             if((confstream = fopen(configfile, "r")) == NULL)
504             {
505                 flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno));
506             } else {
507                 preinit(1);
508                 readconfig(confstream);
509                 fclose(confstream);
510                 init(1);
511             }
512             reinit = 0;
513         }
514         delay = 1000; /* -1; */
515         for(mod = modchain; mod != NULL; mod = mod->next)
516         {
517             if(mod->run && mod->run())
518                 delay = 0;
519         }
520         if(!running)
521             delay = 0;
522         if(delay != 0)
523         {
524             now = ntime();
525             for(timer = timers; timer != NULL; timer = timer->next)
526             {
527                 if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay))
528                     delay = (int)((timer->at - now) * 1000.0);
529             }
530         }
531         if(childrendone)
532         {
533             delay = 0;
534             childrendone = 0;
535         }
536         pollsocks(delay);
537         now = ntime();
538         do
539         {
540             for(timer = timers; timer != NULL; timer = timer->next)
541             {
542                 if(now < timer->at)
543                     continue;
544                 if(timer->prev != NULL)
545                     timer->prev->next = timer->next;
546                 if(timer->next != NULL)
547                     timer->next->prev = timer->prev;
548                 if(timer == timers)
549                     timers = timer->next;
550                 timer->func(0, timer->data);
551                 free(timer);
552                 break;
553             }
554         } while(timer != NULL);
555         do
556         {
557             for(child = children; child != NULL; child = child->next)
558             {
559                 if(child->finished)
560                 {
561                     child->callback(child->pid, child->status, child->data);
562                     if(child == children)
563                         children = child->next;
564                     if(child->prev != NULL)
565                         child->prev->next = child->next;
566                     if(child->next != NULL)
567                         child->next->prev = child->prev;
568                     free(child);
569                     break;
570                 }
571             }
572         } while(child != NULL);
573     }
574     flog(LOG_INFO, "terminating...");
575     terminate();
576     if(pidfile != NULL)
577         unlink(pidfile);
578     return(0);
579 }