/* 
 * 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.
 *
 * These licenses can be found with the distribution in the file LICENSES
 */

#include "spf_alt/spf_sys_config.h"

#ifdef STDC_HEADERS
# include <stdio.h>        /* stdin / stdout */
# include <stdlib.h>       /* malloc / free */
#endif

#ifdef HAVE_STRING_H
# include <string.h>       /* strstr / strdup */
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>       /* strstr / strdup */
# endif
#endif


#include "spf_alt/spf.h"
#include "spf_alt/spf_dns.h"
#include "spf_alt/spf_internal.h"
#include "spf_alt/spf_dns_internal.h"



char *SPF_smtp_comment( SPF_config_t spfcid, SPF_id_t spfid, SPF_dns_config_t spfdcid, SPF_output_t output )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);
    SPF_err_t		err;
    
    char	*buf;
    size_t	buf_len = SPF_SMTP_COMMENT_SIZE;
    
    char	*p, *p_end;


    buf = malloc( buf_len );
    if ( buf == NULL )
	return buf;
    
    err = SPF_get_exp( spfcid, spfid, spfdcid, &buf, &buf_len );
    if ( buf == NULL )
	return buf;
    
    p = buf + strlen( buf );
    p_end = buf + buf_len;


    /* FIXME  this isn't quite the same as the perl "$smtp_why" */

    if ( err == SPF_E_SUCCESS )
    {
	if ( output.reason != SPF_REASON_NONE
	     &&  output.reason != SPF_REASON_MECH )
	{
	    snprintf( p, p_end - p, " : Reason: %s",
		      SPF_strreason( output.reason ) );
	}
	    
    } else {
	if ( spfic->debug > 0 )
	    printf( "Error formatting explanation string:  %s\n",
		    SPF_strerror( err ) );

	snprintf( p, p_end - p, " : %s", SPF_strerror( err ) );
    }

    return buf;
}



char *SPF_header_comment( SPF_config_t spfcid, SPF_output_t output )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);
    char		*spf_source;
    
    size_t		len;

    char	ip4_buf[ INET_ADDRSTRLEN ];
    char	ip6_buf[ INET6_ADDRSTRLEN ];
    const char	*ip;

    char	*buf;
    char	*sender_dom;
    char	*p, *p_end;

    
    sender_dom = spfic->dp_from;
    if ( sender_dom == NULL ) sender_dom = spfic->helo_dom;
	

    if ( output.reason == SPF_REASON_LOCAL_POLICY )
	spf_source = strdup( "local policy" );
    else if ( output.reason == SPF_REASON_2MX )
    {
	if ( spfic->rcpt_to_dom == NULL  || spfic->rcpt_to_dom[0] == '\0' )
	    SPF_error( "RCPT TO domain is NULL" );

	spf_source = strdup( spfic->rcpt_to_dom );
    }
    else if ( sender_dom == NULL )
	spf_source = strdup( "unknown domain" );
    else
    {
	len = strlen( sender_dom ) + sizeof( "domain of " );
	spf_source = malloc( len );
	if ( spf_source )
	    snprintf( spf_source, len, "domain of %s", sender_dom );
    }


    if ( spf_source == NULL )
	return NULL;

    ip = NULL;
    if ( spfic->client_ver == AF_INET )
    {
	ip = inet_ntop( AF_INET, &spfic->ipv4,
			ip4_buf, sizeof( ip4_buf ) );
    }
    else if (spfic->client_ver == AF_INET6 )
    {
	ip = inet_ntop( AF_INET6, &spfic->ipv6,
			ip6_buf, sizeof( ip6_buf ) );
    }

    if ( ip == NULL )
	ip = "(unknown ip address)";
    

    len = strlen( spfic->rec_dom ) + strlen( spf_source ) + strlen( ip ) + 80;
    buf = malloc( len );
    if ( buf == NULL )
    {
	free( spf_source );
	return NULL;
    }
    
    p = buf;
    p_end = p + len;

    
    /* create the stock header comment */
    p += snprintf( p, p_end - p, "%s: ",  spfic->rec_dom );

    switch( output.result)
    {
    case SPF_RESULT_PASS:
	if ( output.reason == SPF_REASON_LOCALHOST )
	    snprintf( p, p_end - p, "localhost is always allowed." );
	else if ( output.reason == SPF_REASON_2MX )
	    snprintf( p, p_end - p, "message received from %s which is an MX secondary for %s.",
		      ip, spf_source );
	else
	    snprintf( p, p_end - p, "%s designates %s as permitted sender",
		      spf_source, ip );
	break;

    case SPF_RESULT_FAIL:
	snprintf( p, p_end - p, "%s does not designate %s as permitted sender",
		  spf_source, ip );
	break;

    case SPF_RESULT_SOFTFAIL:
	snprintf( p, p_end - p, "transitioning %s does not designate %s as permitted sender",
		  spf_source, ip );
	break;

    case SPF_RESULT_UNKNOWN:
	snprintf( p, p_end - p, "error in processing during lookup of %s: %s",
		      spf_source, SPF_strerror( output.err ) );
	break;
	
    case SPF_RESULT_NEUTRAL:
    case SPF_RESULT_NONE:
	snprintf( p, p_end - p, "%s is neither permitted nor denied by %s",
		  ip, spf_source );
	break;

    case SPF_RESULT_ERROR:
	snprintf( p, p_end - p, "encountered temporary error during SPF processing of %s",
		  spf_source );
	break;


    default:
	snprintf( p, p_end - p, "error: unknown SPF result %d encountered while checking %s for %s",
		  output.result, ip, spf_source );
	break;
    }
    
    if( spf_source ) free( spf_source );

    return buf;
}



