commit - c45570465634b62926ee7e196568b1c801c1d771
commit + 7e7046817041529117a0aa3b446e48c43a50619f
blob - c7af2ef576381355cc60edf461252b87b32a5771
blob + 33546d36fa9c8e45e9c30423b18e5cb446de5f59
--- main.c
+++ main.c
/*
* 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,
/* 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
};
/*
struct spf_record *spf;
struct event_asr *eva;
int type;
- enum spf_state q;
+ enum ar_state q;
int include;
int exists;
char *domain;
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;
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;
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 *);
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 *);
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));
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;
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;
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;
break;
case AR_PASS:
case AR_FAIL:
+ case AR_SOFTFAIL:
osmtpd_errx(1, "Unexpected transition");
case AR_POLICY:
if (state == AR_PASS)
return "pass";
case AR_FAIL:
return "fail";
+ case AR_SOFTFAIL:
+ return "softfail";
case AR_POLICY:
return "policy";
case AR_NEUTRAL:
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 *
int reverse;
if (domain == NULL || domain[0] == '\0') {
- spf_done(spf, SPF_PERMERROR, "Empty domain");
+ spf_done(spf, AR_PERMERROR, "Empty domain");
return NULL;
}
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;
}
case '-':
if (i + 3 >= sizeof(spec)) {
spf_done(
- spf, SPF_PERMERROR, "domain-spec too large");
+ spf, AR_PERMERROR, "domain-spec too large");
return NULL;
}
"%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;
mlen = strlcpy(macro, "ip6",
sizeof(macro));
else {
- spf_done(spf, SPF_PERMERROR,
+ spf_done(spf, AR_PERMERROR,
"unsupported type of address");
return NULL;
}
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;
}
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;
}
}
if (domain[0] != '}') {
- spf_done(spf, SPF_PERMERROR,
+ spf_done(spf, AR_PERMERROR,
"Mallformed macro, expected end");
return NULL;
}
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;
}
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;
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;
}
return;
if (domain == NULL || !strlen(domain)) {
- spf_done(spf, SPF_PERMERROR, "Empty domain");
+ spf_done(spf, AR_PERMERROR, "Empty domain");
return;
}
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;
}
"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;
}
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;
}
end:
free(ar->ar_data);
if (!spf->done && spf->running == 0)
- spf_done(spf, SPF_NONE, NULL);
+ spf_done(spf, AR_NONE, NULL);
}
void
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;
}
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)
*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) {
}
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;
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
if ((*aroff =
auth_ar_cat(line, linelen, *aroff,
- "; spf=%s", spf_state2str(spf->state))
+ "; spf=%s", ar_state2str(spf->state))
) == -1) {
return -1;
}
}
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,