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 ); }
static int simalias_create( void ) { int linenum = 0, i; int state = ALIAS_WHITE; char rawline[ MAXPATHLEN ]; yastr line, key, value; char *p; #ifdef HAVE_LMDB int rc; struct simta_dbh *dbh = NULL; #endif /* HAVE_LMDB */ unlink( output ); #ifdef HAVE_LMDB if (( rc = simta_db_new( &dbh, output )) != 0 ) { fprintf( stderr, "simta_db_new: %s: %s\n", output, simta_db_strerror( rc )); return( 1 ); } #else /* HAVE_LMDB */ fprintf( stderr, "Compiled without DB support, data will not be saved.\n" ); #endif /* HAVE_LMDB */ line = yaslempty( ); key = yaslempty( ); value = yaslempty( ); while ( fgets( rawline, MAXPATHLEN, finput ) != NULL ) { linenum++; line = yaslcpy( line, rawline ); yasltrim( line, " \f\n\r\t\v" ); /* Blank line or comment */ if (( *line == '\0' ) || ( *line == '#' )) { continue; } yasltolower( line ); line = yaslcatlen( line, "\n", 1 ); if ( isspace( *rawline )) { if ( state == ALIAS_WHITE ) { /* How unexpected. */ fprintf( stderr, "%s line %d: Unexpected continuation line.\n", input, linenum ); state = ALIAS_CONT; } } else if ( state == ALIAS_CONT ) { fprintf( stderr, "%s line %d: Expected a continuation line.\n", input, linenum ); state = ALIAS_WHITE; } if ( state == ALIAS_WHITE ) { if (( p = strchr( line, ':' )) != NULL ) { key = yaslcpylen( key, line, (size_t) ( p - line )); yaslrange( line, p - line + 1, -1 ); if ( strncmp( key, "owner-", 6 ) == 0 ) { /* Canonicalise sendmail-style owner */ if ( verbose ) { fprintf ( stderr, "%s line %d: noncanonical owner %s " "will be made canonical\n", input, linenum, key ); } yaslrange( key, 6, -1 ); key = yaslcat( key, "-errors" ); } else if ((( p = strrchr( key, '-' )) != NULL ) && (( strcmp( p, "-owner" ) == 0 ) || ( strcmp( p, "-owners" ) == 0 ) || ( strcmp( p, "-error" ) == 0 ) || ( strcmp( p, "-request" ) == 0 ) || ( strcmp( p, "-requests" ) == 0 ))) { /* Canonicalise simta-style owner */ if ( verbose ) { fprintf ( stderr, "%s line %d: noncanonical owner %s " "will be made canonical\n", input, linenum, key ); } yaslrange( key, 0, p - key ); key = yaslcat( key, "-errors" ); } } else { fprintf( stderr, "%s line %d: Expected a colon somewhere. Skipping.\n", input, linenum ); continue; } } else { state = ALIAS_WHITE; } i = 0; yaslclear( value ); for ( p = line ; *p != '\0' ; p++ ) { if ( *p == '"' ) { if ( i > 0 && ( value[ i - 1 ] == '\\' )) { value[ i - 1 ] = '"'; } else if ( state == ALIAS_QUOTE ) { state = ALIAS_WHITE; if ( *value == '\0' ) { fprintf( stderr, "%s line %d: Empty quoted value.\n", input, linenum ); } } else if ( state == ALIAS_WORD ) { fprintf( stderr, "%s line %d: Unexpected quote.\n", input, linenum ); } else { state = ALIAS_QUOTE; } } else if ( *p == ',' ) { switch ( state ) { case ALIAS_QUOTE: value = yaslcatlen( value, p, 1 ); break; case ALIAS_CONT: fprintf( stderr, "%s line %d: Empty list element.\n", input, linenum ); break; default : state = ALIAS_CONT; } } else if ( isspace( *p )) { switch ( state ) { case ALIAS_QUOTE: value = yaslcatlen( value, p, 1 ); break; case ALIAS_WORD : state = ALIAS_WHITE; break; default : break; } } else { if ( state == ALIAS_WHITE || state == ALIAS_CONT ) { state = ALIAS_WORD; } value = yaslcatlen( value, p, 1 ); } if ( *value != '\0' && ( state == ALIAS_WHITE || state == ALIAS_CONT )) { /* Check for known but unsupported syntax */ if ( *value == '/' ) { /* We have special support for nullrouting, so that's OK. */ if ( strcmp( value, "/dev/null" ) != 0 ) { fprintf( stderr, "%s line %d: Unsupported: delivery to file\n", input, linenum ); } } else if ( *value == '|' ) { fprintf( stderr, "%s line %d: Unsupported: delivery to pipe\n", input, linenum ); } else if ( strncmp( value, ":include:", 9 ) == 0 ) { fprintf( stderr, "%s line %d: Unsupported: file include\n", input, linenum ); #ifdef HAVE_LMDB } else if (( rc = simta_db_put( dbh, key, value )) != 0 ) { fprintf( stderr, "simta_db_put: %s: %s\n", input, simta_db_strerror( rc )); return( 1 ); #endif /* HAVE_LMDB */ } else if ( verbose ) { printf( "%s line %d: Added %s -> %s\n", input, linenum, key, value ); } yaslclear( value ); i = 0; } } } #ifdef HAVE_LMDB simta_db_close( dbh ); if ( verbose ) printf( "%s: created\n", output ); #else /* HAVE_LMDB */ if ( verbose ) printf( "%s: not created\n", output ); #endif /* HAVE_LMDB */ yaslfree( line ); yaslfree( key ); yaslfree( value ); return( 0 ); }