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