]> source.dussan.org Git - rspamd.git/commitdiff
* Add C client for rspamd that is using librspamdclient
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Mon, 31 Jan 2011 17:33:12 +0000 (20:33 +0300)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Mon, 31 Jan 2011 17:33:12 +0000 (20:33 +0300)
CMakeLists.txt
lib/CMakeLists.txt
lib/librspamdclient.c
src/client/CMakeLists.txt [new file with mode: 0644]
src/client/rspamc.c [new file with mode: 0644]

index 6ee3cf56a487a23c96253b7834b56df6ee1e881e..c47ccec3688b4cd8e5d4d2ed60ea23a2c7cbaa91 100644 (file)
@@ -584,6 +584,7 @@ IF(ENABLE_PERL MATCHES "ON")
 ENDIF(ENABLE_PERL MATCHES "ON")
 ADD_SUBDIRECTORY(src/lua)
 ADD_SUBDIRECTORY(lib)
+ADD_SUBDIRECTORY(src/client)
 
 ADD_SUBDIRECTORY(src/json)
 # ADD_SUBDIRECTORY(src/evdns)
index e57d18a2edc327e707f74b82b28b0128426a3d4f..f625b2833b7fa8ede7cb87ffaa1e024abe254ab1 100644 (file)
@@ -1,5 +1,5 @@
 # Librspamd
-SET(LIBRSPAMDSRC                         librspamdclient.c ../src/util.c ../src/upstream.c)
+SET(LIBRSPAMDSRC                         librspamdclient.c ../src/util.c ../src/upstream.c ../src/mem_pool.c)
 
 ADD_LIBRARY(rspamdclient SHARED ${LIBRSPAMDSRC})
 ADD_LIBRARY(rspamdclient_static STATIC ${LIBRSPAMDSRC})
index 8fa280440331e6013560e7ad17b03f196b3c44f8..d7568eb6245e746bd208b7010493a772b42df820 100644 (file)
@@ -92,7 +92,7 @@ symbol_free_func (gpointer arg)
 }
 
 static struct rspamd_connection *
-rspamd_connect_random_server (guint16 port, GError **err)
+rspamd_connect_random_server (gboolean is_control, GError **err)
 {
        struct rspamd_server            *selected = NULL;
        struct rspamd_connection        *new;
@@ -122,7 +122,9 @@ rspamd_connect_random_server (guint16 port, GError **err)
        new->server = selected;
        new->connection_time = now;
        /* Create socket */
-       new->socket = make_tcp_socket (&selected->addr, port, FALSE, TRUE);
+       new->socket = make_tcp_socket (&selected->addr,
+                                                               is_control ? selected->controller_port : selected->client_port,
+                                                                       FALSE, TRUE);
        if (new->socket == -1) {
                goto err;
        }
@@ -159,13 +161,27 @@ rspamd_create_metric (const gchar *begin, guint len)
        return new;
 }
 
+static struct rspamd_result *
+rspamd_create_result (struct rspamd_connection *c)
+{
+       struct rspamd_result           *new;
+
+       new = g_malloc (sizeof (struct rspamd_result));
+       new->conn = c;
+       new->headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       new->metrics = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, metric_free_func);
+       new->is_ok = FALSE;
+
+       return new;
+}
+
 /*
  * Parse line like RSPAMD/{version} {code} {message}
  */
 static gboolean
-parse_rspamd_first_line (struct rspamd_connection *c, guint len, GError **err)
+parse_rspamd_first_line (struct rspamd_connection *conn, guint len, GError **err)
 {
-       gchar                           *b = c->in_buf->str + sizeof("RSPAMD/") - 1, *p;
+       gchar                           *b = conn->in_buf->str + sizeof("RSPAMD/") - 1, *p, *c;
        guint                            remain = len - sizeof("RSPAMD/") + 1, state = 0, next_state;
 
        p = b;
@@ -187,6 +203,9 @@ parse_rspamd_first_line (struct rspamd_connection *c, guint len, GError **err)
                        if (g_ascii_isspace (*p)) {
                                state = 99;
                                next_state = 2;
+                               if (*c == '0') {
+                                       conn->result->is_ok = TRUE;
+                               }
                        }
                        else if (!g_ascii_isdigit (*p)) {
                                goto err;
@@ -207,6 +226,7 @@ parse_rspamd_first_line (struct rspamd_connection *c, guint len, GError **err)
                        /* Skip spaces */
                        if (!g_ascii_isspace (*p)) {
                                state = next_state;
+                               c = p;
                        }
                        else {
                                p ++;
@@ -219,12 +239,13 @@ parse_rspamd_first_line (struct rspamd_connection *c, guint len, GError **err)
                goto err;
        }
 
+       return TRUE;
 err:
        if (*err == NULL) {
                *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid protocol line: %*s at pos: %d",
-                               len, b, (int)(p - b));
+                               remain, b, (int)(p - b));
        }
