commit 7e7046817041529117a0aa3b446e48c43a50619f from: Kirill A. Korinsky date: Fri Jan 31 23:45:29 2025 UTC Refactoring: use only one ar_state for everything commit - c45570465634b62926ee7e196568b1c801c1d771 commit + 7e7046817041529117a0aa3b446e48c43a50619f blob - c7af2ef576381355cc60edf461252b87b32a5771 blob + 33546d36fa9c8e45e9c30423b18e5cb446de5f59 --- main.c +++ main.c @@ -46,12 +46,15 @@ /* * Use RFC8601 (Authentication-Results) codes instead of RFC6376 codes, - * since they're more expressive. + * since they're more expressive with additional NONE and SOFTFAIL for + * RFC7208 (Sender Policy Framework (SPF)). */ enum ar_state { AR_UNKNOWN, + AR_NONE, AR_PASS, AR_FAIL, + AR_SOFTFAIL, AR_POLICY, AR_NEUTRAL, AR_TEMPERROR, @@ -104,28 +107,6 @@ struct ar_signature { /* RFC 6376 doesn't care about CNAME, use simalr with SPF limit */ #define AR_LOOKUP_LOOKUP_LIMIT 11 int nqueries; -}; - -/* - * Use RFC7601 (Authentication-Results), anyway OpenSMTPD reports only pass or fail - */ -enum iprev_state { - IPREV_NONE, - IPREV_PASS, - IPREV_FAIL -}; - -/* - * Base on RFC7208 - */ -enum spf_state { - SPF_NONE, - SPF_NEUTRAL, - SPF_PASS, - SPF_FAIL, - SPF_SOFTFAIL, - SPF_TEMPERROR, - SPF_PERMERROR }; /* @@ -137,7 +118,7 @@ struct spf_query { struct spf_record *spf; struct event_asr *eva; int type; - enum spf_state q; + enum ar_state q; int include; int exists; char *domain; @@ -147,7 +128,7 @@ struct spf_query { struct spf_record { struct osmtpd_ctx *ctx; - enum spf_state state; + enum ar_state state; const char *state_reason; char *sender_local; char *sender_domain; @@ -189,7 +170,7 @@ struct message { struct session { struct osmtpd_ctx *ctx; - enum iprev_state iprev; + enum ar_state iprev; struct spf_record *spf_helo; struct spf_record *spf_mailfrom; struct sockaddr_storage src; @@ -234,11 +215,10 @@ void ar_header_cat(struct osmtpd_ctx *, const char *); void ar_body_parse(struct message *, const char *); void ar_body_verify(struct ar_signature *); void ar_rr_resolve(struct asr_result *, void *); -const char *iprev_state2str(enum iprev_state); char *spf_evaluate_domain(struct spf_record *, const char *); void spf_lookup_record(struct spf_record *, const char *, int, - enum spf_state, int, int); -void spf_done(struct spf_record *, enum spf_state, const char *); + enum ar_state, int, int); +void spf_done(struct spf_record *, enum ar_state, const char *); void spf_resolve(struct asr_result *, void *); void spf_resolve_txt(struct dns_rr *, struct spf_query *); void spf_resolve_mx(struct dns_rr *, struct spf_query *); @@ -249,7 +229,6 @@ char* spf_parse_txt(const char *, size_t); int spf_check_cidr(struct spf_record *, struct in_addr *, int ); int spf_check_cidr6(struct spf_record *, struct in6_addr *, int ); int spf_execute_txt(struct spf_query *); -const char *spf_state2str(enum spf_state); int spf_ar_cat(const char *, struct spf_record *, char **, size_t *, ssize_t *); void auth_message_verify(struct message *); void auth_ar_create(struct osmtpd_ctx *); @@ -316,9 +295,9 @@ auth_connect(struct osmtpd_ctx *ctx, const char *rdns, struct session *ses = ctx->local_session; if (fcrdns == OSMTPD_STATUS_OK) - ses->iprev = IPREV_PASS; + ses->iprev = AR_PASS; else - ses->iprev = IPREV_FAIL; + ses->iprev = AR_FAIL; memcpy(&ses->src, src, sizeof(struct sockaddr_storage)); @@ -422,7 +401,7 @@ spf_record_new(struct osmtpd_ctx *ctx, const char *fro osmtpd_err(1, "%s: malloc", __func__); spf->ctx = ctx; - spf->state = SPF_NONE; + spf->state = AR_NONE; spf->state_reason = NULL; spf->nqueries = 0; spf->running = 0; @@ -454,7 +433,7 @@ spf_record_new(struct osmtpd_ctx *ctx, const char *fro osmtpd_err(1, "%s: malloc", __func__); spf_lookup_record( - spf, spf->sender_domain, T_TXT, SPF_PASS, 0, 0); + spf, spf->sender_domain, T_TXT, AR_PASS, 0, 0); return spf; @@ -495,7 +474,7 @@ auth_session_new(struct osmtpd_ctx *ctx) osmtpd_err(1, "%s: malloc", __func__); ses->ctx = ctx; - ses->iprev = IPREV_NONE; + ses->iprev = AR_NONE; ses->spf_helo = NULL; ses->spf_mailfrom = NULL; @@ -1344,6 +1323,7 @@ ar_signature_state(struct ar_signature *sig, enum ar_s break; case AR_PASS: case AR_FAIL: + case AR_SOFTFAIL: osmtpd_errx(1, "Unexpected transition"); case AR_POLICY: if (state == AR_PASS) @@ -1377,6 +1357,8 @@ ar_state2str(enum ar_state state) return "pass"; case AR_FAIL: return "fail"; + case AR_SOFTFAIL: + return "softfail"; case AR_POLICY: return "policy"; case AR_NEUTRAL: @@ -1838,20 +1820,6 @@ ar_body_verify(struct ar_signature *sig) if (digestsz != sig->bhsz || memcmp(digest, sig->bh, digestsz) != 0) ar_signature_state(sig, AR_FAIL, "bh mismatch"); -} - -const char * -iprev_state2str(enum iprev_state state) -{ - switch (state) - { - case IPREV_NONE: - return "none"; - case IPREV_PASS: - return "pass"; - case IPREV_FAIL: - return "fail"; - } } char * @@ -1869,7 +1837,7 @@ spf_evaluate_domain(struct spf_record *spf, const char int reverse; if (domain == NULL || domain[0] == '\0') { - spf_done(spf, SPF_PERMERROR, "Empty domain"); + spf_done(spf, AR_PERMERROR, "Empty domain"); return NULL; } @@ -1879,7 +1847,7 @@ spf_evaluate_domain(struct spf_record *spf, const char if (domain[0] < 0x21 || domain[0] > 0x7e) { spf_done( - spf, SPF_PERMERROR, "Invalid character in domain-spec"); + spf, AR_PERMERROR, "Invalid character in domain-spec"); return NULL; } @@ -1899,7 +1867,7 @@ spf_evaluate_domain(struct spf_record *spf, const char case '-': if (i + 3 >= sizeof(spec)) { spf_done( - spf, SPF_PERMERROR, "domain-spec too large"); + spf, AR_PERMERROR, "domain-spec too large"); return NULL; } @@ -1920,64 +1888,204 @@ spf_evaluate_domain(struct spf_record *spf, const char "%s@%s", spf->sender_local, spf->sender_domain); break; - case 'L': - case 'l': - mlen = strlcpy(macro, - spf->sender_local, sizeof(macro)); - break; - case 'O': - case 'o': - mlen = strlcpy(macro, - spf->sender_domain, - sizeof(macro)); - break; - case 'D': - case 'd': - if (spf->nqueries < 1) { - spf_done(spf, SPF_PERMERROR, - "no domain for d macro"); + case '{': + domain++; + digits = -1; + reverse = 0; + delimiters[0] = '\0'; + + switch (domain[0]) { + case 'S': + case 's': + mlen = (size_t) snprintf(macro, sizeof(macro), + "%s@%s", spf->sender_local, + spf->sender_domain); + break; + case 'L': + case 'l': + mlen = strlcpy(macro, + spf->sender_local, sizeof(macro)); + break; + case 'O': + case 'o': + mlen = strlcpy(macro, + spf->sender_domain, + sizeof(macro)); + break; + case 'D': + case 'd': + if (spf->nqueries < 1) { + spf_done(spf, AR_PERMERROR, + "no domain for d macro"); + return NULL; + } + mlen = strlcpy(macro, + spf->queries[spf->nqueries - 1].domain, + sizeof(macro)); + break; + case 'I': + case 'i': + if (ses->src.ss_family == AF_INET) { + addr = (u_char *)(&((struct sockaddr_in *) + &(ses->src))->sin_addr); + mlen = snprintf(macro, sizeof(macro), + "%u.%u.%u.%u", + (addr[0] & 0xff), (addr[1] & 0xff), + (addr[2] & 0xff), (addr[3] & 0xff)); + } else if (ses->src.ss_family == AF_INET6) { + addr = (u_char *)(&((struct sockaddr_in6 *) + &(ses->src))->sin6_addr); + mlen = snprintf(macro, sizeof(macro), + "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx." + "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx." + "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx." + "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx", + (u_char) ((addr[0] >> 4) & 0x0f), (u_char) (addr[0] & 0x0f), + (u_char) ((addr[1] >> 4) & 0x0f), (u_char) (addr[1] & 0x0f), + (u_char) ((addr[2] >> 4) & 0x0f), (u_char) (addr[2] & 0x0f), + (u_char) ((addr[3] >> 4) & 0x0f), (u_char) (addr[3] & 0x0f), + (u_char) ((addr[4] >> 4) & 0x0f), (u_char) (addr[4] & 0x0f), + (u_char) ((addr[5] >> 4) & 0x0f), (u_char) (addr[5] & 0x0f), + (u_char) ((addr[6] >> 4) & 0x0f), (u_char) (addr[6] & 0x0f), + (u_char) ((addr[7] >> 4) & 0x0f), (u_char) (addr[7] & 0x0f), + (u_char) ((addr[8] >> 4) & 0x0f), (u_char) (addr[8] & 0x0f), + (u_char) ((addr[9] >> 4) & 0x0f), (u_char) (addr[9] & 0x0f), + (u_char) ((addr[10] >> 4) & 0x0f), (u_char) (addr[10] & 0x0f), + (u_char) ((addr[11] >> 4) & 0x0f), (u_char) (addr[11] & 0x0f), + (u_char) ((addr[12] >> 4) & 0x0f), (u_char) (addr[12] & 0x0f), + (u_char) ((addr[13] >> 4) & 0x0f), (u_char) (addr[13] & 0x0f), + (u_char) ((addr[14] >> 4) & 0x0f), (u_char) (addr[14] & 0x0f), + (u_char) ((addr[15] >> 4) & 0x0f), (u_char) (addr[15] & 0x0f)); + } else { + spf_done(spf, AR_PERMERROR, + "unsupported type of address"); + return NULL; + } + break; + case 'P': + case 'p': + mlen = strlcpy(macro, ses->rdns, sizeof(macro)); + break; + case 'V': + case 'v': + if (ses->src.ss_family == AF_INET) + mlen = strlcpy(macro, "in-addr", + sizeof(macro)); + else if (ses->src.ss_family == AF_INET6) + mlen = strlcpy(macro, "ip6", + sizeof(macro)); + else { + spf_done(spf, AR_PERMERROR, + "unsupported type of address"); + return NULL; + } + break; + case 'H': + case 'h': + mlen = strlcpy(macro, ses->identity, + sizeof(macro)); + break; + default: + spf_done(spf, AR_PERMERROR, + "Unexpected macro in domain-spec"); return NULL; } - mlen = strlcpy(macro, - spf->queries[spf->nqueries - 1].domain, - sizeof(macro)); - break; - case 'I': - case 'i': - if (ses->src.ss_family == AF_INET) { - addr = (u_char *)(&((struct sockaddr_in *) - &(ses->src))->sin_addr); - mlen = snprintf(macro, sizeof(macro), - "%u.%u.%u.%u", - (addr[0] & 0xff), (addr[1] & 0xff), - (addr[2] & 0xff), (addr[3] & 0xff)); - } else if (ses->src.ss_family == AF_INET6) { - addr = (u_char *)(&((struct sockaddr_in6 *) - &(ses->src))->sin6_addr); - mlen = snprintf(macro, sizeof(macro), - "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx." - "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx." - "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx." - "%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx", - (u_char) ((addr[0] >> 4) & 0x0f), (u_char) (addr[0] & 0x0f), - (u_char) ((addr[1] >> 4) & 0x0f), (u_char) (addr[1] & 0x0f), - (u_char) ((addr[2] >> 4) & 0x0f), (u_char) (addr[2] & 0x0f), - (u_char) ((addr[3] >> 4) & 0x0f), (u_char) (addr[3] & 0x0f), - (u_char) ((addr[4] >> 4) & 0x0f), (u_char) (addr[4] & 0x0f), - (u_char) ((addr[5] >> 4) & 0x0f), (u_char) (addr[5] & 0x0f), - (u_char) ((addr[6] >> 4) & 0x0f), (u_char) (addr[6] & 0x0f), - (u_char) ((addr[7] >> 4) & 0x0f), (u_char) (addr[7] & 0x0f), - (u_char) ((addr[8] >> 4) & 0x0f), (u_char) (addr[8] & 0x0f), - (u_char) ((addr[9] >> 4) & 0x0f), (u_char) (addr[9] & 0x0f), - (u_char) ((addr[10] >> 4) & 0x0f), (u_char) (addr[10] & 0x0f), - (u_char) ((addr[11] >> 4) & 0x0f), (u_char) (addr[11] & 0x0f), - (u_char) ((addr[12] >> 4) & 0x0f), (u_char) (addr[12] & 0x0f), - (u_char) ((addr[13] >> 4) & 0x0f), (u_char) (addr[13] & 0x0f), - (u_char) ((addr[14] >> 4) & 0x0f), (u_char) (addr[14] & 0x0f), - (u_char) ((addr[15] >> 4) & 0x0f), (u_char) (addr[15] & 0x0f)); + + if (mlen >= sizeof(macro)) { + spf_done(spf, AR_PERMERROR, + "Macro expansions too large"); + return NULL; + } + + domain++; + if (isdigit(domain[0])) { + digits = strtol(domain, &endptr, 10); + if (digits < 1) { + spf_done(spf, AR_PERMERROR, + "digits in macro can't be 0"); + return NULL; + } + domain = endptr; + } + + if (domain[0] == 'r') { + domain++; + reverse = 1; + } + + for (; strchr(".-+,/_=", domain[0]) != NULL; domain++) { + if (strchr(delimiters, domain[0]) == NULL) { + delimiters[strlen(delimiters) + 1] = '\0'; + delimiters[strlen(delimiters)] = domain[0]; + } + } + + if (delimiters[0] == '\0') { + delimiters[0] = '.'; + delimiters[1] = '\0'; + } + + if (domain[0] != '}') { + spf_done(spf, AR_PERMERROR, + "Mallformed macro, expected end"); + return NULL; + } + + if (reverse) { + smacro[0] = '\0'; + tmp = macro + strlen(macro) - 1; + /* + * DIGIT rightmost elements after reversal is DIGIT + * lefmost elements before reversal + */ + while (1) { + while (tmp > macro && + strchr(delimiters, tmp[0]) == NULL) + tmp--; + if (tmp == macro) + break; + if (digits == 0) + break; + if (digits > 0) + digits--; + + tmp[0] = '\0'; + if (smacro[0] != '\0') + strlcat(smacro, ".", sizeof(smacro)); + strlcat(smacro, tmp + 1, sizeof(smacro)); + tmp--; + } + if (digits != 0) { + if (smacro[0] != '\0') + strlcat(smacro, ".", sizeof(smacro)); + strlcat(smacro, macro, sizeof(smacro)); + } } else { - spf_done(spf, SPF_PERMERROR, - "unsupported type of address"); + if (digits != -1) { + tmp = macro; + endptr = macro + strlen(macro); + while (digits > 0) { + while (tmp < endptr && + strchr(delimiters, tmp[0]) == NULL) + tmp++; + if (tmp == endptr) + break; + if (digits == 1) { + tmp[0] = '\0'; + break; + } + digits--; + tmp++; + } + } + strlcpy(smacro, macro, sizeof(smacro)); + } + + spec[i] = '\0'; + i = strlcat(spec, smacro, sizeof(spec)); + if (i >= sizeof(spec)) { + spf_done( + spf, AR_PERMERROR, "domain-spec too large"); return NULL; } break; @@ -1994,7 +2102,7 @@ spf_evaluate_domain(struct spf_record *spf, const char mlen = strlcpy(macro, "ip6", sizeof(macro)); else { - spf_done(spf, SPF_PERMERROR, + spf_done(spf, AR_PERMERROR, "unsupported type of address"); return NULL; } @@ -2005,13 +2113,13 @@ spf_evaluate_domain(struct spf_record *spf, const char sizeof(macro)); break; default: - spf_done(spf, SPF_PERMERROR, - "Unexpected macro in domain-spec"); + spf_done(spf, AR_PERMERROR, + "Mallformed macro, unexpected character after %"); return NULL; } if (mlen >= sizeof(macro)) { - spf_done(spf, SPF_PERMERROR, + spf_done(spf, AR_PERMERROR, "Macro expansions too large"); return NULL; } @@ -2020,7 +2128,7 @@ spf_evaluate_domain(struct spf_record *spf, const char if (isdigit(domain[0])) { digits = strtol(domain, &endptr, 10); if (digits < 1) { - spf_done(spf, SPF_PERMERROR, + spf_done(spf, AR_PERMERROR, "digits in macro can't be 0"); return NULL; } @@ -2045,7 +2153,7 @@ spf_evaluate_domain(struct spf_record *spf, const char } if (domain[0] != '}') { - spf_done(spf, SPF_PERMERROR, + spf_done(spf, AR_PERMERROR, "Mallformed macro, expected end"); return NULL; } @@ -2104,13 +2212,13 @@ spf_evaluate_domain(struct spf_record *spf, const char i = strlcat(spec, smacro, sizeof(spec)); if (i >= sizeof(spec)) { spf_done( - spf, SPF_PERMERROR, "domain-spec too large"); + spf, AR_PERMERROR, "domain-spec too large"); return NULL; } break; default: - spf_done(spf, SPF_PERMERROR, + spf_done(spf, AR_PERMERROR, "Mallformed macro, unexpected character after %"); return NULL; } @@ -2124,7 +2232,7 @@ spf_evaluate_domain(struct spf_record *spf, const char void spf_lookup_record(struct spf_record *spf, const char *domain, int type, - enum spf_state qualifier, int include, int exists) + enum ar_state qualifier, int include, int exists) { struct asr_query *aq; struct spf_query *query; @@ -2133,7 +2241,7 @@ spf_lookup_record(struct spf_record *spf, const char * return; if (spf->nqueries >= SPF_DNS_LOOKUP_LIMIT) { - spf_done(spf, SPF_PERMERROR, "To many DNS queries"); + spf_done(spf, AR_PERMERROR, "To many DNS queries"); return; } @@ -2150,7 +2258,7 @@ spf_lookup_record(struct spf_record *spf, const char * return; if (domain == NULL || !strlen(domain)) { - spf_done(spf, SPF_PERMERROR, "Empty domain"); + spf_done(spf, AR_PERMERROR, "Empty domain"); return; } @@ -2182,14 +2290,14 @@ spf_resolve(struct asr_result *ar, void *arg) if (ar->ar_h_errno == TRY_AGAIN || ar->ar_h_errno == NO_RECOVERY) { - spf_done(query->spf, SPF_TEMPERROR, hstrerror(ar->ar_h_errno)); + spf_done(query->spf, AR_TEMPERROR, hstrerror(ar->ar_h_errno)); goto end; } if (ar->ar_h_errno == HOST_NOT_FOUND) { if (query->include && !query->exists) spf_done(query->spf, - SPF_PERMERROR, hstrerror(ar->ar_h_errno)); + AR_PERMERROR, hstrerror(ar->ar_h_errno)); goto consume; } @@ -2200,7 +2308,7 @@ spf_resolve(struct asr_result *ar, void *arg) "Mallformed SPF DNS response for domain %s: %s", print_dname(q.q_dname, buf, sizeof(buf)), pack.err); - spf_done(query->spf, SPF_TEMPERROR, pack.err); + spf_done(query->spf, AR_TEMPERROR, pack.err); goto end; } @@ -2239,7 +2347,7 @@ spf_resolve(struct asr_result *ar, void *arg) osmtpd_warn(spf->ctx, "Unexpected SPF DNS record: %d for domain %s", rr.rr_type, query->domain); - spf_done(query->spf, SPF_TEMPERROR, "Unexpected record"); + spf_done(query->spf, AR_TEMPERROR, "Unexpected record"); break; } @@ -2261,7 +2369,7 @@ spf_resolve(struct asr_result *ar, void *arg) end: free(ar->ar_data); if (!spf->done && spf->running == 0) - spf_done(spf, SPF_NONE, NULL); + spf_done(spf, AR_NONE, NULL); } void @@ -2281,7 +2389,7 @@ spf_resolve_txt(struct dns_rr *rr, struct spf_query *q if (query->txt != NULL) { free(txt); - spf_done(query->spf, SPF_PERMERROR, "Duplicated SPF record"); + spf_done(query->spf, AR_PERMERROR, "Duplicated SPF record"); return; } @@ -2437,7 +2545,7 @@ spf_execute_txt(struct spf_query *query) char *end; int bits; - enum spf_state q = query->q; + enum ar_state q = query->q; while ((ap = strsep(&in, " ")) != NULL) { if (strcasecmp(ap, "v=spf1") == 0) @@ -2448,20 +2556,20 @@ spf_execute_txt(struct spf_query *query) *end = '\0'; if (*ap == '+') { - q = SPF_PASS; + q = AR_PASS; ap++; } else if (*ap == '-') { - q = SPF_FAIL; + q = AR_FAIL; ap++; } else if (*ap == '~') { - q = SPF_SOFTFAIL; + q = AR_SOFTFAIL; ap++; } else if (*ap == '?') { - q = SPF_NEUTRAL; + q = AR_NEUTRAL; ap++; } - if (q != SPF_PASS && query->include) + if (q != AR_PASS && query->include) continue; if (strncasecmp("all", ap, 3) == 0) { @@ -2539,7 +2647,7 @@ spf_execute_txt(struct spf_query *query) } void -spf_done(struct spf_record *spf, enum spf_state state, const char *reason) +spf_done(struct spf_record *spf, enum ar_state state, const char *reason) { int i; @@ -2560,28 +2668,6 @@ spf_done(struct spf_record *spf, enum spf_state state, spf->done = 1; osmtpd_filter_proceed(spf->ctx); -} - -const char * -spf_state2str(enum spf_state state) -{ - switch (state) - { - case SPF_NONE: - return "none"; - case SPF_NEUTRAL: - return "neutral"; - case SPF_PASS: - return "pass"; - case SPF_FAIL: - return "fail"; - case SPF_SOFTFAIL: - return "softfail"; - case SPF_TEMPERROR: - return "temperror"; - case SPF_PERMERROR: - return "permerror"; - } } int @@ -2599,7 +2685,7 @@ spf_ar_cat(const char *type, struct spf_record *spf, c if ((*aroff = auth_ar_cat(line, linelen, *aroff, - "; spf=%s", spf_state2str(spf->state)) + "; spf=%s", ar_state2str(spf->state)) ) == -1) { return -1; } @@ -2707,7 +2793,7 @@ auth_ar_create(struct osmtpd_ctx *ctx) } if ((aroff = auth_ar_cat(&line, &linelen, aroff, - "; iprev=%s", iprev_state2str(ses->iprev))) == -1) + "; iprev=%s", ar_state2str(ses->iprev))) == -1) osmtpd_err(1, "%s: malloc", __func__); if (spf_ar_cat("smtp.helo", ses->spf_helo,