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