1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <sys/time.h>
23 #include <time.h>
24 #include <urcu.h>
25 
26 #ifdef ENABLE_SYSTEMD
27 #define SD_JOURNAL_SUPPRESS_LOCATION 1
28 #include <systemd/sd-journal.h>
29 #include <systemd/sd-daemon.h>
30 #endif
31 
32 #include "knot/common/log.h"
33 #include "libknot/libknot.h"
34 #include "contrib/ucw/lists.h"
35 
36 /*! Single log message buffer length (one line). */
37 #define LOG_BUFLEN	512
38 #define NULL_ZONE_STR	"?"
39 
40 #ifdef ENABLE_SYSTEMD
41 int use_journal = 0;
42 #endif
43 
44 /*! Log context. */
45 typedef struct {
46 	size_t target_count; /*!< Log target count. */
47 	int *target;         /*!< Log targets. */
48 	size_t file_count;   /*!< Open files count. */
49 	FILE **file;         /*!< Open files. */
50 	log_flag_t flags;    /*!< Formatting flags. */
51 } log_t;
52 
53 /*! Log singleton. */
54 log_t *s_log = NULL;
55 
log_isopen(void)56 static bool log_isopen(void)
57 {
58 	return s_log != NULL;
59 }
60 
sink_free(log_t * log)61 static void sink_free(log_t *log)
62 {
63 	if (log == NULL) {
64 		return;
65 	}
66 
67 	// Close open log files.
68 	for (int i = 0; i < log->file_count; ++i) {
69 		fclose(log->file[i]);
70 	}
71 	free(log->target);
72 	free(log->file);
73 	free(log);
74 }
75 
76 /*!
77  * \brief Create logging targets respecting their canonical order.
78  *
79  * Facilities ordering: Syslog, Stderr, Stdout, File0...
80  */
sink_setup(size_t file_count)81 static log_t *sink_setup(size_t file_count)
82 {
83 	log_t *log = malloc(sizeof(*log));
84 	if (log == NULL) {
85 		return NULL;
86 	}
87 	memset(log, 0, sizeof(*log));
88 
89 	// Reserve space for targets.
90 	log->target_count = LOG_TARGET_FILE + file_count;
91 	log->target = malloc(LOG_SOURCE_ANY * sizeof(int) * log->target_count);
92 	if (!log->target) {
93 		free(log);
94 		return NULL;
95 	}
96 	memset(log->target, 0, LOG_SOURCE_ANY * sizeof(int) * log->target_count);
97 
98 	// Reserve space for log files.
99 	if (file_count > 0) {
100 		log->file = malloc(sizeof(FILE *) * file_count);
101 		if (!log->file) {
102 			free(log->target);
103 			free(log);
104 			return NULL;
105 		}
106 		memset(log->file, 0, sizeof(FILE *) * file_count);
107 	}
108 
109 	return log;
110 }
111 
sink_publish(log_t * log)112 static void sink_publish(log_t *log)
113 {
114 	log_t **current_log = &s_log;
115 	log_t *old_log = rcu_xchg_pointer(current_log, log);
116 	synchronize_rcu();
117 	sink_free(old_log);
118 }
119 
src_levels(log_t * log,log_target_t target,log_source_t src)120 static int *src_levels(log_t *log, log_target_t target, log_source_t src)
121 {
122 	assert(src < LOG_SOURCE_ANY);
123 	return &log->target[LOG_SOURCE_ANY * target + src];
124 }
125 
sink_levels_set(log_t * log,log_target_t target,log_source_t src,int levels)126 static void sink_levels_set(log_t *log, log_target_t target, log_source_t src, int levels)
127 {
128 	// Assign levels to the specified source.
129 	if (src != LOG_SOURCE_ANY) {
130 		*src_levels(log, target, src) = levels;
131 	} else {
132 		// ANY ~ set levels to all sources.
133 		for (int i = 0; i < LOG_SOURCE_ANY; ++i) {
134 			*src_levels(log, target, i) = levels;
135 		}
136 	}
137 }
138 
sink_levels_add(log_t * log,log_target_t target,log_source_t src,int levels)139 static void sink_levels_add(log_t *log, log_target_t target, log_source_t src, int levels)
140 {
141 	// Add levels to the specified source.
142 	if (src != LOG_SOURCE_ANY) {
143 		*src_levels(log, target, src) |= levels;
144 	} else {
145 		// ANY ~ add levels to all sources.
146 		for (int i = 0; i < LOG_SOURCE_ANY; ++i) {
147 			*src_levels(log, target, i) |= levels;
148 		}
149 	}
150 }
151 
log_init(void)152 void log_init(void)
153 {
154 	// Setup initial state.
155 	int emask = LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING);
156 	int imask = LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO);
157 
158 	// Publish base log sink.
159 	log_t *log = sink_setup(0);
160 	if (log == NULL) {
161 		fprintf(stderr, "Failed to setup logging\n");
162 		return;
163 	}
164 
165 #ifdef ENABLE_SYSTEMD
166 	// Should only use the journal if system was booted with systemd.
167 	use_journal = sd_booted();
168 #endif
169 
170 	sink_levels_set(log, LOG_TARGET_SYSLOG, LOG_SOURCE_ANY, emask);
171 	sink_levels_set(log, LOG_TARGET_STDERR, LOG_SOURCE_ANY, emask);
172 	sink_levels_set(log, LOG_TARGET_STDOUT, LOG_SOURCE_ANY, imask);
173 	sink_publish(log);
174 
175 	setlogmask(LOG_UPTO(LOG_DEBUG));
176 	openlog(PACKAGE_NAME, LOG_PID, LOG_DAEMON);
177 }
178 
log_close(void)179 void log_close(void)
180 {
181 	sink_publish(NULL);
182 
183 	fflush(stdout);
184 	fflush(stderr);
185 
186 	closelog();
187 }
188 
log_flag_set(log_flag_t flag)189 void log_flag_set(log_flag_t flag)
190 {
191 	if (log_isopen()) {
192 		s_log->flags |= flag;
193 	}
194 }
195 
log_levels_set(log_target_t target,log_source_t src,int levels)196 void log_levels_set(log_target_t target, log_source_t src, int levels)
197 {
198 	if (log_isopen()) {
199 		sink_levels_set(s_log, target, src, levels);
200 	}
201 }
202 
log_levels_add(log_target_t target,log_source_t src,int levels)203 void log_levels_add(log_target_t target, log_source_t src, int levels)
204 {
205 	if (log_isopen()) {
206 		sink_levels_add(s_log, target, src, levels);
207 	}
208 }
209 
emit_log_msg(int level,log_source_t src,const char * zone,size_t zone_len,const char * msg,const char * param)210 static void emit_log_msg(int level, log_source_t src, const char *zone,
211                          size_t zone_len, const char *msg, const char *param)
212 {
213 	log_t *log = s_log;
214 
215 	// Syslog target.
216 	if (*src_levels(log, LOG_TARGET_SYSLOG, src) & LOG_MASK(level)) {
217 #ifdef ENABLE_SYSTEMD
218 		if (use_journal) {
219 			char *zone_fmt = zone ? "ZONE=%.*s." : NULL;
220 			sd_journal_send("PRIORITY=%d", level,
221 			                "MESSAGE=%s", msg,
222 			                zone_fmt, zone_len, zone,
223 			                param, NULL);
224 		} else
225 #endif
226 		{
227 			syslog(level, "%s", msg);
228 		}
229 	}
230 
231 	// Prefix date and time.
232 	char tstr[LOG_BUFLEN] = { 0 };
233 	if (!(s_log->flags & LOG_FLAG_NOTIMESTAMP)) {
234 		struct tm lt;
235 		struct timeval tv;
236 		gettimeofday(&tv, NULL);
237 		time_t sec = tv.tv_sec;
238 		if (localtime_r(&sec, &lt) != NULL) {
239 			strftime(tstr, sizeof(tstr), KNOT_LOG_TIME_FORMAT " ", &lt);
240 		}
241 	}
242 
243 	// Other log targets.
244 	for (int i = LOG_TARGET_STDERR; i < LOG_TARGET_FILE + log->file_count; ++i) {
245 		if (*src_levels(log, i, src) & LOG_MASK(level)) {
246 			FILE *stream;
247 			switch (i) {
248 			case LOG_TARGET_STDERR: stream = stderr; break;
249 			case LOG_TARGET_STDOUT: stream = stdout; break;
250 			default: stream = log->file[i - LOG_TARGET_FILE]; break;
251 			}
252 
253 			// Print the message.
254 			fprintf(stream, "%s%s\n", tstr, msg);
255 			if (stream == stdout) {
256 				fflush(stream);
257 			}
258 		}
259 	}
260 }
261 
level_prefix(int level)262 static const char *level_prefix(int level)
263 {
264 	switch (level) {
265 	case LOG_DEBUG:   return "debug";
266 	case LOG_INFO:    return "info";
267 	case LOG_NOTICE:  return "notice";
268 	case LOG_WARNING: return "warning";
269 	case LOG_ERR:     return "error";
270 	case LOG_CRIT:    return "critical";
271 	default:          return NULL;
272 	};
273 }
274 
log_msg_add(char ** write,size_t * capacity,const char * fmt,...)275 static int log_msg_add(char **write, size_t *capacity, const char *fmt, ...)
276 {
277 	va_list args;
278 	va_start(args, fmt);
279 	int written = vsnprintf(*write, *capacity, fmt, args);
280 	va_end(args);
281 
282 	if (written < 0 || written >= *capacity) {
283 		return KNOT_ESPACE;
284 	}
285 
286 	*write += written;
287 	*capacity -= written;
288 
289 	return KNOT_EOK;
290 }
291 
log_msg_text(int level,log_source_t src,const char * zone,const char * fmt,va_list args,const char * param)292 static void log_msg_text(int level, log_source_t src, const char *zone,
293                          const char *fmt, va_list args, const char *param)
294 {
295 	if (!log_isopen() || src == LOG_SOURCE_ANY) {
296 		return;
297 	}
298 
299 	// Buffer for log message.
300 	char buff[LOG_BUFLEN];
301 	char *write = buff;
302 	size_t capacity = sizeof(buff);
303 
304 	rcu_read_lock();
305 
306 	// Prefix error level.
307 	if (level != LOG_INFO || !(s_log->flags & LOG_FLAG_NOINFO)) {
308 		const char *prefix = level_prefix(level);
309 		int ret = log_msg_add(&write, &capacity, "%s: ", prefix);
310 		if (ret != KNOT_EOK) {
311 			rcu_read_unlock();
312 			return;
313 		}
314 	}
315 
316 	// Prefix zone name.
317 	size_t zone_len = 0;
318 	if (zone != NULL) {
319 		zone_len = strlen(zone);
320 		if (zone_len > 0 && zone[zone_len - 1] == '.') {
321 			zone_len--;
322 		}
323 
324 		int ret = log_msg_add(&write, &capacity, "[%.*s.] ", (int)zone_len, zone);
325 		if (ret != KNOT_EOK) {
326 			rcu_read_unlock();
327 			return;
328 		}
329 	}
330 
331 	// Compile log message.
332 	int ret = vsnprintf(write, capacity, fmt, args);
333 	if (ret >= 0) {
334 		// Send to logging targets.
335 		emit_log_msg(level, src, zone, zone_len, buff, param);
336 	}
337 
338 	rcu_read_unlock();
339 }
340 
log_fmt(int priority,log_source_t src,const char * fmt,...)341 void log_fmt(int priority, log_source_t src, const char *fmt, ...)
342 {
343 	va_list args;
344 	va_start(args, fmt);
345 	log_msg_text(priority, src, NULL, fmt, args, NULL);
346 	va_end(args);
347 }
348 
log_fmt_zone(int priority,log_source_t src,const knot_dname_t * zone,const char * param,const char * fmt,...)349 void log_fmt_zone(int priority, log_source_t src, const knot_dname_t *zone,
350                   const char *param, const char *fmt, ...)
351 {
352 	knot_dname_txt_storage_t buff;
353 	char *zone_str = knot_dname_to_str(buff, zone, sizeof(buff));
354 	if (zone_str == NULL) {
355 		zone_str = NULL_ZONE_STR;
356 	}
357 
358 	va_list args;
359 	va_start(args, fmt);
360 	log_msg_text(priority, src, zone_str, fmt, args, param);
361 	va_end(args);
362 }
363 
log_fmt_zone_str(int priority,log_source_t src,const char * zone,const char * fmt,...)364 void log_fmt_zone_str(int priority, log_source_t src, const char *zone,
365                       const char *fmt, ...)
366 {
367 	if (zone == NULL) {
368 		zone = NULL_ZONE_STR;
369 	}
370 
371 	va_list args;
372 	va_start(args, fmt);
373 	log_msg_text(priority, src, zone, fmt, args, NULL);
374 	va_end(args);
375 }
376 
log_update_privileges(int uid,int gid)377 int log_update_privileges(int uid, int gid)
378 {
379 	if (!log_isopen()) {
380 		return KNOT_EOK;
381 	}
382 
383 	for (int i = 0; i < s_log->file_count; ++i) {
384 		if (fchown(fileno(s_log->file[i]), uid, gid) < 0) {
385 			log_error("failed to change log file owner");
386 		}
387 	}
388 
389 	return KNOT_EOK;
390 }
391 
get_logtype(const char * logname)392 static log_target_t get_logtype(const char *logname)
393 {
394 	assert(logname);
395 
396 	if (strcasecmp(logname, "syslog") == 0) {
397 		return LOG_TARGET_SYSLOG;
398 	} else if (strcasecmp(logname, "stderr") == 0) {
399 		return LOG_TARGET_STDERR;
400 	} else if (strcasecmp(logname, "stdout") == 0) {
401 		return LOG_TARGET_STDOUT;
402 	} else {
403 		return LOG_TARGET_FILE;
404 	}
405 }
406 
log_open_file(log_t * log,const char * filename)407 static int log_open_file(log_t *log, const char *filename)
408 {
409 	assert(LOG_TARGET_FILE + log->file_count < log->target_count);
410 
411 	// Open the file.
412 	log->file[log->file_count] = fopen(filename, "a");
413 	if (log->file[log->file_count] == NULL) {
414 		return knot_map_errno();
415 	}
416 
417 	// Disable buffering.
418 	setvbuf(log->file[log->file_count], NULL, _IONBF, 0);
419 
420 	return LOG_TARGET_FILE + log->file_count++;
421 }
422 
log_reconfigure(conf_t * conf)423 void log_reconfigure(conf_t *conf)
424 {
425 	// Use defaults if no 'log' section is configured.
426 	if (conf_id_count(conf, C_LOG) == 0) {
427 		log_close();
428 		log_init();
429 		return;
430 	}
431 
432 	// Find maximum log target id.
433 	unsigned files = 0;
434 	for (conf_iter_t iter = conf_iter(conf, C_LOG); iter.code == KNOT_EOK;
435 	     conf_iter_next(conf, &iter)) {
436 		conf_val_t id = conf_iter_id(conf, &iter);
437 		if (get_logtype(conf_str(&id)) == LOG_TARGET_FILE) {
438 			++files;
439 		}
440 	}
441 
442 	// Initialize logsystem.
443 	log_t *log = sink_setup(files);
444 	if (log == NULL) {
445 		fprintf(stderr, "Failed to setup logging\n");
446 		return;
447 	}
448 
449 	// Setup logs.
450 	for (conf_iter_t iter = conf_iter(conf, C_LOG); iter.code == KNOT_EOK;
451 	     conf_iter_next(conf, &iter)) {
452 		conf_val_t id = conf_iter_id(conf, &iter);
453 		const char *logname = conf_str(&id);
454 
455 		// Get target.
456 		int target = get_logtype(logname);
457 		if (target == LOG_TARGET_FILE) {
458 			target = log_open_file(log, logname);
459 			if (target < 0) {
460 				log_error("failed to open log, file '%s' (%s)",
461 				          logname, knot_strerror(target));
462 				continue;
463 			}
464 		}
465 
466 		conf_val_t levels_val;
467 		unsigned levels;
468 
469 		// Set SERVER logging.
470 		levels_val = conf_id_get(conf, C_LOG, C_SERVER, &id);
471 		levels = conf_opt(&levels_val);
472 		sink_levels_add(log, target, LOG_SOURCE_SERVER, levels);
473 
474 		// Set CONTROL logging.
475 		levels_val = conf_id_get(conf, C_LOG, C_CTL, &id);
476 		levels = conf_opt(&levels_val);
477 		sink_levels_add(log, target, LOG_SOURCE_CONTROL, levels);
478 
479 		// Set ZONE logging.
480 		levels_val = conf_id_get(conf, C_LOG, C_ZONE, &id);
481 		levels = conf_opt(&levels_val);
482 		sink_levels_add(log, target, LOG_SOURCE_ZONE, levels);
483 
484 		// Set ANY logging.
485 		levels_val = conf_id_get(conf, C_LOG, C_ANY, &id);
486 		levels = conf_opt(&levels_val);
487 		sink_levels_add(log, target, LOG_SOURCE_ANY, levels);
488 	}
489 
490 	sink_publish(log);
491 }
492