#!/usr/bin/perl -sw eval 'exec /usr/local/bin/perl -sw -S $0 ${1+"$@"}' if 0; # not running under some shell # simple forking daemon to provide SPF services # mengwong+spf@pobox.com # Tue Oct 28 00:46:44 EST 2003 # # if you're reading source code, you should probably be on spf-devel@listbox.com. # # echo "ip=IP\nhelo=HELOHOST\nsender=EMAILADDRESS\n" | nc localhost 5970 # # or use Mail::Postfix::Attr to query spfd over a unix domain socket. # # SYNOPSIS # spfd [OPTION]... # # DEFAULT # spfd -port=5970 # # OPTIONS # -port=PORTNUM # Listen with a tcp socket on port PORTNUM (mutually exlusive with -path) # # -path=FILESPEC # Listen with a unix socket at FILESPEC (mutually exlusive with -port) # # -pathuser=USER # When using a unix socket set owner to USER # # -pathgroup=GROUP # When using a unix socket set group to GROUP # # -pathmode=MODE # When using a unix socket set socket permissions to MODE (octal) # # -setuser=(uid|username) # Drop privileges to uid or username after opening the socket # # -setgroup=(gid|groupname) # Drop privileges to gid or groupname after opening the socket # # example usage # # 20040113-22:39:24 mengwong@dumbo:~% echo "ip=208.210.125.24\nhelo=moo.com\nsender=mengwong@vw.mailzone.com\n" | nc localhost 5970 # result=fail # smtp_comment=please see http://spf.pobox.com/why.html?sender=mengwong%40vw.mailzone.com&ip=208.210.125.24&receiver=dumbo.pobox.com # header_comment=dumbo.pobox.com: domain of mengwong@vw.mailzone.com does not designate 208.210.125.24 as permitted sender # guess=pass # smtp_guess=vw.mailzone.com MX dumbo.pobox.com A 208.210.125.24 # header_guess=seems reasonable for mengwong@vw.mailzone.com to mail through 208.210.125.24 # guess_tf=pass # smtp_tf=dumbo.pobox.com.wl.trusted-forwarder.org found # header_tf=seems reasonable for mengwong@vw.mailzone.com to mail through 208.210.125.24 # spf_record=v=spf1 mx:vw.com ptr:vw.com ptr:monkey.org -all # # the three sets of results correspond to ->result(), ->best_guess(), and ->trusted_forwarder() # in Mail::SPF::Query. # use Mail::SPF::Query; use Socket; use strict; use vars qw($port $path $pathuser $pathgroup $pathmode $setuser $setgroup); # FIXME sub usage () { print "usage: spfd ( -port=5970 | -path=/var/spfd ) [-setuser=(uid|username)] [-setgroup=(gid|groupname)]\n"; print "usage: [ -pathuser=(uid|username)] [ -pathgroup=(gid|groupname)] [-pathmode=mode]\n"; print "usage: spfd assuming -port=5970\n"; } sub DEBUG () { $ENV{DEBUG} } if (not $port and not $path) { usage; $port=5970; } if ($port and $path) { usage; exit 1; } $|++; my @args; my $sock_type; if ($port) { $sock_type = "inet"; @args = (Listen => 1, LocalAddr => "127.0.0.1", LocalPort => $port, ReuseAddr => 1 ); print "$$ will listen on $port\n"; $0 = "spfd listening on $port"; } elsif ($path) { $sock_type = "unix"; unlink $path if -S $path; @args = (Listen => 1, Local => $path, ); print "$$ will listen at $path\n"; $0 = "spfd listening at $path"; } print "$$: creating server with args @args\n"; my $server = $sock_type eq "inet" ? IO::Socket::INET->new(@args) : IO::Socket::UNIX->new(@args); if ($path) { if (defined $pathuser or defined $pathgroup) { unless ( defined $pathuser ) { $pathuser = -1 } unless ( defined $pathgroup ) { $pathgroup = -1 } if ($pathuser =~ /\D/) { $pathuser = getpwnam($pathuser) || die "User: $pathuser not found\n"; } if ($pathgroup =~ /\D/) { $pathgroup = getgrnam($pathgroup) || die "Group: $pathgroup not found\n"; } chown $pathuser, $pathgroup, $path or die "chown call failed on $path: $!\n"; } if (defined $pathmode) { chmod oct($pathmode), $path or die "Cannot fixup perms on $path -- $!\n"; } } DEBUG and print "$$: server is $server\n"; if ($setgroup) { if ($setgroup =~ /\D/) { $setgroup = getgrnam($setgroup) || die "Group: $setgroup not found\n"; } $( = $setgroup; $) = $setgroup; unless ($( == $setgroup and $) == $setgroup) { die( "setgid($setgroup) call failed: $!\n" ); } } if ($setuser) { if ($setuser =~ /\D/) { $setuser = getpwnam($setuser) || die "User: $setuser not found\n"; } $< = $setuser; $> = $setuser; unless ($< == $setuser and $> == $setuser) { die( "setuid($setuser) call failed: $!\n" ); } } while (my $sock = $server->accept()) { if (fork) { close $sock; wait; next; } # this is the grandfather trick. elsif (fork) { exit; } # the child exits immediately, so no zombies. my $oldfh = select($sock); $| = 1; select($oldfh); my %in; while (<$sock>) { chomp; chomp; last if (/^$/); my ($lhs, $rhs) = split /=/, $_, 2; $in{lc $lhs} = $rhs; } my $peerinfo = $sock_type eq "inet" ? ($sock->peerhost . "/" . gethostbyaddr($sock->peeraddr, AF_INET)) : ""; my $time = localtime; DEBUG and print "$time $peerinfo\n"; foreach my $key (sort keys %in) { DEBUG and print "learned $key = $in{$key}\n" }; my %q = map { exists $in{$_} ? ($_ => $in{$_}) : () } qw ( ip ipv4 ipv6 sender helo fallbacks guess_mechs ); my %a; my $query = eval { Mail::SPF::Query->new(%q); }; my $error = $@; for ($error) { s/\n/ /; s/\s+$//; } if ($@) { @a{qw(result smtp_comment header_comment)} = ("unknown", $error, "SPF error: $error"); } else { @a{qw(result smtp_comment header_comment spf_record)} = $query->result(); @a{qw(guess smtp_guess header_guess )} = $query->best_guess(); @a{qw(guess_tf smtp_tf header_tf )} = $query->trusted_forwarder(); } if (DEBUG) { for (qw(result smtp_comment header_comment guess smtp_guess header_guess guess_tf smtp_tf header_tf spf_record )) { print "moo! $_=$a{$_}\n"; } } for (qw(result smtp_comment header_comment guess smtp_guess header_guess guess_tf smtp_tf header_tf spf_record )) { no warnings 'uninitialized'; print $sock "$_=$a{$_}\n"; } DEBUG and print "moo! output all done.\n"; print $sock "\n"; DEBUG and print "\n"; close $sock; exit; }