1/*
2 * Copyright (C) 2004-2007, 2011, 2013  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 1999-2001  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/* $Id: logconf.c,v 1.45 2011/03/05 23:52:29 tbox Exp $ */
19
20/*! \file */
21
22#include <config.h>
23
24#include <isc/file.h>
25#include <isc/offset.h>
26#include <isc/result.h>
27#include <isc/stdio.h>
28#include <isc/string.h>
29#include <isc/syslog.h>
30
31#include <isccfg/cfg.h>
32#include <isccfg/log.h>
33
34#include <named/log.h>
35#include <named/logconf.h>
36
37#define CHECK(op) \
38	do { result = (op); 				  	 \
39	       if (result != ISC_R_SUCCESS) goto cleanup; 	 \
40	} while (0)
41
42/*%
43 * Set up a logging category according to the named.conf data
44 * in 'ccat' and add it to 'logconfig'.
45 */
46static isc_result_t
47category_fromconf(const cfg_obj_t *ccat, isc_logconfig_t *logconfig) {
48	isc_result_t result;
49	const char *catname;
50	isc_logcategory_t *category;
51	isc_logmodule_t *module;
52	const cfg_obj_t *destinations = NULL;
53	const cfg_listelt_t *element = NULL;
54
55	catname = cfg_obj_asstring(cfg_tuple_get(ccat, "name"));
56	category = isc_log_categorybyname(ns_g_lctx, catname);
57	if (category == NULL) {
58		cfg_obj_log(ccat, ns_g_lctx, ISC_LOG_ERROR,
59			    "unknown logging category '%s' ignored",
60			    catname);
61		/*
62		 * Allow further processing by returning success.
63		 */
64		return (ISC_R_SUCCESS);
65	}
66
67	if (logconfig == NULL)
68		return (ISC_R_SUCCESS);
69
70	module = NULL;
71
72	destinations = cfg_tuple_get(ccat, "destinations");
73	for (element = cfg_list_first(destinations);
74	     element != NULL;
75	     element = cfg_list_next(element))
76	{
77		const cfg_obj_t *channel = cfg_listelt_value(element);
78		const char *channelname = cfg_obj_asstring(channel);
79
80		result = isc_log_usechannel(logconfig, channelname, category,
81					    module);
82		if (result != ISC_R_SUCCESS) {
83			isc_log_write(ns_g_lctx, CFG_LOGCATEGORY_CONFIG,
84				      NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
85				      "logging channel '%s': %s", channelname,
86				      isc_result_totext(result));
87			return (result);
88		}
89	}
90	return (ISC_R_SUCCESS);
91}
92
93/*%
94 * Set up a logging channel according to the named.conf data
95 * in 'cchan' and add it to 'logconfig'.
96 */
97static isc_result_t
98channel_fromconf(const cfg_obj_t *channel, isc_logconfig_t *logconfig)
99{
100	isc_result_t result;
101	isc_logdestination_t dest;
102	unsigned int type;
103	unsigned int flags = 0;
104	int level;
105	const char *channelname;
106	const cfg_obj_t *fileobj = NULL;
107	const cfg_obj_t *syslogobj = NULL;
108	const cfg_obj_t *nullobj = NULL;
109	const cfg_obj_t *stderrobj = NULL;
110	const cfg_obj_t *severity = NULL;
111	int i;
112
113	channelname = cfg_obj_asstring(cfg_map_getname(channel));
114
115	(void)cfg_map_get(channel, "file", &fileobj);
116	(void)cfg_map_get(channel, "syslog", &syslogobj);
117	(void)cfg_map_get(channel, "null", &nullobj);
118	(void)cfg_map_get(channel, "stderr", &stderrobj);
119
120	i = 0;
121	if (fileobj != NULL)
122		i++;
123	if (syslogobj != NULL)
124		i++;
125	if (nullobj != NULL)
126		i++;
127	if (stderrobj != NULL)
128		i++;
129
130	if (i != 1) {
131		cfg_obj_log(channel, ns_g_lctx, ISC_LOG_ERROR,
132			      "channel '%s': exactly one of file, syslog, "
133			      "null, and stderr must be present", channelname);
134		return (ISC_R_FAILURE);
135	}
136
137	type = ISC_LOG_TONULL;
138
139	if (fileobj != NULL) {
140		const cfg_obj_t *pathobj = cfg_tuple_get(fileobj, "file");
141		const cfg_obj_t *sizeobj = cfg_tuple_get(fileobj, "size");
142		const cfg_obj_t *versionsobj =
143				 cfg_tuple_get(fileobj, "versions");
144		isc_int32_t versions = ISC_LOG_ROLLNEVER;
145		isc_offset_t size = 0;
146
147		type = ISC_LOG_TOFILE;
148
149		if (versionsobj != NULL && cfg_obj_isuint32(versionsobj))
150			versions = cfg_obj_asuint32(versionsobj);
151		if (versionsobj != NULL && cfg_obj_isstring(versionsobj) &&
152		    strcasecmp(cfg_obj_asstring(versionsobj), "unlimited") == 0)
153			versions = ISC_LOG_ROLLINFINITE;
154		if (sizeobj != NULL &&
155		    cfg_obj_isuint64(sizeobj) &&
156		    cfg_obj_asuint64(sizeobj) < ISC_OFFSET_MAXIMUM)
157			size = (isc_offset_t)cfg_obj_asuint64(sizeobj);
158		dest.file.stream = NULL;
159		dest.file.name = cfg_obj_asstring(pathobj);
160		dest.file.versions = versions;
161		dest.file.maximum_size = size;
162	} else if (syslogobj != NULL) {
163		int facility = LOG_DAEMON;
164
165		type = ISC_LOG_TOSYSLOG;
166
167		if (cfg_obj_isstring(syslogobj)) {
168			const char *facilitystr = cfg_obj_asstring(syslogobj);
169			(void)isc_syslog_facilityfromstring(facilitystr,
170							    &facility);
171		}
172		dest.facility = facility;
173	} else if (stderrobj != NULL) {
174		type = ISC_LOG_TOFILEDESC;
175		dest.file.stream = stderr;
176		dest.file.name = NULL;
177		dest.file.versions = ISC_LOG_ROLLNEVER;
178		dest.file.maximum_size = 0;
179	}
180
181	/*
182	 * Munge flags.
183	 */
184	{
185		const cfg_obj_t *printcat = NULL;
186		const cfg_obj_t *printsev = NULL;
187		const cfg_obj_t *printtime = NULL;
188
189		(void)cfg_map_get(channel, "print-category", &printcat);
190		(void)cfg_map_get(channel, "print-severity", &printsev);
191		(void)cfg_map_get(channel, "print-time", &printtime);
192
193		if (printcat != NULL && cfg_obj_asboolean(printcat))
194			flags |= ISC_LOG_PRINTCATEGORY;
195		if (printtime != NULL && cfg_obj_asboolean(printtime))
196			flags |= ISC_LOG_PRINTTIME;
197		if (printsev != NULL && cfg_obj_asboolean(printsev))
198			flags |= ISC_LOG_PRINTLEVEL;
199	}
200
201	level = ISC_LOG_INFO;
202	if (cfg_map_get(channel, "severity", &severity) == ISC_R_SUCCESS) {
203		if (cfg_obj_isstring(severity)) {
204			const char *str = cfg_obj_asstring(severity);
205			if (strcasecmp(str, "critical") == 0)
206				level = ISC_LOG_CRITICAL;
207			else if (strcasecmp(str, "error") == 0)
208				level = ISC_LOG_ERROR;
209			else if (strcasecmp(str, "warning") == 0)
210				level = ISC_LOG_WARNING;
211			else if (strcasecmp(str, "notice") == 0)
212				level = ISC_LOG_NOTICE;
213			else if (strcasecmp(str, "info") == 0)
214				level = ISC_LOG_INFO;
215			else if (strcasecmp(str, "dynamic") == 0)
216				level = ISC_LOG_DYNAMIC;
217		} else
218			/* debug */
219			level = cfg_obj_asuint32(severity);
220	}
221
222	if (logconfig == NULL)
223		result = ISC_R_SUCCESS;
224	else
225		result = isc_log_createchannel(logconfig, channelname,
226					       type, level, &dest, flags);
227
228	if (result == ISC_R_SUCCESS && type == ISC_LOG_TOFILE) {
229		FILE *fp;
230
231		/*
232		 * Test to make sure that file is a plain file.
233		 * Fix defect #22771
234		*/
235		result = isc_file_isplainfile(dest.file.name);
236		if (result == ISC_R_SUCCESS || result == ISC_R_FILENOTFOUND) {
237			/*
238			 * Test that the file can be opened, since
239			 * isc_log_open() can't effectively report
240			 * failures when called in isc_log_doit().
241			 */
242			result = isc_stdio_open(dest.file.name, "a", &fp);
243			if (result != ISC_R_SUCCESS) {
244				if (logconfig != NULL && !ns_g_nosyslog)
245					syslog(LOG_ERR,
246						"isc_stdio_open '%s' failed: "
247						"%s", dest.file.name,
248						isc_result_totext(result));
249				fprintf(stderr,
250					"isc_stdio_open '%s' failed: %s\n",
251					dest.file.name,
252					isc_result_totext(result));
253			} else
254				(void)isc_stdio_close(fp);
255			goto done;
256		}
257		if (logconfig != NULL && !ns_g_nosyslog)
258			syslog(LOG_ERR, "isc_file_isplainfile '%s' failed: %s",
259			       dest.file.name, isc_result_totext(result));
260		fprintf(stderr, "isc_file_isplainfile '%s' failed: %s\n",
261			dest.file.name, isc_result_totext(result));
262	}
263
264 done:
265	return (result);
266}
267
268isc_result_t
269ns_log_configure(isc_logconfig_t *logconfig, const cfg_obj_t *logstmt) {
270	isc_result_t result;
271	const cfg_obj_t *channels = NULL;
272	const cfg_obj_t *categories = NULL;
273	const cfg_listelt_t *element;
274	isc_boolean_t default_set = ISC_FALSE;
275	isc_boolean_t unmatched_set = ISC_FALSE;
276	const cfg_obj_t *catname;
277
278	if (logconfig != NULL)
279		CHECK(ns_log_setdefaultchannels(logconfig));
280
281	(void)cfg_map_get(logstmt, "channel", &channels);
282	for (element = cfg_list_first(channels);
283	     element != NULL;
284	     element = cfg_list_next(element))
285	{
286		const cfg_obj_t *channel = cfg_listelt_value(element);
287		CHECK(channel_fromconf(channel, logconfig));
288	}
289
290	(void)cfg_map_get(logstmt, "category", &categories);
291	for (element = cfg_list_first(categories);
292	     element != NULL;
293	     element = cfg_list_next(element))
294	{
295		const cfg_obj_t *category = cfg_listelt_value(element);
296		CHECK(category_fromconf(category, logconfig));
297		if (!default_set) {
298			catname = cfg_tuple_get(category, "name");
299			if (strcmp(cfg_obj_asstring(catname), "default") == 0)
300				default_set = ISC_TRUE;
301		}
302		if (!unmatched_set) {
303			catname = cfg_tuple_get(category, "name");
304			if (strcmp(cfg_obj_asstring(catname), "unmatched") == 0)
305				unmatched_set = ISC_TRUE;
306		}
307	}
308
309	if (logconfig != NULL && !default_set)
310		CHECK(ns_log_setdefaultcategory(logconfig));
311
312	if (logconfig != NULL && !unmatched_set)
313		CHECK(ns_log_setunmatchedcategory(logconfig));
314
315	return (ISC_R_SUCCESS);
316
317 cleanup:
318	return (result);
319}
320