static SPF_errcode_t SPF_i_set_header_comment(SPF_response_t *spf_response) { SPF_server_t *spf_server; SPF_request_t *spf_request; char *spf_source; size_t len; char ip4_buf[ INET_ADDRSTRLEN ]; char ip6_buf[ INET6_ADDRSTRLEN ]; const char *ip; char *buf; char *sender_dom; char *p, *p_end; SPF_ASSERT_NOTNULL(spf_response); spf_request = spf_response->spf_request; SPF_ASSERT_NOTNULL(spf_request); spf_server = spf_request->spf_server; SPF_ASSERT_NOTNULL(spf_server); if (spf_response->header_comment) free(spf_response->header_comment); spf_response->header_comment = NULL; /* Is this cur_dom? */ sender_dom = spf_request->env_from_dp; if (sender_dom == NULL) sender_dom = spf_request->helo_dom; if ( spf_response->reason == SPF_REASON_LOCAL_POLICY ) { spf_source = strdup( "local policy" ); } else if ( spf_response->reason == SPF_REASON_2MX ) { if ( spf_request->rcpt_to_dom == NULL || spf_request->rcpt_to_dom[0] == '\0' ) SPF_error( "RCPT TO domain is NULL" ); spf_source = strdup( spf_request->rcpt_to_dom ); } else if ( sender_dom == NULL ) { spf_source = strdup( "unknown domain" ); } else { len = strlen( sender_dom ) + sizeof( "domain of " ); spf_source = malloc( len ); if ( spf_source ) snprintf( spf_source, len, "domain of %s", sender_dom ); } if ( spf_source == NULL ) return SPF_E_INTERNAL_ERROR; ip = NULL; if ( spf_request->client_ver == AF_INET ) { ip = inet_ntop( AF_INET, &spf_request->ipv4, ip4_buf, sizeof( ip4_buf ) ); } else if (spf_request->client_ver == AF_INET6 ) { ip = inet_ntop( AF_INET6, &spf_request->ipv6, ip6_buf, sizeof( ip6_buf ) ); } if ( ip == NULL ) ip = "(unknown ip address)"; len = strlen( SPF_request_get_rec_dom(spf_request) ) + strlen( spf_source ) + strlen( ip ) + 80; buf = malloc( len ); if ( buf == NULL ) { free( spf_source ); return SPF_E_INTERNAL_ERROR; } p = buf; p_end = p + len; /* create the stock header comment */ p += snprintf( p, p_end - p, "%s: ", SPF_request_get_rec_dom(spf_request) ); switch(spf_response->result) { case SPF_RESULT_PASS: if ( spf_response->reason == SPF_REASON_LOCALHOST ) snprintf( p, p_end - p, "localhost is always allowed." ); else if ( spf_response->reason == SPF_REASON_2MX ) snprintf( p, p_end - p, "message received from %s which is an MX secondary for %s.", ip, spf_source ); else snprintf( p, p_end - p, "%s designates %s as permitted sender", spf_source, ip ); break; case SPF_RESULT_FAIL: snprintf( p, p_end - p, "%s does not designate %s as permitted sender", spf_source, ip ); break; case SPF_RESULT_SOFTFAIL: snprintf( p, p_end - p, "transitioning %s does not designate %s as permitted sender", spf_source, ip ); break; case SPF_RESULT_PERMERROR: snprintf(p, p_end - p, "error in processing during lookup of %s: %s", spf_source, SPF_strerror(spf_response->err)); break; case SPF_RESULT_NEUTRAL: snprintf(p, p_end - p, "%s is neither permitted nor denied by %s", ip, spf_source); break; case SPF_RESULT_NONE: snprintf(p, p_end - p, "%s does not provide an SPF record", spf_source); break; case SPF_RESULT_TEMPERROR: snprintf(p, p_end - p, "encountered temporary error during SPF processing of %s", spf_source ); break; default: snprintf( p, p_end - p, "error: unknown SPF result %d encountered while checking %s for %s", spf_response->result, ip, spf_source ); break; } if (spf_source) free(spf_source); spf_response->header_comment = SPF_sanitize(spf_server, buf); return SPF_E_SUCCESS; }
/** * This could better collect errors, like the compiler does. * This requires that *bufp be either malloced to *buflenp, or NULL * This may realloc *bufp. */ SPF_errcode_t SPF_record_expand_data(SPF_server_t *spf_server, SPF_request_t *spf_request, SPF_response_t *spf_response, SPF_data_t *data, size_t data_len, char **bufp, size_t *buflenp) { SPF_data_t *d, *data_end; size_t len; const char *p_err; // XXX Check this value, when returned. char *p, *p_end; const char *p_read; const char *p_read_end; char *p_write; char *p2, *p2_end; const char *var; char *munged_var = NULL; char *url_var = NULL; /* Pretty-printing buffers. */ char ip4_buf[ INET_ADDRSTRLEN ]; char ip6_buf[ INET6_ADDRSTRLEN ]; /* Hex buffer for ipv6 (size in nibbles) */ char ip6_rbuf[ sizeof( struct in6_addr ) * 4 + 1 ]; char time_buf[ sizeof( "4294967296" ) ]; /* 2^32 seconds max */ int num_found; int i; size_t buflen; int compute_length; SPF_errcode_t err; /* * make sure we were passed valid data to work with */ SPF_ASSERT_NOTNULL(spf_server); SPF_ASSERT_NOTNULL(data); SPF_ASSERT_NOTNULL(bufp); SPF_ASSERT_NOTNULL(buflenp); buflen = 1; /* For the terminating '\0' */ compute_length = 1; p = NULL; p_end = NULL; /* data_end = SPF_mech_end_data( mech ); */ /* doesn't work for mods */ data_end = (SPF_data_t *)((char *)data + data_len); top: #ifdef DEBUG fprintf(stderr, "Pass start compute_length=%d\n", compute_length); #endif /* * expand the data */ for (d = data; d < data_end; d = SPF_data_next(d)) { #ifdef DEBUG fprintf(stderr, " Item type=%d at %p\n", d->dc.parm_type, d); #endif if (d->dc.parm_type == PARM_CIDR) continue; if (d->ds.parm_type == PARM_STRING) { if (compute_length) { buflen += d->ds.len; continue; } /* This should NEVER happen now. */ if (p_end - (p + d->ds.len) <= 0) SPF_error("Failed to allocate enough memory " "to expand string."); memcpy(p, SPF_data_str(d), d->ds.len); p += d->ds.len; continue; } /* Otherwise, it's a variable. */ var = NULL; switch (d->dv.parm_type) { case PARM_LP_FROM: /* local-part of envelope-sender */ var = spf_request->env_from_lp; break; case PARM_ENV_FROM: /* envelope-sender */ var = spf_request->env_from; break; case PARM_DP_FROM: /* envelope-domain */ var = spf_request->env_from_dp; break; case PARM_CUR_DOM: /* current-domain */ var = spf_request->cur_dom; break; case PARM_CLIENT_IP: /* SMTP client IP */ if (compute_length) { len = sizeof(ip6_buf); if (d->dv.url_encode) len *= 3; buflen += len; continue; } if (spf_request->client_ver == AF_INET) { p_err = inet_ntop(AF_INET, &spf_request->ipv4, ip4_buf, sizeof(ip4_buf)); var = ip4_buf; } else if (spf_request->client_ver == AF_INET6) { p2 = ip6_rbuf; p2_end = p2 + sizeof(ip6_rbuf); for (i = 0; i < array_elem(spf_request->ipv6.s6_addr); i++) { p2 += snprintf(p2, p2_end - p2, "%.1x.%.1x.", spf_request->ipv6.s6_addr[i] >> 4, spf_request->ipv6.s6_addr[i] & 0xf); } /* squash the final '.' */ ip6_rbuf[sizeof(struct in6_addr) * 4 - 1] = '\0'; var = ip6_rbuf; } break; case PARM_CLIENT_IP_P: /* SMTP client IP (pretty) */ if (compute_length) { len = sizeof(ip6_buf); if (d->dv.url_encode) len *= 3; buflen += len; continue; } if (spf_request->client_ver == AF_INET) { p_err = inet_ntop(AF_INET, &spf_request->ipv4, ip4_buf, sizeof(ip4_buf)); var = ip4_buf; } else if (spf_request->client_ver == AF_INET6) { p_err = inet_ntop(AF_INET6, &spf_request->ipv6, ip6_buf, sizeof(ip6_buf)); var = ip6_buf; } break; case PARM_TIME: /* time in UTC epoch secs */ if (compute_length) { len = sizeof(time_buf); /* This never gets bigger using URL encoding. */ buflen += len; continue; } snprintf(time_buf, sizeof(time_buf), "%ld", (long)time(NULL)); var = time_buf; break; case PARM_CLIENT_DOM: /* SMTP client domain name */ var = SPF_request_get_client_dom(spf_request); if (! var) return SPF_E_NO_MEMORY; break; case PARM_CLIENT_VER: /* IP ver str - in-addr/ip6 */ if (spf_request->client_ver == AF_INET) var = client_ver_ipv4; else if (spf_request->client_ver == AF_INET6) var = client_ver_ipv6; break; case PARM_HELO_DOM: /* HELO/EHLO domain */ var = spf_request->helo_dom; break; case PARM_REC_DOM: /* receiving domain */ var = SPF_request_get_rec_dom(spf_request); break; default: #ifdef DEBUG fprintf(stderr, "Invalid variable %d\n", d->dv.parm_type); #endif return SPF_E_INVALID_VAR; break; } if (var == NULL) return SPF_E_UNINIT_VAR; len = strlen(var); if (compute_length) { if (d->dv.url_encode) len *= 3; buflen += len; continue; } /* Now we put 'var' through the munging procedure. */ munged_var = (char *)malloc(len + 1); if (munged_var == NULL) return SPF_E_NO_MEMORY; memset(munged_var, 0, len + 1); p_read_end = var + len; p_write = munged_var; /* reverse */ /* The following code confuses both me and Coverity. Shevek. */ if (d->dv.rev) { p_read = p_read_end - 1; while ( p_read >= var ) { if ( SPF_delim_valid(d, *p_read) ) { /* Subtract 1 because p_read points to delim, and * p_read_end points to the following delim. */ len = p_read_end - p_read - 1; memcpy( p_write, p_read + 1, len ); p_write += len; *p_write++ = '.'; p_read_end = p_read; } p_read--; } /* Now p_read_end should point one before the start of the * string. p_read_end might also point there if the string * starts with a delimiter. */ if (p_read_end >= p_read) { len = p_read_end - p_read - 1; memcpy( p_write, p_read + 1, len ); p_write += len; *p_write++ = '.'; } /* p_write always points to the 'next' character. */ p_write--; *p_write = '\0'; } else { p_read = var; while (p_read < p_read_end) { if (SPF_delim_valid(d, *p_read)) *p_write++ = '.'; else *p_write++ = *p_read; p_read++; } *p_write = '\0'; } /* Now munged_var is a copy of var, possibly reversed, and * thus len == strlen(munged_var). However, we continue to * manipulate the underlying munged_var since var is const. */ /* truncate, from the right hand side. */ if (d->dv.num_rhs > 0) { p_read_end = munged_var + len; /* const, at '\0' */ p_write = munged_var + len - 1; num_found = 0; while (p_write > munged_var) { if (*p_write == '.') num_found++; if (num_found == d->dv.num_rhs) break; p_write--; } p_write++; /* Move to just after the '.' */ /* This moves the '\0' as well. */ len = p_read_end - p_write; memmove(munged_var, p_write, len + 1); } var = munged_var; /* Now, we have 'var', of length 'len' */ /* URL encode */ if (d->dv.url_encode) { url_var = malloc(len * 3 + 1); if (url_var == NULL) { if (munged_var) free(munged_var); return SPF_E_NO_MEMORY; } p_read = var; p_write = url_var; /* escape non-uric characters (rfc2396) */ while ( *p_read != '\0' ) { if ( isalnum( (unsigned char)( *p_read ) ) ) *p_write++ = *p_read++; else { switch( *p_read ) { case '-': case '_': case '.': case '!': case '~': case '*': case '\'': case '(': case ')': *p_write++ = *p_read++; break; default: /* No point doing snprintf with a const '4' * because we know we're going to get 4 * characters anyway. */ sprintf( p_write, "%%%02x", *p_read ); p_write += 3; p_read++; break; } } } *p_write = '\0'; var = url_var; len = p_write - url_var; /* Not actually used. */ } /* finish up */ len = snprintf(p, p_end - p, "%s", var); p += len; if (p_end - p <= 0) { if (munged_var) free(munged_var); if (url_var) free(url_var); return SPF_E_INTERNAL_ERROR; } if (munged_var) free(munged_var); munged_var = NULL; if (url_var) free(url_var); url_var = NULL; }
static int verify_data(spctx_t* ctx) { char ebuf[256]; int n; SPF_request_t *spf_request = NULL; char * xforwardaddr = NULL; char * xforwardhelo = NULL; if(spf_server == NULL) { /* redirect errors */ SPF_error_handler = SPF_error_syslog; SPF_warning_handler = SPF_warning_syslog; SPF_info_handler = SPF_info_syslog; SPF_debug_handler = SPF_debug_syslog; spf_server = SPF_server_new(SPF_DNS_CACHE, 1); if (spf_server == NULL) return -1; } /* trim string */ if(ctx->xforwardaddr) xforwardaddr = trim_space(ctx->xforwardaddr); if(ctx->xforwardhelo) xforwardhelo = trim_space(ctx->xforwardhelo); sp_messagex(ctx, LOG_DEBUG, "New connection: ADDR %s - MAIL FROM %s - XF-ADDR %s - XF-HELO %s", ctx->client.peername, ctx->sender, xforwardaddr, xforwardhelo); spf_request = SPF_request_new(spf_server); if( xforwardaddr ) SPF_request_set_ipv4_str( spf_request, xforwardaddr ); else if ( ctx->client.peername ) SPF_request_set_ipv4_str( spf_request, ctx->client.peername ); if( xforwardhelo ) SPF_request_set_helo_dom( spf_request, xforwardhelo ); if( ctx->sender ) SPF_request_set_env_from( spf_request, ctx->sender ); SPF_response_t *spf_response = NULL; SPF_request_query_mailfrom(spf_request, &spf_response); char hostname[100]; strncpy(hostname, SPF_request_get_rec_dom(spf_request), 99); char *result_spf = NULL; switch(SPF_response_result(spf_response)) { case SPF_RESULT_NONE: sp_messagex(ctx, LOG_DEBUG, "No SPF policy found for %s", ctx->sender); result_spf = "none"; break; case SPF_RESULT_NEUTRAL: result_spf = "neutral"; sp_messagex(ctx, LOG_DEBUG, "SPF: NEUTRAL for %s", ctx->sender); break; case SPF_RESULT_SOFTFAIL: result_spf = "softfail"; sp_messagex(ctx, LOG_DEBUG, "SPF: SOFTFAIL for %s", ctx->sender); break; case SPF_RESULT_PASS: result_spf = "pass"; sp_messagex(ctx, LOG_DEBUG, "SPF: PASS for %s", ctx->sender); break; case SPF_RESULT_FAIL: buffer_reject_message("550 SPF Reject", ebuf, sizeof(ebuf)); buffer_reject_message(SPF_response_get_smtp_comment(spf_response), ebuf, sizeof(ebuf)); final_reject_message(ebuf, sizeof(ebuf)); sp_messagex(ctx, LOG_DEBUG, "SPF FAIL for %s, ignore message", ctx->sender); SPF_response_free(spf_response); SPF_request_free(spf_request); if(sp_fail_data(ctx, ebuf) == -1) return -1; else return 0; break; case SPF_RESULT_TEMPERROR: case SPF_RESULT_PERMERROR: case SPF_RESULT_INVALID: buffer_reject_message("450 temporary failure", ebuf, sizeof(ebuf)); final_reject_message(ebuf, sizeof(ebuf)); sp_messagex(ctx, LOG_DEBUG, "TEMP ERROR or INVALID RECORD in SPF for %s", ctx->sender); SPF_response_free(spf_response); SPF_request_free(spf_request); if(sp_fail_data(ctx, ebuf) == -1) return -1; else return 0; break; }; char auth_result_spf[1025]; snprintf(auth_result_spf, 1024, "spf=%s smtp.mailfrom=%s", result_spf, ctx->sender); SPF_response_free(spf_response); SPF_request_free(spf_request); /* Tell client to start sending data */ if(sp_start_data (ctx) < 0) return -1; /* Message already printed */ /* Setup DKIM verifier */ DKIMContext ctxt; DKIMVerifyOptions vopts = {0}; vopts.nCheckPractices = 1; vopts.pfnSelectorCallback = NULL; //SelectorCallback; n = DKIMVerifyInit( &ctxt, &vopts ); /* Read data into verifier */ int len = -1; const char *buffer = 0; do { len = sp_read_data(ctx, &buffer); if(len == -1) return -1; if(len > 0) { DKIMVerifyProcess( &ctxt, buffer, len ); sp_write_data( ctx, buffer, len ); } } while(len > 0); sp_write_data( ctx, NULL, 0 ); /* Verify DKIM */ n = DKIMVerifyResults( &ctxt ); /* Get verification details */ int nSigCount = 0; DKIMVerifyDetails* pDetails; char szPolicy[512]; DKIMVerifyGetDetails(&ctxt, &nSigCount, &pDetails, szPolicy ); /* Proxy based on verification results */ char auth_result_dkim[1025]; if(nSigCount == 0) { sp_messagex(ctx, LOG_DEBUG, "No DKIM signature, passthrough"); snprintf(auth_result_dkim, 1024, "dkim=none"); } else if (n == DKIM_SUCCESS || n == DKIM_PARTIAL_SUCCESS) { sp_messagex(ctx, LOG_DEBUG, "DKIM verification: Success, adding header information"); int strpos = 0; int i=0; for(; i<nSigCount; ++i) { snprintf(&auth_result_dkim[strpos], 1024 - strpos, "%sdkim=%s header.d=%s", (i>0 ? ";\n" : ""), (pDetails[i].nResult == DKIM_SUCCESS ? "pass" : "fail"), pDetails[i].szSignatureDomain); strpos = strlen(auth_result_dkim); } } else { sp_messagex(ctx, LOG_DEBUG, "DKIM verification: Failed, report error and ignore message."); buffer_reject_message("550 DKIM Signature failed verification (http://www.dkim.org/info/dkim-faq.html)", ebuf, sizeof(ebuf)); final_reject_message(ebuf, sizeof(ebuf)); DKIMVerifyFree( &ctxt ); if(sp_fail_data(ctx, ebuf) == -1) return -1; else return 0; } DKIMVerifyFree( &ctxt ); char auth_results_header[1025]; snprintf(auth_results_header, 1024, "Authentication-Results: %s;\n %s;\n %s;", hostname, auth_result_spf, auth_result_dkim); if( sp_done_data(ctx, auth_results_header) == -1) return -1; return 0; }