chiark / gitweb /
Import release 0.1.14
[secnet.git] / log.c
1 #include "secnet.h"
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <time.h>
6 #include <errno.h>
7 #include <syslog.h>
8 #include <assert.h>
9 #include "process.h"
10
11 bool_t secnet_is_daemon=False;
12 uint32_t message_level=M_WARNING|M_ERR|M_SECURITY|M_FATAL;
13 struct log_if *system_log=NULL;
14
15 static void vMessage(uint32_t class, char *message, va_list args)
16 {
17     FILE *dest=stdout;
18 #define MESSAGE_BUFLEN 1023
19     static char buff[MESSAGE_BUFLEN+1]={0,};
20     uint32_t bp;
21     char *nlp;
22
23     if (secnet_is_daemon) {
24         /* Messages go to the system log interface */
25         bp=strlen(buff);
26         vsnprintf(buff+bp,MESSAGE_BUFLEN-bp,message,args);
27         /* Each line is sent separately */
28         while ((nlp=strchr(buff,'\n'))) {
29             *nlp=0;
30             log(system_log,class,buff);
31             memmove(buff,nlp+1,strlen(nlp+1)+1);
32         }
33     } else {
34         /* Messages go to stdout/stderr */
35         if (class & message_level) {
36             if (class&M_FATAL || class&M_ERR || class&M_WARNING) {
37                 dest=stderr;
38             }
39             vfprintf(dest,message,args);
40         }
41     }
42 }  
43
44 void Message(uint32_t class, char *message, ...)
45 {
46     va_list ap;
47
48     va_start(ap,message);
49     vMessage(class,message,ap);
50     va_end(ap);
51 }
52
53 static NORETURN(vfatal(int status, bool_t perror, char *message,
54                        va_list args));
55
56 static void vfatal(int status, bool_t perror, char *message, va_list args)
57 {
58     int err;
59
60     err=errno;
61
62     enter_phase(PHASE_SHUTDOWN);
63     Message(M_FATAL, "secnet fatal error: ");
64     vMessage(M_FATAL, message, args);
65     if (perror)
66         Message(M_FATAL, ": %s\n",strerror(err));
67     else
68         Message(M_FATAL, "\n");
69     exit(status);
70 }
71
72 void fatal(char *message, ...)
73 {
74     va_list args;
75     va_start(args,message);
76     vfatal(current_phase,False,message,args);
77     va_end(args);
78 }
79
80 void fatal_status(int status, char *message, ...)
81 {
82     va_list args;
83     va_start(args,message);
84     vfatal(status,False,message,args);
85     va_end(args);
86 }
87
88 void fatal_perror(char *message, ...)
89 {
90     va_list args;
91     va_start(args,message);
92     vfatal(current_phase,True,message,args);
93     va_end(args);
94 }
95
96 void fatal_perror_status(int status, char *message, ...)
97 {
98     va_list args;
99     va_start(args,message);
100     vfatal(status,True,message,args);
101     va_end(args);
102 }
103
104 void vcfgfatal_maybefile(FILE *maybe_f /* or 0 */, struct cloc loc,
105                          string_t facility, char *message, va_list args)
106 {
107     enter_phase(PHASE_SHUTDOWN);
108
109     if (maybe_f && ferror(maybe_f)) {
110         assert(loc.file);
111         Message(M_FATAL, "error reading config file (%s, %s): %s",
112                 facility, loc.file, strerror(errno));
113     } else if (maybe_f && feof(maybe_f)) {
114         assert(loc.file);
115         Message(M_FATAL, "unexpected end of config file (%s, %s)",
116                 facility, loc.file);
117     } else if (loc.file && loc.line) {
118         Message(M_FATAL, "config error (%s, %s:%d): ",facility,loc.file,
119                 loc.line);
120     } else if (!loc.file && loc.line) {
121         Message(M_FATAL, "config error (%s, line %d): ",facility,loc.line);
122     } else {
123         Message(M_FATAL, "config error (%s): ",facility);
124     }
125     
126     vMessage(M_FATAL,message,args);
127     exit(current_phase);
128 }
129
130 void cfgfatal_maybefile(FILE *maybe_f, struct cloc loc, string_t facility,
131                         char *message, ...)
132 {
133     va_list args;
134
135     va_start(args,message);
136     vcfgfatal_maybefile(maybe_f,loc,facility,message,args);
137     va_end(args);
138 }    
139
140 void cfgfatal(struct cloc loc, string_t facility, char *message, ...)
141 {
142     va_list args;
143
144     va_start(args,message);
145     vcfgfatal_maybefile(0,loc,facility,message,args);
146     va_end(args);
147 }
148
149 void cfgfile_postreadcheck(struct cloc loc, FILE *f)
150 {
151     assert(loc.file);
152     if (ferror(f)) {
153         Message(M_FATAL, "error reading config file (%s): %s",
154                 loc.file, strerror(errno));
155         exit(current_phase);
156     } else if (feof(f)) {
157         Message(M_FATAL, "unexpected end of config file (%s)", loc.file);
158         exit(current_phase);
159     }
160 }
161
162 /* Take a list of log closures and merge them */
163 struct loglist {
164     struct log_if *l;
165     struct loglist *next;
166 };
167
168 static void log_vmulti(void *sst, int class, char *message, va_list args)
169 {
170     struct loglist *st=sst, *i;
171
172     if (secnet_is_daemon) {
173         for (i=st; i; i=i->next) {
174             i->l->vlog(i->l->st,class,message,args);
175         }
176     } else {
177         vMessage(class,message,args);
178         Message(class,"\n");
179     }
180 }
181
182 static void log_multi(void *st, int priority, char *message, ...)
183 {
184     va_list ap;
185
186     va_start(ap,message);
187     log_vmulti(st,priority,message,ap);
188     va_end(ap);
189 }
190
191 struct log_if *init_log(list_t *ll)
192 {
193     int i=0;
194     item_t *item;
195     closure_t *cl;
196     struct loglist *l=NULL, *n;
197     struct log_if *r;
198
199     if (list_length(ll)==1) {
200         item=list_elem(ll,0);
201         cl=item->data.closure;
202         if (cl->type!=CL_LOG) {
203             cfgfatal(item->loc,"init_log","closure is not a logger");
204         }
205         return cl->interface;
206     }
207     while ((item=list_elem(ll,i++))) {
208         if (item->type!=t_closure) {
209             cfgfatal(item->loc,"init_log","item is not a closure");
210         }
211         cl=item->data.closure;
212         if (cl->type!=CL_LOG) {
213             cfgfatal(item->loc,"init_log","closure is not a logger");
214         }
215         n=safe_malloc(sizeof(*n),"init_log");
216         n->l=cl->interface;
217         n->next=l;
218         l=n;
219     }
220     if (!l) {
221         fatal("init_log: no log");
222     }
223     r=safe_malloc(sizeof(*r), "init_log");
224     r->st=l;
225     r->log=log_multi;
226     r->vlog=log_vmulti;
227     return r;
228 }
229
230 struct logfile {
231     closure_t cl;
232     struct log_if ops;
233     struct cloc loc;
234     string_t logfile;
235     uint32_t level;
236     FILE *f;
237 };
238
239 static string_t months[]={
240     "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
241
242 static void logfile_vlog(void *sst, int class, char *message, va_list args)
243 {
244     struct logfile *st=sst;
245     time_t t;
246     struct tm *tm;
247
248     if (secnet_is_daemon) {
249         if (class&st->level) {
250             t=time(NULL);
251             tm=localtime(&t);
252             fprintf(st->f,"%s %2d %02d:%02d:%02d ",
253                     months[tm->tm_mon],tm->tm_mday,tm->tm_hour,tm->tm_min,
254                     tm->tm_sec);
255             vfprintf(st->f,message,args);
256             fprintf(st->f,"\n");
257             fflush(st->f);
258         }
259     } else {
260         vMessage(class,message,args);
261         Message(class,"\n");
262     }
263 }
264
265 static void logfile_log(void *state, int priority, char *message, ...)
266 {
267     va_list ap;
268
269     va_start(ap,message);
270     logfile_vlog(state,priority,message,ap);
271     va_end(ap);
272 }
273
274 static void logfile_hup_notify(void *sst, int signum)
275 {
276     struct logfile *st=sst;
277     FILE *f;
278     f=fopen(st->logfile,"a");
279     if (!f) {
280         logfile_log(st,M_FATAL,"received SIGHUP, but could not reopen "
281                     "logfile: %s",strerror(errno));
282     } else {
283         fclose(st->f);
284         st->f=f;
285         logfile_log(st,M_INFO,"received SIGHUP");
286     }
287 }
288
289 static void logfile_phase_hook(void *sst, uint32_t new_phase)
290 {
291     struct logfile *st=sst;
292     FILE *f;
293
294     if (background) {
295         f=fopen(st->logfile,"a");
296         if (!f) fatal_perror("logfile (%s:%d): cannot open \"%s\"",
297                              st->loc.file,st->loc.line,st->logfile);
298         st->f=f;
299         request_signal_notification(SIGHUP, logfile_hup_notify,st);
300     }
301 }
302
303 static struct flagstr message_class_table[]={
304     { "debug-config", M_DEBUG_CONFIG },
305     { "debug-phase", M_DEBUG_PHASE },
306     { "debug", M_DEBUG },
307     { "all-debug", M_DEBUG|M_DEBUG_PHASE|M_DEBUG_CONFIG },
308     { "info", M_INFO },
309     { "notice", M_NOTICE },
310     { "warning", M_WARNING },
311     { "error", M_ERR },
312     { "security", M_SECURITY },
313     { "fatal", M_FATAL },
314     { "default", M_WARNING|M_ERR|M_SECURITY|M_FATAL },
315     { "verbose", M_INFO|M_NOTICE|M_WARNING|M_ERR|M_SECURITY|M_FATAL },
316     { "quiet", M_FATAL },
317     { NULL, 0 }
318 };
319
320 static list_t *logfile_apply(closure_t *self, struct cloc loc, dict_t *context,
321                              list_t *args)
322 {
323     struct logfile *st;
324     item_t *item;
325     dict_t *dict;
326
327     /* We should defer opening the logfile until the getresources
328        phase.  We should defer writing into the logfile until after we
329        become a daemon. */
330     
331     st=safe_malloc(sizeof(*st),"logfile_apply");
332     st->cl.description="logfile";
333     st->cl.type=CL_LOG;
334     st->cl.apply=NULL;
335     st->cl.interface=&st->ops;
336     st->ops.st=st;
337     st->ops.log=logfile_log;
338     st->ops.vlog=logfile_vlog;
339     st->loc=loc;
340     st->f=stderr;
341
342     item=list_elem(args,0);
343     if (!item || item->type!=t_dict) {
344         cfgfatal(loc,"logfile","argument must be a dictionary\n");
345     }
346     dict=item->data.dict;
347
348     st->logfile=dict_read_string(dict,"filename",True,"logfile",loc);
349     st->level=string_list_to_word(dict_lookup(dict,"class"),
350                                        message_class_table,"logfile");
351
352     add_hook(PHASE_GETRESOURCES,logfile_phase_hook,st);
353
354     return new_closure(&st->cl);
355 }
356
357 struct syslog {
358     closure_t cl;
359     struct log_if ops;
360     string_t ident;
361     int facility;
362     bool_t open;
363 };
364
365 static int msgclass_to_syslogpriority(uint32_t m)
366 {
367     switch (m) {
368     case M_DEBUG_CONFIG: return LOG_DEBUG;
369     case M_DEBUG_PHASE: return LOG_DEBUG;
370     case M_DEBUG: return LOG_DEBUG;
371     case M_INFO: return LOG_INFO;
372     case M_NOTICE: return LOG_NOTICE;
373     case M_WARNING: return LOG_WARNING;
374     case M_ERR: return LOG_ERR;
375     case M_SECURITY: return LOG_CRIT;
376     case M_FATAL: return LOG_EMERG;
377     default: return LOG_NOTICE;
378     }
379 }
380     
381 static void syslog_vlog(void *sst, int class, char *message,
382                          va_list args)
383 {
384     struct syslog *st=sst;
385
386     if (st->open)
387         vsyslog(msgclass_to_syslogpriority(class),message,args);
388     else {
389         vMessage(class,message,args);
390         Message(class,"\n");
391     }
392 }
393
394 static void syslog_log(void *sst, int priority, char *message, ...)
395 {
396     va_list ap;
397
398     va_start(ap,message);
399     syslog_vlog(sst,priority,message,ap);
400     va_end(ap);
401 }
402
403 static struct flagstr syslog_facility_table[]={
404 #ifdef LOG_AUTH
405     { "auth", LOG_AUTH },
406 #endif
407 #ifdef LOG_AUTHPRIV
408     { "authpriv", LOG_AUTHPRIV },
409 #endif
410     { "cron", LOG_CRON },
411     { "daemon", LOG_DAEMON },
412     { "kern", LOG_KERN },
413     { "local0", LOG_LOCAL0 },
414     { "local1", LOG_LOCAL1 },
415     { "local2", LOG_LOCAL2 },
416     { "local3", LOG_LOCAL3 },
417     { "local4", LOG_LOCAL4 },
418     { "local5", LOG_LOCAL5 },
419     { "local6", LOG_LOCAL6 },
420     { "local7", LOG_LOCAL7 },
421     { "lpr", LOG_LPR },
422     { "mail", LOG_MAIL },
423     { "news", LOG_NEWS },
424     { "syslog", LOG_SYSLOG },
425     { "user", LOG_USER },
426     { "uucp", LOG_UUCP },
427     { NULL, 0 }
428 };
429
430 static void syslog_phase_hook(void *sst, uint32_t newphase)
431 {
432     struct syslog *st=sst;
433
434     if (background) {
435         openlog(st->ident,0,st->facility);
436         st->open=True;
437     }
438 }
439
440 static list_t *syslog_apply(closure_t *self, struct cloc loc, dict_t *context,
441                             list_t *args)
442 {
443     struct syslog *st;
444     dict_t *d;
445     item_t *item;
446     string_t facstr;
447
448     st=safe_malloc(sizeof(*st),"syslog_apply");
449     st->cl.description="syslog";
450     st->cl.type=CL_LOG;
451     st->cl.apply=NULL;
452     st->cl.interface=&st->ops;
453     st->ops.st=st;
454     st->ops.log=syslog_log;
455     st->ops.vlog=syslog_vlog;
456
457     item=list_elem(args,0);
458     if (!item || item->type!=t_dict)
459         cfgfatal(loc,"syslog","parameter must be a dictionary\n");
460     d=item->data.dict;
461
462     st->ident=dict_read_string(d, "ident", False, "syslog", loc);
463     facstr=dict_read_string(d, "facility", True, "syslog", loc);
464     st->facility=string_to_word(facstr,loc,
465                                 syslog_facility_table,"syslog");
466     st->open=False;
467     add_hook(PHASE_GETRESOURCES,syslog_phase_hook,st);
468
469     return new_closure(&st->cl);
470 }    
471
472 init_module log_module;
473 void log_module(dict_t *dict)
474 {
475     add_closure(dict,"logfile",logfile_apply);
476     add_closure(dict,"syslog",syslog_apply);
477 }