]> source.dussan.org Git - rspamd.git/commitdiff
* Implement controller interface inside librspamdclient/rspamc
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Tue, 1 Feb 2011 19:01:19 +0000 (22:01 +0300)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Tue, 1 Feb 2011 19:01:19 +0000 (22:01 +0300)
* REMOVE perl client (at last)
Fixed some stupid moments in a controller's protocol

Change version to 0.3.5

CMakeLists.txt
lib/librspamdclient.c
lib/librspamdclient.h
src/client/rspamc.c
src/controller.c
src/plugins/fuzzy_check.c

index c47ccec3688b4cd8e5d4d2ed60ea23a2c7cbaa91..3d01c636a2365856457d2974eb3b1cde782abc82 100644 (file)
@@ -7,7 +7,7 @@ PROJECT(rspamd C)
 
 SET(RSPAMD_VERSION_MAJOR 0)
 SET(RSPAMD_VERSION_MINOR 3)
-SET(RSPAMD_VERSION_PATCH 4)
+SET(RSPAMD_VERSION_PATCH 5)
 
 
 SET(RSPAMD_VERSION         "${RSPAMD_VERSION_MAJOR}.${RSPAMD_VERSION_MINOR}.${RSPAMD_VERSION_PATCH}")
@@ -19,6 +19,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.6.3 FATAL_ERROR)
 
 OPTION(DEBUG_MODE          "Enable debug output [default: ON]"                  ON)
 OPTION(ENABLE_OPTIMIZATION "Enable optimization [default: OFF]"                 OFF)
+OPTION(ENABLE_PERL         "Enable perl client API [default: OFF]"              OFF)
 OPTION(SKIP_RELINK_RPATH   "Skip relinking and full RPATH for the install tree" OFF)
 OPTION(ENABLE_REDIRECTOR   "Enable redirector install [default: OFF]"           OFF)
 OPTION(ENABLE_PROFILING    "Enable profiling [default: OFF]"                    OFF)
@@ -579,9 +580,6 @@ SET(RSPAMDSRC       src/modules.c
                                src/view.c
                                src/worker.c)
 
-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/client)
@@ -652,7 +650,7 @@ ADD_CUSTOM_COMMAND(OUTPUT src/modules.c
                                        COMMAND ../utils/gen-modules.sh ${PLUGINSSRC}
                                        WORKING_DIRECTORY src)
 
-IF(PERL_EXECUTABLE)
+IF(ENABLE_PERL MATCHES "ON")
        ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_SOURCE_DIR}/perl/Makefile 
                                                DEPENDS ${CMAKE_SOURCE_DIR}/perl/Makefile.PL
                                                COMMAND ${PERL_EXECUTABLE} ./Makefile.PL DESTDIR=${DESTDIR} PREFIX=${PREFIX} INSTALLMAN3DIR=${MAN_PREFIX}/man3
@@ -664,11 +662,10 @@ IF(PERL_EXECUTABLE)
                                                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/perl
                                                VERBATIM)
        
-ENDIF(PERL_EXECUTABLE)
+ENDIF(ENABLE_PERL MATCHES "ON")
 
 CONFIGURE_FILE(config.h.in src/config.h)
 CONFIGURE_FILE(contrib/exim/local_scan.c.in contrib/exim/local_scan_rspamd.c @ONLY)
-CONFIGURE_FILE(rspamc.pl.in rspamc.pl @ONLY)
 CONFIGURE_FILE(rspamd.xml.sample conf/rspamd.xml.sample @ONLY)
 
 ######################### LINK SECTION ###############################
@@ -687,9 +684,9 @@ IF(ENABLE_PERL MATCHES "ON")
        ENDIF(PERL_DYNALOADER)
 
 ENDIF(ENABLE_PERL MATCHES "ON")
-IF(PERL_EXECUTABLE)
+IF(ENABLE_PERL MATCHES "ON")
        ADD_DEPENDENCIES(rspamd perlmodule)
-ENDIF(PERL_EXECUTABLE)
+ENDIF(ENABLE_PERL MATCHES "ON")
 
 TARGET_LINK_LIBRARIES(rspamd rspamd_lua)
 TARGET_LINK_LIBRARIES(rspamd "${LUA_LIBRARY}")
@@ -763,9 +760,7 @@ ENDIF(ENABLE_STATIC MATCHES "ON")
 
 # Binaries
 INSTALL(PROGRAMS rspamd-${RSPAMD_VERSION} DESTINATION bin RENAME rspamd)
-IF(PERL_EXECUTABLE)
-       INSTALL(PROGRAMS rspamc.pl DESTINATION bin RENAME rspamc)
-ENDIF(PERL_EXECUTABLE)
+INSTALL(PROGRAMS src/client/rspamc DESTINATION bin)
 
 
 # Configs
@@ -812,14 +807,14 @@ FOREACH(LUA_CONF ${LUA_CONFIGS})
 ENDFOREACH(LUA_CONF)
 
 # Perl lib
-IF(PERL_EXECUTABLE)
+IF(ENABLE_PERL MATCHES "ON")
        INSTALL(CODE "EXECUTE_PROCESS(COMMAND make install WORKING_DIRECTORY perl)")
-ENDIF(PERL_EXECUTABLE)
+ENDIF(ENABLE_PERL MATCHES "ON")
 
 # Redirector
