/* * This program is free software; you can redistribute it and/or modify * it under the terms of either: * * a) The GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1, or (at your option) any * later version, * * OR * * b) The two-clause BSD license. * * These licenses can be found with the distribution in the file LICENSES */ #include "spf_sys_config.h" #ifdef STDC_HEADERS # include /* stdin / stdout */ # include /* malloc / free */ # include /* isupper / tolower */ #endif #ifdef HAVE_INTTYPES_H #include #endif #ifdef HAVE_NETDB_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STRING_H # include /* strstr / strdup */ #else # ifdef HAVE_STRINGS_H # include /* strstr / strdup */ # endif #endif #ifdef HAVE_NETDB_H # include #endif #ifndef HOST_NAME_MAX #define HOST_NAME_MAX 255 #endif #include "spf.h" #include "spf_response.h" #include "spf_record.h" #include "spf_server.h" #include "spf_dns.h" #include "spf_dns_resolv.h" #include "spf_dns_cache.h" #include "spf_dns_zone.h" #include "spf_internal.h" #include "spf_dns_internal.h" __attribute__((warn_unused_result)) static SPF_errcode_t SPF_server_set_rec_dom_ghbn(SPF_server_t *sp) { sp->rec_dom = malloc(HOST_NAME_MAX); if (! sp->rec_dom) return SPF_E_NO_MEMORY; #ifdef _WIN32 gethostnameFQDN(sp->rec_dom, HOST_NAME_MAX); return 0; /* XXX FIXME? */ #else if (gethostname(sp->rec_dom, HOST_NAME_MAX) < 0) /* XXX Error using strerror. */ return SPF_E_INTERNAL_ERROR; #endif return SPF_E_SUCCESS; } static void SPF_server_new_common_pre(SPF_server_t *sp, int debug) { SPF_errcode_t err; memset(sp, 0, sizeof(SPF_server_t)); sp->max_dns_mech = SPF_MAX_DNS_MECH; sp->max_dns_ptr = SPF_MAX_DNS_PTR; sp->max_dns_mx = SPF_MAX_DNS_MX; sp->debug = debug; err = SPF_server_set_rec_dom_ghbn(sp); if (err != SPF_E_SUCCESS) SPF_error("Failed to set rec_dom using gethostname()"); } static void SPF_server_new_common_post(SPF_server_t *sp) { SPF_response_t *spf_response; SPF_errcode_t err; spf_response = NULL; err = SPF_server_set_explanation(sp, SPF_DEFAULT_EXP, &spf_response); if (err != SPF_E_SUCCESS) SPF_errorf("Error code %d compiling default explanation", err); if (spf_response) { /* XXX Print the errors?! */ if (SPF_response_messages(spf_response) > 0) SPF_error("Response errors compiling default explanation"); SPF_response_free(spf_response); } spf_response = NULL; err = SPF_server_set_localpolicy(sp, "", 0, &spf_response); if (err != SPF_E_SUCCESS) SPF_errorf("Error code %d compiling default whitelist", err); if (spf_response) { /* XXX Print the errors?! */ if (SPF_response_messages(spf_response) > 0) SPF_error("Response errors compiling default whitelist"); SPF_response_free(spf_response); } } SPF_server_t * SPF_server_new(SPF_server_dnstype_t dnstype, int debug) { SPF_dns_server_t *dc_r; SPF_dns_server_t *dc_c; SPF_dns_server_t *dc_z; SPF_server_t *sp; sp = (SPF_server_t *)malloc(sizeof(SPF_server_t)); if (! sp) return sp; SPF_server_new_common_pre(sp, debug); sp->destroy_resolver = 1; switch (dnstype) { case SPF_DNS_RESOLV: dc_r = SPF_dns_resolv_new(NULL, NULL, debug); if (dc_r == NULL) SPF_error("Failed to create DNS resolver"); sp->resolver = dc_r; break; case SPF_DNS_CACHE: dc_r = SPF_dns_resolv_new(NULL, NULL, debug); if (dc_r == NULL) SPF_error("Failed to create DNS resolver"); dc_c = SPF_dns_cache_new(dc_r, NULL, debug, 8); if (dc_c == NULL) SPF_error("Failed to create DNS cache"); sp->resolver = dc_c; break; case SPF_DNS_ZONE: dc_z = SPF_dns_zone_new(NULL, NULL, debug); if (dc_z == NULL) SPF_error("Failed to create DNS zone"); sp->resolver = dc_z; break; default: SPF_errorf("Unknown DNS type %d", dnstype); } SPF_server_new_common_post(sp); return sp; } SPF_server_t * SPF_server_new_dns(SPF_dns_server_t *dns, int debug) { SPF_server_t *sp; sp = (SPF_server_t *)malloc(sizeof(SPF_server_t)); if (! sp) return sp; SPF_server_new_common_pre(sp, debug); sp->destroy_resolver = 0; sp->resolver = dns; SPF_server_new_common_post(sp); return sp; } /** * This function destroys the DNS layer as well. * If the (custom) DNS layer has no destructor, * then this cannot and does not destroy it. */ void SPF_server_free(SPF_server_t *sp) { if (sp->resolver && sp->destroy_resolver) SPF_dns_free(sp->resolver); if (sp->local_policy) SPF_record_free(sp->local_policy); if (sp->explanation) SPF_macro_free(sp->explanation); if (sp->rec_dom) free(sp->rec_dom); /* XXX TODO: Free other parts of the structure. */ free(sp); } SPF_errcode_t SPF_server_set_rec_dom(SPF_server_t *sp, const char *dom) { if (sp->rec_dom) free(sp->rec_dom); if (dom == NULL) return SPF_server_set_rec_dom_ghbn(sp); sp->rec_dom = strdup(dom); if (! sp->rec_dom) return SPF_E_NO_MEMORY; return SPF_E_SUCCESS; } SPF_errcode_t SPF_server_set_sanitize(SPF_server_t *sp, int sanitize) { sp->sanitize = sanitize; return SPF_E_SUCCESS; } SPF_errcode_t SPF_server_set_explanation(SPF_server_t *sp, const char *exp, SPF_response_t **spf_responsep) { SPF_macro_t *spf_macro = NULL; SPF_errcode_t err; SPF_ASSERT_NOTNULL(exp); /* This is a hackish way to get the errors. */ if (! *spf_responsep) { *spf_responsep = SPF_response_new(NULL); if (! *spf_responsep) return SPF_E_NO_MEMORY; } err = SPF_record_compile_macro(sp, *spf_responsep, &spf_macro, exp); if (err == SPF_E_SUCCESS) { if (sp->explanation) SPF_macro_free(sp->explanation); sp->explanation = spf_macro; } else { SPF_response_add_error(*spf_responsep, err, "Failed to compile explanation '%s'", exp); if (spf_macro) SPF_macro_free(spf_macro); } return err; } SPF_errcode_t SPF_server_set_localpolicy(SPF_server_t *sp, const char *policy, int use_default_whitelist, SPF_response_t **spf_responsep) { SPF_record_t *spf_record = NULL; SPF_errcode_t err; char *record; size_t len; SPF_ASSERT_NOTNULL(policy); /* This is a hackish way to get the errors. */ if (! *spf_responsep) { *spf_responsep = SPF_response_new(NULL); if (! *spf_responsep) return SPF_E_NO_MEMORY; } len = sizeof(SPF_VER_STR) + strlen(policy) + 20; if (use_default_whitelist) len += sizeof(SPF_DEFAULT_WHITELIST); record = malloc(len); if (! record) return SPF_E_NO_MEMORY; if (use_default_whitelist) snprintf(record, len, "%s %s %s", SPF_VER_STR, policy, SPF_DEFAULT_WHITELIST); else snprintf(record, len, "%s %s", SPF_VER_STR, policy); err = SPF_record_compile(sp, *spf_responsep, &spf_record, record); if (err == SPF_E_SUCCESS) { if (sp->local_policy) SPF_record_free(sp->local_policy); sp->local_policy = spf_record; } else { SPF_response_add_error(*spf_responsep, err, "Failed to compile local policy '%s'", policy); if (spf_record) SPF_record_free(spf_record); } free(record); return err; } SPF_errcode_t SPF_server_get_record(SPF_server_t *spf_server, SPF_request_t *spf_request, SPF_response_t *spf_response, SPF_record_t **spf_recordp) { SPF_dns_server_t *resolver; SPF_dns_rr_t *rr_txt; SPF_errcode_t err; SPF_dns_stat_t herrno; const char *domain; ns_type rr_type; int num_found; int idx_found; int i; SPF_ASSERT_NOTNULL(spf_server); SPF_ASSERT_NOTNULL(spf_request); SPF_ASSERT_NOTNULL(spf_server->resolver); SPF_ASSERT_NOTNULL(spf_recordp); domain = spf_request->cur_dom; SPF_ASSERT_NOTNULL(domain); *spf_recordp = NULL; resolver = spf_server->resolver; if (resolver->get_spf) return resolver->get_spf(spf_server, spf_request, spf_response, spf_recordp); /* I am VERY, VERY sorry about the gotos. Shevek. */ rr_type = ns_t_spf; retry: rr_txt = SPF_dns_lookup(resolver, domain, rr_type, TRUE); switch (rr_txt->herrno) { case HOST_NOT_FOUND: if (spf_server->debug > 0) SPF_debugf("get_record(%s): HOST_NOT_FOUND", domain); SPF_dns_rr_free(rr_txt); if (rr_type == ns_t_spf) { rr_type = ns_t_txt; goto retry; } spf_response->result = SPF_RESULT_NONE; spf_response->reason = SPF_REASON_FAILURE; return SPF_response_add_error(spf_response, SPF_E_NOT_SPF, "Host '%s' not found.", domain); // break; case NO_DATA: if (spf_server->debug > 0) SPF_debugf("get_record(%s): NO_DATA", domain); SPF_dns_rr_free(rr_txt); if (rr_type == ns_t_spf) { rr_type = ns_t_txt; goto retry; } spf_response->result = SPF_RESULT_NONE; spf_response->reason = SPF_REASON_FAILURE; return SPF_response_add_error(spf_response, SPF_E_NOT_SPF, "No DNS data for '%s'.", domain); // break; case TRY_AGAIN: if (spf_server->debug > 0) SPF_debugf("get_record(%s): TRY_AGAIN", domain); SPF_dns_rr_free(rr_txt); return SPF_response_add_error(spf_response, SPF_E_DNS_ERROR, "Temporary DNS failure for '%s'.", domain); // break; case NO_RECOVERY: if (spf_server->debug > 0) SPF_debugf("get_record(%s): NO_RECOERY", domain); SPF_dns_rr_free(rr_txt); return SPF_response_add_error(spf_response, SPF_E_DNS_ERROR, "Unrecoverable DNS failure for '%s'.", domain); // break; case NETDB_SUCCESS: if (spf_server->debug > 0) SPF_debugf("get_record(%s): NETDB_SUCCESS", domain); break; default: if (spf_server->debug > 0) SPF_debugf("get_record(%s): UNKNOWN_ERROR", domain); herrno = rr_txt->herrno; // Avoid use-after-free SPF_dns_rr_free(rr_txt); return SPF_response_add_error(spf_response, SPF_E_DNS_ERROR, "Unknown DNS failure for '%s': %d.", domain, herrno); // break; } if (rr_txt->num_rr == 0) { SPF_dns_rr_free(rr_txt); if (rr_type == ns_t_spf) { rr_type = ns_t_txt; goto retry; } return SPF_response_add_error(spf_response, SPF_E_NOT_SPF, "No TXT records returned from DNS lookup for '%s'", domain); } /* Actually, this could never be used uninitialised anyway. */ idx_found = 0; /* check for multiple SPF records */ num_found = 0; for (i = 0; i < rr_txt->num_rr; i++) { /* if (spf_server->debug > 1) SPF_debugf("Comparing '%s' with '%s'", SPF_VER_STR " ", rr_txt->rr[i]->txt); */ if (strncasecmp(rr_txt->rr[i]->txt, SPF_VER_STR, sizeof(SPF_VER_STR) - 1) == 0) { char e = rr_txt->rr[i]->txt[sizeof(SPF_VER_STR) - 1]; if (e == ' ' || e == '\0') { if (spf_server->debug > 0) SPF_debugf("found SPF record: %s", rr_txt->rr[i]->txt); num_found++; idx_found = i; } } } if (num_found == 0) { SPF_dns_rr_free(rr_txt); if (rr_type == ns_t_spf) { rr_type = ns_t_txt; goto retry; } spf_response->result = SPF_RESULT_NONE; spf_response->reason = SPF_REASON_FAILURE; return SPF_response_add_error(spf_response, SPF_E_NOT_SPF, "No SPF records for '%s'", domain); } if (num_found > 1) { SPF_dns_rr_free(rr_txt); // rfc4408 requires permerror here. /* XXX This could be refactored with SPF_i_done. */ spf_response->result = SPF_RESULT_PERMERROR; spf_response->reason = SPF_REASON_FAILURE; return SPF_response_add_error(spf_response, SPF_E_MULTIPLE_RECORDS, "Multiple SPF records for '%s'", domain); } /* try to compile the SPF record */ err = SPF_record_compile(spf_server, spf_response, spf_recordp, rr_txt->rr[idx_found]->txt ); SPF_dns_rr_free(rr_txt); /* FIXME: support multiple versions */ if (err != SPF_E_SUCCESS) return SPF_response_add_error(spf_response, SPF_E_NOT_SPF, "Failed to compile SPF record for '%s'", domain); return SPF_E_SUCCESS; } /** * Various accessors. * * The user is permitted to override the maximums. */ #define SPF_ACCESS_INT(f) \ SPF_errcode_t SPF_server_set_ ## f(SPF_server_t *s, int n) { \ s->f = n; return SPF_E_SUCCESS; \ } \ int SPF_server_get_ ## f(SPF_server_t *s) { \ return s->f; \ } /** * The return values from these getter functions are used without * modification. If you set a value higher than the specified * maximum, it will be used. Beware. */ SPF_ACCESS_INT(max_dns_mech); SPF_ACCESS_INT(max_dns_ptr); SPF_ACCESS_INT(max_dns_mx);