char *SPF_received_spf( SPF_config_t spfcid, SPF_c_results_t c_results, SPF_output_t output )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);
    
    char	ip4_buf[ INET_ADDRSTRLEN ];
    char	ip6_buf[ INET6_ADDRSTRLEN ];
    const char	*ip;

    char	*buf;
    size_t	buf_len = SPF_RECEIVED_SPF_SIZE;
    
    char	*p, *p_end;


    buf = malloc( buf_len );
    if ( buf == NULL )
	return buf;
    
    p = buf;
    p_end = p + buf_len;

    
    /* create the stock Received-SPF: header */
    p += snprintf( p, p_end - p, "Received-SPF: %s (%s)",
		   SPF_strresult( output.result ),
		   output.header_comment );
    if ( p_end - p <= 0 ) return buf;

    
    
    /* add in the optional ip address keyword */
    ip = NULL;
    if ( spfic->client_ver == AF_INET )
    {
	ip = inet_ntop( AF_INET, &spfic->ipv4,
			ip4_buf, sizeof( ip4_buf ) );
    }
    else if (spfic->client_ver == AF_INET6 )
    {
	ip = inet_ntop( AF_INET6, &spfic->ipv6,
			ip6_buf, sizeof( ip6_buf ) );
    }

    if ( ip != NULL )
    {
	p += snprintf( p, p_end - p, " client-ip=%s;", ip );
	if ( p_end - p <= 0 ) return buf;
    }
    

    /* add in the optional envelope-from keyword */
    if ( spfic->env_from != NULL )
    {
	p += snprintf( p, p_end - p, " envelope-from=%s;", spfic->env_from );
	if ( p_end - p <= 0 ) return buf;
    }
    

    /* add in the optional helo domain keyword */
    if ( spfic->helo_dom != NULL )
    {
	p += snprintf( p, p_end - p, " helo=%s;", spfic->helo_dom );
	if ( p_end - p <= 0 ) return buf;
    }
    

    /* add in the optional compiler error keyword */
    if ( output.err_msg != NULL )
    {
	p += snprintf( p, p_end - p, " problem=%s;", output.err_msg );
	if ( p_end - p <= 0 ) return buf;
    }
    else if ( c_results.err_msg != NULL )
    {
	p += snprintf( p, p_end - p, " problem=%s;", c_results.err_msg );
	if ( p_end - p <= 0 ) return buf;
    }
    
    /* FIXME  should the explanation string be included in the header? */

    /* FIXME  should the header be reformated to include line breaks? */

    return buf;
}



