summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/controller.c305
-rw-r--r--src/fstring.c3
-rw-r--r--src/main.h13
-rw-r--r--src/protocol.c7
-rw-r--r--src/protocol.h8
-rw-r--r--src/smtp.c27
-rw-r--r--src/smtp_proto.c1
7 files changed, 284 insertions, 80 deletions
diff --git a/src/controller.c b/src/controller.c
index c5aebb699..7bd90e7db 100644
--- a/src/controller.c
+++ b/src/controller.c
@@ -70,7 +70,9 @@ enum command_type {
COMMAND_HELP,
COMMAND_COUNTERS,
COMMAND_SYNC,
- COMMAND_WEIGHTS
+ COMMAND_WEIGHTS,
+ COMMAND_GET,
+ COMMAND_POST
};
struct controller_command {
@@ -106,7 +108,9 @@ static struct controller_command commands[] = {
{"counters", FALSE, COMMAND_COUNTERS},
{"sync", FALSE, COMMAND_SYNC},
{"learn_spam", TRUE, COMMAND_LEARN_SPAM},
- {"learn_ham", TRUE, COMMAND_LEARN_HAM}
+ {"learn_ham", TRUE, COMMAND_LEARN_HAM},
+ {"get", FALSE, COMMAND_GET},
+ {"post", FALSE, COMMAND_POST}
};
static GList *custom_commands = NULL;
@@ -190,6 +194,10 @@ free_session (void *ud)
}
rspamd_remove_dispatcher (session->dispatcher);
+ if (session->kwargs) {
+ g_hash_table_destroy (session->kwargs);
+ }
+
close (session->sock);
memory_pool_delete (session->session_pool);
@@ -467,6 +475,12 @@ process_command (struct controller_command *cmd, gchar **cmd_args, struct contro
struct rspamd_controller_ctx *ctx = session->worker->ctx;
switch (cmd->type) {
+ case COMMAND_GET:
+ case COMMAND_POST:
+ session->restful = TRUE;
+ session->state = STATE_HEADER;
+ session->kwargs = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal);
+ break;
case COMMAND_PASSWORD:
arg = *cmd_args;
if (!arg || *arg == '\0') {
@@ -553,34 +567,65 @@ process_command (struct controller_command *cmd, gchar **cmd_args, struct contro
break;
case COMMAND_LEARN_SPAM:
if (check_auth (cmd, session)) {
- arg = *cmd_args;
- if (!arg || *arg == '\0') {
- msg_debug ("no statfile specified in learn command");
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: stat filename and its size" CRLF);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
+ if (!session->restful) {
+ arg = *cmd_args;
+ if (!arg || *arg == '\0') {
+ msg_debug ("no statfile specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
}
- return TRUE;
- }
- arg = *(cmd_args + 1);
- if (arg == NULL || *arg == '\0') {
- msg_debug ("no message size specified in learn command");
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: symbol and message size" CRLF);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
+ arg = *(cmd_args + 1);
+ if (arg == NULL || *arg == '\0') {
+ msg_debug ("no message size specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
}
- return TRUE;
+ size = strtoul (arg, &err_str, 10);
+ if (err_str && *err_str != '\0') {
+ msg_debug ("message size is invalid: %s", arg);
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+ cl = find_classifier_conf (session->cfg, *cmd_args);
}
- size = strtoul (arg, &err_str, 10);
- if (err_str && *err_str != '\0') {
- msg_debug ("message size is invalid: %s", arg);
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
+ else {
+ if ((arg = g_hash_table_lookup (session->kwargs, "classifier")) == NULL) {
+ msg_debug ("no classifier specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+ else {
+ cl = find_classifier_conf (session->cfg, arg);
+ }
+ if ((arg = g_hash_table_lookup (session->kwargs, "content-length")) == NULL) {
+ msg_debug ("no size specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ return rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE);
+ }
+ else {
+ size = strtoul (arg, &err_str, 10);
+ if (err_str && *err_str != '\0') {
+ msg_debug ("message size is invalid: %s", arg);
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
}
- return TRUE;
}
- cl = find_classifier_conf (session->cfg, *cmd_args);
session->learn_classifier = cl;
@@ -592,34 +637,65 @@ process_command (struct controller_command *cmd, gchar **cmd_args, struct contro
break;
case COMMAND_LEARN_HAM:
if (check_auth (cmd, session)) {
- arg = *cmd_args;
- if (!arg || *arg == '\0') {
- msg_debug ("no statfile specified in learn command");
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: stat filename and its size" CRLF);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
+ if (!session->restful) {
+ arg = *cmd_args;
+ if (!arg || *arg == '\0') {
+ msg_debug ("no statfile specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
}
- return TRUE;
- }
- arg = *(cmd_args + 1);
- if (arg == NULL || *arg == '\0') {
- msg_debug ("no message size specified in learn command");
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn command requires at least two arguments: symbol and message size" CRLF);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
+ arg = *(cmd_args + 1);
+ if (arg == NULL || *arg == '\0') {
+ msg_debug ("no message size specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
}
- return TRUE;
+ size = strtoul (arg, &err_str, 10);
+ if (err_str && *err_str != '\0') {
+ msg_debug ("message size is invalid: %s", arg);
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+ cl = find_classifier_conf (session->cfg, *cmd_args);
}
- size = strtoul (arg, &err_str, 10);
- if (err_str && *err_str != '\0') {
- msg_debug ("message size is invalid: %s", arg);
- r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
- if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
- return FALSE;
+ else {
+ if ((arg = g_hash_table_lookup (session->kwargs, "classifier")) == NULL) {
+ msg_debug ("no classifier specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+ else {
+ cl = find_classifier_conf (session->cfg, arg);
+ }
+ if ((arg = g_hash_table_lookup (session->kwargs, "content-length")) == NULL) {
+ msg_debug ("no size specified in learn command");
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn_spam command requires at least two arguments: classifier name and a message's size" CRLF);
+ return rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE);
+ }
+ else {
+ size = strtoul (arg, &err_str, 10);
+ if (err_str && *err_str != '\0') {
+ msg_debug ("message size is invalid: %s", arg);
+ r = rspamd_snprintf (out_buf, sizeof (out_buf), "learn size is invalid" CRLF);
+ if (! rspamd_dispatcher_write (session->dispatcher, out_buf, r, FALSE, FALSE)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
}
- return TRUE;
}
- cl = find_classifier_conf (session->cfg, *cmd_args);
session->learn_classifier = cl;
@@ -631,6 +707,7 @@ process_command (struct controller_command *cmd, gchar **cmd_args, struct contro
break;
case COMMAND_LEARN:
if (check_auth (cmd, session)) {
+ /* TODO: remove this command as currenly it should not be used anywhere */
arg = *cmd_args;
if (!arg || *arg == '\0') {
msg_debug ("no statfile specified in learn command");
@@ -722,6 +799,7 @@ process_command (struct controller_command *cmd, gchar **cmd_args, struct contro
break;
case COMMAND_WEIGHTS:
+ /* TODO: remove this command as currenly it should not be used anywhere */
arg = *cmd_args;
if (!arg || *arg == '\0') {
msg_debug ("no statfile specified in weights command");
@@ -798,35 +876,39 @@ process_command (struct controller_command *cmd, gchar **cmd_args, struct contro
return TRUE;
}
-static gboolean
-process_custom_command (gchar *line, gchar **cmd_args, struct controller_session *session)
+static controller_func_t
+parse_custom_command (gchar *line, gchar **cmd_args, struct controller_session *session, gsize len)
{
GList *cur;
struct custom_controller_command *cmd;
+ if (len == 0) {
+ len = strlen (line);
+ }
cur = custom_commands;
while (cur) {
cmd = cur->data;
- if (g_ascii_strcasecmp (cmd->command, line) == 0) {
- /* Call handler */
- cmd->handler (cmd_args, session);
- return TRUE;
+ if (g_ascii_strncasecmp (cmd->command, line, len) == 0) {
+ return cmd->handler;
}
cur = g_list_next (cur);
}
- return FALSE;
+ return NULL;
}
static struct controller_command *
-process_normal_command (const gchar *line)
+parse_normal_command (const gchar *line, gsize len)
{
guint i;
struct controller_command *c;
+ if (len == 0) {
+ len = strlen (line);
+ }
for (i = 0; i < G_N_ELEMENTS (commands); i ++) {
c = &commands[i];
- if (g_ascii_strcasecmp (line, c->command) == 0) {
+ if (g_ascii_strncasecmp (line, c->command, len) == 0) {
return c;
}
}
@@ -834,6 +916,58 @@ process_normal_command (const gchar *line)
return NULL;
}
+static gboolean
+process_header (f_str_t *line, struct controller_session *session)
+{
+ gchar *headern;
+ struct controller_command *command;
+ struct rspamd_controller_ctx *ctx = session->worker->ctx;
+ controller_func_t custom_handler;
+
+ headern = separate_command (line, ':');
+
+ if (line == NULL || headern == NULL) {
+ return FALSE;
+ }
+ /* Eat whitespaces */
+ g_strstrip (headern);
+ fstrstrip (line);
+
+ if (*headern == 'c' || *headern == 'C') {
+ if (g_ascii_strcasecmp (headern, "command") == 0) {
+ /* This header is actually command */
+ command = parse_normal_command (line->begin, line->len);
+ if (command == NULL) {
+ if ((custom_handler = parse_custom_command (line->begin, NULL, session, line->len)) == NULL) {
+ msg_info ("bad command header: %V", line);
+ return FALSE;
+ }
+ else {
+ session->custom_handler = custom_handler;
+ }
+ }
+ session->cmd = command;
+ return TRUE;
+ }
+ }
+ else if (*headern == 'p' || *headern == 'P') {
+ /* Password header */
+ if (g_ascii_strcasecmp (headern, "password") == 0) {
+ if (line->len == strlen (ctx->password) && memcmp (line->begin, ctx->password, line->len) == 0) {
+ session->authorized = TRUE;
+ }
+ else {
+ msg_info ("wrong password in controller command");
+ }
+ return TRUE;
+ }
+ }
+
+ g_hash_table_insert (session->kwargs, headern, fstrcstr (line, session->session_pool));
+
+ return TRUE;
+}
+
/*
* Called if all filters are processed, non-threaded and simple version
*/
@@ -889,6 +1023,7 @@ controller_read_socket (f_str_t * in, void *arg)
GTree *tokens = NULL;
GError *err = NULL;
f_str_t c;
+ controller_func_t custom_handler;
switch (session->state) {
case STATE_COMMAND:
@@ -901,24 +1036,27 @@ controller_read_socket (f_str_t * in, void *arg)
if (len > 0) {
cmd = g_strstrip (params[0]);
- command = process_normal_command (cmd);
+ command = parse_normal_command (cmd, 0);
if (command != NULL) {
if (! process_command (command, &params[1], session)) {
return FALSE;
}
}
else {
- if (!process_custom_command (cmd, &params[1], session)) {
+ if ((custom_handler = parse_custom_command (cmd, &params[1], session, 0)) == NULL) {
msg_debug ("'%s'", cmd);
i = rspamd_snprintf (out_buf, sizeof (out_buf), "Unknown command" CRLF);
if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
return FALSE;
}
}
+ else {
+ custom_handler (&params[1], session);
+ }
}
}
if (session->state != STATE_LEARN && session->state != STATE_LEARN_SPAM_PRE
- && session->state != STATE_WEIGHTS && session->state != STATE_OTHER) {
+ && session->state != STATE_WEIGHTS && session->state != STATE_OTHER && session->state != STATE_HEADER) {
if (!rspamd_dispatcher_write (session->dispatcher, END, sizeof (END) - 1, FALSE, TRUE)) {
return FALSE;
}
@@ -929,6 +1067,43 @@ controller_read_socket (f_str_t * in, void *arg)
}
break;
+ case STATE_HEADER:
+ if (in->len == 0) {
+ /* End of headers */
+ if (session->cmd == NULL && session->custom_handler == NULL) {
+ i = rspamd_snprintf (out_buf, sizeof (out_buf), "500 Bad command" CRLF);
+ if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
+ return FALSE;
+ }
+ destroy_session (session->s);
+ return FALSE;
+ }
+ /* Perform command */
+ else if (session->cmd != NULL) {
+ if (! process_command (session->cmd, NULL, session)) {
+ destroy_session (session->s);
+ return FALSE;
+ }
+ }
+ else {
+ session->custom_handler (NULL, session);
+ }
+ if (session->state != STATE_LEARN && session->state != STATE_LEARN_SPAM_PRE
+ && session->state != STATE_WEIGHTS && session->state != STATE_OTHER) {
+ destroy_session (session->s);
+ return FALSE;
+ }
+ }
+ if (!process_header (in, session)) {
+ msg_debug ("'%V'", in);
+ i = rspamd_snprintf (out_buf, sizeof (out_buf), "500 Bad header" CRLF);
+ if (!rspamd_dispatcher_write (session->dispatcher, out_buf, i, FALSE, FALSE)) {
+ return FALSE;
+ }
+ destroy_session (session->s);
+ return FALSE;
+ }
+ break;
case STATE_LEARN:
session->learn_buf = in;
task = construct_task (session->worker);
@@ -1159,8 +1334,14 @@ controller_write_socket (void *arg)
return TRUE;
}
else if (session->state == STATE_REPLY) {
- session->state = STATE_COMMAND;
- rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_LINE, BUFSIZ);
+ if (session->restful) {
+ destroy_session (session->s);
+ return FALSE;
+ }
+ else {
+ session->state = STATE_COMMAND;
+ rspamd_set_dispatcher_policy (session->dispatcher, BUFFER_LINE, BUFSIZ);
+ }
}
rspamd_dispatcher_restore (session->dispatcher);
return TRUE;
diff --git a/src/fstring.c b/src/fstring.c
index 07b5546a0..9f17ac861 100644
--- a/src/fstring.c
+++ b/src/fstring.c
@@ -218,7 +218,8 @@ fstrcstr (f_str_t * str, memory_pool_t * pool)
gchar *res;
res = memory_pool_alloc (pool, str->len + 1);
- memcpy (res, str->begin, str->len);
+ /* Do not allow multiply \0 characters */
+ memccpy (res, str->begin, '\0', str->len);
res[str->len] = 0;
return res;
diff --git a/src/main.h b/src/main.h
index f90edf1d2..9a28793cd 100644
--- a/src/main.h
+++ b/src/main.h
@@ -131,10 +131,15 @@ union sa_union {
/**
* Control session object
*/
+struct controller_command;
+struct controller_session;
+typedef void (*controller_func_t)(gchar **args, struct controller_session *session);
+
struct controller_session {
struct rspamd_worker *worker; /**< pointer to worker structure (controller in fact) */
enum {
STATE_COMMAND,
+ STATE_HEADER,
STATE_LEARN,
STATE_LEARN_SPAM_PRE,
STATE_LEARN_SPAM,
@@ -146,7 +151,10 @@ struct controller_session {
} state; /**< current session state */
gint sock; /**< socket descriptor */
/* Access to authorized commands */
- gint authorized; /**< whether this session is authorized */
+ gboolean authorized; /**< whether this session is authorized */
+ gboolean restful; /**< whether this session is a restful session */
+ GHashTable *kwargs; /**< keyword arguments for restful command */
+ struct controller_command *cmd; /**< real command */
memory_pool_t *session_pool; /**< memory pool for session */
struct config_file *cfg; /**< pointer to config file */
gchar *learn_rcpt; /**< recipient for learning */
@@ -161,14 +169,13 @@ struct controller_session {
void (*other_handler)(struct controller_session *session,
f_str_t *in); /**< other command handler to execute at the end of processing */
void *other_data; /**< and its data */
+ controller_func_t custom_handler; /**< custom command handler */
struct rspamd_async_session* s; /**< async session object */
struct worker_task *learn_task;
struct rspamd_dns_resolver *resolver; /**< DNS resolver */
struct event_base *ev_base; /**< Event base */
};
-typedef void (*controller_func_t)(gchar **args, struct controller_session *session);
-
/**
* Worker task structure
*/
diff --git a/src/protocol.c b/src/protocol.c
index 4f6e0be38..7df9ae673 100644
--- a/src/protocol.c
+++ b/src/protocol.c
@@ -123,7 +123,7 @@ rspamc_proto_str (guint ver)
}
}
-static gchar *
+gchar *
separate_command (f_str_t * in, gchar c)
{
guint r = 0;
@@ -137,6 +137,11 @@ separate_command (f_str_t * in, gchar c)
in->len -= r + 1;
return b;
}
+ else if (*p == '\0') {
+ /* Actually we cannot allow several \0 characters in string, so write to the log about it */
+ msg_warn ("cannot separate command with \0 character, this can be an attack attempt");
+ return NULL;
+ }
p++;
r++;
}
diff --git a/src/protocol.h b/src/protocol.h
index de6d0ea03..72460940f 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -58,6 +58,14 @@ struct custom_command {
};
/**
+ * Find a character in command in and return pointer to the first part of the string, in is modified to point to the second part of string
+ * @param in f_str_t input
+ * @param c separator character
+ * @return pointer to the first part of string or NULL if there is no separator found
+ */
+gchar* separate_command (f_str_t * in, gchar c);
+
+/**
* Read one line of user's input for specified task
* @param task task object
* @param line line of user's input
diff --git a/src/smtp.c b/src/smtp.c
index 6e226c42a..c1e8765e4 100644
--- a/src/smtp.c
+++ b/src/smtp.c
@@ -34,6 +34,7 @@
#include "message.h"
#include "settings.h"
#include "dns.h"
+#include "lua/lua_common.h"
/* Max line size as it is defined in rfc2822 */
#define OUTBUFSIZ 1000
@@ -309,6 +310,7 @@ process_smtp_data (struct smtp_session *session)
session->task->fin_callback = smtp_write_socket;
session->task->fin_arg = session;
session->task->msg = memory_pool_alloc (session->pool, sizeof (f_str_t));
+ session->task->s = session->s;
#ifdef HAVE_MMAP_NOCORE
if ((session->task->msg->begin = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED | MAP_NOCORE, session->temp_fd, 0)) == MAP_FAILED) {
#else
@@ -346,23 +348,22 @@ process_smtp_data (struct smtp_session *session)
if (process_message (session->task) == -1) {
msg_err ("cannot process message");
munmap (session->task->msg->begin, st.st_size);
- msg_err ("process message failed: %s", strerror (errno));
goto err;
}
- r = process_filters (session->task);
- if (r == -1) {
- munmap (session->task->msg->begin, st.st_size);
- msg_err ("cannot process filters");
- goto err;
- }
- else if (r == 0) {
- session->state = SMTP_STATE_END;
- rspamd_dispatcher_pause (session->dispatcher);
+ if (session->task->cfg->pre_filters == NULL) {
+ r = process_filters (session->task);
+ if (r == -1) {
+ msg_err ("cannot process message");
+ munmap (session->task->msg->begin, st.st_size);
+ goto err;
+ }
}
else {
- process_statfiles (session->task);
- session->state = SMTP_STATE_END;
- return smtp_write_socket (session);
+ lua_call_pre_filters (session->task);
+ /* We want fin_task after pre filters are processed */
+ session->task->s->wanna_die = TRUE;
+ session->task->state = WAIT_PRE_FILTER;
+ check_session_pending (session->task->s);
}
}
else {
diff --git a/src/smtp_proto.c b/src/smtp_proto.c
index 2211411e1..e1e7f3229 100644
--- a/src/smtp_proto.c
+++ b/src/smtp_proto.c
@@ -394,6 +394,7 @@ smtp_upstream_read_socket (f_str_t * in, void *arg)
gchar outbuf[BUFSIZ];
gint r;
+ msg_debug ("in: %V, state: %d", in, session->upstream_state);
switch (session->upstream_state) {
case SMTP_STATE_GREETING:
r = check_smtp_ustream_reply (in, '2');