int env_unlink( struct envelope *env ) { char efile_fname[ MAXPATHLEN + 1 ]; sprintf( efile_fname, "%s/E%s", env->e_dir, env->e_id ); if ( unlink( efile_fname ) != 0 ) { syslog( LOG_ERR, "Syserror: env_unlink unlink %s: %m", efile_fname ); return( -1 ); } env->e_flags = ( env->e_flags & ( ~ENV_FLAG_EFILE )); if ( env->e_dir == simta_dir_fast ) { simta_fast_files--; simta_debuglog( 3, "env_unlink env <%s> fast_files decrement %d", env->e_id, simta_fast_files ); } env_dfile_unlink( env ); simta_debuglog( 2, "env_unlink env <%s> %s: unlinked", env->e_dir, env->e_id ); return( 0 ); }
int env_jail_set( struct envelope *e, int val ) { char *s; if ( simta_debug > 2 ) { switch ( val ) { default: s = "Unknown"; break; case ENV_JAIL_NO_CHANGE: s = "JAIL_NO_CHANGE"; break; case ENV_JAIL_PAROLEE: s = "JAIL_PAROLEE"; break; case ENV_JAIL_PRISONER: s = "JAIL_PRISONER"; break; } simta_debuglog( 3, "Jail %s: value %d (%s)", e->e_id, val, s ); } e->e_jail = val; return( 0 ); }
int env_tfile_unlink( struct envelope *e ) { char tf[ MAXPATHLEN + 1 ]; simta_debuglog( 3, "env_tfile_unlink %s", e->e_id ); sprintf( tf, "%s/t%s", e->e_dir, e->e_id ); if ( unlink( tf ) != 0 ) { syslog( LOG_ERR, "Syserror: env_tfile_unlink unlink %s: %m", tf ); return( -1 ); } e->e_flags = ( e->e_flags & ( ~ENV_FLAG_TFILE )); return( 0 ); }
struct spf * spf_lookup( const char *helo, const char *email, const struct sockaddr *addr ) { char *p; struct spf *s; s = calloc( 1, sizeof( struct spf )); s->spf_queries = 0; s->spf_sockaddr = addr; s->spf_helo = yaslauto( helo ); if ( strlen( email ) == 0 ) { /* RFC 7208 2.4 The "MAIL FROM" Identity * When the reverse-path is null, this document defines the "MAIL FROM" * identity to be the mailbox composed of the local-part "postmaster" * and the "HELO" identity */ s->spf_domain = yaslauto( helo ); s->spf_localpart = yaslauto( "postmaster" ); } else if (( p = strrchr( email, '@' )) != NULL ) { s->spf_domain = yaslauto( p + 1 ); s->spf_localpart = yaslnew( email, (size_t) (p - email )); } else { /* RFC 7208 4.3 Initial Processing * If the <sender> has no local-part, substitute the string * "postmaster" for the local-part. */ s->spf_domain = yaslauto( email ); s->spf_localpart = yaslauto( "postmaster" ); } simta_debuglog( 2, "SPF %s: localpart %s", s->spf_domain, s->spf_localpart ); s->spf_result = spf_check_host( s, s->spf_domain ); return( s ); }
int env_efile( struct envelope *e ) { char tf[ MAXPATHLEN + 1 ]; char ef[ MAXPATHLEN + 1 ]; char df[ MAXPATHLEN + 1 ]; struct timeval tv_now; struct dll_entry *e_dll; sprintf( tf, "%s/t%s", e->e_dir, e->e_id ); sprintf( ef, "%s/E%s", e->e_dir, e->e_id ); sprintf( df, "%s/D%s", e->e_dir, e->e_id ); if ( rename( tf, ef ) < 0 ) { syslog( LOG_ERR, "Syserror: env_efile rename %s %s: %m", tf, ef ); unlink( tf ); return( -1 ); } if ( e->e_dir == simta_dir_fast ) { simta_fast_files++; simta_debuglog( 2, "Envelope env <%s> fast_files increment %d", e->e_id, simta_fast_files ); } simta_debuglog( 3, "env_efile %s %s %s", e->e_dir, e->e_id, e->e_hostname ? e->e_hostname : "" ); e->e_flags = ( e->e_flags & ( ~ENV_FLAG_TFILE )); e->e_flags |= ENV_FLAG_EFILE; if ( simta_gettimeofday( &tv_now ) != 0 ) { return( -1 ); } e->e_etime.tv_sec = tv_now.tv_sec; if ( simta_sync ) { env_fsync( ef ); env_fsync( df ); /* fsync() does not ensure that the directory entries for the files * have been synced, so we must explicitly sync the directory. */ env_fsync( e->e_dir ); } if ( simta_mid_list_enable != 0 ) { if (( e_dll = dll_lookup_or_create( &simta_env_list, e->e_id, 0 )) == NULL ) { return( 1 ); } if ( e_dll->dll_data == NULL ) { e_dll->dll_data = e; e->e_env_list_entry = e_dll; } } if ( simta_sender_list_enable != 0 ) { if ( sender_list_add( e ) != 0 ) { return( 1 ); } } return( 0 ); }
int env_tfile( struct envelope *e ) { int fd; struct recipient *r; FILE *tff; char tf[ MAXPATHLEN + 1 ]; int version_to_write; assert( e->e_dir != NULL ); assert( e->e_id != NULL ); sprintf( tf, "%s/t%s", e->e_dir, e->e_id ); /* make tfile */ if (( fd = open( tf, O_WRONLY | O_CREAT | O_EXCL, 0600 )) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile open %s: %m", tf ); return( -1 ); } if (( tff = fdopen( fd, "w" )) == NULL ) { close( fd ); syslog( LOG_ERR, "Syserror: env_tfile fdopen: %m" ); unlink( tf ); return( -1 ); } /* VSIMTA_EFILE_VERSION */ version_to_write = SIMTA_EFILE_VERSION; #if 0 if (( !e->e_attributes ) && ( !e->e_jail )) { version_to_write = 3; } #endif if ( fprintf( tff, "V%d\n", version_to_write ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } /* Emessage-id */ if ( fprintf( tff, "E%s\n", e->e_id ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } /* Idinode */ if ( e->e_dinode <= 0 ) { panic( "env_tfile: bad dinode" ); } if ( fprintf( tff, "I%lu\n", e->e_dinode ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } simta_debuglog( 3, "env_tfile %s: Dinode %d", e->e_id, (int)e->e_dinode ); /* Xpansion Level */ if ( fprintf( tff, "X%d\n", e->e_n_exp_level ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } /* Jail Level */ if (( version_to_write < 5 )) { } else if ( fprintf( tff, "J%d\n", e->e_jail ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } /* Hdestination-host */ if (( e->e_hostname != NULL ) && ( e->e_dir != simta_dir_dead )) { if ( fprintf( tff, "H%s\n", e->e_hostname ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } } else { if ( fprintf( tff, "H\n" ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } } if (( version_to_write < 4 )) { } else if ( fprintf( tff, "D%u\n", e->e_attributes ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } /* [email protected] */ if ( e->e_mail != NULL ) { if ( fprintf( tff, "F%s\n", e->e_mail ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } } else { if ( fprintf( tff, "F\n" ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } } /* [email protected] */ if ( e->e_rcpt != NULL ) { for ( r = e->e_rcpt; r != NULL; r = r->r_next ) { if ( fprintf( tff, "R%s\n", r->r_rcpt ) < 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fprintf: %m" ); goto cleanup; } } } else { syslog( LOG_ERR, "Envelope env <%s>: no recipients while writing tfile", e->e_id ); goto cleanup; } if ( fclose( tff ) != 0 ) { syslog( LOG_ERR, "Syserror: env_tfile fclose: %m" ); unlink( tf ); return( -1 ); } e->e_flags |= ENV_FLAG_TFILE; return( 0 ); cleanup: fclose( tff ); unlink( tf ); return( -1 ); }
int env_move( struct envelope *env, char *target_dir ) { char dfile_new[ MAXPATHLEN ]; char efile_new[ MAXPATHLEN ]; char dfile_old[ MAXPATHLEN ]; char efile_old[ MAXPATHLEN ]; /* only move messages to slow or fast */ assert(( target_dir == simta_dir_slow ) || ( target_dir == simta_dir_fast )); /* move message to target_dir if it isn't there already */ if ( env->e_dir != target_dir ) { sprintf( efile_old, "%s/E%s", env->e_dir, env->e_id ); sprintf( dfile_old, "%s/D%s", env->e_dir, env->e_id ); sprintf( dfile_new, "%s/D%s", target_dir, env->e_id ); sprintf( efile_new, "%s/E%s", target_dir, env->e_id ); if ( link( dfile_old, dfile_new ) != 0 ) { syslog( LOG_ERR, "Syserror: env_move link %s %s: %m", dfile_old, dfile_new ); return( -1 ); } if ( link( efile_old, efile_new ) != 0 ) { syslog( LOG_ERR, "Syserror: env_move link %s %s: %m", efile_old, efile_new ); if ( unlink( dfile_new ) != 0 ) { syslog( LOG_ERR, "Syserror: env_move unlink %s: %m", dfile_new ); } return( -1 ); } if ( target_dir == simta_dir_fast ) { simta_fast_files++; simta_debuglog( 3, "env_move env <%s> fast_files increment %d", env->e_id, simta_fast_files ); } if ( env_unlink( env ) != 0 ) { if ( unlink( efile_new ) != 0 ) { syslog( LOG_ERR, "env_move unlink %s: %m", efile_new ); } else { if ( target_dir == simta_dir_fast ) { simta_fast_files--; simta_debuglog( 3, "env_move %s fast_files decrement %d", env->e_id, simta_fast_files ); } if ( unlink( dfile_new ) != 0 ) { syslog( LOG_ERR, "env_move unlink %s: %m", dfile_new ); } } return( -1 ); } env->e_dir = target_dir; env->e_flags |= ENV_FLAG_EFILE; simta_debuglog( 1, "Envelope env <%s>: moved to %s", env->e_id, env->e_dir ); } return( 0 ); }
int expand( struct envelope *unexpanded_env ) { struct expand exp; struct envelope *base_error_env; struct envelope *env_dead = NULL; struct envelope *env; struct envelope **env_p; struct recipient *rcpt; struct expand_output *host_stab = NULL; struct expand_output *eo; struct expand_output *eo_free; struct exp_addr *e_addr; struct exp_addr *next_e_addr; struct simta_red *hq_red; char *domain; SNET *snet = NULL; int n_rcpts; int return_value = 1; int env_out = 0; int fast_file_start; int sendermatch; char e_original[ MAXPATHLEN ]; char d_original[ MAXPATHLEN ]; char d_out[ MAXPATHLEN ]; /* RFC 5321 4.5.3.1.3. Path * The maximum total length of a reverse-path or forward-path is 256 * octets (including the punctuation and element separators). */ char header[ 270 ]; #ifdef HAVE_LDAP char *p; int loop_color = 1; struct exp_link *memonly; struct exp_link *parent; #endif /* HAVE_LDAP */ if ( unexpanded_env->e_hostname != NULL ) { syslog( LOG_INFO, "Expand env <%s>: already expanded for host %s", unexpanded_env->e_id, unexpanded_env->e_hostname ); return_value = 0; goto done; } memset( &exp, 0, sizeof( struct expand )); exp.exp_env = unexpanded_env; fast_file_start = simta_fast_files; /* call address_expand on each address in the expansion list. * * if an address is expandable, the address(es) that it expands to will * be added to the expansion list. These non-terminal addresses must * have their st_data set to NULL to specify that they are not to be * included in the terminal expansion list. * * Any address in the expansion list whose st_data is not NULL is * considered a terminal address and will be written out as one * of the addresses in expanded envelope(s). */ if (( base_error_env = address_bounce_create( &exp )) == NULL ) { syslog( LOG_ERR, "Expand env <%s>: address_bounce_create: %m", unexpanded_env->e_id ); goto done; } if ( env_recipient( base_error_env, unexpanded_env->e_mail ) != 0 ) { syslog( LOG_ERR, "Expand env <%s>: env_recipient: %m", unexpanded_env->e_id ); goto done; } /* add all of the original recipients to the expansion list */ for ( rcpt = unexpanded_env->e_rcpt; rcpt != NULL; rcpt = rcpt->r_next ) { if ( add_address( &exp, rcpt->r_rcpt, base_error_env, ADDRESS_TYPE_EMAIL, exp.exp_env->e_mail ) != 0 ) { /* add_address syslogs errors */ goto cleanup1; } } /* process the expansion list */ for ( exp.exp_addr_cursor = exp.exp_addr_head; exp.exp_addr_cursor != NULL; exp.exp_addr_cursor = exp.exp_addr_cursor->e_addr_next ) { switch ( address_expand( &exp )) { case ADDRESS_EXCLUDE: exp.exp_addr_cursor->e_addr_terminal = 0; /* the address is not a terminal local address */ break; case ADDRESS_FINAL: exp.exp_addr_cursor->e_addr_terminal = 1; break; case ADDRESS_SYSERROR: goto cleanup1; default: panic( "Expand: address_expand out of range" ); } } #ifdef HAVE_LDAP /* Members-only processing */ for ( memonly = exp.exp_memonly; memonly != NULL; memonly = memonly->el_next ) { if ((( p = parent_permitted( memonly->el_exp_addr )) != NULL ) || ( sender_is_child( memonly->el_exp_addr->e_addr_children, loop_color++ ))) { if ( p != NULL ) { syslog( LOG_INFO, "Expand env <%s>: members-only group %s OK: " "parent %s permitted", unexpanded_env->e_id, memonly->el_exp_addr->e_addr, p ); } else { syslog( LOG_INFO, "Expand env <%s>: members-only group %s OK: " "sender is child", unexpanded_env->e_id, memonly->el_exp_addr->e_addr ); } memonly->el_exp_addr->e_addr_ldap_flags = ( memonly->el_exp_addr->e_addr_ldap_flags & ( ~STATUS_LDAP_MEMONLY )); if ( memonly->el_exp_addr->e_addr_env_moderated != NULL ) { env_free( memonly->el_exp_addr->e_addr_env_moderated ); memonly->el_exp_addr->e_addr_env_moderated = NULL; } } else { syslog( LOG_NOTICE, "Expand env <%s>: members-only group %s suppressed", unexpanded_env->e_id, memonly->el_exp_addr->e_addr ); memonly->el_exp_addr->e_addr_ldap_flags |= STATUS_LDAP_SUPPRESSOR; suppress_addrs( memonly->el_exp_addr->e_addr_children, loop_color++ ); } } #endif /* HAVE_LDAP */ sprintf( d_original, "%s/D%s", unexpanded_env->e_dir, unexpanded_env->e_id ); /* Create one expanded envelope for every host we expanded address for */ for ( e_addr = exp.exp_addr_head; e_addr != NULL; e_addr = e_addr->e_addr_next ) { #ifdef HAVE_LDAP if ((( e_addr->e_addr_ldap_flags & STATUS_LDAP_SUPPRESSED ) != 0 ) && ( !unblocked_path_to_root( e_addr, loop_color++ ))) { if ( simta_expand_debug != 0 ) { printf( "Suppressed: %s\n", e_addr->e_addr ); } continue; } if ( e_addr->e_addr_env_gmailfwd != NULL ) { e_addr->e_addr_env_gmailfwd->e_attributes = unexpanded_env->e_attributes | ENV_ATTR_ARCHIVE_ONLY; if ( simta_expand_debug != 0 ) { printf( "Group mail forwarding: %s\n", e_addr->e_addr ); env_stdout( e_addr->e_addr_env_gmailfwd ); continue; } sprintf( d_out, "%s/D%s", e_addr->e_addr_env_gmailfwd->e_dir, e_addr->e_addr_env_gmailfwd->e_id ); if ( env_dfile_copy( e_addr->e_addr_env_gmailfwd, d_original, NULL ) == 0 ) { syslog( LOG_ERR, "Expand env <%s>: %s: env_dfile_copy failed", unexpanded_env->e_id, e_addr->e_addr_env_gmailfwd->e_id ); goto cleanup3; } simta_debuglog( 2, "Expand env <%s>: group mail env %s dinode %d", unexpanded_env->e_id, e_addr->e_addr_env_gmailfwd->e_id, (int)e_addr->e_addr_env_gmailfwd->e_dinode ); sendermatch = !strcasecmp( unexpanded_env->e_mail, e_addr->e_addr_env_gmailfwd->e_mail ); n_rcpts = 0; for ( rcpt = e_addr->e_addr_env_gmailfwd->e_rcpt; rcpt != NULL; rcpt = rcpt->r_next ) { n_rcpts++; if ( sendermatch ) { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s>", unexpanded_env->e_id, e_addr->e_addr_env_gmailfwd->e_id, rcpt->r_rcpt, e_addr->e_addr_env_gmailfwd->e_mail ); } else { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s> (%s)", unexpanded_env->e_id, e_addr->e_addr_env_gmailfwd->e_id, rcpt->r_rcpt, e_addr->e_addr_env_gmailfwd->e_mail, unexpanded_env->e_mail ); } } syslog( LOG_INFO, "Expand env <%s>: %s: Expanded %d group mail forwarders", unexpanded_env->e_id, e_addr->e_addr_env_gmailfwd->e_id, n_rcpts ); if ( env_outfile( e_addr->e_addr_env_gmailfwd ) != 0 ) { /* env_outfile syslogs errors */ if ( unlink( d_out ) != 0 ) { syslog( LOG_ERR, "Syserror: expand unlink %s: %m", d_out ); } goto cleanup3; } env_out++; queue_envelope( e_addr->e_addr_env_gmailfwd ); continue; } if ( e_addr->e_addr_env_moderated != NULL ) { e_addr->e_addr_env_moderated->e_attributes = unexpanded_env->e_attributes; if ( simta_expand_debug != 0 ) { printf( "Moderated: %s\n", e_addr->e_addr ); env_stdout( e_addr->e_addr_env_moderated ); continue; } sprintf( d_out, "%s/D%s", e_addr->e_addr_env_moderated->e_dir, e_addr->e_addr_env_moderated->e_id ); if ( env_dfile_copy( e_addr->e_addr_env_moderated, d_original, NULL ) == 0 ) { syslog( LOG_ERR, "Expand env <%s>: %s: env_dfile_copy failed", unexpanded_env->e_id, e_addr->e_addr_env_moderated->e_id ); goto cleanup3; } simta_debuglog( 2, "Expand env <%s>: %s: moderation env dinode %d", unexpanded_env->e_id, e_addr->e_addr_env_moderated->e_id, (int)e_addr->e_addr_env_moderated->e_dinode ); sendermatch = !strcasecmp( unexpanded_env->e_mail, e_addr->e_addr_env_moderated->e_mail ); n_rcpts = 0; for ( rcpt = e_addr->e_addr_env_moderated->e_rcpt; rcpt != NULL; rcpt = rcpt->r_next ) { n_rcpts++; if ( sendermatch ) { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s>", unexpanded_env->e_id, e_addr->e_addr_env_moderated->e_id, rcpt->r_rcpt, e_addr->e_addr_env_moderated->e_mail ); } else { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s> (%s)", unexpanded_env->e_id, e_addr->e_addr_env_moderated->e_id, rcpt->r_rcpt, e_addr->e_addr_env_moderated->e_mail, unexpanded_env->e_mail ); } } syslog( LOG_INFO, "Expand env <%s>: %s: Expanded %d moderators", unexpanded_env->e_id, e_addr->e_addr_env_moderated->e_id, n_rcpts ); if ( env_outfile( e_addr->e_addr_env_moderated ) != 0 ) { /* env_outfile syslogs errors */ if ( unlink( d_out ) != 0 ) { syslog( LOG_ERR, "expand unlink %s: %m", d_out ); } goto cleanup3; } env_out++; queue_envelope( e_addr->e_addr_env_moderated ); continue; } else if ( e_addr->e_addr_ldap_flags & STATUS_LDAP_SUPPRESSOR ) { for ( parent = e_addr->e_addr_parents; parent != NULL; parent = parent->el_next ) { if ( parent->el_exp_addr == NULL ) { if ( bounce_text( base_error_env, TEXT_ERROR, "Members only group conditions not met: ", e_addr->e_addr, NULL ) != 0 ) { goto cleanup3; } if ( bounce_text( base_error_env, TEXT_ERROR, "If you have any questions, please contact the group owner: ", e_addr->e_addr_owner, NULL ) != 0 ) { goto cleanup3; } } else if (( e_addr->e_addr_ldap_flags & STATUS_LDAP_PRIVATE ) == 0 ) { if ( bounce_text( parent->el_exp_addr->e_addr_errors, TEXT_ERROR, "Members only group conditions not met: ", e_addr->e_addr, NULL ) != 0 ) { goto cleanup3; } if ( bounce_text( parent->el_exp_addr->e_addr_errors, TEXT_ERROR, "If you have any questions, please contact the group owner: ", e_addr->e_addr_owner, NULL ) != 0 ) { goto cleanup3; } } } continue; } #endif /* HAVE_LDAP */ if ( e_addr->e_addr_terminal == 0 ) { if ( simta_expand_debug != 0 ) { printf( "Non-terminal: %s\n", e_addr->e_addr ); } /* not a terminal expansion, do not add */ continue; } if ( simta_expand_debug != 0 ) { printf( "Terminal: %s\n", e_addr->e_addr ); } switch ( e_addr->e_addr_type ) { case ADDRESS_TYPE_EMAIL: if (( domain = strchr( e_addr->e_addr, '@' )) == NULL ) { syslog( LOG_ERR, "Expand env <%s>: strchr blivet", unexpanded_env->e_id ); goto cleanup3; } domain++; env = eo_lookup( host_stab, domain, e_addr->e_addr_from ); break; case ADDRESS_TYPE_DEAD: domain = NULL; env = env_dead; break; default: panic( "expand: address type out of range" ); } if ( env == NULL ) { /* Create envelope and add it to list */ if (( env = env_create( domain ? simta_dir_fast : simta_dir_dead, NULL, e_addr->e_addr_from, unexpanded_env )) == NULL ) { syslog( LOG_ERR, "Expand env <%s>: env_create: %m", unexpanded_env->e_id ); goto cleanup3; } simta_debuglog( 2, "Expand env <%s>: %s: expansion env dinode %d", unexpanded_env->e_id, env->e_id, (int)env->e_dinode ); /* fill in env */ env->e_attributes = unexpanded_env->e_attributes; if ( domain != NULL ) { if ( env_hostname( env, domain ) != 0 ) { env_free( env ); goto cleanup3; } } else { env_dead = env; } /* Add env to host_stab */ if ( eo_insert( &host_stab, env ) != 0 ) { syslog( LOG_ERR, "Expand env <%s>: eo_insert %s failed: %m", unexpanded_env->e_id, env->e_id ); env_free( env ); goto cleanup3; } } if ( env_recipient( env, e_addr->e_addr ) != 0 ) { goto cleanup3; } syslog( LOG_NOTICE, "Expand env <%s>: %s: recipient <%s> added to env for host %s", unexpanded_env->e_id, env->e_id, e_addr->e_addr, env->e_hostname ? env->e_hostname : "NULL" ); } /* Write out all expanded envelopes and place them in to the host_q */ for ( eo = host_stab; eo != NULL; eo = eo->eo_next ) { env = eo->eo_env; if ( simta_expand_debug == 0 ) { sprintf( d_out, "%s/D%s", env->e_dir, env->e_id ); /* RFC 5321 4.4 Trace Information * When the delivery SMTP server makes the "final delivery" of a * message, it inserts a return-path line at the beginning of the * mail data. This use of return-path is required; mail systems * MUST support it. The return-path line preserves the * information in the <reverse-path> from the MAIL command. * Here, final delivery means the message has left the SMTP * environment. */ if ((( hq_red = red_host_lookup( eo->eo_hostname )) != NULL ) && ( hq_red->red_deliver_type == RED_DELIVER_BINARY )) { if ( snprintf( header, 270, "Return-Path: <%s>", env->e_mail ) >= 270 ) { syslog( LOG_ERR, "Expand env <%s>: %s: return path is too large", unexpanded_env->e_id, env->e_id ); } if ( env_dfile_copy( env, d_original, header ) == 0 ) { syslog( LOG_ERR, "Expand env <%s>: %s: env_dfile_copy failed", unexpanded_env->e_id, env->e_id ); goto cleanup4; } } else { /* Dfile: link Dold_id env->e_dir/Dnew_id */ if ( link( d_original, d_out ) != 0 ) { syslog( LOG_ERR, "Syserror: expand link %s %s: %m", d_original, d_out ); goto cleanup4; } } sendermatch = !strcasecmp( unexpanded_env->e_mail, env->e_mail ); n_rcpts = 0; for ( rcpt = env->e_rcpt; rcpt != NULL; rcpt = rcpt->r_next ) { n_rcpts++; if ( sendermatch ) { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s>", unexpanded_env->e_id, env->e_id, rcpt->r_rcpt, env->e_mail ); } else { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s> (%s)", unexpanded_env->e_id, env->e_id, rcpt->r_rcpt, env->e_mail, unexpanded_env->e_mail ); } } syslog( LOG_INFO, "Expand env <%s>: %s: Expanded %d recipients", unexpanded_env->e_id, env->e_id, n_rcpts ); /* Efile: write env->e_dir/Enew_id for all recipients at host */ syslog( LOG_NOTICE, "Expand env <%s>: %s: writing Efile for %s", unexpanded_env->e_id, env->e_id, env->e_hostname ? env->e_hostname : "NULL" ); if ( env_outfile( env ) != 0 ) { /* env_outfile syslogs errors */ if ( unlink( d_out ) != 0 ) { syslog( LOG_ERR, "Syserror: expand unlink %s: %m", d_out ); } goto cleanup4; } env_out++; queue_envelope( env ); } else { printf( "\n" ); env_stdout( env ); } } if ( env_out == 0 ) { syslog( LOG_NOTICE, "Expand env <%s>: no terminal recipients, " "deleting message", unexpanded_env->e_id ); } /* write errors out to disk */ env_p = &(exp.exp_errors); while (( env = *env_p ) != NULL ) { if ( simta_expand_debug == 0 ) { if ( env->e_error != 0 ) { env_p = &(env->e_next); if ( snet == NULL ) { if (( snet = snet_open( d_original, O_RDONLY, 0, 1024 * 1024 )) == NULL ) { syslog( LOG_ERR, "Liberror: expand snet_open %s: %m", d_original ); goto cleanup5; } } else { if ( lseek( snet_fd( snet ), (off_t)0, SEEK_SET ) != 0 ) { syslog( LOG_ERR, "Syserror: q_deliver lseek: %m" ); panic( "q_deliver lseek fail" ); } } /* write out error text, get Dfile inode */ if ( bounce_dfile_out( env, snet ) == 0 ) { if ( snet != NULL ) { if ( snet_close( snet ) != 0 ) { syslog( LOG_ERR, "Liberror: expand snet_close %s: %m", d_original ); } } goto cleanup5; } simta_debuglog( 2, "Expand env <%s>: %s: errors env dinode %d", unexpanded_env->e_id, env->e_id, (int)env->e_dinode ); line_file_free( env->e_err_text ); env->e_err_text = NULL; env->e_error = 0; if ( env_outfile( env ) != 0 ) { /* env_outfile syslogs errors */ sprintf( d_out, "%s/D%s", env->e_dir, env->e_id ); if ( unlink( d_out ) != 0 ) { syslog( LOG_ERR, "Syserror: expand unlink %s: %m", d_out ); } goto cleanup5; } sendermatch = !strcasecmp( unexpanded_env->e_mail, env->e_mail ); n_rcpts = 0; for ( rcpt = env->e_rcpt; rcpt != NULL; rcpt = rcpt->r_next ) { n_rcpts++; if ( sendermatch ) { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s>", unexpanded_env->e_id, env->e_id, rcpt->r_rcpt, env->e_mail ); } else { syslog( LOG_INFO, "Expand env <%s>: %s: To <%s> From <%s> (%s)", unexpanded_env->e_id, env->e_id, rcpt->r_rcpt, env->e_mail, unexpanded_env->e_mail ); } } syslog( LOG_NOTICE, "Expand env <%s>: %s: Expanded %d bounces", unexpanded_env->e_id, env->e_id, n_rcpts ); queue_envelope( env ); } else { *env_p = env->e_next; env_free( env ); } } else { *env_p = env->e_next; bounce_stdout( env ); env_free( env ); } } if ( snet != NULL ) { if ( snet_close( snet ) != 0 ) { syslog( LOG_ERR, "Liberror: expand snet_close %s: %m", d_original ); sprintf( d_out, "%s/D%s", env->e_dir, env->e_id ); if ( unlink( d_out ) != 0 ) { syslog( LOG_ERR, "Syserror: expand unlink %s: %m", d_out ); } goto cleanup5; } snet = NULL; } syslog( LOG_INFO, "Expand env <%s>: Metric %d entries %d levels", unexpanded_env->e_id, exp.exp_entries, exp.exp_max_level ); if ( simta_expand_debug != 0 ) { return_value = 0; goto cleanup2; } if ( utime( d_original, NULL ) != 0 ) { syslog( LOG_ERR, "Syserror: expand utime %s: %m", d_original ); goto cleanup5; } if ( unexpanded_env->e_dir != simta_dir_fast ) { /* truncate orignal Efile */ sprintf( e_original, "%s/E%s", unexpanded_env->e_dir, unexpanded_env->e_id ); if ( truncate( e_original, (off_t)0 ) != 0 ) { syslog( LOG_ERR, "Syserror: expand truncate %s: %m", e_original ); goto cleanup5; } } /* delete original message */ if ( env_unlink( unexpanded_env ) != 0 ) { syslog( LOG_ERR, "Expand env <%s>: Expansion complete, can't delete message", unexpanded_env->e_id ); } else { syslog( LOG_INFO, "Expand env <%s>: Expansion complete, message deleted", unexpanded_env->e_id ); } return_value = 0; goto cleanup2; cleanup5: cleanup_envelope_list( &exp.exp_errors ); #ifdef HAVE_LDAP cleanup_envelope_list( &exp.exp_gmailfwding ); #endif /* HAVE_LDAP */ cleanup4: for ( eo = host_stab; eo != NULL; eo = eo->eo_next ) { env = eo->eo_env; eo->eo_env = NULL; if (( env->e_flags & ENV_FLAG_EFILE ) != 0 ) { queue_remove_envelope( env ); if ( env_unlink( env ) == 0 ) { syslog( LOG_WARNING, "Expand env <%s>: Message Deleted: " "System error, unwinding expansion", env->e_id ); } else { syslog( LOG_ERR, "Expand env <%s>: " "System error, can't unwind expansion", env->e_id ); } } env_free( env ); } cleanup3: #ifdef HAVE_LDAP for ( memonly = exp.exp_memonly; memonly != NULL; memonly = memonly->el_next ) { if (( memonly->el_exp_addr->e_addr_env_moderated != NULL ) && (( memonly->el_exp_addr->e_addr_env_moderated->e_flags & ENV_FLAG_EFILE ) != 0 )) { env_unlink( memonly->el_exp_addr->e_addr_env_moderated ); env_free( memonly->el_exp_addr->e_addr_env_moderated ); memonly->el_exp_addr->e_addr_env_moderated = NULL; } } #endif /* HAVE_LDAP */ if ( simta_fast_files != fast_file_start ) { syslog( LOG_ERR, "Expand env <%s>: could not unwind expansion", unexpanded_env->e_id ); return_value = 1; } cleanup2: /* free host_stab */ eo = host_stab; while ( eo != NULL ) { eo_free = eo; eo = eo->eo_next; free( eo_free ); } cleanup1: #ifdef HAVE_LDAP exp_addr_link_free( exp.exp_memonly ); #endif /* HAVE_LDAP */ /* free the expansion list */ for ( e_addr = exp.exp_addr_head; e_addr != NULL; e_addr = next_e_addr ) { next_e_addr = e_addr->e_addr_next; #ifdef HAVE_LDAP exp_addr_link_free( e_addr->e_addr_parents ); exp_addr_link_free( e_addr->e_addr_children ); permitted_destroy( e_addr ); if (( e_addr->e_addr_env_moderated != NULL ) && (( e_addr->e_addr_env_moderated->e_flags & ENV_FLAG_EFILE ) == 0 )) { env_free( e_addr->e_addr_env_moderated ); } if ( e_addr->e_addr_owner ) { free( e_addr->e_addr_owner ); } if ( e_addr->e_addr_dn ) { free( e_addr->e_addr_dn ); } #endif free( e_addr->e_addr ); free( e_addr->e_addr_from ); free( e_addr ); } done: if ( return_value != 0 ) { syslog( LOG_ERR, "Expand env <%s>: Expansion failed", unexpanded_env->e_id ); } return( return_value ); }
int spf_check_host( struct spf *s, const yastr domain ) { int i, j, rc, qualifier, ret = SPF_RESULT_NONE; struct dnsr_result *dnsr_res, *dnsr_res_mech = NULL; struct dnsr_string *txt; yastr record = NULL, redirect = NULL, domain_spec, tmp; size_t tok_count = 0; yastr *split = NULL; char *p; unsigned long cidr, cidr6; int mech_queries = 0; /* RFC 7208 3.1 DNS Resource Records * SPF records MUST be published as a DNS TXT (type 16) Resource Record * (RR) [RFC1035] only. */ if (( dnsr_res = get_txt( domain )) == NULL ) { syslog( LOG_WARNING, "SPF %s [%s]: TXT lookup %s failed", s->spf_domain, domain, domain ); return( SPF_RESULT_TEMPERROR ); } for ( i = 0 ; i < dnsr_res->r_ancount ; i++ ) { if ( dnsr_res->r_answer[ i ].rr_type == DNSR_TYPE_TXT ) { txt = dnsr_res->r_answer[ i ].rr_txt.txt_data; /* RFC 7208 4.5 Selecting Records * Starting with the set of records that were returned by the * lookup, discard records that do not begin with a version section * of exactly "v=spf1". Note that the version section is * terminated by either an SP character or the end of the record. */ if (( strncasecmp( txt->s_string, "v=spf1", 6 ) == 0 ) && (( txt->s_string[ 6 ] == ' ' ) || ( txt->s_string[ 6 ] == '\0' ))) { if ( record != NULL ) { /* RFC 7208 3.2 Multiple DNS Records * A domain name MUST NOT have multiple records that would * cause an authorization check to select more than one * record. */ syslog( LOG_ERR, "SPF %s [%s]: multiple v=spf1 records found", s->spf_domain, domain ); ret = SPF_RESULT_PERMERROR; goto cleanup; } record = yaslempty( ); /* RFC 7208 3.3 Multiple Strings in a Single DNS Record * If a published record contains multiple character-strings, * then the record MUST be treated as if those strings are * concatenated together without adding spaces. */ for ( ; txt != NULL ; txt = txt->s_next ) { record = yaslcat( record, txt->s_string ); } } } } if ( record == NULL ) { simta_debuglog( 1, "SPF %s [%s]: no SPF record found", s->spf_domain, domain ); goto cleanup; } simta_debuglog( 2, "SPF %s [%s]: record: %s", s->spf_domain, domain, record ); split = yaslsplitlen( record, yasllen( record ), " ", 1, &tok_count ); /* Start at 1, 0 is v=spf1 */ for ( i = 1 ; i < tok_count ; i++ ) { /* multiple spaces in a record will result in empty elements */ if ( yasllen( split[ i ] ) == 0 ) { continue; } /* RFC 7208 4.6.4 DNS Lookup Limits * Some mechanisms and modifiers (collectively, "terms") cause DNS * queries at the time of evaluation [...] SPF implementations MUST * limit the total number of those terms to 10 during SPF evaluation, * to avoid unreasonable load on the DNS. If this limit is exceeded, * the implementation MUST return "permerror". */ /* In real life strictly enforcing a limit of ten will break SPF * evaluation of multiple major domains, so we use a higher limit. */ if ( s->spf_queries > 25 ) { syslog( LOG_WARNING, "SPF %s [%s]: DNS lookup limit exceeded", s->spf_domain, domain ); ret = SPF_RESULT_PERMERROR; goto cleanup; } /* RFC 7208 4.6.2 Mechanisms * The possible qualifiers, and the results they cause check_host() to * return, are as follows: * * "+" pass * "-" fail * "~" softfail * "?" neutral * * The qualifier is optional and defaults to "+". */ switch ( *split[ i ] ) { case '+': qualifier = SPF_RESULT_PASS; yaslrange( split[ i ], 1, -1 ); break; case '-': qualifier = SPF_RESULT_FAIL; yaslrange( split[ i ], 1, -1 ); break; case '~': qualifier = SPF_RESULT_SOFTFAIL; yaslrange( split[ i ], 1, -1 ); break; case '?': qualifier = SPF_RESULT_NEUTRAL; yaslrange( split[ i ], 1, -1 ); break; default: qualifier = SPF_RESULT_PASS; break; } if ( strncasecmp( split[ i ], "redirect=", 9 ) == 0 ) { s->spf_queries++; redirect = split[ i ]; yaslrange( redirect, 9, -1 ); simta_debuglog( 2, "SPF %s [%s]: redirect to %s", s->spf_domain, domain, redirect ); /* RFC 7208 5.1 "all" * The "all" mechanism is a test that always matches. */ } else if ( strcasecmp( split[ i ], "all" ) == 0 ) { simta_debuglog( 2, "SPF %s [%s]: matched all: %s", s->spf_domain, domain, spf_result_str( qualifier )); ret = qualifier; goto cleanup; /* RFC 7208 5.2 "include" * The "include" mechanism triggers a recursive evaluation of * check_host(). */ } else if ( strncasecmp( split[ i ], "include:", 8 ) == 0 ) { s->spf_queries++; yaslrange( split[ i ], 8, -1 ); simta_debuglog( 2, "SPF %s [%s]: include %s", s->spf_domain, domain, split[ i ] ); rc = spf_check_host( s, split[ i ] ); switch ( rc ) { case SPF_RESULT_NONE: ret = SPF_RESULT_PERMERROR; goto cleanup; case SPF_RESULT_PASS: ret = qualifier; goto cleanup; case SPF_RESULT_TEMPERROR: case SPF_RESULT_PERMERROR: ret = rc; goto cleanup; } /* RFC 7208 5.3 "a" */ } else if (( strcasecmp( split[ i ], "a" ) == 0 ) || ( strncasecmp( split[ i ], "a:", 2 ) == 0 ) || ( strncasecmp( split[ i ], "a/", 2 ) == 0 )) { s->spf_queries++; yaslrange( split[ i ], 1, -1 ); if (( domain_spec = spf_parse_domainspec_cidr( s, domain, split[ i ], &cidr, &cidr6 )) == NULL ) { /* Macro expansion failed, probably a syntax problem. */ ret = SPF_RESULT_PERMERROR; goto cleanup; } rc = spf_check_a( s, domain, cidr, cidr6, domain_spec ); switch( rc ) { case SPF_RESULT_PASS: simta_debuglog( 2, "SPF %s [%s]: matched a %s/%ld/%ld: %s", s->spf_domain, domain, domain_spec, cidr, cidr6, spf_result_str( qualifier )); yaslfree( domain_spec ); ret = qualifier; goto cleanup; case SPF_RESULT_TEMPERROR: yaslfree( domain_spec ); ret = rc; goto cleanup; default: break; } yaslfree( domain_spec ); /* RFC 7208 5.4 "mx" */ } else if (( strcasecmp( split[ i ], "mx" ) == 0 ) || ( strncasecmp( split[ i ], "mx:", 3 ) == 0 ) || ( strncasecmp( split[ i ], "mx/", 3 ) == 0 )) { s->spf_queries++; mech_queries = 0; yaslrange( split[ i ], 2, -1 ); if (( domain_spec = spf_parse_domainspec_cidr( s, domain, split[ i ], &cidr, &cidr6 )) == NULL ) { /* Macro expansion failed, probably a syntax problem. */ ret = SPF_RESULT_PERMERROR; goto cleanup; } if (( dnsr_res_mech = get_mx( domain_spec )) == NULL ) { syslog( LOG_WARNING, "SPF %s [%s]: MX lookup %s failed", s->spf_domain, domain, domain_spec ); yaslfree( domain_spec ); ret = SPF_RESULT_TEMPERROR; goto cleanup; } for ( j = 0 ; j < dnsr_res_mech->r_ancount ; j++ ) { if ( dnsr_res_mech->r_answer[ j ].rr_type == DNSR_TYPE_MX ) { /* RFC 7208 4.6.4 DNS Lookup Limits * When evaluating the "mx" mechanism, the number of "MX" * resource records queried is included in the overall * limit of 10 mechanisms/modifiers that cause DNS lookups */ s->spf_queries++; rc = spf_check_a( s, domain, cidr, cidr6, dnsr_res_mech->r_answer[ j ].rr_mx.mx_exchange ); switch( rc ) { case SPF_RESULT_PASS: simta_debuglog( 2, "SPF %s [%s]: matched mx %s/%ld/%ld: %s", s->spf_domain, domain, domain_spec, cidr, cidr6, spf_result_str( qualifier )); ret = qualifier; dnsr_free_result( dnsr_res_mech ); yaslfree( domain_spec ); goto cleanup; case SPF_RESULT_PERMERROR: case SPF_RESULT_TEMPERROR: ret = rc; dnsr_free_result( dnsr_res_mech ); yaslfree( domain_spec ); goto cleanup; default: break; } } } dnsr_free_result( dnsr_res_mech ); yaslfree( domain_spec ); /* RFC 7208 5.5 "ptr" (do not use) */ } else if (( strcasecmp( split[ i ], "ptr" ) == 0 ) || ( strncasecmp( split[ i ], "ptr:", 4 ) == 0 )) { s->spf_queries++; mech_queries = 0; if (( dnsr_res_mech = get_ptr( s->spf_sockaddr )) == NULL ) { /* RFC 7208 5.5 "ptr" (do not use ) * If a DNS error occurs while doing the PTR RR lookup, * then this mechanism fails to match. */ continue; } if ( dnsr_res_mech->r_ancount == 0 ) { dnsr_free_result( dnsr_res_mech ); continue; } if ( split[ i ][ 3 ] == ':' ) { domain_spec = yaslnew(( split[ i ] + 4 ), ( yasllen( split[ i ] ) - 4 )); } else { domain_spec = yasldup( domain ); } for ( j = 0 ; j < dnsr_res_mech->r_ancount ; j++ ) { if ( dnsr_res_mech->r_answer[ j ].rr_type != DNSR_TYPE_PTR ) { continue; } /* We only care if it's a pass; like the initial PTR query, * DNS errors are treated as a non-match rather than an error. */ /* RFC 7208 4.6.4 DNS Lookup Limits * the evaluation of each "PTR" record MUST NOT result in * querying more than 10 address records -- either "A" or * "AAAA" resource records. If this limit is exceeded, all * records other than the first 10 MUST be ignored. */ if (( mech_queries++ < 10 ) && ( spf_check_a( s, domain, 32, 128, dnsr_res_mech->r_answer[ j ].rr_dn.dn_name ) == SPF_RESULT_PASS )) { tmp = yaslauto( dnsr_res_mech->r_answer[ j ].rr_dn.dn_name ); while (( yasllen( tmp ) > yasllen( domain_spec )) && ( p = strchr( tmp, '.' ))) { yaslrange( tmp, ( p - tmp + 1 ), -1 ); } rc = strcasecmp( tmp, domain_spec ); yaslfree( tmp ); if ( rc == 0 ) { simta_debuglog( 2, "SPF %s [%s]: matched ptr %s (%s): %s", s->spf_domain, domain, domain_spec, dnsr_res_mech->r_answer[ j ].rr_dn.dn_name, spf_result_str( qualifier )); ret = qualifier; yaslfree( domain_spec ); dnsr_free_result( dnsr_res_mech ); goto cleanup; } } } yaslfree( domain_spec ); dnsr_free_result( dnsr_res_mech ); /* RFC 7208 5.6 "ip4" and "ip6" * These mechanisms test whether <ip> is contained within a given * IP network. */ } else if ( strncasecmp( split[ i ], "ip4:", 4 ) == 0 ) { if ( s->spf_sockaddr->sa_family != AF_INET ) { continue; } yaslrange( split[ i ], 4, -1 ); if (( p = strchr( split[ i ], '/' )) != NULL ) { errno = 0; cidr = strtoul( p + 1, NULL, 10 ); if ( errno ) { syslog( LOG_WARNING, "SPF %s [%s]: failed parsing CIDR mask %s: %m", s->spf_domain, domain, p + 1 ); ret = SPF_RESULT_PERMERROR; goto cleanup; } if ( cidr > 32 ) { syslog( LOG_WARNING, "SPF %s [%s]: invalid CIDR mask: %ld", s->spf_domain, domain, cidr ); ret = SPF_RESULT_PERMERROR; goto cleanup; } yaslrange( split[ i ], 0, p - split[ i ] - 1 ); } else { cidr = 32; } if (( rc = simta_cidr_compare( cidr, s->spf_sockaddr, NULL, split[ i ] )) < 0 ) { syslog( LOG_WARNING, "SPF %s [%s]: simta_cidr_compare failed for %s", s->spf_domain, domain, split[ i ] ); ret = SPF_RESULT_PERMERROR; goto cleanup; } else if ( rc == 0 ) { simta_debuglog( 2, "SPF %s [%s]: matched ip4 %s/%ld: %s", s->spf_domain, domain, split[ i ], cidr, spf_result_str( qualifier )); ret = qualifier; goto cleanup; } } else if ( strncasecmp( split[ i ], "ip6:", 4 ) == 0 ) { if ( s->spf_sockaddr->sa_family != AF_INET6 ) { continue; } yaslrange( split[ i ], 4, -1 ); if (( p = strchr( split[ i ], '/' )) != NULL ) { errno = 0; cidr = strtoul( p + 1, NULL, 10 ); if ( errno ) { syslog( LOG_WARNING, "SPF %s [%s]: failed parsing CIDR mask %s: %m", s->spf_domain, domain, p + 1 ); } if ( cidr > 128 ) { syslog( LOG_WARNING, "SPF %s [%s]: invalid CIDR mask: %ld", s->spf_domain, domain, cidr ); ret = SPF_RESULT_PERMERROR; goto cleanup; } yaslrange( split[ i ], 0, p - split[ i ] - 1 ); } else { cidr = 128; } if (( rc = simta_cidr_compare( cidr, s->spf_sockaddr, NULL, split[ i ] )) < 0 ) { syslog( LOG_WARNING, "SPF %s [%s]: simta_cidr_compare failed for %s", s->spf_domain, domain, split[ i ] ); ret = SPF_RESULT_PERMERROR; goto cleanup; } else if ( rc == 0 ) { simta_debuglog( 2, "SPF %s [%s]: matched ip6 %s/%ld: %s", s->spf_domain, domain, split[ i ], cidr, spf_result_str( qualifier )); ret = qualifier; goto cleanup; } /* RFC 7208 5.7 "exists" */ } else if ( strncasecmp( split[ i ], "exists:", 7 ) == 0 ) { s->spf_queries++; yaslrange( split[ i ], 7, -1 ); if (( domain_spec = spf_macro_expand( s, domain, split[ i ] )) == NULL ) { /* Macro expansion failed, probably a syntax problem. */ ret = SPF_RESULT_PERMERROR; goto cleanup; } if (( dnsr_res_mech = get_a( domain_spec )) == NULL ) { syslog( LOG_WARNING, "SPF %s [%s]: A lookup %s failed", s->spf_domain, domain, domain_spec ); yaslfree( domain_spec ); ret = SPF_RESULT_TEMPERROR; goto cleanup; } if ( dnsr_res_mech->r_ancount > 0 ) { simta_debuglog( 2, "SPF %s [%s]: matched exists %s: %s", s->spf_domain, domain, domain_spec, spf_result_str( qualifier )); dnsr_free_result( dnsr_res_mech ); yaslfree( domain_spec ); ret = qualifier; goto cleanup; } yaslfree( domain_spec ); dnsr_free_result( dnsr_res_mech ); } else { for ( p = split[ i ] ; isalnum( *p ) ; p++ ); if ( *p == '=' ) { /* RFC 7208 6 Modifier Definitions * Unrecognized modifiers MUST be ignored */ simta_debuglog( 1, "SPF %s [%s]: %s unknown modifier %s", s->spf_domain, domain, spf_result_str( qualifier ), split[ i ] ); } else { syslog( LOG_WARNING, "SPF %s [%s]: %s unknown mechanism %s", s->spf_domain, domain, spf_result_str( qualifier ), split[ i ] ); ret = SPF_RESULT_PERMERROR; goto cleanup; } } } if ( redirect != NULL ) { if (( domain_spec = spf_macro_expand( s, domain, redirect )) == NULL ) { /* Macro expansion failed, probably a syntax problem. */ ret = SPF_RESULT_PERMERROR; } else { ret = spf_check_host( s, domain_spec ); yaslfree( domain_spec ); } if ( ret == SPF_RESULT_NONE ) { ret = SPF_RESULT_PERMERROR; } } else { /* RFC 7208 4.7 Default Result * If none of the mechanisms match and there is no "redirect" modifier, * then the check_host() returns a result of "neutral", just as if * "?all" were specified as the last directive. */ ret = SPF_RESULT_NEUTRAL; simta_debuglog( 2, "SPF %s [%s]: default result: %s", s->spf_domain, domain, spf_result_str( ret )); } cleanup: if ( split != NULL ) { yaslfreesplitres( split, tok_count ); } yaslfree( record ); dnsr_free_result( dnsr_res ); return( ret ); }
static yastr spf_macro_expand( struct spf *s, const yastr domain, const yastr macro ) { int urlescape, rtransform; long dtransform, i, j; char *p, *pp; char delim; yastr expanded, tmp, escaped; yastr *split; size_t tok_count; expanded = yaslempty( ); escaped = yaslempty( ); tmp = yaslempty( ); for ( p = macro ; *p != '\0' ; p++ ) { if ( *p != '%' ) { expanded = yaslcatlen( expanded, p, 1 ); continue; } p++; switch( *p ) { case '%': expanded = yaslcat( expanded, "%" ); break; case '_': expanded = yaslcat( expanded, "_" ); break; case '-': expanded = yaslcat( expanded, "%20" ); break; case '{': p++; /* RFC 7208 7.3 Macro Processing Details * Uppercase macros expand exactly as their lowercase equivalents, * and are then URL escaped. */ urlescape = isupper( *p ); switch( *p ) { case 'S': case 's': yaslclear( tmp ); tmp = yaslcatprintf( tmp, "%s@%s", s->spf_localpart, s->spf_domain ); break; case 'L': case 'l': tmp = yaslcpy( tmp, s->spf_localpart ); break; case 'O': case 'o': tmp = yaslcpy( tmp, s->spf_domain ); break; case 'D': case 'd': tmp = yaslcpy( tmp, domain ); break; case 'I': case 'i': if ( s->spf_sockaddr->sa_family == AF_INET ) { tmp = yaslgrowzero( tmp, INET_ADDRSTRLEN ); if ( inet_ntop( s->spf_sockaddr->sa_family, &((struct sockaddr_in *)s->spf_sockaddr)->sin_addr, tmp, (socklen_t)yasllen( tmp )) == NULL ) { goto error; } yaslupdatelen( tmp ); } break; case 'P': case 'p': /* This is overly complex and should not be used, * so we're not going to implement it. */ tmp = yaslcpy( tmp, "unknown" ); break; case 'V': case 'v': tmp = yaslcpy( tmp, ( s->spf_sockaddr->sa_family == AF_INET6 ) ? "ip6" : "in-addr" ); break; case 'H': case 'h': tmp = yaslcpy( tmp, s->spf_helo ); break; default: syslog( LOG_WARNING, "SPF %s [%s]: invalid macro-letter: %c", s->spf_domain, domain, *p ); goto error; } if ( urlescape ) { /* RFC 7208 7.3 Macro Processing Details * Uppercase macros expand exactly as their lowercase * equivalents, and are then URL escaped. URL escaping MUST be * performed for characters not in the "unreserved" set, which * is defined in [RFC3986]. */ yaslclear( escaped ); for ( pp = tmp ; *pp != '\0' ; pp++ ) { /* RFC 3986 2.3 Unreserved Characters * Characters that are allowed in a URI but do not have a * reserved purpose are called unreserved. These include * uppercase and lowercase letters, decimal digits, hyphen, * period, underscore, and tilde. */ if ( isalnum( *pp ) || *pp == '-' || *pp == '.' || *pp == '_' || *pp == '~' ) { escaped = yaslcatlen( escaped, pp, 1 ); } else { /* Reserved */ escaped = yaslcatprintf( escaped, "%%%X", *pp ); } } tmp = yaslcpylen( tmp, escaped, yasllen( escaped )); } p++; /* Check for transformers */ dtransform = 0; rtransform = 0; if ( isdigit( *p )) { dtransform = strtoul( p, &pp, 10 ); p = pp; } if ( *p == 'r' ) { rtransform = 1; p++; } delim = '\0'; for ( pp = p ; *pp != '\0' ; pp++ ) { if ( *pp == '}' ) { break; } switch( *pp ) { /* RFC 7208 7.1 Formal Specification * delimiter = "." / "-" / "+" / "," / "/" / "_" / "=" */ case '.': case '-': case '+': case ',': case '/': case '_': case '=': if ( delim != '\0' ) { tmp = yaslmapchars( tmp, pp, &delim, 1 ); } else { delim = *pp; } break; default: syslog( LOG_WARNING, "SPF %s [%s]: invalid delimiter: %c", s->spf_domain, domain, *pp ); goto error; } } if (( rtransform == 1 ) || ( dtransform > 0 ) || ( delim != '\0' )) { if ( delim == '\0' ) { delim = '.'; } split = yaslsplitlen( tmp, yasllen( tmp ), &delim, 1, &tok_count ); yaslclear( tmp ); if ( rtransform == 1 ) { if (( dtransform > 0 ) && ( tok_count > dtransform )) { j = tok_count - dtransform; } else { j = 0; } for ( i = tok_count - 1 ; i >= j ; i-- ) { if ( yasllen( tmp ) > 0 ) { tmp = yaslcat( tmp, "." ); } tmp = yaslcatyasl( tmp, split [ i ] ); } } else { if (( dtransform > 0 ) && (tok_count > dtransform )) { j = dtransform; } else { j = tok_count; } for ( i = 0 ; i < j ; i++ ) { if ( yasllen( tmp ) > 0 ) { tmp = yaslcat( tmp, "." ); } tmp = yaslcatyasl( tmp, split[ i ] ); } } yaslfreesplitres( split, tok_count ); } expanded = yaslcatyasl( expanded, tmp ); break; default: syslog( LOG_WARNING, "SPF %s [%s]: invalid macro-expand: %s", s->spf_domain, domain, p ); goto error; } } if ( yaslcmp( macro, expanded )) { simta_debuglog( 3, "SPF %s [%s]: expanded %s to %s", s->spf_domain, domain, macro, expanded ); } yaslfree( tmp ); yaslfree( escaped ); return( expanded ); error: yaslfree( tmp ); yaslfree( escaped ); yaslfree( expanded ); return( NULL ); }