libspf2
1.2.10
|
00001 /* 00002 * spfquery - Sender Policy Framwork command line utility 00003 * 00004 * Author: Wayne Schlitt <wayne@midwestcs.com> 00005 * 00006 * File: spfquery.c 00007 * Desc: SPF command line utility 00008 * 00009 * 00010 * This program is free software; you can redistribute it and/or modify 00011 * it under the terms of either: 00012 * 00013 * a) The GNU Lesser General Public License as published by the Free 00014 * Software Foundation; either version 2.1, or (at your option) any 00015 * later version, 00016 * 00017 * OR 00018 * 00019 * b) The two-clause BSD license. 00020 * 00021 * 00022 * The two-clause BSD license: 00023 * 00024 * 00025 * Redistribution and use in source and binary forms, with or without 00026 * modification, are permitted provided that the following conditions 00027 * are met: 00028 * 00029 * 1. Redistributions of source code must retain the above copyright 00030 * notice, this list of conditions and the following disclaimer. 00031 * 2. Redistributions in binary form must reproduce the above copyright 00032 * notice, this list of conditions and the following disclaimer in the 00033 * documentation and/or other materials provided with the distribution. 00034 * 00035 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 00036 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00037 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 00038 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 00039 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 00040 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 00041 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 00042 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00043 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 00044 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00045 */ 00046 00047 #define SPF_TEST_VERSION "3.0" 00048 00049 #ifdef HAVE_CONFIG_H 00050 # include "config.h" 00051 #endif 00052 00053 #ifdef STDC_HEADERS 00054 # include <stdio.h> 00055 # include <stdlib.h> /* malloc / free */ 00056 #endif 00057 00058 #ifdef HAVE_SYS_TYPES_H 00059 #include <sys/types.h> /* types (u_char .. etc..) */ 00060 #endif 00061 00062 #ifdef HAVE_INTTYPES_H 00063 #include <inttypes.h> 00064 #endif 00065 00066 #ifdef HAVE_STRING_H 00067 # include <string.h> /* strstr / strdup */ 00068 #else 00069 # ifdef HAVE_STRINGS_H 00070 # include <strings.h> /* strstr / strdup */ 00071 # endif 00072 #endif 00073 00074 #ifdef HAVE_SYS_SOCKET_H 00075 # include <sys/socket.h> /* inet_ functions / structs */ 00076 #endif 00077 #ifdef HAVE_NETINET_IN_H 00078 # include <netinet/in.h> /* inet_ functions / structs */ 00079 #endif 00080 00081 #ifdef HAVE_ARPA_NAMESER_H 00082 # include <arpa/nameser.h> /* DNS HEADER struct */ 00083 #endif 00084 00085 #ifdef HAVE_ARPA_INET_H 00086 # include <arpa/inet.h> /* in_addr struct */ 00087 #endif 00088 00089 #define _GNU_SOURCE 00090 #include <getopt.h> 00091 00092 #ifdef _WIN32 00093 #include "spf_win32.h" 00094 #endif 00095 00096 #include "spf.h" 00097 #include "spf_dns.h" 00098 #include "spf_dns_null.h" 00099 #include "spf_dns_test.h" 00100 #include "spf_dns_cache.h" 00101 #ifndef _WIN32 00102 #include "spf_dns_resolv.h" 00103 #else 00104 #include "spf_dns_windns.h" 00105 #endif 00106 00107 00108 00109 #define TRUE 1 00110 #define FALSE 0 00111 00112 #define FREE(x, f) do { if ((x)) (f)((x)); (x) = NULL; } while(0) 00113 #define FREE_REQUEST(x) FREE((x), SPF_request_free) 00114 #define FREE_RESPONSE(x) FREE((x), SPF_response_free) 00115 00116 #define CONTINUE_ERROR do { res = 255; continue; } while(0) 00117 #define WARN_ERROR do { res = 255; } while(0) 00118 #define FAIL_ERROR do { res = 255; goto error; } while(0) 00119 00120 #define RESIZE_RESULT(n) do { \ 00121 if (result == NULL) { \ 00122 result_len = 256 + n; \ 00123 result = malloc(result_len); \ 00124 result[0] = '\0'; \ 00125 } \ 00126 else if (strlen(result) + n >= result_len) { \ 00127 result_len = result_len + (result_len >> 1) + 8 + n; \ 00128 result = realloc(result, result_len); \ 00129 } \ 00130 } while(0) 00131 #define APPEND_RESULT(n) do { \ 00132 partial_result = SPF_strresult(n); \ 00133 RESIZE_RESULT(strlen(partial_result)); \ 00134 strcat(result, partial_result); \ 00135 } while(0) 00136 00137 #define X_OR_EMPTY(x) ((x) ? (x) : "") 00138 00139 static struct option long_options[] = { 00140 {"file", 1, 0, 'f'}, 00141 00142 {"ip", 1, 0, 'i'}, 00143 {"sender", 1, 0, 's'}, 00144 {"helo", 1, 0, 'h'}, 00145 {"rcpt-to", 1, 0, 'r'}, 00146 00147 {"debug", 2, 0, 'd'}, 00148 {"local", 1, 0, 'l'}, 00149 {"trusted", 1, 0, 't'}, 00150 {"guess", 1, 0, 'g'}, 00151 {"default-explanation", 1, 0, 'e'}, 00152 {"max-lookup", 1, 0, 'm'}, 00153 {"sanitize", 1, 0, 'c'}, 00154 {"name", 1, 0, 'n'}, 00155 {"override", 1, 0, 'a'}, 00156 {"fallback", 1, 0, 'z'}, 00157 00158 {"keep-comments", 0, 0, 'k'}, 00159 {"version", 0, 0, 'v'}, 00160 {"help", 0, 0, '?'}, 00161 00162 {0, 0, 0, 0} 00163 }; 00164 00165 static void 00166 unimplemented(const char flag) 00167 { 00168 struct option *opt; 00169 int i; 00170 00171 for (i = 0; (opt = &long_options[i])->name; i++) { 00172 if (flag == opt->val) { 00173 fprintf(stderr, "Unimplemented option: -%s or -%c\n", 00174 opt->name, flag); 00175 return; 00176 } 00177 } 00178 00179 fprintf(stderr, "Unimplemented option: -%c\n", flag); 00180 } 00181 00182 00183 static void 00184 usage() 00185 { 00186 fprintf( 00187 stderr, 00188 "Usage:\n" 00189 "\n" 00190 "spfquery [control options | data options] ...\n" 00191 "\n" 00192 "Use the -help option for more information\n" 00193 ); 00194 } 00195 00196 static void 00197 help() 00198 { 00199 fprintf( 00200 stderr, 00201 "Usage:\n" 00202 "\n" 00203 "spfquery [control options | data options] ...\n" 00204 "\n" 00205 "Valid data options are:\n" 00206 " -file <filename> read spf data from a file. Use '-'\n" 00207 " to read from stdin.\n" 00208 "\n" 00209 " -ip <IP address> The IP address that is sending email\n" 00210 " -sender <email address> The email address used as the\n" 00211 " envelope-from. If no username (local\n" 00212 " part) is given, 'postmaster' will be\n" 00213 " assumed.\n" 00214 " -helo <domain name> The domain name given on the SMTP HELO\n" 00215 " command. This is only needed if the\n" 00216 " -sender option is not given.\n" 00217 " -rcpt-to <email addresses> A comma separated lists of email addresses\n" 00218 " that will have email from their secondary\n" 00219 " MXes automatically allowed.\n" 00220 "\n" 00221 "The data options are required. The -file option conflicts with all\n" 00222 "the other data options. The -helo and -rcpt-to are optional.\n" 00223 "\n" 00224 "\n" 00225 "Valid control options are:\n" 00226 " -debug [debug level] debug level.\n" 00227 " -local <SPF mechanisms> Local policy for whitelisting.\n" 00228 " -trusted <0|1> Should trusted-forwarder.org be checked?\n" 00229 " -guess <SPF mechanisms> Default checks if no SPF record is found.\n" 00230 " -default-explanation <str> Default explanation string to use.\n" 00231 " -max-lookup <number> Maximum number of DNS lookups to allow\n" 00232 " -sanitize <0|1> Clean up invalid characters in output?\n" 00233 " -name <domain name> The name of the system doing the SPF\n" 00234 " checking\n" 00235 " -override <...> Override SPF records for domains\n" 00236 " -fallback <...> Fallback SPF records for domains\n" 00237 "\n" 00238 " -keep-comments Print comments found when reading\n" 00239 " from a file.\n" 00240 " -version Print version of spfquery.\n" 00241 " -help Print out these options.\n" 00242 "\n" 00243 "Examples:\n" 00244 "\n" 00245 "spfquery -ip=11.22.33.44 -sender=user@aol.com -helo=spammer.tld\n" 00246 "spfquery -f test_data\n" 00247 "echo \"127.0.0.1 myname@mydomain.com helohost.com\" | spfquery -f -\n" 00248 ); 00249 } 00250 00251 00252 static void 00253 response_print_errors(const char *context, 00254 SPF_response_t *spf_response, SPF_errcode_t err) 00255 { 00256 SPF_error_t *spf_error; 00257 int i; 00258 00259 printf("StartError\n"); 00260 00261 if (context != NULL) 00262 printf("Context: %s\n", context); 00263 if (err != SPF_E_SUCCESS) 00264 printf("ErrorCode: (%d) %s\n", err, SPF_strerror(err)); 00265 00266 if (spf_response != NULL) { 00267 for (i = 0; i < SPF_response_messages(spf_response); i++) { 00268 spf_error = SPF_response_message(spf_response, i); 00269 printf( "%s: %s%s\n", 00270 SPF_error_errorp(spf_error) ? "Error" : "Warning", 00271 // SPF_error_code(spf_error), 00272 // SPF_strerror(SPF_error_code(spf_error)), 00273 ((SPF_error_errorp(spf_error) && (!err)) 00274 ? "[UNRETURNED] " 00275 : ""), 00276 SPF_error_message(spf_error) ); 00277 } 00278 } 00279 else { 00280 printf("libspf2 gave a NULL spf_response\n"); 00281 } 00282 printf("EndError\n"); 00283 } 00284 00285 static void 00286 response_print(const char *context, SPF_response_t *spf_response) 00287 { 00288 printf("--vv--\n"); 00289 printf("Context: %s\n", context); 00290 if (spf_response == NULL) { 00291 printf("NULL RESPONSE!\n"); 00292 } 00293 else { 00294 printf("Response result: %s\n", 00295 SPF_strresult(SPF_response_result(spf_response))); 00296 printf("Response reason: %s\n", 00297 SPF_strreason(SPF_response_reason(spf_response))); 00298 printf("Response err: %s\n", 00299 SPF_strerror(SPF_response_errcode(spf_response))); 00300 response_print_errors(NULL, spf_response, 00301 SPF_response_errcode(spf_response)); 00302 } 00303 printf("--^^--\n"); 00304 } 00305 00306 typedef 00307 struct SPF_client_options_struct { 00308 // void *hook; 00309 char *localpolicy; 00310 const char *explanation; 00311 const char *fallback; 00312 const char *rec_dom; 00313 int use_trusted; 00314 int max_lookup; 00315 int sanitize; 00316 int debug; 00317 } SPF_client_options_t; 00318 00319 typedef 00320 struct SPF_client_request_struct { 00321 char *ip; 00322 char *sender; 00323 char *helo; 00324 char *rcpt_to; 00325 } SPF_client_request_t; 00326 00327 int main( int argc, char *argv[] ) 00328 { 00329 SPF_client_options_t *opts; 00330 SPF_client_request_t *req; 00331 00332 SPF_server_t *spf_server = NULL; 00333 SPF_request_t *spf_request = NULL; 00334 SPF_response_t *spf_response = NULL; 00335 SPF_response_t *spf_response_2mx = NULL; 00336 SPF_response_t *spf_response_fallback = NULL; 00337 SPF_errcode_t err; 00338 00339 char *opt_file = NULL; 00340 int opt_keep_comments = 0; 00341 00342 FILE *fin; 00343 char in_line[4096]; 00344 char *p, *p_end; 00345 int done_once; 00346 int major, minor, patch; 00347 00348 int res = 0; 00349 int c; 00350 00351 const char *partial_result; 00352 char *result = NULL; 00353 int result_len = 0; 00354 00355 opts = (SPF_client_options_t *)malloc(sizeof(SPF_client_options_t)); 00356 memset(opts, 0, sizeof(SPF_client_options_t)); 00357 00358 req = (SPF_client_request_t *)malloc(sizeof(SPF_client_request_t)); 00359 memset(req, 0, sizeof(SPF_client_request_t)); 00360 00361 opts->rec_dom = "spfquery"; 00362 00363 #ifdef _WIN32 00364 if (SPF_win32_startup() == 0) { 00365 fprintf( stderr, "Could not startup WinSock, wrong version." ); 00366 FAIL_ERROR; 00367 } 00368 #endif 00369 00370 /* 00371 * check the arguments 00372 */ 00373 00374 for (;;) { 00375 int option_index; /* Largely unused */ 00376 00377 c = getopt_long_only (argc, argv, "f:i:s:h:r:lt::gemcnd::kz:a:v", 00378 long_options, &option_index); 00379 00380 if (c == -1) 00381 break; 00382 00383 switch (c) { 00384 case 'f': 00385 opt_file = optarg; 00386 break; 00387 00388 00389 case 'i': 00390 req->ip = optarg; 00391 break; 00392 00393 case 's': 00394 req->sender = optarg; 00395 break; 00396 00397 case 'h': 00398 req->helo = optarg; 00399 break; 00400 00401 case 'r': 00402 req->rcpt_to = optarg; 00403 break; 00404 00405 00406 case 'l': 00407 opts->localpolicy = optarg; 00408 break; 00409 00410 case 't': 00411 if (optarg == NULL) 00412 opts->use_trusted = 1; 00413 else 00414 opts->use_trusted = atoi(optarg); 00415 break; 00416 00417 case 'g': 00418 opts->fallback = optarg; 00419 break; 00420 00421 case 'e': 00422 opts->explanation = optarg; 00423 break; 00424 00425 case 'm': 00426 opts->max_lookup = atoi(optarg); 00427 break; 00428 00429 case 'c': /* "clean" */ 00430 opts->sanitize = atoi(optarg); 00431 break; 00432 00433 case 'n': /* name of host doing SPF checking */ 00434 opts->rec_dom = optarg; 00435 break; 00436 00437 case 'a': 00438 unimplemented('a'); 00439 break; 00440 00441 case 'z': 00442 unimplemented('z'); 00443 break; 00444 00445 00446 case 'v': 00447 fprintf( stderr, "spfquery version information:\n" ); 00448 fprintf( stderr, "SPF test system version: %s\n", 00449 SPF_TEST_VERSION ); 00450 fprintf( stderr, "Compiled with SPF library version: %d.%d.%d\n", 00451 SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, 00452 SPF_LIB_VERSION_PATCH ); 00453 SPF_get_lib_version( &major, &minor, &patch ); 00454 fprintf( stderr, "Running with SPF library version: %d.%d.%d\n", 00455 major, minor, patch ); 00456 fprintf( stderr, "\n" ); 00457 usage(); 00458 FAIL_ERROR; 00459 break; 00460 00461 case 0: 00462 case '?': 00463 help(); 00464 FAIL_ERROR; 00465 break; 00466 00467 case 'k': 00468 opt_keep_comments = 1; 00469 break; 00470 00471 case 'd': 00472 if (optarg == NULL) 00473 opts->debug = 1; 00474 else 00475 opts->debug = atoi( optarg ); 00476 break; 00477 00478 default: 00479 fprintf( stderr, "Error: getopt returned character code 0%o ??\n", c); 00480 FAIL_ERROR; 00481 } 00482 } 00483 00484 if (optind != argc) { 00485 help(); 00486 FAIL_ERROR; 00487 } 00488 00489 /* 00490 * set up the SPF configuration 00491 */ 00492 00493 spf_server = SPF_server_new(SPF_DNS_CACHE, opts->debug); 00494 00495 if ( opts->rec_dom ) 00496 SPF_server_set_rec_dom( spf_server, opts->rec_dom ); 00497 if ( opts->sanitize ) 00498 SPF_server_set_sanitize( spf_server, opts->sanitize ); 00499 if ( opts->max_lookup ) 00500 SPF_server_set_max_dns_mech(spf_server, opts->max_lookup); 00501 00502 if (opts->localpolicy) { 00503 err = SPF_server_set_localpolicy( spf_server, opts->localpolicy, opts->use_trusted, &spf_response); 00504 if ( err ) { 00505 response_print_errors("Error setting local policy", 00506 spf_response, err); 00507 WARN_ERROR; 00508 } 00509 FREE_RESPONSE(spf_response); 00510 } 00511 00512 00513 if ( opts->explanation ) { 00514 err = SPF_server_set_explanation( spf_server, opts->explanation, &spf_response ); 00515 if ( err ) { 00516 response_print_errors("Error setting default explanation", 00517 spf_response, err); 00518 WARN_ERROR; 00519 } 00520 FREE_RESPONSE(spf_response); 00521 } 00522 00523 /* 00524 * process the SPF request 00525 */ 00526 00527 if (opt_file) { 00528 /* 00529 * the requests are on STDIN 00530 */ 00531 if (strcmp(opt_file, "-" ) == 0) 00532 fin = stdin; 00533 else 00534 fin = fopen( opt_file, "r" ); 00535 00536 if (!fin) { 00537 fprintf( stderr, "Could not open: %s\n", opt_file ); 00538 FAIL_ERROR; 00539 } 00540 } 00541 else { 00542 fin = NULL; 00543 00544 if ((req->ip == NULL) || 00545 (req->sender == NULL && req->helo == NULL) ) { 00546 usage(); 00547 FAIL_ERROR; 00548 } 00549 } 00550 00551 done_once = FALSE; 00552 00553 while ( TRUE ) { 00554 if ( fin ) { 00555 if ( fgets( in_line, sizeof( in_line ), fin ) == NULL ) 00556 break; 00557 00558 in_line[strcspn(in_line, "\r\n")] = '\0'; 00559 p = in_line; 00560 00561 p += strspn( p, " \t\n" ); 00562 { 00563 if ( *p == '\0' || *p == '#' ) { 00564 if ( opt_keep_comments ) 00565 printf( "%s\n", in_line ); 00566 continue; 00567 } 00568 } 00569 req->ip = p; 00570 p += strcspn( p, " \t\n" ); 00571 *p++ = '\0'; 00572 00573 p += strspn( p, " \t\n" ); 00574 req->sender = p; 00575 p += strcspn( p, " \t\n" ); 00576 *p++ = '\0'; 00577 00578 p += strspn( p, " \t\n" ); 00579 req->helo = p; 00580 p += strcspn( p, " \t\n" ); 00581 *p++ = '\0'; 00582 00583 p += strspn( p, " \t\n" ); 00584 req->rcpt_to = p; 00585 p += strcspn( p, " \t\n" ); 00586 *p++ = '\0'; 00587 } 00588 else { 00589 if ( done_once ) 00590 break; 00591 done_once = TRUE; 00592 } 00593 00594 /* We have to do this here else we leak on CONTINUE_ERROR */ 00595 FREE_REQUEST(spf_request); 00596 FREE_RESPONSE(spf_response); 00597 00598 spf_request = SPF_request_new(spf_server); 00599 00600 if (SPF_request_set_ipv4_str(spf_request, req->ip) 00601 && SPF_request_set_ipv6_str(spf_request, req->ip)) { 00602 printf( "Invalid IP address.\n" ); 00603 CONTINUE_ERROR; 00604 } 00605 00606 if (req->helo) { 00607 if (SPF_request_set_helo_dom( spf_request, req->helo ) ) { 00608 printf( "Invalid HELO domain.\n" ); 00609 CONTINUE_ERROR; 00610 } 00611 } 00612 00613 if (SPF_request_set_env_from( spf_request, req->sender ) ) { 00614 printf( "Invalid envelope from address.\n" ); 00615 CONTINUE_ERROR; 00616 } 00617 00618 err = SPF_request_query_mailfrom(spf_request, &spf_response); 00619 if (opts->debug) 00620 response_print("Main query", spf_response); 00621 if (err) { 00622 response_print_errors("Failed to query MAIL-FROM", 00623 spf_response, err); 00624 CONTINUE_ERROR; 00625 } 00626 00627 if (result != NULL) 00628 result[0] = '\0'; 00629 APPEND_RESULT(SPF_response_result(spf_response)); 00630 00631 if (req->rcpt_to != NULL && *req->rcpt_to != '\0' ) { 00632 p = req->rcpt_to; 00633 p_end = p + strcspn(p, ",;"); 00634 00635 /* This is some incarnation of 2mx mode. */ 00636 while (SPF_response_result(spf_response)!=SPF_RESULT_PASS) { 00637 if (*p_end) 00638 *p_end = '\0'; 00639 else 00640 p_end = NULL; /* Note this is last rcpt */ 00641 00642 err = SPF_request_query_rcptto(spf_request, 00643 &spf_response_2mx, p); 00644 if (opts->debug) 00645 response_print("2mx query", spf_response_2mx); 00646 if (err) { 00647 response_print_errors("Failed to query RCPT-TO", 00648 spf_response, err); 00649 CONTINUE_ERROR; 00650 } 00651 00652 /* append the result */ 00653 APPEND_RESULT(SPF_response_result(spf_response_2mx)); 00654 00655 spf_response = SPF_response_combine(spf_response, 00656 spf_response_2mx); 00657 00658 if (!p_end) 00659 break; 00660 p = p_end + 1; 00661 } 00662 } 00663 00664 /* We now have an option to call SPF_request_query_fallback */ 00665 if (opts->fallback) { 00666 err = SPF_request_query_fallback(spf_request, 00667 &spf_response_fallback, opts->fallback); 00668 if (opts->debug) 00669 response_print("fallback query", spf_response_fallback); 00670 if (err) { 00671 response_print_errors("Failed to query best-guess", 00672 spf_response_fallback, err); 00673 CONTINUE_ERROR; 00674 } 00675 00676 /* append the result */ 00677 APPEND_RESULT(SPF_response_result(spf_response_fallback)); 00678 00679 spf_response = SPF_response_combine(spf_response, 00680 spf_response_fallback); 00681 } 00682 00683 printf( "%s\n%s\n%s\n%s\n", 00684 result, 00685 X_OR_EMPTY(SPF_response_get_smtp_comment(spf_response)), 00686 X_OR_EMPTY(SPF_response_get_header_comment(spf_response)), 00687 X_OR_EMPTY(SPF_response_get_received_spf(spf_response)) 00688 ); 00689 00690 res = SPF_response_result(spf_response); 00691 00692 fflush(stdout); 00693 } 00694 00695 error: 00696 FREE(result, free); 00697 FREE_RESPONSE(spf_response); 00698 FREE_REQUEST(spf_request); 00699 FREE(spf_server, SPF_server_free); 00700 00701 FREE(req, free); 00702 FREE(opts, free); 00703 00704 #ifdef _WIN32 00705 SPF_win32_cleanup(); 00706 #endif 00707 00708 return res; 00709 }