-IF(ENABLE_REDIRECTOR MATCHES "ON" AND PERL_EXECUTABLE)
+IF(ENABLE_REDIRECTOR MATCHES "ON" AND ENABLE_PERL MATCHES "ON")
        INSTALL(PROGRAMS utils/redirector.pl DESTINATION bin RENAME rspamd-redirector)
-ENDIF(ENABLE_REDIRECTOR MATCHES "ON" AND PERL_EXECUTABLE)
+ENDIF(ENABLE_REDIRECTOR MATCHES "ON" AND ENABLE_PERL MATCHES "ON")
 
 # Start scripts
 IF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND NOT BUILD_PORT)
index d7568eb6245e746bd208b7010493a772b42df820..0b32e853d21ba47cfc0f028b0eaa26c9576619f8 100644 (file)
@@ -761,10 +761,10 @@ rspamd_sendfile (gint sock, gint fd, GError **err)
        off_t                           off = 0;
 #  if defined(FREEBSD)
        /* FreeBSD version */
-       if (sendfile (sock, fd, 0, 0, NULL, &off, 0) != 0) {
+       if (sendfile (fd, sock, 0, 0, NULL, &off, 0) != 0) {
 #  elif defined(DARWIN)
        /* Darwin version */
-       if (sendfile (sock, fd, 0, &off, NULL, 0) != 0) {
+       if (sendfile (fd, sock, 0, &off, NULL, 0) != 0) {
 #  endif
                goto err;
        }
@@ -775,7 +775,7 @@ rspamd_sendfile (gint sock, gint fd, GError **err)
 
        fstat (fd, &st);
        /* Linux version */
-       r = sendfile (fd, sock, &off, st.st_size);
+       r = sendfile (sock, fd, &off, st.st_size);
        if (r == -1) {
                goto err;
        }
@@ -845,6 +845,147 @@ rspamd_free_connection (struct rspamd_connection *c)
        g_free (c);
 }
 
+/*
+ * Send a single command to controller and get reply
+ */
+static GString *
+rspamd_send_controller_command (struct rspamd_connection *c, const gchar *line, gsize len, gint fd, GError **err)
+{
+       GString                        *res = NULL;
+       static gchar                    tmpbuf[BUFSIZ];
+       gint                            r = 0;
+       static const gchar              end_marker[] = "END\r\n";
+
+       /* Set blocking for writing */
+       make_socket_blocking (c->socket);
+       if (write (c->socket, line, len) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Write error: %s",
+                                       strerror (errno));
+               }
+               return NULL;
+       }
+       if (fd != -1) {
+               if (!rspamd_sendfile (c->socket, fd, err)) {
+                       return NULL;
+               }
+       }
+       /* Now set non-blocking mode and read buffer till END\r\n marker */
+       make_socket_nonblocking (c->socket);
+       /* Poll socket */
+       do {
+               if (poll_sync_socket (c->socket, client->read_timeout, POLL_IN) == -1) {
+                       if (*err == NULL) {
+                               *err = g_error_new (G_RSPAMD_ERROR, errno, "Cannot read reply from controller %s: %s",
+                                               c->server->name, strerror (errno));
+                       }
+                       upstream_fail (&c->server->up, c->connection_time);
+                       return NULL;
+               }
+               if ((r = read (c->socket, tmpbuf, sizeof (tmpbuf))) > 0) {
+                       /* Check the end of the buffer for END marker */
+                       if (r >= sizeof (end_marker) - 1 &&
+                                       memcmp (tmpbuf + r - sizeof (end_marker) + 1, end_marker, sizeof (end_marker) - 1) == 0) {
+                               r -= sizeof (end_marker) - 1;
+                               /* Copy the rest to the result string */
+                               if (res == NULL) {
+                                       res = g_string_new_len (tmpbuf, r);
+                                       return res;
+                               }
+                               else {
+                                       /* Append data to string */
+                                       if (r > 0) {
+                                               res = g_string_append_len (res, tmpbuf, r);
+                                       }
+                                       return res;
+                               }
+                       }
+                       else {
+                               /* Store data inside res */
+                               if (res == NULL) {
+                                       res = g_string_new_len (tmpbuf, r);
+                               }
+                               else {
+                                       /* Append data to string */
+                                       res = g_string_append_len (res, tmpbuf, r);
+                               }
+                       }
+               }
+       } while (r > 0);
+
+       /* Incomplete reply, so store error */
+       if (*err == NULL) {
+               *err = g_error_new (G_RSPAMD_ERROR, errno, "Cannot read reply from controller %s: %s",
+                               c->server->name, strerror (errno));
+       }
+       upstream_fail (&c->server->up, c->connection_time);
+       return NULL;
+}
+
+/*
+ * Authenticate on the controller
+ */
+static gboolean
+rspamd_controller_auth (struct rspamd_connection *c, const gchar *password, GError **err)
+{
+       gchar                           outbuf[BUFSIZ];
+       static const gchar              success_str[] = "password accepted";
+       gint                            r;
+       GString                        *in;
+
+       r = rspamd_snprintf (outbuf, sizeof (outbuf), "password %s\r\n", password);
+       in = rspamd_send_controller_command (c, outbuf, r, -1, err);
+
+       if (in == NULL) {
+               return FALSE;
+       }
+
+       if (in->len >= sizeof (success_str) - 1 &&
+               memcmp (in->str, success_str, sizeof (success_str) - 1) == 0) {
+               g_string_free (in, TRUE);
+               return TRUE;
+       }
+
+       g_string_free (in, TRUE);
+       return FALSE;
+}
+
+/*
+ * Read greeting from the controller
+ */
+static gboolean
+rspamd_read_controller_greeting (struct rspamd_connection *c, GError **err)
+{
+       gchar                           inbuf[BUFSIZ];
+       gint                            r;
+       static const gchar              greeting_str[] = "Rspamd";
+
+       if (poll_sync_socket (c->socket, client->read_timeout, POLL_IN) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Cannot read reply from controller %s: %s",
+                                       c->server->name, strerror (errno));
+               }
+               upstream_fail (&c->server->up, c->connection_time);
+               return FALSE;
+       }
+       if ((r = read (c->socket, inbuf, sizeof (inbuf))) > 0) {
+               if (r >= sizeof (greeting_str) - 1 &&
+                               memcmp (inbuf, greeting_str, sizeof (greeting_str) - 1) == 0) {
+                       return TRUE;
+               }
+       }
+       else {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Cannot read reply from controller %s: %s",
+                                       c->server->name, strerror (errno));
+               }
+               upstream_fail (&c->server->up, c->connection_time);
+               return FALSE;
+       }
+
+       return FALSE;
+}
+
 /** Public API **/
 
 /*
@@ -959,6 +1100,7 @@ rspamd_scan_memory (const guchar *message, gsize length, GHashTable *headers, GE
        /* Read result cycle */
        while (read_rspamd_reply_line (c, err));
 
