int env_outfile( struct envelope *env ) { yastr headers; yastr tmp; headers = env->e_extra_headers; env->e_extra_headers = NULL; #ifdef HAVE_LIBOPENDKIM if ( env->e_flags & ENV_FLAG_DKIMSIGN ) { tmp = env_dkim_sign( env ); if ( headers != NULL ) { tmp = yaslcatyasl( yaslcat( tmp, "\n" ), headers ); yaslfree( headers ); } headers = tmp; env->e_flags ^= ENV_FLAG_DKIMSIGN; } #endif /* HAVE_LIBOPENDKIM */ if ( headers ) { env_dfile_copy( env, NULL, headers ); yaslfree( headers ); } if (( env->e_flags & ENV_FLAG_TFILE ) == 0 ) { if ( env_tfile( env ) != 0 ) { return( 1 ); } } if ( env_efile( env ) != 0 ) { return( 1 ); } return( 0 ); }
yastr env_dkim_sign( struct envelope *env ) { char df[ MAXPATHLEN + 1 ]; DKIM_LIB *libhandle; unsigned int flags; DKIM *dkim; DKIM_STAT result; yastr signature = NULL; yastr key = NULL; yastr tmp = NULL; yastr *split = NULL; size_t tok_count = 0; char buf[ 1024 * 1024 ]; unsigned char *dkim_header; SNET *snet; ssize_t chunk; sprintf( df, "%s/D%s", env->e_dir, env->e_id ); if (( snet = snet_open( simta_dkim_key, O_RDONLY, 0, 1024 * 1024 )) == NULL ) { syslog( LOG_ERR, "Liberror: env_dkim_sign snet_open %s: %m", simta_dkim_key ); return( NULL ); } key = yaslempty(); while (( chunk = snet_read( snet, buf, 1024 * 1024, NULL )) > 0 ) { key = yaslcatlen( key, buf, chunk ); } snet_close( snet ); if (( libhandle = dkim_init( NULL, NULL )) == NULL ) { syslog( LOG_ERR, "Liberror: env_dkim_sign dkim_init" ); return( NULL ); } /* Data is stored in UNIX format, so tell libopendkim to fix * CRLF issues. */ flags = DKIM_LIBFLAGS_FIXCRLF; dkim_options( libhandle, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS, &flags, sizeof( flags )); /* Only sign the headers recommended by RFC 6376 */ dkim_options( libhandle, DKIM_OP_SETOPT, DKIM_OPTS_SIGNHDRS, dkim_should_signhdrs, sizeof( unsigned char ** )); if (( dkim = dkim_sign( libhandle, (unsigned char *)(env->e_id), NULL, (unsigned char *)key, (unsigned char *)simta_dkim_selector, (unsigned char *)simta_dkim_domain, DKIM_CANON_RELAXED, DKIM_CANON_RELAXED, DKIM_SIGN_RSASHA256, -1, &result )) == NULL ) { syslog( LOG_NOTICE, "Liberror: env_dkim_sign dkim_sign: %s", dkim_getresultstr( result )); goto error; } if (( snet = snet_open( df, O_RDONLY, 0, 1024 * 1024 )) == NULL ) { syslog( LOG_ERR, "Liberror: env_dkim_sign snet_open %s: %m", buf ); goto error; } while (( chunk = snet_read( snet, buf, 1024 * 1024, NULL )) > 0 ) { if (( result = dkim_chunk( dkim, (unsigned char *)buf, chunk )) != 0 ) { syslog( LOG_NOTICE, "Liberror: env_dkim_sign dkim_chunk: %s: %s", dkim_getresultstr( result ), dkim_geterror( dkim )); snet_close( snet ); goto error; } } snet_close( snet ); if (( result = dkim_chunk( dkim, NULL, 0 )) != 0 ) { syslog( LOG_NOTICE, "Liberror: env_dkim_sign dkim_chunk: %s: %s", dkim_getresultstr( result ), dkim_geterror( dkim )); goto error; } if (( result = dkim_eom( dkim, NULL )) != 0 ) { syslog( LOG_NOTICE, "Liberror: env_dkim_sign dkim_eom: %s: %s", dkim_getresultstr( result ), dkim_geterror( dkim )); goto error; } if (( result = dkim_getsighdr_d( dkim, 16, &dkim_header, (size_t *)&chunk )) != 0 ) { syslog( LOG_NOTICE, "Liberror: env_dkim_sign dkim_getsighdr_d: %s: %s", dkim_getresultstr( result ), dkim_geterror( dkim )); goto error; } /* Get rid of carriage returns in libopendkim output */ split = yaslsplitlen( (const char *)dkim_header, strlen( (const char *)dkim_header ), "\r", 1, &tok_count ); tmp = yasljoinyasl( split, tok_count, "", 0 ); signature = yaslcatyasl( yaslauto( "DKIM-Signature: " ), tmp ); error: yaslfree( tmp ); yaslfreesplitres( split, tok_count ); yaslfree( key ); dkim_free( dkim ); dkim_close( libhandle ); return( signature ); }
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 ); }