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