1 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "istream.h"
6 #include "hash.h"
7 #include "str.h"
8 #include "strescape.h"
9 #include "time-util.h"
10 #include "master-service-private.h"
11 #include "master-service-settings.h"
12 #include "doveadm.h"
13 #include "doveadm-print.h"
14 
15 #include <stdio.h>
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <dirent.h>
19 #include <signal.h>
20 #include <sys/stat.h>
21 
22 #define LAST_LOG_TYPE LOG_TYPE_PANIC
23 #define TEST_LOG_MSG_PREFIX "This is Dovecot's "
24 #define LOG_ERRORS_FNAME "log-errors"
25 #define LOG_TIMESTAMP_FORMAT "%b %d %H:%M:%S"
26 
27 static void ATTR_NULL(2)
cmd_log_test(struct doveadm_cmd_context * cctx ATTR_UNUSED)28 cmd_log_test(struct doveadm_cmd_context *cctx ATTR_UNUSED)
29 {
30 	struct failure_context ctx;
31 	unsigned int i;
32 
33 	master_service->log_initialized = FALSE;
34 	master_service->flags |= MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR;
35 	master_service_init_log(master_service);
36 
37 	i_zero(&ctx);
38 	for (i = 0; i < LAST_LOG_TYPE; i++) {
39 		const char *prefix = failure_log_type_prefixes[i];
40 
41 		/* add timestamp so that syslog won't just write
42 		   "repeated message" text */
43 		ctx.type = i;
44 		i_log_type(&ctx, TEST_LOG_MSG_PREFIX"%s log (%u)",
45 			   t_str_lcase(t_strcut(prefix, ':')),
46 			   (unsigned int)ioloop_time);
47 	}
48 }
49 
cmd_log_reopen(struct doveadm_cmd_context * cctx ATTR_UNUSED)50 static void cmd_log_reopen(struct doveadm_cmd_context *cctx ATTR_UNUSED)
51 {
52 	doveadm_master_send_signal(SIGUSR1);
53 }
54 
55 struct log_find_file {
56 	const char *path;
57 	uoff_t size;
58 
59 	/* 1 << enum log_type */
60 	unsigned int mask;
61 };
62 
63 struct log_find_context {
64 	pool_t pool;
65 	HASH_TABLE(char *, struct log_find_file *) files;
66 };
67 
cmd_log_find_add(struct log_find_context * ctx,const char * path,enum log_type type)68 static void cmd_log_find_add(struct log_find_context *ctx,
69 			     const char *path, enum log_type type)
70 {
71 	struct log_find_file *file;
72 	char *key;
73 
74 	file = hash_table_lookup(ctx->files, path);
75 	if (file == NULL) {
76 		file = p_new(ctx->pool, struct log_find_file, 1);
77 		file->path = key = p_strdup(ctx->pool, path);
78 		hash_table_insert(ctx->files, key, file);
79 	}
80 
81 	file->mask |= 1 << type;
82 }
83 
84 static void
cmd_log_find_syslog_files(struct log_find_context * ctx,const char * path)85 cmd_log_find_syslog_files(struct log_find_context *ctx, const char *path)
86 {
87 	struct log_find_file *file;
88 	DIR *dir;
89 	struct dirent *d;
90 	struct stat st;
91 	char *key;
92 	string_t *full_path;
93 	size_t dir_len;
94 
95 	dir = opendir(path);
96 	if (dir == NULL) {
97 		i_error("opendir(%s) failed: %m", path);
98 		return;
99 	}
100 
101 	full_path = t_str_new(256);
102 	str_append(full_path, path);
103 	str_append_c(full_path, '/');
104 	dir_len = str_len(full_path);
105 
106 	while ((d = readdir(dir)) != NULL) {
107 		if (d->d_name[0] == '.')
108 			continue;
109 
110 		str_truncate(full_path, dir_len);
111 		str_append(full_path, d->d_name);
112 		if (stat(str_c(full_path), &st) < 0)
113 			continue;
114 
115 		if (S_ISDIR(st.st_mode)) {
116 			/* recursively go through all subdirectories */
117 			cmd_log_find_syslog_files(ctx, str_c(full_path));
118 		} else if (hash_table_lookup(ctx->files,
119 					     str_c(full_path)) == NULL) {
120 			file = p_new(ctx->pool, struct log_find_file, 1);
121 			file->size = st.st_size;
122 			file->path = key =
123 				p_strdup(ctx->pool, str_c(full_path));
124 			hash_table_insert(ctx->files, key, file);
125 		}
126 	}
127 
128 	(void)closedir(dir);
129 }
130 
log_type_find(const char * str,enum log_type * type_r)131 static bool log_type_find(const char *str, enum log_type *type_r)
132 {
133 	unsigned int i;
134 	size_t len = strlen(str);
135 
136 	for (i = 0; i < LAST_LOG_TYPE; i++) {
137 		if (strncasecmp(str, failure_log_type_prefixes[i], len) == 0 &&
138 		    failure_log_type_prefixes[i][len] == ':') {
139 			*type_r = i;
140 			return TRUE;
141 		}
142 	}
143 	return FALSE;
144 }
145 
cmd_log_find_syslog_file_messages(struct log_find_file * file)146 static void cmd_log_find_syslog_file_messages(struct log_find_file *file)
147 {
148 	struct istream *input;
149 	const char *line, *p;
150 	enum log_type type;
151 	int fd;
152 
153 	fd = open(file->path, O_RDONLY);
154 	if (fd == -1)
155 		return;
156 
157 	input = i_stream_create_fd_autoclose(&fd, 1024);
158 	i_stream_seek(input, file->size);
159 	while ((line = i_stream_read_next_line(input)) != NULL) {
160 		p = strstr(line, TEST_LOG_MSG_PREFIX);
161 		if (p == NULL)
162 			continue;
163 		p += strlen(TEST_LOG_MSG_PREFIX);
164 
165 		/* <type> log */
166 		T_BEGIN {
167 			if (log_type_find(t_strcut(p, ' '), &type))
168 				file->mask |= 1 << type;
169 		} T_END;
170 	}
171 	i_stream_destroy(&input);
172 }
173 
cmd_log_find_syslog_messages(struct log_find_context * ctx)174 static void cmd_log_find_syslog_messages(struct log_find_context *ctx)
175 {
176 	struct hash_iterate_context *iter;
177 	struct stat st;
178 	char *key;
179 	struct log_find_file *file;
180 
181 	iter = hash_table_iterate_init(ctx->files);
182 	while (hash_table_iterate(iter, ctx->files, &key, &file)) {
183 		if (stat(file->path, &st) < 0 ||
184 		    (uoff_t)st.st_size <= file->size)
185 			continue;
186 
187 		cmd_log_find_syslog_file_messages(file);
188 	}
189 	hash_table_iterate_deinit(&iter);
190 }
191 
192 static void
cmd_log_find_syslog(struct log_find_context * ctx,struct doveadm_cmd_context * cctx)193 cmd_log_find_syslog(struct log_find_context *ctx,
194 		    struct doveadm_cmd_context *cctx)
195 {
196 	const char *log_dir;
197 	struct stat st;
198 
199 	if (doveadm_cmd_param_str(cctx, "log-dir", &log_dir))
200 		;
201 	else if (stat("/var/log", &st) == 0 && S_ISDIR(st.st_mode))
202 		log_dir = "/var/log";
203 	else if (stat("/var/adm", &st) == 0 && S_ISDIR(st.st_mode))
204 		log_dir = "/var/adm";
205 	else
206 		return;
207 
208 	printf("Looking for log files from %s\n", log_dir);
209 	cmd_log_find_syslog_files(ctx, log_dir);
210 	cmd_log_test(cctx);
211 
212 	/* give syslog some time to write the messages to files */
213 	sleep(1);
214 	cmd_log_find_syslog_messages(ctx);
215 }
216 
cmd_log_find(struct doveadm_cmd_context * cctx)217 static void cmd_log_find(struct doveadm_cmd_context *cctx)
218 {
219 	const struct master_service_settings *set;
220 	const char *log_file_path;
221 	struct log_find_context ctx;
222 	unsigned int i;
223 
224 	i_zero(&ctx);
225 	ctx.pool = pool_alloconly_create("log file", 1024*32);
226 	hash_table_create(&ctx.files, ctx.pool, 0, str_hash, strcmp);
227 
228 	/* first get the paths that we know are used */
229 	set = master_service_settings_get(master_service);
230 	log_file_path = set->log_path;
231 	if (strcmp(log_file_path, "syslog") == 0)
232 		log_file_path = "";
233 	if (*log_file_path != '\0') {
234 		cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_WARNING);
235 		cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_ERROR);
236 		cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_FATAL);
237 	}
238 
239 	if (strcmp(set->info_log_path, "syslog") != 0) {
240 		if (*set->info_log_path != '\0')
241 			log_file_path = set->info_log_path;
242 		if (*log_file_path != '\0')
243 			cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_INFO);
244 	}
245 
246 	if (strcmp(set->debug_log_path, "syslog") != 0) {
247 		if (*set->debug_log_path != '\0')
248 			log_file_path = set->debug_log_path;
249 		if (*log_file_path != '\0')
250 			cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_DEBUG);
251 	}
252 
253 	if (*set->log_path == '\0' ||
254 	    strcmp(set->log_path, "syslog") == 0 ||
255 	    strcmp(set->info_log_path, "syslog") == 0 ||
256 	    strcmp(set->debug_log_path, "syslog") == 0) {
257 		/* at least some logs were logged via syslog */
258 		cmd_log_find_syslog(&ctx, cctx);
259 	}
260 
261 	/* print them */
262 	for (i = 0; i < LAST_LOG_TYPE; i++) {
263 		struct hash_iterate_context *iter;
264 		char *key;
265 		struct log_find_file *file;
266 		bool found = FALSE;
267 
268 		iter = hash_table_iterate_init(ctx.files);
269 		while (hash_table_iterate(iter, ctx.files, &key, &file)) {
270 			if ((file->mask & (1 << i)) != 0) {
271 				printf("%s%s\n", failure_log_type_prefixes[i],
272 				       file->path);
273 				found = TRUE;
274 			}
275 		}
276 		hash_table_iterate_deinit(&iter);
277 
278 		if (!found)
279 			printf("%sNot found\n", failure_log_type_prefixes[i]);
280 	}
281 	hash_table_destroy(&ctx.files);
282 	pool_unref(&ctx.pool);
283 }
284 
t_cmd_log_error_trim(const char * orig)285 static const char *t_cmd_log_error_trim(const char *orig)
286 {
287 	size_t pos;
288 
289 	/* Trim whitespace from suffix and remove ':' if it exists */
290 	for (pos = strlen(orig); pos > 0; pos--) {
291 		if (orig[pos-1] != ' ') {
292 			if (orig[pos-1] == ':')
293 				pos--;
294 			break;
295 		}
296 	}
297 	return orig[pos] == '\0' ? orig : t_strndup(orig, pos);
298 }
299 
cmd_log_error_write(const char * const * args,time_t min_timestamp)300 static void cmd_log_error_write(const char *const *args, time_t min_timestamp)
301 {
302 	/* <type> <timestamp> <prefix> <text> */
303 	const char *type_prefix = "?";
304 	unsigned int type;
305 	time_t t;
306 
307 	/* find type's prefix */
308 	for (type = 0; type < LOG_TYPE_COUNT; type++) {
309 		if (strcmp(args[0], failure_log_type_names[type]) == 0) {
310 			type_prefix = failure_log_type_prefixes[type];
311 			break;
312 		}
313 	}
314 
315 	if (str_to_time(args[1], &t) < 0) {
316 		i_error("Invalid timestamp: %s", args[1]);
317 		t = 0;
318 	}
319 	if (t >= min_timestamp) {
320 		doveadm_print(t_strflocaltime(LOG_TIMESTAMP_FORMAT, t));
321 		doveadm_print(t_cmd_log_error_trim(args[2]));
322 		doveadm_print(t_cmd_log_error_trim(type_prefix));
323 		doveadm_print(args[3]);
324 	}
325 }
326 
cmd_log_errors(struct doveadm_cmd_context * cctx)327 static void cmd_log_errors(struct doveadm_cmd_context *cctx)
328 {
329 	struct istream *input;
330 	const char *path, *line, *const *args;
331 	time_t min_timestamp = 0;
332 	int64_t since_int64;
333 	int fd;
334 
335 	if (doveadm_cmd_param_int64(cctx, "since", &since_int64))
336 		min_timestamp = since_int64;
337 
338 	path = t_strconcat(doveadm_settings->base_dir,
339 			   "/"LOG_ERRORS_FNAME, NULL);
340 	fd = net_connect_unix(path);
341 	if (fd == -1)
342 		i_fatal("net_connect_unix(%s) failed: %m", path);
343 	net_set_nonblock(fd, FALSE);
344 
345 	input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
346 
347 	doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED);
348 	doveadm_print_formatted_set_format("%{timestamp} %{type}: %{prefix}: %{text}\n");
349 
350 	doveadm_print_header_simple("timestamp");
351 	doveadm_print_header_simple("prefix");
352 	doveadm_print_header_simple("type");
353 	doveadm_print_header_simple("text");
354 
355 	while ((line = i_stream_read_next_line(input)) != NULL) T_BEGIN {
356 		args = t_strsplit_tabescaped(line);
357 		if (str_array_length(args) == 4)
358 			cmd_log_error_write(args, min_timestamp);
359 		else {
360 			i_error("Invalid input from log: %s", line);
361 			doveadm_exit_code = EX_PROTOCOL;
362 		}
363 	} T_END;
364 	i_stream_destroy(&input);
365 }
366 
367 struct doveadm_cmd_ver2 doveadm_cmd_log[] = {
368 {
369 	.name = "log test",
370 	.cmd = cmd_log_test,
371 	.usage = "",
372 DOVEADM_CMD_PARAMS_START
373 DOVEADM_CMD_PARAMS_END
374 },
375 {
376 	.name = "log reopen",
377 	.cmd = cmd_log_reopen,
378 	.usage = "",
379 DOVEADM_CMD_PARAMS_START
380 DOVEADM_CMD_PARAMS_END
381 },
382 {
383 	.name = "log find",
384 	.cmd = cmd_log_find,
385 	.usage = "[<dir>]",
386 DOVEADM_CMD_PARAMS_START
387 DOVEADM_CMD_PARAM('\0', "log-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
388 DOVEADM_CMD_PARAMS_END
389 },
390 {
391 	.name = "log errors",
392 	.usage = "[-s <min_timestamp>]",
393 	.cmd = cmd_log_errors,
394 DOVEADM_CMD_PARAMS_START
395 DOVEADM_CMD_PARAM('s', "since", CMD_PARAM_INT64, 0)
396 DOVEADM_CMD_PARAMS_END
397 }
398 };
399 
doveadm_register_log_commands(void)400 void doveadm_register_log_commands(void)
401 {
402 	unsigned int i;
403 
404 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_log); i++)
405 		doveadm_cmd_register_ver2(&doveadm_cmd_log[i]);
406 }
407