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