You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rspamc.pl.in 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #!/usr/bin/perl -w
  2. # Simple script that read message from STDIN and test it on rspamd server
  3. # using specified command.
  4. #
  5. # Usage: rspamc.pl [-c conf_file] [command] [-s statfile]
  6. #
  7. # By default rspamc.pl would read ./rspamd.conf and default command is SYMBOLS
  8. use Socket qw(:DEFAULT :crlf);
  9. use Term::Cap;
  10. use Mail::Rspamd::Client;
  11. use Data::Dumper;
  12. my %cfg = (
  13. 'conf_file' => '@CMAKE_INSTALL_PREFIX@/etc/rspamd.conf',
  14. 'command' => 'SYMBOLS',
  15. 'hosts' => ['localhost:11333', ],
  16. 'require_input' => 0,
  17. 'password' => '',
  18. 'control' => 0,
  19. 'statfile' => '',
  20. 'deliver_to'=> '',
  21. 'weight' => 1,
  22. 'imap_search' => 'ALL',
  23. 'ip' => '127.0.0.1',
  24. );
  25. my $terminal;
  26. $main::VERSION = '@RSPAMD_VERSION@';
  27. sub HELP_MESSAGE {
  28. print <<EOD;
  29. Usage: rspamc.pl [-h host] [-H hosts_list] [-P password] [-c conf_file] [-s statfile] [-d user\@domain] [command] [path]
  30. -h host to connect (in format host:port) or unix socket path
  31. -H path to file that contains list of hosts
  32. -P define control password
  33. -c config file to parse
  34. -s statfile to use for learn commands
  35. Additional options:
  36. -d define deliver-to header
  37. -w define weight for fuzzy operations
  38. -S define search string for IMAP operations
  39. -i emulate that message was send from specified IP
  40. -p pass message throught all filters
  41. Notes:
  42. imap format: imap:user:<username>:password:[<password>]:host:<hostname>:mbox:<mboxname>
  43. Password may be omitted and then it would be asked in terminal
  44. imaps requires IO::Socket::SSL
  45. IMAP search strings samples:
  46. ALL - All messages in the mailbox;
  47. FROM <string> - Messages that contain the specified string in the envelope structure's FROM field;
  48. HEADER <field-name> <string> - Messages that have a header with the specified field-name and that
  49. contains the specified string in the text of the header (what comes after the colon);
  50. NEW - Messages that have the \\Recent flag set but not the \\Seen flag.
  51. This is functionally equivalent to "(RECENT UNSEEN)".
  52. OLD - Messages that do not have the \\Recent flag set.
  53. SEEN - Messages that have the \\Seen flag set.
  54. SENTBEFORE <date> - Messages whose [RFC-2822] Date: header (disregarding time and timezone)
  55. is earlier than the specified date.
  56. TO <string> - Messages that contain the specified string in the envelope structure's TO field.
  57. TEXT <string> - Messages that contain the specified string in the header or body of the message.
  58. OR <search-key1> <search-key2> - Messages that match either search key (same for AND and NOT operations).
  59. Version: @RSPAMD_VERSION@
  60. EOD
  61. exit;
  62. };
  63. sub load_hosts_file {
  64. my $file = shift;
  65. open (HOSTS, "< $file") or die "cannot open file $file";
  66. $cfg{'hosts'} = [ ];
  67. while (<HOSTS>) {
  68. chomp;
  69. next if $_ =~ /^\s*#/;
  70. if ($_ =~ /^\s*(([^:]+):(\d+))\s*$/) {
  71. push (@{ $cfg{'hosts'} }, $1);
  72. }
  73. elsif ($_ =~ /^\s*([^:]+)\s*$/) {
  74. if ($cfg{'control'}) {
  75. push (@{ $cfg{'hosts'} }, "$1:11334");
  76. }
  77. else {
  78. push (@{ $cfg{'hosts'} }, "$1:11333");
  79. }
  80. }
  81. elsif ($_ =~ /^\s*(\/\S*)\s*$/) {
  82. push (@{ $cfg{'hosts'} }, "$1");
  83. }
  84. }
  85. close HOSTS;
  86. }
  87. # Load rspamd config params
  88. sub parse_config {
  89. my ($is_ctrl) = @_;
  90. if (! open CONF, "< $cfg{'conf_file'}") {
  91. print STDERR "Config file $cfg{'conf_file'} cannot be opened\n";
  92. return;
  93. }
  94. my $ctrl = 0, $skip = 0;
  95. while (<CONF>) {
  96. if ($_ =~ /^.*type.*=.*controller.*$/i) {
  97. $ctrl = 1;
  98. }
  99. if ($ctrl && $_ =~ /}/) {
  100. $ctrl = 0;
  101. }
  102. if ($_ =~ /^.*type.*=.*(?:lmtp|delivery|fuzzy).*$/i) {
  103. $skip = 1;
  104. }
  105. if ($skip && $_ =~ /}/) {
  106. $skip = 0;
  107. }
  108. if (!$skip && ((!$is_ctrl && !$ctrl) || ($ctrl && $is_ctrl))
  109. && $_ =~ /^\s*bind_socket\s*=\s*((([^:]+):(\d+))|(\/\S*))/i) {
  110. if ($3 && $4) {
  111. if ($3 eq '*') {
  112. $cfg{'hosts'} = [ "127.0.0.1:$4" ];
  113. }
  114. else {
  115. $cfg{'hosts'} = [ "$3:$4" ];
  116. }
  117. }
  118. else {
  119. $cfg{'hosts'} = [ "$5" ];
  120. }
  121. }
  122. if ($ctrl && $is_ctrl && $_ =~ /^\s*password\s*=\s*"(\S+)"/) {
  123. $cfg{'password'} = $1;
  124. }
  125. }
  126. close CONF;
  127. }
  128. sub print_control_result {
  129. my ($host, $res) = @_;
  130. $terminal->Tputs( 'md', 1, *STDOUT );
  131. print "Results for host $host:\n\n";
  132. $terminal->Tputs( 'me', 1, *STDOUT );
  133. if ($res->{error_code} == 0) {
  134. print "$res->{error}\n";
  135. }
  136. else {
  137. print "Error occured: $res->{error_code}:\n$res->{error}\n";
  138. }
  139. }
  140. sub print_rspamc_result {
  141. my ($host, $res) = @_;
  142. $terminal->Tputs( 'md', 1, *STDOUT );
  143. print "Results for host $host:\n\n";
  144. $terminal->Tputs( 'me', 1, *STDOUT );
  145. if (defined($res->{error})) {
  146. print "Error occured: $res->{error}\n\n";
  147. }
  148. else {
  149. while (my ($metric, $result) = each (%{ $res })) {
  150. $terminal->Tputs( 'md', 1, *STDOUT );
  151. print "$metric: ";
  152. $terminal->Tputs( 'me', 1, *STDOUT );
  153. print "$result->{isspam}, [ $result->{score} / $result->{threshold} ]\n";
  154. $terminal->Tputs( 'md', 1, *STDOUT );
  155. print "Symbols: ";
  156. $terminal->Tputs( 'me', 1, *STDOUT );
  157. print join("; ", @{ $result->{symbols} }) . "\n";
  158. $terminal->Tputs( 'md', 1, *STDOUT );
  159. print "Action: ";
  160. $terminal->Tputs( 'me', 1, *STDOUT );
  161. print "$result->{action}\n";
  162. $terminal->Tputs( 'md', 1, *STDOUT );
  163. print "Urls: ";
  164. $terminal->Tputs( 'me', 1, *STDOUT );
  165. print join(", ", @{ $result->{urls} }) . "\n";
  166. foreach my $msg (@{ $result->{messages} }) {
  167. $terminal->Tputs( 'md', 1, *STDOUT );
  168. print "Message: ";
  169. $terminal->Tputs( 'me', 1, *STDOUT );
  170. print "$msg\n";
  171. }
  172. print "\n\n";
  173. }
  174. }
  175. }
  176. sub print_item_result {
  177. my ($file, $res) = @_;
  178. $terminal->Tputs( 'md', 1, *STDOUT );
  179. print "Results for item $file:\n\n";
  180. $terminal->Tputs( 'me', 1, *STDOUT );
  181. while (my ($host, $result) = each (%{ $res })) {
  182. if ($cfg{control}) {
  183. print_control_result ($host, $result);
  184. }
  185. else {
  186. print_rspamc_result ($host, $result);
  187. }
  188. }
  189. }
  190. ############################# Main part ###########################################
  191. my %args;
  192. HELP_MESSAGE() unless scalar @ARGV >= 1;
  193. while (my $opt = shift @ARGV) {
  194. if ($opt eq '-c') {
  195. $args{c} = shift @ARGV;
  196. }
  197. elsif ($opt eq '-h') {
  198. $args{h} = shift @ARGV;
  199. }
  200. elsif ($opt eq '-P') {
  201. $args{P} = shift @ARGV;
  202. }
  203. elsif ($opt eq '-s') {
  204. $args{s} = shift @ARGV;
  205. }
  206. elsif ($opt eq '-w') {
  207. $args{w} = shift @ARGV;
  208. }
  209. elsif ($opt eq '-d') {
  210. $args{d} = shift @ARGV;
  211. }
  212. elsif ($opt eq '-S') {
  213. $args{S} = shift @ARGV;
  214. }
  215. elsif ($opt eq '-H') {
  216. $args{H} = shift @ARGV;
  217. }
  218. elsif ($opt eq '-i') {
  219. $args{i} = shift @ARGV;
  220. }
  221. elsif ($opt eq '-p') {
  222. $args{p} = 1;
  223. }
  224. elsif ($opt eq '-?' || $opt eq '--help') {
  225. HELP_MESSAGE();
  226. }
  227. elsif ($opt eq '--') {
  228. last;
  229. }
  230. else {
  231. unshift @ARGV,$opt;
  232. last;
  233. }
  234. }
  235. my $cmd = shift @ARGV;
  236. my @path = shift @ARGV;
  237. if (!defined ($cmd) || $cmd eq '') {
  238. $cmd = 'SYMBOLS';
  239. }
  240. if (defined ($args{c})) {
  241. if (-r $args{c}) {
  242. $cfg{'conf_file'} = $args{c};
  243. }
  244. else {
  245. die "config file $args{c} is not readable";
  246. }
  247. }
  248. if ($cmd =~ /(SYMBOLS|PROCESS|CHECK|URLS|EMAILS)/i) {
  249. $cfg{'command'} = $1;
  250. $cfg{'control'} = 0;
  251. }
  252. elsif ($cmd =~ /(STAT|LEARN|SHUTDOWN|RELOAD|UPTIME|COUNTERS|FUZZY_ADD|FUZZY_DEL|WEIGHTS)/i) {
  253. $cfg{'command'} = $1;
  254. $cfg{'control'} = 1;
  255. }
  256. else {
  257. die "unknown command $cmd";
  258. }
  259. if (-r $cfg{'conf_file'}) {
  260. # Try to parse config
  261. parse_config ($cfg{'control'});
  262. }
  263. if (defined ($args{S})) {
  264. $cfg{'imap_search'} = $args{S};
  265. }
  266. if (defined ($args{s})) {
  267. if ($args{s}) {
  268. $cfg{'statfile'} = $args{s};
  269. }
  270. else {
  271. main::HELP_MESSAGE();
  272. }
  273. }
  274. if (defined ($args{h})) {
  275. $cfg{'hosts'} = [ $args{h} ];
  276. }
  277. if (defined ($args{P})) {
  278. $cfg{'password'} = $args{P};
  279. }
  280. if (defined ($args{d})) {
  281. $cfg{'deliver_to'} = $args{d};
  282. }
  283. if (defined ($args{w})) {
  284. $cfg{'weight'} = $args{w};
  285. }
  286. if (defined ($args{i})) {
  287. $cfg{'ip'} = $args{i};
  288. }
  289. if (exists ($args{p})) {
  290. $cfg{'pass_all'} = 1;
  291. }
  292. if ($cmd =~ /SYMBOLS|SCAN|PROCESS|CHECK|REPORT_IFSPAM|REPORT|URLS|EMAILS|LEARN|FUZZY_ADD|FUZZY_DEL|WEIGHTS/i) {
  293. $cfg{'require_input'} = 1;
  294. }
  295. if (defined ($args{H})) {
  296. load_hosts_file ($args{H});
  297. }
  298. my $rspamd = Mail::Rspamd::Client->new(\%cfg);
  299. $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => 9600 };
  300. if (!defined ($path[0]) || ! $cfg{'require_input'}) {
  301. my $input;
  302. if ($cfg{'require_input'}) {
  303. while (defined (my $line = <>)) {
  304. $input .= $line;
  305. }
  306. }
  307. my $res = $rspamd->do_all_cmd ($input);
  308. while (my ($host, $result) = each (%{ $res })) {
  309. if ($cfg{control}) {
  310. print_control_result ($host, $result);
  311. }
  312. else {
  313. print_rspamc_result ($host, $result);
  314. }
  315. }
  316. }
  317. else {
  318. $rspamd->process_path (\&print_item_result, @path);
  319. }