void SPF_result_comments( SPF_config_t spfcid, SPF_dns_config_t spfdcid,
			  SPF_c_results_t c_results, SPF_output_t *output )
{
    char		*buf;

    
    /* smtp_comment = exp= + <why string> */
    if ( c_results.spfid != NULL
	&& output->result != SPF_RESULT_PASS
	&& output->result != SPF_RESULT_NEUTRAL
	&& output->result != SPF_RESULT_UNKNOWN
	&& output->result != SPF_RESULT_NONE
	)
    {
	buf = SPF_smtp_comment( spfcid, c_results.spfid, spfdcid, *output );
	if ( buf )
	{
	    if ( output->smtp_comment ) free( output->smtp_comment );
	    output->smtp_comment = SPF_sanitize( spfcid, buf );
	}
    }

    
    /* header_comment = <list based off of SPF_result_t> */
    buf = SPF_header_comment( spfcid, *output );
    if ( buf )
    {
	if ( output->header_comment ) free( output->header_comment );
	output->header_comment = SPF_sanitize( spfcid, buf );
    }


    /* received_spf = <list based off of SPF_result_t> */
    buf = SPF_received_spf( spfcid, c_results, *output );
    if ( buf )
    {
	if ( output->received_spf ) free( output->received_spf );
	output->received_spf = SPF_sanitize( spfcid, buf );
    }

}




SPF_output_t SPF_result( SPF_config_t spfcid, SPF_dns_config_t spfdcid )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);
    SPF_output_t	output;
    SPF_err_t		err;
    SPF_c_results_t	c_results;



    SPF_init_output( &output );

    SPF_init_c_results( &c_results );


    /*
     * get the (compiled) SPF record
     */
    
    if ( SPF_is_loopback( spfcid ) )
    {
	output.result = SPF_RESULT_PASS;
	output.reason = SPF_REASON_LOCALHOST;
	output.err = SPF_E_SUCCESS;

    } else {

	err = SPF_get_spf( spfcid, spfdcid, NULL, &c_results );
	if ( err )
	{
	    if ( err == SPF_E_NOT_SPF )
		output.result = SPF_RESULT_NONE;
	    else
		output.result = SPF_RESULT_UNKNOWN;
	    output.reason = SPF_REASON_NONE;
	    output.err = err;
	    if ( output.err_msg ) free( output.err_msg );
	    if ( c_results.err_msg )
		output.err_msg = strdup( c_results.err_msg );
	    else
		output.err_msg = NULL;
	
	} else {
	    
	    /*
	     * find out whether this configuration passes
	     */

	    output = SPF_eval_id( spfcid, c_results.spfid, spfdcid,
				  TRUE, FALSE, NULL );
	    if ( spfic->debug > 0 )
		SPF_print( c_results.spfid );

	}
	
    }
    

    SPF_result_comments( spfcid, spfdcid, c_results, &output );

    SPF_free_c_results( &c_results );
    return output;
}


