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