/[smeserver]/spamassassin-botnet/S/Botnet-0.8.tar
ViewVC logotype

Contents of /spamassassin-botnet/S/Botnet-0.8.tar

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Mon Aug 27 20:32:37 2007 UTC (17 years, 3 months ago) by gregswallow
Branch: MAIN
CVS Tags: HEAD
Content type: application/x-tar
Mon Aug 27 14:31:58 2007                                      gswallow

adding 0.8 source file
----------------------------------------------------------------------

1 Botnet.api.txt0000444000000000000060000000725110542471052013530 0ustar rootmail00000000000000
2 If you want to write perl programs that do the same checks as Botnet, they
3 can now be used without having to go through SpamAssassin. You will need
4 to have SpamAssassin installed to get Botnet.pm to load, but other than
5 that, you don't have to interact with SpamAssassin. Here are the perl
6 statements that evaluate to the same process as the Botnet checks:
7
8
9 Same as BOTNET_NORDNS:
10 $hostname = Mail::SpamAssassin::Plugin::Botnet::get_rdns($ip);
11 $nordns = ($hostname eq "");
12
13 Given the IP address (without surrounding []'s), will return the
14 hostname contained within the _FIRST_ PTR record it finds for that
15 IP address.
16
17
18 Same as BOTNET_BADDNS:
19 $baddns =
20 Mail::SpamAssassin::Plugin::Botnet::check_dns($hostname, $ip, "A", -1);
21
22 Returns 1 if $hostname resolves back to $ip. Otherwise returns 0.
23 The third argument can be set to "MX" to resolve MX records back to
24 an IP address. Only "A" and "MX" are currently supported.
25 The fourth argument says how many records to look at. -1 says "all of
26 them". If you set this to 5, it will only look at 5 records before
27 giving up. If you set this to 5, and set the record type to "MX", then
28 the only the first 5 MX records are checked, AND for each MX record only
29 the first 5 A records are checked.
30
31
32 Same as BOTNET_IPINHOSTNAME:
33 $iphost =
34 Mail::SpamAssassin::Plugin::Botnet::check_ipinhostname($hostname, $ip);
35
36 Returns 1 if the hostname contains 2 or more octets of the IP address, in
37 decimal or hexidecimal form.
38
39
40 Same as BOTNET_CLIENTWORDS or BOTNET_SERVERWORDS:
41 $cwordexp = '((\b|\d)cable(\b|\d))|((\b|\d)catv(\b|\d))|((\b|\d)ddns(\b|\d))|' .
42 '((\b|\d)dhcp(\b|\d))|((\b|\d)dial-?up(\b|\d))|' .
43 '((\b|\d)dip(\b|\d))|((\b|\d)(a|s|d(yn)?)?dsl(\b|\d))|' .
44 '((\b|\d)dynamic(\b|\d))|((\b|\d)modem(\b|\d))|' .
45 '((\b|\d)ppp(\b|\d))|((\b|\d)res(net|ident(ial)?)?(\b|\d))|' .
46 '((\b|\d)client(\b|\d))|((\b|\d)fixed(\b|\d))|' .
47 '((\b|\d)pool(\b|\d))|((\b|\d)static(\b|\d))|((\b|\d)user(\b|\d))';
48 $cwords = Mail::SpamAssassin::Plugin::Botnet::check_words($hostname, $cwordexp);
49
50 $swordexp = '((\b|\d)mail(\b|\d))|((\b|\d)mta(\b|\d))|((\b|\d)mx(\b|\d))|' .
51 '((\b|\d)relay(\b|\d))|((\b|\d)smtp(\b|\d))';
52 $swords = Mail::SpamAssassin::Plugin::Botnet::check_words($hostname, $swordexp);
53
54 (the above $cwordexp matches the expression sent to the client word check
55 based upon the default Botnet.cf; similarly, the above $swordexp matches
56 the expression sent to the server word check based upon the default
57 Botnet.cf)
58
59 Returns 1 if the hostname matches the regular expression in $cwordexp,
60 or $swordexp, not including within the two right-most domains in $hostname.
61 You must supply the regular expression yourself, and act accordingly to
62 whether or not it is server words or client words.
63
64
65 Same as BOTNET_CLIENT:
66 $client = ((! $swords) && ($cwords || $iphost));
67 OR
68 $client = check_client($hostname, $ip, $cwordexp, $swordexp, \$tests)
69
70 $tests (optional) will contain the names of which subchecks were triggered:
71 serverwords, clientwords, ipinhostname
72
73
74 Same as BOTNET_SOHO:
75 $soho =
76 Mail::SpamAssassin::Plugin::Botnet::check_soho($hostname, $ip, $domain, $helo);
77
78 $domain should be the part after the @ in the sender's email address.
79 $helo doesn't actualy do anything ... and probably wont ever.
80
81
82 Same as BOTNET:
83 $botnet = ((! $soho) && ($nordns || $baddns || $client));
84 OR
85 $botnet =
86 Mail::SpamAssassin::Plugin::Botnet::check_botnet($hostname, $ip,
87 $cwordexp, $swordexp, $domain, $helo, \$tests);
88
89 $tests (optional) will contain the names of which subchecks were triggered:
90 nordns, badrdns, serverwords, clientwords, ipinhostname, client, soho
91
92 Botnet.cf0000444000000000000060000001114210655500263012525 0ustar rootmail00000000000000#
93 # See Botnet.txt for explanations
94
95 ######################################################################
96 #
97 # THE PLUGIN
98 #
99 ######################################################################
100
101 loadplugin Mail::SpamAssassin::Plugin::Botnet Botnet.pm
102
103
104 ######################################################################
105 #
106 # CONFIGURATION SETTINGS
107 #
108 ######################################################################
109
110 # I don't think SA is properly setting the 'auth=' field in the
111 # untrusted-relay's pseudoheader anyway, so I don't think this matters
112 botnet_pass_auth 0
113
114 # If there are trusted relays, then look to see if there's a
115 # public IP address; if so, then pass the message through.
116 botnet_pass_trusted public
117
118 # look past Untrusted Relays with these IP's at the next Untrusted Relay
119 # I've included ip-v4 loopback, and the RFC 1918 reserved IP blocks.
120 botnet_skip_ip ^127\.0\.0\.1$
121 botnet_skip_ip ^10\..*$
122 botnet_skip_ip ^172\.1[6789]\..*$
123 botnet_skip_ip ^172\.2[0-9]\..*$
124 botnet_skip_ip ^172\.3[01]\..*$
125 botnet_skip_ip ^192\.168\..*$
126
127 # pass messages entirely (no botnet rules triggered) if you come to an
128 # untrusted relay with these IP addresses. (example only, unless you're
129 # using that block locally, you probably don't want to uncomment this
130 # next line)
131 #botnet_pass_ip ^10\.0\.0\..*$
132 botnet_pass_ip ^128\.223\.98\.16$ # dynamic.uoregon.edu
133
134 # domain names we pass; note: if the RDNS owner tricks this, by putting
135 # this domain in their PTR record, then:
136 # a) it's probably a direct spammer and not a botnet anyway, and
137 # b) there are other spam assassin rules for dealing with those
138 # issues.
139 botnet_pass_domains amazon\.com # they use IP in Hostname; dorks
140 botnet_pass_domains apple\.com # special test case
141 botnet_pass_domains ebay\.com # pool in hostname
142
143 # basic substrings (regular expressions) for "client-like hostnames"
144 botnet_clientwords .*dsl.* cable catv ddns dhcp dial(-?up)? dip docsis
145 botnet_clientwords dyn(amic)?(ip)? modem ppp(oe)? res(net|ident(ial)?)?
146 botnet_clientwords bredband
147
148 # slightly more controversial client words
149 botnet_clientwords client fixed ip pool static user
150
151 # basic substrings (regular expressions) for "mail server-like hostnames"
152 botnet_serverwords e?mail(out)? mta mx(pool)? relay smtp
153
154 # used by many exchange servers
155 botnet_serverwords exch(ange)?
156
157
158 ######################################################################
159 #
160 # THE RULES
161 #
162 ######################################################################
163
164 describe BOTNET Relay might be a spambot or virusbot
165 header BOTNET eval:botnet()
166 score BOTNET 5.0
167
168 describe BOTNET_SOHO Relay might be a SOHO mail server
169 header BOTNET_SOHO eval:botnet_soho()
170 score BOTNET_SOHO 0.0
171
172 describe BOTNET_NORDNS Relay's IP address has no PTR record
173 header BOTNET_NORDNS eval:botnet_nordns()
174 score BOTNET_NORDNS 0.0
175
176 describe BOTNET_BADDNS Relay doesn't have full circle DNS
177 header BOTNET_BADDNS eval:botnet_baddns()
178 score BOTNET_BADDNS 0.0
179
180 describe BOTNET_CLIENT Relay has a client-like hostname
181 header BOTNET_CLIENT eval:botnet_client()
182 score BOTNET_CLIENT 0.0
183
184 describe BOTNET_IPINHOSTNAME Hostname contains its own IP address
185 header BOTNET_IPINHOSTNAME eval:botnet_ipinhostname()
186 score BOTNET_IPINHOSTNAME 0.0
187
188 describe BOTNET_CLIENTWORDS Hostname contains client-like substrings
189 header BOTNET_CLIENTWORDS eval:botnet_clientwords()
190 score BOTNET_CLIENTWORDS 0.0
191
192 describe BOTNET_SERVERWORDS Hostname contains server-like substrings
193 header BOTNET_SERVERWORDS eval:botnet_serverwords()
194 score BOTNET_SERVERWORDS 0.0
195
196
197 ######################################################################
198 #
199 # NON-MODULE RULES
200 #
201 ######################################################################
202
203 # Botnet rules that don't make direct or indirect use of the Botnet.pm module
204
205
206 # shawcable.net uses customer hostnames that don't match other botnet patterns
207 describe BOTNET_SHAWCABLE Shawcable.net customer address
208 meta BOTNET_SHAWCABLE (__BOTNET_SHAWCABLE && __BOTNET_NOTRUST)
209 header __BOTNET_SHAWCABLE X-Spam-Relays-Untrusted =~ /^[^\]]+ rdns=s[0-9a-f]*\...\.shawcable\.net\b/i
210 score BOTNET_SHAWCABLE 5.0
211
212 # ocn.ne.jp uses customer hostnames that don't match other botnet patterns
213 describe BOTNET_OCNNEJP Ocn.ne.jp customer address
214 meta BOTNET_OCNNEJP (__BOTNET_OCNNEJP && __BOTNET_NOTRUST)
215 header __BOTNET_OCNNEJP X-Spam-Relays-Untrusted =~ /^[^\]]+ rdns=p\d{4}-ip\S*\.ocn\.ne\.jp\b/i
216 score BOTNET_OCNNEJP 5.0
217
218 # If the message was authenticated or hit a trusted host, then we want to
219 # exempt these 'non-module' rules.
220 describe __BOTNET_NOTRUST Message has no trusted relays
221 header __BOTNET_NOTRUST X-Spam-Relays-Trusted !~ /ip=/i
222 Botnet.credits.txt0000444000000000000060000000120310542467656014422 0ustar rootmail00000000000000
223 People who have given suggestions, bug reports, feedback, and other help
224 in the development of the Botnet plugin:
225
226 Mark Boolootian
227 Terry Figel
228 Josh Homan
229 Paul Tatarsky
230 Jim Warner
231 David F. Skoll
232 Chris (Pollock?)
233 John D. Hardin
234 Bret Miller
235 Jonas Eckerman
236 Tom Shaw
237 Craig Morrison
238 Patrick Snyers
239 Jeff Mincy
240 Rob Mangiafico
241 Till Klampaeckel
242 Loren Wilton
243 Daryl C.W. O'Shea
244 Mark Martinec
245 Dennis Davis
246 Bill Landry
247 Larry M. Rosenbaum
248 Ralf Hildebrandt
249 Michael Schaap
250 Chris/decoder
251 Chris Lear
252 Rene Berber
253 Billy Huddleston
254 Mark Nienberg
255 Carlos Horowicz
256 Federico Giannici
257 Phil Barnett
258 Steven Manross
259 Dylan Bouterse
260 Kosmaj
261 Derek Harding
262 Michael Alan Dorman
263 Botnet.pl0000555000000000000060000001132310655500746012562 0ustar rootmail00000000000000#!/usr/bin/perl
264
265 use Botnet;
266
267 my $ip = shift(@ARGV);
268 my $domain = shift(@ARGV);
269 my $max = shift(@ARGV);
270
271 my @clientwords = ('.*dsl.*', 'cable', 'catv', 'ddns', 'dhcp',
272 'dial(-?up)?', 'dip', 'docsis', 'dyn(amic)?(ip)?', 'modem', 'ppp(oe)?',
273 'res(net|ident(ial)?)?', 'bredband'
274 , 'client', 'fixed', 'ip', 'pool', 'static', 'user' # controversial ones
275 );
276
277 my $cwordre = '((\b|\d)' . join('(\b|\d))|((\b|\d)', @clientwords) . '(\b|\d))';
278
279 my @serverwords = ('e?mail(out)?', 'mta', 'mx(pool)?', 'relay', 'smtp'
280 , 'exch(ange)?'
281 );
282
283 my $swordre = '((\b|\d)' . join('(\b|\d))|((\b|\d)', @serverwords) . '(\b|\d))';
284
285 my ($word, $i, $temp, $tests);
286 my ($rdns, $baddns, $client, $soho, $cwords, $swords, $iphost);
287 $rdns = $baddns = $client = $soho = $cwords = $swords = $iphost = 0;
288
289 if (defined($ip)) {
290 $ip =~ s/^\[//;
291 $ip =~ s/\]$//;
292 }
293 else {
294 print "usage: $0 ip-address [maximum]\n";
295 exit(1);
296 }
297
298
299 unless ($ip =~ /^\d+\.\d+\.\d+\.\d+$/) {
300 print "must be a ipv4 ip-address\n";
301 exit(1);
302 }
303
304 my $version = Mail::SpamAssassin::Plugin::Botnet::get_version();
305
306 print "Botnet Version = " . $version . "\n";
307
308 print "checking IP address: $ip\n";
309
310 unless (defined $domain) {
311 $domain = "";
312 }
313
314 if ($domain ne "") {
315 print "checking mail domain: $domain\n";
316 }
317
318 unless ((defined ($max)) && ($max =~ /^\d+$/)) {
319 $max = 5;
320 }
321
322
323
324 my $hostname = Mail::SpamAssassin::Plugin::Botnet::get_rdns($ip);
325
326 if ($hostname eq "") {
327 $rdns = 1;
328 print " BOTNET_NORDNS: hit\n";
329 }
330 else {
331 print " BOTNET_NORDNS: not hit - $hostname\n";
332 }
333
334 if ( ($hostname ne "") &&
335 (Mail::SpamAssassin::Plugin::Botnet::check_dns($hostname, $ip, "A", "-1"))) {
336 print " BOTNET_BADDNS: not hit - hostname resolves back to ip\n";
337 }
338 elsif ($hostname ne "") {
339 print " BOTNET_BADDNS: hit - hostname doesn't resolve back to ip\n";
340 $baddns = 1;
341 }
342 else {
343 print " BOTNET_BADDNS: not hit\n";
344 }
345
346
347 #print " BOTNET_CLIENT:\n";
348 if (Mail::SpamAssassin::Plugin::Botnet::check_ipinhostname($hostname, $ip)) {
349 print " BOTNET_IPINHOSTNAME: hit\n";
350 $iphost = 1;
351 }
352 else {
353 print " BOTNET_IPINHOSTNAME: not hit\n";
354 }
355
356 #print " BOTNET_CLIENTWORDS:\n";
357 $i = 0; $tests = "";
358 foreach $word (@clientwords) {
359 $temp = '((\b|\d)' . $word . '(\b|\d))';
360 if (Mail::SpamAssassin::Plugin::Botnet::check_words($hostname, $temp)) {
361 #print " hostname matched $word\n";
362 $tests .= $word . " ";
363 $i++;
364 }
365 }
366
367 $tests =~ s/ $//;
368
369 if ($i) {
370 print " BOTNET_CLIENTWORDS: hit, matches=$tests\n";
371 $cwords = 1;
372 }
373 else {
374 print " BOTNET_CLIENTWORDS: not hit\n";
375 }
376
377 #print " BOTNET_SERVERWORDS:\n";
378 $i = 0; $tests = "";
379 foreach $word (@serverwords) {
380 $temp = '((\b|\d)' . $word . '(\b|\d))';
381 if (Mail::SpamAssassin::Plugin::Botnet::check_words($hostname, $temp)) {
382 #print " hostname matched $word\n";
383 $tests .= $word . " ";
384 $i++;
385 }
386 }
387
388 $tests =~ s/ $//;
389
390 if ($i) {
391 print " BOTNET_SERVERWORDS: hit, matches=$tests\n";
392 $swords = 1;
393 }
394 else {
395 print " BOTNET_SERVERWORDS: not hit\n";
396 }
397
398 if ((! $swords) && ($cwords || $iphost)) {
399 $client = 1;
400 print " BOTNET_CLIENT (meta) hit\n";
401 }
402 elsif ($swords && ($cwords || $iphost)) {
403 print " BOTNET_CLIENT (meta) not hit, BOTNET_SERVERWORDS exemption\n";
404 }
405 else {
406 print " BOTNET_CLIENT (meta) not hit\n";
407 }
408
409 $tests = "";
410 if (Mail::SpamAssassin::Plugin::Botnet::check_client($hostname, $ip, $cwordre,
411 $swordre, \$tests)) {
412 $tests = "none" if ($tests eq "");
413 print " BOTNET_CLIENT (code) hit, tests=$tests\n";
414 }
415 else {
416 $tests = "none" if ($tests eq "");
417 print " BOTNET_CLIENT (code) not hit, tests=$tests\n";
418 }
419
420 if (($domain ne "") && ($hostname ne $domain)) {
421 if (Mail::SpamAssassin::Plugin::Botnet::check_soho($hostname, $ip,
422 $domain, "")) {
423 $soho = 1;
424 print " BOTNET_SOHO: hit\n";
425 }
426 else {
427 print " BOTNET_SOHO: not hit\n";
428 }
429 }
430 elsif ($domain ne "") {
431 print " BOTNET_SOHO: skipped (hostname eq mail domain)\n";
432 }
433 else {
434 print " BOTNET_SOHO: skipped (no mail domain given)\n";
435 }
436
437 if ((! $soho) && ($rdns || $baddns || $client)) {
438 print "BOTNET (meta) hit\n";
439 }
440 else {
441 print "BOTNET (meta) not hit\n";
442 }
443
444 $tests = "";
445 if (Mail::SpamAssassin::Plugin::Botnet::check_botnet($hostname, $ip, $cwordre,
446 $swordre, $domain, $helo, \$tests)) {
447 $tests = "none" if ($tests eq "");
448 print "BOTNET (code) hit, tests=$tests\n";
449 }
450 else {
451 $tests = "none" if ($tests eq "");
452 print "BOTNET (code) not hit, tests=$tests\n";
453 }
454 Botnet.pm0000444000000000000060000006771010655477463012604 0ustar rootmail00000000000000
455 package Mail::SpamAssassin::Plugin::Botnet;
456
457 # Copyright (C) 2003 The Regents of the University of California
458 #
459 # This program is free software; you can redistribute it and/or modify
460 # it under the terms of the GNU General Public License as published by
461 # the Free Software Foundation; either version 2 of the License, or
462 # (at your option) any later version.
463 #
464 # This program is distributed in the hope that it will be useful,
465 # but WITHOUT ANY WARRANTY; without even the implied warranty of
466 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
467 # GNU General Public License for more details.
468 #
469 # You should have received a copy of the GNU General Public License
470 # along with this program; if not, write to the Free Software
471 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
472 #
473 # The Author, John Rudd, can be reached via email at
474 # jrudd@ucsc.edu
475 #
476
477
478 # Botnet - perform DNS validations on the first untrusted relay
479 # looking for signs of a Botnet infected host, such as no reverse
480 # DNS, a hostname that would indicate an ISP client or domain
481 # workstation, or other hosts that aren't intended to be acting as
482 # a direct mail submitter outside of their own domain.
483
484 use Socket;
485 use Net::DNS;
486 use Mail::SpamAssassin::Plugin;
487 use strict;
488 use warnings;
489 use vars qw(@ISA);
490 @ISA = qw(Mail::SpamAssassin::Plugin);
491 my $VERSION = 0.8;
492
493
494 sub new {
495 my ($class, $mailsa) = @_;
496
497 $class = ref($class) || $class;
498 my $self = $class->SUPER::new($mailsa);
499 bless ($self, $class);
500
501 Mail::SpamAssassin::Plugin::dbg("Botnet: version " . $VERSION);
502 $self->register_eval_rule("botnet_nordns");
503 $self->register_eval_rule("botnet_baddns");
504 $self->register_eval_rule("botnet_ipinhostname");
505 $self->register_eval_rule("botnet_clientwords");
506 $self->register_eval_rule("botnet_serverwords");
507 $self->register_eval_rule("botnet_soho");
508 $self->register_eval_rule("botnet_client");
509 $self->register_eval_rule("botnet");
510
511 $self->{main}->{conf}->{botnet_pass_auth} = 0;
512 $self->{main}->{conf}->{botnet_pass_trusted} = "public";
513 $self->{main}->{conf}->{botnet_skip_ip} = "";
514 $self->{main}->{conf}->{botnet_pass_ip} = "";
515 $self->{main}->{conf}->{botnet_pass_domains} = "";
516 $self->{main}->{conf}->{botnet_clientwords} = "";
517 $self->{main}->{conf}->{botnet_serverwords} = "";
518
519 return $self;
520 }
521
522
523 sub parse_config {
524 my ($self, $opts) = @_;
525 my ($temp);
526 my $key = $opts->{key};
527 my $value = $opts->{value};
528
529 if ( ($key eq "botnet_pass_auth") ||
530 ($key eq "botnet_pass_trusted") ) {
531 Mail::SpamAssassin::Plugin::dbg("Botnet: setting $key to $value");
532 $self->{main}->{conf}->{$key} = $value;
533 $self->inhibit_further_callbacks();
534 }
535 elsif ( ($key eq "botnet_skip_ip")
536 || ($key eq "botnet_pass_ip")
537 || ($key eq "botnet_pass_domains")
538 || ($key eq "botnet_clientwords")
539 || ($key eq "botnet_serverwords") ) {
540
541 foreach $temp (split(/\s+/, $value)) {
542 if ($temp eq "=") { next ; } # not sure why that happens
543
544 if ( ($key eq "botnet_clientwords") ||
545 ($key eq "botnet_pass_domains") ||
546 ($key eq "botnet_serverwords") ) {
547 $temp =~ s/\^//g; # remove any carets
548 $temp =~ s/\$//g; # remove any dollars
549 }
550
551 if ( ($key eq "botnet_clientwords") ||
552 ($key eq "botnet_serverwords") ) {
553 $temp = '(\b|\d)' . $temp . '(\b|\d)';
554 }
555
556 if (($key eq "botnet_pass_domains") && ($temp !~ /^\\(\.|A)/)) {
557 $temp = '(\.|\A)' . $temp;
558 }
559
560 if ($temp eq "") {
561 # don't add empty terms
562 next;
563 }
564
565 if ($key eq "botnet_pass_domains") {
566 # anchor each domain to end of string
567 $temp .= '$';
568 }
569
570 Mail::SpamAssassin::Plugin::dbg("Botnet: adding " . $temp
571 . " to $key");
572
573 if ($self->{main}->{conf}->{$key} ne "") {
574 $self->{main}->{conf}->{$key} =
575 $self->{main}->{conf}->{$key} . "|(" . $temp . ")";
576 }
577 else {
578 $self->{main}->{conf}->{$key} = "(" . $temp . ")";
579 }
580 }
581 $self->inhibit_further_callbacks();
582 }
583 else {
584 return 0;
585 }
586 return 1;
587 }
588
589
590 sub _botnet_get_relay {
591 my ($self, $pms) = @_;
592 my $msg = $pms->get_message();
593 my @untrusted = @{$msg->{metadata}->{relays_untrusted}};
594 my @trusted = @{$msg->{metadata}->{relays_trusted}};
595 my ($relay, $rdns, $ip, $auth, $tmp, $iaddr, $hostname, $helo);
596 my $skip_ip = $self->{main}->{conf}->{botnet_skip_ip};
597 my $pass_ip = $self->{main}->{conf}->{botnet_pass_ip};
598 my $pass_trusted = $self->{main}->{conf}->{botnet_pass_trusted};
599 my $pass_auth = $self->{main}->{conf}->{botnet_pass_auth};
600 my $pass_domains = '(?:' . $self->{main}->{conf}->{botnet_pass_domains} .
601 ')';
602 my $private_ips = '(?:^127\..*$|^10\..*$|^172\.1[6789]\..*$|' .
603 '^172\.2[0-9]\..*$|^172\.3[01]\..*$|^192\.168\..*$)';
604
605 # if there are any trusted relays, AND $pass_trusted is set to
606 # public, private, or any, or $pass_auth is true, then check the
607 # trusted relays for any pass conditions
608 if ( (defined($trusted[0]->{ip})) &&
609 (($pass_auth) ||
610 ($pass_trusted eq "any") ||
611 ($pass_trusted eq "public") ||
612 ($pass_trusted eq "private")) ) {
613 foreach $relay (@trusted) {
614 if ($pass_trusted eq "any") {
615 Mail::SpamAssassin::Plugin::dbg("Botnet: found any trusted");
616 return (0, "", "", "");
617 }
618 elsif (($pass_auth) && ($relay->{auth} ne "")) {
619 $auth = $relay->{auth};
620 Mail::SpamAssassin::Plugin::dbg("Botnet: Passed auth " . $auth);
621 return (0, "", "", "");
622 }
623 elsif ( ($pass_trusted eq "private") &&
624 ($relay->{ip} =~ /$private_ips/) ) {
625 Mail::SpamAssassin::Plugin::dbg("Botnet: found private trusted");
626 return (0, "", "", "");
627 }
628 elsif ( ($pass_trusted eq "public") &&
629 ($relay->{ip} !~ /$private_ips/) ) {
630 Mail::SpamAssassin::Plugin::dbg("Botnet: found public trusted");
631 return (0, "", "", "");
632 }
633 }
634 if ( ($pass_trusted eq "any") ||
635 ($pass_trusted eq "public") ||
636 ($pass_trusted eq "private") ) {
637 # didn't find what we were looking for above
638 Mail::SpamAssassin::Plugin::dbg("Botnet: " . $pass_trusted .
639 " trusted relays not found");
640 }
641 if ($pass_auth) {
642 Mail::SpamAssassin::Plugin::dbg("Botnet: authenticated and" .
643 " trusted relay not found");
644 }
645 }
646 elsif (!defined ($trusted[0]->{ip})) {
647 Mail::SpamAssassin::Plugin::dbg("Botnet: no trusted relays");
648 }
649
650 while (1) {
651 $relay = shift(@untrusted);
652
653 if (! defined ($relay)) {
654 Mail::SpamAssassin::Plugin::dbg("Botnet: All skipped/no untrusted");
655 return (0, "", "", "");
656 }
657
658 $ip = $relay->{ip};
659
660 if (! defined ($ip) ) {
661 Mail::SpamAssassin::Plugin::dbg("Botnet: All skipped/no untrusted");
662 return (0, "", "", "");
663 }
664 elsif (($pass_ip ne "") && ($ip =~ /(?:$pass_ip)/)) {
665 Mail::SpamAssassin::Plugin::dbg("Botnet: Passed ip $ip");
666 return (0, "", "", "");
667 }
668 elsif (($skip_ip ne "") && ($ip =~ /(?:$skip_ip)/)) {
669 Mail::SpamAssassin::Plugin::dbg("Botnet: Skipped ip $ip");
670 next;
671 }
672 ## I think we should only look for authenticated relays in the
673 ## trusted relays
674 #elsif (($pass_auth) && ($relay->{auth} ne "")) {
675 # $auth = $relay->{auth};
676 # Mail::SpamAssassin::Plugin::dbg("Botnet: Passed auth " . $auth);
677 # return (0, "", "", "");
678 # }
679 else {
680 if ((exists $relay->{rdns}) &&
681 ($relay->{rdns} ne "") &&
682 ($relay->{rdns} ne "-1")) {
683 # we've got this relay's RDNS
684 Mail::SpamAssassin::Plugin::dbg("Botnet: get_relay good RDNS");
685 $rdns = $relay->{rdns};
686 }
687 elsif ((exists $relay->{rdns}) && ($relay->{rdns} eq "-1")) {
688 # rdns = -1, which means we set it to that, because the
689 # MTA didn't include it, and then we couldn't find it on
690 # lookup, which means the IP addr REALLY doesn't have RDNS
691 Mail::SpamAssassin::Plugin::dbg("Botnet: get_relay -1 RDNS");
692 $rdns = "";
693 }
694 else {
695 # rdns hasn't been set in the hash, which means _either_
696 # the IP addr really doesn't have RDNS, _OR_ the MTA
697 # is lame, like CommuniGate Pro, and doesn't put the RDNS
698 # data into the Received header. So, we'll try to look
699 # it up _one_ time, and if we don't get anything we'll
700 # set the value in the hash to -1
701 Mail::SpamAssassin::Plugin::dbg(
702 "Botnet: get_relay didn't find RDNS");
703 $hostname = get_rdns($ip);
704
705 if ((defined $hostname) && ($hostname ne "")) {
706 $relay->{rdns} = $hostname;
707 $rdns = $hostname;
708 }
709 else {
710 $relay->{rdns} = "-1";
711 $rdns = "";
712 }
713 }
714 $helo = $relay->{helo};
715
716 Mail::SpamAssassin::Plugin::dbg("Botnet: IP is '$ip'");
717 Mail::SpamAssassin::Plugin::dbg("Botnet: RDNS is '$rdns'");
718 Mail::SpamAssassin::Plugin::dbg("Botnet: HELO is '$helo'");
719
720 if ($rdns =~ /(?:$pass_domains)/i) {
721 # is this a domain we exempt/pass?
722 Mail::SpamAssassin::Plugin::dbg("Botnet: pass_domain '$rdns'");
723 return(0, "", "", "");
724 }
725
726 return (1, $ip, $rdns, $helo);
727 }
728 }
729 }
730
731
732 sub botnet_nordns {
733 my ($self, $pms) = @_;
734 my ($code, $ip, $helo);
735 my $hostname = "";
736
737 Mail::SpamAssassin::Plugin::dbg("Botnet: checking NORDNS");
738
739 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
740 unless ($code) {
741 Mail::SpamAssassin::Plugin::dbg("Botnet: NORDNS skipped");
742 return 0;
743 }
744
745 if ($hostname eq "") {
746 # the IP address doesn't have a PTR record
747 $pms->test_log("botnet_nordns,ip=$ip");
748 Mail::SpamAssassin::Plugin::dbg("Botnet: NORDNS hit");
749 return (1);
750 }
751 Mail::SpamAssassin::Plugin::dbg("Botnet: NORDNS miss");
752 return (0);
753 }
754
755
756 sub botnet_baddns {
757 my ($self, $pms) = @_;
758 my ($code, $ip, $helo);
759 my $hostname = "";
760
761 Mail::SpamAssassin::Plugin::dbg("Botnet: checking BADDNS");
762
763 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
764 unless ($code) {
765 Mail::SpamAssassin::Plugin::dbg("Botnet: BADDNS skipped");
766 return 0;
767 }
768
769 if ($hostname eq "") {
770 # covered by NORDNS
771 Mail::SpamAssassin::Plugin::dbg("Botnet: BADDNS miss");
772 return (0);
773 }
774 elsif (check_dns($hostname, $ip, "A", -1)) {
775 # resolved the hostname
776 Mail::SpamAssassin::Plugin::dbg("Botnet: BADDNS miss");
777 return (0);
778 }
779 else {
780 # failed to resolve the hostname
781 $pms->test_log("botnet_baddns,ip=$ip,rdns=$hostname");
782 Mail::SpamAssassin::Plugin::dbg("Botnet: BADDNS hit");
783 return (1);
784 }
785 }
786
787
788 sub botnet_ipinhostname {
789 my ($self, $pms) = @_;
790 my ($code, $ip, $helo);
791 my $hostname = "";
792
793 Mail::SpamAssassin::Plugin::dbg("Botnet: checking IPINHOSTNAME");
794
795 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
796 unless ($code) {
797 Mail::SpamAssassin::Plugin::dbg("Botnet: IPINHOSTNAME skipped");
798 return 0;
799 }
800
801 if ($hostname eq "") {
802 # covered by NORDNS
803 Mail::SpamAssassin::Plugin::dbg("Botnet: IPINHOSTNAME miss");
804 return (0);
805 }
806 elsif (check_ipinhostname($hostname, $ip)) {
807 $pms->test_log("botnet_ipinhosntame,ip=$ip,rdns=$hostname");
808 Mail::SpamAssassin::Plugin::dbg("Botnet: IPINHOSTNAME hit");
809 return(1);
810 }
811 else {
812 Mail::SpamAssassin::Plugin::dbg("Botnet: IPINHOSTNAME miss");
813 return (0);
814 }
815 }
816
817
818 sub botnet_clientwords {
819 my ($self, $pms) = @_;
820 my ($code, $ip, $helo);
821 my $hostname = "";
822 my $wordre = $self->{main}->{conf}->{botnet_clientwords};
823
824 Mail::SpamAssassin::Plugin::dbg("Botnet: checking CLIENTWORDS");
825
826 if ($wordre eq "") {
827 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENTWORDS miss");
828 return (0);
829 }
830 else {
831 Mail::SpamAssassin::Plugin::dbg("Botnet: client words regexp is" .
832 $wordre);
833 }
834
835 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
836 unless ($code) {
837 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENTWORDS skipped");
838 return 0;
839 }
840
841 if ($hostname eq "") {
842 # covered by NORDNS
843 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENTWORDS miss");
844 return (0);
845 }
846 elsif (check_words($hostname, $wordre)) {
847 # hostname contains client keywords, outside of the registered domain
848 $pms->test_log("botnet_clientwords,ip=$ip,rdns=$hostname");
849 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENTWORDS hit");
850 return (1);
851 }
852 else {
853 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENTWORDS miss");
854 return (0);
855 }
856 }
857
858
859 sub botnet_serverwords {
860 my ($self, $pms) = @_;
861 my ($code, $ip, $helo);
862 my $hostname = "";
863 my $wordre = $self->{main}->{conf}->{botnet_serverwords};
864
865 Mail::SpamAssassin::Plugin::dbg("Botnet: checking SERVERWORDS");
866
867 if ($wordre eq "") {
868 Mail::SpamAssassin::Plugin::dbg("Botnet: SERVERWORDS miss");
869 return (0);
870 }
871 else {
872 Mail::SpamAssassin::Plugin::dbg("Botnet: server words list is" .
873 $wordre);
874 }
875
876 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
877 unless ($code) {
878 Mail::SpamAssassin::Plugin::dbg("Botnet: SERVERWORDS skipped");
879 return 0;
880 }
881
882 if ($hostname eq "") {
883 # covered by NORDNS
884 Mail::SpamAssassin::Plugin::dbg("Botnet: SERVERWORDS miss");
885 return (0);
886 }
887 elsif (check_words($hostname, $wordre)) {
888 # hostname contains server keywords outside of the registered domain
889 $pms->test_log("botnet_serverwords,ip=$ip,rdns=$hostname");
890 Mail::SpamAssassin::Plugin::dbg("Botnet: SERVERWORDS hit");
891 return (1);
892 }
893 else {
894 Mail::SpamAssassin::Plugin::dbg("Botnet: SERVERWORDS miss");
895 return (0);
896 }
897 }
898
899
900 sub botnet_soho {
901 my ($self, $pms) = @_;
902 my ($code, $ip, $helo);
903 my $hostname = "";
904 my ($sender, $user, $domain);
905
906 Mail::SpamAssassin::Plugin::dbg("Botnet: checking for SOHO server");
907
908 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
909 unless ($code) {
910 Mail::SpamAssassin::Plugin::dbg("Botnet: SOHO skipped");
911 return 0;
912 }
913
914 if (defined ($sender = $pms->get("EnvelopeFrom"))) {
915 Mail::SpamAssassin::Plugin::dbg("Botnet: EnvelopeFrom is " . $sender);
916 }
917 elsif (defined ($sender = $pms->get("Return-Path:addr"))) {
918 Mail::SpamAssassin::Plugin::dbg("Botnet: Return-Path is " . $sender);
919 }
920 elsif (defined ($sender = $pms->get("From:addr"))) {
921 Mail::SpamAssassin::Plugin::dbg("Botnet: From is " . $sender);
922 }
923 else {
924 Mail::SpamAssassin::Plugin::dbg("Botnet: no sender");
925 Mail::SpamAssassin::Plugin::dbg("Botnet: SOHO miss");
926 return 0;
927 }
928
929 ($user, $domain) = split (/\@/, $sender);
930
931 if ( (defined ($domain)) &&
932 ($domain ne "") &&
933 (check_soho($hostname, $ip, $domain, $helo)) ) {
934 # looks like a SOHO mail server
935 $pms->test_log("botnet_soho,ip=$ip,maildomain=$domain,helo=$helo");
936 Mail::SpamAssassin::Plugin::dbg("Botnet: mail domain is " . $domain);
937 Mail::SpamAssassin::Plugin::dbg("Botnet: SOHO hit");
938 return 1;
939 }
940 elsif ( (defined($domain)) && ($domain ne "")) {
941 # does not look lik a SOHO mail server
942 Mail::SpamAssassin::Plugin::dbg("Botnet: mail domain is " . $domain);
943 Mail::SpamAssassin::Plugin::dbg("Botnet: SOHO miss");
944 return 0;
945 }
946 else {
947 # no domain
948 Mail::SpamAssassin::Plugin::dbg("Botnet: no sender domain");
949 Mail::SpamAssassin::Plugin::dbg("Botnet: SOHO miss");
950 return 0;
951 }
952 # shouldn't get here
953 Mail::SpamAssassin::Plugin::dbg("Botnet: SOHO miss");
954 return (0);
955 }
956
957
958 sub botnet_client {
959 my ($self, $pms) = @_;
960 my ($code, $ip, $helo);
961 my $hostname = "";
962 my $cwordre = $self->{main}->{conf}->{botnet_clientwords};
963 my $swordre = $self->{main}->{conf}->{botnet_serverwords};
964 my $tests = 0;
965
966 Mail::SpamAssassin::Plugin::dbg("Botnet: checking for CLIENT");
967
968 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
969 unless ($code) {
970 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENT skipped");
971 return 0;
972 }
973
974 if (check_client($hostname, $ip, $cwordre, $swordre, \$tests)) {
975 $pms->test_log("botnet_client,ip=$ip,rdns=$hostname," . $tests);
976 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENT hit (" . $tests . ")");
977 return 1;
978 }
979 else {
980 $tests = "none" if ($tests eq "");
981 Mail::SpamAssassin::Plugin::dbg("Botnet: CLIENT miss (" . $tests . ")");
982 return 0;
983 }
984 }
985
986
987 sub botnet {
988 my ($self, $pms) = @_;
989 my ($code, $ip, $helo);
990 my $hostname = "";
991 my $cwordre = $self->{main}->{conf}->{botnet_clientwords};
992 my $swordre = $self->{main}->{conf}->{botnet_serverwords};
993 my ($sender, $user, $domain);
994 my $tests = "";
995
996 Mail::SpamAssassin::Plugin::dbg("Botnet: starting");
997
998 ($code, $ip, $hostname, $helo) = $self->_botnet_get_relay($pms);
999 unless ($code) {
1000 Mail::SpamAssassin::Plugin::dbg("Botnet: skipping");
1001 return 0;
1002 }
1003
1004 if ( (defined ($sender = $pms->get("EnvelopeFrom"))) ||
1005 (defined ($sender = $pms->get("Return-Path:addr"))) ||
1006 (defined ($sender = $pms->get("From:addr"))) ) {
1007 # if we find a sender
1008 Mail::SpamAssassin::Plugin::dbg("Botnet: sender '$sender'");
1009 ($user, $domain) = split (/\@/, $sender);
1010 unless (defined $domain) { $domain = ""; }
1011 }
1012 else {
1013 $domain = "";
1014 }
1015
1016 if (check_botnet($hostname, $ip, $cwordre, $swordre,
1017 $domain, $helo, \$tests)) {
1018 if (($tests =~ /nordns/) && ($domain eq "")) {
1019 $pms->test_log("botnet" . $VERSION . ",ip=$ip," . $tests);
1020 }
1021 elsif ($tests =~ /nordns/) { # could use "eq", but used "=~" to be safe
1022 $pms->test_log("botnet" . $VERSION . ",ip=$ip,maildomain=$domain," .
1023 $tests);
1024 }
1025 elsif ($domain eq "") {
1026 $pms->test_log("botnet" . $VERSION . ",ip=$ip,rdns=$hostname," .
1027 $tests);
1028 }
1029 else {
1030 $pms->test_log("botnet" . $VERSION . ",ip=$ip,rdns=$hostname," .
1031 "maildomain=$domain," . $tests);
1032 }
1033 Mail::SpamAssassin::Plugin::dbg("Botnet: hit (" . $tests . ")");
1034 return 1;
1035 }
1036 else {
1037 $tests = "none" if ($tests eq "");
1038 Mail::SpamAssassin::Plugin::dbg("Botnet: miss (" . $tests . ")");
1039 return 0;
1040 }
1041 }
1042
1043
1044 sub check_client {
1045 my ($hostname, $ip, $cwordre, $swordre, $tests) = @_;
1046 my $iphost = check_ipinhostname($hostname, $ip);
1047 my $cwords = check_words($hostname, $cwordre);
1048
1049 if (defined ($tests)) {
1050 if ($iphost && $cwords) { $$tests = "ipinhostname,clientwords"; }
1051 elsif ($iphost) { $$tests = "ipinhostname"; }
1052 elsif ($cwords) { $$tests = "clientwords"; }
1053 else { $$tests = ""; }
1054 }
1055
1056 if ( ($iphost || $cwords) && # only run swordsre check if necessary
1057 (check_words($hostname, $swordre)) ) {
1058 if (defined ($tests)) { $$tests = $$tests . ",serverwords"; }
1059 return 0;
1060 }
1061 elsif ($iphost || $cwords) {
1062 return 1;
1063 }
1064 else {
1065 return 0;
1066 }
1067 }
1068
1069
1070 sub check_botnet {
1071 my ($hostname, $ip, $cwordre, $swordre, $domain, $helo, $tests) = @_;
1072 my ($baddns, $client, $temp);
1073
1074 if ($hostname eq "") {
1075 if (defined ($tests)) { $$tests = "nordns"; }
1076 return 1;
1077 }
1078
1079 $baddns = ! (check_dns($hostname, $ip, "A", -1));
1080
1081 $client = check_client($hostname, $ip, $cwordre, $swordre, \$temp);
1082
1083 if (defined ($tests)) {
1084 if ($baddns && $client) { $$tests = "baddns,client," . $temp; }
1085 elsif ($baddns) { $$tests = "baddns"; }
1086 elsif ($client) { $$tests = "client," . $temp; }
1087 else { $$tests = ""; }
1088 }
1089
1090 # if the above things triggered, check for soho mail server
1091 if ( ($baddns || $client) && # only run soho check if necessary
1092 (check_soho($hostname, $ip, $domain, $helo)) ) {
1093 # looks like a SOHO mail server
1094 if (defined ($tests)) { $$tests = $$tests . ",soho"; }
1095 return 0;
1096 }
1097 elsif ($baddns || $client) {
1098 return 1;
1099 }
1100 else {
1101 return 0;
1102 }
1103 }
1104
1105
1106 sub check_soho {
1107 my ($hostname, $ip, $domain, $helo) = @_;
1108
1109 if ((defined $domain) && ($domain ne "")) {
1110 if ( (defined ($hostname)) && (lc($hostname) eq lc($domain)) ) {
1111 # if the mail domain is the hostname, and the hostname looks
1112 # like a botnet (or we wouldn't have gotten here), then it's
1113 # probably a botnet attempting to abuse the soho exemption
1114 return 0;
1115 }
1116 elsif (check_dns($domain, $ip, "A", 5)) {
1117 # we only check 5 because we expect a SOHO to not have a huge
1118 # round-robin DNS A record
1119 return (1);
1120 }
1121 # I don't like the suggested HELO check, because the HELO string is
1122 # within the botnet coder's control, and thus cannot be relied upon,
1123 # so I have commented out the code for it. I have left it here, its
1124 # head upon a pike, as a warning, and so everyone knows it wasn't
1125 # an oversight.
1126 # 0.8 update: I give an exemption above based on the mail domain,
1127 # and the botnet coder has as much control over that as they do
1128 # over the HELO string. So, under the same circumstances (HELO !=
1129 # Hostname) I'll let the HELO string act in the same capacity as
1130 # the mail domain.
1131 elsif ( (defined $helo) && (defined $hostname) &&
1132 (lc($hostname) ne lc($helo)) &&
1133 ($helo ne "") &&
1134 (check_dns($helo, $ip, "A", 5)) ) {
1135 # we only check 5 because we expect a SOHO to not have a huge
1136 # round-robin DNS A record
1137 return (1);
1138 }
1139 elsif (check_dns($domain, $ip, "MX", 5)) {
1140 # we only check 5 because we expect a SOHO to not have a huge
1141 # number of MX hosts
1142 return (1);
1143 }
1144 return (0);
1145 }
1146 else {
1147 return (0);
1148 }
1149 # shouldn't get here
1150 return (0);
1151 }
1152
1153
1154 sub check_dns {
1155 my ($name, $ip, $type, $max) = @_;
1156 my ($resolver, $query, $rr, $i, @a);
1157
1158 if ( (defined $name) &&
1159 ($name ne "") &&
1160 (defined $ip) &&
1161 ($ip =~ /^\d+\.\d+\.\d+\.\d+$/) &&
1162 (defined $type) &&
1163 ($type =~ /^(?:A|MX)$/) &&
1164 (defined $max) &&
1165 ($max =~ /^-?\d+$/) ) {
1166 $resolver = Net::DNS::Resolver->new();
1167 if ($query = $resolver->search($name, $type)) {
1168 # found matches
1169 $i = 0;
1170 foreach $rr ($query->answer()) {
1171 $i++;
1172 if (($max != -1) && ($i >= $max)) {
1173 # max == -1 means "check all of the records"
1174 # $ip isn't in the first $max A records for $name
1175 return(0);
1176 }
1177 elsif (($type eq "A") && ($rr->type eq "A")) {
1178 if ($rr->address eq $ip) {
1179 # $name resolves back to this ip addr
1180 return(1);
1181 }
1182 }
1183 elsif (($type eq "MX") && ($rr->type eq "MX")) {
1184 if (check_dns($rr->exchange, $ip, "A", $max)) {
1185 # found $ip in the first MX hosts for $domain
1186 return(1);
1187 }
1188 }
1189 }
1190 # $ip isn't in the A records for $name at all
1191 return(0);
1192 }
1193 else {
1194 # the sender leads to a host that doesn't have an A record
1195 return (0);
1196 }
1197 }
1198 # can't resolve an empty name nor ip that doesn't look like an address
1199 return (0);
1200 }
1201
1202
1203 sub check_ipinhostname {
1204 # check for 2 octets of the IP address within the hostname, in
1205 # hexidecimal or decimal format, with zero padding or not, and with
1206 # optional spacing or not. And, for decimal format, check for
1207 # combined decimal values (ex: 3rd octet * 256 + 4th octet)
1208 my ($name, $ip) = @_;
1209 my ($a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n);
1210
1211 unless ( (defined ($name)) && ($name ne "") ) { return 0; }
1212
1213 ($a, $b, $c, $d) = split(/\./, $ip); # decimal octets
1214
1215 # permutations of combined decimal octets into single decimal values
1216 $e = ($a * 256 * 256 * 256) + ($b * 256 * 256)
1217 + ($c * 256) + $d; # all 4 octets
1218 $f = ($a * 256 * 256) + ($b * 256) + $c; # first 3 octets
1219 $g = ($b * 256 * 256) + ($c * 256) + $d; # last 3 octets
1220 $h = ($a * 256) + $b; # first 2 octets
1221 $i = ($b * 256) + $c; # middle 2 octets
1222 $j = ($c * 256) + $d; # last 2 octets
1223
1224 # hex versions of the ip address octets, in lower case
1225 # we don't need combined hex octets, as they'll
1226 # just look like sequential individual octets
1227 $k = sprintf("%02x", $a); # first octet
1228 $l = sprintf("%02x", $b); # second octet
1229 $m = sprintf("%02x", $c); # third octet
1230 $n = sprintf("%02x", $d); # fourth octet
1231
1232 #$k = lc (sprintf("%02x", $a)); # first octet
1233 #$l = lc (sprintf("%02x", $b)); # second octet
1234 #$m = lc (sprintf("%02x", $c)); # third octet
1235 #$n = lc (sprintf("%02x", $d)); # fourth octet
1236 #
1237 #$name = lc ($name); # so that we're all lower case
1238
1239 return check_words($name, "$a.*$b|$b.*$c|$c.*$d|$d.*$c|$c.*$b|$b.*$a|" .
1240 "$n.*$m|$m.*$l|$l.*$k|$k.*$l|$l.*$m|$m.*$n|" .
1241 "$e|$f|$g|$h|$i|$j");
1242
1243 #"(?:$a.*$b|$b.*$c|$c.*$d|$n.*$m|$m.*$l|$l.*$k|$k.*$l|$l.*$m|" .
1244 # "$m.*$n|$e|$f|$g|$h|$i|$j)" . ".*\..+\..+$';
1245
1246 #if ( ($name =~ /(?:$a.*$b|$b.*$c|$c.*$d).*\..+\..+$/) ||
1247 # ($name =~ /(?:$d.*$c|$c.*$b|$b.*$a).*\..+\..+$/) ||
1248 # ($name =~ /(?:$n.*$m|$m.*$l|$l.*$k).*\..+\..+$/) ||
1249 # ($name =~ /(?:$k.*$l|$l.*$m|$m.*$n).*\..+\..+$/) ||
1250 # ($name =~ /(?:$e|$f|$g|$h|$i|$j).*\..+\..+$/ ) ) {
1251 # # hostname contains two or more octets of its own IP addr
1252 # # in hex or decimal form, with or w/o leading 0's or separators
1253 # # but don't check in the tld nor registered domain
1254 # # probably a spambot since this is an untrusted relay
1255 # return(1);
1256 # }
1257 #
1258 #return(0);
1259 }
1260
1261
1262 sub check_words {
1263 # check for words outside of the top 2 levels of the hostname
1264 my ($name, $wordre) = @_;
1265 my $wordexp = '(' . $wordre . ')\S*\.\S+\.\S+$';
1266
1267 return check_hostname($name, $wordexp);
1268 #if (($name ne "") && ($wordre ne "") && ($name =~ /(?:$wordexp)/i) ) {
1269 # return (1);
1270 # }
1271 #return (0);
1272 }
1273
1274
1275 sub check_hostname {
1276 # check for an expression within the entire hostname
1277 my ($name, $regexp) = @_;
1278
1279 if (($name ne "") && ($regexp ne "") && ($name =~ /(?:$regexp)/i) ) {
1280 return (1);
1281 }
1282 return (0);
1283 }
1284
1285
1286 sub get_rdns {
1287 my ($ip) = @_;
1288 my ($query, @answer, $rr);
1289 my $resolver = Net::DNS::Resolver->new();
1290 my $name = "";
1291
1292 if ($query = $resolver->query($ip, 'PTR', 'IN')) {
1293 @answer = $query->answer();
1294 #if ($answer[0]->type eq "PTR") {
1295 # # just return the first one, even if it returns many
1296 # $name = $answer[0]->ptrdname();
1297 # }
1298
1299 # just return the first PTR record, even if it returns many
1300 foreach $rr (@answer) {
1301 if ($rr->type eq "PTR") {
1302 return ($rr->ptrdname());
1303 }
1304 }
1305 }
1306 return "";
1307 }
1308
1309
1310 sub get_version {
1311 return $VERSION;
1312 }
1313
1314
1315 1;
1316 Botnet.txt0000444000000000000060000001625710540561104012762 0ustar rootmail00000000000000Plugin: Botnet
1317
1318 Botnet looks for possible botnet sources of email by checking
1319 various DNS values that indicate things such as other ISP's clients or
1320 workstations, or misconfigured DNS settings that are more likely to happen
1321 with client or workstation addresses than servers.
1322
1323 Botnet looks in the Untrusted Relays pseudoheader. It defaults to
1324 looking at the first relay in that list. However, certain options allow
1325 it to skip past relays in that list (or not score a hit if it finds certain
1326 relays).
1327
1328 Installing:
1329 Copy Botnet.pm and Botnet.cf into /etc/mail/spamassassin (or whatever
1330 directory you use for your plugins). If you use something like
1331 spamc/spamd, mailscanner, or a milter, you probably need to restart
1332 that. From there, it should "just work".
1333
1334
1335 Rule: BOTNET_NORDNS
1336 The relay has no PTR record (no reverse dns). This rule does NOT incur
1337 a DNS check, as Botnet obtains this invormation from the rdns= field in
1338 SpamAssassin's Untrusted Relays pseudo-header.
1339
1340 Rule: BOTNET_BADDNS
1341 The relay doesn't have a full circle DNS. Full circle DNS means that,
1342 starting with the relay's IP address, going to its PTR record, and then
1343 looking at the IPs returned from that hostname's A record, is the relay's
1344 IP address in that group if addresses? If it isn't, then there's probably
1345 a DNS forgery.
1346 Note: BOTNET_BADDNS causes Botnet to do a DNS lookup. This can be time
1347 consuming for your SpamAssassin Checks.
1348
1349 Rule: BOTNET_IPINHOSTNAME
1350 Does the relay's hostname contain 2 or more octets of its IP address
1351 within the hostname? They can be in decimal or hexadecimal format. Each
1352 octet can have leading zeroes, or a single separator character.
1353
1354 Rule: BOTNET_CLIENTWORDS
1355 Does the relay's hostname contain certain keywords that look like a
1356 client hostname? They can be any keywords, but the included list is intended
1357 to identify ISP end clients and dynamic workstations.
1358
1359 Rule: BOTNET_SERVERWORDS
1360 Does the relay's hostname contain certain keywords that look like a mail
1361 server hostname? They can be any keywords, but the included list is intended
1362 to identify exceptions to the BOTNET_IPINHOSTNAME and BOTNET_CLIENTWORDS
1363 checks, that might indicate they actually are legitimate mail servers.
1364
1365 Rule: BOTNET_CLIENT
1366 This rule duplicates the checks in BOTNET_IPINHOSTNAME, BOTNET_CLIENTWORDS,
1367 and BOTNET_SERVERWORDS to decide whether or not the hostname looks
1368 like a client.
1369 It is effectively (!serverwords && (iphostname || clientwords))
1370 See Botnet.variants.txt for a way to replace this a meta rule.
1371
1372 Rule: BOTNET_SOHO
1373 This rule checks to see if the relay is possibly a SOHO (small office,
1374 home office) mail server. In this case, the sender's mail domain is examined,
1375 and resolved. First an A record look up is done, and if the relay's IP
1376 address is found in the first 5, then BOTNET_SOHO hits. Second, the same
1377 check is done on the MX records for the domain, again limited to 5 records.
1378 These checks are limited to 5 records because a SOHO domain is not likely
1379 to have a large round-robin A record nor a large number of MX records. In
1380 order to avoid having this check used as a back-door by botnet coders, by
1381 using a throw-away sender domain that has all of its botnet hosts in the
1382 A records or MX records, BOTNET_SOHO only looks at 5 records.
1383
1384 Rule: BOTNET
1385 This rule duplicates the checks done by the above rules.
1386 The intent is to flag a message automatically for quarantine or storage
1387 in a spam folder if the message does have the fingerprints of a spambot
1388 or virusbot, but does NOT have the fingerprints of a server.
1389 It is effectively (!soho && (client || baddns || nordns))
1390 See Botnet.variants.txt for a way to replace this with a meta rule, or
1391 replace this with piece-meal rules.
1392
1393
1394 Option: botnet_pass_auth (1|0)
1395 If the untrusted relay being considered performed SMTP-AUTH, (the auth
1396 field is not empty), then Botnet will not score a hit if this setting is
1397 non-zero. Defaults to 0 (off).
1398
1399 Option: botnet_pass_trusted (any|public|private|ignore)
1400 If there are trusted relays (received headers that match the trusted
1401 networks, before getting to a received header that doesn't match the
1402 trusted networks), then pass the message through Botnet without matching
1403 any rules, IF it matches the critereon of this option. If the option is
1404 set to "any", then pass the message if there are any trusted relays. If
1405 the option is set to "private", then pass the message if there are any
1406 relays from localhost and/or RFC-1918 reserved IP addresses (10.*, etc.).
1407 If the option is set to "public", then pass the message if there are any
1408 relays that are neither localhost nor RFC-1918 reserved. If the option
1409 is set to "ignore" (or, really, anything other than "any", "public", or
1410 "private"), then ignore the trusted relays. Defaults to "public".
1411
1412 Option: botnet_skip_ip (regular-expression)
1413 A regular expression that will cause Botnet to move to the NEXT
1414 untrusted relay if the current one's IP address matches the expression.
1415 Multiple entries are ORed together. Multiple entries may be space delimited
1416 or made with multiple lines. Defaults to empty (no IPs will be skipped).
1417
1418 Option: botnet_pass_ip (regular-expression)
1419 A regular expression that will cause Botnet to not score a hit
1420 if the current relay's IP address matches the expression. All Botnet tests
1421 will return 0. Multiple entries are ORed together. Multiple entries may be
1422 space delimited or made with multiple lines. Defaults to empty (no IPs will
1423 be passed without checking).
1424
1425 Option: botnet_pass_domains (regular-expression)
1426 A regular expression that will cause Botnet to not score a hit if the
1427 current relay's hostname matches the expression. The expression is
1428 automatically anchored with a $, so it will only match the end of the
1429 hostname, and prepended with "(\.|\A)". If the relay has RDNS, the expression
1430 will not match at the beginning nor end of the hostname (carets are removed
1431 from the expression). All Botnet tests will return 0 if the RDNS matches
1432 the expression. Multiple entries are ORed together. Multiple entries may be
1433 space delimited or made with multiple lines. Defaults to empty (no domain
1434 names will be passed without checking).
1435
1436 Note: if the RDNS owner tricks botnet_pass_domains, by putting these domains
1437 into their PTR record, then:
1438 a) it's probably a direct spammer and not a botnet anyway, and
1439 b) there are other spam assassin rules for dealing with those issues.
1440
1441
1442 Option: botnet_clientwords (regular-expression)
1443 Space delimited list of regexps that are indicate an end client or
1444 dynamic host which should not directly connect to other mail servers
1445 besides its own provider's. Multiple entries are ORed together. Multiple
1446 entries may be space delimited or made with multiple lines. Defaults
1447 to empty (no client word check will be done). The example cf file comes
1448 with a basic entry, however. The expressions will not match against the
1449 top two domains (the TLD and usually the registed domain). All word
1450 expressions have (\b|\d) added to the beginning and end, to ensure they
1451 are not sub-words of larger words.
1452
1453 Option: botnet_serverwords (regular-expression)
1454 Same as above, but for hostname words that indicate it might NOT
1455 be a client, but is, instead, an actual mail server. Such as "mail" or
1456 "smtp" being in the hostname.
1457
1458 Botnet.variants.txt0000444000000000000060000001111410541144111014567 0ustar rootmail00000000000000
1459 ===========================
1460 Skipping some Botnet checks
1461 ===========================
1462
1463 If you want to skip some Botnet checks, but not all of them, such as
1464 BOTNET_BADDNS, then you'll need to use the piece-meal rules variation,
1465 and replace BOTNET and/or BOTNET_CLIENT with meta rules.
1466
1467
1468 ==========================
1469 BOTNET as piece-meal rules
1470 ==========================
1471
1472 set the following scores as shown:
1473
1474 score BOTNET 0.0
1475 score BOTNET_CLIENT 0.0
1476
1477 And set the following scores as you want to weight them (be sure to keep
1478 BOTNET_SOHO and BOTNET_SERVERWORDS as negateive numbers):
1479
1480 score BOTNET_SOHO -0.01
1481 score BOTNET_NORDNS 0.01
1482 score BOTNET_BADDNS 0.00 0.01 0.00 0.01
1483 score BOTNET_IPINHOSTNAME 0.01
1484 score BOTNET_CLIENTWORDS 0.01
1485 score BOTNET_SERVERWORDS -0.01
1486
1487
1488 ============================
1489 BOTNET_CLIENT as a meta rule
1490 ============================
1491
1492 The old style for these two rules was to do them as meta rules.
1493 To set this up, replace the following line:
1494
1495 header BOTNET_CLIENT eval:botnet_client()
1496
1497 with:
1498
1499 meta BOTNET_CLIENT (!BOTNET_SERVERWORDS && (BOTNET_IPINHOSTNAME || BOTNET_CLIENTWORDS)
1500
1501 and, last, set the following scores:
1502
1503 score BOTNET_IPINHOSTNAME 0.01
1504 score BOTNET_CLIENTWORDS 0.01
1505 score BOTNET_SERVERWORDS -0.01
1506
1507
1508 =====================
1509 BOTNET as a meta rule
1510 =====================
1511
1512 The old style for these two rules was to do them as meta rules.
1513 To set this up, replace the following line:
1514
1515 header BOTNET eval:botnet()
1516
1517 with:
1518
1519 meta BOTNET (!BOTNET_SOHO && (BOTNET_CLIENT || BOTNET_BADDNS || BOTNET_NORDNS))
1520
1521 and, last, set the following scores:
1522
1523 score BOTNET_SOHO -0.01
1524 score BOTNET_NORDNS 0.01
1525 score BOTNET_BADDNS 0.00 0.01 0.00 0.01
1526 score BOTNET_CLIENT 0.01
1527
1528
1529 ====================
1530 DKIM, DK, and/or p0f
1531 ====================
1532
1533 (see above for making BOTNET a meta rule)
1534
1535 From Mark Martinec (using all 3):
1536 >
1537 > ... coupling it with p0f (passive operating system fingerprinting)
1538 > matching on non-unix hosts seems to bring up the best of both approaches:
1539 >
1540 > meta BOTNET_W !DKIM_VERIFIED && !DK_VERIFIED && (L_P0F_WXP ||
1541 > L_P0F_W || L_P0F_UNKN) && (BOTNET_CLIENT+BOTNET_BADDNS+BOTNET_NORDNS) > 0
1542 > score BOTNET_W 3.2
1543 >
1544 > meta BOTNET_OTHER !BOTNET_W &&
1545 > (BOTNET_CLIENT+BOTNET_BADDNS+BOTNET_NORDNS) > 0
1546 > score BOTNET_OTHER 0.5
1547 >
1548 > About p0f see:
1549 > http://marc.theaimsgroup.com/?l=amavis-user&m=116439276912418
1550 > http://marc.theaimsgroup.com/?l=amavis-user&m=116440910822408
1551
1552
1553 From Jonas Eckerman (just using p0f):
1554 > describe BOTNET Relay might be part of botnet
1555 > meta BOTNET (!BOTNET_SOHO && (BOTNET_CLIENT || BOTNET_BADDNS || BOTNET_NORDNS))
1556 > score BOTNET 2.0
1557 >
1558 > describe BOTNET_WINDOWS Windows relay might be part if botnet
1559 > meta BOTNET_WINDOWS (BOTNET && __OS_WINDOWS)
1560 > score BOTNET_WINDOWS 1.0
1561 >
1562 > header __OS_WINDOWS p0fIP2OS =~ /Windows/i
1563
1564 I personally would suggest not using p0f (there's nothing to prevent a
1565 linux box from being root-kitted and used as a spambot), and I still stick
1566 with the idea that 5.0 is a good score for Botnet. So, my suggestion is:
1567
1568 meta BOTNET (!DKIM_VERIFIED && !DK_VERIFIED && !BOTNET_SOHO && (BOTNET_CLIENT || BOTNET_BADDNS || BOTNET_NORDNS))
1569
1570
1571 ==========================
1572 Using SPF to exempt Botnet
1573 ==========================
1574
1575 (see above for making BOTNET a meta rule)
1576
1577 You _could_ have the BOTNET meta rule say:
1578
1579 meta BOTNET (!SPF_PASS && !DKIM_VERIFIED && !DK_VERIFIED && !BOTNET_SOHO && (BOTNET_CLIENT || BOTNET_BADDNS || BOTNET_NORDNS))
1580
1581 But, some spambot owner could then make a throw-away domain, and give it an
1582 SPF record that says "+all" or any of a few other mechanisms that evaluate
1583 to "every host may send mail from this domain". That essentially makes the
1584 Botnet checks impotent.
1585
1586 So, I would recommend NOT using SPF as a means of exempting a message from
1587 the Botnet check. In order to try to deal with small scale mail servers
1588 (SOHO, small office/home office) that might be stuck with service from an
1589 ISP that has terrible DNS policies, I have added the BOTNET_SOHO check.
1590 It has some limitations, but should work for SOHO mail servers. Larger
1591 organizations have other means of dealing with "not looking like a Botnet",
1592 such as: a) forcing their ISP to do the right thing, b) using a different
1593 ISP, or c) using a hosted mail server that has good DNS.
1594
1595
1596 COPYING0000444000000000000060000004330610525736544012033 0ustar rootmail00000000000000 GNU GENERAL PUBLIC LICENSE
1597 Version 2, June 1991
1598
1599 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
1600 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1601 Everyone is permitted to copy and distribute verbatim copies
1602 of this license document, but changing it is not allowed.
1603
1604 Preamble
1605
1606 The licenses for most software are designed to take away your
1607 freedom to share and change it. By contrast, the GNU General Public
1608 License is intended to guarantee your freedom to share and change free
1609 software--to make sure the software is free for all its users. This
1610 General Public License applies to most of the Free Software
1611 Foundation's software and to any other program whose authors commit to
1612 using it. (Some other Free Software Foundation software is covered by
1613 the GNU Library General Public License instead.) You can apply it to
1614 your programs, too.
1615
1616 When we speak of free software, we are referring to freedom, not
1617 price. Our General Public Licenses are designed to make sure that you
1618 have the freedom to distribute copies of free software (and charge for
1619 this service if you wish), that you receive source code or can get it
1620 if you want it, that you can change the software or use pieces of it
1621 in new free programs; and that you know you can do these things.
1622
1623 To protect your rights, we need to make restrictions that forbid
1624 anyone to deny you these rights or to ask you to surrender the rights.
1625 These restrictions translate to certain responsibilities for you if you
1626 distribute copies of the software, or if you modify it.
1627
1628 For example, if you distribute copies of such a program, whether
1629 gratis or for a fee, you must give the recipients all the rights that
1630 you have. You must make sure that they, too, receive or can get the
1631 source code. And you must show them these terms so they know their
1632 rights.
1633
1634 We protect your rights with two steps: (1) copyright the software, and
1635 (2) offer you this license which gives you legal permission to copy,
1636 distribute and/or modify the software.
1637
1638 Also, for each author's protection and ours, we want to make certain
1639 that everyone understands that there is no warranty for this free
1640 software. If the software is modified by someone else and passed on, we
1641 want its recipients to know that what they have is not the original, so
1642 that any problems introduced by others will not reflect on the original
1643 authors' reputations.
1644
1645 Finally, any free program is threatened constantly by software
1646 patents. We wish to avoid the danger that redistributors of a free
1647 program will individually obtain patent licenses, in effect making the
1648 program proprietary. To prevent this, we have made it clear that any
1649 patent must be licensed for everyone's free use or not licensed at all.
1650
1651 The precise terms and conditions for copying, distribution and
1652 modification follow.
1653 ^L
1654 GNU GENERAL PUBLIC LICENSE
1655 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
1656
1657 0. This License applies to any program or other work which contains
1658 a notice placed by the copyright holder saying it may be distributed
1659 under the terms of this General Public License. The "Program", below,
1660 refers to any such program or work, and a "work based on the Program"
1661 means either the Program or any derivative work under copyright law:
1662 that is to say, a work containing the Program or a portion of it,
1663 either verbatim or with modifications and/or translated into another
1664 language. (Hereinafter, translation is included without limitation in
1665 the term "modification".) Each licensee is addressed as "you".
1666
1667 Activities other than copying, distribution and modification are not
1668 covered by this License; they are outside its scope. The act of
1669 running the Program is not restricted, and the output from the Program
1670 is covered only if its contents constitute a work based on the
1671 Program (independent of having been made by running the Program).
1672 Whether that is true depends on what the Program does.
1673
1674 1. You may copy and distribute verbatim copies of the Program's
1675 source code as you receive it, in any medium, provided that you
1676 conspicuously and appropriately publish on each copy an appropriate
1677 copyright notice and disclaimer of warranty; keep intact all the
1678 notices that refer to this License and to the absence of any warranty;
1679 and give any other recipients of the Program a copy of this License
1680 along with the Program.
1681
1682 You may charge a fee for the physical act of transferring a copy, and
1683 you may at your option offer warranty protection in exchange for a fee.
1684
1685 2. You may modify your copy or copies of the Program or any portion
1686 of it, thus forming a work based on the Program, and copy and
1687 distribute such modifications or work under the terms of Section 1
1688 above, provided that you also meet all of these conditions:
1689
1690 a) You must cause the modified files to carry prominent notices
1691 stating that you changed the files and the date of any change.
1692
1693 b) You must cause any work that you distribute or publish, that in
1694 whole or in part contains or is derived from the Program or any
1695 part thereof, to be licensed as a whole at no charge to all third
1696 parties under the terms of this License.
1697
1698 c) If the modified program normally reads commands interactively
1699 when run, you must cause it, when started running for such
1700 interactive use in the most ordinary way, to print or display an
1701 announcement including an appropriate copyright notice and a
1702 notice that there is no warranty (or else, saying that you provide
1703 a warranty) and that users may redistribute the program under
1704 these conditions, and telling the user how to view a copy of this
1705 License. (Exception: if the Program itself is interactive but
1706 does not normally print such an announcement, your work based on
1707 the Program is not required to print an announcement.)
1708 ^L
1709 These requirements apply to the modified work as a whole. If
1710 identifiable sections of that work are not derived from the Program,
1711 and can be reasonably considered independent and separate works in
1712 themselves, then this License, and its terms, do not apply to those
1713 sections when you distribute them as separate works. But when you
1714 distribute the same sections as part of a whole which is a work based
1715 on the Program, the distribution of the whole must be on the terms of
1716 this License, whose permissions for other licensees extend to the
1717 entire whole, and thus to each and every part regardless of who wrote it.
1718
1719 Thus, it is not the intent of this section to claim rights or contest
1720 your rights to work written entirely by you; rather, the intent is to
1721 exercise the right to control the distribution of derivative or
1722 collective works based on the Program.
1723
1724 In addition, mere aggregation of another work not based on the Program
1725 with the Program (or with a work based on the Program) on a volume of
1726 a storage or distribution medium does not bring the other work under
1727 the scope of this License.
1728
1729 3. You may copy and distribute the Program (or a work based on it,
1730 under Section 2) in object code or executable form under the terms of
1731 Sections 1 and 2 above provided that you also do one of the following:
1732
1733 a) Accompany it with the complete corresponding machine-readable
1734 source code, which must be distributed under the terms of Sections
1735 1 and 2 above on a medium customarily used for software interchange; or,
1736
1737 b) Accompany it with a written offer, valid for at least three
1738 years, to give any third party, for a charge no more than your
1739 cost of physically performing source distribution, a complete
1740 machine-readable copy of the corresponding source code, to be
1741 distributed under the terms of Sections 1 and 2 above on a medium
1742 customarily used for software interchange; or,
1743
1744 c) Accompany it with the information you received as to the offer
1745 to distribute corresponding source code. (This alternative is
1746 allowed only for noncommercial distribution and only if you
1747 received the program in object code or executable form with such
1748 an offer, in accord with Subsection b above.)
1749
1750 The source code for a work means the preferred form of the work for
1751 making modifications to it. For an executable work, complete source
1752 code means all the source code for all modules it contains, plus any
1753 associated interface definition files, plus the scripts used to
1754 control compilation and installation of the executable. However, as a
1755 special exception, the source code distributed need not include
1756 anything that is normally distributed (in either source or binary
1757 form) with the major components (compiler, kernel, and so on) of the
1758 operating system on which the executable runs, unless that component
1759 itself accompanies the executable.
1760
1761 If distribution of executable or object code is made by offering
1762 access to copy from a designated place, then offering equivalent
1763 access to copy the source code from the same place counts as
1764 distribution of the source code, even though third parties are not
1765 compelled to copy the source along with the object code.
1766 ^L
1767 4. You may not copy, modify, sublicense, or distribute the Program
1768 except as expressly provided under this License. Any attempt
1769 otherwise to copy, modify, sublicense or distribute the Program is
1770 void, and will automatically terminate your rights under this License.
1771 However, parties who have received copies, or rights, from you under
1772 this License will not have their licenses terminated so long as such
1773 parties remain in full compliance.
1774
1775 5. You are not required to accept this License, since you have not
1776 signed it. However, nothing else grants you permission to modify or
1777 distribute the Program or its derivative works. These actions are
1778 prohibited by law if you do not accept this License. Therefore, by
1779 modifying or distributing the Program (or any work based on the
1780 Program), you indicate your acceptance of this License to do so, and
1781 all its terms and conditions for copying, distributing or modifying
1782 the Program or works based on it.
1783
1784 6. Each time you redistribute the Program (or any work based on the
1785 Program), the recipient automatically receives a license from the
1786 original licensor to copy, distribute or modify the Program subject to
1787 these terms and conditions. You may not impose any further
1788 restrictions on the recipients' exercise of the rights granted herein.
1789 You are not responsible for enforcing compliance by third parties to
1790 this License.
1791
1792 7. If, as a consequence of a court judgment or allegation of patent
1793 infringement or for any other reason (not limited to patent issues),
1794 conditions are imposed on you (whether by court order, agreement or
1795 otherwise) that contradict the conditions of this License, they do not
1796 excuse you from the conditions of this License. If you cannot
1797 distribute so as to satisfy simultaneously your obligations under this
1798 License and any other pertinent obligations, then as a consequence you
1799 may not distribute the Program at all. For example, if a patent
1800 license would not permit royalty-free redistribution of the Program by
1801 all those who receive copies directly or indirectly through you, then
1802 the only way you could satisfy both it and this License would be to
1803 refrain entirely from distribution of the Program.
1804
1805 If any portion of this section is held invalid or unenforceable under
1806 any particular circumstance, the balance of the section is intended to
1807 apply and the section as a whole is intended to apply in other
1808 circumstances.
1809
1810 It is not the purpose of this section to induce you to infringe any
1811 patents or other property right claims or to contest validity of any
1812 such claims; this section has the sole purpose of protecting the
1813 integrity of the free software distribution system, which is
1814 implemented by public license practices. Many people have made
1815 generous contributions to the wide range of software distributed
1816 through that system in reliance on consistent application of that
1817 system; it is up to the author/donor to decide if he or she is willing
1818 to distribute software through any other system and a licensee cannot
1819 impose that choice.
1820
1821 This section is intended to make thoroughly clear what is believed to
1822 be a consequence of the rest of this License.
1823 ^L
1824 8. If the distribution and/or use of the Program is restricted in
1825 certain countries either by patents or by copyrighted interfaces, the
1826 original copyright holder who places the Program under this License
1827 may add an explicit geographical distribution limitation excluding
1828 those countries, so that distribution is permitted only in or among
1829 countries not thus excluded. In such case, this License incorporates
1830 the limitation as if written in the body of this License.
1831
1832 9. The Free Software Foundation may publish revised and/or new versions
1833 of the General Public License from time to time. Such new versions will
1834 be similar in spirit to the present version, but may differ in detail to
1835 address new problems or concerns.
1836
1837 Each version is given a distinguishing version number. If the Program
1838 specifies a version number of this License which applies to it and "any
1839 later version", you have the option of following the terms and conditions
1840 either of that version or of any later version published by the Free
1841 Software Foundation. If the Program does not specify a version number of
1842 this License, you may choose any version ever published by the Free Software
1843 Foundation.
1844
1845 10. If you wish to incorporate parts of the Program into other free
1846 programs whose distribution conditions are different, write to the author
1847 to ask for permission. For software which is copyrighted by the Free
1848 Software Foundation, write to the Free Software Foundation; we sometimes
1849 make exceptions for this. Our decision will be guided by the two goals
1850 of preserving the free status of all derivatives of our free software and
1851 of promoting the sharing and reuse of software generally.
1852
1853 NO WARRANTY
1854
1855 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
1856 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
1857 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
1858 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
1859 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1860 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
1861 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
1862 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
1863 REPAIR OR CORRECTION.
1864
1865 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
1866 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
1867 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
1868 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
1869 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
1870 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
1871 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
1872 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
1873 POSSIBILITY OF SUCH DAMAGES.
1874
1875 END OF TERMS AND CONDITIONS
1876 ^L
1877 How to Apply These Terms to Your New Programs
1878
1879 If you develop a new program, and you want it to be of the greatest
1880 possible use to the public, the best way to achieve this is to make it
1881 free software which everyone can redistribute and change under these terms.
1882
1883 To do so, attach the following notices to the program. It is safest
1884 to attach them to the start of each source file to most effectively
1885 convey the exclusion of warranty; and each file should have at least
1886 the "copyright" line and a pointer to where the full notice is found.
1887
1888 <one line to give the program's name and a brief idea of what it does.>
1889 Copyright (C) 19yy <name of author>
1890
1891 This program is free software; you can redistribute it and/or modify
1892 it under the terms of the GNU General Public License as published by
1893 the Free Software Foundation; either version 2 of the License, or
1894 (at your option) any later version.
1895
1896 This program is distributed in the hope that it will be useful,
1897 but WITHOUT ANY WARRANTY; without even the implied warranty of
1898 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1899 GNU General Public License for more details.
1900
1901 You should have received a copy of the GNU General Public License
1902 along with this program; if not, write to the Free Software
1903 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1904
1905
1906 Also add information on how to contact you by electronic and paper mail.
1907
1908 If the program is interactive, make it output a short notice like this
1909 when it starts in an interactive mode:
1910
1911 Gnomovision version 69, Copyright (C) 19yy name of author
1912 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
1913 This is free software, and you are welcome to redistribute it
1914 under certain conditions; type `show c' for details.
1915
1916 The hypothetical commands `show w' and `show c' should show the appropriate
1917 parts of the General Public License. Of course, the commands you use may
1918 be called something other than `show w' and `show c'; they could even be
1919 mouse-clicks or menu items--whatever suits your program.
1920
1921 You should also get your employer (if you work as a programmer) or your
1922 school, if any, to sign a "copyright disclaimer" for the program, if
1923 necessary. Here is a sample; alter the names:
1924
1925 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
1926 `Gnomovision' (which makes passes at compilers) written by James Hacker.
1927
1928 <signature of Ty Coon>, 1 April 1989
1929 Ty Coon, President of Vice
1930
1931 This General Public License does not permit incorporating your program into
1932 proprietary programs. If your program is a subroutine library, you may
1933 consider it more useful to permit linking proprietary applications with the
1934 library. If this is what you want to do, use the GNU Library General
1935 Public License instead of this License.
1936
1937 INSTALL0000444000000000000060000000040010525753144012010 0ustar rootmail00000000000000
1938 Copy the .pm and .cf file(s) in this directory into /etc/mail/spamassassin
1939 (or whatever directory you put your plugins into).
1940
1941 If you use spamc/spamd, mailscanner, or a milter, you may need to restart
1942 that process in order for this plugin to get loaded.
1943
1944

admin@koozali.org
ViewVC Help
Powered by ViewVC 1.2.1 RSS 2.0 feed