From 87c9659fdd08bbbc0eb796afccf7237a03181498 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Mon, 19 Jan 2009 17:01:08 +0300 Subject: [PATCH] * Rewrite perl client for rspamd, now it allows access to both normal and control interfaces * Fix small errors in tokenizer and controller interface --- rspamc.pl | 240 +++++++++++++++++++++++++++--------- src/controller.c | 16 +-- src/fstring.c | 16 +++ src/fstring.h | 6 + src/statfile.c | 6 + src/tokenizers/osb.c | 1 + src/tokenizers/tokenizers.c | 6 +- 7 files changed, 222 insertions(+), 69 deletions(-) diff --git a/rspamc.pl b/rspamc.pl index ee203b85a..46940987c 100755 --- a/rspamc.pl +++ b/rspamc.pl @@ -3,7 +3,7 @@ # Simple script that read message from STDIN and test it on rspamd server # using specified command. # -# Usage: rspamc.pl [-c conf_file] [command] +# Usage: rspamc.pl [-c conf_file] [command] [-s statfile] # # By default rspamc.pl would read ./rspamd.conf and default command is SYMBOLS @@ -14,95 +14,215 @@ my %cfg = ( 'command' => 'SYMBOLS', 'host' => 'localhost', 'port' => '11333', - 'is_unix' => 0, + 'is_unix' => 0, + 'password' => '', + 'control' => 0, + 'statfile' => '', ); + sub usage { - return "Usage: rspamc.pl [-c conf_file] [command]"; + return "Usage: rspamc.pl [-c conf_file] [-s statfile] [command]"; } -while (my $param = shift) { - if ($param eq '-c') { - my $value = shift; - if ($value) { - if (-r $value) { - $cfg{'conf_file'} = $value; +# Load rspamd config params +sub parse_config { + my ($is_ctrl) = @_; + + open CONF, "< $cfg{'conf_file'}" or die "config file $cfg{'conf_file'} cannot be opened"; + + my $ctrl = 0; + while () { + if ($_ =~ /control\s*{/i) { + $ctrl = 1; + } + if ($ctrl && $_ =~ /}/) { + $ctrl = 0; + } + if (((!$is_ctrl && !$ctrl) || ($ctrl && $is_ctrl)) + && $_ =~ /^\s*bind_socket\s*=\s*((([^:]+):(\d+))|(\/\S*))/i) { + if ($3 && $4) { + $cfg{'host'} = $3; + $cfg{'port'} = $4; + $cfg{'is_unix'} = 0; } else { - die "config file $value is not readable"; + $cfg{'host'} = $5; + $cfg{'is_unix'} = 1; } } - else { - die usage(); + if ($ctrl && $is_ctrl && $_ =~ /^\s*password\s*=\s*"(\S+)"/) { + $cfg{'password'} = $1; } } - elsif ($param =~ /(SYMBOLS|SCAN|PROCESS|CHECK|REPORT_IFSPAM|REPORT)/i) { - $cfg{'command'} = $1; - } + + close CONF; + } -open CONF, "< $cfg{'conf_file'}" or die "config file $cfg{'conf_file'} cannot be opened"; +sub connect_socket { + my $sock; -my $ctrl = 0; -while () { - if ($_ =~ /control\s*{/i) { - $ctrl = 1; - } - if ($ctrl && $_ =~ /}/) { - $ctrl = 0; + if ($cfg{'is_unix'}) { + socket ($sock, PF_UNIX, SOCK_STREAM, 0) or die "cannot create unix socket"; + my $sun = sockaddr_un($cfg{'host'}); + connect ($sock, $sun) or die "cannot connect to socket $cfg{'host'}"; } - if (!$ctrl && $_ =~ /^\s*bind_socket\s*=\s*((([^:]+):(\d+))|(\/\S*))/i) { - if ($3 && $4) { - $cfg{'host'} = $3; - $cfg{'port'} = $4; - $cfg{'is_unix'} = 0; + else { + my $proto = getprotobyname('tcp'); + my $sin; + socket ($sock, PF_INET, SOCK_STREAM, $proto) or die "cannot create tcp socket"; + if (inet_aton ($cfg{'host'})) { + $sin = sockaddr_in ($cfg{'port'}, inet_aton($cfg{'host'})); } else { - $cfg{'host'} = $5; - $cfg{'is_unix'} = 1; + my $addr = gethostbyname($cfg{'host'}); + if (!$addr) { + die "cannot resolve $cfg{'host'}"; + } + $sin = sockaddr_in ($cfg{'port'}, $addr); } - last; + + connect ($sock, $sin) or die "cannot connect to socket $cfg{'host'}:$cfg{'port'}"; } + return $sock; } -close CONF; +# Currently just read stdin for user's message and pass it to rspamd +sub do_rspamc_command { + my ($sock) = @_; + + my $input; + while (defined (my $line = <>)) { + $input .= $line; + } + + print "Sending ". length ($input) ." bytes...\n"; -if ($cfg{'is_unix'}) { - my $proto = getprotobyname('tcp'); - socket (SOCK, PF_UNIX, SOCK_STREAM, $proto) or die "cannot create unix socket"; - my $sun = sockaddr_un($cfg{'host'}); - connect (SOCK, $sun) or die "cannot connect to socket $cfg{'host'}"; + syswrite $sock, "$cfg{'command'} RSPAMC/1.0 $CRLF"; + syswrite $sock, "Content-Length: " . length ($input) . $CRLF . $CRLF; + syswrite $sock, $input; + syswrite $sock, $CRLF; + while (<$sock>) { + print $_; + } } -else { - my $proto = getprotobyname('tcp'); - my $sin; - socket (SOCK, PF_INET, SOCK_STREAM, $proto) or die "cannot create tcp socket"; - if (inet_aton ($cfg{'host'})) { - $sin = sockaddr_in ($cfg{'port'}, inet_aton($cfg{'host'})); + +sub do_ctrl_auth { + my ($sock) = @_; + + syswrite $sock, "password $cfg{'password'}" . $CRLF; + if (defined (my $reply = <$sock>)) { + my $end = <$sock>; + if ($reply =~ /^password accepted/) { + return 1; + } + } + + return 0; +} + +sub do_control_command { + my ($sock) = @_; + + # Read greeting first + if (defined (my $greeting = <$sock>)) { + if ($greeting !~ /^Rspamd version/) { + die "not rspamd greeting line $greeting"; + } + } + if ($cfg{'command'} =~ /^learn$/i) { + my $input; + die "statfile is not specified to learn command" if !$cfg{'statfile'}; + + while (defined (my $line = <>)) { + $input .= $line; + } + + if (do_ctrl_auth ($sock)) { + my $len = length ($input); + print "Sending $len bytes...\n"; + syswrite $sock, "learn $cfg{'statfile'} $len" . $CRLF; + syswrite $sock, $input . $CRLF; + if (defined (my $reply = <$sock>)) { + if ($reply =~ /^learn ok/) { + print "Learn succeed\n"; + } + else { + print "Learn failed\n"; + } + } + } + else { + print "Authentication failed\n"; + } + } + elsif ($cfg{'command'} =~ /(reload|shutdown)/i) { + if (do_ctrl_auth ($sock)) { + syswrite $sock, $cfg{'command'} . $CRLF; + while (defined (my $line = <$sock>)) { + last if $line =~ /^END/; + print $line; + } + } + else { + print "Authentication failed\n"; + } } else { - my $addr = gethostbyname($cfg{'host'}); - if (!$addr) { - die "cannot resolve $cfg{'host'}"; + syswrite $sock, $cfg{'command'} . $CRLF; + while (defined (my $line = <$sock>)) { + last if $line =~ /^END/; + print $line; } - $sin = sockaddr_in ($cfg{'port'}, $addr); } - - connect (SOCK, $sin) or die "cannot connect to socket $cfg{'host'}:$cfg{'port'}"; } -my $input; -while (defined (my $line = <>)) { - $input .= $line; +while (my $param = shift) { + if ($param eq '-c') { + my $value = shift; + if ($value) { + if (-r $value) { + $cfg{'conf_file'} = $value; + } + else { + die "config file $value is not readable"; + } + } + else { + die usage(); + } + } + elsif ($param eq '-s') { + my $value = shift; + if ($value) { + $cfg{'statfile'} = $value; + } + else { + die usage(); + } + } + elsif ($param =~ /(SYMBOLS|SCAN|PROCESS|CHECK|REPORT_IFSPAM|REPORT)/i) { + $cfg{'command'} = $1; + $cfg{'control'} = 0; + } + elsif ($param =~ /(STAT|LEARN|SHUTDOWN|RELOAD|UPTIME)/i) { + $cfg{'command'} = $1; + $cfg{'control'} = 1; + } + else { + die usage(); + } } -print "Sending ". length ($input) ." bytes...\n"; +parse_config ($cfg{'control'}); +my $sock = connect_socket (); -syswrite SOCK, "$cfg{'command'} RSPAMC/1.0 $CRLF"; -syswrite SOCK, "Content-Length: " . length ($input) . $CRLF . $CRLF; -syswrite SOCK, $input; -syswrite SOCK, $CRLF; -while () { - print $_; +if ($cfg{'control'}) { + do_control_command ($sock); } -close SOCK; +else { + do_rspamc_command ($sock); +} + +close ($sock); diff --git a/src/controller.c b/src/controller.c index fb14cf8bb..56eeb042a 100644 --- a/src/controller.c +++ b/src/controller.c @@ -26,6 +26,7 @@ #include "classifiers/classifiers.h" #define CRLF "\r\n" +#define END "END" CRLF enum command_type { COMMAND_PASSWORD, @@ -149,9 +150,7 @@ process_command (struct controller_command *cmd, char **cmd_args, struct control } break; case COMMAND_QUIT: - session->state = STATE_QUIT; - r = snprintf (out_buf, sizeof (out_buf), "bye" CRLF); - bufferevent_write (session->bev, out_buf, r); + session->state = STATE_QUIT; break; case COMMAND_RELOAD: if (check_auth (cmd, session)) { @@ -170,7 +169,6 @@ process_command (struct controller_command *cmd, char **cmd_args, struct control session->worker->srv->stat->connections_count); r += snprintf (out_buf + r, sizeof (out_buf) - r, "Control connections count: %u" CRLF, session->worker->srv->stat->control_connections_count); - r += snprintf (out_buf + r, sizeof (out_buf) - r, "-- end of stats report" CRLF); bufferevent_write (session->bev, out_buf, r); } break; @@ -187,8 +185,8 @@ process_command (struct controller_command *cmd, char **cmd_args, struct control /* If uptime more than 2 hours, print as a number of days. */ if (uptime >= 2 * 3600) { days = uptime / 86400; - hours = (uptime % 3600) / 60; - minutes = (uptime % 60) / 60; + hours = uptime / 3600 - days * 86400; + minutes = uptime / 60 - hours * 3600 - days * 86400; r = snprintf (out_buf, sizeof (out_buf), "%d day%s %d hour%s %d minute%s" CRLF, days, days > 1 ? "s" : " ", hours, hours > 1 ? "s" : " ", @@ -201,7 +199,7 @@ process_command (struct controller_command *cmd, char **cmd_args, struct control /* Else print the minutes and seconds. */ else { hours = uptime / 3600; - minutes = (uptime % 60) / 60; + minutes = uptime / 60 - hours * 3600; r = snprintf (out_buf, sizeof (out_buf), "%d hour%s %d minite%s %d second%s" CRLF, hours, hours > 1 ? "s" : " ", minutes, minutes > 1 ? "s" : " ", @@ -371,10 +369,14 @@ read_socket (struct bufferevent *bev, void *arg) session->state = STATE_REPLY; } if (session->state != STATE_LEARN) { + bufferevent_write (bev, END, sizeof (END) - 1); bufferevent_enable (bev, EV_WRITE); } g_strfreev (params); } + else { + bufferevent_enable (bev, EV_WRITE); + } if (s != NULL) { free (s); } diff --git a/src/fstring.c b/src/fstring.c index 2935fe8e6..b2008c047 100644 --- a/src/fstring.c +++ b/src/fstring.c @@ -1,4 +1,5 @@ #include +#include #include "fstring.h" @@ -154,6 +155,21 @@ fstrcat (f_str_t *dest, f_str_t *src) } +/* + * Make copy of string to 0-terminated string + */ +char* +fstrcstr (f_str_t *str, memory_pool_t *pool) +{ + char *res; + res = memory_pool_alloc (pool, str->len + 1); + + memcpy (res, str->begin, str->len); + res[str->len] = 0; + + return res; +} + /* * Push one character to fstr */ diff --git a/src/fstring.h b/src/fstring.h index 6840d9088..896cd8dcf 100644 --- a/src/fstring.h +++ b/src/fstring.h @@ -93,4 +93,10 @@ f_str_t* fstrgrow (memory_pool_t *pool, f_str_t *orig, size_t newlen); */ uint32_t fstrhash (f_str_t *str); + +/* + * Make copy of string to 0-terminated string + */ +char* fstrcstr (f_str_t *str, memory_pool_t *pool); + #endif diff --git a/src/statfile.c b/src/statfile.c index c0a2a2487..830bc5960 100644 --- a/src/statfile.c +++ b/src/statfile.c @@ -337,6 +337,12 @@ statfile_pool_set_block (statfile_pool_t *pool, char *filename, uint32_t h1, uin msg_debug ("statfile_pool_set_block: chain %u is full, starting expire", blocknum); break; } + /* First try to find block in chain */ + if (block->hash1 == h1 && block->hash2 == h2) { + block->last_access = now - (time_t)header->create_time; + block->value = value; + return; + } /* Check whether we have a free block in chain */ if (block->hash1 == 0 && block->hash2 == 0) { /* Write new block here */ diff --git a/src/tokenizers/osb.c b/src/tokenizers/osb.c index afd2febd8..451644675 100644 --- a/src/tokenizers/osb.c +++ b/src/tokenizers/osb.c @@ -43,6 +43,7 @@ osb_tokenize_text (struct tokenizer *tokenizer, memory_pool_t *pool, f_str_t *in hashpipe[i] = hashpipe[i - 1]; } hashpipe[0] = fstrhash (&token); + msg_debug ("osb_tokenize_text: text token %s, hash: %d", fstrcstr (&token, pool), hashpipe[0]); for (i = 1; i < FEATURE_WINDOW_SIZE; i ++) { h1 = hashpipe[0]* primes[0] + hashpipe[i] * primes[i<<1]; diff --git a/src/tokenizers/tokenizers.c b/src/tokenizers/tokenizers.c index 280ebd477..f0481e00d 100644 --- a/src/tokenizers/tokenizers.c +++ b/src/tokenizers/tokenizers.c @@ -60,11 +60,13 @@ get_next_word (f_str_t *buf, f_str_t *token) pos = token->begin; /* Skip non graph symbols */ - while (remain-- && !g_ascii_isgraph (*pos ++)) { + while (remain-- && !g_ascii_isgraph (*pos)) { token->begin ++; + pos ++; } - while (remain-- && g_ascii_isgraph (*pos ++)) { + while (remain-- && g_ascii_isgraph (*pos)) { token->len ++; + pos ++; } if (token->len == 0) { -- 2.39.5