aboutsummaryrefslogtreecommitdiffstats
path: root/src/libutil/util.c
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2014-04-21 16:25:51 +0100
committerVsevolod Stakhov <vsevolod@highsecure.ru>2014-04-21 16:25:51 +0100
commit61555065f3d1c8badcc9573691232f1b6e42988c (patch)
tree563d5b7cb8c468530f7e79c4da0a75267b1184e1 /src/libutil/util.c
parentad5bf825b7f33bc10311673991f0cc888e69c0b1 (diff)
downloadrspamd-61555065f3d1c8badcc9573691232f1b6e42988c.tar.gz
rspamd-61555065f3d1c8badcc9573691232f1b6e42988c.zip
Rework project structure, remove trash files.
Diffstat (limited to 'src/libutil/util.c')
-rw-r--r--src/libutil/util.c2275
1 files changed, 2275 insertions, 0 deletions
diff --git a/src/libutil/util.c b/src/libutil/util.c
new file mode 100644
index 000000000..03b38e087
--- /dev/null
+++ b/src/libutil/util.c
@@ -0,0 +1,2275 @@
+/*
+ * Copyright (c) 2009-2012, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "config.h"
+#include "util.h"
+#include "cfg_file.h"
+#include "main.h"
+#include "statfile.h"
+#include "filter.h"
+#include "message.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#endif
+
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#ifdef HAVE_READPASSPHRASE_H
+#include <readpassphrase.h>
+#endif
+
+/* Check log messages intensity once per minute */
+#define CHECK_TIME 60
+/* More than 2 log messages per second */
+#define BUF_INTENSITY 2
+/* Default connect timeout for sync sockets */
+#define CONNECT_TIMEOUT 3
+
+gint
+make_socket_nonblocking (gint fd)
+{
+ gint ofl;
+
+ ofl = fcntl (fd, F_GETFL, 0);
+
+ if (fcntl (fd, F_SETFL, ofl | O_NONBLOCK) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ return -1;
+ }
+ return 0;
+}
+
+gint
+make_socket_blocking (gint fd)
+{
+ gint ofl;
+
+ ofl = fcntl (fd, F_GETFL, 0);
+
+ if (fcntl (fd, F_SETFL, ofl & (~O_NONBLOCK)) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ return -1;
+ }
+ return 0;
+}
+
+gint
+poll_sync_socket (gint fd, gint timeout, short events)
+{
+ gint r;
+ struct pollfd fds[1];
+
+ fds->fd = fd;
+ fds->events = events;
+ fds->revents = 0;
+ while ((r = poll (fds, 1, timeout)) < 0) {
+ if (errno != EINTR) {
+ break;
+ }
+ }
+
+ return r;
+}
+
+static gint
+make_inet_socket (gint type, struct addrinfo *addr, gboolean is_server, gboolean async, GList **list)
+{
+ gint fd, r, optlen, on = 1, s_error;
+ struct addrinfo *cur;
+
+ cur = addr;
+ while (cur) {
+ /* Create socket */
+ fd = socket (cur->ai_family, type, 0);
+ if (fd == -1) {
+ msg_warn ("socket failed: %d, '%s'", errno, strerror (errno));
+ goto out;
+ }
+
+ if (make_socket_nonblocking (fd) < 0) {
+ goto out;
+ }
+
+ /* Set close on exec */
+ if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ goto out;
+ }
+
+ if (is_server) {
+ setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&on, sizeof (gint));
+#ifdef HAVE_IPV6_V6ONLY
+ if (cur->ai_family == AF_INET6) {
+ setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, (const void *)&on, sizeof (gint));
+ }
+#endif
+ r = bind (fd, cur->ai_addr, cur->ai_addrlen);
+ }
+ else {
+ r = connect (fd, cur->ai_addr, cur->ai_addrlen);
+ }
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ msg_warn ("bind/connect failed: %d, '%s'", errno, strerror (errno));
+ goto out;
+ }
+ if (!async) {
+ /* Try to poll */
+ if (poll_sync_socket (fd, CONNECT_TIMEOUT * 1000, POLLOUT) <= 0) {
+ errno = ETIMEDOUT;
+ msg_warn ("bind/connect failed: timeout");
+ goto out;
+ }
+ else {
+ /* Make synced again */
+ if (make_socket_blocking (fd) < 0) {
+ goto out;
+ }
+ }
+ }
+ }
+ else {
+ /* Still need to check SO_ERROR on socket */
+ optlen = sizeof (s_error);
+ getsockopt (fd, SOL_SOCKET, SO_ERROR, (void *)&s_error, &optlen);
+ if (s_error) {
+ errno = s_error;
+ goto out;
+ }
+ }
+ if (list == NULL) {
+ /* Go out immediately */
+ break;
+ }
+ else if (fd != -1) {
+ *list = g_list_prepend (*list, GINT_TO_POINTER (fd));
+ cur = cur->ai_next;
+ continue;
+ }
+out:
+ if (fd != -1) {
+ close (fd);
+ }
+ fd = -1;
+ cur = cur->ai_next;
+ }
+ return (fd);
+}
+
+gint
+make_tcp_socket (struct addrinfo *addr, gboolean is_server, gboolean async)
+{
+ return make_inet_socket (SOCK_STREAM, addr, is_server, async, NULL);
+}
+
+gint
+make_udp_socket (struct addrinfo *addr, gboolean is_server, gboolean async)
+{
+ return make_inet_socket (SOCK_DGRAM, addr, is_server, async, NULL);
+}
+
+gint
+make_unix_socket (const gchar *path, struct sockaddr_un *addr, gint type, gboolean is_server, gboolean async)
+{
+ gint fd = -1, s_error, r, optlen, serrno, on = 1;
+ struct stat st;
+
+ if (path == NULL)
+ return -1;
+
+ addr->sun_family = AF_UNIX;
+
+ rspamd_strlcpy (addr->sun_path, path, sizeof (addr->sun_path));
+#ifdef FREEBSD
+ addr->sun_len = SUN_LEN (addr);
+#endif
+
+ if (is_server) {
+ /* Unlink socket if it exists already */
+ if (lstat (addr->sun_path, &st) != -1) {
+ if (S_ISSOCK (st.st_mode)) {
+ if (unlink (addr->sun_path) == -1) {
+ msg_warn ("unlink %s failed: %d, '%s'", addr->sun_path, errno, strerror (errno));
+ goto out;
+ }
+ }
+ else {
+ msg_warn ("%s is not a socket", addr->sun_path);
+ goto out;
+ }
+ }
+ }
+ fd = socket (PF_LOCAL, type, 0);
+
+ if (fd == -1) {
+ msg_warn ("socket failed %s: %d, '%s'", addr->sun_path, errno, strerror (errno));
+ return -1;
+ }
+
+ if (make_socket_nonblocking (fd) < 0) {
+ goto out;
+ }
+
+ /* Set close on exec */
+ if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) {
+ msg_warn ("fcntl failed %s: %d, '%s'", addr->sun_path, errno, strerror (errno));
+ goto out;
+ }
+ if (is_server) {
+ setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&on, sizeof (gint));
+ r = bind (fd, (struct sockaddr *)addr, SUN_LEN (addr));
+ }
+ else {
+ r = connect (fd, (struct sockaddr *)addr, SUN_LEN (addr));
+ }
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ msg_warn ("bind/connect failed %s: %d, '%s'", addr->sun_path, errno, strerror (errno));
+ goto out;
+ }
+ if (!async) {
+ /* Try to poll */
+ if (poll_sync_socket (fd, CONNECT_TIMEOUT * 1000, POLLOUT) <= 0) {
+ errno = ETIMEDOUT;
+ msg_warn ("bind/connect failed %s: timeout", addr->sun_path);
+ goto out;
+ }
+ else {
+ /* Make synced again */
+ if (make_socket_blocking (fd) < 0) {
+ goto out;
+ }
+ }
+ }
+ }
+ else {
+ /* Still need to check SO_ERROR on socket */
+ optlen = sizeof (s_error);
+ getsockopt (fd, SOL_SOCKET, SO_ERROR, (void *)&s_error, &optlen);
+ if (s_error) {
+ errno = s_error;
+ goto out;
+ }
+ }
+
+
+ return (fd);
+
+ out:
+ serrno = errno;
+ if (fd != -1) {
+ close (fd);
+ }
+ errno = serrno;
+ return (-1);
+}
+
+/**
+ * Make a universal socket
+ * @param credits host, ip or path to unix socket
+ * @param port port (used for network sockets)
+ * @param async make this socket asynced
+ * @param is_server make this socket as server socket
+ * @param try_resolve try name resolution for a socket (BLOCKING)
+ */
+gint
+make_universal_socket (const gchar *credits, guint16 port,
+ gint type, gboolean async, gboolean is_server, gboolean try_resolve)
+{
+ struct sockaddr_un un;
+ struct stat st;
+ struct addrinfo hints, *res;
+ gint r;
+ gchar portbuf[8];
+
+ if (*credits == '/') {
+ if (is_server) {
+ return make_unix_socket (credits, &un, type, is_server, async);
+ }
+ else {
+ r = stat (credits, &st);
+ if (r == -1) {
+ /* Unix socket doesn't exists it must be created first */
+ errno = ENOENT;
+ return -1;
+ }
+ else {
+ if ((st.st_mode & S_IFSOCK) == 0) {
+ /* Path is not valid socket */
+ errno = EINVAL;
+ return -1;
+ }
+ else {
+ return make_unix_socket (credits, &un, type, is_server, async);
+ }
+ }
+ }
+ }
+ else {
+ /* TCP related part */
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = type; /* Type of the socket */
+ hints.ai_flags = is_server ? AI_PASSIVE : 0;
+ hints.ai_protocol = 0; /* Any protocol */
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ if (!try_resolve) {
+ hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
+ }
+
+ rspamd_snprintf (portbuf, sizeof (portbuf), "%d", (int)port);
+ if ((r = getaddrinfo (credits, portbuf, &hints, &res)) == 0) {
+ r = make_inet_socket (type, res, is_server, async, NULL);
+ freeaddrinfo (res);
+ return r;
+ }
+ else {
+ msg_err ("address resolution for %s failed: %s", credits, gai_strerror (r));
+ return FALSE;
+ }
+ }
+}
+
+/**
+ * Make universal stream socket
+ * @param credits host, ip or path to unix socket
+ * @param port port (used for network sockets)
+ * @param async make this socket asynced
+ * @param is_server make this socket as server socket
+ * @param try_resolve try name resolution for a socket (BLOCKING)
+ */
+GList*
+make_universal_sockets_list (const gchar *credits, guint16 port,
+ gint type, gboolean async, gboolean is_server, gboolean try_resolve)
+{
+ struct sockaddr_un un;
+ struct stat st;
+ struct addrinfo hints, *res;
+ gint r, fd, serrno;
+ gchar portbuf[8], **strv, **cur;
+ GList *result = NULL, *rcur;
+
+ strv = g_strsplit_set (credits, ",", -1);
+ if (strv == NULL) {
+ msg_err ("invalid sockets credentials: %s", credits);
+ return NULL;
+ }
+ cur = strv;
+ while (*cur != NULL) {
+ if (*credits == '/') {
+ if (is_server) {
+ fd = make_unix_socket (credits, &un, type, is_server, async);
+ }
+ else {
+ r = stat (credits, &st);
+ if (r == -1) {
+ /* Unix socket doesn't exists it must be created first */
+ errno = ENOENT;
+ goto err;
+ }
+ else {
+ if ((st.st_mode & S_IFSOCK) == 0) {
+ /* Path is not valid socket */
+ errno = EINVAL;
+ goto err;
+ }
+ else {
+ fd = make_unix_socket (credits, &un, type, is_server, async);
+ }
+ }
+ }
+ if (fd != -1) {
+ result = g_list_prepend (result, GINT_TO_POINTER (fd));
+ }
+ else {
+ goto err;
+ }
+ }
+ else {
+ /* TCP related part */
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = type; /* Type of the socket */
+ hints.ai_flags = is_server ? AI_PASSIVE : 0;
+ hints.ai_protocol = 0; /* Any protocol */
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ if (!try_resolve) {
+ hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
+ }
+
+ rspamd_snprintf (portbuf, sizeof (portbuf), "%d", (int)port);
+ if ((r = getaddrinfo (credits, portbuf, &hints, &res)) == 0) {
+ r = make_inet_socket (type, res, is_server, async, &result);
+ freeaddrinfo (res);
+ if (r == -1) {
+ goto err;
+ }
+ }
+ else {
+ msg_err ("address resolution for %s failed: %s", credits, gai_strerror (r));
+ goto err;
+ }
+ }
+ cur ++;
+ }
+
+ g_strfreev (strv);
+ return result;
+
+err:
+ g_strfreev (strv);
+ serrno = errno;
+ rcur = result;
+ while (rcur != NULL) {
+ fd = GPOINTER_TO_INT (rcur->data);
+ if (fd != -1) {
+ close (fd);
+ }
+ rcur = g_list_next (rcur);
+ }
+ if (result != NULL) {
+ g_list_free (result);
+ }
+
+ errno = serrno;
+ return NULL;
+}
+
+gint
+make_socketpair (gint pair[2])
+{
+ gint r;
+
+ r = socketpair (AF_LOCAL, SOCK_STREAM, 0, pair);
+
+ if (r == -1) {
+ msg_warn ("socketpair failed: %d, '%s'", errno, strerror (errno), pair[0], pair[1]);
+ return -1;
+ }
+ /* Set close on exec */
+ if (fcntl (pair[0], F_SETFD, FD_CLOEXEC) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ goto out;
+ }
+ if (fcntl (pair[1], F_SETFD, FD_CLOEXEC) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ goto out;
+ }
+
+ return 0;
+
+out:
+ close (pair[0]);
+ close (pair[1]);
+ return (-1);
+}
+
+gint
+write_pid (struct rspamd_main *main)
+{
+ pid_t pid;
+
+ if (main->cfg->pid_file == NULL) {
+ return -1;
+ }
+ main->pfh = rspamd_pidfile_open (main->cfg->pid_file, 0644, &pid);
+
+ if (main->pfh == NULL) {
+ return -1;
+ }
+
+ if (main->is_privilleged) {
+ /* Force root user as owner of pid file */
+#ifdef HAVE_PIDFILE_FILENO
+ if (fchown (pidfile_fileno (main->pfh), 0, 0) == -1) {
+#else
+ if (fchown (main->pfh->pf_fd, 0, 0) == -1) {
+#endif
+ msg_err ("cannot chown of pidfile %s to 0:0 user", main->cfg->pid_file);
+ }
+ }
+
+ rspamd_pidfile_write (main->pfh);
+
+ return 0;
+}
+
+#ifdef HAVE_SA_SIGINFO
+void
+init_signals (struct sigaction *signals, void (*sig_handler)(gint, siginfo_t *, void *))
+#else
+void
+init_signals (struct sigaction *signals, void (*sig_handler)(gint))
+#endif
+{
+ struct sigaction sigpipe_act;
+ /* 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);
+ sigaddset (&signals->sa_mask, SIGALRM);
+
+
+#ifdef HAVE_SA_SIGINFO
+ signals->sa_flags = SA_SIGINFO;
+ signals->sa_handler = NULL;
+ signals->sa_sigaction = sig_handler;
+#else
+ signals->sa_handler = sig_handler;
+ signals->sa_flags = 0;
+#endif
+ sigaction (SIGTERM, signals, NULL);
+ sigaction (SIGINT, signals, NULL);
+ sigaction (SIGHUP, signals, NULL);
+ sigaction (SIGCHLD, signals, NULL);
+ sigaction (SIGUSR1, signals, NULL);
+ sigaction (SIGUSR2, signals, NULL);
+ sigaction (SIGALRM, signals, NULL);
+
+ /* Ignore SIGPIPE as we handle write errors manually */
+ sigemptyset (&sigpipe_act.sa_mask);
+ sigaddset (&sigpipe_act.sa_mask, SIGPIPE);
+ sigpipe_act.sa_handler = SIG_IGN;
+ sigpipe_act.sa_flags = 0;
+ sigaction (SIGPIPE, &sigpipe_act, NULL);
+}
+
+static void
+pass_signal_cb (gpointer key, gpointer value, gpointer ud)
+{
+ struct rspamd_worker *cur = value;
+ gint signo = GPOINTER_TO_INT (ud);
+
+ kill (cur->pid, signo);
+}
+
+void
+pass_signal_worker (GHashTable * workers, gint signo)
+{
+ g_hash_table_foreach (workers, pass_signal_cb, GINT_TO_POINTER (signo));
+}
+
+void
+convert_to_lowercase (gchar *str, guint size)
+{
+ while (size--) {
+ *str = g_ascii_tolower (*str);
+ str++;
+ }
+}
+
+#ifndef HAVE_SETPROCTITLE
+
+static gchar *title_buffer = 0;
+static size_t title_buffer_size = 0;
+static gchar *title_progname, *title_progname_full;
+
+gint
+setproctitle (const gchar *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.
+*/
+
+gint
+init_title (gint argc, gchar *argv[], gchar *envp[])
+{
+#if defined(DARWIN) || defined(SOLARIS)
+ /* XXX: try to handle these OSes too */
+ return 0;
+#else
+ gchar *begin_of_buffer = 0, *end_of_buffer = 0;
+ gint 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;
+
+ gchar **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;
+
+ gchar *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
+}
+#endif
+
+#ifndef HAVE_PIDFILE
+extern gchar *__progname;
+static gint _rspamd_pidfile_remove (rspamd_pidfh_t *pfh, gint freeit);
+
+static gint
+rspamd_pidfile_verify (rspamd_pidfh_t *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 gint
+rspamd_pidfile_read (const gchar *path, pid_t * pidptr)
+{
+ gchar buf[16], *endptr;
+ gint 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;
+}
+
+rspamd_pidfh_t *
+rspamd_pidfile_open (const gchar *path, mode_t mode, pid_t * pidptr)
+{
+ rspamd_pidfh_t *pfh;
+ struct stat sb;
+ gint 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", g_get_prgname ());
+ else
+ len = snprintf (pfh->pf_path, sizeof (pfh->pf_path), "%s", path);
+ if (len >= (gint)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);
+ lock_file (fd, TRUE);
+ if (fd == -1) {
+ count = 0;
+ rqtp.tv_sec = 0;
+ rqtp.tv_nsec = 5000000;
+ if (errno == EWOULDBLOCK && pidptr != NULL) {
+ again:
+ errno = rspamd_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;
+}
+
+gint
+rspamd_pidfile_write (rspamd_pidfh_t *pfh)
+{
+ gchar pidstr[16];
+ gint error, fd;
+
+ /*
+ * Check remembered descriptor, so we don't overwrite some other
+ * file if pidfile was closed and descriptor reused.
+ */
+ errno = rspamd_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;
+ _rspamd_pidfile_remove (pfh, 0);
+ errno = error;
+ return -1;
+ }
+
+ rspamd_snprintf (pidstr, sizeof (pidstr), "%P", getpid ());
+ if (pwrite (fd, pidstr, strlen (pidstr), 0) != (ssize_t) strlen (pidstr)) {
+ error = errno;
+ _rspamd_pidfile_remove (pfh, 0);
+ errno = error;
+ return -1;
+ }
+
+ return 0;
+}
+
+gint
+rspamd_pidfile_close (rspamd_pidfh_t *pfh)
+{
+ gint error;
+
+ error = rspamd_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 gint
+_rspamd_pidfile_remove (rspamd_pidfh_t *pfh, gint freeit)
+{
+ gint error;
+
+ error = rspamd_pidfile_verify (pfh);
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+
+ if (unlink (pfh->pf_path) == -1)
+ error = errno;
+ if (!unlock_file (pfh->pf_fd, FALSE)) {
+ 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;
+}
+
+gint
+rspamd_pidfile_remove (rspamd_pidfh_t *pfh)
+{
+
+ return (_rspamd_pidfile_remove (pfh, 1));
+}
+#endif
+
+/* Replace %r with rcpt value and %f with from value, new string is allocated in pool */
+gchar *
+resolve_stat_filename (rspamd_mempool_t * pool, gchar *pattern, gchar *rcpt, gchar *from)
+{
+ gint need_to_format = 0, len = 0;
+ gint rcptlen, fromlen;
+ gchar *c = pattern, *new, *s;
+
+ if (rcpt) {
+ rcptlen = strlen (rcpt);
+ }
+ else {
+ rcptlen = 0;
+ }
+
+ if (from) {
+ fromlen = strlen (from);
+ }
+ else {
+ fromlen = 0;
+ }
+
+ /* Calculate length */
+ while (*c++) {
+ if (*c == '%' && *(c + 1) == 'r') {
+ len += rcptlen;
+ c += 2;
+ need_to_format = 1;
+ continue;
+ }
+ else if (*c == '%' && *(c + 1) == 'f') {
+ len += fromlen;
+ c += 2;
+ need_to_format = 1;
+ continue;
+ }
+ len++;
+ }
+
+ /* Do not allocate extra memory if we do not need to format string */
+ if (!need_to_format) {
+ return pattern;
+ }
+
+ /* Allocate new string */
+ new = rspamd_mempool_alloc (pool, len);
+ c = pattern;
+ s = new;
+
+ /* Format string */
+ while (*c++) {
+ if (*c == '%' && *(c + 1) == 'r') {
+ c += 2;
+ memcpy (s, rcpt, rcptlen);
+ s += rcptlen;
+ continue;
+ }
+ else if (*c == '%' && *(c + 1) == 'r') {
+ c += 2;
+ memcpy (s, from, fromlen);
+ s += fromlen;
+ continue;
+ }
+ *s++ = *c;
+ }
+
+ *s = '\0';
+
+ return new;
+}
+
+#ifdef HAVE_CLOCK_GETTIME
+const gchar *
+calculate_check_time (struct timeval *tv, struct timespec *begin, gint resolution, guint32 *scan_time)
+#else
+const gchar *
+calculate_check_time (struct timeval *begin, gint resolution, guint32 *scan_time)
+#endif
+{
+ double vdiff, diff;
+ static gchar res[64];
+ static gchar fmt[sizeof ("%.10f ms real, %.10f ms virtual")];
+ struct timeval tv_now;
+
+ if (gettimeofday (&tv_now, NULL) == -1) {
+ msg_warn ("gettimeofday failed: %s", strerror (errno));
+ }
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+
+ diff = (tv_now.tv_sec - tv->tv_sec) * 1000. + /* Seconds */
+ (tv_now.tv_usec - tv->tv_usec) / 1000.; /* Microseconds */
+#ifdef HAVE_CLOCK_PROCESS_CPUTIME_ID
+ clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &ts);
+#elif defined(HAVE_CLOCK_VIRTUAL)
+ clock_gettime (CLOCK_VIRTUAL, &ts);
+#else
+ clock_gettime (CLOCK_REALTIME, &ts);
+#endif
+
+ vdiff = (ts.tv_sec - begin->tv_sec) * 1000. + /* Seconds */
+ (ts.tv_nsec - begin->tv_nsec) / 1000000.; /* Nanoseconds */
+#else
+ diff = (tv_now.tv_sec - begin->tv_sec) * 1000. + /* Seconds */
+ (tv_now.tv_usec - begin->tv_usec) / 1000.; /* Microseconds */
+
+ vdiff = diff;
+#endif
+
+ *scan_time = diff;
+
+ sprintf (fmt, "%%.%dfms real, %%.%dfms virtual", resolution, resolution);
+ snprintf (res, sizeof (res), fmt, diff, vdiff);
+
+ return (const gchar *)res;
+}
+
+#ifndef g_tolower
+# define g_tolower(x) (((x) >= 'A' && (x) <= 'Z') ? (x) - 'A' + 'a' : (x))
+#endif
+
+
+gboolean
+rspamd_strcase_equal (gconstpointer v, gconstpointer v2)
+{
+ if (g_ascii_strcasecmp ((const gchar *)v, (const gchar *)v2) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+guint
+rspamd_strcase_hash (gconstpointer key)
+{
+ const gchar *p = key;
+ gchar buf[256];
+ guint h = 0, i = 0;
+
+
+ while (*p != '\0') {
+ buf[i] = g_ascii_tolower (*p);
+ i++;
+ p++;
+ if (i == sizeof (buf)) {
+ h ^= murmur32_hash (buf, i);
+ i = 0;
+ }
+ }
+
+ if (i > 0) {
+ h ^= murmur32_hash (buf, i);
+ }
+
+ return h;
+}
+
+guint
+rspamd_str_hash (gconstpointer key)
+{
+ gsize len;
+
+ len = strlen ((const gchar *)key);
+
+ return murmur32_hash (key, len);
+}
+
+gboolean
+rspamd_str_equal (gconstpointer v, gconstpointer v2)
+{
+ return strcmp ((const gchar *)v, (const gchar *)v2) == 0;
+}
+
+gboolean
+fstr_strcase_equal (gconstpointer v, gconstpointer v2)
+{
+ const f_str_t *f1 = v, *f2 = v2;
+ if (f1->len == f2->len && g_ascii_strncasecmp (f1->begin, f2->begin, f1->len) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+guint
+fstr_strcase_hash (gconstpointer key)
+{
+ const f_str_t *f = key;
+ const gchar *p;
+ guint h = 0, i = 0;
+ gchar buf[256];
+
+ p = f->begin;
+ while (p - f->begin < (gint)f->len) {
+ buf[i] = g_ascii_tolower (*p);
+ i++;
+ p++;
+ if (i == sizeof (buf)) {
+ h ^= murmur32_hash (buf, i);
+ i = 0;
+ }
+ }
+
+ if (i > 0) {
+ h ^= murmur32_hash (buf, i);
+ }
+
+ return h;
+}
+
+void
+gperf_profiler_init (struct config_file *cfg, const gchar *descr)
+{
+#if defined(WITH_GPERF_TOOLS)
+ gchar prof_path[PATH_MAX];
+
+ if (getenv ("CPUPROFILE")) {
+
+ /* disable inherited Profiler enabled in master process */
+ ProfilerStop ();
+ }
+ /* Try to create temp directory for gmon.out and chdir to it */
+ if (cfg->profile_path == NULL) {
+ cfg->profile_path = g_strdup_printf ("%s/rspamd-profile", cfg->temp_dir);
+ }
+
+ snprintf (prof_path, sizeof (prof_path), "%s-%s.%d", cfg->profile_path, descr, (gint)getpid ());
+ if (ProfilerStart (prof_path)) {
+ /* start ITIMER_PROF timer */
+ ProfilerRegisterThread ();
+ }
+ else {
+ msg_warn ("cannot start google perftools profiler");
+ }
+
+#endif
+}
+
+#ifdef HAVE_FLOCK
+/* Flock version */
+gboolean
+lock_file (gint fd, gboolean async)
+{
+ gint flags;
+
+ if (async) {
+ flags = LOCK_EX | LOCK_NB;
+ }
+ else {
+ flags = LOCK_EX;
+ }
+
+ if (flock (fd, flags) == -1) {
+ if (async && errno == EAGAIN) {
+ return FALSE;
+ }
+ msg_warn ("lock on file failed: %s", strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+unlock_file (gint fd, gboolean async)
+{
+ gint flags;
+
+ if (async) {
+ flags = LOCK_UN | LOCK_NB;
+ }
+ else {
+ flags = LOCK_UN;
+ }
+
+ if (flock (fd, flags) == -1) {
+ if (async && errno == EAGAIN) {
+ return FALSE;
+ }
+ msg_warn ("lock on file failed: %s", strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+#else /* HAVE_FLOCK */
+/* Fctnl version */
+gboolean
+lock_file (gint fd, gboolean async)
+{
+ struct flock fl = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0
+ };
+
+ if (fcntl (fd, async ? F_SETLK : F_SETLKW, &fl) == -1) {
+ if (async && (errno == EAGAIN || errno == EACCES)) {
+ return FALSE;
+ }
+ msg_warn ("lock on file failed: %s", strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+unlock_file (gint fd, gboolean async)
+{
+ struct flock fl = {
+ .l_type = F_UNLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0
+ };
+
+ if (fcntl (fd, async ? F_SETLK : F_SETLKW, &fl) == -1) {
+ if (async && (errno == EAGAIN || errno == EACCES)) {
+ return FALSE;
+ }
+ msg_warn ("lock on file failed: %s", strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+#endif /* HAVE_FLOCK */
+
+
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 22))
+void
+g_ptr_array_unref (GPtrArray *array)
+{
+ g_ptr_array_free (array, TRUE);
+}
+#endif
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 14))
+void
+g_queue_clear (GQueue *queue)
+{
+ g_return_if_fail (queue != NULL);
+
+ g_list_free (queue->head);
+ queue->head = queue->tail = NULL;
+ queue->length = 0;
+}
+#endif
+
+gsize
+rspamd_strlcpy (gchar *dst, const gchar *src, gsize siz)
+{
+ gchar *d = dst;
+ const gchar *s = src;
+ gsize n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0') {
+ break;
+ }
+ }
+ }
+
+ if (n == 0 && siz != 0) {
+ *d = '\0';
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+
+gsize
+rspamd_strlcpy_tolower (gchar *dst, const gchar *src, gsize siz)
+{
+ gchar *d = dst;
+ const gchar *s = src;
+ gsize n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = g_ascii_tolower (*s++)) == '\0') {
+ break;
+ }
+ }
+ }
+
+ if (n == 0 && siz != 0) {
+ *d = '\0';
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+
+/* Compare two emails for building emails tree */
+gint
+compare_email_func (gconstpointer a, gconstpointer b)
+{
+ const struct uri *u1 = a, *u2 = b;
+ gint r;
+
+ if (u1->hostlen != u2->hostlen || u1->hostlen == 0) {
+ return u1->hostlen - u2->hostlen;
+ }
+ else {
+ if ((r = g_ascii_strncasecmp (u1->host, u2->host, u1->hostlen)) == 0){
+ if (u1->userlen != u2->userlen || u1->userlen == 0) {
+ return u1->userlen - u2->userlen;
+ }
+ else {
+ return g_ascii_strncasecmp (u1->user, u2->user, u1->userlen);
+ }
+ }
+ else {
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+gint
+compare_url_func (gconstpointer a, gconstpointer b)
+{
+ const struct uri *u1 = a, *u2 = b;
+ int r;
+
+ if (u1->hostlen != u2->hostlen || u1->hostlen == 0) {
+ return u1->hostlen - u2->hostlen;
+ }
+ else {
+ r = g_ascii_strncasecmp (u1->host, u2->host, u1->hostlen);
+ if (r == 0 && u1->is_phished != u2->is_phished) {
+ /* Always insert phished urls to the tree */
+ return -1;
+ }
+ }
+
+ return r;
+}
+
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
+gchar *
+rspamd_strncasestr (const gchar *s, const gchar *find, gint len)
+{
+ gchar c, sc;
+ gsize mlen;
+
+ if ((c = *find++) != 0) {
+ c = g_ascii_tolower (c);
+ mlen = strlen (find);
+ do {
+ do {
+ if ((sc = *s++) == 0 || len -- == 0)
+ return (NULL);
+ } while (g_ascii_tolower (sc) != c);
+ } while (g_ascii_strncasecmp (s, find, mlen) != 0);
+ s--;
+ }
+ return ((gchar *)s);
+}
+
+/*
+ * Try to convert string of length to long
+ */
+gboolean
+rspamd_strtol (const gchar *s, gsize len, glong *value)
+{
+ const gchar *p = s, *end = s + len;
+ gchar c;
+ glong v = 0;
+ const glong cutoff = G_MAXLONG / 10, cutlim = G_MAXLONG % 10;
+ gboolean neg;
+
+ /* Case negative values */
+ if (*p == '-') {
+ neg = TRUE;
+ p ++;
+ }
+ else {
+ neg = FALSE;
+ }
+ /* Some preparations for range errors */
+
+ while (p < end) {
+ c = *p;
+ if (c >= '0' && c <= '9') {
+ c -= '0';
+ if (v > cutoff || (v == cutoff && c > cutlim)) {
+ /* Range error */
+ *value = neg ? G_MINLONG : G_MAXLONG;
+ return FALSE;
+ }
+ else {
+ v *= 10;
+ v += c;
+ }
+ }
+ else {
+ return FALSE;
+ }
+ p ++;
+ }
+
+ *value = neg ? -(v) : v;
+ return TRUE;
+}
+
+/*
+ * Try to convert string of length to long
+ */
+gboolean
+rspamd_strtoul (const gchar *s, gsize len, gulong *value)
+{
+ const gchar *p = s, *end = s + len;
+ gchar c;
+ gulong v = 0;
+ const gulong cutoff = G_MAXULONG / 10, cutlim = G_MAXULONG % 10;
+
+ /* Some preparations for range errors */
+ while (p < end) {
+ c = *p;
+ if (c >= '0' && c <= '9') {
+ c -= '0';
+ if (v > cutoff || (v == cutoff && (guint8)c > cutlim)) {
+ /* Range error */
+ *value = G_MAXULONG;
+ return FALSE;
+ }
+ else {
+ v *= 10;
+ v += c;
+ }
+ }
+ else {
+ return FALSE;
+ }
+ p ++;
+ }
+
+ *value = v;
+ return TRUE;
+}
+
+gint
+rspamd_fallocate (gint fd, off_t offset, off_t len)
+{
+#if defined(HAVE_FALLOCATE)
+ return fallocate (fd, 0, offset, len);
+#elif defined(HAVE_POSIX_FALLOCATE)
+ return posix_fallocate (fd, offset, len);
+#else
+ /* Return 0 as nothing can be done on this system */
+ return 0;
+#endif
+}
+
+
+/**
+ * Create new mutex
+ * @return mutex or NULL
+ */
+inline rspamd_mutex_t*
+rspamd_mutex_new (void)
+{
+ rspamd_mutex_t *new;
+
+ new = g_slice_alloc (sizeof (rspamd_mutex_t));
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_init (&new->mtx);
+#else
+ g_static_mutex_init (&new->mtx);
+#endif
+
+ return new;
+}
+
+/**
+ * Lock mutex
+ * @param mtx
+ */
+inline void
+rspamd_mutex_lock (rspamd_mutex_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_lock (&mtx->mtx);
+#else
+ g_static_mutex_lock (&mtx->mtx);
+#endif
+}
+
+/**
+ * Unlock mutex
+ * @param mtx
+ */
+inline void
+rspamd_mutex_unlock (rspamd_mutex_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_unlock (&mtx->mtx);
+#else
+ g_static_mutex_unlock (&mtx->mtx);
+#endif
+}
+
+void
+rspamd_mutex_free (rspamd_mutex_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_clear (&mtx->mtx);
+#endif
+ g_slice_free1 (sizeof (rspamd_mutex_t), mtx);
+}
+
+/**
+ * Create new rwlock
+ * @return
+ */
+rspamd_rwlock_t*
+rspamd_rwlock_new (void)
+{
+ rspamd_rwlock_t *new;
+
+ new = g_malloc (sizeof (rspamd_rwlock_t));
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_rw_lock_init (&new->rwlock);
+#else
+ g_static_rw_lock_init (&new->rwlock);
+#endif
+
+ return new;
+}
+
+/**
+ * Lock rwlock for writing
+ * @param mtx
+ */
+inline void
+rspamd_rwlock_writer_lock (rspamd_rwlock_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_rw_lock_writer_lock (&mtx->rwlock);
+#else
+ g_static_rw_lock_writer_lock (&mtx->rwlock);
+#endif
+}
+
+/**
+ * Lock rwlock for reading
+ * @param mtx
+ */
+inline void
+rspamd_rwlock_reader_lock (rspamd_rwlock_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_rw_lock_reader_lock (&mtx->rwlock);
+#else
+ g_static_rw_lock_reader_lock (&mtx->rwlock);
+#endif
+}
+
+/**
+ * Unlock rwlock from writing
+ * @param mtx
+ */
+inline void
+rspamd_rwlock_writer_unlock (rspamd_rwlock_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_rw_lock_writer_unlock (&mtx->rwlock);
+#else
+ g_static_rw_lock_writer_unlock (&mtx->rwlock);
+#endif
+}
+
+/**
+ * Unlock rwlock from reading
+ * @param mtx
+ */
+inline void
+rspamd_rwlock_reader_unlock (rspamd_rwlock_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_rw_lock_reader_unlock (&mtx->rwlock);
+#else
+ g_static_rw_lock_reader_unlock (&mtx->rwlock);
+#endif
+}
+
+void
+rspamd_rwlock_free (rspamd_rwlock_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_rw_lock_clear (&mtx->rwlock);
+#endif
+ g_slice_free1 (sizeof (rspamd_rwlock_t), mtx);
+}
+
+struct rspamd_thread_data {
+ gchar *name;
+ gint id;
+ GThreadFunc func;
+ gpointer data;
+};
+
+static gpointer
+rspamd_thread_func (gpointer ud)
+{
+ struct rspamd_thread_data *td = ud;
+ sigset_t s_mask;
+
+ /* Ignore signals in thread */
+ sigemptyset (&s_mask);
+ sigaddset (&s_mask, SIGTERM);
+ sigaddset (&s_mask, SIGINT);
+ sigaddset (&s_mask, SIGHUP);
+ sigaddset (&s_mask, SIGCHLD);
+ sigaddset (&s_mask, SIGUSR1);
+ sigaddset (&s_mask, SIGUSR2);
+ sigaddset (&s_mask, SIGALRM);
+ sigaddset (&s_mask, SIGPIPE);
+
+ sigprocmask (SIG_BLOCK, &s_mask, NULL);
+
+ ud = td->func (td->data);
+ g_free (td->name);
+ g_free (td);
+
+ return ud;
+}
+
+/**
+ * Create new named thread
+ * @param name name pattern
+ * @param func function to start
+ * @param data data to pass to function
+ * @param err error pointer
+ * @return new thread object that can be joined
+ */
+GThread*
+rspamd_create_thread (const gchar *name, GThreadFunc func, gpointer data, GError **err)
+{
+ GThread *new;
+ struct rspamd_thread_data *td;
+ static gint32 id;
+ guint r;
+
+ r = strlen (name);
+ td = g_malloc (sizeof (struct rspamd_thread_data));
+ td->id = ++id;
+ td->name = g_malloc (r + sizeof ("4294967296"));
+ td->func = func;
+ td->data = data;
+
+ rspamd_snprintf (td->name, r + sizeof ("4294967296"), "%s-%d", name, id);
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ new = g_thread_try_new (td->name, rspamd_thread_func, td, err);
+#else
+ new = g_thread_create (rspamd_thread_func, td, TRUE, err);
+#endif
+
+ return new;
+}
+
+guint32
+murmur32_hash (const guint8 *in, gsize len)
+{
+
+
+ const guint32 c1 = 0xcc9e2d51;
+ const guint32 c2 = 0x1b873593;
+
+ const int nblocks = len / 4;
+ const guint32 *blocks = (const guint32 *)(in);
+ const guint8 *tail;
+ guint32 h = 0;
+ gint i;
+ guint32 k;
+
+ if (in == NULL || len == 0) {
+ return 0;
+ }
+
+ tail = (const guint8 *)(in + (nblocks * 4));
+
+ for (i = 0; i < nblocks; i++) {
+ k = blocks[i];
+
+ k *= c1;
+ k = (k << 15) | (k >> (32 - 15));
+ k *= c2;
+
+ h ^= k;
+ h = (h << 13) | (h >> (32 - 13));
+ h = (h * 5) + 0xe6546b64;
+ }
+
+ k = 0;
+ switch (len & 3) {
+ case 3:
+ k ^= tail[2] << 16;
+ case 2:
+ k ^= tail[1] << 8;
+ case 1:
+ k ^= tail[0];
+ k *= c1;
+ k = (k << 13) | (k >> (32 - 15));
+ k *= c2;
+ h ^= k;
+ };
+
+ h ^= len;
+
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ return h;
+}
+
+void
+murmur128_hash (const guint8 *in, gsize len, guint64 out[])
+{
+ const guint64 c1 = 0x87c37b91114253d5ULL;
+ const guint64 c2 = 0x4cf5ad432745937fULL;
+ const gint nblocks = len / 16;
+ const guint64 *blocks = (const guint64 *)(in);
+ const guint8 *tail;
+ guint64 h1 = 0;
+ guint64 h2 = 0;
+ int i;
+ guint64 k1, k2;
+
+ if (in == NULL || len == 0 || out == NULL) {
+ return;
+ }
+
+ tail = (const guint8 *)(in + (nblocks * 16));
+
+ for (i = 0; i < nblocks; i++) {
+ k1 = blocks[i*2+0];
+ k2 = blocks[i*2+1];
+
+ k1 *= c1;
+ k1 = (k1 << 31) | (k1 >> (64 - 31));
+ k1 *= c2;
+ h1 ^= k1;
+
+ h1 = (h1 << 27) | (h1 >> (64 - 27));
+ h1 += h2;
+ h1 = h1*5+0x52dce729;
+
+ k2 *= c2;
+ k2 = (k2 << 33) | (k2 >> (64 - 33));
+ k2 *= c1;
+ h2 ^= k2;
+
+ h2 = (h2 << 31) | (h2 >> (64 - 31));
+ h2 += h1;
+ h2 = h2*5+0x38495ab5;
+ }
+
+ k1 = k2 = 0;
+ switch (len & 15) {
+ case 15:
+ k2 ^= (guint64)(tail[14]) << 48;
+ case 14:
+ k2 ^= (guint64)(tail[13]) << 40;
+ case 13:
+ k2 ^= (guint64)(tail[12]) << 32;
+ case 12:
+ k2 ^= (guint64)(tail[11]) << 24;
+ case 11:
+ k2 ^= (guint64)(tail[10]) << 16;
+ case 10:
+ k2 ^= (guint64)(tail[ 9]) << 8;
+ case 9:
+ k2 ^= (guint64)(tail[ 8]) << 0;
+ k2 *= c2;
+ k2 = (k2 << 33) | (k2 >> (64 - 33));
+ k2 *= c1;
+ h2 ^= k2;
+
+ case 8:
+ k1 ^= (guint64)(tail[ 7]) << 56;
+ case 7:
+ k1 ^= (guint64)(tail[ 6]) << 48;
+ case 6:
+ k1 ^= (guint64)(tail[ 5]) << 40;
+ case 5:
+ k1 ^= (guint64)(tail[ 4]) << 32;
+ case 4:
+ k1 ^= (guint64)(tail[ 3]) << 24;
+ case 3:
+ k1 ^= (guint64)(tail[ 2]) << 16;
+ case 2:
+ k1 ^= (guint64)(tail[ 1]) << 8;
+ case 1:
+ k1 ^= (guint64)(tail[ 0]) << 0;
+ k1 *= c1;
+ k1 = (k1 << 31) | (k1 >> (64 - 31));
+ k1 *= c2;
+ h1 ^= k1;
+ };
+
+ //----------
+ // finalization
+
+ h1 ^= len;
+ h2 ^= len;
+
+ h1 += h2;
+ h2 += h1;
+
+ h1 ^= h1 >> 33;
+ h1 *= 0xff51afd7ed558ccdULL;
+ h1 ^= h1 >> 33;
+ h1 *= 0xc4ceb9fe1a85ec53ULL;
+ h1 ^= h1 >> 33;
+
+ h2 ^= h2 >> 33;
+ h2 *= 0xff51afd7ed558ccdULL;
+ h2 ^= h2 >> 33;
+ h2 *= 0xc4ceb9fe1a85ec53ULL;
+ h2 ^= h2 >> 33;
+
+ h1 += h2;
+ h2 += h1;
+
+ out[0] = h1;
+ out[1] = h2;
+}
+
+struct hash_copy_callback_data {
+ gpointer (*key_copy_func)(gconstpointer data, gpointer ud);
+ gpointer (*value_copy_func)(gconstpointer data, gpointer ud);
+ gpointer ud;
+ GHashTable *dst;
+};
+
+static void
+copy_foreach_callback (gpointer key, gpointer value, gpointer ud)
+{
+ struct hash_copy_callback_data *cb = ud;
+ gpointer nkey, nvalue;
+
+ nkey = cb->key_copy_func ? cb->key_copy_func (key, cb->ud) : (gpointer)key;
+ nvalue = cb->value_copy_func ? cb->value_copy_func (value, cb->ud) : (gpointer)value;
+ g_hash_table_insert (cb->dst, nkey, nvalue);
+}
+/**
+ * Deep copy of one hash table to another
+ * @param src source hash
+ * @param dst destination hash
+ * @param key_copy_func function called to copy or modify keys (or NULL)
+ * @param value_copy_func function called to copy or modify values (or NULL)
+ * @param ud user data for copy functions
+ */
+void rspamd_hash_table_copy (GHashTable *src, GHashTable *dst,
+ gpointer (*key_copy_func)(gconstpointer data, gpointer ud),
+ gpointer (*value_copy_func)(gconstpointer data, gpointer ud),
+ gpointer ud)
+{
+ struct hash_copy_callback_data cb;
+ if (src != NULL && dst != NULL) {
+ cb.key_copy_func = key_copy_func;
+ cb.value_copy_func = value_copy_func;
+ cb.ud = ud;
+ cb.dst = dst;
+ g_hash_table_foreach (src, copy_foreach_callback, &cb);
+ }
+}
+
+/**
+ * Utility function to provide mem_pool copy for rspamd_hash_table_copy function
+ * @param data string to copy
+ * @param ud memory pool to use
+ * @return
+ */
+gpointer
+rspamd_str_pool_copy (gconstpointer data, gpointer ud)
+{
+ rspamd_mempool_t *pool = ud;
+
+ return data ? rspamd_mempool_strdup (pool, data) : NULL;
+}
+
+gboolean
+parse_ipmask_v4 (const char *line, struct in_addr *ina, int *mask)
+{
+ const char *pos;
+ char ip_buf[INET_ADDRSTRLEN + 1], mask_buf[3] = { '\0', '\0', '\0' };
+
+ bzero (ip_buf, sizeof (ip_buf));
+
+ if ((pos = strchr (line, '/')) != NULL) {
+ rspamd_strlcpy (ip_buf, line, MIN ((gsize)(pos - line), sizeof (ip_buf)));
+ rspamd_strlcpy (mask_buf, pos + 1, sizeof (mask_buf));
+ }
+ else {
+ rspamd_strlcpy (ip_buf, line, sizeof (ip_buf));
+ }
+
+ if (!inet_aton (ip_buf, ina)) {
+ return FALSE;
+ }
+
+ if (mask_buf[0] != '\0') {
+ /* Also parse mask */
+ *mask = (mask_buf[0] - '0') * 10 + mask_buf[1] - '0';
+ if (*mask > 32) {
+ return FALSE;
+ }
+ }
+ else {
+ *mask = 32;
+ }
+
+ *mask = G_MAXUINT32 << (32 - *mask);
+
+ return TRUE;
+}
+
+static volatile sig_atomic_t saved_signo[NSIG];
+
+static
+void read_pass_tmp_sig_handler (int s)
+{
+
+ saved_signo[s] = 1;
+}
+
+#ifndef _PATH_TTY
+# define _PATH_TTY "/dev/tty"
+#endif
+
+gint
+rspamd_read_passphrase (gchar *buf, gint size, gint rwflag, gpointer key)
+{
+#ifdef HAVE_PASSPHRASE_H
+ gint len = 0;
+ gchar pass[BUFSIZ];
+
+ if (readpassphrase ("Enter passphrase: ", buf, size, RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) {
+ return 0;
+ }
+
+ return strlen (buf);
+#else
+ struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm;
+ struct sigaction savetstp, savettin, savettou, savepipe;
+ struct termios term, oterm;
+ gint input, output, i;
+ gchar *end, *p, ch;
+
+restart:
+ if ((input = output = open (_PATH_TTY, O_RDWR)) == -1) {
+ errno = ENOTTY;
+ return 0;
+ }
+ if (fcntl (input, F_SETFD, FD_CLOEXEC) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ }
+
+ /* Turn echo off */
+ if (tcgetattr (input, &oterm) != 0) {
+ errno = ENOTTY;
+ return 0;
+ }
+ memcpy(&term, &oterm, sizeof(term));
+ term.c_lflag &= ~(ECHO | ECHONL);
+ (void)tcsetattr(input, TCSAFLUSH, &term);
+ (void)write (output, "Enter passphrase: ", sizeof ("Enter passphrase: ") - 1);
+
+ /* Save the current sighandler */
+ for (i = 0; i < NSIG; i++) {
+ saved_signo[i] = 0;
+ }
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = read_pass_tmp_sig_handler;
+ (void)sigaction (SIGALRM, &sa, &savealrm);
+ (void)sigaction (SIGHUP, &sa, &savehup);
+ (void)sigaction (SIGINT, &sa, &saveint);
+ (void)sigaction (SIGPIPE, &sa, &savepipe);
+ (void)sigaction (SIGQUIT, &sa, &savequit);
+ (void)sigaction (SIGTERM, &sa, &saveterm);
+ (void)sigaction (SIGTSTP, &sa, &savetstp);
+ (void)sigaction (SIGTTIN, &sa, &savettin);
+ (void)sigaction (SIGTTOU, &sa, &savettou);
+
+ /* Now read a passphrase */
+ p = buf;
+ end = p + size - 1;
+ while (read (input, &ch, 1) == 1 && ch != '\n' && ch != '\r') {
+ if (p < end) {
+ *p++ = ch;
+ }
+ }
+ *p = '\0';
+ (void)write (output, "\n", 1);
+
+ /* Restore terminal state */
+ if (memcmp (&term, &oterm, sizeof (term)) != 0) {
+ while (tcsetattr (input, TCSAFLUSH, &oterm) == -1 &&
+ errno == EINTR && !saved_signo[SIGTTOU]);
+ }
+
+ /* Restore signal handlers */
+ (void)sigaction (SIGALRM, &savealrm, NULL);
+ (void)sigaction (SIGHUP, &savehup, NULL);
+ (void)sigaction (SIGINT, &saveint, NULL);
+ (void)sigaction (SIGQUIT, &savequit, NULL);
+ (void)sigaction (SIGPIPE, &savepipe, NULL);
+ (void)sigaction (SIGTERM, &saveterm, NULL);
+ (void)sigaction (SIGTSTP, &savetstp, NULL);
+ (void)sigaction (SIGTTIN, &savettin, NULL);
+ (void)sigaction (SIGTTOU, &savettou, NULL);
+
+ close (input);
+
+ /* Send signals pending */
+ for (i = 0; i < NSIG; i++) {
+ if (saved_signo[i]) {
+ kill(getpid(), i);
+ switch (i) {
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ goto restart;
+ }
+ }
+ }
+
+ return p - buf;
+#endif
+}
+
+gboolean
+rspamd_ip_is_valid (rspamd_inet_addr_t *addr)
+{
+ const struct in_addr ip4_any = { INADDR_ANY }, ip4_none = { INADDR_NONE };
+ const struct in6_addr ip6_any = IN6ADDR_ANY_INIT;
+
+ gboolean ret = FALSE;
+
+ if (G_LIKELY (addr->af == AF_INET)) {
+ if (memcmp (&addr->addr.s4.sin_addr, &ip4_any, sizeof (struct in_addr)) != 0 &&
+ memcmp (&addr->addr.s4.sin_addr, &ip4_none,
+ sizeof (struct in_addr)) != 0) {
+ ret = TRUE;
+ }
+ }
+ else if (G_UNLIKELY (addr->af == AF_INET6)) {
+ if (memcmp (&addr->addr.s6.sin6_addr, &ip6_any,
+ sizeof (struct in6_addr)) != 0) {
+ ret = TRUE;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * GString ucl emitting functions
+ */
+static int
+rspamd_gstring_append_character (unsigned char c, size_t len, void *ud)
+{
+ GString *buf = ud;
+ gsize old_len;
+
+ if (len == 1) {
+ g_string_append_c (buf, c);
+ }
+ else {
+ if (buf->allocated_len - buf->len <= len) {
+ old_len = buf->len;
+ g_string_set_size (buf, buf->len + len + 1);
+ buf->len = old_len;
+ }
+ memset (&buf->str[buf->len], c, len);
+ buf->len += len;
+ }
+
+ return 0;
+}
+
+static int
+rspamd_gstring_append_len (const unsigned char *str, size_t len, void *ud)
+{
+ GString *buf = ud;
+
+ g_string_append_len (buf, str, len);
+
+ return 0;
+}
+
+static int
+rspamd_gstring_append_int (int64_t val, void *ud)
+{
+ GString *buf = ud;
+
+ rspamd_printf_gstring (buf, "%L", (intmax_t)val);
+ return 0;
+}
+
+static int
+rspamd_gstring_append_double (double val, void *ud)
+{
+ GString *buf = ud;
+ const double delta = 0.0000001;
+
+ if (val == (double)(int)val) {
+ rspamd_printf_gstring (buf, "%.1f", val);
+ }
+ else if (fabs (val - (double)(int)val) < delta) {
+ /* Write at maximum precision */
+ rspamd_printf_gstring (buf, "%.*g", DBL_DIG, val);
+ }
+ else {
+ rspamd_printf_gstring (buf, "%f", val);
+ }
+
+ return 0;
+}
+
+void
+rspamd_ucl_emit_gstring (ucl_object_t *obj, enum ucl_emitter emit_type, GString *target)
+{
+ struct ucl_emitter_functions func = {
+ .ucl_emitter_append_character = rspamd_gstring_append_character,
+ .ucl_emitter_append_len = rspamd_gstring_append_len,
+ .ucl_emitter_append_int = rspamd_gstring_append_int,
+ .ucl_emitter_append_double = rspamd_gstring_append_double
+ };
+
+ func.ud = target;
+ ucl_object_emit_full (obj, emit_type, &func);
+}
+
+gint
+rspamd_accept_from_socket (gint sock, rspamd_inet_addr_t *addr)
+{
+ gint nfd, serrno;
+ socklen_t len = sizeof (addr->addr.ss);
+
+ if ((nfd = accept (sock, &addr->addr.sa, &len)) == -1) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
+ return 0;
+ }
+ return -1;
+ }
+
+ addr->slen = len;
+ addr->af = addr->addr.sa.sa_family;
+
+ if (make_socket_nonblocking (nfd) < 0) {
+ goto out;
+ }
+
+ /* Set close on exec */
+ if (fcntl (nfd, F_SETFD, FD_CLOEXEC) == -1) {
+ msg_warn ("fcntl failed: %d, '%s'", errno, strerror (errno));
+ goto out;
+ }
+
+ return (nfd);
+
+ out:
+ serrno = errno;
+ close (nfd);
+ errno = serrno;
+ return (-1);
+
+}
+
+gboolean
+rspamd_parse_inet_address (rspamd_inet_addr_t *target, const char *src)
+{
+ gboolean ret = FALSE;
+
+ if (inet_pton (AF_INET6, src, &target->addr.s6.sin6_addr) == 1) {
+ target->af = AF_INET6;
+ target->slen = sizeof (target->addr.s6);
+ ret = TRUE;
+ }
+ else if (inet_pton (AF_INET, src, &target->addr.s4.sin_addr) == 1) {
+ target->af = AF_INET;
+ target->slen = sizeof (target->addr.s4);
+ ret = TRUE;
+ }
+
+ target->addr.sa.sa_family = target->af;
+
+ return ret;
+}
+
+const char*
+rspamd_inet_address_to_string (rspamd_inet_addr_t *addr)
+{
+ static char addr_str[INET6_ADDRSTRLEN + 1];
+
+ switch (addr->af) {
+ case AF_INET:
+ return inet_ntop (addr->af, &addr->addr.s4.sin_addr, addr_str,
+ sizeof (addr_str));
+ case AF_INET6:
+ return inet_ntop (addr->af, &addr->addr.s6.sin6_addr, addr_str,
+ sizeof (addr_str));
+ case AF_UNIX:
+ return addr->addr.su.sun_path;
+ }
+
+ return "undefined";
+}
+
+uint16_t
+rspamd_inet_address_get_port (rspamd_inet_addr_t *addr)
+{
+ switch (addr->af) {
+ case AF_INET:
+ return ntohs (addr->addr.s4.sin_port);
+ case AF_INET6:
+ return ntohs (addr->addr.s6.sin6_port);
+ }
+
+ return 0;
+}