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

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

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


Revision 1.1 - (hide annotations) (download) (as text)
Sun Jan 14 16:23:26 2007 UTC (17 years, 6 months ago) by slords
Branch: MAIN
Branch point for: SPAMASSASSIN-BOTNET
Content type: application/x-tar
Initial revision

1 slords 1.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