Ang-st has asked for the wisdom of the Perl Monks concerning the following question:

hello

I try to set up a simple filter for postfix. the concept is pretty simple: a mail is received in queue and will be piped to the filter, the script extract the header ("To:" and "From:") and compare it to a sql database. if the sender isn't registred then the script will send a registration mail linking to a registration form.

the problem is when i test the filter by hand ( cat mail | ./filter ) everything work properly but when it is called by postfix the header aren't extract ( i don't know if it is the mail which isn't piped or another problem )
here is the code
#!/usr/bin/perl use DBI(); $temp=$$ ; $log = "/var/spool/filter/log"; if (-e $log){ open (LOG, ">>$log");} else {open (LOG, ">$log");} @liste = (<>) ; $i = 0 ; #trouve l'expediteur du mail $to = find_exp_to('To:'); $from = find_exp_to('From:'); print LOG "querrying contact:$from for user:$to \n\n"; db_check($to ,$from); if ($trouve <= 0 ) { mail($from) ; after_mail(); } else { after_mail(); } close LOG; sub db_check { $dbh = DBI->connect( "dbi:mysql:email", "user", "pass", {'RaiseError' => 1} ); $select= shift ; $test = shift ; $trouve = 0; $query= qq{select * from contact where user_name='$select' }; $sth = $dbh->prepare($query); $rv= $sth->execute || die $sth->errstr; while ($answer = $sth->fetchrow_arrayref ) { my ($id,$user,$contact) = @$answer; if ($contact=~ /($test)/i){ print LOG "contact => $test exist \n" ; $trouve = 1 ; } } $sth->finish; $dbh->disconnect(); if ($trouve <= 0){print LOG "$test not in db gonna send a mail\n";} } sub find_exp_to { my $find = shift ; $i = 0; while($liste[$i] ) { if ($liste[$i]=~ m/$find*?(.\w+\S)/cg) { #tous les car +acteres sur la lignes contenant From: $liste[$i]=~ s/($find)\s\b//g;#on vire le from $liste[$i]=~ s/\s//g; $data = $liste[$i]; } $i++; } return($data); } #envoi un mail a l'addr pass\uffff en param sub mail { require Mail::Send ; my $sender = shift ; $msg = new Mail::Send ; $msg->to($sender); $msg->subject('Mail register') ; #appelle les header de la classe: $msg->set($header, @values); $fh = $msg->open; print $fh "Bonjour, pour pouvoir envoyer un mail a $to vous de +vez vous enregister http://sever/cgi-bin/register.pl"; $fh->close ; } #reverifie la db ap mailing si pas de reponse avt time out detruit le +mail sub after_mail { $j = 0; open(TEMP, "+>/var/spool/filter/$temp"); print(TEMP "@liste"); @delete = TEMP ; close(TEMP); $trouve = 0 ; $i =0; while ( $i < 10 && $trouve ==0 ) { db_check($to, $from) ; if ($trouve == 0) { print LOG "pas de news ds la base rest ds 10 s +econde\n"; sleep 10; } else { $trouve = 1; } $i++; } if ($trouve == 0) { print LOG "operation time out deleting mail from $from +\n"; unlink @delete ; } else { @arg=("sendmail","-i","$sender","$recipient","<","@lis +te"); system(@arg) == 0 || die "exec a send back error :$?"; unlink @delete; } }
Any advice would be welcome. Thanks in advance

Replies are listed 'Best First'.
Re: postfix filter...
by hsinclai (Deacon) on Feb 06, 2005 at 16:35 UTC
    If I isolate the sub  find_exp_to into a separate script, it breaks on Postfix's Delivered-To: and X-Original-To: headers when an email is piped through by hand. Also your regexes search through the entire email, so if the body contains "To: blah@blah" that probably gets returned..

    To parse email, it will be useful to get explicit about the header and the body - probably best to use a module that does that for you like Mail::Internet, or look in the Perl Cookbook where you'll find the use of the  .. range operator to determine whether you're in headers or body (P179 of the first edition)

    I would work on parsing the email first, without the mysql stuff. Have your filter write output of the parsed email to a disk file you can verify as a debugging step, so that you can make sure the piping through Postfix is working. You should see the filter return properly through Postfix's very imformative logs, but before installing the filter your test case should work from the command line, as you are already doing..

    Most importantly,
    use strict; use warnings;

Re: postfix filter...
by hsinclai (Deacon) on Feb 07, 2005 at 03:27 UTC
    Ang-st,

    Here is a safer way to obtain the header values you need. If it works from the command line, e.g.,  cat mailmsg.txt | script.pl, it should work well within your script, allowing you to get further headers easily without writing more code:)
    #!/usr/bin/perl -w use strict; use Mail::Header; my @liste = (<>); my $header_object = new Mail::Header \@liste, Modify => 0, MailFrom => + "COERCE" ; my $to = $header_object->get('To'); my $apparently_from = $header_object->get('Return-Path'); my $from = $header_object->get('From'); my $mail_from = $header_object->get('Mail-From'); print ' To: ' . $to; print ' From: ' . $from; print ' maybe From: ' . $apparently_from; print ' mbox From: ' . (split(/\s+/,$mail_from))[0]; print $/; # our header keys # my @headerarray = $header_object->tags(); # print $_.$/ for @headerarray;
    HTH
Re: postfix filter...
by fauria (Deacon) on Feb 06, 2005 at 16:51 UTC
    Hi!

    This is a bit offtopic, but i thought that maybe is useful for you:

    There is a project called SPF (sender policy framework), which is a draft in the IETF as long is i know, that aims to build a network of trusted senders of emails.

    Basically, a host exports a list of valid smtp hosts, so origin of emails is clearly defined.

    This will not terminate spam, but at least can spot where it was originated.

    More info in Newsforge.

    Mail::SPF::Query module may help you to identify trusted hosts.
Re: postfix filter...
by grinder (Bishop) on Feb 06, 2005 at 17:53 UTC

    I think this is less of a Perl problem than an issue of Postfix configuration. So, at the risk of being off-topic:...

    When are you running your filter? If you are doing it as a recipient check, e.g., hanging off smtpd_recipient_restriction, you will not yet have the received that DATA. You will need to hang it off smtpd_data_restrictions instead.

    If you are not concerned about rejecting the message, but only to send a message back if they fail the database check, I would be inclined to take your script as is, and fire it off from a procmail rule, and leave Postfix out of the picture. Which would have the added advantage of simplifying your Postfix configuration, since it becomes less bound to the application level, and simply does its job as a Mail Transfer Agent. Besides, as you have already shown, it's easier to test from the command line anyway.

    - another intruder with the mooring in the heart of the Perl

      smtpd_recipient_restriction, you will not yet have the received that DATA. You will need to hang it off smtpd_data_restrictions

      Neither of those is a "filter" in the general Postfix parlance. A filter is generally an external program or additional Postfix process (like a new smtpd) invoked either from main.cf as content_filter or through master.cf -- such as a program, Perl or shell script to which the entire email would handed off for processing. Postfix would expect it to be returned into the flow as processing or create a bounce. smtpd_data_restrictions reads after the SMTP conversation DATA command so it would not work for the OP's intention..

      From what I can tell of the OP's intended task, (remember: "mail received in queue") he should be running his program as a "filter" in this sense, but even then - that may not be the best design decision - since all incoming mail would run through the Perl script , perhaps needlessly. Better would be to have mail to certain user accounts pipe through the program and get processed by a Perl script accordingly to find out whether they're already registered in the database. Maybe that's what he meant anyway..!