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