+       upstream_ok (&c->server->up, c->connection_time);
        return res;
 }
 
@@ -1031,6 +1173,7 @@ rspamd_scan_fd (int fd, GHashTable *headers, GError **err)
        /* Read result cycle */
        while (read_rspamd_reply_line (c, err));
 
+       upstream_ok (&c->server->up, c->connection_time);
        return res;
 }
 
@@ -1038,30 +1181,324 @@ 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)
+rspamd_learn_memory (const guchar *message, gsize length, const gchar *symbol, const gchar *password, GError **err)
 {
+       struct rspamd_connection             *c;
+       GString                              *in;
+       gchar                                *outbuf;
+       guint                                 r;
+       static const gchar                    ok_str[] = "learn ok";
+
        g_assert (client != NULL);
 
+       /* Connect to server */
+       c = rspamd_connect_random_server (TRUE, err);
+
+       if (c == NULL) {
+               return FALSE;
+       }
+
+       /* Read greeting */
+       if (! rspamd_read_controller_greeting(c, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid greeting");
+               }
+               return FALSE;
+       }
+       /* Perform auth */
+       if (! rspamd_controller_auth (c, password, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Authentication error");
+               }
+               return FALSE;
+       }
+
+       r = length + sizeof ("learn %s %uz\r\n") + strlen (symbol) + sizeof ("4294967296");
+       outbuf = g_malloc (r);
+       r = rspamd_snprintf (outbuf, r, "learn %s %uz\r\n%s", symbol, length, message);
+       in = rspamd_send_controller_command (c, outbuf, r, -1, err);
+       g_free (outbuf);
+       if (in == NULL) {
+               return FALSE;
+       }
+
+       /* Search for string learn ok */
+       if (in->len > sizeof (ok_str) - 1 && memcmp (in->str, ok_str, sizeof (ok_str) - 1) == 0) {
+               upstream_ok (&c->server->up, c->connection_time);
+               return TRUE;
+       }
+       else {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Bad reply: %s", in->str);
+               }
+       }
+       return FALSE;
 }
 
 /*
  * Learn message from file
  */
 gboolean
-rspamd_learn_file (const guchar *filename, const gchar *symbol, GError **err)
+rspamd_learn_file (const guchar *filename, const gchar *symbol, const gchar *password, 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 FALSE;
+       }
+
+       return rspamd_learn_fd (fd, symbol, password, err);
 }
 
 /*
  * Learn message from fd
  */
 gboolean
