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.2KB

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