]> source.dussan.org Git - rspamd.git/commitdiff
* Initial release of librspamdclient
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Fri, 28 Jan 2011 18:30:34 +0000 (21:30 +0300)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Fri, 28 Jan 2011 18:30:34 +0000 (21:30 +0300)
CMakeLists.txt
lib/CMakeLists.txt [new file with mode: 0644]
lib/librspamdclient.c [new file with mode: 0644]
lib/librspamdclient.h [new file with mode: 0644]

index 17dd4d738edb652c22ff4a0575ef43cf0a82d694..6ee3cf56a487a23c96253b7834b56df6ee1e881e 100644 (file)
@@ -583,6 +583,7 @@ IF(ENABLE_PERL MATCHES "ON")
        LIST(APPEND RSPAMDSRC src/perl.c)
 ENDIF(ENABLE_PERL MATCHES "ON")
 ADD_SUBDIRECTORY(src/lua)
+ADD_SUBDIRECTORY(lib)
 
 ADD_SUBDIRECTORY(src/json)
 # ADD_SUBDIRECTORY(src/evdns)
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e57d18a
--- /dev/null
@@ -0,0 +1,7 @@
+# Librspamd
+SET(LIBRSPAMDSRC                         librspamdclient.c ../src/util.c ../src/upstream.c)
+
+ADD_LIBRARY(rspamdclient SHARED ${LIBRSPAMDSRC})
+ADD_LIBRARY(rspamdclient_static STATIC ${LIBRSPAMDSRC})
+SET_TARGET_PROPERTIES(rspamdclient PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -I../src/")
+SET_TARGET_PROPERTIES(rspamdclient_static PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -I../src/")
\ No newline at end of file
diff --git a/lib/librspamdclient.c b/lib/librspamdclient.c
new file mode 100644 (file)
index 0000000..8fa2804
--- /dev/null
@@ -0,0 +1,930 @@
+/*
+ * Copyright (c) 2011, 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 ''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 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 "librspamdclient.h"
+#include "config.h"
+#include "upstream.h"
+#include "util.h"
+#include "cfg_file.h"
+
+#define MAX_RSPAMD_SERVERS 255
+#define DEFAULT_CONNECT_TIMEOUT 500
+#define DEFAULT_READ_TIMEOUT 5000
+#define G_RSPAMD_ERROR rspamd_error_quark ()
+
+struct rspamd_server {
+       struct upstream up;
+       struct in_addr addr;
+       guint16 client_port;
+       guint16 controller_port;
+       gchar *name;
+};
+
+struct rspamd_client {
+       struct rspamd_server servers[MAX_RSPAMD_SERVERS];
+       guint servers_num;
+       guint connect_timeout;
+       guint read_timeout;
+};
+
+struct rspamd_connection {
+       struct rspamd_server *server;
+       time_t connection_time;
+       gint socket;
+       struct rspamd_result *result;
+       GString *in_buf;
+       struct rspamd_metric *cur_metric;
+};
+
+static struct rspamd_client *client = NULL;
+
+/** Private functions **/
+static inline                   GQuark
+rspamd_error_quark (void)
+{
+       return g_quark_from_static_string ("g-rspamd-error-quark");
+}
+
+static void
+metric_free_func (gpointer arg)
+{
+       struct rspamd_metric            *m = arg;
+
+       g_hash_table_destroy (m->symbols);
+       g_free (m->name);
+       g_free (m);
+}
+
+static void
+symbol_free_func (gpointer arg)
+{
+       struct rspamd_symbol            *s = arg;
+       GList                           *cur = s->options;
+
+       while (cur) {
+               g_free (cur->data);
+               cur = g_list_next (cur);
+       }
+       g_list_free (s->options);
+       g_free (s->name);
+       g_free (s);
+}
+
+static struct rspamd_connection *
+rspamd_connect_random_server (guint16 port, GError **err)
+{
+       struct rspamd_server            *selected = NULL;
+       struct rspamd_connection        *new;
+       time_t                           now;
+
+       if (client->servers_num == 0) {
+               errno = EINVAL;
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, 1, "No servers can be reached");
+               }
+               return NULL;
+       }
+       /* Select random server */
+       now = time (NULL);
+       selected = (struct rspamd_server *)get_random_upstream (client->servers,
+                       client->servers_num, sizeof (struct rspamd_server),
+                       now, DEFAULT_UPSTREAM_ERROR_TIME, DEFAULT_UPSTREAM_DEAD_TIME, DEFAULT_UPSTREAM_MAXERRORS);
+       if (selected == NULL) {
+               errno = EINVAL;
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, 1, "No servers can be reached");
+               }
+               return NULL;
+       }
+       /* Allocate connection */
+       new = g_malloc (sizeof (struct rspamd_connection));
+       new->server = selected;
+       new->connection_time = now;
+       /* Create socket */
+       new->socket = make_tcp_socket (&selected->addr, port, FALSE, TRUE);
+       if (new->socket == -1) {
+               goto err;
+       }
+       /* Poll waiting for writing */
+       if (poll_sync_socket (new->socket, client->connect_timeout , POLLOUT) <= 0) {
+               errno = ETIMEDOUT;
+               goto err;
+       }
+
+       new->in_buf = g_string_sized_new (BUFSIZ);
+       return new;
+
+err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Could not connect to server %s: %s",
+                       selected->name, strerror (errno));
+       }
+       upstream_fail (&selected->up, now);
+       g_free (new);
+       return NULL;
+}
+
+static struct rspamd_metric *
+rspamd_create_metric (const gchar *begin, guint len)
+{
+       struct rspamd_metric            *new;
+
+       new = g_malloc0 (sizeof (struct rspamd_metric));
+       new->symbols = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, symbol_free_func);
+       new->name = g_malloc (len + 1);
+       memcpy (new->name, begin, len);
+       new->name[len] = '\0';
+
+       return new;
+}
+
+/*
+ * Parse line like RSPAMD/{version} {code} {message}
+ */
+static gboolean
+parse_rspamd_first_line (struct rspamd_connection *c, guint len, GError **err)
+{
+       gchar                           *b = c->in_buf->str + sizeof("RSPAMD/") - 1, *p;
+       guint                            remain = len - sizeof("RSPAMD/") + 1, state = 0, next_state;
+
+       p = b;
+       while (p - b < remain) {
+               switch (state) {
+               case 0:
+                       /* Read version */
+                       if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 1;
+                       }
+                       else if (!g_ascii_isdigit (*p) && *p != '.') {
+                               goto err;
+                       }
+                       p ++;
+                       break;
+               case 1:
+                       /* Read code */
+                       if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 2;
+                       }
+                       else if (!g_ascii_isdigit (*p)) {
+                               goto err;
+                       }
+                       p ++;
+                       break;
+               case 2:
+                       /* Read message */
+                       if (g_ascii_isspace (*p) || p - b == remain - 1) {
+                               state = 99;
+                               next_state = 3;
+                       }
+                       p ++;
+                       break;
+               case 3:
+                       goto err;
+               case 99:
+                       /* Skip spaces */
+                       if (!g_ascii_isspace (*p)) {
+                               state = next_state;
+                       }
+                       else {
+                               p ++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 99) {
+               goto err;
+       }
+
+err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid protocol line: %*s at pos: %d",
+                               len, b, (int)(p - b));
+       }
+       upstream_fail (&c->server->up, c->connection_time);
+       return FALSE;
+}
+
+/*
+ * Parse line like Metric: <name>; <True|False|Skip>; <score> / <required_score>[ / <reject_score>]
+ */
+static gboolean
+parse_rspamd_metric_line (struct rspamd_connection *conn, guint len, GError **err)
+{
+       gchar                           *b = conn->in_buf->str + sizeof("Metric:") - 1, *p, *c, *err_str;
+       guint                            remain = len - sizeof("Metric:") + 1, state = 0, next_state;
+       struct rspamd_metric            *new;
+
+       p = b;
+       c = b;
+       while (p - b < remain) {
+               switch (state) {
+               case 0:
+                       /* Read metric's name */
+                       if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 0;
+                       }
+                       else if (*p == ';') {
+                               if (p - c <= 1) {
+                                       /* Empty metric name */
+                                       goto err;
+                               }
+                               else {
+                                       /* Create new metric */
+                                       new = rspamd_create_metric (c, p - c - 1);
+                                       if (g_hash_table_lookup (conn->result->metrics, new->name) != NULL) {
+                                               /* Duplicate metric */
+                                               metric_free_func (new);
+                                               goto err;
+                                       }
+                                       g_hash_table_insert (conn->result->metrics, new->name, new);
+                                       conn->cur_metric = new;
+                                       state = 99;
+                                       next_state = 1;
+                               }
+                       }
+                       p ++;
+                       break;
+               case 1:
+                       /* Read boolean result */
+                       if (*p == ';') {
+                               if (p - c > sizeof("Skip")) {
+                                       if (memcmp (c, "Skip", p - c - 1) == 0) {
+                                               new->is_skipped = TRUE;
+                                       }
+                                       state = 99;
+                                       next_state = 2;
+                               }
+                       }
+                       p ++;
+                       break;
+               case 2:
+                       /* Read score */
+                       if (g_ascii_isspace (*p)) {
+                               new->score = strtod (c, &err_str);
+                               if (*err_str != *p) {
+                                       /* Invalid score */
+                                       goto err;
+                               }
+                               state = 99;
+                               next_state = 3;
+                       }
+                       p ++;
+                       break;
+               case 3:
+                       /* Read / */
+                       if (*p != '/') {
+                               goto err;
+                       }
+                       else if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 4;
+                       }
+                       p ++;
+                       break;
+               case 4:
+                       /* Read required score */
+                       if (g_ascii_isspace (*p)) {
+                               new->required_score = strtod (c, &err_str);
+                               if (*err_str != *p) {
+                                       /* Invalid score */
+                                       goto err;
+                               }
+                               state = 99;
+                               next_state = 5;
+                       }
+                       p ++;
+                       break;
+               case 5:
+                       /* Read / if it exists */
+                       if (*p != '/') {
+                               goto err;
+                       }
+                       else if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 6;
+                       }
+                       p ++;
+                       break;
+               case 6:
+                       /* Read reject score */
+                       if (g_ascii_isspace (*p) || p - b == remain - 1) {
+                               new->reject_score = strtod (c, &err_str);
+                               if (*err_str != *p && *err_str != *(p + 1)) {
+                                       /* Invalid score */
+                                       goto err;
+                               }
+                               state = 99;
+                       }
+                       p ++;
+                       break;
+               case 99:
+                       /* Skip spaces */
+                       if (!g_ascii_isspace (*p)) {
+                               state = next_state;
+                               c = p;
+                       }
+                       else {
+                               p ++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 99) {
+               goto err;
+       }
+
+err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid metric line: %*s at pos: %d",
+                       len, b, (int)(p - b));
+       }
+       upstream_fail (&conn->server->up, conn->connection_time);
+       return FALSE;
+}
+
+/*
+ * Parse line like Symbol: <name>[(score)]; [option1, [option2 ...]]
+ */
+static gboolean
+parse_rspamd_symbol_line (struct rspamd_connection *conn, guint len, GError **err)
+{
+       gchar                           *b = conn->in_buf->str + sizeof("Symbol:") - 1, *p, *c, *err_str, *sym;
+       guint                            remain = len - sizeof("Symbol:") + 1, state = 0, next_state, l;
+       struct rspamd_symbol            *new;
+
+       p = b;
+       c = b;
+       while (p - b < remain) {
+               switch (state) {
+               case 0:
+                       /* Read symbol's name */
+                       if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 0;
+                       }
+                       else if (*p == ';' || *p == '(') {
+                               if (p - c <= 1) {
+                                       /* Empty symbol name */
+                                       goto err;
+                               }
+                               else {
+                                       /* Create new symbol */
+                                       sym = g_malloc (p - c);
+                                       sym[p - c - 1] = '\0';
+                                       memcpy (sym, c, p - c - 1);
+
+                                       if (g_hash_table_lookup (conn->cur_metric->symbols, sym) != NULL) {
+                                               /* Duplicate symbol */
+                                               g_free (sym);
+                                               goto err;
+                                       }
+                                       new = g_malloc0 (sizeof (struct rspamd_symbol));
+                                       g_hash_table_insert (conn->cur_metric->symbols, sym, new);
+                                       state = 99;
+                                       if (*p == '(') {
+                                               next_state = 1;
+                                               new->weight = 0;
+                                       }
+                                       else {
+                                               next_state = 2;
+                                       }
+                               }
+                       }
+                       p ++;
+                       break;
+               case 1:
+                       /* Read symbol's weight */
+                       if (*p == ')') {
+                               new->weight = strtod (c, &err_str);
+                               if (*err_str != *p) {
+                                       /* Invalid weight */
+                                       goto err;
+                               }
+                               if (*(p + 1) == ';') {
+                                       p ++;
+                               }
+                               state = 99;
+                               next_state = 2;
+                       }
+                       p ++;
+                       break;
+               case 2:
+                       /* Read option */
+                       if (*p == ',' || p - b == remain - 1) {
+                               /* Insert option into linked list */
+                               l = p - c;
+                               if (p - b == remain - 1) {
+                                       l ++;
+                               }
+                               sym = g_malloc (l + 1);
+                               sym[l] = '\0';
+                               memcpy (sym, c, l);
+                               new->options = g_list_prepend (new->options, sym);
+                               state = 99;
+                               next_state = 2;
+                       }
+                       p ++;
+                       break;
+               case 99:
+                       /* Skip spaces */
+                       if (!g_ascii_isspace (*p)) {
+                               state = next_state;
+                               c = p;
+                       }
+                       else {
+                               p ++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 99) {
+               goto err;
+       }
+
+       err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid symbol line: %*s at pos: %d",
+                               len, b, (int)(p - b));
+       }
+       upstream_fail (&conn->server->up, conn->connection_time);
+       return FALSE;
+}
+
+/*
+ * Parse line like Action: <action>
+ */
+static gboolean
+parse_rspamd_action_line (struct rspamd_connection *conn, guint len, GError **err)
+{
+       gchar                           *b = conn->in_buf->str + sizeof("Action:") - 1, *p, *c, *sym;
+       guint                            remain = len - sizeof("Action:") + 1, state = 0, next_state;
+
+       p = b;
+       c = b;
+       while (p - b < remain) {
+               switch (state) {
+               case 0:
+                       /* Read action */
+                       if (g_ascii_isspace (*p)) {
+                               state = 99;
+                               next_state = 0;
+                       }
+                       else if (p - b == remain - 1) {
+                               if (p - c <= 1) {
+                                       /* Empty action name */
+                                       goto err;
+                               }
+                               else {
+                                       /* Create new action */
+                                       sym = g_malloc (p - c + 1);
+                                       sym[p - c] = '\0';
+                                       memcpy (sym, c, p - c);
+
+                                       conn->cur_metric->action = sym;
+                                       state = 99;
+                               }
+                       }
+                       p ++;
+                       break;
+               case 99:
+                       /* Skip spaces */
+                       if (!g_ascii_isspace (*p)) {
+                               state = next_state;
+                               c = p;
+                       }
+                       else {
+                               p ++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 99) {
+               goto err;
+       }
+
+       err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid action line: %*s at pos: %d",
+                               len, b, (int)(p - b));
+       }
+       upstream_fail (&conn->server->up, conn->connection_time);
+       return FALSE;
+}
+
+/*
+ * Parse line like Header: <value>
+ */
+static gboolean
+parse_rspamd_header_line (struct rspamd_connection *conn, guint len, GError **err)
+{
+       gchar                           *b = conn->in_buf->str, *p, *c, *hname = NULL, *hvalue = NULL;
+       guint                            remain = len, state = 0, next_state;
+
+       p = b;
+       c = b;
+       while (p - b < remain) {
+               switch (state) {
+               case 0:
+                       /* Read header name */
+                       if (*p == ':') {
+                               if (p - c <= 1) {
+                                       /* Empty header name */
+                                       goto err;
+                               }
+                               else {
+                                       /* Create header name */
+                                       hname = g_malloc (p - c);
+                                       hname[p - c - 1] = '\0';
+                                       memcpy (hname, c, p - c - 1);
+                                       next_state = 1;
+                                       state = 99;
+                               }
+                       }
+                       p ++;
+                       break;
+               case 1:
+                       if (p - b == remain - 1) {
+                               if (p - c <= 1) {
+                                       /* Empty action name */
+                                       goto err;
+                               }
+                               else {
+                                       /* Create header value */
+                                       hvalue = g_malloc (p - c + 1);
+                                       hvalue[p - c] = '\0';
+                                       memcpy (hvalue, c, p - c);
+                                       g_hash_table_replace (conn->result->headers, hname, hvalue);
+                                       state = 99;
+                               }
+                       }
+                       p ++;
+                       break;
+               case 99:
+                       /* Skip spaces */
+                       if (!g_ascii_isspace (*p)) {
+                               state = next_state;
+                               c = p;
+                       }
+                       else {
+                               p ++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 99) {
+               goto err;
+       }
+
+       err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid header line: %*s at pos: %d",
+                               len, b, (int)(p - b));
+       }
+       if (hname) {
+               g_free (hname);
+       }
+       if (hvalue) {
+               g_free (hvalue);
+       }
+       upstream_fail (&conn->server->up, conn->connection_time);
+       return FALSE;
+}
+
+static gboolean
+parse_rspamd_reply_line (struct rspamd_connection *c, guint len, GError **err)
+{
+       gchar                           *p = c->in_buf->str;
+
+       g_assert (len > 0);
+
+       /*
+        * In fact we have 3 states of parsing:
+        * 1) we have current metric and parse symbols
+        * 2) we have no current metric and skip everything to headers hash
+        * 3) we have current metric but got not symbol but header -> put it into headers hash
+        * Line is parsed using specific state machine
+        */
+       if (c->cur_metric == NULL) {
+               if (len > sizeof ("RSPAMD/") && memcmp (p, "RSPAMD/", sizeof ("RSPAMD/") - 1) == 0) {
+                       return parse_rspamd_first_line (c, len, err);
+               }
+               else if (len > sizeof ("Metric:") && memcmp (p, "Metric:", sizeof("Metric:") - 1) == 0) {
+                       return parse_rspamd_metric_line (c, len, err);
+               }
+               else {
+                       return parse_rspamd_header_line (c, len, err);
+               }
+       }
+       else {
+               if (len > sizeof ("Metric:") && memcmp (p, "Metric:", sizeof("Metric:") - 1) == 0) {
+                       return parse_rspamd_metric_line (c, len, err);
+               }
+               else if (len > sizeof ("Symbol:") && memcmp (p, "Symbol:", sizeof("Symbol:") - 1) == 0) {
+                       return parse_rspamd_symbol_line (c, len, err);
+               }
+               else if (len > sizeof ("Action:") && memcmp (p, "Action:", sizeof("Action:") - 1) == 0) {
+                       return parse_rspamd_action_line (c, len, err);
+               }
+               else {
+                       return parse_rspamd_header_line (c, len, err);
+               }
+       }
+
+       /* Not reached */
+       return FALSE;
+}
+
+static gboolean
+read_rspamd_reply_line (struct rspamd_connection *c, GError **err)
+{
+       gint                             len, r;
+       char                             p;
+
+       /* Try to obtain string from the input buffer */
+       if (c->in_buf->len > 0) {
+               len = 0;
+               while (len < c->in_buf->len) {
+                       p = c->in_buf->str[len];
+                       if (p == '\r' || p == '\n') {
+                               if (parse_rspamd_reply_line (c, len, err)) {
+                                       /* Strip '\r\n' */
+                                       while (len < c->in_buf->len && (p == '\r' || p == '\n')) {
+                                               p = c->in_buf->str[++len];
+                                       }
+                                       /* Move remaining buffer to the begin of string */
+                                       c->in_buf = g_string_erase (c->in_buf, 0, len);
+                               }
+                               else {
+                                       return FALSE;
+                               }
+                       }
+                       len ++;
+               }
+       }
+       /* Poll socket */
+       if (poll_sync_socket (c->socket, client->read_timeout, POLL_IN) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Could not connect to server %s: %s",
+                                       c->server->name, strerror (errno));
+               }
+               upstream_fail (&c->server->up, c->connection_time);
+               return FALSE;
+       }
+       if (c->in_buf->allocated_len - c->in_buf->len < BUFSIZ / 2) {
+               /* Grow buffer */
+               c->in_buf = g_string_set_size (c->in_buf, c->in_buf->allocated_len * 2);
+       }
+       /* Read new data to a string */
+       if ((r = read (c->socket,
+                       c->in_buf->str + c->in_buf->len,
+                       c->in_buf->allocated_len - c->in_buf->len)) < 0) {
+               /* Try to parse remaining data */
+               return parse_rspamd_reply_line (c, c->in_buf->len, err);
+       }
+
+       return TRUE;
+}
+
+/*
+ * More or less portable version of sendfile
+ */
+static gboolean
+rspamd_sendfile (gint sock, gint fd, GError **err)
+{
+
+       make_socket_blocking (sock);
+#ifdef HAVE_SENDFILE
+# if defined(FREEBSD) || defined(DARWIN)
+       off_t                           off = 0;
+#  if defined(FREEBSD)
+       /* FreeBSD version */
+       if (sendfile (sock, fd, 0, 0, NULL, &off, 0) != 0) {
+#  elif defined(DARWIN)
+       /* Darwin version */
+       if (sendfile (sock, fd, 0, &off, NULL, 0) != 0) {
+#  endif
+               goto err;
+       }
+# else
+       ssize_t                         r;
+       off_t                           off = 0;
+       struct stat                     st;
+
+       fstat (fd, &st);
+       /* Linux version */
+       r = sendfile (fd, sock, &off, st.st_size);
+       if (r == -1) {
+               goto err;
+       }
+# endif
+#else
+       /* Emulating version */
+       ssize_t                         r;
+       gchar                           buf[BUFSIZ];
+
+       while ((r = read (fd, buf, sizeof (buf))) > 0) {
+               if ((r = write (sock, buf, r)) != r) {
+                       goto err;
+               }
+       }
+#endif
+       make_socket_nonblocking (sock);
+
+       return TRUE;
+
+err:
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Sendfile error: %s",
+                       strerror (errno));
+       }
+       return FALSE;
+}
+
+static void
+rspamd_free_connection (struct rspamd_connection *c)
+{
+       make_socket_blocking (c->socket);
+       (void)close (c->socket);
+       g_string_free (c->in_buf, TRUE);
+
+       g_free (c);
+}
+
+/** Public API **/
+
+/*
+ * Init rspamd client library
+ */
+void
+rspamd_client_init (void)
+{
+       if (client != NULL) {
+               rspamd_client_close ();
+       }
+       client = g_malloc0 (sizeof (struct rspamd_client));
+       client->read_timeout = DEFAULT_READ_TIMEOUT;
+       client->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+}
+
+/*
+ * Add rspamd server
+ */
+gboolean
+rspamd_add_server (const gchar *host, guint16 port, guint16 controller_port, GError **err)
+{
+       struct rspamd_server           *new;
+       struct hostent                 *hent;
+
+       g_assert (client != NULL);
+       if (client->servers_num >= MAX_RSPAMD_SERVERS) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, 1, "Maximum number of servers reached: %d", MAX_RSPAMD_SERVERS);
+               }
+               return FALSE;
+       }
+       new = &client->servers[client->servers_num];
+
+       if (!inet_aton (host, &new->addr)) {
+               /* Try to call gethostbyname */
+               hent = gethostbyname (host);
+               if (hent == NULL) {
+                       if (*err == NULL) {
+                               *err = g_error_new (G_RSPAMD_ERROR, 1, "Cannot resolve: %s", host);
+                       }
+                       return FALSE;
+               }
+               else {
+                       memcpy (&new->addr, hent->h_addr, sizeof (struct in_addr));
+               }
+       }
+       new->client_port = port;
+       new->controller_port = controller_port;
+       new->name = g_strdup (host);
+
+       client->servers_num ++;
+       return TRUE;
+}
+
+/*
+ * Set timeouts (values in milliseconds)
+ */
+void
+rspamd_set_timeout (guint connect_timeout, guint read_timeout)
+{
+       g_assert (client != NULL);
+
+       if (connect_timeout > 0) {
+               client->connect_timeout = connect_timeout;
+       }
+       if (read_timeout > 0) {
+               client->read_timeout = read_timeout;
+       }
+}
+
+/*
+ * Scan message from memory
+ */
+struct rspamd_result *
+rspamd_scan_memory (const guchar *message, gsize length, GHashTable *headers, GError **err)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Scan message from file
+ */
+struct rspamd_result *
+rspamd_scan_file (const guchar *filename, GHashTable *headers, GError **err)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Scan message from fd
+ */
+struct rspamd_result *
+rspamd_scan_fd (int fd, GHashTable *headers, GError **err)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Learn message from memory
+ */
+gboolean
+rspamd_learn_memory (const guchar *message, gsize length, const gchar *symbol, GError **err)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Learn message from file
+ */
+gboolean
+rspamd_learn_file (const guchar *filename, const gchar *symbol, GError **err)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Learn message from fd
+ */
+gboolean
+rspamd_learn_fd (int fd, const gchar *symbol, GError **err)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Free results
+ */
+void
+rspamd_free_result (struct rspamd_result *result)
+{
+       g_assert (client != NULL);
+
+}
+
+/*
+ * Close library and free associated resources
+ */
+void
+rspamd_client_close (void)
+{
+       g_assert (client != NULL);
+}
diff --git a/lib/librspamdclient.h b/lib/librspamdclient.h
new file mode 100644 (file)
index 0000000..fc5a6f2
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef LIBRSPAMD_CLIENT_H
+#define LIBRSPAMD_CLIENT_H
+
+#include <glib.h>
+
+/**
+ * Struct for representing symbols
+ */
+struct rspamd_symbol {
+       gchar *name;                /**< name */
+       double weight;                  /**< weight */
+       GList *options;                 /**< List of options (as const gchar *) */
+};
+
+/**
+ * Struct for representing metrics
+ */
+struct rspamd_metric {
+       gchar *name;
+       gchar *action;
+       double score;
+       double required_score;
+       double reject_score;
+       gboolean is_skipped;
+       GHashTable *symbols;
+};
+
+struct rspamd_connection;
+/**
+ * Result of scan
+ */
+struct rspamd_result {
+       struct rspamd_connection *conn;
+       gboolean is_ok;
+       GHashTable *metrics;
+       GHashTable *headers;
+};
+
+/*
+ * Init rspamd client library
+ */
+void rspamd_client_init (void);
+
+/*
+ * Add rspamd server
+ */
+gboolean rspamd_add_server (const gchar *host, guint16 port, guint16 controller_port, GError **err);
+
+/*
+ * Set timeouts (values in milliseconds)
+ */
+void rspamd_set_timeout (guint connect_timeout, guint read_timeout);
+
+/*
+ * Scan message from memory
+ */
+struct rspamd_result * rspamd_scan_memory (const guchar *message, gsize length, GHashTable *headers, GError **err);
+
+/*
+ * Scan message from file
+ */
+struct rspamd_result * rspamd_scan_file (const guchar *filename, GHashTable *headers, GError **err);
+
+/*
+ * Scan message from fd
+ */
+struct rspamd_result * rspamd_scan_fd (int fd, GHashTable *headers, GError **err);
+
+/*
+ * Learn message from memory
+ */
+gboolean rspamd_learn_memory (const guchar *message, gsize length, const gchar *symbol, GError **err);
+
+/*
+ * Learn message from file
+ */
+gboolean rspamd_learn_file (const guchar *filename, const gchar *symbol, GError **err);
+
+/*
+ * Learn message from fd
+ */
+gboolean rspamd_learn_fd (int fd, const gchar *symbol, GError **err);
+
+/*
+ * Free results
+ */
+void rspamd_free_result (struct rspamd_result *result);
+
+/*
+ * Close library and free associated resources
+ */
+void rspamd_client_close (void);
+
+#endif