1 /*
2 * Copyright (C) 2004, 2005, 2008 Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 1996-1999, 2001, 2003 Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include "port_before.h"
19
20 #include <sys/types.h>
21 #include <sys/time.h>
22 #include <sys/stat.h>
23
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <syslog.h>
31 #include <errno.h>
32 #include <time.h>
33 #include <unistd.h>
34
35 #include <isc/assertions.h>
36 #include <isc/logging.h>
37 #include <isc/memcluster.h>
38 #include <isc/misc.h>
39
40 #include "port_after.h"
41
42 #include "logging_p.h"
43
44 static const int syslog_priority[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE,
45 LOG_WARNING, LOG_ERR, LOG_CRIT };
46
47 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
48 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
49
50 static const char *level_text[] = {
51 "info: ", "notice: ", "warning: ", "error: ", "critical: "
52 };
53
54 static void
version_rename(log_channel chan)55 version_rename(log_channel chan) {
56 unsigned int ver;
57 char old_name[PATH_MAX+1];
58 char new_name[PATH_MAX+1];
59
60 ver = chan->out.file.versions;
61 if (ver < 1)
62 return;
63 if (ver > LOG_MAX_VERSIONS)
64 ver = LOG_MAX_VERSIONS;
65 /*
66 * Need to have room for '.nn' (XXX assumes LOG_MAX_VERSIONS < 100)
67 */
68 if (strlen(chan->out.file.name) > (size_t)(PATH_MAX-3))
69 return;
70 for (ver--; ver > 0; ver--) {
71 sprintf(old_name, "%s.%d", chan->out.file.name, ver-1);
72 sprintf(new_name, "%s.%d", chan->out.file.name, ver);
73 (void)isc_movefile(old_name, new_name);
74 }
75 sprintf(new_name, "%s.0", chan->out.file.name);
76 (void)isc_movefile(chan->out.file.name, new_name);
77 }
78
79 FILE *
log_open_stream(log_channel chan)80 log_open_stream(log_channel chan) {
81 FILE *stream;
82 int fd, flags;
83 struct stat sb;
84 int regular;
85
86 if (chan == NULL || chan->type != log_file) {
87 errno = EINVAL;
88 return (NULL);
89 }
90
91 /*
92 * Don't open already open streams
93 */
94 if (chan->out.file.stream != NULL)
95 return (chan->out.file.stream);
96
97 if (stat(chan->out.file.name, &sb) < 0) {
98 if (errno != ENOENT) {
99 syslog(LOG_ERR,
100 "log_open_stream: stat of %s failed: %s",
101 chan->out.file.name, strerror(errno));
102 chan->flags |= LOG_CHANNEL_BROKEN;
103 return (NULL);
104 }
105 regular = 1;
106 } else
107 regular = (sb.st_mode & S_IFREG);
108
109 if (chan->out.file.versions) {
110 if (!regular) {
111 syslog(LOG_ERR,
112 "log_open_stream: want versions but %s isn't a regular file",
113 chan->out.file.name);
114 chan->flags |= LOG_CHANNEL_BROKEN;
115 errno = EINVAL;
116 return (NULL);
117 }
118 }
119
120 flags = O_WRONLY|O_CREAT|O_APPEND;
121
122 if ((chan->flags & LOG_TRUNCATE) != 0) {
123 if (regular) {
124 (void)unlink(chan->out.file.name);
125 flags |= O_EXCL;
126 } else {
127 syslog(LOG_ERR,
128 "log_open_stream: want truncation but %s isn't a regular file",
129 chan->out.file.name);
130 chan->flags |= LOG_CHANNEL_BROKEN;
131 errno = EINVAL;
132 return (NULL);
133 }
134 }
135
136 fd = open(chan->out.file.name, flags,
137 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
138 if (fd < 0) {
139 syslog(LOG_ERR, "log_open_stream: open(%s) failed: %s",
140 chan->out.file.name, strerror(errno));
141 chan->flags |= LOG_CHANNEL_BROKEN;
142 return (NULL);
143 }
144 stream = fdopen(fd, "a");
145 if (stream == NULL) {
146 syslog(LOG_ERR, "log_open_stream: fdopen() failed");
147 chan->flags |= LOG_CHANNEL_BROKEN;
148 return (NULL);
149 }
150 (void) fchown(fd, chan->out.file.owner, chan->out.file.group);
151
152 chan->out.file.stream = stream;
153 return (stream);
154 }
155
156 int
log_close_stream(log_channel chan)157 log_close_stream(log_channel chan) {
158 FILE *stream;
159
160 if (chan == NULL || chan->type != log_file) {
161 errno = EINVAL;
162 return (0);
163 }
164 stream = chan->out.file.stream;
165 chan->out.file.stream = NULL;
166 if (stream != NULL && fclose(stream) == EOF)
167 return (-1);
168 return (0);
169 }
170
171 void
log_close_debug_channels(log_context lc)172 log_close_debug_channels(log_context lc) {
173 log_channel_list lcl;
174 int i;
175
176 for (i = 0; i < lc->num_categories; i++)
177 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl->next)
178 if (lcl->channel->type == log_file &&
179 lcl->channel->out.file.stream != NULL &&
180 lcl->channel->flags & LOG_REQUIRE_DEBUG)
181 (void)log_close_stream(lcl->channel);
182 }
183
184 FILE *
log_get_stream(log_channel chan)185 log_get_stream(log_channel chan) {
186 if (chan == NULL || chan->type != log_file) {
187 errno = EINVAL;
188 return (NULL);
189 }
190 return (chan->out.file.stream);
191 }
192
193 char *
log_get_filename(log_channel chan)194 log_get_filename(log_channel chan) {
195 if (chan == NULL || chan->type != log_file) {
196 errno = EINVAL;
197 return (NULL);
198 }
199 return (chan->out.file.name);
200 }
201
202 int
log_check_channel(log_context lc,int level,log_channel chan)203 log_check_channel(log_context lc, int level, log_channel chan) {
204 int debugging, chan_level;
205
206 REQUIRE(lc != NULL);
207
208 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
209
210 /*
211 * If not debugging, short circuit debugging messages very early.
212 */
213 if (level > 0 && !debugging)
214 return (0);
215
216 if ((chan->flags & (LOG_CHANNEL_BROKEN|LOG_CHANNEL_OFF)) != 0)
217 return (0);
218
219 /* Some channels only log when debugging is on. */
220 if ((chan->flags & LOG_REQUIRE_DEBUG) && !debugging)
221 return (0);
222
223 /* Some channels use the global level. */
224 if ((chan->flags & LOG_USE_CONTEXT_LEVEL) != 0) {
225 chan_level = lc->level;
226 } else
227 chan_level = chan->level;
228
229 if (level > chan_level)
230 return (0);
231
232 return (1);
233 }
234
235 int
log_check(log_context lc,int category,int level)236 log_check(log_context lc, int category, int level) {
237 log_channel_list lcl;
238 int debugging;
239
240 REQUIRE(lc != NULL);
241
242 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
243
244 /*
245 * If not debugging, short circuit debugging messages very early.
246 */
247 if (level > 0 && !debugging)
248 return (0);
249
250 if (category < 0 || category > lc->num_categories)
251 category = 0; /*%< use default */
252 lcl = lc->categories[category];
253 if (lcl == NULL) {
254 category = 0;
255 lcl = lc->categories[0];
256 }
257
258 for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
259 if (log_check_channel(lc, level, lcl->channel))
260 return (1);
261 }
262 return (0);
263 }
264
265 void
log_vwrite(log_context lc,int category,int level,const char * format,va_list args)266 log_vwrite(log_context lc, int category, int level, const char *format,
267 va_list args) {
268 log_channel_list lcl;
269 int pri, debugging, did_vsprintf = 0;
270 int original_category;
271 FILE *stream;
272 log_channel chan;
273 struct timeval tv;
274 struct tm *local_tm;
275 #ifdef HAVE_TIME_R
276 struct tm tm_tmp;
277 #endif
278 time_t tt;
279 const char *category_name;
280 const char *level_str;
281 char time_buf[256];
282 char level_buf[256];
283
284 REQUIRE(lc != NULL);
285
286 debugging = (lc->flags & LOG_OPTION_DEBUG);
287
288 /*
289 * If not debugging, short circuit debugging messages very early.
290 */
291 if (level > 0 && !debugging)
292 return;
293
294 if (category < 0 || category > lc->num_categories)
295 category = 0; /*%< use default */
296 original_category = category;
297 lcl = lc->categories[category];
298 if (lcl == NULL) {
299 category = 0;
300 lcl = lc->categories[0];
301 }
302
303 /*
304 * Get the current time and format it.
305 */
306 time_buf[0]='\0';
307 if (gettimeofday(&tv, NULL) < 0) {
308 syslog(LOG_INFO, "gettimeofday failed in log_vwrite()");
309 } else {
310 tt = tv.tv_sec;
311 #ifdef HAVE_TIME_R
312 local_tm = localtime_r(&tt, &tm_tmp);
313 #else
314 local_tm = localtime(&tt);
315 #endif
316 if (local_tm != NULL) {
317 sprintf(time_buf, "%02d-%s-%4d %02d:%02d:%02d.%03ld ",
318 local_tm->tm_mday, months[local_tm->tm_mon],
319 local_tm->tm_year+1900, local_tm->tm_hour,
320 local_tm->tm_min, local_tm->tm_sec,
321 (long)tv.tv_usec/1000);
322 }
323 }
324
325 /*
326 * Make a string representation of the current category and level
327 */
328
329 if (lc->category_names != NULL &&
330 lc->category_names[original_category] != NULL)
331 category_name = lc->category_names[original_category];
332 else
333 category_name = "";
334
335 if (level >= log_critical) {
336 if (level >= 0) {
337 sprintf(level_buf, "debug %d: ", level);
338 level_str = level_buf;
339 } else
340 level_str = level_text[-level-1];
341 } else {
342 sprintf(level_buf, "level %d: ", level);
343 level_str = level_buf;
344 }
345
346 /*
347 * Write the message to channels.
348 */
349 for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
350 chan = lcl->channel;
351
352 if (!log_check_channel(lc, level, chan))
353 continue;
354
355 if (!did_vsprintf) {
356 (void)vsprintf(lc->buffer, format, args);
357 if (strlen(lc->buffer) > (size_t)LOG_BUFFER_SIZE) {
358 syslog(LOG_CRIT,
359 "memory overrun in log_vwrite()");
360 exit(1);
361 }
362 did_vsprintf = 1;
363 }
364
365 switch (chan->type) {
366 case log_syslog:
367 if (level >= log_critical)
368 pri = (level >= 0) ? 0 : -level;
369 else
370 pri = -log_critical;
371 syslog(chan->out.facility|syslog_priority[pri],
372 "%s%s%s%s",
373 (chan->flags & LOG_TIMESTAMP) ? time_buf : "",
374 (chan->flags & LOG_PRINT_CATEGORY) ?
375 category_name : "",
376 (chan->flags & LOG_PRINT_LEVEL) ?
377 level_str : "",
378 lc->buffer);
379 break;
380 case log_file:
381 stream = chan->out.file.stream;
382 if (stream == NULL) {
383 stream = log_open_stream(chan);
384 if (stream == NULL)
385 break;
386 }
387 if (chan->out.file.max_size != ULONG_MAX) {
388 long pos;
389
390 pos = ftell(stream);
391 if (pos >= 0 &&
392 (unsigned long)pos >
393 chan->out.file.max_size) {
394 /*
395 * try to roll over the log files,
396 * ignoring all all return codes
397 * except the open (we don't want
398 * to write any more anyway)
399 */
400 log_close_stream(chan);
401 version_rename(chan);
402 stream = log_open_stream(chan);
403 if (stream == NULL)
404 break;
405 }
406 }
407 fprintf(stream, "%s%s%s%s\n",
408 (chan->flags & LOG_TIMESTAMP) ? time_buf : "",
409 (chan->flags & LOG_PRINT_CATEGORY) ?
410 category_name : "",
411 (chan->flags & LOG_PRINT_LEVEL) ?
412 level_str : "",
413 lc->buffer);
414 fflush(stream);
415 break;
416 case log_null:
417 break;
418 default:
419 syslog(LOG_ERR,
420 "unknown channel type in log_vwrite()");
421 }
422 }
423 }
424
425 void
log_write(log_context lc,int category,int level,const char * format,...)426 log_write(log_context lc, int category, int level, const char *format, ...) {
427 va_list args;
428
429 va_start(args, format);
430 log_vwrite(lc, category, level, format, args);
431 va_end(args);
432 }
433
434 /*%
435 * Functions to create, set, or destroy contexts
436 */
437
438 int
log_new_context(int num_categories,char ** category_names,log_context * lc)439 log_new_context(int num_categories, char **category_names, log_context *lc) {
440 log_context nlc;
441
442 nlc = memget(sizeof (struct log_context));
443 if (nlc == NULL) {
444 errno = ENOMEM;
445 return (-1);
446 }
447 nlc->num_categories = num_categories;
448 nlc->category_names = category_names;
449 nlc->categories = memget(num_categories * sizeof (log_channel_list));
450 if (nlc->categories == NULL) {
451 memput(nlc, sizeof (struct log_context));
452 errno = ENOMEM;
453 return (-1);
454 }
455 memset(nlc->categories, '\0',
456 num_categories * sizeof (log_channel_list));
457 nlc->flags = 0U;
458 nlc->level = 0;
459 *lc = nlc;
460 return (0);
461 }
462
463 void
log_free_context(log_context lc)464 log_free_context(log_context lc) {
465 log_channel_list lcl, lcl_next;
466 log_channel chan;
467 int i;
468
469 REQUIRE(lc != NULL);
470
471 for (i = 0; i < lc->num_categories; i++)
472 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl_next) {
473 lcl_next = lcl->next;
474 chan = lcl->channel;
475 (void)log_free_channel(chan);
476 memput(lcl, sizeof (struct log_channel_list));
477 }
478 memput(lc->categories,
479 lc->num_categories * sizeof (log_channel_list));
480 memput(lc, sizeof (struct log_context));
481 }
482
483 int
log_add_channel(log_context lc,int category,log_channel chan)484 log_add_channel(log_context lc, int category, log_channel chan) {
485 log_channel_list lcl;
486
487 if (lc == NULL || category < 0 || category >= lc->num_categories) {
488 errno = EINVAL;
489 return (-1);
490 }
491
492 lcl = memget(sizeof (struct log_channel_list));
493 if (lcl == NULL) {
494 errno = ENOMEM;
495 return(-1);
496 }
497 lcl->channel = chan;
498 lcl->next = lc->categories[category];
499 lc->categories[category] = lcl;
500 chan->references++;
501 return (0);
502 }
503
504 int
log_remove_channel(log_context lc,int category,log_channel chan)505 log_remove_channel(log_context lc, int category, log_channel chan) {
506 log_channel_list lcl, prev_lcl, next_lcl;
507 int found = 0;
508
509 if (lc == NULL || category < 0 || category >= lc->num_categories) {
510 errno = EINVAL;
511 return (-1);
512 }
513
514 for (prev_lcl = NULL, lcl = lc->categories[category];
515 lcl != NULL;
516 lcl = next_lcl) {
517 next_lcl = lcl->next;
518 if (lcl->channel == chan) {
519 log_free_channel(chan);
520 if (prev_lcl != NULL)
521 prev_lcl->next = next_lcl;
522 else
523 lc->categories[category] = next_lcl;
524 memput(lcl, sizeof (struct log_channel_list));
525 /*
526 * We just set found instead of returning because
527 * the channel might be on the list more than once.
528 */
529 found = 1;
530 } else
531 prev_lcl = lcl;
532 }
533 if (!found) {
534 errno = ENOENT;
535 return (-1);
536 }
537 return (0);
538 }
539
540 int
log_option(log_context lc,int option,int value)541 log_option(log_context lc, int option, int value) {
542 if (lc == NULL) {
543 errno = EINVAL;
544 return (-1);
545 }
546 switch (option) {
547 case LOG_OPTION_DEBUG:
548 if (value)
549 lc->flags |= option;
550 else
551 lc->flags &= ~option;
552 break;
553 case LOG_OPTION_LEVEL:
554 lc->level = value;
555 break;
556 default:
557 errno = EINVAL;
558 return (-1);
559 }
560 return (0);
561 }
562
563 int
log_category_is_active(log_context lc,int category)564 log_category_is_active(log_context lc, int category) {
565 if (lc == NULL) {
566 errno = EINVAL;
567 return (-1);
568 }
569 if (category >= 0 && category < lc->num_categories &&
570 lc->categories[category] != NULL)
571 return (1);
572 return (0);
573 }
574
575 log_channel
log_new_syslog_channel(unsigned int flags,int level,int facility)576 log_new_syslog_channel(unsigned int flags, int level, int facility) {
577 log_channel chan;
578
579 chan = memget(sizeof (struct log_channel));
580 if (chan == NULL) {
581 errno = ENOMEM;
582 return (NULL);
583 }
584 chan->type = log_syslog;
585 chan->flags = flags;
586 chan->level = level;
587 chan->out.facility = facility;
588 chan->references = 0;
589 return (chan);
590 }
591
592 log_channel
log_new_file_channel(unsigned int flags,int level,const char * name,FILE * stream,unsigned int versions,unsigned long max_size)593 log_new_file_channel(unsigned int flags, int level,
594 const char *name, FILE *stream, unsigned int versions,
595 unsigned long max_size) {
596 log_channel chan;
597
598 chan = memget(sizeof (struct log_channel));
599 if (chan == NULL) {
600 errno = ENOMEM;
601 return (NULL);
602 }
603 chan->type = log_file;
604 chan->flags = flags;
605 chan->level = level;
606 if (name != NULL) {
607 size_t len;
608
609 len = strlen(name);
610 /*
611 * Quantize length to a multiple of 256. There's space for the
612 * NUL, since if len is a multiple of 256, the size chosen will
613 * be the next multiple.
614 */
615 chan->out.file.name_size = ((len / 256) + 1) * 256;
616 chan->out.file.name = memget(chan->out.file.name_size);
617 if (chan->out.file.name == NULL) {
618 memput(chan, sizeof (struct log_channel));
619 errno = ENOMEM;
620 return (NULL);
621 }
622 /* This is safe. */
623 strcpy(chan->out.file.name, name);
624 } else {
625 chan->out.file.name_size = 0;
626 chan->out.file.name = NULL;
627 }
628 chan->out.file.stream = stream;
629 chan->out.file.versions = versions;
630 chan->out.file.max_size = max_size;
631 chan->out.file.owner = getuid();
632 chan->out.file.group = getgid();
633 chan->references = 0;
634 return (chan);
635 }
636
637 int
log_set_file_owner(log_channel chan,uid_t owner,gid_t group)638 log_set_file_owner(log_channel chan, uid_t owner, gid_t group) {
639 if (chan->type != log_file) {
640 errno = EBADF;
641 return (-1);
642 }
643 chan->out.file.owner = owner;
644 chan->out.file.group = group;
645 return (0);
646 }
647
648 log_channel
log_new_null_channel()649 log_new_null_channel() {
650 log_channel chan;
651
652 chan = memget(sizeof (struct log_channel));
653 if (chan == NULL) {
654 errno = ENOMEM;
655 return (NULL);
656 }
657 chan->type = log_null;
658 chan->flags = LOG_CHANNEL_OFF;
659 chan->level = log_info;
660 chan->references = 0;
661 return (chan);
662 }
663
664 int
log_inc_references(log_channel chan)665 log_inc_references(log_channel chan) {
666 if (chan == NULL) {
667 errno = EINVAL;
668 return (-1);
669 }
670 chan->references++;
671 return (0);
672 }
673
674 int
log_dec_references(log_channel chan)675 log_dec_references(log_channel chan) {
676 if (chan == NULL || chan->references <= 0) {
677 errno = EINVAL;
678 return (-1);
679 }
680 chan->references--;
681 return (0);
682 }
683
684 log_channel_type
log_get_channel_type(log_channel chan)685 log_get_channel_type(log_channel chan) {
686 REQUIRE(chan != NULL);
687
688 return (chan->type);
689 }
690
691 int
log_free_channel(log_channel chan)692 log_free_channel(log_channel chan) {
693 if (chan == NULL || chan->references <= 0) {
694 errno = EINVAL;
695 return (-1);
696 }
697 chan->references--;
698 if (chan->references == 0) {
699 if (chan->type == log_file) {
700 if ((chan->flags & LOG_CLOSE_STREAM) &&
701 chan->out.file.stream != NULL)
702 (void)fclose(chan->out.file.stream);
703 if (chan->out.file.name != NULL)
704 memput(chan->out.file.name,
705 chan->out.file.name_size);
706 }
707 memput(chan, sizeof (struct log_channel));
708 }
709 return (0);
710 }
711
712 /*! \file */
713