#!/usr/bin/perl -w-

# spfquery - Sender Permitted From command line utility
#
#  Author: Wayne Schlitt <wayne@midwestcs.com>
#
#  File:   spfquery.c
#  Desc:   SPF command line utility
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of either:
#
#   a) The GNU Lesser General Public License as published by the Free
#      Software Foundation; either version 2.1, or (at your option) any
#      later version,
#
#   OR
#
#   b) The two-clause BSD license.
#
#
# The two-clause BSD license:
#
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


my $SPFQUERY_VERSION = "2.1";


use Mail::SPF::Query;

use Getopt::Long qw(:config gnu_compat);

sub usage()
{
  printf STDERR <<'EOT';
Usage:

spfquery [control options | data options] ...

Use the -help option for more information
EOT
}

sub help()
{
  printf STDERR <<'EOT';
Usage:

spfquery [control options | data options] ...

Valid data options are:
    -file <filename>           read spf data from a file.  Use '-'
                               to read from stdin.

    -ip <IP address>           The IP address that is sending email
    -sender <email address>    The email address used as the
                               envelope-from.  If no username (local
                               part) is given, 'postmaster' will be
                               assumed.
    -helo <domain name>        The domain name given on the SMTP HELO
                               command.  This is only needed if the
                               -sender option is not given.
    -rcpt-to <email addresses> A comma separated lists of email addresses
                               that will have email from their secondary
                               MXes automatically allowed.

The data options are required.  The -file option conflicts with all
the other data options.  The -helo and -rcpt-to are optional.
 

Valid control options are:
    -debug [debug level]       debug level.
    -local <SPF mechanisms>    Local policy for whitelisting.
    -trusted <0|1>             Should trusted-forwarder.org be checked?
    -guess <SPF mechanisms>    Default checks if no SPF record is found.
    -default-explanation <str> Default explanation string to use.
    -max-lookup <number>       Maximum number of DNS lookups to allow
    -sanitize <0|1>            Clean up invalid characters in output?
    -name <domain name>        The name of the system doing the SPF
                               checking
    -override <...>            Override SPF records for domains
    -fallback <...>            Fallback SPF records for domains
    -dns <dns layers>          Comma seperated list of DNS layers
                               to use.

    -keep-comments             Print comments found when reading
                               from a file.
    -version                   Print version of spfquery.
    -help                      Print out these options.

Examples:

spfquery -ip=11.22.33.44 -sender=user@aol.com -helo=spammer.tld
spfquery -f test_data
echo \"127.0.0.1 myname@mydomain.com helohost.com\" | spfquery -f -
EOT
}


my $opt_file = undef;

my $opt_ip = undef;
my $opt_sender = undef;
my $opt_helo = undef;
my $opt_rcpt_to = undef;

my $opt_local = undef;
my $opt_trusted = undef;
my $opt_guess = undef;
my $opt_exp = undef;
my $opt_max_lookup = undef;
my $opt_sanitize = undef;
my $opt_name = "spfquery";
my $opt_debug = 0;
my $opt_keep_comments = 0;
my $opt_fallback = undef;
my $opt_override = undef;
my $opt_dns = undef;
my $opt_help = undef;

my $result = GetOptions('file=s'		=> \$opt_file,

			'ip:s'			=> \$opt_ip,
			'ipv4:s'		=> \$opt_ip,
			'sender:s'		=> \$opt_sender,
			'helo:s'		=> \$opt_helo,
			'rcpt-to:s'		=> \$opt_rcpt_to,
			
			'debug=i'		=> \$opt_debug,
			'local:s'		=> \$opt_local,
			'trusted:s'		=> \$opt_trusted,
			'guess:s'		=> \$opt_guess,
			'default-explanation:s' => \$opt_exp,
			'max-lookup:s'		=> \$opt_max_lookup,
			'sanitize:s'		=> \$opt_sanitize,
			'name:s'		=> \$opt_name,
			'override:s'		=> \$opt_override,
			'fallback:s'		=> \$opt_fallback,
			'dns:s'			=> \$opt_dns,

			'keep-comments'		=> \$opt_keep_comments,
			'version'		=> \$opt_version,
			'help'			=> \$opt_help
			);