-rspamd_learn_fd (int fd, const gchar *symbol, GError **err)
+rspamd_learn_fd (int fd, const gchar *symbol, const gchar *password, GError **err)
 {
+       struct rspamd_connection             *c;
+       GString                              *in;
+       gchar                                *outbuf;
+       guint                                 r;
+       struct stat                           st;
+       static const gchar                    ok_str[] = "learn ok";
+
        g_assert (client != NULL);
 
+       /* Connect to server */
+       c = rspamd_connect_random_server (TRUE, err);
+
+       if (c == NULL) {
+               return FALSE;
+       }
+
+       /* Read greeting */
+       if (! rspamd_read_controller_greeting(c, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid greeting");
+               }
+               return FALSE;
+       }
+       /* Perform auth */
+       if (! rspamd_controller_auth (c, password, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Authentication error");
+               }
+               return FALSE;
+       }
+
+       /* Get length */
+       if (fstat (fd, &st) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Stat error: %s",
+                                       strerror (errno));
+               }
+               return FALSE;
+       }
+       r = sizeof ("learn %s %uz\r\n") + strlen (symbol) + sizeof ("4294967296");
+       outbuf = g_malloc (r);
+       r = rspamd_snprintf (outbuf, r, "learn %s %uz\r\n", symbol, st.st_size);
+       in = rspamd_send_controller_command (c, outbuf, r, fd, err);
+       g_free (outbuf);
+       if (in == NULL) {
+               return FALSE;
+       }
+
+       /* Search for string learn ok */
+       if (in->len > sizeof (ok_str) - 1 && memcmp (in->str, ok_str, sizeof (ok_str) - 1) == 0) {
+               upstream_ok (&c->server->up, c->connection_time);
+               return TRUE;
+       }
+       else {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Bad reply: %s", in->str);
+               }
+       }
+
+       return FALSE;
+}
+
+/*
+ * Learn message fuzzy from memory
+ */
+gboolean
+rspamd_fuzzy_memory (const guchar *message, gsize length, const gchar *password, gint weight, gint flag, gboolean delete, GError **err)
+{
+       struct rspamd_connection             *c;
+       GString                              *in;
+       gchar                                *outbuf;
+       guint                                 r;
+       static const gchar                    ok_str[] = "OK";
+
+       g_assert (client != NULL);
+
+       /* Connect to server */
+       c = rspamd_connect_random_server (TRUE, err);
+
+       if (c == NULL) {
+               return FALSE;
+       }
+
+       /* Read greeting */
+       if (! rspamd_read_controller_greeting(c, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid greeting");
+               }
+               return FALSE;
+       }
+       /* Perform auth */
+       if (! rspamd_controller_auth (c, password, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Authentication error");
+               }
+               return FALSE;
+       }
+
+       r = length + sizeof ("fuzzy_add %uz %d %d\r\n") + sizeof ("4294967296") * 3;
+       outbuf = g_malloc (r);
+       if (delete) {
+               r = rspamd_snprintf (outbuf, r, "fuzzy_del %uz %d %d\r\n%s", length, weight, flag, message);
+       }
+       else {
+               r = rspamd_snprintf (outbuf, r, "fuzzy_add %uz %d %d\r\n%s", length, weight, flag, message);
+       }
+       in = rspamd_send_controller_command (c, outbuf, r, -1, err);
+       g_free (outbuf);
+       if (in == NULL) {
+               return FALSE;
+       }
+
+       /* Search for string "OK" */
+       if (in->len > sizeof (ok_str) - 1 && memcmp (in->str, ok_str, sizeof (ok_str) - 1) == 0) {
+               upstream_ok (&c->server->up, c->connection_time);
+               return TRUE;
+       }
+       else {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Bad reply: %s", in->str);
+               }
+       }
+       return FALSE;
+}
+
+/*
+ * Learn message fuzzy from file
+ */
+gboolean
+rspamd_fuzzy_file (const guchar *filename, const gchar *password, gint weight, gint flag, gboolean delete, 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 FALSE;
+       }
+
+       return rspamd_fuzzy_fd (fd, password, weight, flag, delete, err);
+}
+
+/*
+ * Learn message fuzzy from fd
+ */
+gboolean
+rspamd_fuzzy_fd (int fd, const gchar *password, gint weight, gint flag, gboolean delete, GError **err)
+{
+       struct rspamd_connection             *c;
+       GString                              *in;
+       gchar                                *outbuf;
+       guint                                 r;
+       struct stat                           st;
+       static const gchar                    ok_str[] = "OK";
+
+       g_assert (client != NULL);
+
+       /* Connect to server */
+       c = rspamd_connect_random_server (TRUE, err);
+
+       if (c == NULL) {
+               return FALSE;
+       }
+
+       /* Read greeting */
+       if (! rspamd_read_controller_greeting(c, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Invalid greeting");
+               }
+               return FALSE;
+       }
+       /* Perform auth */
+       if (! rspamd_controller_auth (c, password, err)) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Authentication error");
+               }
+               return FALSE;
+       }
+       /* Get length */
+       if (fstat (fd, &st) == -1) {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Stat error: %s",
+                                       strerror (errno));
+               }
+               return FALSE;
+       }
+       r = sizeof ("fuzzy_add %uz %d %d\r\n") + sizeof ("4294967296") * 3;
+       outbuf = g_malloc (r);
+       if (delete) {
+               r = rspamd_snprintf (outbuf, r, "fuzzy_del %uz %d %d\r\n", st.st_size, weight, flag);
+       }
+       else {
+               r = rspamd_snprintf (outbuf, r, "fuzzy_add %uz %d %d\r\n", st.st_size, weight, flag);
+       }
+       in = rspamd_send_controller_command (c, outbuf, r, fd, err);
+
+       g_free (outbuf);
+       if (in == NULL) {
+               return FALSE;
+       }
+
+       /* Search for string "OK" */
+       if (in->len > sizeof (ok_str) - 1 && memcmp (in->str, ok_str, sizeof (ok_str) - 1) == 0) {
+               upstream_ok (&c->server->up, c->connection_time);
+               return TRUE;
+       }
+       else {
+               if (*err == NULL) {
+                       *err = g_error_new (G_RSPAMD_ERROR, errno, "Bad reply: %s", in->str);
+               }
+       }
+       return FALSE;
+}
+
+GString *
+rspamd_get_stat (GError **err)
+{
+       struct rspamd_connection             *c;
+       GString                              *res;
+       static const gchar                    outcmd[] = "stat\r\n";
+
+       g_assert (client != NULL);
+
+       /* Connect to server */
+       c = rspamd_connect_random_server (TRUE, err);
+
+       if (c == NULL) {
+               return NULL;
+       }
+
+       res = rspamd_send_controller_command (c, outcmd, strlen (outcmd), -1, err);
+
+       return res;
 }
 
 /*
@@ -1071,7 +1508,11 @@ void
 rspamd_free_result (struct rspamd_result *result)
 {
        g_assert (client != NULL);
+       g_assert (result != NULL);
 
+       g_hash_table_destroy (result->headers);
+       g_hash_table_destroy (result->metrics);
+       rspamd_free_connection (result->conn);
 }
 
 /*
@@ -1081,4 +1522,7 @@ void
 rspamd_client_close (void)
 {
        g_assert (client != NULL);
+
+       g_free (client);
+       client = NULL;
 }
index fc5a6f2ca1523d644990ff7b77e31187fce8b3d0..2d2efc16425399c2806417098470b34869edf498 100644 (file)
@@ -69,17 +69,37 @@ 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);
+gboolean rspamd_learn_memory (const guchar *message, gsize length, const gchar *symbol, const gchar *password, GError **err);
 
 /*
  * Learn message from file
  */
