#ifdef HAVE_CONFIG_H # include "config.h" #else /* Let's take a wild guess */ # define STDC_HEADERS # define HAVE_SYS_SOCKET_H # define HAVE_SYS_TIME_H # define HAVE_UNISTD_H # define HAVE_ERRNO_H # define HAVE_CTYPE_H # define HAVE_SYS_SOCKET_H # define HAVE_NETINET_IN_H # define HAVE_ARPA_INET_H #endif #ifdef STDC_HEADERS #include #include #include #endif #ifdef HAVE_SYSLOG_H #include #endif #ifdef HAVE_CTYPE_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif #include #include #include #define POSTFIX_DUNNO "DUNNO" #define POSTFIX_REJECT "REJECT" typedef struct _config_t { char *localpolicy; char *explanation; int trustedforwarder; int softfailreject; int debug; } config_t; #define RESULTSIZE 1024 typedef struct _request_t { char *helo_address; char *sender_address; char *client_ip; struct in_addr client_in_addr; char result[RESULTSIZE]; } request_t; static const char *argv0; static SPF_config_t spfc_global; static SPF_c_results_t spfr_localpolicy; static SPF_c_results_t spfr_explanation; static SPF_dns_config_t spfdcid_resolv; static SPF_dns_config_t spfdcid_cache; #define DIE() do{fprintf(stderr, "Exiting.\n");exit(255);}while(0) #ifdef HAVE_GETOPT_LONG static const struct option longopts[] = { { "localpolicy", required_argument, NULL, 'l', }, { "trustedforwarder", no_argument, NULL, 't', }, { "softfailreject", no_argument, NULL, 's', }, { "explanation", required_argument, NULL, 'x', }, { "debug", optional_argument, NULL, 'd', }, { "help", no_argument, NULL, 'h', }, { 0, 0, 0, 0 }, }; int idx; #define DOC_LONGOPT(l, v, t, p1) do { \ fprintf(stderr, " --%s%c%s%*s" t "\n", \ l, (v ? '=' : ' '), (v ? v : ""), p1, ""); \ } while(0) #else #define DOC_LONGOPT(l, v, t, p1) do { } while(0) #endif static const char *shortopts = "l:x:d:tsh"; #define DOC_OPT(s, l, v, t, p0, p1) do { \ fprintf(stderr, " -%c%c%s%*s" t "\n", \ s, (v ? ' ' : ' '), (v ? v : ""), p0, ""); \ DOC_LONGOPT(l, v, t, p1); \ } while(0) static void usage(char *name) { fprintf(stderr, "Postfix SPF Policy Daemon for libspf2: " "http://www.libspf2.org/\n"); fprintf(stderr, "Usage: %s [options]\n", name); DOC_OPT('l', "localpolicy", "", "Set the SPF local policy.", 21, 10); DOC_OPT('t', "trustedforwarder", NULL, "Use the trusted-forwarder.com whitelist.", 29, 13); DOC_OPT('s', "softfailreject", NULL, "Reject SOFTFAIL.", 29, 15); DOC_OPT('x', "explanation", "", "Set the SPF explanation.", 16, 5); DOC_OPT('d', "debug", "", "Set the debug level.", 22, 17); DOC_OPT('h', "help", NULL, "Display this help.", 29, 25); } static void init_libspf2(config_t *conf) { spfc_global = SPF_create_config(); if (spfc_global == (SPF_config_t)0) { fprintf(stderr, "%s: SPF_create_config() failed\n", argv0); exit(1); } SPF_set_debug(spfc_global, conf->debug); if (conf->localpolicy != NULL) { SPF_init_c_results(&spfr_localpolicy); if (SPF_compile_local_policy(spfc_global, conf->localpolicy, conf->trustedforwarder, &spfr_localpolicy)) { fprintf(stderr, "%s: SPF_compile_local_policy failed: %s\n", argv0, spfr_localpolicy.err_msg); exit(1); } SPF_set_local_policy(spfc_global, spfr_localpolicy); } if (conf->explanation != NULL) { SPF_init_c_results(&spfr_explanation); if (SPF_compile_exp(spfc_global, conf->explanation, &spfr_explanation)) { fprintf(stderr, "%s: SPF_compile_exp failed: %s\n", argv0, spfr_explanation.err_msg); exit(1); } SPF_set_exp(spfc_global, spfr_explanation); } spfdcid_resolv = SPF_dns_create_config_resolv((SPF_dns_config_t)0, conf->debug); if (spfdcid_resolv == NULL) { fprintf(stderr, "%s: SPF_dns_create_config_resolv failed\n", argv0); exit(1); } spfdcid_cache = SPF_dns_create_config_cache(spfdcid_resolv, 8, conf->debug); if (spfdcid_cache == NULL) { fprintf(stderr, "%s: SPF_dns_create_config_cache failed\n", argv0); exit(1); } } static void fini_libspf2(config_t *conf) { SPF_dns_destroy_config_cache(spfdcid_cache); SPF_dns_destroy_config_resolv(spfdcid_resolv); if (conf->localpolicy != NULL) SPF_free_c_results(&spfr_localpolicy); if (conf->explanation != NULL) SPF_free_c_results(&spfr_explanation); SPF_destroy_config(spfc_global); SPF_destroy_default_config(); } static void read_request(config_t *conf, request_t *req) { char line[BUFSIZ]; while (fgets(line, BUFSIZ, stdin) != NULL) { line[strcspn(line, "\r\n")] = '\0'; if (conf->debug) syslog(LOG_INFO, "--> %s", line); /* DBG */ switch (line[0]) { case '\0': return; case 'c': if (strncasecmp(line, "client_address=", 15) == 0) { req->client_ip = strdup(&line[15]); if (conf->debug) syslog(LOG_INFO, "[ip %s]", req->client_ip); /* DBG */ continue; } break; case 's': if (strncasecmp(line, "sender=", 7) == 0) { req->sender_address = strdup(&line[7]); if (conf->debug) syslog(LOG_INFO, "[sender %s]", req->sender_address); /* DBG */ continue; } break; case 'h': if (strncasecmp(line, "helo_name=", 10) == 0) { req->helo_address = strdup(&line[10]); if (conf->debug) syslog(LOG_INFO, "[helo %s]", req->helo_address); /* DBG */ continue; } break; } /* Ignore line. */ } } static void process_request(request_t *req, config_t *conf) { SPF_output_t output; /* XXX Add support for inet6 */ if (!inet_aton(req->client_ip, &req->client_in_addr)) { syslog(LOG_ERR, "Invalid IP address %s", req->client_ip); sprintf(req->result, POSTFIX_DUNNO); return; } SPF_set_ipv4(spfc_global, req->client_in_addr); SPF_set_helo_dom(spfc_global, req->helo_address); SPF_set_env_from(spfc_global, req->sender_address); output = SPF_result(spfc_global, spfdcid_cache); switch (output.result) { case SPF_RESULT_PASS: strcpy(req->result, POSTFIX_DUNNO); break; case SPF_RESULT_FAIL: snprintf(req->result, RESULTSIZE, POSTFIX_REJECT " %s", (output.smtp_comment ? output.smtp_comment : (output.header_comment ? output.header_comment : ""))); break; case SPF_RESULT_ERROR: snprintf(req->result, RESULTSIZE, "450 temporary failure: %s", (output.smtp_comment ? output.smtp_comment : "")); break; case SPF_RESULT_SOFTFAIL: if (conf->softfailreject == 1) { snprintf(req->result, RESULTSIZE, POSTFIX_REJECT " %s", (output.smtp_comment ? output.smtp_comment : (output.header_comment ? output.header_comment : ""))); break; } case SPF_RESULT_NEUTRAL: case SPF_RESULT_UNKNOWN: case SPF_RESULT_NONE: default: strcpy(req->result, POSTFIX_DUNNO); break; } SPF_free_output(&output); } int main(int argc, char **argv) { config_t config; request_t req; char c; memset(&config, 0, sizeof(config)); /* Figure out our name */ argv0 = strrchr(argv[0], '/'); if (argv0 != NULL) ++argv0; else argv0 = argv[0]; config.localpolicy = NULL; config.explanation = NULL; config.trustedforwarder = 0; config.softfailreject = 0; config.debug = 0; while ((c = #ifdef HAVE_GETOPT_LONG getopt_long(argc, argv, shortopts, longopts, &idx) #else getopt(argc, argv, shortopts) #endif ) != -1) { switch (c) { case 'l': config.localpolicy = optarg; break; case 't': config.trustedforwarder = 1; break; case 's': config.softfailreject = 1; break; case 'x': config.explanation = optarg; break; case 'd': if (optarg) config.debug = atoi(optarg); else config.debug = 1; break; case 'h': case '?': usage(argv[0]); DIE(); default: fprintf(stderr, "Unrecognised option %c\n", c); DIE(); } } argv += optind; argc -= optind; init_libspf2(&config); openlog(argv0, 0, LOG_MAIL); for (;;) { memset(&req, 0, sizeof(req)); read_request(&config, &req); if (feof(stdin)) break; #define CHECK(x, y) if (!(x)) { \ syslog(LOG_ERR, "No parameter " y); \ strcpy(req.result, POSTFIX_DUNNO); \ } CHECK(req.client_ip, "client_address") else CHECK(req.sender_address, "sender") else CHECK(req.helo_address, "helo_name") else process_request(&req, &config); req.result[RESULTSIZE - 1] = '\0'; printf("action=%s\n\n", req.result); fflush(stdout); #define FREE(x) do { if ((x)) free((x)); } while(0) FREE(req.client_ip); FREE(req.sender_address); FREE(req.helo_address); } closelog(); fini_libspf2(&config); return 0; }