aboutsummaryrefslogtreecommitdiffstats
path: root/src/controller.c
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@rambler-co.ru>2012-09-14 20:59:23 +0400
committerVsevolod Stakhov <vsevolod@rambler-co.ru>2012-09-14 20:59:23 +0400
commit2d81eded1e64737d2ecca278efc2a84be7dbd8f5 (patch)
treef9eab401bca807461a76911bcf0199bdb27f8e6d /src/controller.c
parentea68f17ec2f7bffbb8db9000a05d7208fb611204 (diff)
downloadrspamd-2d81eded1e64737d2ecca278efc2a84be7dbd8f5.tar.gz
rspamd-2d81eded1e64737d2ecca278efc2a84be7dbd8f5.zip
* Initial approach to RESTful controller.
Fix security issues in fstring handling.
Diffstat (limited to 'src/controller.c')
-rw-r--r--src/controller.c305
1 files changed, 243 insertions, 62 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;