-gboolean rspamd_learn_file (const guchar *filename, const gchar *symbol, GError **err);
+gboolean rspamd_learn_file (const guchar *filename, const gchar *symbol, const gchar *password, GError **err);
 
 /*
  * Learn message from fd
  */
-gboolean rspamd_learn_fd (int fd, const gchar *symbol, GError **err);
+gboolean rspamd_learn_fd (int fd, const gchar *symbol, const gchar *password, GError **err);
+
+/*
+ * Learn message fuzzy from memory
+ */
+gboolean rspamd_fuzzy_memory (const guchar *message, gsize length, const gchar *password, gint weight, gint flag, gboolean delete, GError **err);
+
+/*
+ * Learn message fuzzy from file
+ */
+gboolean rspamd_fuzzy_file (const guchar *filename, const gchar *password, gint weight, gint flag, gboolean delete, GError **err);
+
+/*
+ * Learn message fuzzy from fd
+ */
+gboolean rspamd_fuzzy_fd (int fd, const gchar *password, gint weight, gint flag, gboolean delete, GError **err);
+
+/*
+ * Get statistic from server
+ */
+GString *rspamd_get_stat (GError **err);
 
 /*
  * Free results
index 1bae2970cdce8e27edb934227cfdc3a05fba385a..874c720742b948973a94eab926a469328ce67650 100644 (file)
@@ -34,7 +34,8 @@ static gchar                   *connect_str = "localhost";
 static gchar                   *password;
 static gchar                   *statfile;
 static gchar                   *ip;
-static gdouble                  weight;
+static gint                     weight = 1;
+static gint                     flag;
 static gboolean                 pass_all;
 static gboolean                 tty = FALSE;
 
@@ -43,7 +44,8 @@ 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 },
+               { "weight", 'w', 0, G_OPTION_ARG_INT, &weight, "Weight for fuzzy operations", NULL },
+               { "flag", 'f', 0, G_OPTION_ARG_INT, &flag, "Flag 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 }
@@ -52,7 +54,10 @@ static GOptionEntry entries[] =
 enum rspamc_command {
        RSPAMC_COMMAND_UNKNOWN = 0,
        RSPAMC_COMMAND_SYMBOLS,
-       RSPAMC_COMMAND_LEARN
+       RSPAMC_COMMAND_LEARN,
+       RSPAMC_COMMAND_FUZZY_ADD,
+       RSPAMC_COMMAND_FUZZY_DEL,
+       RSPAMC_COMMAND_STAT
 };
 
 /*
@@ -93,6 +98,15 @@ check_rspamc_command (const gchar *cmd)
        else if (g_ascii_strcasecmp (cmd, "LEARN") == 0) {
                return RSPAMC_COMMAND_LEARN;
        }
+       else if (g_ascii_strcasecmp (cmd, "FUZZY_ADD") == 0) {
+               return RSPAMC_COMMAND_FUZZY_ADD;
+       }
+       else if (g_ascii_strcasecmp (cmd, "FUZZY_DEL") == 0) {
+               return RSPAMC_COMMAND_FUZZY_DEL;
+       }
+       else if (g_ascii_strcasecmp (cmd, "STAT") == 0) {
+               return RSPAMC_COMMAND_STAT;
+       }
 
        return RSPAMC_COMMAND_UNKNOWN;
 }
@@ -282,6 +296,7 @@ scan_rspamd_stdin ()
                exit (EXIT_FAILURE);
        }
        print_rspamd_result (res);
+       rspamd_free_result (res);
 }
 
 static void
@@ -302,9 +317,197 @@ scan_rspamd_file (const gchar *file)
        g_hash_table_destroy (opts);
        if (err != NULL) {
                fprintf (stderr, "cannot scan message: %s\n", err->message);
-               exit (EXIT_FAILURE);
        }
        print_rspamd_result (res);
+       if (res) {
+               rspamd_free_result (res);
+       }
+}
+
+static void
+learn_rspamd_stdin ()
+{
+       gchar                           *in_buf;
+       gint                             r = 0, len;
+       GError                          *err = NULL;
+
+       if (password == NULL || statfile == NULL) {
+               fprintf (stderr, "cannot learn message without password and symbol name\n");
+               exit (EXIT_FAILURE);
+       }
+       /* Add server */
+       add_rspamd_server (TRUE);
+
+       /* 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);
+               }
+       }
+       if (!rspamd_learn_memory (in_buf, r, statfile, password, &err)) {
+               if (err != NULL) {
+                       fprintf (stderr, "cannot learn message: %s\n", err->message);
+               }
+               else {
+                       fprintf (stderr, "cannot learn message\n");
+               }
+               exit (EXIT_FAILURE);
+       }
+       else {
+               if (tty) {
+                       printf ("\033[1m");
+               }
+               PRINT_FUNC ("Results for host: %s: learn ok\n", connect_str);
+               if (tty) {
+                       printf ("\033[0m");
+               }
+       }
+}
+
+static void
+learn_rspamd_file (const gchar *file)
+{
+       GError                          *err = NULL;
+
+       if (password == NULL || statfile == NULL) {
+               fprintf (stderr, "cannot learn message without password and symbol name\n");
+               exit (EXIT_FAILURE);
+       }
+       /* Add server */
+       add_rspamd_server (TRUE);
+
+       if (!rspamd_learn_file (file, statfile, password, &err)) {
+               if (err != NULL) {
+                       fprintf (stderr, "cannot learn message: %s\n", err->message);
+               }
+               else {
+                       fprintf (stderr, "cannot learn message\n");
+               }
+       }
+       else {
+               if (tty) {
+                       printf ("\033[1m");
+               }
+               PRINT_FUNC ("learn ok\n");
+               if (tty) {
+                       printf ("\033[0m");
+               }
+       }
+}
+
+static void
+fuzzy_rspamd_stdin (gboolean delete)
+{
+       gchar                           *in_buf;
+       gint                             r = 0, len;
+       GError                          *err = NULL;
+
+       if (password == NULL) {
+               fprintf (stderr, "cannot learn message without password\n");
+               exit (EXIT_FAILURE);
+       }
+       /* Add server */
+       add_rspamd_server (TRUE);
+
+       /* 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);
+               }
+       }
+       if (!rspamd_fuzzy_memory (in_buf, r, password, weight, flag, delete, &err)) {
+               if (err != NULL) {
+                       fprintf (stderr, "cannot learn message: %s\n", err->message);
+               }
+               else {
+                       fprintf (stderr, "cannot learn message\n");
+               }
+               exit (EXIT_FAILURE);
+       }
+       else {
+               if (tty) {
+                       printf ("\033[1m");
+               }
+               PRINT_FUNC ("Results for host: %s: learn ok\n", connect_str);
+               if (tty) {
+                       printf ("\033[0m");
+               }
+       }
+}
+
+static void
+fuzzy_rspamd_file (const gchar *file, gboolean delete)
+{
+       GError                          *err = NULL;
+
+       if (password == NULL) {
+               fprintf (stderr, "cannot learn message without password\n");
+               exit (EXIT_FAILURE);
+       }
+       /* Add server */
+       add_rspamd_server (TRUE);
+
+       if (!rspamd_fuzzy_file (file, password, weight, flag, delete, &err)) {
+               if (err != NULL) {
+                       fprintf (stderr, "cannot learn message: %s\n", err->message);
+               }
+               else {
+                       fprintf (stderr, "cannot learn message\n");
+               }
+       }
+       else {
+               if (tty) {
+                       printf ("\033[1m");
+               }
+               PRINT_FUNC ("learn ok\n");
+               if (tty) {
+                       printf ("\033[0m");
+               }
+       }
+}
+
+static void
+rspamd_do_stat ()
+{
+       GError                          *err = NULL;
+       GString                         *res;
+
+       /* Add server */
+       add_rspamd_server (TRUE);
+
+       res = rspamd_get_stat (&err);
+       if (res == NULL) {
+               if (err != NULL) {
+                       fprintf (stderr, "cannot learn message: %s\n", err->message);
+               }
+               else {
+                       fprintf (stderr, "cannot learn message\n");
+               }
+               exit (EXIT_FAILURE);
+       }
+       if (tty) {
+               printf ("\033[1m");
+       }
+       PRINT_FUNC ("Results for host: %s\n\n", connect_str);
+       if (tty) {
+               printf ("\033[0m");
+       }
+       res = g_string_append_c (res, '\0');
+       printf ("%s\n", res->str);
 }
 
 gint
