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

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

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


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

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

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

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