if ($opt_help) {
  help();
  exit 255;
}

if (!$result) {
  usage();
  exit 255;
}

if ($opt_version) {
  printf STDERR "spfquery version %s\n\n", $SPFQUERY_VERSION;
  usage();
  exit 255;
}

#
# process the SPF request
#
my $res;

if (!defined($opt_ip) || (!defined($opt_sender) && !defined($opt_helo))) {
  if (!defined($opt_file) ||
      defined($opt_ip) || defined($opt_sender) || defined($opt_helo)) {
    usage();
    exit 255;
  }

  #
  # the requests are on STDIN
  #
	
  local *FIN;

  if ( $opt_file eq "-" ) {
    *FIN = \*STDIN;
  }
  else {
    open( FIN, $opt_file ) || die "Could not open: %s\n", $opt_file;
  }
	
  while ( <FIN> ) {
    chomp;

    if ( /^\s*$/ || /^\s*#/ ) {
      if ( $opt_keep_comments ) {
	printf "%s\n", $_;
      }

      next;
    }
    s/^\s*//;

    ($opt_ip, $opt_sender, $opt_helo, $opt_rcpt_to) = split;

    $res = do_query();
  }
}
else {
  if (defined($opt_file)) {
    usage();
    exit 255;
  }

  $res = do_query();
}

exit $res;



sub do_query {


  #
  # Process the SPF request and print the results
  #

  if ( !defined( $opt_sender ) ) { $opt_sender = ""; }
  if ( !defined( $opt_helo   ) ) { $opt_helo = ""; }

  my $query = new Mail::SPF::Query (ipv4       => $opt_ip,
				    sender     => $opt_sender,
				    helo       => $opt_helo,
				    local      => $opt_local,
				    trusted    => $opt_trusted,
				    guess      => $opt_guess,
				    default_explanation => $opt_exp,
				    max_lookup => $opt_max_lookup,
				    sanitize   => $opt_sanitize,
				    myhostname => $opt_name,
				    fallback   => $opt_fallback,
				    override   => $opt_override,
				    debug      => $opt_debug,
				   );

  my ($result, $smtp_comment, $header_comment);
  my $per_result;
  if (!defined($opt_rcpt_to) || $opt_rcpt_to eq "") {
    ($result, $smtp_comment, $header_comment) = $query->result;
    $per_result = $result;
  }
  else {
    $result = "";
    foreach my $recip (split(',', $opt_rcpt_to)) {

      ($per_result, $smtp_comment, $header_comment) = $query->result2( split(';', $recip));
      if ($result eq "" ) {
	$result = $per_result;
      }
      else {
	$result .= ",".$per_result;
      }
    }
    ($per_result, $smtp_comment, $header_comment) = $query->message_result2;

    if ($result eq "" ) {
      $result = $per_result;
    }
    else {
      $result .= ",".$per_result;
    }
  }
	
  my $received_spf;
  $received_spf = "Received-SPF: $per_result ($header_comment) client-ip=$opt_ip;";
  $received_spf .= " envelope-from=$opt_sender;" if ( defined( $opt_sender ) );
  $received_spf .= " helo=$opt_helo;" if ( defined( $opt_helo ) );
  { no warnings 'uninitialized';
    print "$result\n$smtp_comment\n$header_comment\n$received_spf\n";
  }

  return 0 if $result eq "pass";
  return 1 if $result eq "fail";
  return 2 if $result eq "softfail";
  return 3 if $result eq "neutral";
  return 4 if $result eq "unknown";
  return 5 if $result eq "error";
  return 6 if $result eq "none";

  return 255;
}

