chiark / gitweb /
integer arithmetic types: correct perhaps-possible negative timeout situation
[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     uint32_t max_nfds;
36     uint32_t nfds;
37     cstring_t desc;
38     struct poll_interest *next;
39 };
40 static struct poll_interest *reg=NULL;
41 static uint32_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, uint32_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 static void run(void)
251 {
252     struct timeval tv_now;
253     uint64_t now;
254     struct poll_interest *i;
255     int rv, nfds, remain, idx;
256     int timeout;
257     struct pollfd *fds;
258
259     fds=safe_malloc(sizeof(*fds)*total_nfds, "run");
260
261     Message(M_NOTICE,"%s [%d]: starting\n",version,secnet_pid);
262
263     do {
264         if (gettimeofday(&tv_now, NULL)!=0) {
265             fatal_perror("main loop: gettimeofday");
266         }
267         now=((uint64_t)tv_now.tv_sec*(uint64_t)1000)+
268             ((uint64_t)tv_now.tv_usec/(uint64_t)1000);
269         idx=0;
270         for (i=reg; i; i=i->next) {
271             i->after(i->state, fds+idx, i->nfds, &tv_now, &now);
272             idx+=i->nfds;
273         }
274         remain=total_nfds;
275         idx=0;
276         timeout=-1;
277         for (i=reg; i; i=i->next) {
278             nfds=remain;
279             rv=i->before(i->state, fds+idx, &nfds, &timeout, &tv_now, &now);
280             if (rv!=0) {
281                 /* XXX we need to handle this properly: increase the
282                    nfds available */
283                 fatal("run: beforepoll_fn (%s) returns %d",i->desc,rv);
284             }
285             if (timeout<-1) {
286                 fatal("run: beforepoll_fn (%s) set timeout to %d",timeout);
287             }
288             idx+=nfds;
289             remain-=nfds;
290             i->nfds=nfds;
291         }
292         do {
293             if (finished) break;
294             rv=poll(fds, idx, timeout);
295             if (rv<0) {
296                 if (errno!=EINTR) {
297                     fatal_perror("run: poll");
298                 }
299             }
300         } while (rv<0);
301     } while (!finished);
302     free(fds);
303 }
304
305 static void droppriv(void)
306 {
307     FILE *pf=NULL;
308     pid_t p;
309     int errfds[2];
310
311     add_hook(PHASE_SHUTDOWN,system_phase_hook,NULL);
312
313     /* Open the pidfile for writing now: we may be unable to do so
314        once we drop privileges. */
315     if (pidfile) {
316         pf=fopen(pidfile,"w");
317         if (!pf) {
318             fatal_perror("cannot open pidfile \"%s\"",pidfile);
319         }
320     }
321     if (!background && pf) {
322         fprintf(pf,"%d\n",getpid());
323         fclose(pf);
324     }
325
326     /* Now drop privileges */
327     if (uid!=0) {
328         if (setuid(uid)!=0) {
329             fatal_perror("can't set uid to \"%s\"",userid);
330         }
331     }
332     if (background) {
333         p=fork();
334         if (p>0) {
335             if (pf) {
336                 /* Parent process - write pidfile, exit */
337                 fprintf(pf,"%d\n",p);
338                 fclose(pf);
339             }
340             exit(0);
341         } else if (p==0) {
342             /* Child process - all done, just carry on */
343             if (pf) fclose(pf);
344             /* Close stdin and stdout; we don't need them any more.
345                stderr is redirected to the system/log facility */
346             if (pipe(errfds)!=0) {
347                 fatal_perror("can't create pipe for stderr");
348             }
349             close(0);
350             close(1);
351             close(2);
352             dup2(errfds[1],0);
353             dup2(errfds[1],1);
354             dup2(errfds[1],2);
355             secnet_is_daemon=True;
356             setsid();
357             log_from_fd(errfds[0],"stderr",system_log);
358         } else {
359             /* Error */
360             fatal_perror("cannot fork");
361             exit(1);
362         }
363     }
364     secnet_pid=getpid();
365 }
366
367 static signal_notify_fn finish,ignore_hup;
368 static void finish(void *st, int signum)
369 {
370     finished=True;
371     Message(M_NOTICE,"%s [%d]: received %s\n",version,secnet_pid,(string_t)st);
372 }
373 static void ignore_hup(void *st, int signum)
374 {
375     Message(M_INFO,"%s [%d]: received SIGHUP\n",version,secnet_pid);
376     return;
377 }
378
379 int main(int argc, char **argv)
380 {
381     dict_t *config;
382
383     enter_phase(PHASE_GETOPTS);
384     parse_options(argc,argv);
385
386     enter_phase(PHASE_READCONFIG);
387     config=read_conffile(configfile);
388
389     enter_phase(PHASE_SETUP);
390     setup(config);
391
392     if (just_check_config) {
393         Message(M_INFO,"configuration file check complete\n");
394         exit(0);
395     }
396
397     enter_phase(PHASE_GETRESOURCES);
398     /* Appropriate phase hooks will have been run */
399     
400     enter_phase(PHASE_DROPPRIV);
401     droppriv();
402
403     start_signal_handling();
404     request_signal_notification(SIGTERM,finish,safe_strdup("SIGTERM","run"));
405     if (!background) request_signal_notification(SIGINT,finish,
406                                                  safe_strdup("SIGINT","run"));
407     request_signal_notification(SIGHUP,ignore_hup,NULL);
408     enter_phase(PHASE_RUN);
409     run();
410
411     enter_phase(PHASE_SHUTDOWN);
412     Message(M_NOTICE,"%s [%d]: finished\n",version,secnet_pid);
413
414     return 0;
415 }