/* 
 * 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 */
# include <ctype.h>        /* isupper / tolower */
#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


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



SPF_err_t SPF_data2str( char **p_p, char *p_end,
			SPF_data_t *data, SPF_data_t *data_end,
			int is_mod, int cidr_ok )
{
    char	*p = *p_p;

    size_t	len;

    SPF_data_t	*cidr_data;



    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;

    cidr_data = NULL;
    if ( data < data_end && data->dc.parm_type == PARM_CIDR )
    {
	if ( !cidr_ok )
	    return SPF_E_INTERNAL_ERROR;

	cidr_data = data;
	data = SPF_next_data( data );
    }
	

    for( ; data < data_end; data = SPF_next_data( data ) )
    {
	if ( data->ds.parm_type == PARM_STRING )
	{
	    char *s = SPF_data_str( data );
	    char *s_end = s + data->ds.len;

	    if ( p_end - (p + data->ds.len) <= 0 ) return SPF_E_INTERNAL_ERROR;

	    while( s < s_end )
	    {
		if ( *s == ' ' )
		{
		    *p++ = '%';
		    *p++ = '_';
		    s++;
		}
		else if ( *s == '%' )
		{
		    *p++ = '%';
		    s++;
		    if ( s[0] == '2' && s[1] == '0' )
		    {
			*p++ = '-';
			s += 2;
		    }
		    else
		    {
			*p++ = '%';
			*p++ = *s++;
		    }
		}
		    
		else
		{
		    *p++ = *s++;
		}
	    }

	    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	}
	else if ( data->dc.parm_type == PARM_CIDR )
	{
	    return SPF_E_INVALID_CIDR;
	}
	else
	{
	    len = snprintf( p, p_end - p, "%%{" );
	    p += len;
	    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;


	    if ( p_end - p <= 1 ) return SPF_E_INTERNAL_ERROR;
	    switch( data->dv.parm_type )
	    {
	    case PARM_LP_FROM:		/* local-part of envelope-sender */
		*p = 'l';
		break;
		    
	    case PARM_ENV_FROM:		/* envelope-sender		*/
		*p = 's';
		break;
		    
	    case PARM_DP_FROM:		/* envelope-domain		*/
		*p = 'o';
		break;

	    case PARM_CUR_DOM:		/* current-domain		*/
		*p = 'd';
		break;

	    case PARM_CLIENT_IP:	/* SMTP client IP		*/
		*p = 'i';
		break;

	    case PARM_CLIENT_IP_P:	/* SMTP client IP (pretty)	*/
		*p = 'c';
		break;

	    case PARM_TIME:		/* time in UTC epoch secs	*/
		if ( !is_mod )
		    return SPF_E_INVALID_VAR;
		*p = 't';
		break;

	    case PARM_CLIENT_DOM:	/* SMTP client domain name	*/
		*p = 'p';
		break;

	    case PARM_CLIENT_VER:	/* IP ver str - in-addr/ip6	*/
		*p = 'v';
		break;

	    case PARM_HELO_DOM:		/* HELO/EHLO domain		*/
		*p = 'h';
		break;

	    case PARM_REC_DOM:		/* receiving domain		*/
		*p = 'r';
		break;

	    default:
		return SPF_E_INVALID_VAR;
		break;
	    }
	    if ( data->dv.url_encode )
		*p = toupper( *p );
	    p++;
	    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
		

	    if ( data->dv.num_rhs )
	    {
		len = snprintf( p, p_end - p, "%d", data->dv.num_rhs );
		p += len;
		if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	    }
	    
		
	    if ( p_end - p <= 8 ) return SPF_E_INTERNAL_ERROR;
	    if ( data->dv.rev )
		*p++ = 'r';

	    if ( data->dv.delim_dot
		 && ( data->dv.delim_dash
		      || data->dv.delim_plus
		      || data->dv.delim_equal
		      || data->dv.delim_bar
		      || data->dv.delim_under
		     )
		)
		*p++ = '.';
	    if ( data->dv.delim_dash )
		*p++ = '-';
	    if ( data->dv.delim_plus )
		*p++ = '+';
	    if ( data->dv.delim_equal )
		*p++ = '=';
	    if ( data->dv.delim_bar )
		*p++ = '|';
	    if ( data->dv.delim_under )
		*p++ = '_';

	    *p++ = '}';
	    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	}
    }

	
    if ( cidr_data )
    {
	if ( cidr_data->dc.ipv4 )
	{
	    len = snprintf( p, p_end - p, "/%d", cidr_data->dc.ipv4 );
	    p += len;
	    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	}
	    
	if ( cidr_data->dc.ipv6 )
	{
	    len = snprintf( p, p_end - p, "//%d", cidr_data->dc.ipv6 );
	    p += len;
	    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	}
    }

    *p_p = p;
    return SPF_E_SUCCESS;
}



