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