/[pkg-listmaster]/trunk/gandalf/gandalf
ViewVC logotype

Diff of /trunk/gandalf/gandalf

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 9 by don, Sun Sep 9 12:00:33 2007 UTC revision 10 by don, Sun Sep 9 14:40:38 2007 UTC
# Line 43  use Pod::Usage; Line 43  use Pod::Usage;
43    
44  =head1 NAME  =head1 NAME
45    
46  greylist-tng.pl - weighted greylisting based on information we can obtain  gandalf - weighted greylisting based on information we can obtain
47                    before DATA stage of the SMTP process.            before DATA stage of the SMTP process.
48    
49  =head1 SYNOPSIS  =head1 SYNOPSIS
50    
51   [options]   gandalf [options]
52    
53   Options:   Options:
54    --debug, -d debugging level (Default 0)    --daemonize, -d daemonize (default)
55      --pidfile location of pidfile (/var/run/gandalf/gandalf.pid)
56      --socket location of socket (/var/run/gandalf/gandalf.sock)
57      --host host/port to use for tcp sockets
58      --max-connections maximum connections to allow per socket
59      --pidlock lockfile for pid (pidfile.lock)
60      --verbose, -v verbosity level (Default 0)
61      --debug, -D debugging level (Default 0)
62    --help, -h display this help    --help, -h display this help
63    --man, -m display manual    --man, -m display manual
64    
# Line 59  greylist-tng.pl - weighted greylisting b Line 66  greylist-tng.pl - weighted greylisting b
66    
67  =over  =over
68    
69  =item B<--debug, -d>  =item B<--daemonize,-d>
70    
71    Whether to daemonize (default). --no-daemonize to disable
72    
73    =item B<--pidfile>
74    
75    Pidfile location; defaults to /var/run/gandalf/gandalf.pid
76    
77    =item B<--socket>
78    
79    Socket location; defaults to /var/run/gandalf/gandalf.sock if no other
80    socket or host is specified.
81    
82    =item B<--host>
83    
84    Host to listen on; specified as hostname:portnum (or ip:portnum).
85    
86    =item B<--max-connections>
87    
88    Maximum connections to allow to tcp ports; defaults to 30.
89    
90    =item B<--pidlock>
91    
92    Lockfile for the pidfile; defaults to the location of the pidfile with
93    .lock appended.
94    
95    =item B<--debug, -D>
96    
97  Debug verbosity. (Default 0)  Debug verbosity. (Default 0)
98    
# Line 79  Display this manual. Line 112  Display this manual.
112  =cut  =cut
113    
114    
115  use Fcntl;  use Fcntl qw(:flock);
116  #use BerkeleyDB;  #use BerkeleyDB;
117  use Digest::MD5 qw(md5 md5_hex);  use Digest::MD5 qw(md5 md5_hex);
118  use IO::Socket::INET;  use IO::Socket::INET;
119    use IO::Socket::UNIX;
120  use IO::Select;  use IO::Select;
121    use IO::File;
122  use Net::DNS;  use Net::DNS;
123  use POSIX;  use POSIX qw(strftime setsid);
124  use Sys::Syslog qw(:DEFAULT setlogsock);  use Sys::Syslog qw(:DEFAULT setlogsock);
125  use Params::Validate qw(:types validate_with);  use Params::Validate qw(:types validate_with);
126    
# Line 95  use List::Util qw(first); Line 130  use List::Util qw(first);
130    
131  use constant {BAD => 0, GOOD => 1};  use constant {BAD => 0, GOOD => 1};
132    
133    
134  use vars qw($DEBUG);  use vars qw($DEBUG);
135    
136  my %options = (debug           => 0,  my %options = (debug           => 0,
137                 help            => 0,                 help            => 0,
138                 man             => 0,                 man             => 0,
139                   verbose         => 0,
140                   daemonize       => 1,
141                   pidfile         => '/var/run/gandalf/gandalf.pid',
142                   pidlock         => undef,
143                   socket          => [],
144                   host            => [],
145                   max_connections => 30,
146                 );                 );
147    
148  GetOptions(\%options,'debug|d+','help|h|?','man|m');  GetOptions(\%options,'debug|D+','help|h|?','man|m',
149               'verbose|v+',
150               'daemonize|daemon|d!',
151               'pidfile=s',
152               'pidlock=s',
153               'max_connections|max-connections=s',
154               'host=s@',
155               'socket=s@',
156              );
157    if (not defined $options{pidlock}) {
158         $options{pidlock} = $options{pidfile}.'.lock';
159    }
160    
161    if (not @{$options{socket}} and
162        not @{$options{host}}) {
163         push @{$options{socket}},'/var/run/gandalf/gandalf.sock';
164    }
165    
166  pod2usage() if $options{help};  pod2usage() if $options{help};
167  pod2usage({verbose=>2}) if $options{man};  pod2usage({verbose=>2}) if $options{man};
168    
169  $DEBUG = $options{debug};  $DEBUG = $options{debug};
170    
171    if ($options{daemonize}) {
172         my $pidfh;
173         if ($options{pidfile}) {
174              my $pidlock = new IO::File->new($options{pidlock},'w') or
175                   die "Unable to open pidlock $options{pidlock} for writing: $!";
176              flock($pidlock,LOCK_EX) or
177                   die "Unable to lock pidlock $options{pidlock}: $!";
178              if (-e $options{pidfile}) {
179                   $pidfh = IO::File->new($options{pidfile},'r') or
180                        die "Unable to open pidfile $options{pidfile} for reading: $!";
181                   local $/;
182                   my $pid = <$pidfh>;
183                   ($pid) = $pid =~ /(\d+)/;
184                   if (defined $pid and kill(0,$pid)) {
185                        print STDERR "Copy of $0 running with pid $pid (pidfile: $options{pidfile})";
186                        unlink($options{pidlock});
187                        undef $pidlock;
188                        exit 1;
189                   }
190                   close $pidfh;
191                   unlink ($options{pidfile}) or
192                        die "Unable to unlink stale pidfile $options{pidfile}: $!";
193              }
194              $pidfh = IO::File->new($options{pidfile},'w') or
195                   die "Unable to open $options{pidfile} for writing: $!";
196         }
197         # daemonize
198         chdir '/' or die "Can't chdir to /: $!";
199         open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
200         open STDOUT, '>/dev/null'
201              or die "Can't write to /dev/null: $!";
202         defined(my $pid = fork) or die "Can't fork: $!";
203         exit if $pid;
204         setsid or die "Can't start a new session: $!";
205         if (defined $pidfh) {
206              print {$pidfh} $$ or die "Unable to write to pidfile $options{pidfile}: $!";
207              close $pidfh or die "Unable to close pidfile $options{pidfile}: $!";
208         }
209         open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
210    }
211    
212    
213  my $tcp_port            = 10025;  my $tcp_port            = 10025;
214  my $bind_address        = "localhost";  my $bind_address        = "localhost";
# Line 582  sub handle_message{ Line 682  sub handle_message{
682       # announce decision       # announce decision
683  }  }
684    
685  sub parse_input($) {  # sub parse_input($) {
686      my $attr = shift();  #     my $attr = shift();
687      my $response;  #     my $response;
688      my $score=0;  #     my $score=0;
689    #
690      if (defined(read_database($attr))) {  #     if (defined(read_database($attr))) {
691          $response = read_database($attr);  #       $response = read_database($attr);
692          if ($response) {  #       if ($response) {
693              return "dunno";  #           return "dunno";
694          } else {  #       } else {
695              return "defer_if_permit Service temporarily unavailable";  #           return "defer_if_permit Service temporarily unavailable";
696          }  #       }
697      } else {  #     } else {
698          # Test: Is client's IP listed in some RBL lists?  #       # Test: Is client's IP listed in some RBL lists?
699          $score = $score + test_rbldns($attr->{client_address});  #       $score = $score + test_rbldns($attr->{client_address});
700    #
701          # Test: Is sender listed in some RH RBL lists?  #       # Test: Is sender listed in some RH RBL lists?
702          $score = $score + test_rhrbldns($attr->{sender});  #       $score = $score + test_rhrbldns($attr->{sender});
703    #
704          # Test: is HELO numeric?  #       # Test: is HELO numeric?
705          if (test_helo_numeric($attr->{helo_name})) {  #       if (test_helo_numeric($attr->{helo_name})) {
706              $score = $score + $helo_numeric_score[BAD];  #           $score = $score + $helo_numeric_score[BAD];
707          } else {  #       } else {
708              # Test: Reverse IP == HELO check?  #           # Test: Reverse IP == HELO check?
709              $score = $score + test_helo_reverse($attr->{helo_name}, $attr->{reverse_client_name});  #           $score = $score + test_helo_reverse($attr->{helo_name}, $attr->{reverse_client_name});
710          }  #       }
711    #
712          if ($client_seems_dialup == 1) {  #       if ($client_seems_dialup == 1) {
713              $score = $score + test_helo_seems_dialup($attr->{helo_name});  #           $score = $score + test_helo_seems_dialup($attr->{helo_name});
714          }  #       }
715    #
716          # Test: is our mail coming from a potential daemon?  #       # Test: is our mail coming from a potential daemon?
717          $score = $score + test_sender_anonymous($attr->{sender});  #       $score = $score + test_sender_anonymous($attr->{sender});
718    #
719          # Test: is our mail coming from a domain with SPF records?  #       # Test: is our mail coming from a domain with SPF records?
720          $score = $score + test_sender_spf($attr->{sender});  #       $score = $score + test_sender_spf($attr->{sender});
721    #
722          if ($score > $cutoff) {  #       if ($score > $cutoff) {
723              write_database($attr);  #           write_database($attr);
724              return "defer_if_permit Service temporarily unavailable";  #           return "defer_if_permit Service temporarily unavailable";
725          } else {  #       } else {
726              return "PREPEND X-Greylisting: score is $score";  #           return "PREPEND X-Greylisting: score is $score";
727          }  #       }
728    #
729      }  #     }
730    #
731    #     # no strict 'refs';
732    #     # $response = weighted_check->(attr=>\%attr);
733    # }
734    
735      # no strict 'refs';  $SIG{INT} = \&cleanup;
     # $response = weighted_check->(attr=>\%attr);  
 }  