SPF_err_t SPF_id2str( char **dst_rec, size_t *dst_len, SPF_id_t src_spfid )
{
    SPF_internal_t *spfi = SPF_id2spfi(src_spfid);
    int		i;
    SPF_mech_t	*mech;
    SPF_mod_t	*mod;

    SPF_data_t	*data, *data_end;

    size_t	len;
    const char	*p_err;
    char	*p, *p_end;
    
    char	ip4_buf[ INET_ADDRSTRLEN ];
    char	ip6_buf[ INET6_ADDRSTRLEN ];

    int		cidr_ok;
    SPF_err_t	err;
    


    /*
     * make sure we were passed valid data to work with
     */
    if ( spfi == NULL )
	SPF_error( "spfid is NULL" );
    

    /*
     * make sure the return buffer is big enough
     *
     * The worse case for the version string:
     *   "v=spf1 " = 6                    = 4
     * The worst cases for mechanisms
     *   "ip4:111.222.333.444/31 " = 23   < 6 * 3.9
     *   "ip6:<full-ipv6-spec>/126 " = 49 < 18 * 2.8
     *   "-include:x " = 11               = 5 * 2.2
     *   "-all " = 5			  = 2 * 2.5
     * 
     * The worst case for modifiers:
     *   "a=%{i15r.-+=|_} " = 16          = 5 * 3.2
     */
    
    len = sizeof( SPF_VER_STR )
	+ spfi->header.mech_len * 4 + spfi->header.mod_len * 4 /* data */
	+ sizeof( "\0" );
    
    if ( *dst_len < len )
    {
	char	*new_rec;
	size_t	new_len;
	
	/* FIXME  dup code */
	/* allocate lots so we don't have to remalloc often */
	new_len = len + 64;

	new_rec = realloc( *dst_rec, new_len );
	if ( new_rec == NULL )
	    return SPF_E_NO_MEMORY;

	*dst_rec = new_rec;
	*dst_len = new_len;
    }
    memset( *dst_rec, '\0', *dst_len );	/* cheaper than NUL at each step */
    p = *dst_rec;
    p_end = *dst_rec + *dst_len;


    /*
     * generate SPF version string
     */

    len = snprintf( p, p_end - p, "v=spf%d", spfi->header.version );
    p += len;
    if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	

    /*
     * generate mechanisms
     */
    
    mech = spfi->mech_first;
    for( i = 0; i < spfi->header.num_mech; i++ )
    {
	if ( p_end - p <= 1 ) return SPF_E_INTERNAL_ERROR;
	*p++ = ' ';
	

	if ( p_end - p <= 1 ) return SPF_E_INTERNAL_ERROR;
	switch( mech->prefix_type )
	{
	case PREFIX_PASS:
	    /* *p++ = '+'; */
	    break;
	    
	case PREFIX_FAIL:
	    *p++ = '-';
	    break;
	    
	case PREFIX_SOFTFAIL:
	    *p++ = '~';
	    break;
	    
	case PREFIX_NEUTRAL:
	    *p++ = '?';
	    break;
	    
	case PREFIX_UNKNOWN:
	    return SPF_E_RESULT_UNKNOWN;
	    break;

	default:
	    return SPF_E_INVALID_PREFIX;
	    break;
	}
	
	    
	switch( mech->mech_type )
	{
	case MECH_A:
	    len = snprintf( p, p_end - p, "a" );
	    break;
	    
	case MECH_MX:
	    len = snprintf( p, p_end - p, "mx" );
	    break;
	    
	case MECH_PTR:
	    len = snprintf( p, p_end - p, "ptr" );
	    break;
	    
	case MECH_INCLUDE:
	    len = snprintf( p, p_end - p, "include" );
	    break;
	    
	case MECH_IP4:
	    p_err = inet_ntop( AF_INET, SPF_mech_ip4_data( mech ),
			     ip4_buf, sizeof( ip4_buf ) );
	    if ( p_err == NULL )
		return SPF_E_INTERNAL_ERROR;
	    if ( mech->parm_len )
		len = snprintf( p, p_end - p, "ip4:%s/%d",
				    ip4_buf, mech->parm_len );
	    else
		len = snprintf( p, p_end - p, "ip4:%s", ip4_buf );
	    break;
	    
	case MECH_IP6:
	    p_err = inet_ntop( AF_INET6, SPF_mech_ip6_data( mech ),
			     ip6_buf, sizeof( ip6_buf ) );
	    if ( p_err == NULL )
		return SPF_E_INTERNAL_ERROR;
	    if ( mech->parm_len )
		len = snprintf( p, p_end - p, "ip6:%s/%d",
				    ip6_buf, mech->parm_len );
	    else
		len = snprintf( p, p_end - p, "ip6:%s", ip6_buf );
	    break;
	    
	case MECH_EXISTS:
	    len = snprintf( p, p_end - p, "exists" );
	    break;
	    
	case MECH_ALL:
	    len = snprintf( p, p_end - p, "all" );
	    break;
	    
	case MECH_REDIRECT:
	    len = snprintf( p, p_end - p, "redirect" );
	    break;

	default:
	    return SPF_E_UNKNOWN_MECH;
	    break;
	}
	p += len;
	if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	    
	if ( mech->mech_type != MECH_IP4  &&  mech->mech_type != MECH_IP6 )
	{
	    data = SPF_mech_data( mech );
	    data_end = SPF_mech_end_data( mech );
	
	    if ( data != data_end
		 && (data->dc.parm_type != PARM_CIDR
		     || SPF_next_data( data ) != data_end)
		)
		*p++ = ':';

	    cidr_ok = mech->mech_type == MECH_A || mech->mech_type == MECH_MX;
	    err = SPF_data2str( &p, p_end, data, data_end,
				FALSE, cidr_ok );

	    if ( err != SPF_E_SUCCESS )
		return err;
	}
	
	mech = SPF_next_mech( mech );
    }


    /*
     * generate modifiers
     */

    mod = spfi->mod_first;
    for( i = 0; i < spfi->header.num_mod; i++ )
    {
	if ( p_end - p <= 1 ) return SPF_E_INTERNAL_ERROR;
	*p++ = ' ';
	
	len = snprintf( p, p_end - p, "%.*s=",
			    mod->name_len, SPF_mod_name( mod )  );
	p += len;
	if ( p_end - p <= 0 ) return SPF_E_INTERNAL_ERROR;
	    
	data = SPF_mod_data( mod );
	data_end = SPF_mod_end_data( mod );
	
	err = SPF_data2str( &p, p_end, data, data_end,
			    TRUE, TRUE );

	if ( err != SPF_E_SUCCESS )
	    return err;
	
	
	mod = SPF_next_mod( mod );
    }



    *p++ = '\0';

    return SPF_E_SUCCESS;
}




syntax highlighted by Code2HTML, v. 0.9.1