chiark / gitweb /
f56fac235089940704eacb8802a24e2728af1ba8
[secnet.git] / secnet.c
1 #include "secnet.h"
2 #include <stdio.h>
3 #include <assert.h>
4 #include <limits.h>
5 #include <string.h>
6 #include <getopt.h>
7 #include <errno.h>
8 #include <unistd.h>
9 #include <sys/socket.h>
10 #include <arpa/inet.h>
11 #include <pwd.h>
12
13 #include "util.h"
14 #include "conffile.h"
15 #include "process.h"
16
17 /* XXX should be from autoconf */
18 static const char *configfile="/etc/secnet/secnet.conf";
19 static const char *sites_key="sites";
20 bool_t just_check_config=False;
21 static char *userid=NULL;
22 static uid_t uid=0;
23 bool_t background=True;
24 static char *pidfile=NULL;
25 bool_t require_root_privileges=False;
26 cstring_t require_root_privileges_explanation=NULL;
27
28 static pid_t secnet_pid;
29
30 /* Structures dealing with poll() call */
31 struct poll_interest {
32     beforepoll_fn *before;
33     afterpoll_fn *after;
34     void *state;
35     int32_t max_nfds;
36     int32_t nfds;
37     cstring_t desc;
38     struct poll_interest *next;
39 };
40 static struct poll_interest *reg=NULL;
41 static int32_t total_nfds=10;
42
43 static bool_t finished=False;
44
45 /* Parse the command line options */
46 static void parse_options(int argc, char **argv)
47 {
48     int c;
49
50     while (True) {
51         int option_index = 0;
52         static struct option long_options[] = {
53             {"verbose", 0, 0, 'v'},
54             {"nowarnings", 0, 0, 'w'},
55             {"help", 0, 0, 2},
56             {"version", 0, 0, 1},
57             {"nodetach", 0, 0, 'n'},
58             {"silent", 0, 0, 'f'},
59             {"quiet", 0, 0, 'f'},
60             {"debug", 1, 0, 'd'},
61             {"config", 1, 0, 'c'},
62             {"just-check-config", 0, 0, 'j'},
63             {"sites-key", 1, 0, 's'},
64             {0,0,0,0}
65         };
66
67         c=getopt_long(argc, argv, "vwdnjc:ft:s:",
68                       long_options, &option_index);
69         if (c==-1)
70             break;
71
72         switch(c) {
73         case 2:
74             /* Help */
75             printf("Usage: secnet [OPTION]...\n\n"
76                    "  -f, --silent, --quiet   suppress error messages\n"
77                    "  -w, --nowarnings        suppress warnings\n"
78                    "  -v, --verbose           output extra diagnostics\n"
79                    "  -c, --config=filename   specify a configuration file\n"
80                    "  -j, --just-check-config stop after reading "
81                    "configuration file\n"
82                    "  -s, --sites-key=name    configuration key that "
83                    "specifies active sites\n"
84                    "  -n, --nodetach          do not run in background\n"
85                    "  -d, --debug=item,...    set debug options\n"
86                    "      --help              display this help and exit\n"
87                    "      --version           output version information "
88                    "and exit\n"
89                 );
90             exit(0);
91             break;
92       
93         case 1:
94             /* Version */
95             printf("%s\n",version);
96             exit(0);
97             break;
98
99         case 'v':
100             message_level|=M_INFO|M_NOTICE|M_WARNING|M_ERR|M_SECURITY|
101                 M_FATAL;
102             break;
103
104         case 'w':
105             message_level&=(~M_WARNING);
106             break;
107
108         case 'd':
109             message_level|=M_DEBUG_CONFIG|M_DEBUG_PHASE|M_DEBUG;
110             break;
111
112         case 'f':
113             message_level=M_FATAL;
114             break;
115
116         case 'n':
117             background=False;
118             break;
119
120         case 'c':
121             if (optarg)
122                 configfile=safe_strdup(optarg,"config_filename");
123             else
124                 fatal("secnet: no config filename specified");
125             break;
126
127         case 'j':
128             just_check_config=True;
129             break;
130
131         case 's':
132             if (optarg)
133                 sites_key=safe_strdup(optarg,"sites-key");
134             else
135                 fatal("secnet: no sites key specified");
136             break;
137
138         case '?':
139             break;
140
141         default:
142             Message(M_ERR,"secnet: Unknown getopt code %c\n",c);
143         }
144     }
145
146     if (argc-optind != 0) {
147         Message(M_ERR,"secnet: You gave extra command line parameters, "
148                 "which were ignored.\n");
149     }
150 }
151
152 static void setup(dict_t *config)
153 {
154     list_t *l;
155     item_t *site;
156     dict_t *system;
157     struct passwd *pw;
158     struct cloc loc;
159     int i;
160
161     l=dict_lookup(config,"system");
162
163     if (!l || list_elem(l,0)->type!=t_dict) {
164         fatal("configuration does not include a \"system\" dictionary");
165     }
166     system=list_elem(l,0)->data.dict;
167     loc=list_elem(l,0)->loc;
168
169     /* Arrange systemwide log facility */
170     l=dict_lookup(system,"log");
171     if (!l) {
172         fatal("configuration does not include a system/log facility");
173     }
174     system_log=init_log(l);
175
176     /* Who are we supposed to run as? */
177     userid=dict_read_string(system,"userid",False,"system",loc);
178     if (userid) {
179         do {
180             pw=getpwent();
181             if (pw && strcmp(pw->pw_name,userid)==0) {
182                 uid=pw->pw_uid;
183                 break;
184             }
185         } while(pw);
186         endpwent();
187         if (uid==0) {
188             fatal("userid \"%s\" not found",userid);
189         }
190     }
191
192     /* Pidfile name */
193     pidfile=dict_read_string(system,"pidfile",False,"system",loc);
194
195     /* Check whether we need root privileges */
196     if (require_root_privileges && uid!=0) {
197         fatal("the configured feature \"%s\" requires "
198               "that secnet retain root privileges while running.",
199               require_root_privileges_explanation);
200     }
201
202     /* Go along site list, starting sites */
203     l=dict_lookup(config,sites_key);
204     if (!l) {
205         Message(M_WARNING,"secnet: configuration key \"%s\" is missing; no "
206                 "remote sites are defined\n",sites_key);
207     } else {
208         i=0;
209         while ((site=list_elem(l, i++))) {
210             struct site_if *s;
211             if (site->type!=t_closure) {
212                 cfgfatal(site->loc,"system","non-closure in site list");
213             }
214             if (site->data.closure->type!=CL_SITE) {
215                 cfgfatal(site->loc,"system","non-site closure in site list");
216             }
217             s=site->data.closure->interface;
218             s->control(s->st,True);
219         }
220     }
221 }
222
223 void register_for_poll(void *st, beforepoll_fn *before,
224                        afterpoll_fn *after, int32_t max_nfds, cstring_t desc)
225 {
226     struct poll_interest *i;
227
228     i=safe_malloc(sizeof(*i),"register_for_poll");
229     i->before=before;
230     i->after=after;
231     i->state=st;
232     i->max_nfds=max_nfds;
233     i->nfds=0;
234     i->desc=desc;
235     assert(total_nfds < INT_MAX - max_nfds);
236     total_nfds+=max_nfds;
237     i->next=reg;
238     reg=i;
239     return;
240 }
241
242 static void system_phase_hook(void *sst, uint32_t newphase)
243 {
244     if (newphase==PHASE_SHUTDOWN && pidfile) {
245         /* Try to unlink the pidfile; don't care if it fails */
246         unlink(pidfile);
247     }
248 }
249
250 struct timeval tv_now_global;
251 uint64_t now_global;
252
253 static void run(void)
254 {
255     struct poll_interest *i;
256     int rv, nfds, remain, idx;
257     int timeout;
258     struct pollfd *fds;
259
260     fds=safe_malloc(sizeof(*fds)*total_nfds, "run");
261
262     Message(M_NOTICE,"%s [%d]: starting\n",version,secnet_pid);
263
264     do {
265         if (gettimeofday(&tv_now_global, NULL)!=0) {
266             fatal_perror("main loop: gettimeofday");
267         }
268         now_global=((uint64_t)tv_now_global.tv_sec*(uint64_t)1000)+
269                    ((uint64_t)tv_now_global.tv_usec/(uint64_t)1000);
270         idx=0;
271         for (i=reg; i; i=i->next) {
272             i->after(i->state, fds+idx, i->nfds, &tv_now_global, &now_global);
273             idx+=i->nfds;
274         }
275         remain=total_nfds;
276         idx=0;
277         timeout=-1;
278         for (i=reg; i; i=i->next) {
279             nfds=remain;
280             rv=i->before(i->state, fds+idx, &nfds, &timeout, &tv_now_global, &now_global);
281             if (rv!=0) {
282                 /* XXX we need to handle this properly: increase the
283                    nfds available */
284                 fatal("run: beforepoll_fn (%s) returns %d",i->desc,rv);
285             }
286             if (timeout<-1) {
287                 fatal("run: beforepoll_fn (%s) set timeout to %d",timeout);
288             }
289             idx+=nfds;
290             remain-=nfds;
291             i->nfds=nfds;
292         }
293         do {
294             if (finished) break;
295             rv=poll(fds, idx, timeout);
296             if (rv<0) {
297                 if (errno!=EINTR) {
298                     fatal_perror("run: poll");
299                 }
300             }
301         } while (rv<0);
302     } while (!finished);
303     free(fds);
304 }
305
306 static void droppriv(void)
307 {
308     FILE *pf=NULL;
309     pid_t p;
310     int errfds[2];
311
312     add_hook(PHASE_SHUTDOWN,system_phase_hook,NULL);
313
314     /* Open the pidfile for writing now: we may be unable to do so
315        once we drop privileges. */
316     if (pidfile) {
317         pf=fopen(pidfile,"w");
318         if (!pf) {
319             fatal_perror("cannot open pidfile \"%s\"",pidfile);
320         }
321     }
322     if (!background && pf) {
323         fprintf(pf,"%d\n",getpid());
324         fclose(pf);
325     }
326
327     /* Now drop privileges */
328     if (uid!=0) {
329         if (setuid(uid)!=0) {
330             fatal_perror("can't set uid to \"%s\"",userid);
331         }
332     }
333     if (background) {
334         p=fork();
335         if (p>0) {
336             if (pf) {
337                 /* Parent process - write pidfile, exit */
338                 fprintf(pf,"%d\n",p);
339                 fclose(pf);
340             }
341             exit(0);
342         } else if (p==0) {
343             /* Child process - all done, just carry on */
344             if (pf) fclose(pf);
345             /* Close stdin and stdout; we don't need them any more.
346                stderr is redirected to the system/log facility */
347             if (pipe(errfds)!=0) {
348                 fatal_perror("can't create pipe for stderr");
349             }
350             close(0);
351             close(1);
352             close(2);
353             dup2(errfds[1],0);
354             dup2(errfds[1],1);
355             dup2(errfds[1],2);
356             secnet_is_daemon=True;
357             setsid();
358             log_from_fd(errfds[0],"stderr",system_log);
359         } else {
360             /* Error */
361             fatal_perror("cannot fork");
362             exit(1);
363         }
364     }
365     secnet_pid=getpid();
366 }
367
368 static signal_notify_fn finish,ignore_hup;
369 static void finish(void *st, int signum)
370 {
371     finished=True;
372     Message(M_NOTICE,"%s [%d]: received %s\n",version,secnet_pid,(string_t)st);
373 }
374 static void ignore_hup(void *st, int signum)
375 {
376     Message(M_INFO,"%s [%d]: received SIGHUP\n",version,secnet_pid);
377     return;
378 }
379
380 int main(int argc, char **argv)
381 {
382     dict_t *config;
383
384     enter_phase(PHASE_GETOPTS);
385     parse_options(argc,argv);
386
387     enter_phase(PHASE_READCONFIG);
388     config=read_conffile(configfile);
389
390     enter_phase(PHASE_SETUP);
391     setup(config);
392
393     if (just_check_config) {
394         Message(M_INFO,"configuration file check complete\n");
395         exit(0);
396     }
397
398     enter_phase(PHASE_GETRESOURCES);
399     /* Appropriate phase hooks will have been run */
400     
401     enter_phase(PHASE_DROPPRIV);
402     droppriv();
403
404     start_signal_handling();
405     request_signal_notification(SIGTERM,finish,safe_strdup("SIGTERM","run"));
406     if (!background) request_signal_notification(SIGINT,finish,
407                                                  safe_strdup("SIGINT","run"));
408     request_signal_notification(SIGHUP,ignore_hup,NULL);
409     enter_phase(PHASE_RUN);
410     run();
411
412     enter_phase(PHASE_SHUTDOWN);
413     Message(M_NOTICE,"%s [%d]: finished\n",version,secnet_pid);
414
415     return 0;
416 }