diff options
Diffstat (limited to 'src/util.c')
-rw-r--r-- | src/util.c | 834 |
1 files changed, 834 insertions, 0 deletions
diff --git a/src/util.c b/src/util.c new file mode 100644 index 000000000..90a536359 --- /dev/null +++ b/src/util.c @@ -0,0 +1,834 @@ +#include <sys/param.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <netdb.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <sys/file.h> +#include <syslog.h> +#include <glib.h> + +#include "config.h" +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#include "util.h" +#include "cfg_file.h" + +sig_atomic_t do_reopen_log = 0; + +int +event_make_socket_nonblocking (int fd) +{ + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { + return -1; + } + return 0; +} + +static int +make_socket_ai (struct addrinfo *ai) +{ + struct linger linger; + int fd, on = 1, r; + int serrno; + + /* Create listen socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + return (-1); + } + + if (event_make_socket_nonblocking(fd) < 0) + goto out; + + if (fcntl(fd, F_SETFD, 1) == -1) { + goto out; + } + + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); + linger.l_onoff = 1; + linger.l_linger = 5; + setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); + + r = bind(fd, ai->ai_addr, ai->ai_addrlen); + + if (r == -1) { + if (errno != EINPROGRESS) { + goto out; + } + } + + return (fd); + + out: + serrno = errno; + close(fd); + errno = serrno; + return (-1); +} + +int +make_socket (const char *address, u_short port) +{ + int fd; + struct addrinfo ai, *aitop = NULL; + char strport[NI_MAXSERV]; + int ai_result; + + memset (&ai, 0, sizeof (ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + ai.ai_flags = AI_PASSIVE; + snprintf (strport, sizeof (strport), "%d", port); + if ((ai_result = getaddrinfo (address, strport, &ai, &aitop)) != 0) { + return (-1); + } + + fd = make_socket_ai (aitop); + + freeaddrinfo (aitop); + + return (fd); +} + +int +make_unix_socket (const char *path, struct sockaddr_un *addr) +{ + size_t len = strlen (path); + int sock; + + if (len > sizeof (addr->sun_path) - 1) return -1; + + #ifdef FREEBSD + addr->sun_len = sizeof (struct sockaddr_un); + #endif + + addr->sun_family = AF_UNIX; + + strncpy (addr->sun_path, path, len); + + sock = socket (PF_LOCAL, SOCK_STREAM, 0); + + if (sock != -1) { + if (bind (sock, (struct sockaddr *) addr, sizeof (struct sockaddr_un)) == -1) return -1; + } + + return sock; +} + +void +read_cmd_line (int argc, char **argv, struct config_file *cfg) +{ + int ch; + while ((ch = getopt(argc, argv, "hfc:")) != -1) { + switch (ch) { + case 'f': + cfg->no_fork = 1; + break; + case 'c': + if (optarg && cfg->cfg_name) { + cfg->cfg_name = memory_pool_strdup (cfg->cfg_pool, optarg); + } + break; + case 'h': + case '?': + default: + /* Show help message and exit */ + printf ("Rspamd version " RVERSION "\n" + "Usage: rspamd [-h] [-n] [-f] [-c config_file]\n" + "-h: This help message\n" + "-f: Do not daemonize main process\n" + "-c: Specify config file (./rspamd.conf is used by default)\n"); + exit (0); + break; + } + } +} + +int +write_pid (struct rspamd_main *main) +{ + pid_t pid; + main->pfh = pidfile_open (main->cfg->pid_file, 0644, &pid); + + if (main->pfh == NULL) { + return -1; + } + + pidfile_write (main->pfh); + + return 0; +} + +void +init_signals (struct sigaction *signals, sig_t sig_handler) +{ + /* Setting up signal handlers */ + /* SIGUSR1 - reopen config file */ + /* SIGUSR2 - worker is ready for accept */ + sigemptyset(&signals->sa_mask); + sigaddset(&signals->sa_mask, SIGTERM); + sigaddset(&signals->sa_mask, SIGINT); + sigaddset(&signals->sa_mask, SIGHUP); + sigaddset(&signals->sa_mask, SIGCHLD); + sigaddset(&signals->sa_mask, SIGUSR1); + sigaddset(&signals->sa_mask, SIGUSR2); + + + signals->sa_handler = sig_handler; + sigaction (SIGTERM, signals, NULL); + sigaction (SIGINT, signals, NULL); + sigaction (SIGHUP, signals, NULL); + sigaction (SIGCHLD, signals, NULL); + sigaction (SIGUSR1, signals, NULL); + sigaction (SIGUSR2, signals, NULL); +} + +void +pass_signal_worker (struct workq *workers, int signo) +{ + struct rspamd_worker *cur; + TAILQ_FOREACH (cur, workers, next) { + kill (cur->pid, signo); + } +} + +void convert_to_lowercase (char *str, unsigned int size) +{ + while (size--) { + *str = tolower (*str); + str ++; + } +} + +#ifndef HAVE_SETPROCTITLE + +static char *title_buffer = 0; +static size_t title_buffer_size = 0; +static char *title_progname, *title_progname_full; + +int +setproctitle (const char *fmt, ...) +{ + if (!title_buffer || !title_buffer_size) { + errno = ENOMEM; + return -1; + } + + memset (title_buffer, '\0', title_buffer_size); + + ssize_t written; + + if (fmt) { + ssize_t written2; + va_list ap; + + written = snprintf (title_buffer, title_buffer_size, "%s: ", title_progname); + if (written < 0 || (size_t) written >= title_buffer_size) + return -1; + + va_start (ap, fmt); + written2 = + vsnprintf (title_buffer + written, + title_buffer_size - written, fmt, ap); + va_end (ap); + if (written2 < 0 + || (size_t) written2 >= title_buffer_size - written) + return -1; + } else { + written = + snprintf (title_buffer, title_buffer_size, "%s", + title_progname); + if (written < 0 || (size_t) written >= title_buffer_size) + return -1; + } + + written = strlen (title_buffer); + memset (title_buffer + written, '\0', title_buffer_size - written); + + return 0; +} + +/* + It has to be _init function, because __attribute__((constructor)) + functions gets called without arguments. +*/ + +int +init_title (int argc, char *argv[], char *envp[]) +{ + char *begin_of_buffer = 0, *end_of_buffer = 0; + int i; + + for (i = 0; i < argc; ++i) { + if (!begin_of_buffer) + begin_of_buffer = argv[i]; + if (!end_of_buffer || end_of_buffer + 1 == argv[i]) + end_of_buffer = argv[i] + strlen (argv[i]); + } + + for (i = 0; envp[i]; ++i) { + if (!begin_of_buffer) + begin_of_buffer = envp[i]; + if (!end_of_buffer || end_of_buffer + 1 == envp[i]) + end_of_buffer = envp[i] + strlen(envp[i]); + } + + if (!end_of_buffer) + return 0; + + char **new_environ = g_malloc ((i + 1) * sizeof (envp[0])); + + if (!new_environ) + return 0; + + for (i = 0; envp[i]; ++i) { + if (!(new_environ[i] = g_strdup (envp[i]))) + goto cleanup_enomem; + } + new_environ[i] = 0; + + if (program_invocation_name) { + title_progname_full = g_strdup (program_invocation_name); + + if (!title_progname_full) + goto cleanup_enomem; + + char *p = strrchr (title_progname_full, '/'); + + if (p) + title_progname = p + 1; + else + title_progname = title_progname_full; + + program_invocation_name = title_progname_full; + program_invocation_short_name = title_progname; + } + + environ = new_environ; + title_buffer = begin_of_buffer; + title_buffer_size = end_of_buffer - begin_of_buffer; + + return 0; + + cleanup_enomem: + for (--i; i >= 0; --i) { + g_free (new_environ[i]); + } + g_free (new_environ); + return 0; +} +#endif + +#ifndef HAVE_PIDFILE +extern char * __progname; +static int _pidfile_remove (struct pidfh *pfh, int freeit); + +static int +pidfile_verify (struct pidfh *pfh) +{ + struct stat sb; + + if (pfh == NULL || pfh->pf_fd == -1) + return (-1); + /* + * Check remembered descriptor. + */ + if (fstat (pfh->pf_fd, &sb) == -1) + return (errno); + if (sb.st_dev != pfh->pf_dev || sb.st_ino != pfh->pf_ino) + return -1; + return 0; +} + +static int +pidfile_read (const char *path, pid_t *pidptr) +{ + char buf[16], *endptr; + int error, fd, i; + + fd = open (path, O_RDONLY); + if (fd == -1) + return (errno); + + i = read (fd, buf, sizeof(buf) - 1); + error = errno; /* Remember errno in case close() wants to change it. */ + close (fd); + if (i == -1) + return error; + else if (i == 0) + return EAGAIN; + buf[i] = '\0'; + + *pidptr = strtol (buf, &endptr, 10); + if (endptr != &buf[i]) + return EINVAL; + + return 0; +} + +struct pidfh * +pidfile_open (const char *path, mode_t mode, pid_t *pidptr) +{ + struct pidfh *pfh; + struct stat sb; + int error, fd, len, count; + struct timespec rqtp; + + pfh = g_malloc (sizeof(*pfh)); + if (pfh == NULL) + return NULL; + + if (path == NULL) + len = snprintf (pfh->pf_path, sizeof(pfh->pf_path), + "/var/run/%s.pid", __progname); + else + len = snprintf (pfh->pf_path, sizeof(pfh->pf_path), + "%s", path); + if (len >= (int)sizeof (pfh->pf_path)) { + g_free (pfh); + errno = ENAMETOOLONG; + return NULL; + } + + /* + * Open the PID file and obtain exclusive lock. + * We truncate PID file here only to remove old PID immediatelly, + * PID file will be truncated again in pidfile_write(), so + * pidfile_write() can be called multiple times. + */ + fd = open (pfh->pf_path, + O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, mode); + flock (fd, LOCK_EX | LOCK_NB); + if (fd == -1) { + count = 0; + rqtp.tv_sec = 0; + rqtp.tv_nsec = 5000000; + if (errno == EWOULDBLOCK && pidptr != NULL) { + again: + errno = pidfile_read (pfh->pf_path, pidptr); + if (errno == 0) + errno = EEXIST; + else if (errno == EAGAIN) { + if (++count <= 3) { + nanosleep (&rqtp, 0); + goto again; + } + } + } + g_free (pfh); + return NULL; + } + /* + * Remember file information, so in pidfile_write() we are sure we write + * to the proper descriptor. + */ + if (fstat (fd, &sb) == -1) { + error = errno; + unlink (pfh->pf_path); + close (fd); + g_free (pfh); + errno = error; + return NULL; + } + + pfh->pf_fd = fd; + pfh->pf_dev = sb.st_dev; + pfh->pf_ino = sb.st_ino; + + return pfh; +} + +int +pidfile_write (struct pidfh *pfh) +{ + char pidstr[16]; + int error, fd; + + /* + * Check remembered descriptor, so we don't overwrite some other + * file if pidfile was closed and descriptor reused. + */ + errno = pidfile_verify (pfh); + if (errno != 0) { + /* + * Don't close descriptor, because we are not sure if it's ours. + */ + return -1; + } + fd = pfh->pf_fd; + + /* + * Truncate PID file, so multiple calls of pidfile_write() are allowed. + */ + if (ftruncate (fd, 0) == -1) { + error = errno; + _pidfile_remove (pfh, 0); + errno = error; + return -1; + } + + snprintf (pidstr, sizeof(pidstr), "%u", getpid ()); + if (pwrite (fd, pidstr, strlen (pidstr), 0) != (ssize_t)strlen (pidstr)) { + error = errno; + _pidfile_remove (pfh, 0); + errno = error; + return -1; + } + + return 0; +} + +int +pidfile_close (struct pidfh *pfh) +{ + int error; + + error = pidfile_verify (pfh); + if (error != 0) { + errno = error; + return -1; + } + + if (close (pfh->pf_fd) == -1) + error = errno; + g_free (pfh); + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + +static int +_pidfile_remove (struct pidfh *pfh, int freeit) +{ + int error; + + error = pidfile_verify (pfh); + if (error != 0) { + errno = error; + return -1; + } + + if (unlink (pfh->pf_path) == -1) + error = errno; + if (flock (pfh->pf_fd, LOCK_UN) == -1) { + if (error == 0) + error = errno; + } + if (close (pfh->pf_fd) == -1) { + if (error == 0) + error = errno; + } + if (freeit) + g_free (pfh); + else + pfh->pf_fd = -1; + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + +int +pidfile_remove (struct pidfh *pfh) +{ + + return (_pidfile_remove (pfh, 1)); +} +#endif + +/* + * Functions for parsing expressions + */ + +struct expression_stack { + char op; + struct expression_stack *next; +}; + +/* + * Push operand or operator to stack + */ +static struct expression_stack* +push_expression_stack (memory_pool_t *pool, struct expression_stack *head, char op) +{ + struct expression_stack *new; + new = memory_pool_alloc (pool, sizeof (struct expression_stack)); + new->op = op; + new->next = head; + return new; +} + +/* + * Delete symbol from stack, return pointer to operand or operator (casted to void* ) + */ +static char +delete_expression_stack (struct expression_stack **head) +{ + struct expression_stack *cur; + char res; + + if(*head == NULL) return 0; + + cur = *head; + res = cur->op; + + *head = cur->next; + return res; +} + +/* + * Return operation priority + */ +static int +logic_priority (char a) +{ + switch (a) { + case '!': + return 3; + case '|': + case '&': + return 2; + case '(': + return 1; + default: + return 0; + } +} + +/* + * Return 0 if symbol is not operation symbol (operand) + * Return 1 if symbol is operation symbol + */ +static int +is_operation_symbol (char a) +{ + switch (a) { + case '!': + case '&': + case '|': + case '(': + case ')': + return 1; + default: + return 0; + } +} + +static void +insert_expression (memory_pool_t *pool, struct expression **head, int type, char op, void *operand) +{ + struct expression *new, *cur; + + new = memory_pool_alloc (pool, sizeof (struct expression)); + new->type = type; + if (new->type == EXPR_OPERAND) { + new->content.operand = operand; + } + else { + new->content.operation = op; + } + new->next = NULL; + + if (!*head) { + *head = new; + } + else { + cur = *head; + while (cur->next) { + cur = cur->next; + } + cur->next = new; + } +} + +/* + * Make inverse polish record for specified expression + * Memory is allocated from given pool + */ +struct expression* +parse_expression (memory_pool_t *pool, char *line) +{ + struct expression *expr = NULL; + struct expression_stack *stack = NULL; + char *p, *c, *str, op, in_regexp = 0; + + if (line == NULL || pool == NULL) { + return NULL; + } + + p = line; + c = p; + while (*p) { + if (is_operation_symbol (*p) && !in_regexp) { + if (c != p) { + /* Copy operand */ + str = memory_pool_alloc (pool, p - c + 1); + strlcpy (str, c, (p - c + 1)); + insert_expression (pool, &expr, EXPR_OPERAND, 0, str); + } + if (*p == ')') { + if (stack == NULL) { + return NULL; + } + /* Pop all operators from stack to nearest '(' or to head */ + while (stack->op != '(') { + op = delete_expression_stack (&stack); + if (op != '(') { + insert_expression (pool, &expr, EXPR_OPERATION, op, NULL); + } + } + } + else if (*p == '(') { + /* Push it to stack */ + stack = push_expression_stack (pool, stack, *p); + } + else { + if (stack == NULL) { + stack = push_expression_stack (pool, stack, *p); + } + /* Check priority of logic operation */ + else { + if (logic_priority (stack->op) < logic_priority (*p)) { + stack = push_expression_stack (pool, stack, *p); + } + else { + /* Pop all operations that have higher priority than this one */ + while((stack != NULL) && (logic_priority (stack->op) >= logic_priority (*p))) { + op = delete_expression_stack (&stack); + if (op != '(') { + insert_expression (pool, &expr, EXPR_OPERATION, op, NULL); + } + } + stack = push_expression_stack (pool, stack, *p); + } + } + } + c = p + 1; + } + if (*p == '/' && (p == line || *(p - 1) != '\\')) { + in_regexp = !in_regexp; + } + p++; + } + /* Write last operand if it exists */ + if (c != p) { + /* Copy operand */ + str = memory_pool_alloc (pool, p - c + 1); + strlcpy (str, c, (p - c + 1)); + insert_expression (pool, &expr, EXPR_OPERAND, 0, str); + } + /* Pop everything from stack */ + while(stack != NULL) { + op = delete_expression_stack (&stack); + if (op != '(') { + insert_expression (pool, &expr, EXPR_OPERATION, op, NULL); + } + } + + return expr; +} + +/* Logging utility functions */ +int +open_log (struct config_file *cfg) +{ + switch (cfg->log_type) { + case RSPAMD_LOG_CONSOLE: + /* Do nothing with console */ + return 0; + case RSPAMD_LOG_SYSLOG: + openlog ("rspamd", LOG_NDELAY | LOG_PID, cfg->log_facility); + return 0; + case RSPAMD_LOG_FILE: + cfg->log_fd = open (cfg->log_file, O_CREAT | O_WRONLY | O_APPEND); + if (cfg->log_fd == -1) { + msg_err ("open_log: cannot open desired log file: %s, %m", cfg->log_file); + return -1; + } + return 0; + } +} + +int +reopen_log (struct config_file *cfg) +{ + do_reopen_log = 0; + switch (cfg->log_type) { + case RSPAMD_LOG_CONSOLE: + /* Do nothing with console */ + return 0; + case RSPAMD_LOG_SYSLOG: + closelog (); + break; + case RSPAMD_LOG_FILE: + close (cfg->log_fd); + break; + } + return open_log (cfg); +} + +void +syslog_log_function (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer arg) +{ + struct config_file *cfg = (struct config_file *)arg; + if (do_reopen_log) { + reopen_log (cfg); + } + + if (log_level <= cfg->log_level) { + if (log_level >= G_LOG_LEVEL_DEBUG) { + syslog (LOG_DEBUG, message); + } + else if (log_level >= G_LOG_LEVEL_INFO) { + syslog (LOG_INFO, message); + } + else if (log_level >= G_LOG_LEVEL_WARNING) { + syslog (LOG_WARNING, message); + } + else if (log_level >= G_LOG_LEVEL_CRITICAL) { + syslog (LOG_ERR, message); + } + } +} + +void +file_log_function (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer arg) +{ + struct config_file *cfg = (struct config_file *)arg; + char tmpbuf[128]; + int r; + struct iovec out[3]; + + if (cfg->log_fd == -1) { + return; + } + + if (do_reopen_log) { + reopen_log (cfg); + } + + if (log_level <= cfg->log_level) { + r = snprintf (tmpbuf, sizeof (tmpbuf), "#%d: %d rspamd ", (int)getpid (), (int)time (NULL)); + out[0].iov_base = tmpbuf; + out[0].iov_len = r; + out[1].iov_base = (char *)message; + out[1].iov_len = strlen (message); + out[2].iov_base = "\r\n"; + out[2].iov_len = 2; + + writev (cfg->log_fd, out, sizeof (out) / sizeof (out[0])); + } +} +/* + * vi:ts=4 + */ |