@@ -326,11 +529,24 @@ main (gint argc, gchar **argv, gchar **env)
                /* 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) {
+                       switch (cmd) {
+                       case RSPAMC_COMMAND_SYMBOLS:
                                scan_rspamd_stdin ();
-                       }
-                       else {
-                               /* XXX: implement this */
+                               break;
+                       case RSPAMC_COMMAND_LEARN:
+                               learn_rspamd_stdin ();
+                               break;
+                       case RSPAMC_COMMAND_FUZZY_ADD:
+                               fuzzy_rspamd_stdin (FALSE);
+                               break;
+                       case RSPAMC_COMMAND_FUZZY_DEL:
+                               fuzzy_rspamd_stdin (TRUE);
+                               break;
+                       case RSPAMC_COMMAND_STAT:
+                               rspamd_do_stat ();
+                       default:
+                               fprintf (stderr, "invalid arguments\n");
+                               exit (EXIT_FAILURE);
                        }
                }
                else {
@@ -341,11 +557,29 @@ main (gint argc, gchar **argv, gchar **env)
                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]);
+                               if (tty) {
+                                       printf ("\033[1m");
                                }
-                               else {
-                                       /* XXX: implement this */
+                               PRINT_FUNC ("Results for file: %s\n\n", argv[i]);
+                               if (tty) {
+                                       printf ("\033[0m");
+                               }
+                               switch (cmd) {
+                               case RSPAMC_COMMAND_SYMBOLS:
+                                       scan_rspamd_file (argv[i]);
+                                       break;
+                               case RSPAMC_COMMAND_LEARN:
+                                       learn_rspamd_file (argv[i]);
+                                       break;
+                               case RSPAMC_COMMAND_FUZZY_ADD:
+                                       fuzzy_rspamd_file (argv[i], FALSE);
+                                       break;
+                               case RSPAMC_COMMAND_FUZZY_DEL:
+                                       fuzzy_rspamd_file (argv[i], TRUE);
+                                       break;
+                               default:
+                                       fprintf (stderr, "invalid arguments\n");
+                                       exit (EXIT_FAILURE);
                                }
                        }
                }
