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