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

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

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


Revision 1.1.1.1 - (show annotations) (download) (as text) (vendor branch)
Sun Jan 14 16:23:26 2007 UTC (17 years, 9 months ago) by slords
Branch: SPAMASSASSIN-BOTNET
CVS Tags: SPAMASSASSIN-BOTNET-0_7-1_EL4_SME
Changes since 1.1: +0 -0 lines
Content type: application/x-tar
Import of spamassassin-botnet

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

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