SPF_output_t SPF_result_helo( SPF_config_t spfcid, SPF_dns_config_t spfdcid )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);
    SPF_output_t	output;
    SPF_err_t		err;
    SPF_c_results_t	c_results;



    SPF_init_output( &output );

    SPF_init_c_results( &c_results );


    /*
     * get the (compiled) SPF record
     */
    
    if ( SPF_is_loopback( spfcid ) )
    {
	output.result = SPF_RESULT_PASS;
	output.reason = SPF_REASON_LOCALHOST;
	output.err = SPF_E_SUCCESS;
    }
    else if ( spfic->helo_dom == NULL )
    {
	output.result = SPF_RESULT_NONE;
	output.reason = SPF_REASON_NONE;
	output.err = SPF_E_NOT_CONFIG;
    }
    else
    {

	err = SPF_get_spf( spfcid, spfdcid, spfic->helo_dom, &c_results );
	if ( err )
	{
	    if ( err == SPF_E_NOT_SPF )
		output.result = SPF_RESULT_NONE;
	    else
		output.result = SPF_RESULT_UNKNOWN;
	    output.reason = SPF_REASON_NONE;
	    output.err = err;
	    if ( output.err_msg ) free( output.err_msg );
	    if ( c_results.err_msg )
		output.err_msg = strdup( c_results.err_msg );
	    else
		output.err_msg = NULL;
	
	} else {
	    
	    /*
	     * find out whether this configuration passes
	     */

	    output = SPF_eval_id( spfcid, c_results.spfid, spfdcid,
				  TRUE, TRUE, NULL );
	    if ( spfic->debug > 0 )
		SPF_print( c_results.spfid );

	}
	
    }
    

    SPF_result_comments( spfcid, spfdcid, c_results, &output );

    SPF_free_c_results( &c_results );
    return output;
}




SPF_output_t SPF_result_2mx( SPF_config_t spfcid, SPF_dns_config_t spfdcid,
			     const char *rcpt_to )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);
    SPF_output_t	output;
    SPF_err_t		err;
    SPF_c_results_t	c_results;

    size_t		buf_len;
    char		*buf;


    SPF_init_output( &output );
    SPF_free_output( &spfic->output_2mx );

    SPF_init_c_results( &c_results );


    if ( !spfic->found_non_2mx )
    {
	/* FIXME  more validation of rcpt_to needs to be done */

	spfic->rcpt_to_dom = strrchr( rcpt_to, '@' );
	if ( spfic->rcpt_to_dom != NULL )
 	    spfic->rcpt_to_dom++;	/* move past '@'		*/

	if ( spfic->rcpt_to_dom != NULL  &&  spfic->rcpt_to_dom[0] != '\0' )
	{

	    buf_len = strlen( spfic->rcpt_to_dom )
		+ sizeof( SPF_VER_STR " mx: " );
	    buf = malloc( buf_len );
    
	    snprintf( buf, buf_len, SPF_VER_STR " mx:%s", spfic->rcpt_to_dom );
	    
	    err = SPF_compile( spfcid, buf, &c_results );
	    free( buf );
	    
	    if ( err )
	    {
		if ( spfic->debug )
		    SPF_debugf( "Bad RCPT TO: %s (%s)  %s",
				rcpt_to, spfic->rcpt_to_dom, c_results.err_msg );

	    } else {
	    
		output = SPF_eval_id( spfcid, c_results.spfid, spfdcid,
				      FALSE, FALSE, NULL );
		if ( spfic->debug > 0 )
		    SPF_print( c_results.spfid );

		if ( output.result == SPF_RESULT_PASS )
		{
		    if ( spfic->debug  &&  output.reason != SPF_REASON_MECH )
			SPF_debugf( "Unexpected reason: %s",
				    SPF_strreason( output.reason ) );

		    output.reason = SPF_REASON_2MX;
		
		    SPF_result_comments( spfcid, spfdcid, c_results, &output );

		    SPF_free_c_results( &c_results );
		    spfic->output_2mx = SPF_dup_output( output );
		    spfic->found_2mx = TRUE;
		    return output;
		}
	    }

	} else {
	    if ( spfic->debug )
		SPF_debugf( "RCPT TO: missing '@' %s", rcpt_to );
	}
    }
    
    
    output = SPF_result( spfcid, spfdcid );
    
    SPF_free_c_results( &c_results );
    spfic->output_2mx = SPF_dup_output( output );
    spfic->found_non_2mx = TRUE;
    return output;
    
}


