/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "istream.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "time-util.h" #include "master-service-private.h" #include "master-service-settings.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include #include #include #include #define LAST_LOG_TYPE LOG_TYPE_PANIC #define TEST_LOG_MSG_PREFIX "This is Dovecot's " #define LOG_ERRORS_FNAME "log-errors" #define LOG_TIMESTAMP_FORMAT "%b %d %H:%M:%S" static void ATTR_NULL(2) cmd_log_test(struct doveadm_cmd_context *cctx ATTR_UNUSED) { struct failure_context ctx; unsigned int i; master_service->log_initialized = FALSE; master_service->flags |= MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR; master_service_init_log(master_service); i_zero(&ctx); for (i = 0; i < LAST_LOG_TYPE; i++) { const char *prefix = failure_log_type_prefixes[i]; /* add timestamp so that syslog won't just write "repeated message" text */ ctx.type = i; i_log_type(&ctx, TEST_LOG_MSG_PREFIX"%s log (%u)", t_str_lcase(t_strcut(prefix, ':')), (unsigned int)ioloop_time); } } static void cmd_log_reopen(struct doveadm_cmd_context *cctx ATTR_UNUSED) { doveadm_master_send_signal(SIGUSR1); } struct log_find_file { const char *path; uoff_t size; /* 1 << enum log_type */ unsigned int mask; }; struct log_find_context { pool_t pool; HASH_TABLE(char *, struct log_find_file *) files; }; static void cmd_log_find_add(struct log_find_context *ctx, const char *path, enum log_type type) { struct log_find_file *file; char *key; file = hash_table_lookup(ctx->files, path); if (file == NULL) { file = p_new(ctx->pool, struct log_find_file, 1); file->path = key = p_strdup(ctx->pool, path); hash_table_insert(ctx->files, key, file); } file->mask |= 1 << type; } static void cmd_log_find_syslog_files(struct log_find_context *ctx, const char *path) { struct log_find_file *file; DIR *dir; struct dirent *d; struct stat st; char *key; string_t *full_path; size_t dir_len; dir = opendir(path); if (dir == NULL) { i_error("opendir(%s) failed: %m", path); return; } full_path = t_str_new(256); str_append(full_path, path); str_append_c(full_path, '/'); dir_len = str_len(full_path); while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; str_truncate(full_path, dir_len); str_append(full_path, d->d_name); if (stat(str_c(full_path), &st) < 0) continue; if (S_ISDIR(st.st_mode)) { /* recursively go through all subdirectories */ cmd_log_find_syslog_files(ctx, str_c(full_path)); } else if (hash_table_lookup(ctx->files, str_c(full_path)) == NULL) { file = p_new(ctx->pool, struct log_find_file, 1); file->size = st.st_size; file->path = key = p_strdup(ctx->pool, str_c(full_path)); hash_table_insert(ctx->files, key, file); } } (void)closedir(dir); } static bool log_type_find(const char *str, enum log_type *type_r) { unsigned int i; size_t len = strlen(str); for (i = 0; i < LAST_LOG_TYPE; i++) { if (strncasecmp(str, failure_log_type_prefixes[i], len) == 0 && failure_log_type_prefixes[i][len] == ':') { *type_r = i; return TRUE; } } return FALSE; } static void cmd_log_find_syslog_file_messages(struct log_find_file *file) { struct istream *input; const char *line, *p; enum log_type type; int fd; fd = open(file->path, O_RDONLY); if (fd == -1) return; input = i_stream_create_fd_autoclose(&fd, 1024); i_stream_seek(input, file->size); while ((line = i_stream_read_next_line(input)) != NULL) { p = strstr(line, TEST_LOG_MSG_PREFIX); if (p == NULL) continue; p += strlen(TEST_LOG_MSG_PREFIX); /* log */ T_BEGIN { if (log_type_find(t_strcut(p, ' '), &type)) file->mask |= 1 << type; } T_END; } i_stream_destroy(&input); } static void cmd_log_find_syslog_messages(struct log_find_context *ctx) { struct hash_iterate_context *iter; struct stat st; char *key; struct log_find_file *file; iter = hash_table_iterate_init(ctx->files); while (hash_table_iterate(iter, ctx->files, &key, &file)) { if (stat(file->path, &st) < 0 || (uoff_t)st.st_size <= file->size) continue; cmd_log_find_syslog_file_messages(file); } hash_table_iterate_deinit(&iter); } static void cmd_log_find_syslog(struct log_find_context *ctx, struct doveadm_cmd_context *cctx) { const char *log_dir; struct stat st; if (doveadm_cmd_param_str(cctx, "log-dir", &log_dir)) ; else if (stat("/var/log", &st) == 0 && S_ISDIR(st.st_mode)) log_dir = "/var/log"; else if (stat("/var/adm", &st) == 0 && S_ISDIR(st.st_mode)) log_dir = "/var/adm"; else return; printf("Looking for log files from %s\n", log_dir); cmd_log_find_syslog_files(ctx, log_dir); cmd_log_test(cctx); /* give syslog some time to write the messages to files */ sleep(1); cmd_log_find_syslog_messages(ctx); } static void cmd_log_find(struct doveadm_cmd_context *cctx) { const struct master_service_settings *set; const char *log_file_path; struct log_find_context ctx; unsigned int i; i_zero(&ctx); ctx.pool = pool_alloconly_create("log file", 1024*32); hash_table_create(&ctx.files, ctx.pool, 0, str_hash, strcmp); /* first get the paths that we know are used */ set = master_service_settings_get(master_service); log_file_path = set->log_path; if (strcmp(log_file_path, "syslog") == 0) log_file_path = ""; if (*log_file_path != '\0') { cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_WARNING); cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_ERROR); cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_FATAL); } if (strcmp(set->info_log_path, "syslog") != 0) { if (*set->info_log_path != '\0') log_file_path = set->info_log_path; if (*log_file_path != '\0') cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_INFO); } if (strcmp(set->debug_log_path, "syslog") != 0) { if (*set->debug_log_path != '\0') log_file_path = set->debug_log_path; if (*log_file_path != '\0') cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_DEBUG); } if (*set->log_path == '\0' || strcmp(set->log_path, "syslog") == 0 || strcmp(set->info_log_path, "syslog") == 0 || strcmp(set->debug_log_path, "syslog") == 0) { /* at least some logs were logged via syslog */ cmd_log_find_syslog(&ctx, cctx); } /* print them */ for (i = 0; i < LAST_LOG_TYPE; i++) { struct hash_iterate_context *iter; char *key; struct log_find_file *file; bool found = FALSE; iter = hash_table_iterate_init(ctx.files); while (hash_table_iterate(iter, ctx.files, &key, &file)) { if ((file->mask & (1 << i)) != 0) { printf("%s%s\n", failure_log_type_prefixes[i], file->path); found = TRUE; } } hash_table_iterate_deinit(&iter); if (!found) printf("%sNot found\n", failure_log_type_prefixes[i]); } hash_table_destroy(&ctx.files); pool_unref(&ctx.pool); } static const char *t_cmd_log_error_trim(const char *orig) { size_t pos; /* Trim whitespace from suffix and remove ':' if it exists */ for (pos = strlen(orig); pos > 0; pos--) { if (orig[pos-1] != ' ') { if (orig[pos-1] == ':') pos--; break; } } return orig[pos] == '\0' ? orig : t_strndup(orig, pos); } static void cmd_log_error_write(const char *const *args, time_t min_timestamp) { /* */ const char *type_prefix = "?"; unsigned int type; time_t t; /* find type's prefix */ for (type = 0; type < LOG_TYPE_COUNT; type++) { if (strcmp(args[0], failure_log_type_names[type]) == 0) { type_prefix = failure_log_type_prefixes[type]; break; } } if (str_to_time(args[1], &t) < 0) { i_error("Invalid timestamp: %s", args[1]); t = 0; } if (t >= min_timestamp) { doveadm_print(t_strflocaltime(LOG_TIMESTAMP_FORMAT, t)); doveadm_print(t_cmd_log_error_trim(args[2])); doveadm_print(t_cmd_log_error_trim(type_prefix)); doveadm_print(args[3]); } } static void cmd_log_errors(struct doveadm_cmd_context *cctx) { struct istream *input; const char *path, *line, *const *args; time_t min_timestamp = 0; int64_t since_int64; int fd; if (doveadm_cmd_param_int64(cctx, "since", &since_int64)) min_timestamp = since_int64; path = t_strconcat(doveadm_settings->base_dir, "/"LOG_ERRORS_FNAME, NULL); fd = net_connect_unix(path); if (fd == -1) i_fatal("net_connect_unix(%s) failed: %m", path); net_set_nonblock(fd, FALSE); input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{timestamp} %{type}: %{prefix}: %{text}\n"); doveadm_print_header_simple("timestamp"); doveadm_print_header_simple("prefix"); doveadm_print_header_simple("type"); doveadm_print_header_simple("text"); while ((line = i_stream_read_next_line(input)) != NULL) T_BEGIN { args = t_strsplit_tabescaped(line); if (str_array_length(args) == 4) cmd_log_error_write(args, min_timestamp); else { i_error("Invalid input from log: %s", line); doveadm_exit_code = EX_PROTOCOL; } } T_END; i_stream_destroy(&input); } struct doveadm_cmd_ver2 doveadm_cmd_log[] = { { .name = "log test", .cmd = cmd_log_test, .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAMS_END }, { .name = "log reopen", .cmd = cmd_log_reopen, .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAMS_END }, { .name = "log find", .cmd = cmd_log_find, .usage = "[]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "log-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "log errors", .usage = "[-s ]", .cmd = cmd_log_errors, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('s', "since", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAMS_END } }; void doveadm_register_log_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_log); i++) doveadm_cmd_register_ver2(&doveadm_cmd_log[i]); }