-       upstream_fail (&c->server->up, c->connection_time);
+       upstream_fail (&conn->server->up, conn->connection_time);
        return FALSE;
 }
 
@@ -255,7 +276,7 @@ parse_rspamd_metric_line (struct rspamd_connection *conn, guint len, GError **er
                                }
                                else {
                                        /* Create new metric */
-                                       new = rspamd_create_metric (c, p - c - 1);
+                                       new = rspamd_create_metric (c, p - c);
                                        if (g_hash_table_lookup (conn->result->metrics, new->name) != NULL) {
                                                /* Duplicate metric */
                                                metric_free_func (new);
@@ -272,13 +293,13 @@ parse_rspamd_metric_line (struct rspamd_connection *conn, guint len, GError **er
                case 1:
                        /* Read boolean result */
                        if (*p == ';') {
-                               if (p - c > sizeof("Skip")) {
+                               if (p - c >= sizeof("Skip")) {
                                        if (memcmp (c, "Skip", p - c - 1) == 0) {
                                                new->is_skipped = TRUE;
                                        }
-                                       state = 99;
-                                       next_state = 2;
                                }
+                               state = 99;
+                               next_state = 2;
                        }
                        p ++;
                        break;
@@ -297,18 +318,18 @@ parse_rspamd_metric_line (struct rspamd_connection *conn, guint len, GError **er
                        break;
                case 3:
                        /* Read / */
-                       if (*p != '/') {
-                               goto err;
-                       }
-                       else if (g_ascii_isspace (*p)) {
+                       if (g_ascii_isspace (*p)) {
                                state = 99;
                                next_state = 4;
                        }
+                       else if (*p != '/') {
+                               goto err;
+                       }
                        p ++;
                        break;
                case 4:
                        /* Read required score */
-                       if (g_ascii_isspace (*p)) {
+                       if (g_ascii_isspace (*p) || p - b == remain - 1) {
                                new->required_score = strtod (c, &err_str);
                                if (*err_str != *p) {
                                        /* Invalid score */
@@ -321,13 +342,13 @@ parse_rspamd_metric_line (struct rspamd_connection *conn, guint len, GError **er
                        break;
                case 5:
                        /* Read / if it exists */
-                       if (*p != '/') {
-                               goto err;
-                       }
-                       else if (g_ascii_isspace (*p)) {
+                       if (g_ascii_isspace (*p)) {
                                state = 99;
                                next_state = 6;
                        }
+                       else if (*p != '/') {
+                               goto err;
+                       }
                        p ++;
                        break;
                case 6:
@@ -358,11 +379,12 @@ parse_rspamd_metric_line (struct rspamd_connection *conn, guint len, GError **er
        if (state != 99) {
                goto err;
        }
+       return TRUE;
 
 err:
        if (*err == NULL) {
-               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid metric line: %*s at pos: %d",
-                       len, b, (int)(p - b));
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid metric line: %*s at pos: %d, state: %d",
+                       remain, b, (int)(p - b), state);
        }
        upstream_fail (&conn->server->up, conn->connection_time);
        return FALSE;
@@ -395,9 +417,9 @@ parse_rspamd_symbol_line (struct rspamd_connection *conn, guint len, GError **er
                                }
                                else {
                                        /* Create new symbol */
-                                       sym = g_malloc (p - c);
-                                       sym[p - c - 1] = '\0';
-                                       memcpy (sym, c, p - c - 1);
+                                       sym = g_malloc (p - c + 1);
+                                       sym[p - c] = '\0';
+                                       memcpy (sym, c, p - c);
 
                                        if (g_hash_table_lookup (conn->cur_metric->symbols, sym) != NULL) {
                                                /* Duplicate symbol */
@@ -405,6 +427,7 @@ parse_rspamd_symbol_line (struct rspamd_connection *conn, guint len, GError **er
                                                goto err;
                                        }
                                        new = g_malloc0 (sizeof (struct rspamd_symbol));
+                                       new->name = sym;
                                        g_hash_table_insert (conn->cur_metric->symbols, sym, new);
                                        state = 99;
                                        if (*p == '(') {
@@ -467,11 +490,12 @@ parse_rspamd_symbol_line (struct rspamd_connection *conn, guint len, GError **er
        if (state != 99) {
                goto err;
        }
+       return TRUE;
 
        err:
        if (*err == NULL) {
                *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid symbol line: %*s at pos: %d",
-                               len, b, (int)(p - b));
+                               remain, b, (int)(p - b));
        }
        upstream_fail (&conn->server->up, conn->connection_time);
        return FALSE;
@@ -494,18 +518,23 @@ parse_rspamd_action_line (struct rspamd_connection *conn, guint len, GError **er
                        /* Read action */
                        if (g_ascii_isspace (*p)) {
                                state = 99;
-                               next_state = 0;
+                               next_state = 1;
+                       }
+                       else {
+                               state = 1;
                        }
-                       else if (p - b == remain - 1) {
+                       break;
+               case 1:
+                       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);
+                                       sym = g_malloc (p - c + 2);
+                                       sym[p - c + 1] = '\0';
+                                       memcpy (sym, c, p - c + 1);
 
                                        conn->cur_metric->action = sym;
                                        state = 99;
@@ -529,11 +558,12 @@ parse_rspamd_action_line (struct rspamd_connection *conn, guint len, GError **er
        if (state != 99) {
                goto err;
        }
+       return TRUE;
 
        err:
        if (*err == NULL) {
                *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid action line: %*s at pos: %d",
-                               len, b, (int)(p - b));
+                               remain, b, (int)(p - b));
        }
        upstream_fail (&conn->server->up, conn->connection_time);
        return FALSE;
@@ -561,9 +591,9 @@ parse_rspamd_header_line (struct rspamd_connection *conn, guint len, GError **er
                                }
                                else {
                                        /* Create header name */
-                                       hname = g_malloc (p - c);
-                                       hname[p - c - 1] = '\0';
-                                       memcpy (hname, c, p - c - 1);
+                                       hname = g_malloc (p - c + 1);
+                                       hname[p - c] = '\0';
+                                       memcpy (hname, c, p - c);
                                        next_state = 1;
                                        state = 99;
                                }
@@ -603,11 +633,12 @@ parse_rspamd_header_line (struct rspamd_connection *conn, guint len, GError **er
        if (state != 99) {
                goto err;
        }
+       return TRUE;
 
        err:
        if (*err == NULL) {
                *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid header line: %*s at pos: %d",
-                               len, b, (int)(p - b));
+                               remain, b, (int)(p - b));
        }
        if (hname) {
                g_free (hname);
@@ -682,6 +713,7 @@ read_rspamd_reply_line (struct rspamd_connection *c, GError **err)
                                        }
                                        /* Move remaining buffer to the begin of string */
                                        c->in_buf = g_string_erase (c->in_buf, 0, len);
+                                       len = 0;
                                }
                                else {
                                        return FALSE;
@@ -706,12 +738,13 @@ read_rspamd_reply_line (struct rspamd_connection *c, GError **err)
        /* 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) {
+                       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);
+               c->in_buf->len += r;
+               return read_rspamd_reply_line (c, err);
        }
 
-       return TRUE;
+       return FALSE;
 }
 
 /*
@@ -721,6 +754,7 @@ static gboolean
 rspamd_sendfile (gint sock, gint fd, GError **err)
 {
 
+       /* Make socket blocking for further operations */
        make_socket_blocking (sock);
 #ifdef HAVE_SENDFILE
 # if defined(FREEBSD) || defined(DARWIN)
@@ -769,6 +803,38 @@ err:
        return FALSE;
 }
 
+static gboolean
+rspamd_send_normal_command (struct rspamd_connection *c, const gchar *command,
+               gsize clen, GHashTable *headers, GError **err)
+{
+       static gchar                    outbuf[16384];
+       GHashTableIter                  it;
+       gpointer                        key, value;
+       gint                            r;
+
+       /* Write command */
+       r = rspamd_snprintf (outbuf, sizeof (outbuf), "%s RSPAMC/1.2\r\n", command);
+       r += rspamd_snprintf (outbuf + r, sizeof (outbuf) - r, "Content-Length: %uz\r\n", clen);
+       /* Iterate through headers */
+       if (headers != NULL) {
+               g_hash_table_iter_init (&it, headers);
+               while (g_hash_table_iter_next (&it, &key, &value)) {
+                       r += rspamd_snprintf (outbuf + r, sizeof (outbuf) - r, "%s: %s\r\n", key, value);
+               }
+       }
+       r += rspamd_snprintf (outbuf + r, sizeof (outbuf) - r, "\r\n");
+
+       if ((r = write (c->socket, outbuf, r)) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Write error: %s",
+                               strerror (errno));
+               }
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
 static void
 rspamd_free_connection (struct rspamd_connection *c)
 {
@@ -856,8 +922,44 @@ rspamd_set_timeout (guint connect_timeout, guint read_timeout)
 struct rspamd_result *
 rspamd_scan_memory (const guchar *message, gsize length, GHashTable *headers, GError **err)
 {
+       struct rspamd_connection             *c;
+       struct rspamd_result                 *res = NULL;
+
        g_assert (client != NULL);
 
+       /* Connect to server */
+       c = rspamd_connect_random_server (FALSE, err);
+
+       if (c == NULL) {
+               return NULL;
+       }
+
+       /* Set socket blocking for writing */
+       make_socket_blocking (c->socket);
+       /* Send command */
+       if (!rspamd_send_normal_command (c, "SYMBOLS", length, headers, err)) {
+               return NULL;
+       }
+
+       /* Send message */
+       if (write (c->socket, message, length) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Write error: %s",
+                                       strerror (errno));
+               }
+               return NULL;
+       }
+
+       /* Create result structure */
+       res = rspamd_create_result (c);
+       c->result = res;
+       /* Restore non-blocking mode for reading operations */
+       make_socket_nonblocking (c->socket);
+
+       /* Read result cycle */
+       while (read_rspamd_reply_line (c, err));
+
+       return res;
 }
 
 /*
@@ -866,8 +968,19 @@ rspamd_scan_memory (const guchar *message, gsize length, GHashTable *headers, GE
 struct rspamd_result *
 rspamd_scan_file (const guchar *filename, GHashTable *headers, GError **err)
 {
+       gint                                 fd;
        g_assert (client != NULL);
 
+       /* Open file */
+       if ((fd = open (filename, O_RDONLY | O_CLOEXEC)) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Open error for file %s: %s",
+                                       filename, strerror (errno));
+               }
+               return NULL;
+       }
+
+       return rspamd_scan_fd (fd, headers, err);
 }
 
 /*
@@ -876,8 +989,49 @@ rspamd_scan_file (const guchar *filename, GHashTable *headers, GError **err)
 struct rspamd_result *
 rspamd_scan_fd (int fd, GHashTable *headers, GError **err)
 {
+       struct rspamd_connection             *c;
+       struct rspamd_result                 *res = NULL;
+       struct stat                           st;
+
        g_assert (client != NULL);
 
+       /* Connect to server */
+       c = rspamd_connect_random_server (FALSE, err);
+
+       if (c == NULL) {
+               return NULL;
+       }
+
+       /* Get length */
+       if (fstat (fd, &st) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Stat error: %s",
+                                       strerror (errno));
+               }
+               return NULL;
+       }
+       /* Set socket blocking for writing */
+       make_socket_blocking (c->socket);
+       /* Send command */
+       if (!rspamd_send_normal_command (c, "SYMBOLS", (gsize)st.st_size, headers, err)) {
+               return NULL;
+       }
+
+       /* Send message */
+       if (!rspamd_sendfile (c->socket, fd, err)) {
+               return NULL;
+       }
+
+       /* Create result structure */
+       res = rspamd_create_result (c);
+       c->result = res;
+       /* Restore non-blocking mode for reading operations */
+       make_socket_nonblocking (c->socket);
+
+       /* Read result cycle */
+       while (read_rspamd_reply_line (c, err));
+
+       return res;
 }
 
 /*
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
new file mode 100644 (file)
index 0000000..186b458
--- /dev/null
@@ -0,0 +1,8 @@
+# rspamc
+SET(RSPAMCSRC                    rspamc.c)
+
+ADD_EXECUTABLE(rspamc ${RSPAMCSRC})
+SET_TARGET_PROPERTIES(rspamc PROPERTIES COMPILE_FLAGS "-I.. -I../../lib")
+TARGET_LINK_LIBRARIES(rspamc rspamdclient)
+TARGET_LINK_LIBRARIES(rspamc ${CMAKE_REQUIRED_LIBRARIES})
+TARGET_LINK_LIBRARIES(rspamc ${GLIB2_LIBRARIES})
diff --git a/src/client/rspamc.c b/src/client/rspamc.c
new file mode 100644 (file)
index 0000000..1bae297
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * 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 "config.h"
+#include "librspamdclient.h"
+
+#define PRINT_FUNC printf
+
+#define DEFAULT_PORT 11333
+#define DEFAULT_CONTROL_PORT 11334
+
+static gchar                   *connect_str = "localhost";
+static gchar                   *password;
+static gchar                   *statfile;
+static gchar                   *ip;
+static gdouble                  weight;
+static gboolean                 pass_all;
+static gboolean                 tty = FALSE;
+
+static GOptionEntry entries[] =
+{
+               { "connect", 'h', 0, G_OPTION_ARG_STRING, &connect_str, "Specify host and port", NULL },
+               { "password", 'P', 0, G_OPTION_ARG_STRING, &password, "Specify control password", NULL },
+               { "statfile", 's', 0, G_OPTION_ARG_STRING, &statfile, "Statfile to learn (symbol name)", NULL },
+               { "weight", 'w', 0, G_OPTION_ARG_DOUBLE, &weight, "Weight for fuzzy operations", NULL },
+               { "pass", 'p', 0, G_OPTION_ARG_NONE, &pass_all, "Pass all filters", NULL },
+               { "ip", 'i', 0, G_OPTION_ARG_STRING, &ip, "Emulate that message was received from specified ip address", NULL },
+               { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
+};
+
+enum rspamc_command {
+       RSPAMC_COMMAND_UNKNOWN = 0,
+       RSPAMC_COMMAND_SYMBOLS,
+       RSPAMC_COMMAND_LEARN
+};
+
+/*
+ * Parse command line
+ */
+static void
+read_cmd_line (gint *argc, gchar ***argv)
+{
+       GError                         *error = NULL;
+       GOptionContext                 *context;
+
+       /* Prepare parser */
+       context = g_option_context_new ("- run rspamc client");
+       g_option_context_set_summary (context, "Summary:\n  Rspamd client version " RVERSION "\n  Release id: " RID);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       /* Parse options */
+       if (!g_option_context_parse (context, argc, argv, &error)) {
+               fprintf (stderr, "option parsing failed: %s\n", error->message);
+               exit (EXIT_FAILURE);
+       }
+
+       /* Argc and argv are shifted after this function */
+}
+
+/*
+ * Check rspamc command from string (used for arguments parsing)
+ */
+static enum rspamc_command
+check_rspamc_command (const gchar *cmd)
+{
+       if (g_ascii_strcasecmp (cmd, "SYMBOLS") == 0 ||
+               g_ascii_strcasecmp (cmd, "CHECK") == 0 ||
+               g_ascii_strcasecmp (cmd, "REPORT") == 0) {
+               /* These all are symbols, don't use other commands */
+               return RSPAMC_COMMAND_SYMBOLS;
+       }
+       else if (g_ascii_strcasecmp (cmd, "LEARN") == 0) {
+               return RSPAMC_COMMAND_LEARN;
+       }
+
+       return RSPAMC_COMMAND_UNKNOWN;
+}
+
+/*
+ * Parse connect_str and add server to librspamdclient
+ */
+static void
+add_rspamd_server (gboolean is_control)
+{
+       gchar                         **vec, *err_str;
+       guint16                         port;
+       GError                         *err = NULL;
+
+       if (connect_str == NULL) {
+               fprintf (stderr, "cannot connect to rspamd server - empty string\n");
+               exit (EXIT_FAILURE);
+       }
+       vec = g_strsplit_set (connect_str, ":", 2);
+       if (vec == NULL || *vec == NULL) {
+               fprintf (stderr, "cannot connect to rspamd server: %s\n", connect_str);
+               exit (EXIT_FAILURE);
+       }
+
+       if (vec[1] == NULL) {
+               port = is_control ? DEFAULT_CONTROL_PORT : DEFAULT_PORT;
+       }
+       else {
+               port = strtoul (vec[1], &err_str, 10);
+               if (*err_str != '\0') {
+                       fprintf (stderr, "cannot connect to rspamd server: %s, at pos %s\n", connect_str, err_str);
+                       exit (EXIT_FAILURE);
+               }
+       }
+
+       if (! rspamd_add_server (vec[0], port, port, &err)) {
+               fprintf (stderr, "cannot connect to rspamd server: %s, error: %s\n", connect_str, err->message);
+               exit (EXIT_FAILURE);
+       }
+}
+
+static void
+show_metric_result (gpointer key, gpointer value, gpointer ud)
+{
+       struct rspamd_metric            *metric = value;
+       struct rspamd_symbol            *s;
+       GList                           *cur;
+       GHashTableIter                   it;
+       gpointer                         k, v;
+       gboolean                         first;
+
+       if (metric->is_skipped) {
+               PRINT_FUNC ("%s: Skipped\n", key);
+       }
+       else {
+               if (tty) {
+                       PRINT_FUNC ("\033[1m%s:\033[0m %s [ %.2f / %.2f ]\n", key,
+                                               metric->score > metric->required_score ? "True" : "False",
+                                               metric->score, metric->required_score);
+               }
+               else {
+                       PRINT_FUNC ("%s: %s [ %.2f / %.2f ]\n", key,
+                                               metric->score > metric->required_score ? "True" : "False",
+                                               metric->score, metric->required_score);
+               }
+               if (tty) {
+                       if (metric->action) {
+                               PRINT_FUNC ("\033[1mAction:\033[0m %s\n", metric->action);
+                       }
+                       PRINT_FUNC ("\033[1mSymbols: \033[0m");
+               }
+               else {
+                       if (metric->action) {
+                               PRINT_FUNC ("Action: %s\n", metric->action);
+                       }
+                       PRINT_FUNC ("Symbols: ");
+               }
+               if (metric->symbols) {
+                       first = TRUE;
+                       g_hash_table_iter_init (&it, metric->symbols);
+                       while (g_hash_table_iter_next (&it, &k, &v)) {
+                               s = v;
+                               if (! first) {
+                                       PRINT_FUNC (", ");
+                               }
+                               else {
+                                       first = FALSE;
+                               }
+                               PRINT_FUNC ("%s(%.2f)", s->name, s->weight);
+                               if (s->options) {
+                                       PRINT_FUNC ("(");
+                                       cur = g_list_first (s->options);
+                                       while (cur) {
+                                               if (cur->next) {
+                                                       PRINT_FUNC ("%s,", cur->data);
+                                               }
+                                               else {
+                                                       PRINT_FUNC ("%s)", cur->data);
+                                               }
+                                               cur = g_list_next (cur);
+                                       }
+                               }
+
+                       }
+               }
+               PRINT_FUNC ("\n");
+       }
+}
+
+static void
+print_rspamd_result (struct rspamd_result *res)
+{
+       GHashTableIter                   it;
+       gpointer                         k, v;
+
+       g_assert (res != 0);
+
+       if (tty) {
+               printf ("\033[1m");
+       }
+       PRINT_FUNC ("Results for host: %s\n\n", connect_str);
+       if (tty) {
+               printf ("\033[0m");
+       }
+       g_hash_table_foreach (res->metrics, show_metric_result, NULL);
+       /* Show other headers */
+       g_hash_table_iter_init (&it, res->headers);
+       PRINT_FUNC ("\n");
+       while (g_hash_table_iter_next (&it, &k, &v)) {
+               if (tty) {
+                       PRINT_FUNC ("\033[1m%s:\033[0m %s\n", k, v);
+               }
+               else {
+                       PRINT_FUNC ("%s: %s\n", k, v);
+               }
+       }
+       PRINT_FUNC ("\n");
+}
+
+static void
+add_options (GHashTable *opts)
+{
+       if (ip != NULL) {
+               g_hash_table_insert (opts, "Ip", ip);
+       }
+       if (pass_all) {
+               g_hash_table_insert (opts, "Pass", "all");
+       }
+}
+
+/*
+ * Scan STDIN
+ */
+static void
+scan_rspamd_stdin ()
+{
+       gchar                           *in_buf;
+
+       gint                             r = 0, len;
+       GError                          *err = NULL;
+       struct rspamd_result            *res;
+       GHashTable                      *opts;
+
+       /* Init options hash */
+       opts = g_hash_table_new (g_str_hash, g_str_equal);
+       add_options (opts);
+       /* Add server */
+       add_rspamd_server (FALSE);
+
+       /* Allocate input buffer */
+       len = BUFSIZ;
+       in_buf = g_malloc (len);
+
+       /* Read stdin */
+       while (!feof (stdin)) {
+               r += fread (in_buf + r, 1, len - r, stdin);
+               if (len - r < len / 2) {
+                       /* Grow buffer */
+                       len *= 2;
+                       in_buf = g_realloc (in_buf, len);
+               }
+       }
+       res = rspamd_scan_memory (in_buf, r, opts, &err);
+       g_hash_table_destroy (opts);
+       if (err != NULL) {
+               fprintf (stderr, "cannot scan message: %s\n", err->message);
+               exit (EXIT_FAILURE);
+       }
+       print_rspamd_result (res);
+}
+
+static void
+scan_rspamd_file (const gchar *file)
+{
+       GError                          *err = NULL;
+       struct rspamd_result            *res;
+       GHashTable                      *opts;
+
+       /* Init options hash */
+       opts = g_hash_table_new (g_str_hash, g_str_equal);
+       add_options (opts);
+
+       /* Add server */
+       add_rspamd_server (FALSE);
+
+       res = rspamd_scan_file (file, opts, &err);
+       g_hash_table_destroy (opts);
+       if (err != NULL) {
+               fprintf (stderr, "cannot scan message: %s\n", err->message);
+               exit (EXIT_FAILURE);
+       }
+       print_rspamd_result (res);
+}
+
+gint
+main (gint argc, gchar **argv, gchar **env)
+{
+       enum rspamc_command             cmd;
+       gint                            i;
+
+       rspamd_client_init ();
+
+       read_cmd_line (&argc, &argv);
+       tty = isatty (STDOUT_FILENO);
+       /* Now read other args from argc and argv */
+       if (argc == 1) {
+               /* No args, just read stdin */
+               scan_rspamd_stdin ();
+       }
+       else if (argc == 2) {
+               /* One argument is whether command or filename */
+               if ((cmd = check_rspamc_command (argv[1])) != RSPAMC_COMMAND_UNKNOWN) {
+                       /* In case of command read stdin */
+                       if (cmd == RSPAMC_COMMAND_SYMBOLS) {
+                               scan_rspamd_stdin ();
+                       }
+                       else {
+                               /* XXX: implement this */
+                       }
+               }
+               else {
+                       scan_rspamd_file (argv[1]);
+               }
+       }
+       else {
+               if ((cmd = check_rspamc_command (argv[1])) != RSPAMC_COMMAND_UNKNOWN) {
+                       /* In case of command read arguments starting from 2 */
+                       for (i = 2; i < argc; i ++) {
+                               if (cmd == RSPAMC_COMMAND_SYMBOLS) {
+                                       scan_rspamd_file (argv[i]);
+                               }
+                               else {
+                                       /* XXX: implement this */
+                               }
+                       }
+               }
+               else {
+                       for (i = 1; i < argc; i ++) {
+                               scan_rspamd_file (argv[i]);
+                       }
+               }
+       }
+
+
+       return 0;
+}