xref: /netbsd-src/external/mpl/bind/dist/bin/named/logconf.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: logconf.c,v 1.10 2025/01/26 16:24:33 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 
18 #include <inttypes.h>
19 #include <stdbool.h>
20 
21 #include <isc/file.h>
22 #include <isc/result.h>
23 #include <isc/stdio.h>
24 #include <isc/string.h>
25 #include <isc/syslog.h>
26 #include <isc/util.h>
27 
28 #include <isccfg/cfg.h>
29 #include <isccfg/log.h>
30 
31 #include <named/log.h>
32 #include <named/logconf.h>
33 
34 #define CHECK(op)                            \
35 	do {                                 \
36 		result = (op);               \
37 		if (result != ISC_R_SUCCESS) \
38 			goto cleanup;        \
39 	} while (0)
40 
41 /*%
42  * Set up a logging category according to the named.conf data
43  * in 'ccat' and add it to 'logconfig'.
44  */
45 static isc_result_t
46 category_fromconf(const cfg_obj_t *ccat, isc_logconfig_t *logconfig) {
47 	isc_result_t result;
48 	const char *catname;
49 	isc_logcategory_t *category;
50 	isc_logmodule_t *module;
51 	const cfg_obj_t *destinations = NULL;
52 	const cfg_listelt_t *element = NULL;
53 
54 	catname = cfg_obj_asstring(cfg_tuple_get(ccat, "name"));
55 	category = isc_log_categorybyname(named_g_lctx, catname);
56 	if (category == NULL) {
57 		cfg_obj_log(ccat, named_g_lctx, ISC_LOG_ERROR,
58 			    "unknown logging category '%s' ignored", catname);
59 		/*
60 		 * Allow further processing by returning success.
61 		 */
62 		return ISC_R_SUCCESS;
63 	}
64 
65 	if (logconfig == NULL) {
66 		return ISC_R_SUCCESS;
67 	}
68 
69 	module = NULL;
70 
71 	destinations = cfg_tuple_get(ccat, "destinations");
72 	for (element = cfg_list_first(destinations); element != NULL;
73 	     element = cfg_list_next(element))
74 	{
75 		const cfg_obj_t *channel = cfg_listelt_value(element);
76 		const char *channelname = cfg_obj_asstring(channel);
77 
78 		result = isc_log_usechannel(logconfig, channelname, category,
79 					    module);
80 		if (result != ISC_R_SUCCESS) {
81 			isc_log_write(named_g_lctx, CFG_LOGCATEGORY_CONFIG,
82 				      NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
83 				      "logging channel '%s': %s", channelname,
84 				      isc_result_totext(result));
85 			return result;
86 		}
87 	}
88 	return ISC_R_SUCCESS;
89 }
90 
91 /*%
92  * Set up a logging channel according to the named.conf data
93  * in 'cchan' and add it to 'logconfig'.
94  */
95 static isc_result_t
96 channel_fromconf(const cfg_obj_t *channel, isc_logconfig_t *logconfig) {
97 	isc_result_t result = ISC_R_SUCCESS;
98 	isc_logdestination_t dest;
99 	unsigned int type;
100 	unsigned int flags = 0;
101 	int level;
102 	const char *channelname;
103 	const cfg_obj_t *fileobj = NULL;
104 	const cfg_obj_t *syslogobj = NULL;
105 	const cfg_obj_t *nullobj = NULL;
106 	const cfg_obj_t *stderrobj = NULL;
107 	const cfg_obj_t *severity = NULL;
108 	int i;
109 
110 	channelname = cfg_obj_asstring(cfg_map_getname(channel));
111 
112 	(void)cfg_map_get(channel, "file", &fileobj);
113 	(void)cfg_map_get(channel, "syslog", &syslogobj);
114 	(void)cfg_map_get(channel, "null", &nullobj);
115 	(void)cfg_map_get(channel, "stderr", &stderrobj);
116 
117 	i = 0;
118 	if (fileobj != NULL) {
119 		i++;
120 	}
121 	if (syslogobj != NULL) {
122 		i++;
123 	}
124 	if (nullobj != NULL) {
125 		i++;
126 	}
127 	if (stderrobj != NULL) {
128 		i++;
129 	}
130 
131 	if (i != 1) {
132 		cfg_obj_log(channel, named_g_lctx, ISC_LOG_ERROR,
133 			    "channel '%s': exactly one of file, syslog, "
134 			    "null, and stderr must be present",
135 			    channelname);
136 		return ISC_R_FAILURE;
137 	}
138 
139 	type = ISC_LOG_TONULL;
140 
141 	if (fileobj != NULL) {
142 		const cfg_obj_t *pathobj = cfg_tuple_get(fileobj, "file");
143 		const cfg_obj_t *sizeobj = cfg_tuple_get(fileobj, "size");
144 		const cfg_obj_t *versionsobj = cfg_tuple_get(fileobj,
145 							     "versions");
146 		const cfg_obj_t *suffixobj = cfg_tuple_get(fileobj, "suffix");
147 		int32_t versions = ISC_LOG_ROLLNEVER;
148 		isc_log_rollsuffix_t suffix = isc_log_rollsuffix_increment;
149 		off_t size = 0;
150 		uint64_t maxoffset;
151 
152 		/*
153 		 * off_t is a signed integer type, so the maximum
154 		 * value is all 1s except for the MSB.
155 		 */
156 		switch (sizeof(off_t)) {
157 		case 4:
158 			maxoffset = 0x7fffffffULL;
159 			break;
160 		case 8:
161 			maxoffset = 0x7fffffffffffffffULL;
162 			break;
163 		default:
164 			UNREACHABLE();
165 		}
166 
167 		type = ISC_LOG_TOFILE;
168 
169 		if (versionsobj != NULL && cfg_obj_isuint32(versionsobj)) {
170 			versions = cfg_obj_asuint32(versionsobj);
171 		} else if (versionsobj != NULL &&
172 			   cfg_obj_isstring(versionsobj) &&
173 			   strcasecmp(cfg_obj_asstring(versionsobj),
174 				      "unlimited") == 0)
175 		{
176 			versions = ISC_LOG_ROLLINFINITE;
177 		}
178 		if (sizeobj != NULL && cfg_obj_isuint64(sizeobj) &&
179 		    cfg_obj_asuint64(sizeobj) < maxoffset)
180 		{
181 			size = (off_t)cfg_obj_asuint64(sizeobj);
182 		}
183 		if (suffixobj != NULL && cfg_obj_isstring(suffixobj) &&
184 		    strcasecmp(cfg_obj_asstring(suffixobj), "timestamp") == 0)
185 		{
186 			suffix = isc_log_rollsuffix_timestamp;
187 		}
188 
189 		dest.file.stream = NULL;
190 		dest.file.name = cfg_obj_asstring(pathobj);
191 		dest.file.versions = versions;
192 		dest.file.suffix = suffix;
193 		dest.file.maximum_size = size;
194 	} else if (syslogobj != NULL) {
195 		int facility = LOG_DAEMON;
196 
197 		type = ISC_LOG_TOSYSLOG;
198 
199 		if (cfg_obj_isstring(syslogobj)) {
200 			const char *facilitystr = cfg_obj_asstring(syslogobj);
201 			(void)isc_syslog_facilityfromstring(facilitystr,
202 							    &facility);
203 		}
204 		dest.facility = facility;
205 	} else if (stderrobj != NULL) {
206 		type = ISC_LOG_TOFILEDESC;
207 		dest.file.stream = stderr;
208 		dest.file.name = NULL;
209 		dest.file.versions = ISC_LOG_ROLLNEVER;
210 		dest.file.suffix = isc_log_rollsuffix_increment;
211 		dest.file.maximum_size = 0;
212 	}
213 
214 	/*
215 	 * Munge flags.
216 	 */
217 	{
218 		const cfg_obj_t *printcat = NULL;
219 		const cfg_obj_t *printsev = NULL;
220 		const cfg_obj_t *printtime = NULL;
221 		const cfg_obj_t *buffered = NULL;
222 
223 		(void)cfg_map_get(channel, "print-category", &printcat);
224 		(void)cfg_map_get(channel, "print-severity", &printsev);
225 		(void)cfg_map_get(channel, "print-time", &printtime);
226 		(void)cfg_map_get(channel, "buffered", &buffered);
227 
228 		if (printcat != NULL && cfg_obj_asboolean(printcat)) {
229 			flags |= ISC_LOG_PRINTCATEGORY;
230 		}
231 		if (printsev != NULL && cfg_obj_asboolean(printsev)) {
232 			flags |= ISC_LOG_PRINTLEVEL;
233 		}
234 		if (buffered != NULL && cfg_obj_asboolean(buffered)) {
235 			flags |= ISC_LOG_BUFFERED;
236 		}
237 		if (printtime != NULL && cfg_obj_isboolean(printtime)) {
238 			if (cfg_obj_asboolean(printtime)) {
239 				flags |= ISC_LOG_PRINTTIME;
240 			}
241 		} else if (printtime != NULL) { /* local/iso8601/iso8601-utc */
242 			const char *s = cfg_obj_asstring(printtime);
243 			flags |= ISC_LOG_PRINTTIME;
244 			if (strcasecmp(s, "iso8601") == 0) {
245 				flags |= ISC_LOG_ISO8601;
246 			} else if (strcasecmp(s, "iso8601-utc") == 0) {
247 				flags |= ISC_LOG_ISO8601 | ISC_LOG_UTC;
248 			}
249 		}
250 	}
251 
252 	level = ISC_LOG_INFO;
253 	if (cfg_map_get(channel, "severity", &severity) == ISC_R_SUCCESS) {
254 		if (cfg_obj_isstring(severity)) {
255 			const char *str = cfg_obj_asstring(severity);
256 			if (strcasecmp(str, "critical") == 0) {
257 				level = ISC_LOG_CRITICAL;
258 			} else if (strcasecmp(str, "error") == 0) {
259 				level = ISC_LOG_ERROR;
260 			} else if (strcasecmp(str, "warning") == 0) {
261 				level = ISC_LOG_WARNING;
262 			} else if (strcasecmp(str, "notice") == 0) {
263 				level = ISC_LOG_NOTICE;
264 			} else if (strcasecmp(str, "info") == 0) {
265 				level = ISC_LOG_INFO;
266 			} else if (strcasecmp(str, "dynamic") == 0) {
267 				level = ISC_LOG_DYNAMIC;
268 			}
269 		} else {
270 			/* debug */
271 			level = cfg_obj_asuint32(severity);
272 		}
273 	}
274 
275 	if (logconfig != NULL) {
276 		isc_log_createchannel(logconfig, channelname, type, level,
277 				      &dest, flags);
278 	}
279 
280 	if (type == ISC_LOG_TOFILE) {
281 		FILE *fp;
282 
283 		/*
284 		 * Test to make sure that file is a plain file.
285 		 * Fix defect #22771
286 		 */
287 		result = isc_file_isplainfile(dest.file.name);
288 		if (result == ISC_R_SUCCESS || result == ISC_R_FILENOTFOUND) {
289 			/*
290 			 * Test that the file can be opened, since
291 			 * isc_log_open() can't effectively report
292 			 * failures when called in isc_log_doit().
293 			 */
294 			result = isc_stdio_open(dest.file.name, "a", &fp);
295 			if (result != ISC_R_SUCCESS) {
296 				if (logconfig != NULL && !named_g_nosyslog) {
297 					syslog(LOG_ERR,
298 					       "isc_stdio_open '%s' failed: "
299 					       "%s",
300 					       dest.file.name,
301 					       isc_result_totext(result));
302 				}
303 			} else {
304 				(void)isc_stdio_close(fp);
305 			}
306 			goto done;
307 		}
308 		if (logconfig != NULL && !named_g_nosyslog) {
309 			syslog(LOG_ERR, "isc_file_isplainfile '%s' failed: %s",
310 			       dest.file.name, isc_result_totext(result));
311 		}
312 	}
313 
314 done:
315 	return result;
316 }
317 
318 isc_result_t
319 named_logconfig(isc_logconfig_t *logconfig, const cfg_obj_t *logstmt) {
320 	isc_result_t result;
321 	const cfg_obj_t *channels = NULL;
322 	const cfg_obj_t *categories = NULL;
323 	const cfg_listelt_t *element;
324 	bool default_set = false;
325 	bool unmatched_set = false;
326 	const cfg_obj_t *catname;
327 
328 	if (logconfig != NULL) {
329 		named_log_setdefaultchannels(logconfig);
330 		named_log_setdefaultsslkeylogfile(logconfig);
331 	}
332 
333 	(void)cfg_map_get(logstmt, "channel", &channels);
334 	for (element = cfg_list_first(channels); element != NULL;
335 	     element = cfg_list_next(element))
336 	{
337 		const cfg_obj_t *channel = cfg_listelt_value(element);
338 		CHECK(channel_fromconf(channel, logconfig));
339 	}
340 
341 	(void)cfg_map_get(logstmt, "category", &categories);
342 	for (element = cfg_list_first(categories); element != NULL;
343 	     element = cfg_list_next(element))
344 	{
345 		const cfg_obj_t *category = cfg_listelt_value(element);
346 		CHECK(category_fromconf(category, logconfig));
347 		if (!default_set) {
348 			catname = cfg_tuple_get(category, "name");
349 			if (strcmp(cfg_obj_asstring(catname), "default") == 0) {
350 				default_set = true;
351 			}
352 		}
353 		if (!unmatched_set) {
354 			catname = cfg_tuple_get(category, "name");
355 			if (strcmp(cfg_obj_asstring(catname), "unmatched") == 0)
356 			{
357 				unmatched_set = true;
358 			}
359 		}
360 	}
361 
362 	if (logconfig != NULL && !default_set) {
363 		CHECK(named_log_setdefaultcategory(logconfig));
364 	}
365 
366 	if (logconfig != NULL && !unmatched_set) {
367 		CHECK(named_log_setunmatchedcategory(logconfig));
368 	}
369 
370 	return ISC_R_SUCCESS;
371 
372 cleanup:
373 	return result;
374 }
375