chiark / gitweb /
57b4f052074d8a471c4936cd8b4952d3407f9759
[secnet.git] / secnet.c
1 /* $Log: secnet.c,v $
2  * Revision 1.1  1996/03/13 22:27:41  sde1000
3  * Initial revision
4  *
5  */
6
7 extern char version[];
8
9 #include "secnet.h"
10 #include <stdio.h>
11 #include <string.h>
12 #include <getopt.h>
13 #include <errno.h>
14 #include <unistd.h>
15 #include <sys/socket.h>
16 #include <arpa/inet.h>
17 #include <pwd.h>
18
19 #include "util.h"
20 #include "conffile.h"
21 #include "process.h"
22
23 /* XXX should be from autoconf */
24 static char *configfile="/etc/secnet/secnet.conf";
25 bool_t just_check_config=False;
26 static char *userid=NULL;
27 static uid_t uid=0;
28 bool_t background=True;
29 static char *pidfile=NULL;
30 bool_t require_root_privileges=False;
31 string_t require_root_privileges_explanation=NULL;
32
33 static pid_t secnet_pid;
34
35 /* from log.c */
36 extern uint32_t message_level;
37 extern bool_t secnet_is_daemon;
38 extern struct log_if *system_log;
39
40 /* from process.c */
41 extern void start_signal_handling(void);
42
43 /* Structures dealing with poll() call */
44 struct poll_interest {
45     beforepoll_fn *before;
46     afterpoll_fn *after;
47     void *state;
48     uint32_t max_nfds;
49     uint32_t nfds;
50     string_t desc;
51     struct poll_interest *next;
52 };
53 static struct poll_interest *reg=NULL;
54 static uint32_t total_nfds=10;
55
56 static bool_t finished=False;
57
58 /* Parse the command line options */
59 static void parse_options(int argc, char **argv)
60 {
61     int c;
62
63     while (True) {
64         int option_index = 0;
65         static struct option long_options[] = {
66             {"verbose", 0, 0, 'v'},
67             {"nowarnings", 0, 0, 'w'},
68             {"help", 0, 0, 2},
69             {"version", 0, 0, 1},
70             {"nodetach", 0, 0, 'n'},
71             {"silent", 0, 0, 'f'},
72             {"quiet", 0, 0, 'f'},
73             {"debug", 1, 0, 'd'},
74             {"config", 1, 0, 'c'},
75             {"just-check-config", 0, 0, 'j'},
76             {0,0,0,0}
77         };
78
79         c=getopt_long(argc, argv, "vwdnjc:ft:",
80                       long_options, &option_index);
81         if (c==-1)
82             break;
83
84         switch(c) {
85         case 2:
86             /* Help */
87             fprintf(stderr,
88                     "Usage: secnet [OPTION]...\n\n"
89                     "  -f, --silent, --quiet   suppress error messages\n"
90                     "  -w, --nowarnings        suppress warnings\n"
91                     "  -v, --verbose           output extra diagnostics\n"
92                     "  -c, --config=filename   specify a configuration file\n"
93                     "  -j, --just-check-config stop after reading configfile\n"
94                     "  -n, --nodetach          do not run in background\n"
95                     "  -d, --debug=item,...    set debug options\n"
96                     "      --help              display this help and exit\n"
97                     "      --version           output version information 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_ERROR|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 '?':
141             break;
142
143         default:
144             Message(M_ERROR,"secnet: Unknown getopt code %c\n",c);
145         }
146     }
147
148     if (argc-optind != 0) {
149         Message(M_ERROR,"secnet: You gave extra command line parameters, "
150                 "which were ignored.\n");
151     }
152 }
153
154 static void setup(dict_t *config)
155 {
156     list_t *l;
157     item_t *site;
158     dict_t *system;
159     struct passwd *pw;
160     struct cloc loc;
161     int i;
162
163     l=dict_lookup(config,"system");
164
165     if (!l || list_elem(l,0)->type!=t_dict) {
166         fatal("configuration does not include a \"system\" dictionary\n");
167     }
168     system=list_elem(l,0)->data.dict;
169     loc=list_elem(l,0)->loc;
170
171     /* Arrange systemwide log facility */
172     l=dict_lookup(system,"log");
173     if (!l) {
174         fatal("configuration does not include a system/log facility\n");
175     }
176     system_log=init_log(l);
177
178     /* Who are we supposed to run as? */
179     userid=dict_read_string(system,"userid",False,"system",loc);
180     if (userid) {
181         do {
182             pw=getpwent();
183             if (pw && strcmp(pw->pw_name,userid)==0) {
184                 uid=pw->pw_uid;
185                 break;
186             }
187         } while(pw);
188         endpwent();
189         if (uid==0) {
190             fatal("userid \"%s\" not found\n",userid);
191         }
192     }
193
194     /* Pidfile name */
195     pidfile=dict_read_string(system,"pidfile",False,"system",loc);
196
197     /* Check whether we need root privileges */
198     if (require_root_privileges && uid!=0) {
199         fatal("the following configured feature (\"%s\") requires "
200               "that secnet retain root privileges while running.\n",
201               require_root_privileges_explanation);
202     }
203
204     /* Go along site list, starting sites */
205     l=dict_lookup(config,"sites");
206     if (!l) {
207         Message(M_WARNING,"secnet: configuration did not define any "
208                 "remote sites\n");
209     } else {
210         i=0;
211         while ((site=list_elem(l, i++))) {
212             struct site_if *s;
213             if (site->type!=t_closure) {
214                 cfgfatal(site->loc,"system","non-closure in site list");
215             }
216             if (site->data.closure->type!=CL_SITE) {
217                 cfgfatal(site->loc,"system","non-site closure in site list");
218             }
219             s=site->data.closure->interface;
220             s->control(s->st,True);
221         }
222     }
223 }
224
225 void register_for_poll(void *st, beforepoll_fn *before,
226                        afterpoll_fn *after, uint32_t max_nfds, string_t desc)
227 {
228     struct poll_interest *i;
229
230     i=safe_malloc(sizeof(*i),"register_for_poll");
231     i->before=before;
232     i->after=after;
233     i->state=st;
234     i->max_nfds=max_nfds;
235     i->nfds=0;
236     i->desc=desc;
237     total_nfds+=max_nfds;
238     i->next=reg;
239     reg=i;
240     return;
241 }
242
243 static void system_phase_hook(void *sst, uint32_t newphase)
244 {
245     if (newphase==PHASE_SHUTDOWN && pidfile) {
246         /* Try to unlink the pidfile; don't care if it fails */
247         unlink(pidfile);
248     }
249 }
250
251 static void run(void)
252 {
253     struct timeval tv_now;
254     uint64_t now;
255     struct poll_interest *i;
256     int rv, nfds, remain, idx;
257     int timeout;
258     struct pollfd *fds;
259
260     fds=alloca(sizeof(*fds)*total_nfds);
261     if (!fds) {
262         fatal("run: couldn't alloca\n");
263     }
264
265     Message(M_NOTICE,"%s [%d]: starting\n",version,secnet_pid);
266
267     do {
268         if (gettimeofday(&tv_now, NULL)!=0) {
269             fatal_perror("main loop: gettimeofday");
270         }
271         now=(tv_now.tv_sec*1000)+(tv_now.tv_usec/1000);
272         idx=0;
273         for (i=reg; i; i=i->next) {
274             i->after(i->state, fds+idx, i->nfds, &tv_now, &now);
275             idx+=i->nfds;
276         }
277         remain=total_nfds;
278         idx=0;
279         timeout=-1;
280         for (i=reg; i; i=i->next) {
281             nfds=remain;
282             rv=i->before(i->state, fds+idx, &nfds, &timeout, &tv_now, &now);
283             if (rv!=0) {
284                 /* XXX we need to handle this properly: increase the
285                    nfds available */
286                 fatal("run: beforepoll_fn (%s) returns %d\n",i->desc,rv);
287             }
288             if (timeout<-1) {
289                 fatal("run: beforepoll_fn (%s) set timeout to %d\n",timeout);
290             }
291             idx+=nfds;
292             remain-=nfds;
293             i->nfds=nfds;
294         }
295         do {
296             if (finished) break;
297             rv=poll(fds, idx, timeout);
298             if (rv<0) {
299                 if (errno!=EINTR) {
300                     fatal_perror("run: poll");
301                 }
302             }
303         } while (rv<0);
304     } while (!finished);
305 }
306
307 static void droppriv(void)
308 {
309     FILE *pf=NULL;
310     pid_t p;
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, stdout and stderr; we don't need them any more */
346             close(0);
347             close(1);
348             close(2);
349             secnet_is_daemon=True;
350         } else {
351             /* Error */
352             fatal_perror("cannot fork");
353             exit(1);
354         }
355     }
356     secnet_pid=getpid();
357 }
358
359 static signal_notify_fn finish,ignore_hup;
360 static void finish(void *st, int signum)
361 {
362     finished=True;
363     Message(M_NOTICE,"%s [%d]: received %s\n",version,secnet_pid,(string_t)st);
364 }
365 static void ignore_hup(void *st, int signum)
366 {
367     Message(M_INFO,"%s [%d]: received SIGHUP\n",version,secnet_pid);
368     return;
369 }
370
371 int main(int argc, char **argv)
372 {
373     dict_t *config;
374
375     enter_phase(PHASE_GETOPTS);
376     parse_options(argc,argv);
377
378     enter_phase(PHASE_READCONFIG);
379     config=read_conffile(configfile);
380
381     enter_phase(PHASE_SETUP);
382     setup(config);
383
384     if (just_check_config) {
385         Message(M_INFO,"configuration file check complete\n");
386         exit(0);
387     }
388
389     enter_phase(PHASE_GETRESOURCES);
390     /* Appropriate phase hooks will have been run */
391     
392     enter_phase(PHASE_DROPPRIV);
393     droppriv();
394
395     start_signal_handling();
396     request_signal_notification(SIGTERM,finish,"SIGTERM");
397     if (!background) request_signal_notification(SIGINT,finish,"SIGINT");
398     request_signal_notification(SIGHUP,ignore_hup,NULL);
399     enter_phase(PHASE_RUN);
400     run();
401
402     enter_phase(PHASE_SHUTDOWN);
403     Message(M_NOTICE,"%s [%d]: finished\n",version,secnet_pid);
404
405     return 0;
406 }