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