/*
* 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"
#define CIDR_NONE 0
#define CIDR_OPTIONAL 1
#define CIDR_ONLY 2
static SPF_err_t SPF_c_common_data_add( SPF_data_t *data, int *header_len, size_t *parm_len, size_t max_len, SPF_err_t big_err, char const **p_p, char const **p_token, int cidr_ok, int is_mod )
{
const char *p = *p_p;
const char *token = *p_token;
const char *real_end, *data_end;
const char *cur, *start;
size_t len, ds_len;
int str_found;
char *dst;
int c;
SPF_err_t comp_stat;
len = strcspn( p, " " );
real_end = data_end = p + len;
start = p;
/*
* create the CIDR length info
*/
if ( cidr_ok == CIDR_OPTIONAL || cidr_ok == CIDR_ONLY )
{
start = cur = data_end - 1;
/* find the beginning of the CIDR length notation */
while( isdigit( SPF_c2ui( *start ) ) )
start--;
if ( cur != start && *start == '/' )
{
/* we have at least '/nnn' */
data->dc.parm_type = PARM_CIDR;
data->dc.ipv4 = 0;
data->dc.ipv6 = 0;
/* get IPv6 CIDR length */
if ( start[-1] == '/' )
{
data_end = start - 1;
cur = start + 1;
c = 0;
while ( isdigit( SPF_c2ui( *cur ) ) )
{
c *= 10;
c += *cur - '0';
cur++;
if ( c > 128 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
}
if ( c == 0 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
if ( c == 128 ) c = 0;
data->dc.ipv6 = c;
/* now back up and see if there is a ipv4 cidr length */
start -= 2;
cur = start;
while( isdigit( SPF_c2ui( *start ) ) )
start--;
}
/* get IPv4 CIDR length */
if ( cur != start && *start == '/' )
{
data_end = start;
cur = start + 1;
c = 0;
while ( isdigit( SPF_c2ui( *cur ) ) )
{
c *= 10;
c += *cur - '0';
cur++;
if ( c > 32 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
}
if ( c == 0 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
if ( c == 32 ) c = 0;
data->dc.ipv4 = c;
}
if ( data->dc.ipv4 != 0 || data->dc.ipv6 != 0 )
{
len = sizeof( *data );
if ( *header_len + len > max_len )
{
comp_stat = big_err;
goto error;
}
*header_len += len;
if ( *parm_len + len > max_len ) /* redundant */
{
comp_stat = big_err;
goto error;
}
*parm_len += len;
data = SPF_next_data( data );
}
}
}
if ( cidr_ok == CIDR_ONLY && p != data_end )
{
p = start;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
/*
* create the data blocks
*/
while ( p != data_end )
{
/* is this a string? */
str_found = FALSE;
dst = NULL;
ds_len = 0;
while ( p[0] != '%' || p[1] != '{' )
{
if ( !str_found )
{
data->ds.parm_type = PARM_STRING;
ds_len = data->ds.len = 0;
ds_len = data->ds.reserved = 0;
dst = SPF_data_str( data );
str_found = TRUE;
}
token = p;
len = strcspn( p, " %" );
if ( p + len > data_end )
len = data_end - p;
p += len;
memcpy( dst, token, len );
ds_len += len;
dst += len;
if ( p == data_end || p[1] == '{' )
{
#if 0
/* align to an even length */
if ( (ds_len & 1) == 1 )
{
*dst++ = '\0';
ds_len++;
}
#endif
break;
}
/* must be % escape code */
p++;
switch ( *p )
{
case '%':
*dst++ = '%';
ds_len++;
break;
case '_':
*dst++ = ' ';
ds_len++;
break;
case '-':
*dst++ = '%';
*dst++ = '2';
*dst++ = '0';
ds_len += 3;
break;
default:
*dst++ = *p;
ds_len++;
/* FIXME issue a warning? */
#if 0
/* SPF spec says to treat it as a literal */
comp_stat = SPF_E_INVALID_ESC;
goto error;
#endif
break;
}
p++;
}
if ( str_found )
{
if ( ds_len > SPF_MAX_STR_LEN )
{
comp_stat = SPF_E_BIG_STRING;
goto error;
}
data->ds.len = ds_len;
len = sizeof( *data ) + ds_len;
if ( *header_len + len > max_len )
{
comp_stat = big_err;
goto error;
}
*header_len += len;
if ( *parm_len + len > max_len ) /* redundant */
{
comp_stat = big_err;
goto error;
}
*parm_len += len;
data = SPF_next_data( data );
}
/* end of string? */
if ( *p != '%' )
break;
/* this must be a variable */
p += 2;
token = p;
/* URL encoding */
c = *p;
if ( isupper( SPF_c2ui( c ) ) )
{
data->dv.url_encode = TRUE;
c = tolower(c);
}
else
data->dv.url_encode = FALSE;
switch ( c )
{
case 'l': /* local-part of envelope-sender */
data->dv.parm_type = PARM_LP_FROM;
break;
case 's': /* envelope-sender */
data->dv.parm_type = PARM_ENV_FROM;
break;
case 'o': /* envelope-domain */
data->dv.parm_type = PARM_DP_FROM;
break;
case 'd': /* current-domain */
data->dv.parm_type = PARM_CUR_DOM;
break;
case 'i': /* SMTP client IP */
data->dv.parm_type = PARM_CLIENT_IP;
break;
case 'c': /* SMTP client IP (pretty) */
data->dv.parm_type = PARM_CLIENT_IP_P;
break;
case 't': /* time in UTC epoch secs */
if ( !is_mod )
{
comp_stat = SPF_E_INVALID_VAR;
goto error;
}
data->dv.parm_type = PARM_TIME;
break;
case 'p': /* SMTP client domain name */
data->dv.parm_type = PARM_CLIENT_DOM;
break;
case 'v': /* IP ver str - in-addr/ip6 */
data->dv.parm_type = PARM_CLIENT_VER;
break;
case 'h': /* HELO/EHLO domain */
data->dv.parm_type = PARM_HELO_DOM;
break;
case 'r': /* receiving domain */
data->dv.parm_type = PARM_REC_DOM;
break;
default:
comp_stat = SPF_E_INVALID_VAR;
goto error;
break;
}
p++;
token = p;
/* get the number of subdomains to truncate to */
c = 0;
while ( isdigit( SPF_c2ui( *p ) ) )
{
c *= 10;
c += *p - '0';
p++;
}
if ( c > 15 || (c == 0 && p != token) )
{
comp_stat = SPF_E_BIG_SUBDOM;
goto error;
}
data->dv.num_rhs = c;
token = p;
/* should the string be reversed? */
if ( *p == 'r' )
{
data->dv.rev = 1;
p++;
}
else
data->dv.rev = FALSE;
token = p;
/* check for delimiters */
data->dv.delim_dot = FALSE;
data->dv.delim_dash = FALSE;
data->dv.delim_plus = FALSE;
data->dv.delim_equal = FALSE;
data->dv.delim_bar = FALSE;
data->dv.delim_under = FALSE;
if ( *p == '}' )
data->dv.delim_dot = TRUE;
while( *p != '}' )
{
token = p;
switch( *p )
{
case '.':
data->dv.delim_dot = TRUE;
break;
case '-':
data->dv.delim_dash = TRUE;
break;
case '+':
data->dv.delim_plus = TRUE;
break;
case '=':
data->dv.delim_equal = TRUE;
break;
case '|':
data->dv.delim_bar = TRUE;
break;
case '_':
data->dv.delim_under = TRUE;
break;
default:
comp_stat = SPF_E_INVALID_DELIM;
goto error;
break;
}
p++;
}
p++;
token = p;
len = sizeof( *data );
if ( *header_len + len > max_len )
{
comp_stat = big_err;
goto error;
}
*header_len += len;
if ( *parm_len + len > max_len ) /* redundant */
{
comp_stat = big_err;
goto error;
}
*parm_len += len;
data = SPF_next_data( data );
}
comp_stat = SPF_E_SUCCESS;
error:
*p_p = real_end;
*p_token = token;
return comp_stat;
}
SPF_err_t SPF_c_mech_add( SPF_id_t spfid, int mech_type, int prefix )
{
SPF_internal_t *spfi = SPF_id2spfi(spfid);
if ( spfi->mech_buf_len - spfi->header.mech_len < sizeof( SPF_mech_t ) )
{
SPF_mech_t *new_first;
size_t new_len;
/* FIXME dup code */
/* allocate lots so we don't have to remalloc often */
new_len = spfi->mech_buf_len + 8 * sizeof( SPF_mech_t ) + 64;
new_first = realloc( spfi->mech_first, new_len );
if ( new_first == NULL )
return SPF_E_NO_MEMORY;
spfi->mech_last = (SPF_mech_t *)((char *)new_first + ((char *)spfi->mech_last - (char *)spfi->mech_first));
spfi->mech_first = new_first;
spfi->mech_buf_len = new_len;
}
if ( spfi->header.num_mech > 0 )
spfi->mech_last = SPF_next_mech( spfi->mech_last );
spfi->mech_last->mech_type = mech_type;
spfi->mech_last->prefix_type = prefix;
spfi->mech_last->parm_len = 0;
if ( spfi->header.mech_len + sizeof( SPF_mech_t ) > SPF_MAX_MECH_LEN )
return SPF_E_BIG_MECH;
spfi->header.mech_len += sizeof( SPF_mech_t );
spfi->header.num_mech++;
return SPF_E_SUCCESS;
}
SPF_err_t SPF_c_mech_data_add( SPF_id_t spfid, char const **p_p, char const **p_token, int cidr_ok )
{
SPF_internal_t *spfi = SPF_id2spfi(spfid);
const char *p = *p_p;
size_t len;
SPF_mech_t *mech;
SPF_data_t *data;
SPF_err_t comp_stat;
size_t header_len;
size_t parm_len;
/*
* expand the buffer
*
* in the worse case, data can be "%-%-%-%-..." which will be
* converted into "%20%20%20%20....", a 3/2 increase, plus you have to
* add in the overhead of the data struct and a possible rounding to
* an even number of bytes.
*/
len = strcspn( p, " " );
if ( spfi->mech_buf_len - spfi->header.mech_len < (3 * len) / 2 + 8 )
{
SPF_mech_t *new_first;
size_t new_len;
/* FIXME dup code */
/* allocate lots so we don't have to remalloc often */
new_len = spfi->mech_buf_len + 8 * len + 64;
new_first = realloc( spfi->mech_first, new_len );
if ( new_first == NULL )
return SPF_E_NO_MEMORY;
spfi->mech_last = (SPF_mech_t *)((char *)new_first + ((char *)spfi->mech_last - (char *)spfi->mech_first));
spfi->mech_first = new_first;
spfi->mech_buf_len = new_len;
}
mech = spfi->mech_last;
data = SPF_mech_data( mech );
header_len = spfi->header.mech_len;
parm_len = mech->parm_len;
comp_stat = SPF_c_common_data_add( data, &header_len, &parm_len, SPF_MAX_MECH_LEN, SPF_E_BIG_MECH, p_p, p_token, cidr_ok, FALSE );
spfi->header.mech_len = header_len;
mech->parm_len = parm_len;
return comp_stat;
}
SPF_err_t SPF_c_mech_ip4_add( SPF_id_t spfid, char const **p_p, char const **p_token )
{
SPF_internal_t *spfi = SPF_id2spfi(spfid);
const char *p = *p_p;
const char *token = *p_token;
const char *real_end, *data_end;
const char *cur, *start;
SPF_err_t err;
int c;
size_t len;
SPF_mech_t *mech;
struct in_addr *data;
SPF_err_t comp_stat;
char ip4_buf[ INET_ADDRSTRLEN ];
len = strcspn( p, " " );
real_end = data_end = p + len;
start = p;
/*
* expand the buffer
*/
len = sizeof( struct in_addr );
if ( spfi->mech_buf_len - spfi->header.mech_len < len )
{
SPF_mech_t *new_first;
size_t new_len;
/* FIXME dup code */
/* allocate lots so we don't have to remalloc often */
new_len = spfi->mech_buf_len + 8 * len + 64;
new_first = realloc( spfi->mech_first, new_len );
if ( new_first == NULL )
return SPF_E_NO_MEMORY;
spfi->mech_last = (SPF_mech_t *)((char *)new_first + ((char *)spfi->mech_last - (char *)spfi->mech_first));
spfi->mech_first = new_first;
spfi->mech_buf_len = new_len;
}
mech = spfi->mech_last;
data = SPF_mech_ip4_data( mech );
/*
* create the CIDR length info
*/
start = cur = data_end - 1;
/* find the beginning of the CIDR length notation */
while( isdigit( SPF_c2ui( *start ) ) )
start--;
if ( cur != start && *start == '/' )
{
/* get IPv4 CIDR length */
cur = start + 1;
c = 0;
while ( isdigit( SPF_c2ui( *cur ) ) )
{
c *= 10;
c += *cur - '0';
cur++;
if ( c > 32 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
}
if ( c == 0 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
if ( c == 32 ) c = 0;
mech->parm_len = c;
data_end = start;
}
/*
* create the data block
*/
len = data_end - p;
if ( len > sizeof( ip4_buf ) - 1 )
{
comp_stat = SPF_E_INVALID_IP4;
goto error;
}
memcpy( ip4_buf, p, len );
ip4_buf[ len ] = '\0';
err = inet_pton( AF_INET, ip4_buf,
data );
if ( err <= 0 )
{
comp_stat = SPF_E_INVALID_IP4;
goto error;
}
len = sizeof( *data );
if ( spfi->header.mech_len + len > SPF_MAX_MECH_LEN )
{
comp_stat = SPF_E_BIG_MECH;
goto error;
}
spfi->header.mech_len += len;
comp_stat = SPF_E_SUCCESS;
error:
*p_p = real_end;
*p_token = token;
return comp_stat;
}
SPF_err_t SPF_c_mech_ip6_add( SPF_id_t spfid, char const **p_p, char const **p_token )
{
SPF_internal_t *spfi = SPF_id2spfi(spfid);
const char *p = *p_p;
const char *token = *p_token;
const char *real_end, *data_end;
const char *cur, *start;
SPF_err_t err;
int c;
size_t len;
SPF_mech_t *mech;
struct in6_addr *data;
SPF_err_t comp_stat;
char ip6_buf[ INET6_ADDRSTRLEN ];
len = strcspn( p, " " );
real_end = data_end = p + len;
start = p;
/*
* expand the buffer
*/
len = sizeof( struct in_addr );
if ( spfi->mech_buf_len - spfi->header.mech_len < len )
{
SPF_mech_t *new_first;
size_t new_len;
/* FIXME dup code */
/* allocate lots so we don't have to remalloc often */
new_len = spfi->mech_buf_len + 8 * len + 64;
new_first = realloc( spfi->mech_first, new_len );
if ( new_first == NULL )
return SPF_E_NO_MEMORY;
spfi->mech_last = (SPF_mech_t *)((char *)new_first + ((char *)spfi->mech_last - (char *)spfi->mech_first));
spfi->mech_first = new_first;
spfi->mech_buf_len = new_len;
}
mech = spfi->mech_last;
data = SPF_mech_ip6_data( mech );
/*
* create the CIDR length info
*/
start = cur = data_end - 1;
/* find the beginning of the CIDR length notation */
while( isdigit( SPF_c2ui( *start ) ) )
start--;
if ( cur != start && *start == '/' )
{
/* get IPv6 CIDR length */
cur = start + 1;
c = 0;
while ( isdigit( SPF_c2ui( *cur ) ) )
{
c *= 10;
c += *cur - '0';
cur++;
if ( c > 128 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
}
if ( c == 0 )
{
token = start;
p = cur;
comp_stat = SPF_E_INVALID_CIDR;
goto error;
}
if ( c == 128 ) c = 0;
mech->parm_len = c;
data_end = start;
}
/*
* create the data block
*/
len = data_end - p;
if ( len > sizeof( ip6_buf ) - 1 )
{
comp_stat = SPF_E_INVALID_IP6;
goto error;
}
memcpy( ip6_buf, p, len );
ip6_buf[ len ] = '\0';
err = inet_pton( AF_INET6, ip6_buf,
data );
if ( err <= 0 )
{
comp_stat = SPF_E_INVALID_IP6;
goto error;
}
len = sizeof( *data );
if ( spfi->header.mech_len + len > SPF_MAX_MECH_LEN )
{
comp_stat = SPF_E_BIG_MECH;
goto error;
}
spfi->header.mech_len += len;
comp_stat = SPF_E_SUCCESS;
error:
*p_p = real_end;
*p_token = token;
return comp_stat;
}
SPF_err_t SPF_c_mod_add( SPF_id_t spfid, const char *mod_name, size_t name_len )
{
SPF_internal_t *spfi = SPF_id2spfi(spfid);
size_t len;
if ( spfi->mod_buf_len - spfi->header.mod_len
< sizeof( SPF_mod_t ) + name_len )
{
SPF_mod_t *new_first;
size_t new_len;
/* FIXME dup code */
/* allocate lots so we don't have to remalloc often */
new_len = spfi->mod_buf_len + 8 * (sizeof( SPF_mod_t ) + name_len) + 64;
new_first = realloc( spfi->mod_first, new_len );
if ( new_first == NULL )
return SPF_E_NO_MEMORY;
spfi->mod_last = (SPF_mod_t *)((char *)new_first + ((char *)spfi->mod_last - (char *)spfi->mod_first));
spfi->mod_first = new_first;
spfi->mod_buf_len = new_len;
}
if ( spfi->header.num_mod > 0 )
spfi->mod_last = SPF_next_mod( spfi->mod_last );
if ( name_len > SPF_MAX_MOD_LEN )
return SPF_E_BIG_MOD;
spfi->mod_last->name_len = name_len;
spfi->mod_last->data_len = 0;
len = sizeof( SPF_mod_t ) + name_len;
if ( spfi->header.mod_len + len > SPF_MAX_MOD_LEN )
return SPF_E_BIG_MOD;
memcpy( SPF_mod_name( spfi->mod_last ), mod_name, name_len );
spfi->header.mod_len += len;
spfi->header.num_mod++;
return SPF_E_SUCCESS;
}
SPF_err_t SPF_c_mod_data_add( SPF_id_t spfid, char const **p_p, char const **p_token, int cidr_ok )
{
SPF_internal_t *spfi = SPF_id2spfi(spfid);
const char *p = *p_p;
size_t len;
SPF_mod_t *mod;
SPF_data_t *data;
SPF_err_t comp_stat;
size_t header_len;
size_t parm_len;
/*
* expand the buffer
*
* in the worse case, data can be "%-%-%-%-..." which will be
* converted into "%20%20%20%20....", a 3/2 increase, plus you have to
* add in the overhead of the data struct and a possible rounding to
* an even number of bytes.
*/
len = strcspn( p, " " );
if ( spfi->mod_buf_len - spfi->header.mod_len < (3 * len) / 2 + 8 )
{
SPF_mod_t *new_first;
size_t new_len;
/* FIXME dup code */
/* allocate lots so we don't have to remalloc often */
new_len = spfi->mod_buf_len + 8 * len + 64;
new_first = realloc( spfi->mod_first, new_len );
if ( new_first == NULL )
return SPF_E_NO_MEMORY;
spfi->mod_last = (SPF_mod_t *)((char *)new_first + ((char *)spfi->mod_last - (char *)spfi->mod_first));
spfi->mod_first = new_first;
spfi->mod_buf_len = new_len;
}
mod = spfi->mod_last;
data = SPF_mod_data( mod );
header_len = spfi->header.mod_len;
parm_len = mod->data_len;
comp_stat = SPF_c_common_data_add( data, &header_len, &parm_len, SPF_MAX_MOD_LEN, SPF_E_BIG_MOD, p_p, p_token, cidr_ok, TRUE );
spfi->header.mod_len = header_len;
mod->data_len = parm_len;
return comp_stat;
}
void SPF_lint( SPF_id_t spfid, SPF_c_results_t *c_results )
{
SPF_data_t *d, *data_end;
char *s;
char *s_end;
int found_non_ip;
int found_valid_tld;
SPF_internal_t *spfi = SPF_id2spfi(spfid);
SPF_mech_t *mech;
SPF_data_t *data;
size_t header_len;
int i;
header_len = spfi->header.mech_len;
/* FIXME these warnings suck. Should call SPF_id2str to give more
* context. */
/* FIXME there shouldn't be a limit of just one warning */
mech = spfi->mech_first;
for( i = 0; i < spfi->header.num_mech; i++, mech = SPF_next_mech( mech ) )
{
if ( ( mech->mech_type == MECH_ALL
|| mech->mech_type == MECH_REDIRECT )
&& i != spfi->header.num_mech - 1 )
{
if ( c_results->err_msg == NULL
|| c_results->err_msg_len < SPF_C_ERR_MSG_SIZE )
{
char *new_err_msg;
new_err_msg = realloc( c_results->err_msg, SPF_C_ERR_MSG_SIZE );
if ( new_err_msg == NULL )
return;
c_results->err_msg = new_err_msg;
c_results->err_msg_len = SPF_C_ERR_MSG_SIZE;
}
snprintf( c_results->err_msg, c_results->err_msg_len,
"Warning: %s",
SPF_strerror( SPF_E_MECH_AFTER_ALL ) );
}
/*
* if we are dealing with a mechanism, make sure that the data
* at least looks like a valid host name.
*
* note: this routine isn't called to handle ip4: and ip6: and all
* the other mechanisms require a host name.
*/
if ( mech->mech_type == MECH_IP4
|| mech->mech_type == MECH_IP6 )
continue;
data = SPF_mech_data( mech );
data_end = SPF_mech_end_data( mech );
if ( data == data_end )
continue;
if ( data->dc.parm_type == PARM_CIDR )
{
data = SPF_next_data( data );
if ( data == data_end )
continue;
}
found_valid_tld = FALSE;
found_non_ip = FALSE;
for( d = data; d < data_end; d = SPF_next_data( d ) )
{
switch( d->dv.parm_type )
{
case PARM_CIDR:
SPF_error( "Multiple CIDR parameters found" );
break;
case PARM_CLIENT_IP:
case PARM_CLIENT_IP_P:
case PARM_LP_FROM:
found_valid_tld = FALSE;
break;
case PARM_STRING:
found_valid_tld = FALSE;
s = SPF_data_str( d );
s_end = s + d->ds.len;
for( ; s < s_end; s++ )
{
if ( !isdigit( SPF_c2ui( *s ) ) && *s != '.' && *s != ':' )
found_non_ip = TRUE;
if ( *s == '.' )
found_valid_tld = TRUE;
else if ( !isalpha( SPF_c2ui( *s ) ) )
found_valid_tld = FALSE;
}
break;
default:
found_non_ip = TRUE;
found_valid_tld = TRUE;
break;
}
}
if ( !found_valid_tld || !found_non_ip )
{
if ( c_results->err_msg == NULL
|| c_results->err_msg_len < SPF_C_ERR_MSG_SIZE )
{
char *new_err_msg;
new_err_msg = realloc( c_results->err_msg, SPF_C_ERR_MSG_SIZE );
if ( new_err_msg == NULL )
return;
c_results->err_msg = new_err_msg;
c_results->err_msg_len = SPF_C_ERR_MSG_SIZE;
}
if ( !found_non_ip )
{
snprintf( c_results->err_msg, c_results->err_msg_len,
"Warning: %s",
SPF_strerror( SPF_E_BAD_HOST_IP ) );
}
else if ( !found_valid_tld )
{
snprintf( c_results->err_msg, c_results->err_msg_len,
"Warning: %s",
SPF_strerror( SPF_E_BAD_HOST_TLD ) );
}
}
}
/* FIXME check for modifiers that should probably be mechanisms */
}
/*
* The SPF compiler.
*
* It converts the SPF record in string format that is easy for people
* to deal with into a compact binary format that is easy for
* computers to deal with.
*/
SPF_err_t SPF_compile( SPF_config_t spfcid, const char *record, SPF_c_results_t *c_results )
{
SPF_id_t spfid;
SPF_iconfig_t *spfic = SPF_cid2spfic( spfcid );
SPF_err_t comp_stat;
const char *p, *token;
char *p2, *p2_end;
int prefix, mech;
int mech_len;
SPF_err_t err;
int c;
int num_dns_mech = 0;
/* FIXME there shouldn't be a limit of just one error message,
* we should continue parsing the rest of the record. */
/*
* make sure we were passed valid data to work with
*/
if ( spfcid == NULL )
SPF_error( "spfcid is NULL" );
if ( record == NULL )
SPF_error( "SPF record is NULL" );
if ( c_results == NULL )
SPF_error( "c_results is NULL" );
/*
* initialize the SPF data
*/
SPF_reset_c_results( c_results );
if ( c_results->spfid == NULL ) c_results->spfid = SPF_create_id();
spfid = c_results->spfid;
if ( spfid == NULL )
{
comp_stat = SPF_E_NO_MEMORY;
goto error;
}
SPF_reset_id( spfid );
/*
* See if this is record is even an SPF record
*/
p = record;
token = p;
if ( strncmp( p, SPF_VER_STR, sizeof( SPF_VER_STR )-1 ) != 0 )
{
comp_stat = SPF_E_NOT_SPF;
goto error;
}
p += sizeof( SPF_VER_STR ) - 1;
if ( *p != '\0' && *p != ' ' )
{
comp_stat = SPF_E_NOT_SPF;
goto error;
}
token = p;
/*
* parse the SPF record
*/
while( *p != '\0' )
{
/* skip to the next token */
while( *p == ' ' )
p++;
token = p;
if (*p == '\0' )
break;
/* see if we have a valid prefix */
prefix = PREFIX_UNKNOWN;
switch( *p )
{
case '+':
prefix = PREFIX_PASS;
p++;
break;
case '-':
prefix = PREFIX_FAIL;
p++;
break;
case '~':
prefix = PREFIX_SOFTFAIL;
p++;
break;
case '?':
prefix = PREFIX_NEUTRAL;
p++;
break;
}
if ( ispunct( SPF_c2ui( *p ) ) )
{
comp_stat = SPF_E_INVALID_PREFIX;
goto error;
}
token = p;
/* get the mechanism/modifier */
if ( isalpha( SPF_c2ui( *p ) ) )
while ( isalnum( SPF_c2ui( *p ) ) || *p == '_' || *p == '-' )
p++;
/* See if we have a modifier or a prefix */
mech_len = p - token;
c = *p;
if ( strncmp( token, "default=", sizeof( "default=" )-1 ) == 0 )
{
c = ':';
p += strcspn( p, " " );
mech_len = p - token;
}
else if ( strncmp( token, "redirect=", sizeof( "redirect=" )-1 ) == 0 )
c = ':';
else if ( strncmp( token, "ip4:", sizeof( "ip4:" )-1 ) != 0
&& strncmp( token, "ip6:", sizeof( "ip6:" )-1 ) != 0
&& strncmp( token, "exp-text=", sizeof( "exp-text=" )-1 ) != 0 )
{
for( p = token; p < token + mech_len; p++ )
{
if ( !isalpha( SPF_c2ui( *p ) ) )
{
comp_stat = SPF_E_INVALID_CHAR;
goto error;
}
}
}
switch ( c )
{
case ':':
case '/':
case ' ':
case '\0':
/*
* parse the mechanism
*/
/* mechanisms default to PREFIX_PASS */
if ( prefix == PREFIX_UNKNOWN )
prefix = PREFIX_PASS;
if ( mech_len == sizeof( "a" )-1
&& strncmp( token, "a", mech_len ) == 0 )
mech = MECH_A;
else if ( mech_len == sizeof( "mx" )-1
&& strncmp( token, "mx", mech_len ) == 0 )
mech = MECH_MX;
else if ( mech_len == sizeof( "ptr" )-1
&& strncmp( token, "ptr", mech_len ) == 0 )
mech = MECH_PTR;
else if ( mech_len == sizeof( "include" )-1
&& strncmp( token, "include", mech_len ) == 0 )
mech = MECH_INCLUDE;
else if ( mech_len == sizeof( "ip4" )-1
&& strncmp( token, "ip4", mech_len ) == 0 )
mech = MECH_IP4;
else if ( mech_len == sizeof( "ip6" )-1
&& strncmp( token, "ip6", mech_len ) == 0 )
mech = MECH_IP6;
else if ( mech_len == sizeof( "exists" )-1
&& strncmp( token, "exists", mech_len ) == 0 )
mech = MECH_EXISTS;
else if ( mech_len == sizeof( "all" )-1
&& strncmp( token, "all", mech_len ) == 0 )
mech = MECH_ALL;
else if ( mech_len == sizeof( "default=allow" )-1
&& strncmp( token, "default=allow", mech_len ) == 0 )
{
mech = MECH_ALL;
prefix = PREFIX_PASS;
c = *p;
}
else if ( mech_len == sizeof( "default=softfail" )-1
&& strncmp( token, "default=softfail", mech_len ) == 0 )
{
mech = MECH_ALL;
prefix = PREFIX_SOFTFAIL;
c = *p;
}
else if ( mech_len == sizeof( "default=deny" )-1
&& strncmp( token, "default=deny", mech_len ) == 0 )
{
mech = MECH_ALL;
prefix = PREFIX_FAIL;
c = *p;
}
else if ( strncmp( token, "default=", sizeof( "default=" )-1 ) == 0 )
{
comp_stat = SPF_E_INVALID_OPT;
goto error;
}
else if ( mech_len == sizeof( "redirect" )-1
&& strncmp( token, "redirect", mech_len ) == 0 )
/* FIXME the redirect mechanism needs to be moved to the very end */
mech = MECH_REDIRECT;
else
{
comp_stat = SPF_E_UNKNOWN_MECH;
goto error;
}
token = p;
err = SPF_c_mech_add( spfid, mech, prefix );
if ( err )
{
comp_stat = err;
goto error;
}
if ( c == ':' )
{
switch( mech )
{
case MECH_A:
case MECH_MX:
num_dns_mech++;
p++;
err = SPF_c_mech_data_add( spfid, &p, &token, CIDR_OPTIONAL );
if ( err )
{
comp_stat = err;
goto error;
}
break;
case MECH_PTR:
case MECH_INCLUDE:
case MECH_EXISTS:
case MECH_REDIRECT:
num_dns_mech++;
p++;
err = SPF_c_mech_data_add( spfid, &p, &token, CIDR_NONE );
if ( err )
{
comp_stat = err;
goto error;
}
break;
case MECH_ALL:
comp_stat = SPF_E_INVALID_OPT;
goto error;
break;
case MECH_IP4:
p++;
err = SPF_c_mech_ip4_add( spfid, &p, &token );
if ( err )
{
comp_stat = err;
goto error;
}
break;
case MECH_IP6:
p++;
err = SPF_c_mech_ip6_add( spfid, &p, &token );
if ( err )
{
comp_stat = err;
goto error;
}
break;
default:
comp_stat = SPF_E_INTERNAL_ERROR;
goto error;
break;
}
}
else if ( *p == '/' )
{
switch( mech )
{
case MECH_A:
case MECH_MX:
num_dns_mech++;
err = SPF_c_mech_data_add( spfid, &p, &token, CIDR_ONLY );
if ( err )
{
comp_stat = err;
goto error;
}
break;
case MECH_PTR:
case MECH_INCLUDE:
case MECH_EXISTS:
case MECH_REDIRECT:
case MECH_ALL:
case MECH_IP4:
case MECH_IP6:
comp_stat = SPF_E_INVALID_CIDR;
goto error;
break;
default:
comp_stat = SPF_E_INTERNAL_ERROR;
goto error;
break;
}
}
else if ( *p == ' ' || *p == '\0' )
{
switch( mech )
{
case MECH_A:
case MECH_MX:
case MECH_PTR:
num_dns_mech++;
break;
case MECH_ALL:
break;
case MECH_INCLUDE:
case MECH_IP4:
case MECH_IP6:
case MECH_EXISTS:
case MECH_REDIRECT:
comp_stat = SPF_E_MISSING_OPT;
goto error;
break;
default:
comp_stat = SPF_E_INTERNAL_ERROR;
goto error;
break;
}
} else {
comp_stat = SPF_E_SYNTAX;
goto error;
}
if ( num_dns_mech > spfic->max_dns_mech
|| num_dns_mech > SPF_MAX_DNS_MECH )
{
comp_stat = SPF_E_BIG_DNS;
goto error;
}
break;
case '=':
/*
* parse the modifier
*/
/* modifiers can't have prefixes */
if ( prefix != PREFIX_UNKNOWN )
{
comp_stat = SPF_E_MOD_W_PREF;
goto error;
}
/* FIXME reject duplicate mods? Or are dup mods a feature? */
err = SPF_c_mod_add( spfid, token, p - token );
if ( err )
{
comp_stat = err;
goto error;
}
p++;
token = p;
err = SPF_c_mod_data_add( spfid, &p, &token, CIDR_OPTIONAL );
if ( err )
{
comp_stat = err;
goto error;
}
break;
default:
comp_stat = SPF_E_INVALID_CHAR;
goto error;
break;
}
}
/*
* check for common mistakes
*/
SPF_lint( spfid, c_results );
/*
* do final cleanup on the record
*/
/* FIXME realloc (shrink) spfi buffers? */
return SPF_E_SUCCESS;
/*
* common error handling
*/
error:
c_results->token = token;
c_results->token_len = p - token;
c_results->error_loc = p;
p += strcspn( p, " " );
while( token >= record && *token != ' ' )
token--;
token++;
c_results->expression = token;
c_results->expression_len = p - token;
/* reset everthing to scratch */
SPF_reset_id( spfid );
/* add in the "unknown" mechanism here */
err = SPF_c_mech_add( spfid, MECH_ALL, PREFIX_UNKNOWN );
if ( err ) /* this can't(?) happen */
comp_stat = err;
c_results->err = comp_stat;
/*
* format a nice error message
*/
if ( c_results->err_msg == NULL
|| c_results->err_msg_len < SPF_C_ERR_MSG_SIZE )
{
char *new_err_msg;
new_err_msg = realloc( c_results->err_msg, SPF_C_ERR_MSG_SIZE );
if ( new_err_msg != NULL )
{
c_results->err_msg = new_err_msg;
c_results->err_msg_len = SPF_C_ERR_MSG_SIZE;
}
}
p2 = c_results->err_msg;
if ( p2 != NULL )
{
p2_end = p2 + c_results->err_msg_len - 1;
p2 += snprintf( p2, p2_end - p2, "%s",
SPF_strerror( comp_stat ) );
if ( p2 > p2_end ) p2 = p2_end;
if ( c_results->token == c_results->expression
&& c_results->token_len == c_results->expression_len )
p2 += snprintf( p2, p2_end - p2,
" in \"%.*s\".",
c_results->expression_len,
c_results->expression );
else
p2 += snprintf( p2, p2_end - p2,
" near \"%.*s\" in \"%.*s\"",
c_results->token_len, c_results->token,
c_results->expression_len,
c_results->expression );
/* FIXME if err_msg is too long, don't add in token */
SPF_sanitize( spfcid, c_results->err_msg );
}
return comp_stat;
}
void SPF_init_c_results( SPF_c_results_t *c_results )
{
memset( c_results, 0, sizeof( *c_results ) );
}
void SPF_reset_c_results( SPF_c_results_t *c_results )
{
int i;
c_results->err = SPF_E_SUCCESS;
if ( c_results->err_msg ) c_results->err_msg[0] = '\0';
if ( c_results->err_msgs )
{
for( i = 0; i < c_results->num_errs; i++ )
if ( c_results->err_msgs[i] ) c_results->err_msgs[i][0] = '\0';
}
c_results->expression = NULL;
c_results->expression_len = 0;
c_results->token = NULL;
c_results->token_len = 0;
c_results->error_loc = NULL;
}
SPF_c_results_t SPF_dup_c_results( SPF_c_results_t c_results )
{
SPF_c_results_t new_c_results;
int i;
SPF_init_c_results( &new_c_results );
if ( c_results.spfid )
new_c_results.spfid = SPF_dup_id( c_results.spfid );
new_c_results.err = c_results.err;
if ( c_results.err_msg )
{
new_c_results.err_msg = strdup( c_results.err_msg );
new_c_results.err_msg_len = strlen( c_results.err_msg );
}
if ( c_results.err_msgs )
{
new_c_results.num_errs = c_results.num_errs;
new_c_results.err_msgs = malloc( c_results.num_errs * sizeof( c_results.err_msgs ) );
if ( new_c_results.err_msgs )
{
for( i = 0; i < c_results.num_errs; i++ )
if ( c_results.err_msgs[i] )
{
new_c_results.err_msgs[i] = strdup( c_results.err_msgs[i] );
new_c_results.err_msgs_len[i] = strlen( c_results.err_msgs[i] );
}
}
}
return new_c_results;
}
void SPF_free_c_results( SPF_c_results_t *c_results )
{
int i;
if ( c_results->spfid ) SPF_destroy_id( c_results->spfid );
if ( c_results->err_msg ) free( c_results->err_msg );
if ( c_results->err_msgs )
{
for( i = 0; i < c_results->num_errs; i++ )
if ( c_results->err_msgs[i] ) free( c_results->err_msgs[i] );
free( c_results->err_msgs );
}
SPF_init_c_results( c_results );
}
syntax highlighted by Code2HTML, v. 0.9.1