123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- /*
- * Copyright 2023 Vsevolod Stakhov
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- #include "config.h"
- #include "logger.h"
- #include "libserver/cfg_file.h"
- #include "libcryptobox/cryptobox.h"
- #include "unix-std.h"
-
- #include "logger_private.h"
-
- #define FILE_LOG_QUARK g_quark_from_static_string("file_logger")
-
- struct rspamd_file_logger_priv {
- gint fd;
- struct {
- uint32_t size;
- uint32_t used;
- u_char *buf;
- } io_buf;
- gboolean throttling;
- gchar *log_file;
- gboolean is_buffered;
- gboolean log_severity;
- time_t throttling_time;
- uint32_t repeats;
- uint64_t last_line_cksum;
- gchar *saved_message;
- gsize saved_mlen;
- gchar *saved_function;
- gchar *saved_module;
- gchar *saved_id;
- guint saved_loglevel;
- };
-
- /**
- * Calculate checksum for log line (used for repeating logic)
- */
- static inline uint64_t
- rspamd_log_calculate_cksum(const gchar *message, size_t mlen)
- {
- return rspamd_cryptobox_fast_hash(message, mlen, rspamd_hash_seed());
- }
-
- /*
- * Write a line to log file (unbuffered)
- */
- static bool
- direct_write_log_line(rspamd_logger_t *rspamd_log,
- struct rspamd_file_logger_priv *priv,
- void *data,
- gsize count,
- gboolean is_iov,
- gint level_flags)
- {
- struct iovec *iov;
- const gchar *line;
- glong r;
- gint fd;
- gboolean locked = FALSE;
-
- iov = (struct iovec *) data;
- fd = priv->fd;
-
- if (!rspamd_log->no_lock) {
- gsize tlen;
-
- if (is_iov) {
- tlen = 0;
-
- for (guint i = 0; i < count; i++) {
- tlen += iov[i].iov_len;
- }
- }
- else {
- tlen = count;
- }
-
- if (tlen > PIPE_BUF) {
- locked = TRUE;
-
- #ifndef DISABLE_PTHREAD_MUTEX
- if (rspamd_log->mtx) {
- rspamd_mempool_lock_mutex(rspamd_log->mtx);
- }
- else {
- rspamd_file_lock(fd, FALSE);
- }
- #else
- rspamd_file_lock(fd, FALSE);
- #endif
- }
- }
-
- if (is_iov) {
- r = writev(fd, iov, count);
- }
- else {
- line = (const gchar *) data;
- r = write(fd, line, count);
- }
-
- if (locked) {
- #ifndef DISABLE_PTHREAD_MUTEX
- if (rspamd_log->mtx) {
- rspamd_mempool_unlock_mutex(rspamd_log->mtx);
- }
- else {
- rspamd_file_unlock(fd, FALSE);
- }
- #else
- rspamd_file_unlock(fd, FALSE);
- #endif
- }
-
- if (r == -1) {
- /* We cannot write message to file, so we need to detect error and make decision */
- if (errno == EINTR) {
- /* Try again */
- return direct_write_log_line(rspamd_log, priv, data, count, is_iov, level_flags);
- }
-
- if (errno == EFAULT || errno == EINVAL || errno == EFBIG ||
- errno == ENOSPC) {
- /* Rare case */
- priv->throttling = TRUE;
- priv->throttling_time = time(NULL);
- }
- else if (errno == EPIPE || errno == EBADF) {
- /* We write to some pipe and it disappears, disable logging or we has opened bad file descriptor */
- rspamd_log->enabled = FALSE;
- }
-
- return false;
- }
- else if (priv->throttling) {
- priv->throttling = FALSE;
- }
-
- return true;
- }
-
- /**
- * Fill buffer with message (limits must be checked BEFORE this call)
- */
- static void
- fill_buffer(rspamd_logger_t *rspamd_log,
- struct rspamd_file_logger_priv *priv,
- const struct iovec *iov, gint iovcnt)
- {
- gint i;
-
- for (i = 0; i < iovcnt; i++) {
- memcpy(priv->io_buf.buf + priv->io_buf.used,
- iov[i].iov_base,
- iov[i].iov_len);
- priv->io_buf.used += iov[i].iov_len;
- }
- }
-
- static void
- rspamd_log_flush(rspamd_logger_t *rspamd_log, struct rspamd_file_logger_priv *priv)
- {
- if (priv->is_buffered) {
- direct_write_log_line(rspamd_log,
- priv,
- priv->io_buf.buf,
- priv->io_buf.used,
- FALSE,
- rspamd_log->log_level);
- priv->io_buf.used = 0;
- }
- }
-
- /*
- * Write message to buffer or to file (using direct_write_log_line function)
- */
- static bool
- file_log_helper(rspamd_logger_t *rspamd_log,
- struct rspamd_file_logger_priv *priv,
- const struct iovec *iov,
- guint iovcnt,
- gint level_flags)
- {
- size_t len = 0;
- guint i;
-
- if (!priv->is_buffered) {
- /* Write string directly */
- return direct_write_log_line(rspamd_log, priv, (void *) iov, iovcnt,
- TRUE, level_flags);
- }
- else {
- /* Calculate total length */
- for (i = 0; i < iovcnt; i++) {
- len += iov[i].iov_len;
- }
- /* Fill buffer */
- if (priv->io_buf.size < len) {
- /* Buffer is too small to hold this string, so write it directly */
- rspamd_log_flush(rspamd_log, priv);
- return direct_write_log_line(rspamd_log, priv, (void *) iov, iovcnt,
- TRUE, level_flags);
- }
- else if (priv->io_buf.used + len >= priv->io_buf.size) {
- /* Buffer is full, try to write it directly */
- rspamd_log_flush(rspamd_log, priv);
- fill_buffer(rspamd_log, priv, iov, iovcnt);
- }
- else {
- /* Copy incoming string to buffer */
- fill_buffer(rspamd_log, priv, iov, iovcnt);
- }
- }
-
- return true;
- }
-
- static void
- rspamd_log_reset_repeated(rspamd_logger_t *rspamd_log,
- struct rspamd_file_logger_priv *priv)
- {
- gchar tmpbuf[256];
- gssize r;
-
- if (priv->repeats > REPEATS_MIN) {
- r = rspamd_snprintf(tmpbuf,
- sizeof(tmpbuf),
- "Last message repeated %ud times",
- priv->repeats - REPEATS_MIN);
- priv->repeats = 0;
-
- if (priv->saved_message) {
- rspamd_log_file_log(priv->saved_module,
- priv->saved_id,
- priv->saved_function,
- priv->saved_loglevel | RSPAMD_LOG_FORCED,
- priv->saved_message,
- priv->saved_mlen,
- rspamd_log,
- priv);
-
- g_free(priv->saved_message);
- g_free(priv->saved_function);
- g_free(priv->saved_module);
- g_free(priv->saved_id);
- priv->saved_message = NULL;
- priv->saved_function = NULL;
- priv->saved_module = NULL;
- priv->saved_id = NULL;
- }
-
- /* It is safe to use temporary buffer here as it is not static */
- rspamd_log_file_log(NULL, NULL,
- G_STRFUNC,
- priv->saved_loglevel | RSPAMD_LOG_FORCED,
- tmpbuf,
- r,
- rspamd_log,
- priv);
- rspamd_log_flush(rspamd_log, priv);
- }
- }
-
- static gint
- rspamd_try_open_log_fd(rspamd_logger_t *rspamd_log,
- struct rspamd_file_logger_priv *priv,
- uid_t uid, gid_t gid,
- GError **err)
- {
- gint fd;
-
- fd = open(priv->log_file,
- O_CREAT | O_WRONLY | O_APPEND,
- S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
- if (fd == -1) {
- g_set_error(err, FILE_LOG_QUARK, errno,
- "open_log: cannot open desired log file: %s, %s\n",
- priv->log_file, strerror(errno));
- return -1;
- }
-
- if (uid != -1 || gid != -1) {
- if (fchown(fd, uid, gid) == -1) {
- g_set_error(err, FILE_LOG_QUARK, errno,
- "open_log: cannot chown desired log file: %s, %s\n",
- priv->log_file, strerror(errno));
- close(fd);
-
- return -1;
- }
- }
-
- return fd;
- }
-
- void *
- rspamd_log_file_init(rspamd_logger_t *logger, struct rspamd_config *cfg,
- uid_t uid, gid_t gid, GError **err)
- {
- struct rspamd_file_logger_priv *priv;
-
- if (!cfg || !cfg->cfg_name) {
- g_set_error(err, FILE_LOG_QUARK, EINVAL,
- "no log file specified");
- return NULL;
- }
-
- priv = g_malloc0(sizeof(*priv));
-
- if (cfg->log_buffered) {
- if (cfg->log_buf_size != 0) {
- priv->io_buf.size = cfg->log_buf_size;
- }
- else {
- priv->io_buf.size = LOGBUF_LEN;
- }
- priv->is_buffered = TRUE;
- priv->io_buf.buf = g_malloc(priv->io_buf.size);
- }
-
- if (cfg->log_file) {
- priv->log_file = g_strdup(cfg->log_file);
- }
-
- priv->log_severity = (logger->flags & RSPAMD_LOG_FLAG_SEVERITY);
- priv->fd = rspamd_try_open_log_fd(logger, priv, uid, gid, err);
-
- if (priv->fd == -1) {
- rspamd_log_file_dtor(logger, priv);
-
- return NULL;
- }
-
- return priv;
- }
-
- void rspamd_log_file_dtor(rspamd_logger_t *logger, gpointer arg)
- {
- struct rspamd_file_logger_priv *priv = (struct rspamd_file_logger_priv *) arg;
-
- rspamd_log_reset_repeated(logger, priv);
- rspamd_log_flush(logger, priv);
-
- if (priv->fd != -1) {
- if (close(priv->fd) == -1) {
- rspamd_fprintf(stderr, "cannot close log fd %d: %s; log file = %s\n",
- priv->fd, strerror(errno), priv->log_file);
- }
- }
-
- g_free(priv->log_file);
- g_free(priv);
- }
-
- bool rspamd_log_file_log(const gchar *module, const gchar *id,
- const gchar *function,
- gint level_flags,
- const gchar *message,
- gsize mlen,
- rspamd_logger_t *rspamd_log,
- gpointer arg)
- {
- struct rspamd_file_logger_priv *priv = (struct rspamd_file_logger_priv *) arg;
- gdouble now;
- uint64_t cksum;
- gboolean got_time = FALSE;
-
-
- if (!(level_flags & RSPAMD_LOG_FORCED) && !rspamd_log->enabled) {
- return false;
- }
-
- /* Check throttling due to write errors */
- if (!(level_flags & RSPAMD_LOG_FORCED) && priv->throttling) {
- now = rspamd_get_calendar_ticks();
-
- if (priv->throttling_time != now) {
- priv->throttling_time = now;
- got_time = TRUE;
- }
- else {
- /* Do not try to write to file too often while throttling */
- return false;
- }
- }
-
- /* Check repeats */
- cksum = rspamd_log_calculate_cksum(message, mlen);
-
- if (cksum == priv->last_line_cksum) {
- priv->repeats++;
-
- if (priv->repeats > REPEATS_MIN && priv->repeats <
- REPEATS_MAX) {
- /* Do not log anything but save message for future */
- if (priv->saved_message == NULL) {
- priv->saved_function = g_strdup(function);
- priv->saved_mlen = mlen;
- priv->saved_message = g_malloc(mlen);
- memcpy(priv->saved_message, message, mlen);
-
- if (module) {
- priv->saved_module = g_strdup(module);
- }
-
- if (id) {
- priv->saved_id = g_strdup(id);
- }
-
- priv->saved_loglevel = level_flags;
- }
-
- return true;
- }
- else if (priv->repeats > REPEATS_MAX) {
- rspamd_log_reset_repeated(rspamd_log, priv);
-
- bool ret = rspamd_log_file_log(module, id,
- function,
- level_flags,
- message,
- mlen,
- rspamd_log,
- priv);
-
- /* Probably we have more repeats in future */
- priv->repeats = REPEATS_MIN + 1;
-
- return ret;
- }
- }
- else {
- /* Reset counter if new message differs from saved message */
- priv->last_line_cksum = cksum;
-
- if (priv->repeats > REPEATS_MIN) {
- rspamd_log_reset_repeated(rspamd_log, priv);
- return rspamd_log_file_log(module, id,
- function,
- level_flags,
- message,
- mlen,
- rspamd_log,
- arg);
- }
- else {
- priv->repeats = 0;
- }
- }
- if (!got_time) {
- now = rspamd_get_calendar_ticks();
- }
-
- struct rspamd_logger_iov_ctx iov_ctx;
- memset(&iov_ctx, 0, sizeof(iov_ctx));
- rspamd_log_fill_iov(&iov_ctx, now, module, id, function, level_flags, message,
- mlen, rspamd_log);
-
- bool ret = file_log_helper(rspamd_log, priv, iov_ctx.iov, iov_ctx.niov, level_flags);
- rspamd_log_iov_free(&iov_ctx);
-
- return ret;
- }
-
- void *
- rspamd_log_file_reload(rspamd_logger_t *logger, struct rspamd_config *cfg,
- gpointer arg, uid_t uid, gid_t gid, GError **err)
- {
- struct rspamd_file_logger_priv *npriv;
-
- if (!cfg->cfg_name) {
- g_set_error(err, FILE_LOG_QUARK, EINVAL,
- "no log file specified");
- return NULL;
- }
-
- npriv = rspamd_log_file_init(logger, cfg, uid, gid, err);
-
- if (npriv) {
- /* Close old */
- rspamd_log_file_dtor(logger, arg);
- }
-
- return npriv;
- }
-
- bool rspamd_log_file_on_fork(rspamd_logger_t *logger, struct rspamd_config *cfg,
- gpointer arg, GError **err)
- {
- struct rspamd_file_logger_priv *priv = (struct rspamd_file_logger_priv *) arg;
-
- rspamd_log_reset_repeated(logger, priv);
- rspamd_log_flush(logger, priv);
-
- return true;
- }
|