/*
* spfquery - Sender Policy Framwork 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.
*/
#define SPF_TEST_VERSION "2.1"
#include "libreplace/win32_config.h"
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef STDC_HEADERS
# include <stdio.h>
# include <stdlib.h> /* malloc / free */
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h> /* types (u_char .. etc..) */
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h> /* strstr / strdup */
#else
# ifdef HAVE_STRINGS_H
# include <strings.h> /* strstr / strdup */
# endif
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h> /* inet_ functions / structs */
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h> /* inet_ functions / structs */
#endif
#if 0
#ifdef HAVE_ARPA_NAMESER_H
# include <arpa/nameser.h> /* DNS HEADER struct */
#endif
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h> /* in_addr struct */
#endif
#ifdef HAVE_GETOPT_LONG_ONLY
#define _GNU_SOURCE
#include <getopt.h>
#else
#include "libreplace/getopt.h"
#endif
#ifdef _WIN32
#include "spf_win32.h"
#endif
#include "spf.h"
#include "spf_dns.h"
#include "spf_dns_null.h"
#include "spf_dns_test.h"
#include "spf_dns_cache.h"
#ifndef _WIN32
#include "spf_dns_resolv.h"
#else
#include "spf_dns_windns.h"
#endif
#define TRUE 1
#define FALSE 0
static void usage()
{
fprintf(
stderr,
"Usage:\n"
"\n"
"spfquery [control options | data options] ...\n"
"\n"
"Use the -help option for more information\n"
);
}
static void help()
{
fprintf(
stderr,
"Usage:\n"
"\n"
"spfquery [control options | data options] ...\n"
"\n"
"Valid data options are:\n"
" -file <filename> read spf data from a file. Use '-'\n"
" to read from stdin.\n"
"\n"
" -ip <IP address> The IP address that is sending email\n"
" -sender <email address> The email address used as the\n"
" envelope-from. If no username (local\n"
" part) is given, 'postmaster' will be\n"
" assumed.\n"
" -helo <domain name> The domain name given on the SMTP HELO\n"
" command. This is only needed if the\n"
" -sender option is not given.\n"
" -rcpt-to <email addresses> A comma separated lists of email addresses\n"
" that will have email from their secondary\n"
" MXes automatically allowed.\n"
"\n"
"The data options are required. The -file option conflicts with all\n"
"the other data options. The -helo and -rcpt-to are optional.\n"
"\n"
"\n"
"Valid control options are:\n"
" -debug [debug level] debug level.\n"
" -local <SPF mechanisms> Local policy for whitelisting.\n"
" -trusted <0|1> Should trusted-forwarder.org be checked?\n"
" -guess <SPF mechanisms> Default checks if no SPF record is found.\n"
" -default-explanation <str> Default explanation string to use.\n"
" -max-lookup <number> Maximum number of DNS lookups to allow\n"
" -sanitize <0|1> Clean up invalid characters in output?\n"
" -name <domain name> The name of the system doing the SPF\n"
" checking\n"
" -override <...> Override SPF records for domains\n"
" -fallback <...> Fallback SPF records for domains\n"
" -dns <dns layers> Comma seperated list of DNS layers\n"
" to use.\n"
"\n"
" -keep-comments Print comments found when reading\n"
" from a file.\n"
" -version Print version of spfquery.\n"
" -help Print out these options.\n"
"\n"
"Examples:\n"
"\n"
"spfquery -ip=11.22.33.44 -sender=user@aol.com -helo=spammer.tld\n"
"spfquery -f test_data\n"
"echo \"127.0.0.1 myname@mydomain.com helohost.com\" | spfquery -f -\n"
);
}
int main( int argc, char *argv[] )
{
int c;
int res = 0;
char *opt_file = NULL;
char *opt_ip = NULL;
char *opt_sender = NULL;
char *opt_helo = NULL;
char *opt_rcpt_to = NULL;
char *opt_local = NULL;
int opt_trusted = 0;
const char *opt_guess = NULL;
const char *opt_exp = NULL;
const char *opt_max_lookup = NULL;
const char *opt_sanitize = NULL;
const char *opt_name = "spfquery";
int opt_debug = 0;
#ifdef _WIN32
const char *opt_dns = "windns,cache";
#else
const char *opt_dns = "resolv,cache";
#endif
char *opt_fallback = NULL;
char *opt_override = NULL;
int opt_keep_comments = 0;
char in_line[4096];
const char *p, *p_end;
char *p2;
const char *prev_p, *prev_p_end;
size_t len;
int i;
int done_once;
int major, minor, patch;
SPF_id_t spfid = NULL;
SPF_config_t spfcid = NULL;
SPF_dns_config_t spfdcid = NULL;
#define MAX_DNS_LAYERS 10
SPF_dns_config_t spfdcid_opt[MAX_DNS_LAYERS] = { NULL };
char *spfdcid_name[MAX_DNS_LAYERS] = { NULL };
SPF_dns_config_t prev_dns = NULL;
SPF_output_t spf_output;
SPF_c_results_t local_policy;
SPF_c_results_t exp;
SPF_c_results_t best_guess;
SPF_err_t err;
FILE *fin;
char *result;
#ifdef _WIN32
if ( SPF_win32_startup() == 0 )
{
fprintf( stderr, "Could not startup WinSock, wrong version." );
res = 255;
goto error;
}
#endif
SPF_init_c_results( &local_policy );
SPF_init_c_results( &exp );
SPF_init_c_results( &best_guess );
/*
* check the arguments
*/
while (1)
{
int option_index = 0;
static struct option long_options[] = {
{"file", 1, 0, 'f'},
{"ip", 1, 0, 'i'},
{"sender", 1, 0, 's'},
{"helo", 1, 0, 'h'},
{"rcpt-to", 1, 0, 'r'},
{"debug", 2, 0, 'd'},
{"local", 1, 0, 'l'},
{"trusted", 1, 0, 't'},
{"guess", 1, 0, 'g'},
{"default-explanation", 1, 0, 'e'},
{"max-lookup", 1, 0, 'm'},
{"sanitize", 1, 0, 'c'},
{"name", 1, 0, 'n'},
{"override", 1, 0, 'a'},
{"fallback", 1, 0, 'z'},
{"dns", 1, 0, 'D'},
{"keep-comments", 0, 0, 'k'},
{"version", 0, 0, 'v'},
{"help", 0, 0, '?'},
{0, 0, 0, 0}
};
c = getopt_long_only (argc, argv, "f:i:s:h:r:lt::gemcnd::D:kz:a:v",
long_options, &option_index);
if (c == -1)
break;
switch (c)
{
case 'f':
opt_file = optarg;
break;
case 'i':
opt_ip = optarg;
break;
case 's':
opt_sender = optarg;
break;
case 'h':
opt_helo = optarg;
break;
case 'r':
opt_rcpt_to = optarg;
break;
case 'l':
opt_local = optarg;
break;
case 't':
if (optarg == NULL)
opt_trusted = 1;
else
opt_trusted = atoi( optarg );
break;
case 'g':
opt_guess = optarg;
break;
case 'e':
opt_exp = optarg;
break;
case 'm':
opt_max_lookup = optarg;
break;
case 'c': /* "clean" */
opt_sanitize = optarg;
break;
case 'n': /* name of host doing SPF checking */
opt_name = optarg;
break;
case 'a':
opt_override = optarg;
fprintf( stderr, "Unimplemented option: -override\n" );
break;
case 'z':
opt_fallback = optarg;
fprintf( stderr, "Unimplemented option: -fallback\n" );
break;
case 'D': /* DNS layers to use */
opt_dns = optarg;
break;
case 'v':
fprintf( stderr, "spfquery version information:\n" );
fprintf( stderr, "SPF test system version: %s\n",
SPF_TEST_VERSION );
fprintf( stderr, "Compiled with SPF library version: %d.%d.%d\n",
SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR,
SPF_LIB_VERSION_PATCH );
SPF_get_lib_version( &major, &minor, &patch );
fprintf( stderr, "Running with SPF library version: %d.%d.%d\n",
major, minor, patch );
fprintf( stderr, "\n" );
usage();
res = 255;
goto error;
break;
case 0:
case '?':
help();
res = 255;
goto error;
break;
case 'k':
opt_keep_comments = 1;
break;
case 'd':
if (optarg == NULL)
opt_debug = 1;
else
opt_debug = atoi( optarg );
break;
default:
fprintf( stderr, "Error: getopt returned character code 0%o ??\n", c);
}
}
if (optind != argc)
{
help();
res = 255;
goto error;
}
/*
* set up the SPF configuration
*/
spfcid = SPF_create_config();
if ( spfcid == NULL )
{
fprintf( stderr, "SPF_create_config failed.\n" );
res = 255;
goto error;
}
SPF_set_debug( spfcid, 1 ); /* flush err msgs from init */
SPF_set_debug( spfcid, opt_debug );
if ( opt_name )
SPF_set_rec_dom( spfcid, opt_name );
if ( opt_sanitize )
SPF_set_sanitize( spfcid, atoi( opt_sanitize ) );
if ( opt_max_lookup )
SPF_set_max_dns_mech( spfcid, atoi( opt_max_lookup ) );
err = SPF_compile_local_policy( spfcid, opt_local, opt_trusted,
&local_policy );
if ( err )
{
fprintf( stderr, "Error compiling local policy:\n%s\n",
local_policy.err_msg );
#if 0
res = 255;
goto error;
#endif
}
SPF_set_local_policy( spfcid, local_policy );
if ( opt_exp )
{
err = SPF_compile_exp( spfcid, opt_exp, &exp );
if ( err )
{
fprintf( stderr, "Error compiling default explanation:\n%s\n",
exp.err_msg );
#if 0
res = 255;
goto error;
#endif
}
SPF_set_exp( spfcid, exp );
}
if ( opt_guess )
{
err = SPF_compile_local_policy( spfcid, opt_guess,
opt_trusted, &best_guess );
if ( err )
{
fprintf( stderr, "Error compiling best guess mechanisms:\n%s",
best_guess.err_msg );
#if 0
res = 255;
goto error;
#endif
}
}
/*
* set up dns layers to use
*/
p = opt_dns;
prev_dns = NULL;
prev_p = p;
prev_p_end = p;
memset( spfdcid_opt, 0, sizeof( spfdcid ) );
for( i = 0; i < MAX_DNS_LAYERS; i++ )
{
p_end = p + strcspn( p, "," );
if ( p_end - p == sizeof( "null" ) - 1
&& strncmp( "null", p, p_end - p ) == 0 )
{
len = prev_p_end - prev_p + sizeof( "pre-" );
if ( len > sizeof( "pre-" ) )
{
spfdcid_name[i] = malloc( len + 1 );
if ( spfdcid_name[i] )
snprintf( spfdcid_name[i], len, "pre-%.*s", len, prev_p );
}
else
spfdcid_name[i] = strdup( "null" );
spfdcid_opt[i] = SPF_dns_create_config_null(prev_dns, opt_debug,
spfdcid_name[i] );
}
#ifndef _WIN32
else if ( p_end - p == sizeof( "resolv" ) - 1
&& strncmp( "resolv", p, p_end - p ) == 0 )
{
spfdcid_opt[i] = SPF_dns_create_config_resolv( prev_dns,
opt_debug );
}
#else
else if ( p_end - p == sizeof( "windns" ) - 1
&& strncmp( "windns", p, p_end - p ) == 0 )
{
spfdcid_opt[i] = SPF_dns_create_config_windns( prev_dns,
opt_debug );
}
#endif
else if ( p_end - p == sizeof( "test" ) - 1
&& strncmp( "test", p, p_end - p ) == 0 )
{
spfdcid_opt[i] = SPF_dns_create_config_test( prev_dns );
}
else if ( p_end - p == sizeof( "cache" ) - 1
&& strncmp( "cache", p, p_end - p ) == 0 )
{
spfdcid_opt[i] = SPF_dns_create_config_cache( prev_dns, 8,
opt_debug );
SPF_dns_set_conserve_cache( spfdcid_opt[i], FALSE );
}
if ( spfdcid_opt[i] == NULL )
{
fprintf( stderr, "Could not create DNS layer: %.*s\n",
p_end - p, p );
res = 255;
goto error;
}
prev_dns = spfdcid_opt[i];
prev_p = p;
prev_p_end = p_end;
if ( *p_end == '\0' )
break;
p = p_end + 1;
}
if ( i < MAX_DNS_LAYERS-1 )
{
i++;
len = prev_p_end - prev_p + sizeof( "pre-" );
if ( len > 0 )
{
spfdcid_name[i] = malloc( len + 1 );
if ( spfdcid_name[i] )
snprintf( spfdcid_name[i], len, "pre-%.*s", len, prev_p );
}
spfdcid_opt[i] = SPF_dns_create_config_null(prev_dns, opt_debug,
spfdcid_name[i] );
}
spfdcid = spfdcid_opt[i];
/*
* process the SPF request
*/
if (opt_ip == NULL || (opt_sender == NULL && opt_helo == NULL) )
{
if (opt_file == NULL ||
opt_ip || opt_sender || opt_helo)
{
usage();
res = 255;
goto error;
}
/*
* the requests are on STDIN
*/
if (strcmp( opt_file, "-" ) == 0)
fin = stdin;
else
fin = fopen( opt_file, "r" );
if (!fin)
{
fprintf( stderr, "Could not open: %s\n", opt_file );
res = 255;
goto error;
}
} else {
if (opt_file)
{
usage();
res = 255;
goto error;
}
fin = NULL;
}
done_once = FALSE;
while ( TRUE )
{
if ( fin )
{
if ( fgets( in_line, sizeof( in_line ), fin ) == NULL )
break;
p2 = strchr( in_line, '\n' );
if ( p2 )
*p2 = '\0';
p2 = in_line;
p2 += strspn( p2, " \t\n" );
if ( *p2 == '\0' || *p2 == '#' )
{
if ( opt_keep_comments )
printf( "%s\n", in_line );
continue;
}
opt_ip = p2;
p2 += strcspn( p2, " \t\n" );
*p2++ = '\0';
p2 += strspn( p2, " \t\n" );
opt_sender = p2;
p2 += strcspn( p2, " \t\n" );
*p2++ = '\0';
p2 += strspn( p2, " \t\n" );
opt_helo = p2;
p2 += strcspn( p2, " \t\n" );
*p2++ = '\0';
p2 += strspn( p2, " \t\n" );
opt_rcpt_to = p2;
p2 += strcspn( p2, " \t\n" );
*p2++ = '\0';
} else {
if ( done_once )
break;
}
done_once = TRUE;
if ( SPF_set_ip_str( spfcid, opt_ip ) )
{
printf( "Invalid IP address.\n" );
res = 255;
continue;
}
if ( SPF_set_helo_dom( spfcid, opt_helo ) )
{
printf( "Invalid HELO domain.\n" );
res = 255;
continue;
}
if ( SPF_set_env_from( spfcid, opt_sender ) )
{
printf( "Invalid envelope from address.\n" );
res = 255;
continue;
}
if ( opt_rcpt_to == NULL || *opt_rcpt_to == '\0' )
{
spf_output = SPF_result( spfcid, spfdcid );
result = strdup( SPF_strresult( spf_output.result ) );
}
else
{
const char *per_result;
char *p, *next_p;
size_t len;
result = NULL;
/* SPF_result_2mxdoesn't support multiple rcpt-to's */
for( p = opt_rcpt_to; (p = strchr( p, ';' )) != NULL; )
*p = ',';
for( p = next_p = opt_rcpt_to; p != NULL; p = next_p )
{
next_p = strchr( p, ',' );
if ( next_p != NULL )
{
*next_p = '\0';
next_p++;
}
spf_output = SPF_result_2mx( spfcid, spfdcid, p );
per_result = SPF_strresult( spf_output.result );
SPF_free_output( &spf_output );
if ( result == NULL )
{
result = strdup( per_result );
} else {
len = strlen( result ) + sizeof( "," ) + strlen( per_result );
result = realloc( result, len );
strcat( result, "," );
strcat( result, per_result );
}
}
spf_output = SPF_result_2mx_msg( spfcid, spfdcid );
per_result = SPF_strresult( spf_output.result );
if ( result == NULL ) {
result = strdup( per_result );
}
else {
len = strlen( result ) + sizeof( "," ) + strlen( per_result );
result = realloc( result, len );
strcat( result, "," );
strcat( result, per_result );
}
}
if ( opt_debug > 0 )
{
printf ( "err = %s (%d)\n",
SPF_strerror( spf_output.err ), spf_output.err );
printf ( "err_msg = %s\n", spf_output.err_msg ? spf_output.err_msg : "" );
}
printf( "%s\n%s\n%s\n%s\n",
result,
spf_output.smtp_comment ? spf_output.smtp_comment : "",
spf_output.header_comment ? spf_output.header_comment : "",
spf_output.received_spf ? spf_output.received_spf : "" );
free( result );
if ( opt_guess )
{
SPF_free_output( &spf_output );
printf( "\nBest guess:\n" );
spf_output = SPF_eval_id( spfcid, best_guess.spfid, spfdcid, TRUE, FALSE, NULL );
SPF_result_comments( spfcid, spfdcid, best_guess, &spf_output );
if ( opt_debug > 0 )
{
printf ( "result = %s (%d)\n",
SPF_strresult( spf_output.result ), spf_output.result );
printf ( "err = %s (%d)\n",
SPF_strerror( spf_output.err ), spf_output.err );
printf ( "err_msg = %s\n", spf_output.err_msg ? spf_output.err_msg : "" );
}
printf( "%s\n%s\n%s\n%s\n",
SPF_strresult( spf_output.result ),
spf_output.smtp_comment ? spf_output.smtp_comment : "",
spf_output.header_comment ? spf_output.header_comment : "",
spf_output.received_spf ? spf_output.received_spf : "" );
}
res = spf_output.result;
SPF_free_output( &spf_output );
fflush(stdout);
}
error:
if ( spfid ) SPF_destroy_id( spfid );
for( i = MAX_DNS_LAYERS-1; i >= 0; i-- )
{
if ( spfdcid_opt[i] != NULL )
SPF_dns_destroy_config( spfdcid_opt[i] );
if ( spfdcid_name[i] != NULL )
free( spfdcid_name[i] );
}
if ( spfcid ) SPF_destroy_config( spfcid );
SPF_free_c_results( &local_policy );
SPF_free_c_results( &exp );
SPF_free_c_results( &best_guess );
SPF_destroy_default_config();
#ifdef _WIN32
SPF_win32_cleanup();
#endif
return res;
}
syntax highlighted by Code2HTML, v. 0.9.1