Change default hub list.
[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, immsyslog;
375     struct module *mod;
376     struct timer *timer, *ntimer;
377     struct child *child;
378     double now;
379     
380     immsyslog = 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             immsyslog = 1;
429             break;
430         case 'h':
431         case ':':
432         case '?':
433         default:
434             printf("usage: doldacond [-hns] [-C configfile] [-p pidfile] [-f facility]\n");
435             exit(c != 'h');
436         }
437     }
438     setlocale(LC_ALL, "");
439     initlog();
440     if(immsyslog)
441     {
442         logtosyslog = 1;
443         logtostderr = 0;
444     }
445     signal(SIGPIPE, SIG_IGN);
446     signal(SIGHUP, handler);
447     signal(SIGINT, handler);
448     signal(SIGTERM, handler);
449     signal(SIGCHLD, handler);
450     signal(SIGUSR1, handler);
451     signal(SIGUSR2, handler);
452     preinit(0);
453     if(configfile == NULL)
454     {
455         if((configfile = findconfigfile()) == NULL)
456         {
457             flog(LOG_CRIT, "could not find a configuration file");
458             exit(1);
459         }
460     }
461     pfstream = NULL;
462     if(pidfile != NULL)
463     {
464         if((pfstream = fopen(pidfile, "w")) == NULL)
465         {
466             flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno));
467             exit(1);
468         }
469     }
470     if((confstream = fopen(configfile, "r")) == NULL)
471     {
472         flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno));
473         exit(1);
474     }
475     readconfig(confstream);
476     fclose(confstream);
477     init(0);
478     if(!nofork)
479     {
480         logtosyslog = 1;
481         daemon(0, 0);
482         flog(LOG_INFO, "daemonized");
483         logtostderr = 0;
484     }
485     if(pfstream != NULL) {
486         fprintf(pfstream, "%i\n", getpid());
487         fclose(pfstream);
488     }
489     running = 1;
490     reinit = 0;
491     while(running)
492     {
493         if(reinit)
494         {
495             if((confstream = fopen(configfile, "r")) == NULL)
496             {
497                 flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno));
498             } else {
499                 preinit(1);
500                 readconfig(confstream);
501                 fclose(confstream);
502                 init(1);
503             }
504             reinit = 0;
505         }
506         delay = 1000; /* -1; */
507         for(mod = modchain; mod != NULL; mod = mod->next)
508         {
509             if(mod->run && mod->run())
510                 delay = 0;
511         }
512         if(!running)
513             delay = 0;
514         if(delay != 0)
515         {
516             now = ntime();
517             for(timer = timers; timer != NULL; timer = timer->next)
518             {
519                 if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay))
520                     delay = (int)((timer->at - now) * 1000.0);
521             }
522         }
523         if(childrendone)
524         {
525             delay = 0;
526             childrendone = 0;
527         }
528         pollsocks(delay);
529         now = ntime();
530         for(timer = timers; timer != NULL; timer = ntimer)
531         {
532             ntimer = timer->next;
533             if(now < timer->at)
534                 continue;
535             if(timer->prev != NULL)
536                 timer->prev->next = timer->next;
537             if(timer->next != NULL)
538                 timer->next->prev = timer->prev;
539             if(timer == timers)
540                 timers = timer->next;
541             timer->func(0, timer->data);
542             free(timer);
543         }
544         do
545         {
546             for(child = children; child != NULL; child = child->next)
547             {
548                 if(child->finished)
549                 {
550                     child->callback(child->pid, child->status, child->data);
551                     if(child == children)
552                         children = child->next;
553                     if(child->prev != NULL)
554                         child->prev->next = child->next;
555                     if(child->next != NULL)
556                         child->next->prev = child->prev;
557                     free(child);
558                     break;
559                 }
560             }
561         } while(child != NULL);
562     }
563     flog(LOG_INFO, "terminating...");
564     terminate();
565     if(pidfile != NULL)
566         unlink(pidfile);
567     return(0);
568 }