@@ -356,6 +590,7 @@ main (gint argc, gchar **argv, gchar **env)
                }
        }
 
+       rspamd_client_close ();
 
        return 0;
 }
index a39ec21cb31b6118ccc0706d55ed7ee894a243d4..d204a1aa0767149a308f8df33d06395514a2fda8 100644 (file)
@@ -836,7 +836,7 @@ controller_read_socket (f_str_t * in, void *arg)
                
                /* Handle messages without text */
                if (tokens == NULL) {
-                       i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, no tokens can be extracted (no text data)" CRLF);
+                       i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, no tokens can be extracted (no text data)" CRLF END);
                        msg_info ("learn failed for message <%s>, no tokens to extract", task->message_id);
                        free_task (task, FALSE);
                        if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
@@ -860,12 +860,12 @@ controller_read_socket (f_str_t * in, void *arg)
                                                                                                                                session->learn_symbol, tokens, session->in_class, &sum,
                                                                                                                                session->learn_multiplier, &err)) {
                        if (err) {
-                               i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF, err->message);
+                               i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, learn classifier error: %s" CRLF END, err->message);
                                msg_info ("learn failed for message <%s>, learn error: %s", task->message_id, err->message);
                                g_error_free (err);
                        }
                        else {
-                               i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, unknown learn classifier error" CRLF);
+                               i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn failed, unknown learn classifier error" CRLF END);
                                msg_info ("learn failed for message <%s>, unknown learn error", task->message_id);
                        }
                        free_task (task, FALSE);
@@ -882,7 +882,7 @@ controller_read_socket (f_str_t * in, void *arg)
                                task->message_id, session->learn_symbol, sum);
                statfile_pool_plan_invalidate (session->worker->srv->statfile_pool, DEFAULT_STATFILE_INVALIDATE_TIME, DEFAULT_STATFILE_INVALIDATE_JITTER);
                free_task (task, FALSE);
-               i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn ok, sum weight: %.2f" CRLF, sum);
+               i = rspamd_snprintf (out_buf, sizeof (out_buf), "learn ok, sum weight: %.2f" CRLF END, sum);
                if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
                        return FALSE;
                }
@@ -902,7 +902,7 @@ controller_read_socket (f_str_t * in, void *arg)
                        msg_warn ("processing of message failed");
                        free_task (task, FALSE);
                        session->state = STATE_REPLY;
-                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF);
+                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF END);
                        if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                                return FALSE;
                        }