SPF_output_t SPF_result_2mx_msg( SPF_config_t spfcid, SPF_dns_config_t spfdcid )
{
    SPF_iconfig_t	*spfic = SPF_cid2spfic(spfcid);

    if ( !spfic->found_non_2mx )
    {
	if ( spfic->found_2mx )
	    return SPF_dup_output( spfic->output_2mx );

	/* this will only happen if SPF_result_2mx() wasn't called */
	SPF_free_output( &spfic->output_2mx );
	spfic->output_2mx = SPF_result( spfcid, spfdcid );
    }

    return SPF_dup_output( spfic->output_2mx );
}



const char *SPF_strresult( SPF_result_t result )
{
    switch( result )
    {
    case SPF_RESULT_PASS:		/* +				*/
	return "pass";
	break;

    case SPF_RESULT_FAIL:		/* -				*/
	return "fail";
	break;

    case SPF_RESULT_SOFTFAIL:		/* ~				*/
	return "softfail";
	break;

    case SPF_RESULT_NEUTRAL:		/* ?				*/
	return "neutral";
	break;

    case SPF_RESULT_UNKNOWN:		/* permanent error		*/
	return "unknown";
	break;

    case SPF_RESULT_ERROR:		/* temporary error		*/
	return "error";
	break;

    case SPF_RESULT_NONE:		/* no SPF record found		*/
	return "none";
	break;

    default:
	return "(invalid-result)";
	break;
    }
}


const char *SPF_strreason( SPF_reason_t reason )
{
    switch( reason )
    {
    case SPF_REASON_NONE:
	return "none";
	break;
	
    case SPF_REASON_LOCALHOST:
	return "localhost";
	break;
	
    case SPF_REASON_LOCAL_POLICY:
	return "local policy";
	break;
	
    case SPF_REASON_MECH:
	return "mechanism";
	break;
	
    case SPF_REASON_DEFAULT:
	return "default";
	break;
	
    case SPF_REASON_2MX:
	return "secondary MX";
	break;
	
    default:
	return "(invalid reason)";
	break;
	
    }
}




void SPF_init_output( SPF_output_t *output )
{
    memset( output, 0, sizeof( *output ) );
}


SPF_output_t SPF_dup_output( SPF_output_t output )
{
    SPF_output_t new_out;
    int		i;

    SPF_init_output( &new_out );

    new_out.result = output.result;
    new_out.reason = output.reason;
    new_out.err    = output.err;

    if ( output.err_msg ) new_out.err_msg = strdup( output.err_msg );

    if ( output.err_msgs )
    {
	new_out.num_errs = output.num_errs;
	new_out.err_msgs = malloc( output.num_errs * sizeof( output.err_msgs ) );
	if ( new_out.err_msgs )
	{
	    for( i = 0; i < output.num_errs; i++ )
		if ( output.err_msgs[i] )
		    new_out.err_msgs[i] = strdup( output.err_msgs[i] );
	}
    }

    if ( output.smtp_comment )
	new_out.smtp_comment = strdup( output.smtp_comment );
    if ( output.header_comment )
	new_out.header_comment = strdup( output.header_comment );
    if ( output.received_spf )
	new_out.received_spf = strdup( output.received_spf );

    return new_out;
}


void SPF_free_output( SPF_output_t *output )
{
    int		i;

    if ( output->err_msg ) free( output->err_msg );

    if ( output->err_msgs )
    {
	for( i = 0; i < output->num_errs; i++ )
	    if ( output->err_msgs[i] ) free( output->err_msgs[i] );

	free( output->err_msgs );
    }

    if ( output->smtp_comment ) free( output->smtp_comment );
    if ( output->header_comment ) free( output->header_comment );
    if ( output->received_spf ) free( output->received_spf );

    SPF_init_output( output );
}





syntax highlighted by Code2HTML, v. 0.9.1