1 /* $Id: spf.c,v 1.40 2015/10/30 18:22:30 manu Exp $ */
2
3 /*
4 * Copyright (c) 2004-2007 Emmanuel Dreyfus
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by Emmanuel Dreyfus
18 *
19 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
20 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "config.h"
33
34 #ifdef HAVE_SYS_CDEFS_H
35 #include <sys/cdefs.h>
36 #ifdef __RCSID
37 __RCSID("$Id: spf.c,v 1.40 2015/10/30 18:22:30 manu Exp $");
38 #endif
39 #endif
40
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <sysexits.h>
44 #include <errno.h>
45 #include <string.h>
46 #include <strings.h>
47 #include <syslog.h>
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <arpa/inet.h>
52
53 #include "conf.h"
54 #include "spf.h"
55 #include "acl.h"
56 #include "milter-greylist.h"
57
58 #ifdef USE_DMALLOC
59 #include <dmalloc.h>
60 #endif
61
62 #if defined(__FreeBSD__) || defined(__DragonFly__)
63 #define HAVE_NS_TYPE
64 #include <arpa/nameser.h>
65 #endif
66
67
68 #if (defined(HAVE_SPF) || defined(HAVE_SPF_ALT) || \
69 defined(HAVE_SPF2_10) || defined(HAVE_SPF2))
70 static int spf_check_internal(acl_data_t *, acl_stage_t,
71 struct acl_param *, struct mlfi_priv *);
72 static int spf_check_self(acl_data_t *, acl_stage_t,
73 struct acl_param *, struct mlfi_priv *);
74 #endif
75
76 #ifdef HAVE_SPF
77 #include <spf.h>
78
79 #ifndef SPF_FALSE
80 #define SPF_FALSE 0
81 #endif
82
83
84 static int
spf_check_internal(ad,as,ap,priv)85 spf_check_internal(ad, as, ap, priv)
86 acl_data_t *ad;
87 acl_stage_t as;
88 struct acl_param *ap;
89 struct mlfi_priv *priv;
90 {
91 struct sockaddr *sa = SA(&priv->priv_addr);
92 socklen_t salen = priv->priv_addrlen;
93 char *helo = priv->priv_helo;
94 char *fromp = priv->priv_from;
95 peer_info_t *p = NULL;
96 char addr[IPADDRSTRLEN];
97 int result = 0;
98 struct timeval tv1, tv2, tv3;
99 enum spf_status status;
100
101 if (conf.c_debug)
102 gettimeofday(&tv1, NULL);
103
104 if (sa->sa_family != AF_INET) /* libspf doesn't support IPv6 */
105 return result;
106 if (!iptostring(sa, salen, addr, sizeof(addr)))
107 return result;
108
109 if ((p = SPF_init("milter-greylist", addr,
110 NULL, NULL, NULL, SPF_FALSE, SPF_FALSE)) == NULL) {
111 mg_log(LOG_ERR, "SPF_Init failed");
112 goto out1;
113 }
114 SPF_smtp_helo(p, helo);
115 SPF_smtp_from(p, from);
116 p->RES = SPF_policy_main(p);
117
118 status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
119 switch (p->RES) {
120 case SPF_PASS:
121 result = (status == MGSPF_PASS);
122 strcpy(priv->priv_spf_result, "pass");
123 break;
124 case SPF_H_FAIL:
125 result = (status == MGSPF_FAIL);
126 strcpy(priv->priv_spf_result, "fail");
127 break;
128 case SPF_S_FAIL:
129 result = (status == MGSPF_SOFTFAIL);
130 strcpy(priv->priv_spf_result, "softfail");
131 break;
132 case SPF_NEUTRAL:
133 result = (status == MGSPF_NEUTRAL);
134 strcpy(priv->priv_spf_result, "neutral");
135 break;
136 case SPF_UNKNOWN:
137 case SPF_UNMECH:
138 result = (status == MGSPF_UNKNOWN);
139 strcpy(priv->priv_spf_result, "unknown");
140 break;
141 case SPF_ERROR:
142 result = (status == MGSPF_ERROR);
143 strcpy(priv->priv_spf_result, "error");
144 break;
145 case SPF_NONE:
146 result = (status == MGSPF_NONE);
147 strcpy(priv->priv_spf_result, "none");
148 break;
149 default:
150 mg_log(LOG_ERR, "Internal error: unexpected spf result %d",
151 " spf test code %d", p->RES, status);
152 exit(EX_SOFTWARE);
153 break;
154 }
155
156 if (conf.c_debug)
157 mg_log(LOG_DEBUG, "SPF return %s (test code %d, result %d)",
158 priv->priv_spf_result, status, result);
159
160 SPF_close(p);
161
162 out1:
163 if (conf.c_debug) {
164 gettimeofday(&tv2, NULL);
165 timersub(&tv2, &tv1, &tv3);
166 mg_log(LOG_DEBUG, "SPF lookup performed in %ld.%06lds",
167 tv3.tv_sec, tv3.tv_usec);
168 }
169
170 return result;
171 }
172 #endif /* HAVE_SPF */
173
174
175 #if defined(HAVE_SPF_ALT) || defined(HAVE_SPF2_10) || defined(HAVE_SPF2)
176 /* SMTP needs at least 64 chars for local part and 255 for doamin... */
177 #ifndef NS_MAXDNAME
178 #define NS_MAXDNAME 1025
179 #endif
180 #endif
181
182 #ifdef HAVE_SPF_ALT
183 #include <spf_alt/spf.h>
184 #include <spf_alt/spf_dns_resolv.h>
185 #include <spf_alt/spf_lib_version.h>
186 #endif
187
188 #ifdef HAVE_SPF2_10
189 #include <spf2/spf.h>
190 #include <spf2/spf_dns_resolv.h>
191 #include <spf2/spf_lib_version.h>
192 #endif
193
194 #if defined(HAVE_SPF_ALT) || defined(HAVE_SPF2_10)
195 static int
spf_check_internal(ad,as,ap,priv)196 spf_check_internal(ad, as, ap, priv)
197 acl_data_t *ad;
198 acl_stage_t as;
199 struct acl_param *ap;
200 struct mlfi_priv *priv;
201 {
202 struct sockaddr *sa = SA(&priv->priv_addr);
203 socklen_t salen = priv->priv_addrlen;
204 char *helo = priv->priv_helo;
205 char *fromp = priv->priv_from;
206 SPF_config_t spfconf;
207 SPF_dns_config_t dnsconf;
208 char addr[IPADDRSTRLEN];
209 char from[NS_MAXDNAME + 1];
210 SPF_output_t out;
211 int result = 0;
212 struct timeval tv1, tv2, tv3;
213 size_t len;
214 enum spf_status status;
215
216 if (conf.c_debug)
217 gettimeofday(&tv1, NULL);
218
219 if ((spfconf = SPF_create_config()) == NULL) {
220 mg_log(LOG_ERR, "SPF_create_config failed");
221 goto out1;
222 }
223
224 if ((dnsconf = SPF_dns_create_config_resolv(NULL, 0)) == NULL) {
225 mg_log(LOG_ERR, "SPF_dns_create_config_resolv faile");
226 goto out2;
227 }
228
229 /*
230 * Get the IP address
231 */
232 if (!iptostring(sa, salen, addr, sizeof(addr))) {
233 mg_log(LOG_ERR, "SPF_set_ip_str failed");
234 goto out3;
235 }
236 if (SPF_set_ip_str(spfconf, addr) != 0) {
237 mg_log(LOG_ERR, "SPF_set_ip_str failed");
238 goto out3;
239 }
240
241 /* HELO string */
242 if (SPF_set_helo_dom(spfconf, helo) != 0) {
243 mg_log(LOG_ERR, "SPF_set_helo failed");
244 goto out3;
245 }
246
247 /*
248 * And the enveloppe source e-mail
249 */
250 if (fromp[0] == '<')
251 fromp++; /* strip leading < */
252 strncpy(from, fromp, NS_MAXDNAME);
253 from[NS_MAXDNAME] = '\0';
254 len = strlen(from);
255 if (fromp[len - 1] == '>')
256 from[len - 1] = '\0'; /* strip trailing > */
257
258 if (SPF_set_env_from(spfconf, from) != 0) {
259 mg_log(LOG_ERR, "SPF_set_env_from failed");
260 goto out3;
261 }
262
263 /*
264 * Get the SPF result
265 */
266 SPF_init_output(&out);
267 #if 0 &&((SPF_LIB_VERSION_MAJOR == 0) && (SPF_LIB_VERSION_MINOR <= 3))
268 out = SPF_result(spfconf, dnsconf, NULL);
269 #else
270 out = SPF_result(spfconf, dnsconf);
271 #endif
272
273 status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
274 switch (out.result) {
275 case SPF_RESULT_PASS:
276 result = (status == MGSPF_PASS);
277 strcpy(priv->priv_spf_result, "pass");
278 break;
279 case SPF_RESULT_FAIL:
280 result = (status == MGSPF_FAIL);
281 strcpy(priv->priv_spf_result, "fail");
282 break;
283 case SPF_RESULT_SOFTFAIL:
284 result = (status == MGSPF_SOFTFAIL);
285 strcpy(priv->priv_spf_result, "softfail");
286 break;
287 case SPF_RESULT_NEUTRAL:
288 result = (status == MGSPF_NEUTRAL);
289 strcpy(priv->priv_spf_result, "neutral");
290 break;
291 case SPF_RESULT_UNKNOWN:
292 result = (status == MGSPF_UNKNOWN);
293 strcpy(priv->priv_spf_result, "unknown");
294 break;
295 case SPF_RESULT_ERROR:
296 result = (status == MGSPF_ERROR);
297 strcpy(priv->priv_spf_result, "error");
298 break;
299 case SPF_RESULT_NONE:
300 result = (status == MGSPF_NONE);
301 strcpy(priv->priv_spf_result, "none");
302 break;
303 default:
304 mg_log(LOG_ERR,
305 "Internal error: unexpected spf_alt/spf_2_10 result %d",
306 " spf test code %d", out.result, status);
307 exit(EX_SOFTWARE);
308 break;
309 }
310
311 if (conf.c_debug)
312 mg_log(LOG_DEBUG, "SPF return %s (test code %d, result %d)",
313 priv->priv_spf_result, out.result, result);
314
315 SPF_free_output(&out);
316 out3:
317 SPF_dns_destroy_config_resolv(dnsconf);
318 out2:
319 SPF_destroy_config(spfconf);
320 out1:
321 if (conf.c_debug) {
322 gettimeofday(&tv2, NULL);
323 timersub(&tv2, &tv1, &tv3);
324 mg_log(LOG_DEBUG, "SPF lookup performed in %ld.%06lds",
325 tv3.tv_sec, tv3.tv_usec);
326 }
327
328 return result;
329 }
330
331 #endif /* HAVE_SPF_ALT */
332
333 #ifdef HAVE_SPF2
334 #include <spf2/spf.h>
335
336 static int
spf_check_internal(ad,as,ap,priv)337 spf_check_internal(ad, as, ap, priv)
338 acl_data_t *ad;
339 acl_stage_t as;
340 struct acl_param *ap;
341 struct mlfi_priv *priv;
342 {
343 struct sockaddr *sa = SA(&priv->priv_addr);
344 char *fqdn;
345 char *helo = priv->priv_helo;
346 char *fromp = priv->priv_from;
347 SPF_server_t *spf_server;
348 SPF_request_t *spf_request;
349 SPF_response_t *spf_response;
350 char from[NS_MAXDNAME + 1];
351 int res, result = 0;
352 struct timeval tv1, tv2, tv3;
353 size_t len;
354 enum spf_status status;
355
356 if (conf.c_debug)
357 gettimeofday(&tv1, NULL);
358
359 if ((spf_server = SPF_server_new(SPF_DNS_CACHE, 0)) == NULL) {
360 mg_log(LOG_ERR, "SPF_server_new failed");
361 goto out1;
362 }
363
364 if ((spf_request = SPF_request_new(spf_server)) == NULL) {
365 mg_log(LOG_ERR, "SPF_request_new failed");
366 goto out2;
367 }
368
369 /* Local machine FQDN */
370 fqdn = smfi_getsymval(priv->priv_ctx, "{j}");
371 if (fqdn != NULL && SPF_server_set_rec_dom(spf_server, fqdn) != 0)
372 mg_log(LOG_WARNING, "SPF_server_set_rec_dom failed");
373
374 /*
375 * Get the IP address
376 */
377 switch (sa->sa_family) {
378 case AF_INET:
379 res = SPF_request_set_ipv4(spf_request, *SADDR4(sa));
380 break;
381 #ifdef AF_INET6
382 case AF_INET6:
383 res = SPF_request_set_ipv6(spf_request, *SADDR6(sa));
384 break;
385 #endif
386 default:
387 mg_log(LOG_ERR, "unknown address family %d", sa->sa_family);
388 goto out3;
389 }
390 if (res != 0) {
391 mg_log(LOG_ERR, "SPF_request_set_ip_str failed");
392 goto out3;
393 }
394
395 /* HELO string */
396 if (SPF_request_set_helo_dom(spf_request, helo) != 0) {
397 mg_log(LOG_ERR, "SPF_request_set_helo_dom failed");
398 goto out3;
399 }
400
401 /*
402 * And the enveloppe source e-mail
403 */
404 if (fromp[0] == '<')
405 fromp++; /* strip leading < */
406 strncpy(from, fromp, NS_MAXDNAME);
407 from[NS_MAXDNAME] = '\0';
408 len = strlen(from);
409 if (fromp[len - 1] == '>')
410 from[len - 1] = '\0'; /* strip trailing > */
411
412 if (SPF_request_set_env_from(spf_request, from) != 0) {
413 mg_log(LOG_ERR, "SPF_request_set_env_from failed");
414 goto out3;
415 }
416
417 /*
418 * Get the SPF result
419 */
420 SPF_request_query_mailfrom(spf_request, &spf_response);
421 res = SPF_response_result(spf_response);
422
423 status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
424 switch (res) {
425 case SPF_RESULT_PASS:
426 result = (status == MGSPF_PASS);
427 strcpy(priv->priv_spf_result, "pass");
428 break;
429 case SPF_RESULT_FAIL:
430 result = (status == MGSPF_FAIL);
431 strcpy(priv->priv_spf_result, "fail");
432 break;
433 case SPF_RESULT_SOFTFAIL:
434 result = (status == MGSPF_SOFTFAIL);
435 strcpy(priv->priv_spf_result, "softfail");
436 break;
437 case SPF_RESULT_NEUTRAL:
438 result = (status == MGSPF_NEUTRAL);
439 strcpy(priv->priv_spf_result, "neutral");
440 break;
441 case SPF_RESULT_INVALID:
442 result = (status == MGSPF_UNKNOWN);
443 strcpy(priv->priv_spf_result, "unknown");
444 break;
445 case SPF_RESULT_PERMERROR:
446 result = (status == MGSPF_ERROR);
447 strcpy(priv->priv_spf_result, "permerror");
448 break;
449 case SPF_RESULT_TEMPERROR:
450 result = (status == MGSPF_ERROR);
451 strcpy(priv->priv_spf_result, "temperror");
452 break;
453 case SPF_RESULT_NONE:
454 result = (status == MGSPF_NONE);
455 strcpy(priv->priv_spf_result, "none");
456 break;
457 default:
458 mg_log(LOG_ERR, "Internal error: unexpected spf2 result %d",
459 " spf test code %d", res, status);
460 exit(EX_SOFTWARE);
461 break;
462 }
463
464 if (conf.c_debug)
465 mg_log(LOG_DEBUG, "SPF return %s (test code %d, result %d)",
466 priv->priv_spf_result, res, result);
467
468 if (priv->priv_spf_header != NULL)
469 free(priv->priv_spf_header);
470
471
472 if (spf_response->received_spf_value)
473 priv->priv_spf_header =
474 strdup(spf_response->received_spf_value);
475 else
476 priv->priv_spf_header = NULL;
477
478 SPF_response_free(spf_response);
479 out3:
480 SPF_request_free(spf_request);
481 out2:
482 SPF_server_free(spf_server);
483 out1:
484 if (conf.c_debug) {
485 gettimeofday(&tv2, NULL);
486 timersub(&tv2, &tv1, &tv3);
487 mg_log(LOG_DEBUG, "SPF lookup performed in %ld.%06lds",
488 tv3.tv_sec, tv3.tv_usec);
489 }
490
491 return result;
492 }
493
494
495 #endif /* HAVE_SPF2 */
496
497
498
499 #if (defined(HAVE_SPF) || defined(HAVE_SPF_ALT) || \
500 defined(HAVE_SPF2_10) || defined(HAVE_SPF2))
501 char *
acl_print_spf(ad,buf,len)502 acl_print_spf(ad, buf, len)
503 acl_data_t *ad;
504 char *buf;
505 size_t len;
506 {
507 char *tmpstr;
508 enum spf_status status;
509
510 status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
511 switch (status) {
512 case MGSPF_PASS:
513 tmpstr = "pass";
514 break;
515 case MGSPF_FAIL:
516 tmpstr = "fail";
517 break;
518 case MGSPF_SOFTFAIL:
519 tmpstr = "softfail";
520 break;
521 case MGSPF_NEUTRAL:
522 tmpstr = "neutral";
523 break;
524 case MGSPF_UNKNOWN:
525 tmpstr = "unknown";
526 break;
527 case MGSPF_ERROR:
528 tmpstr = "error";
529 break;
530 case MGSPF_NONE:
531 tmpstr = "none";
532 break;
533 case MGSPF_SELF:
534 tmpstr = "self";
535 break;
536 default:
537 mg_log(LOG_ERR, "Internal error: unexpected spf_status %d",
538 status);
539 exit(EX_SOFTWARE);
540 break;
541 }
542 snprintf(buf, len, "%s", tmpstr);
543 return buf;
544 }
545
546 void
acl_add_spf(ad,data)547 acl_add_spf(ad, data)
548 acl_data_t *ad;
549 void *data;
550 {
551 ad->spf_status = *(enum spf_status *)data;
552 return;
553 }
554
555 int
spf_check(ad,as,ap,priv)556 spf_check(ad, as, ap, priv)
557 acl_data_t *ad;
558 acl_stage_t as;
559 struct acl_param *ap;
560 struct mlfi_priv *priv;
561 {
562 enum spf_status status;
563
564 status = ad ? *(enum spf_status *)ad : MGSPF_PASS;
565 switch (status) {
566 case MGSPF_PASS:
567 case MGSPF_FAIL:
568 case MGSPF_SOFTFAIL:
569 case MGSPF_NEUTRAL:
570 case MGSPF_UNKNOWN:
571 case MGSPF_ERROR:
572 case MGSPF_NONE:
573 return spf_check_internal(ad, as, ap, priv);
574 break;
575 case MGSPF_SELF:
576 return spf_check_self(ad, as, ap, priv);
577 break;
578 default:
579 break;
580 }
581
582 mg_log(LOG_ERR, "Internal error: unexpected spf_status %d",
583 status);
584 exit(EX_SOFTWARE);
585
586 return 0;
587 }
588
589 /*
590 * Check if the SPF record is wide open: the simpliest
591 * way of doing it is to check whether our own IP
592 * validates the record.
593 * That is a problem for Postfix and CommSuite installations,
594 * as the local IP is not available ({if_addr} milter macro).
595 * For now such MTAs must define "localaddr" in config.
596 */
597 static int
spf_check_self(ad,as,ap,priv)598 spf_check_self(ad, as, ap, priv)
599 acl_data_t *ad;
600 acl_stage_t as;
601 struct acl_param *ap;
602 struct mlfi_priv *priv;
603 {
604 int retval = 0;
605 sockaddr_t saved_addr;
606 acl_data_t tmp_ad;
607 char *ip;
608
609 (void)memcpy(&saved_addr, &priv->priv_addr, sizeof(saved_addr));
610
611 switch (conf.c_localaddr.ss_family) {
612 case AF_INET:
613 (void)memcpy(&priv->priv_addr, &conf.c_localaddr,
614 sizeof(struct sockaddr_in));
615 break;
616 #ifdef AF_INET6
617 case AF_INET6:
618 (void)memcpy(&priv->priv_addr, &conf.c_localaddr,
619 sizeof(struct sockaddr_in6));
620 break;
621 #endif /* AF_INET6 */
622 default: /* localaddr unspecified, try fallback to {if_addr} */
623 ip = local_ipstr(priv);
624 if (strcmp(ip, "0.0.0.0") == 0) {
625 mg_log(LOG_ERR,
626 "spf self used without localaddr specified "
627 "and no {if_addr} macro is available");
628 return 0;
629 }
630 if (strncmp(ip, "IPv6:", strlen("IPv6:")) == 0) {
631 #ifdef AF_INET6
632 if (inet_pton(AF_INET6, ip + strlen("IPv6:"),
633 SADDR6(SA(&priv->priv_addr))) <= 0) {
634 mg_log(LOG_ERR,
635 "Invalid IPv6 local address %s", ip);
636 exit(EX_SOFTWARE);
637 }
638 #else /* AF_INET6 */
639 mg_log(LOG_ERR,
640 "IPv6 support not compiled but local IP is IPv6");
641 exit(EX_SOFTWARE);
642 #endif /* AF_INET6 */
643 } else {
644 if (inet_pton(AF_INET, ip,
645 SADDR4(SA(&priv->priv_addr))) <= 0) {
646 mg_log(LOG_ERR,
647 "Invalid IPv4 local address %s", ip);
648 exit(EX_SOFTWARE);
649 }
650 }
651 break;
652 }
653
654 if (ad == NULL)
655 (void)memset(&tmp_ad, 0, sizeof(tmp_ad));
656 else
657 (void)memcpy(&tmp_ad, ad, sizeof(tmp_ad));
658
659 tmp_ad.spf_status = MGSPF_PASS;
660
661 retval = spf_check_internal(&tmp_ad, as, ap, priv);
662
663 (void)memcpy(&priv->priv_addr, &saved_addr, sizeof(priv->priv_addr));
664
665 return retval;
666 }
667
668 #endif
669