@@ -920,7 +920,7 @@ controller_read_socket (f_str_t * in, void *arg)
                        c.begin = part->content->data;
                        c.len = part->content->len;
                        if (!session->learn_classifier->tokenizer->tokenize_func (session->learn_classifier->tokenizer, session->session_pool, &c, &tokens)) {
-                               i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, tokenizer error" CRLF);
+                               i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, tokenizer error" CRLF END);
                                free_task (task, FALSE);
                                if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
                                        return FALSE;
@@ -933,7 +933,7 @@ controller_read_socket (f_str_t * in, void *arg)
                
                /* Handle messages without text */
                if (tokens == NULL) {
-                       i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, no tokens can be extracted (no text data)" CRLF);
+                       i = rspamd_snprintf (out_buf, sizeof (out_buf), "weights failed, no tokens can be extracted (no text data)" CRLF END);
                        free_task (task, FALSE);
                        if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
                                return FALSE;
@@ -956,13 +956,14 @@ controller_read_socket (f_str_t * in, void *arg)
                        i += rspamd_snprintf (out_buf + i, sizeof (out_buf) - i, "%s: %G" CRLF, w->name, w->weight);
                        cur = g_list_next (cur);
                }
+               i += rspamd_snprintf (out_buf + i, sizeof (out_buf) - i, END);
                if (i != 0) {
                        if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
                                return FALSE;
                        }
                }
                else {
-                       if (!rspamd_dispatcher_write (session->dispatcher, "weights failed: classifier error", 0, FALSE, TRUE)) {
+                       if (!rspamd_dispatcher_write (session->dispatcher, "weights failed: classifier error" CRLF END, 0, FALSE, TRUE)) {
                                return FALSE;
                        }
                }
index e47beee170c163ab13f6f11dd4947c0c85e24867..f81d549a7df97102d8f3fd1215738ee25e31aeb3 100644 (file)
@@ -567,7 +567,7 @@ fuzzy_learn_callback (gint fd, short what, void *arg)
                else if (buf[0] == 'O' && buf[1] == 'K') {
                        msg_info ("added fuzzy hash '%s' to list: %d for message <%s>",
                                        fuzzy_to_string (session->h), session->flag, session->task->message_id);
-                       r = rspamd_snprintf (buf, sizeof (buf), "OK" CRLF);
+                       r = rspamd_snprintf (buf, sizeof (buf), "OK" CRLF "END" CRLF);
                        if (! rspamd_dispatcher_write (session->session->dispatcher, buf, r, FALSE, FALSE)) {
                                return;
                        }
@@ -575,7 +575,7 @@ fuzzy_learn_callback (gint fd, short what, void *arg)
                }
                else {
                        msg_info ("cannot add fuzzy hash for message <%s>", session->task->message_id);
-                       r = rspamd_snprintf (buf, sizeof (buf), "ERR" CRLF);
+                       r = rspamd_snprintf (buf, sizeof (buf), "ERR" CRLF "END" CRLF);
                        if (! rspamd_dispatcher_write (session->session->dispatcher, buf, r, FALSE, FALSE)) {
                                return;
                        }
@@ -591,7 +591,7 @@ fuzzy_learn_callback (gint fd, short what, void *arg)
 
   err:
        msg_err ("got error in IO with server %s:%d, %d, %s", session->server->name, session->server->port, errno, strerror (errno));
-       r = rspamd_snprintf (buf, sizeof (buf), "Error" CRLF);
+       r = rspamd_snprintf (buf, sizeof (buf), "Error" CRLF "END" CRLF);
        if (! rspamd_dispatcher_write (session->session->dispatcher, buf, r, FALSE, FALSE)) {
                return;
        }
@@ -741,7 +741,7 @@ register_fuzzy_controller_call (struct controller_session *session, struct worke
                if ((sock = make_udp_socket (&selected->addr, selected->port, FALSE, TRUE)) == -1) {
                        msg_warn ("cannot connect to %s, %d, %s", selected->name, errno, strerror (errno));
                        session->state = STATE_REPLY;
-                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "no hashes written" CRLF);
+                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "no hashes written" CRLF "END" CRLF);
                        if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                                return FALSE;
                        }
@@ -810,7 +810,7 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
                msg_warn ("processing of message failed");
                free_task (task, FALSE);
                session->state = STATE_REPLY;
-               r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF);
+               r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot process message" CRLF "END" CRLF);
                if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                        msg_warn ("write error");
                }
@@ -829,7 +829,7 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
                        if (! register_fuzzy_controller_call (session, task, part->fuzzy, cmd, value, flag, saved)) {
                                /* Cannot write hash */
                                session->state = STATE_REPLY;
-                               r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF);
+                               r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
                                if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                                        return;
                                }
@@ -853,7 +853,7 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
                                                if (! register_fuzzy_controller_call (session, task, &fake_fuzzy, cmd, value, flag, saved)) {
                                                        /* Cannot write hash */
                                                        session->state = STATE_REPLY;
-                                                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF);
+                                                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
                                                        if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                                                                return;
                                                        }
@@ -883,7 +883,7 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
                                                if (! register_fuzzy_controller_call (session, task, &fake_fuzzy, cmd, value, flag, saved)) {
                                                        /* Cannot write hash */
                                                        session->state = STATE_REPLY;
-                                                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF);
+                                                       r = rspamd_snprintf (out_buf, sizeof (out_buf), "cannot write fuzzy hash" CRLF "END" CRLF);
                                                        if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                                                                return;
                                                        }
@@ -905,7 +905,7 @@ fuzzy_process_handler (struct controller_session *session, f_str_t * in)
 
        if (*saved == 0) {
                session->state = STATE_REPLY;
-               r = rspamd_snprintf (out_buf, sizeof (out_buf), "no hashes written" CRLF);
+               r = rspamd_snprintf (out_buf, sizeof (out_buf), "no hashes written" CRLF "END" CRLF);
                if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                        return;
                }
@@ -923,7 +923,7 @@ fuzzy_controller_handler (gchar **args, struct controller_session *session, gint
        arg = args[0];
        if (!arg || *arg == '\0') {
                msg_info ("empty content length");
-               r = rspamd_snprintf (out_buf, sizeof (out_buf), "fuzzy command requires length as argument" CRLF);
+               r = rspamd_snprintf (out_buf, sizeof (out_buf), "fuzzy command requires length as argument" CRLF "END" CRLF);
                if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
                        return;
                }