736    
737    
738  sub main {  sub main {
739      my $tcp_socket = IO::Socket::INET->new(       # Create the sockets
740          Proto       => 'tcp',       my @sockets;
741          LocalHost   => $bind_address,       # handle unix sockets
742          LocalPort   => $tcp_port,       for my $socket_file (@{$options{socket}}) {
743          Listen      => $max_connection,            my $socket =
744          Reuse       => 1) or                 IO::Socket::UNIX->new(#Type => SOCK_STREAM,
745      die "master: bind $tcp_port: $@ $!";                                       Local => $socket_file,
746                                         Listen => 1,
747      my $env = BerkeleyDB::Env->new(                                      )
748          -Home       => $dbdir,                           or die "Unable to create socket for $socket_file";
749          -Flags      => DB_CREATE|DB_RECOVER|DB_INIT_TXN|DB_INIT_MPOOL|DB_INIT_LOG,            push @sockets,$socket;
750      ) or die "ERROR: can't create DB environment: $!\n";       }
751         for my $socket_host (@{$options{host}}) {
752      tie %db, 'BerkeleyDB::Btree',            # figure out hostname/port
753          -Filename   => $dbname,            my ($host,$port) = $socket_host =~ /(.+)\:(\d+)$/;
754          -Flags      => DB_CREATE,            if (not defined $host or not defined $port) {
755          -Env        => $env,                 die "Socket host $socket_host not foohost:23 format";
756      or die "ERROR: can't create database $dbdir/$dbname: $!\n";            }
757              my $socket =
758      my %attr;                 IO::Socket::INET->new(Proto       => 'tcp',
759      # FIXME: catch TCPSocketInUseExeption e                                       LocalHost   => $host,
760      my $read_set = new IO::Select();                                       LocalPort   => $port,
761      $read_set->add($tcp_socket);                                       Listen      => $options{max_connections},
762      # end FIXME                                       Reuse       => 1)
763      #                           or die "master: bind $tcp_port: $@ $!";
764      # FIXME: endless loop... grrr            push @sockets,$socket;
765      while (1) {       }
766          # get a set of readable handles (blocks until at least one handle is ready)       my %attr;
767          my ($rh_set) = IO::Select->select($read_set, undef, undef, 0);       # FIXME: catch TCPSocketInUseExeption e
768         my $read_set = IO::Select->new() or
769              die "Unable to create IO::Select";
770         $read_set->add(@sockets);
771         my %listen_sockets;
772         # keep track of which sockets are listen sockets
773         @listen_sockets{@sockets} = (1) x @sockets;
774         # end FIXME
775         #
776         my %request_sockets;
777         # FIXME: endless loop... grrr
778         my @reads;
779         # get a set of readable handles (blocks until at least one handle is ready)
780         while (@reads = $read_set->can_read()) {
781          # take all readable handles in turn          # take all readable handles in turn
782          foreach my $rh (@$rh_set) {          foreach my $rh (@reads) {
783              # if it is the main socket then we have an incoming connection and              # if it is the main socket then we have an incoming connection and
784              # we should accept() it and then add the new socket to the $read_set               # we should accept() it and then add the new socket to the $read_set
785              if ($rh == $tcp_socket) {              if ($listen_sockets{$rh}) {
786                  my $ns = $rh->accept();                  my $ns = $rh->accept();
787                  $read_set->add($ns);                  $read_set->add($ns);
788                    $request_sockets{$ns} = {};
789              } else {              } else {
790                  # otherwise it is an ordinary socket and we should read and process the request                  # otherwise it is an ordinary socket and we should read and process the request
791                  my $buf = <$rh>;                  my $buf = <$rh>;
# Line 678  sub main { Line 794  sub main {
794                          # we get a newline, no more information will come from                          # we get a newline, no more information will come from
795                          # postfix for this mail                          # postfix for this mail
796                          # postfix now waits for my answer                          # postfix now waits for my answer
797                          if (%attr) {                          if ($request_sockets{$rh}{variables}) {
798                              if ($verbose) {                              if ($options{verbose}) {
799                                  for (keys %attr) {                                   while (my ($key,$value) =
800                                      syslog $syslog_priority, "Key: %s, Value: %s", $_, $attr{$_};                                          each %{$request_sockets{$rh}{variables}}
801                                  }                                         ) {
802                                          syslog $syslog_priority, "Key: %s, Value: %s", $key,$value;
803                                     }
804                              }                              }
805                              my $action = parse_input(\%attr);                              #my $action = parse_input(\%attr);
806                                my $action = "PREPEND X-Greylisting: disabled";
807                                print STDERR "Handled $action\n";
808                              $rh->send("action=".$action."\r\n");                              $rh->send("action=".$action."\r\n");
809                              $rh->send("\r\n");                              $rh->send("\r\n");
810                              %attr=();                              delete $request_sockets{$rh}{variables};
811                          }                          }
812                      } elsif ( $buf =~ /([^=]+)=(.*)\r\n/ ) {                      } elsif ( $buf =~ /([^=]+)=(.*)\r\n/ ) {
813                          # we get valid input from postfix                          # we get valid input from postfix
814                          $attr{substr($1, 0, 512)} = substr($2, 0, 512);                          $request_sockets{$rh}{variables}{substr($1, 0, 512)} = substr($2, 0, 512);
815                      } else {                      } else {
816                          syslog $syslog_priority, "warning: ignoring garbage: %.100s", $buf;                          syslog $syslog_priority, "warning: ignoring garbage: %.100s", $buf;
817                      }                      }
# Line 703  sub main { Line 823  sub main {
823              }              }
824          }          }
825      }      }
   
     untie %db;  
826  }  }
827    
828  main();  main();
829    
830    sub cleanup {
831         print STDERR "got sig int\n";
832         exit 0;
833    }
834    
835    END {
836         for my $socket_file (@{$options{socket}}) {
837              print STDERR "unlinking $socket_file\n";
838              unlink($socket_file) if -e $socket_file;
839         }
840    }
841    
842  __END__  __END__

Legend:
Removed from v.9  
changed lines
  Added in v.10

  ViewVC Help
Powered by ViewVC 1.1.5