forked from SirWumpus/milter-link
-
Notifications
You must be signed in to change notification settings - Fork 0
/
milter-link.c
executable file
·2110 lines (1765 loc) · 58.3 KB
/
milter-link.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* milter-link.c
*
* Copyright 2003, 2015 by Anthony Howe. All rights reserved.
*
* The following should be added to the sendmail.mc file:
*
* INPUT_MAIL_FILTER(
* `milter-link',
* `S=unix:/var/run/milter/milter-link.socket, T=C:10s;R:1m;E:5m'
* )dnl
*/
/* "milter-link has the most complete and flexible URL lookup feature
* set making it the ideal tool to make the most of URIBL.com's data
* sets. Its ease of use and rock solid performance has made it an
* invaluable tool on our high traffic trap servers."
* - URIBL.com Admin Team
*/
/***********************************************************************
*** Leave this header alone. Its generate from the configure script.
***********************************************************************/
#include "config.h"
/***********************************************************************
*** You can change the stuff below if the configure script doesn't work.
***********************************************************************/
#ifndef RUN_AS_USER
#define RUN_AS_USER "milter"
#endif
#ifndef RUN_AS_GROUP
#define RUN_AS_GROUP "milter"
#endif
#ifndef MILTER_CF
#define MILTER_CF "/etc/mail/" MILTER_NAME ".cf"
#endif
#ifndef PID_FILE
#define PID_FILE "/var/run/milter/" MILTER_NAME ".pid"
#endif
#ifndef SOCKET_FILE
#define SOCKET_FILE "/var/run/milter/" MILTER_NAME ".socket"
#endif
#ifndef WORK_DIR
#define WORK_DIR "/var/tmp"
#endif
#ifndef SUBJECT_TAG
#define SUBJECT_TAG "[SPAM]"
#endif
#ifndef BLACK_LISTED_URL_FORMAT
#define BLACK_LISTED_URL_FORMAT "black listed URL host %s by %s" /* 1st URL domain, 2nd list name */
#endif
#ifndef BLACK_LISTED_MAIL_FORMAT
#define BLACK_LISTED_MAIL_FORMAT "black listed <%s> by %s" /* 1st mail address, 2nd list name */
#endif
/***********************************************************************
*** No configuration below this point.
***********************************************************************/
/* Re-assert this macro just in case. May cause a compiler warning. */
#define _REENTRANT 1
#include <com/snert/lib/version.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_TYPE_H
# include <sys/types.h>
#endif
#ifdef __sun__
# define _POSIX_PTHREAD_SEMANTICS
#endif
#include <signal.h>
#ifndef __MINGW32__
# if defined(HAVE_SYSLOG_H)
# include <syslog.h>
# endif
#endif
#ifdef HAVE_SQLITE3_H
# include <sqlite3.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <com/snert/lib/sys/Time.h>
#include <com/snert/lib/io/socket2.h>
#include <com/snert/lib/mail/limits.h>
#include <com/snert/lib/mail/smf.h>
#include <com/snert/lib/mail/tlds.h>
#include <com/snert/lib/mail/smdb.h>
#include <com/snert/lib/net/dnsList.h>
#include <com/snert/lib/net/network.h>
#include <com/snert/lib/util/Text.h>
#include <com/snert/lib/util/getopt.h>
#include <com/snert/lib/util/uri.h>
#include <com/snert/lib/util/convertDate.h>
#include <com/snert/lib/sys/sysexits.h>
#if LIBSNERT_MAJOR < 1 || LIBSNERT_MINOR < 75
# error "LibSnert 1.75.41 or better is required"
#endif
# define MILTER_STRING MILTER_NAME "/" MILTER_VERSION
/***********************************************************************
*** Constants
***********************************************************************/
#define TAG_FORMAT "%05d %s: "
#define TAG_ARGS data->work.cid, data->work.qid
#define X_SCANNED_BY "X-Scanned-By"
#define X_MILTER_PASS "X-" MILTER_NAME "-Pass"
#define X_MILTER_REPORT "X-" MILTER_NAME "-Report"
#define POLICY_UNDEFINED '\0'
#define POLICY_QUARANTINE 'q'
#define POLICY_DISCARD 'd'
#define POLICY_REJECT 'r'
#define POLICY_TAG 't'
/***********************************************************************
*** Global Variables
***********************************************************************/
static smfInfo milter;
typedef struct {
smfWork work;
int policy; /* per message */
int hasDate; /* per message */
int hasPass; /* per message */
int hasReport; /* per message */
int hasSubject; /* per message */
int stop_uri_scanning; /* per message */
int stop_mail_scanning; /* per message */
sfsistat uri_found_rc; /* per message */
char line[SMTP_TEXT_LINE_LENGTH+1]; /* general purpose */
char subject[SMTP_TEXT_LINE_LENGTH+1]; /* per message */
const char *mime_string_name; /* per string (headers, body) */
PDQ *pdq; /* per connection */
Mime *mime; /* per connection, reset per message */
Vector senders; /* per connection, reset per message */
Vector recipients; /* per connection, reset per message */
Vector ns_tested; /* per connection, reset per message */
Vector uri_tested; /* per connection, reset per message */
Vector mail_tested; /* per connection */
char reply[SMTP_REPLY_LINE_LENGTH+1]; /* per message */
} *workspace;
static const char black_listed_url_format[] = BLACK_LISTED_URL_FORMAT;
static const char black_listed_mail_format[] = BLACK_LISTED_MAIL_FORMAT;
static Option optIntro = { "", NULL, "\n# " MILTER_NAME "/" MILTER_VERSION "\n#\n# " MILTER_COPYRIGHT "\n#\n" };
static Option opt_subject_tag = { "subject-tag", SUBJECT_TAG, "Subject tag for messages identified as spam." };
static const char usage_links_policy[] =
"Policy to apply if message contains a broken URL found by +test-links.\n"
"# Specify one of none, tag, quarantine, reject, or discard.\n"
"#"
;
static Option opt_links_policy = { "links-policy", "tag", usage_links_policy };
static Option opt_links_test = { "links-test", "-", "Verify HTTP links are valid and find origin server." };
static Option opt_links_timeout = { "links-timeout", "60", "Socket timeout used when testing HTTP links." };
static const char usage_date_required[] =
"Set to one (1) to require a Date header; two (2) requires the header\n"
"# and that it conform to the RFC 5322 date-time format. Zero (0) disables\n"
"# the requirement (default).\n"
"#"
;
static Option opt_date_required = { "date-required", "0", usage_date_required };
static const char usage_date_policy[] =
"Policy applied when date-required fails. Specify one of none, tag,\n"
"# quarantine, reject, or discard.\n"
"#"
;
static Option opt_date_policy = { "date-policy", "reject", usage_date_policy };
static const char usage_domain_bl[] =
"A list of domain black list suffixes to consult, like .dbl.spamhaus.org.\n"
"# The host or domain name found in a URI is checked against these DNS black\n"
"# lists. These black lists are assumed to use wildcards entries, so only a\n"
"# single lookup is done. IP-as-domain in a URI are ignored. See uri-bl-policy.\n"
"#"
;
static Option opt_domain_bl = { "domain-bl", "dbl.spamhaus.org", usage_domain_bl };
static const char usage_uri_bl[] =
"A list of domain name black list suffixes to consult, like .multi.surbl.org.\n"
"# The domain name found in a URI is checked against these DNS black lists.\n"
"#"
;
static Option opt_uri_bl = { "uri-bl", ".multi.surbl.org;.black.uribl.com", usage_uri_bl };
static const char usage_uri_bl_headers[] =
"A list of mail headers to parse for URI and check using the uri-bl,\n"
"# uri-a-bl, and uri-ns-bl options. Specify the empty list to disable.\n"
"#"
;
static Option opt_uri_bl_headers = { "uri-bl-headers", "X-Originating-IP", usage_uri_bl_headers };
static const char usage_uri_bl_helo[] =
"Test the HELO/EHLO argument using the uri-bl, uri-a-bl, and uri-ns-bl\n"
"# options. Reject the command if black listed.\n"
"#"
;
static Option opt_uri_bl_helo = { "uri-bl-helo", "-", usage_uri_bl_helo };
static const char usage_uri_max_test[] =
"Maximum number of unique URI to check. Specify zero for unlimited."
;
Option opt_uri_max_test = { "uri-max-test", "0", usage_uri_max_test };
static const char usage_uri_bl_sub_domains[] =
"When querying against name based black lists, like .multi.surbl.org\n"
"# or .black.uribl.com, first test the registered domain, then any \n"
"# sub-domains from right-to-left. Typically sub-domains are not listed.\n"
"#"
;
static Option opt_uri_bl_sub_domains = { "uri-bl-sub-domains", "-", usage_uri_bl_sub_domains };
static const char usage_uri_a_bl[] =
"A list of IP black list suffixes to consult, like zen.spamhaus.org.\n"
"# The host or domain name found in a URI is used to find its DNS A record\n"
"# and IP address, which is then checked against these IP DNS black lists.\n"
"#"
;
static Option opt_uri_a_bl = { "uri-a-bl", "", usage_uri_a_bl };
static const char usage_uri_ns_bl[] =
"A list of host name and/or domain name black list suffixes to consult. The\n"
"# domain name found in a URI is used to find its DNS NS records; the NS host\n"
"# names are checked against these host name and/or domain name DNS black\n"
"# lists.\n"
"#"
;
static Option opt_uri_ns_bl = { "uri-ns-bl", "", usage_uri_ns_bl };
static const char usage_uri_ns_a_bl[] =
"A comma or semi-colon separated list of IP black list suffixes to consult.\n"
"# The host or domain name found in a URI is used to find its DNS NS records\n"
"# and IP address, which are then checked against these IP black lists.\n"
"#"
;
static Option opt_uri_ns_a_bl = { "uri-ns-a-bl", "", usage_uri_ns_a_bl };
static const char usage_uri_bl_policy[] =
"Policy to apply if message contains a black listed URI found by uri-bl,\n"
"# uri-a-bl, uri-ns-bl. Specify one of none, tag, quarantine, reject, or\n"
"# discard.\n"
"#"
;
static Option opt_uri_bl_policy = { "uri-bl-policy", "reject", usage_uri_bl_policy };
static const char usage_mail_bl[] =
"A list of mail address black list suffixes to consult. The MAIL FROM:\n"
"# address and mail addresses found in select headers and the message are MD5\n"
"# hashed, which are then checked against these black lists.\n"
"# "
;
Option opt_mail_bl = { "mail-bl", "", usage_mail_bl };
static const char usage_mail_bl_headers[] =
"A list of mail headers to parse for mail addresses and check against\n"
"# one or more mail address black lists. Specify the empty list to disable.\n"
"#"
;
Option opt_mail_bl_headers = { "mail-bl-headers", "From;Reply-To;Sender", usage_mail_bl_headers };
static const char usage_mail_bl_max[] =
"Maximum number of unique mail addresses to check. Specify zero for\n"
"# unlimited.\n"
"#"
;
Option opt_mail_bl_max = { "mail-bl-max", "10", usage_mail_bl_max };
static const char usage_mail_bl_policy[] =
"Check if the message contains a black listed mail address found by\n"
"# mail-bl. Specify one of none, tag, quarantine, reject, or discard.\n"
"#"
;
Option opt_mail_bl_policy = { "mail-bl-policy", "reject", usage_mail_bl_policy };
static const char usage_mail_bl_domains[] =
"A list of domain glob-like patterns for which to test against mail-bl,\n"
"# typically free mail services. This reduces the load on public BLs.\n"
"# Specify * to test all domains, empty list to disable.\n"
"#"
;
Option opt_mail_bl_domains = {
"mail-bl-domains",
"gmail.*"
";hotmail.*"
";live.*"
";yahoo.*"
";aol.*"
";aim.com"
";cantv.net"
";centrum.cz"
";centrum.sk"
";googlemail.com"
";gmx.*"
";inmail24.com"
";jmail.co.za"
";libero.it"
";luckymail.com"
";mail2world.com"
";msn.com"
";rediff.com"
";rediffmail.com"
";rocketmail.com"
";she.com"
";shuf.com"
";sify.com"
";terra.com"
";tiscali.it"
";tom.com"
";virgilio.it"
";voila.fr"
";vsnl.*"
";walla.com"
";wanadoo.*"
";windowslive.com"
";y7mail.com"
";yeah.net"
";ymail.com"
, usage_mail_bl_domains
};
static const char usage_uri_bl_port_list[] =
"A list of port numbers corresponding to protocols to test. Some sites\n"
"# prefer to focus on web and/or email related URI. This option provides\n"
"# a means to restrict the scope of testing to a specific subset of URI\n"
"# by port number. An empty list means all URI are tested.\n"
"#"
;
static Option opt_uri_bl_port_list = { "uri-bl-port-list", "", usage_uri_bl_port_list };
Option opt_version = { "version", NULL, "Show version and copyright." };
static const char usage_info[] =
"Write the configuration and compile time options to standard output\n"
"# and exit.\n"
"#"
;
Option opt_info = { "info", NULL, usage_info };
static const char usage_access_check_headers[] =
"When enabled, this option will perform extra access-db lookups\n"
"# with the Sender, From, To, and Cc headers using milter-link-from:\n"
"# milter-link-to:, and combo tags, as described by access-db. This\n"
"# allows special B/W list configurations.\n"
"#"
;
Option opt_access_check_headers = { "access-check-headers", "-", usage_access_check_headers };
static const char usage_access_check_body[] =
"When enabled, this option will perform supplemental milter-link-body\n"
"# combo tag lookups for each URI, IP, domain, or mail address found in\n"
"# the message body. This allows special B/W list configurations.\n"
"#"
;
Option opt_access_check_body = { "access-check-body", "-", usage_access_check_body };
#ifdef DROPPED_ADD_HEADERS
static Option optAddHeaders = { "add-headers", "-", "Add extra informational headers when message passes." };
#endif
static Option *optTable[] = {
&optIntro,
&opt_access_check_headers,
&opt_access_check_body,
#ifdef DROPPED_ADD_HEADERS
&optAddHeaders,
#endif
&opt_date_policy,
&opt_date_required,
DNS_LIST_OPTIONS_TABLE,
PDQ_OPTIONS_TABLE,
&opt_domain_bl,
&opt_info,
&opt_mail_bl,
&opt_mail_bl_domains,
&opt_mail_bl_headers,
&opt_mail_bl_max,
&opt_mail_bl_policy,
&opt_links_policy,
&opt_links_test,
&opt_links_timeout,
&opt_subject_tag,
&opt_uri_a_bl,
&opt_uri_bl,
&opt_uri_bl_headers,
&opt_uri_bl_helo,
&opt_uri_bl_policy,
&opt_uri_bl_port_list,
&opt_uri_bl_sub_domains,
&opt_uri_max_test,
&opt_uri_ns_bl,
&opt_uri_ns_a_bl,
&opt_version,
NULL
};
/***********************************************************************
*** Stats
***********************************************************************/
typedef struct {
const char *name;
unsigned long count;
} Stat;
static pthread_mutex_t stat_mutex;
#define STAT_DECLARE(name) \
static const char stat_name_##name[] = #name; \
static Stat stat_##name = { stat_name_##name }
#define STAT_POINTER(name) &stat_##name
STAT_DECLARE(start_time);
#ifdef HAVE_SMFI_VERSION
STAT_DECLARE(run_time);
#endif
STAT_DECLARE(connect_active);
STAT_DECLARE(connect_total);
STAT_DECLARE(connect_error);
STAT_DECLARE(transactions);
STAT_DECLARE(access_bl);
STAT_DECLARE(access_wl);
STAT_DECLARE(access_other);
STAT_DECLARE(helo_fail);
STAT_DECLARE(link_fail);
STAT_DECLARE(mail_fail);
STAT_DECLARE(origin_fail);
STAT_DECLARE(uri_fail);
STAT_DECLARE(tag);
STAT_DECLARE(error);
STAT_DECLARE(reject);
STAT_DECLARE(discard);
STAT_DECLARE(tempfail);
#ifdef HAVE_SMFI_QUARANTINE
STAT_DECLARE(quarantine);
#endif
#define SMF_MAX_MULTILINE_REPLY 32
#ifdef HAVE_SMFI_VERSION
static Stat *stat_table[SMF_MAX_MULTILINE_REPLY] = {
&stat_start_time,
&stat_run_time,
&stat_connect_active,
&stat_connect_total,
&stat_connect_error,
&stat_transactions,
&stat_tag,
&stat_error,
&stat_reject,
&stat_discard,
&stat_tempfail,
#ifdef HAVE_SMFI_QUARANTINE
&stat_quarantine,
#endif
&stat_access_bl,
&stat_access_wl,
&stat_access_other,
&stat_link_fail,
&stat_helo_fail,
&stat_mail_fail,
&stat_origin_fail,
&stat_uri_fail,
NULL
};
#endif /* HAVE_SMFI_VERSION */
void
statInit(void)
{
(void) pthread_mutex_init(&stat_mutex, NULL);
(void) time((time_t *) &stat_start_time.count);
}
void
statFini(void)
{
(void) pthreadMutexDestroy(&stat_mutex);
}
void
statGet(Stat *stat, Stat *out)
{
if (!pthread_mutex_lock(&stat_mutex)) {
*out = *stat;
(void) pthread_mutex_unlock(&stat_mutex);
} else {
(void) memset(out, 0, sizeof (*out));
}
}
void
statSetValue(Stat *stat, unsigned long value)
{
if (!pthread_mutex_lock(&stat_mutex)) {
stat->count = value;
(void) pthread_mutex_unlock(&stat_mutex);
}
}
void
statAddValue(Stat *stat, long value)
{
if (!pthread_mutex_lock(&stat_mutex)) {
stat->count += value;
(void) pthread_mutex_unlock(&stat_mutex);
}
}
void
statCount(Stat *stat)
{
statAddValue(stat, 1);
}
/***********************************************************************
***
***********************************************************************/
DnsList *d_bl_list;
DnsList *ip_bl_list;
DnsList *ns_bl_list;
DnsList *ns_ip_bl_list;
DnsList *uri_bl_list;
DnsList *mail_bl_list;
Vector uri_bl_headers;
Vector mail_bl_headers;
Vector mail_bl_domains;
Vector port_list;
long *ports;
static sfsistat
testMail(workspace data, const char *mail)
{
sfsistat rc;
const char *list_name;
rc = SMFIS_CONTINUE;
if (data->policy != POLICY_UNDEFINED)
return SMFIS_CONTINUE;
if (0 < opt_mail_bl_max.value
&& opt_mail_bl_max.value <= VectorLength(data->mail_tested)) {
if (!data->stop_mail_scanning)
smfLog(SMF_LOG_INFO, TAG_FORMAT "mail-bl-max reached", TAG_ARGS);
data->stop_mail_scanning = 1;
return SMFIS_CONTINUE;
}
if ((list_name = dnsListQueryMail(mail_bl_list, data->pdq, mail_bl_domains, data->mail_tested, mail)) != NULL) {
(void) snprintf(data->reply, sizeof (data->reply), black_listed_mail_format, mail, list_name);
dnsListLog(data->work.qid, mail, list_name);
data->policy = *opt_mail_bl_policy.string;
rc = data->policy == POLICY_REJECT ? SMFIS_REJECT : SMFIS_CONTINUE;
statCount(&stat_mail_fail);
}
smfLog(SMF_LOG_DEBUG, TAG_FORMAT "testMail(%lx, \"%s\") rc=%d policy=%x reply='%s'", TAG_ARGS, (long) data, mail, rc, data->policy, data->reply);
return rc;
}
static sfsistat
testMailUri(workspace data, URI *uri)
{
return testMail(data, uri->uriDecoded);
}
void
mime_test_mail_uri(URI *uri, void *_data)
{
workspace data = _data;
data->uri_found_rc = testMailUri(data, uri);
}
static sfsistat
access_mail(workspace data, const char *value, long parseFlags)
{
smdb_code access;
smfLog(SMF_LOG_TRACE, TAG_FORMAT "%s(%p, %s, 0x%lx)", TAG_ARGS, __func__, data, TextNull(value), parseFlags);
access = smfAccessMail2(&data->work, MILTER_NAME "-from:", value, parseFlags, SMDB_ACCESS_UNKNOWN);
switch (access) {
case SMDB_ACCESS_ERROR:
case SMDB_ACCESS_REJECT:
data->policy = POLICY_REJECT;
statCount(&stat_access_bl);
(void) TextCopy(data->reply, sizeof (data->reply), "sender blocked");
return smfReply(&data->work, 550, "5.7.1", data->reply);
case SMDB_ACCESS_TEMPFAIL:
data->policy = POLICY_TAG;
statCount(&stat_tempfail);
(void) TextCopy(data->reply, sizeof (data->reply), "sender blocked");
return smfReply(&data->work, 450, "4.7.1", data->reply);
case SMDB_ACCESS_DISCARD:
data->policy = POLICY_DISCARD;
statCount(&stat_discard);
smfLog(SMF_LOG_INFO, TAG_FORMAT "sender %s discard", TAG_ARGS, value);
return SMFIS_DISCARD;
case SMDB_ACCESS_OK:
data->work.skipMessage = 1;
smfLog(SMF_LOG_INFO, TAG_FORMAT "sender %s white listed", TAG_ARGS, value);
statCount(&stat_access_wl);
return SMFIS_ACCEPT;
default:
statCount(&stat_access_other);
smfLog(SMF_LOG_DEBUG, TAG_FORMAT "sender %s unknown access value", TAG_ARGS, value);
break;
}
return SMFIS_CONTINUE;
}
static sfsistat
access_rcpt(workspace data, const char *value, long parseFlags)
{
smdb_code access;
smfLog(SMF_LOG_TRACE, TAG_FORMAT "%s(%p, %s, 0x%lX)", TAG_ARGS, __func__, data, TextNull(value), parseFlags);
access = smfAccessRcpt2(&data->work, MILTER_NAME "-to:", value, parseFlags);
switch (access) {
case SMDB_ACCESS_ERROR:
case SMDB_ACCESS_REJECT:
data->policy = POLICY_REJECT;
statCount(&stat_access_bl);
(void) TextCopy(data->reply, sizeof (data->reply), "blocked");
return smfReply(&data->work, 550, "5.7.1", "recipient blocked");
case SMDB_ACCESS_TEMPFAIL:
data->policy = POLICY_TAG;
statCount(&stat_tempfail);
(void) TextCopy(data->reply, sizeof (data->reply), "blocked");
return smfReply(&data->work, 450, "4.7.1", "recipient blocked");
case SMDB_ACCESS_DISCARD:
data->policy = POLICY_DISCARD;
statCount(&stat_discard);
smfLog(SMF_LOG_INFO, TAG_FORMAT "recipient %s discard", TAG_ARGS, value);
return SMFIS_DISCARD;
case SMDB_ACCESS_OK:
data->work.skipMessage = 1;
statCount(&stat_access_wl);
smfLog(SMF_LOG_INFO, TAG_FORMAT "recipient %s white listed", TAG_ARGS, value);
return SMFIS_ACCEPT;
default:
smfLog(SMF_LOG_DEBUG, TAG_FORMAT "recipient %s unknown access value", TAG_ARGS, value);
statCount(&stat_access_other);
break;
}
return SMFIS_CONTINUE;
}
static sfsistat
access_body_combo(workspace data, const char *ip, const char *host, const char *tag, const char *mail)
{
char *copy;
const char *uri;
smdb_code access;
smfLog(
SMF_LOG_TRACE, TAG_FORMAT "%s(%p, %s, %s, %s, %s)", TAG_ARGS,
__func__, data, TextNull(ip), TextNull(host), tag, mail
);
if (ip != NULL) {
uri = ip;
access = smdbIpMail(smdbAccess, MILTER_NAME "-body:", ip, tag, mail, NULL, NULL);
} else if (host != NULL) {
uri = host;
access = smdbDomainMail(smdbAccess, MILTER_NAME "-body:", host, tag, mail, NULL, NULL);
} else {
return SMFIS_CONTINUE;
}
switch (access) {
case SMDB_ACCESS_OK:
smfLog(SMF_LOG_INFO, TAG_FORMAT "URI \"%s\" ignored", TAG_ARGS, uri);
/* Only mark as seen ignored URI from combo tags so as
* not to retest the URI by the simple milter-link-body:
* tag. The message is not white listed, only that URI
*(host/domian) is white listed/ignored.
*/
if (VectorAdd(data->uri_tested, copy = strdup(uri)))
free(copy);
/* Accept here does not accept message, simply indicates
* we found an entry and can stop further combo lookups.
*/
return SMFIS_ACCEPT;
case SMDB_ACCESS_REJECT:
(void) snprintf(data->reply, sizeof (data->reply), "URI \"%s\" blocked", uri);
data->policy = POLICY_REJECT;
return SMFIS_REJECT;
case SMDB_ACCESS_DISCARD:
(void) snprintf(data->reply, sizeof (data->reply), "discarded for URI \"%s\"", uri);
data->policy = POLICY_DISCARD;
return SMFIS_DISCARD;
case SMDB_ACCESS_TEMPFAIL:
data->policy = POLICY_TAG;
break;
default:
smfLog(SMF_LOG_DEBUG, TAG_FORMAT "URI \"%s\" unknown access value", TAG_ARGS, uri);
break;
}
return SMFIS_CONTINUE;
}
void
mime_collect_headers_mail(URI *uri, void *_data)
{
char *copy;
workspace data = _data;
smfLog(
SMF_LOG_TRACE, TAG_FORMAT "%s(\"%s\", %p)",
TAG_ARGS, __func__, uri->uriDecoded, _data
);
if (VectorAdd(data->senders, copy = strdup(uri->uriDecoded)))
free(copy);
}
void
mime_collect_headers_rcpt(URI *uri, void *_data)
{
char *copy;
workspace data = _data;
smfLog(
SMF_LOG_TRACE, TAG_FORMAT "%s(\"%s\", %p)",
TAG_ARGS, __func__, uri->uriDecoded, _data
);
if (VectorAdd(data->recipients, copy = strdup(uri->uriDecoded)))
free(copy);
}
static sfsistat
testString(workspace data, const char *name, const char *value, UriMimeHook test_fn)
{
Mime *mime;
UriMime *uri_mime;
smfLog(
SMF_LOG_TRACE, TAG_FORMAT "%s(%p, '%s', '%s', %p)",
TAG_ARGS, __func__, data, TextNull(name), TextNull(value), test_fn
);
if (name == NULL || value == NULL)
return SMFIS_CONTINUE;
if ((mime = mimeCreate()) == NULL)
return SMFIS_CONTINUE;
if ((uri_mime = uriMimeInit(test_fn, 1, data)) == NULL) {
free(mime);
return SMFIS_CONTINUE;
}
data->mime_string_name = name;
mimeHooksAdd(mime, (MimeHooks *) uri_mime);
mimeHeadersFirst(mime, 0);
for ( ; data->uri_found_rc == SMFIS_CONTINUE && *value != '\0'; value++) {
if (mimeNextCh(mime, *value))
break;
}
(void) mimeNextCh(mime, EOF);
mimeFree(mime);
return data->uri_found_rc;
}
static sfsistat
testURI(workspace data, URI *uri)
{
long i;
const char *error;
URI *origin = NULL;
char *host, *ip, *copy;
const char *list_name = NULL;
sfsistat rc = SMFIS_REJECT;
smdb_code access;
if (uri == NULL || uri->host == NULL)
return SMFIS_CONTINUE;
if (ports != NULL) {
long *p;
for (p = ports; 0 <= *p; p++) {
if (uriGetSchemePort(uri) == *p)
break;
}
if (*p < 0)
return SMFIS_CONTINUE;
}
if (0 < opt_uri_max_test.value
&& opt_uri_max_test.value <= VectorLength(data->uri_tested)) {
if (!data->stop_uri_scanning)
smfLog(SMF_LOG_INFO, TAG_FORMAT "uri-max-test reached", TAG_ARGS);
data->stop_uri_scanning = 1;
return SMFIS_CONTINUE;
}
/* Session cache for previously PASSED hosts/domains. */
for (i = 0; i < VectorLength(data->uri_tested); i++) {
if ((host = VectorGet(data->uri_tested, i)) == NULL)
continue;
if (TextInsensitiveCompare(uri->host, host) == 0)
return SMFIS_CONTINUE;
}
/* Be sure to apply the correct access lookup. */
if (0 < spanIP((unsigned char *) uri->host)) {
ip = uri->host;
host = NULL;
} else {
ip = NULL;
host = uri->host;
}
if (opt_access_check_body.value) {
ParsePath *saved_mail;
const char **table, **sender;
for (table = (const char **) VectorBase(data->senders); *table != NULL; table++) {
if ((rc = access_body_combo(data, ip, host, ":from:", *table)) != SMFIS_CONTINUE) {
if (rc == SMFIS_ACCEPT)
rc = SMFIS_CONTINUE;
goto error0;
}
}
saved_mail = data->work.mail;
for (sender = (const char **) VectorBase(data->senders); *sender != NULL; sender++) {
ParsePath *path;
if ((error = parsePath(*sender, 0, 0, &path)) != NULL) {
/* Sender parse errors found in the headers
* will have already been reported during
* filterEndHeaders().
*/
smfLog(LOG_DEBUG, TAG_FORMAT "sender %s parse error: %s", TAG_ARGS, *sender, error);
continue;
}
data->work.mail = path;
for (table = (const char **) VectorBase(data->recipients); *table != NULL; table++) {
if ((rc = access_body_combo(data, ip, host, ":to:", *table)) != SMFIS_CONTINUE) {
if (rc == SMFIS_ACCEPT)
rc = SMFIS_CONTINUE;
data->work.mail = saved_mail;
free(path);
goto error0;
}
}
free(path);
}
data->work.mail = saved_mail;
}
/* Simply single tag. */
access = smfAccessClient(&data->work, MILTER_NAME "-body:", host, ip, NULL, NULL);
switch (access) {
case SMDB_ACCESS_OK:
smfLog(SMF_LOG_INFO, TAG_FORMAT "URI \"%s\" ignored", TAG_ARGS, uri->uri);
return SMFIS_CONTINUE;
case SMDB_ACCESS_REJECT:
(void) snprintf(data->reply, sizeof (data->reply), "rejected URL host %s", uri->host);
data->policy = POLICY_REJECT;
rc = SMFIS_REJECT;
goto error0;
case SMDB_ACCESS_DISCARD:
(void) snprintf(data->reply, sizeof (data->reply), "discarded for \"%s\"", uri->uri);
data->policy = POLICY_DISCARD;
rc = SMFIS_DISCARD;
goto error0;
default:
smfLog(SMF_LOG_DEBUG, TAG_FORMAT "URI \"%s\" unknown access value", TAG_ARGS, uri->uri);
break;
}
if (VectorAdd(data->uri_tested, copy = strdup(uri->host)))
free(copy);
rc = SMFIS_REJECT;
if ((list_name = dnsListQueryName(d_bl_list, data->pdq, NULL, uri->host)) != NULL
|| (list_name = dnsListQueryDomain(uri_bl_list, data->pdq, NULL, opt_uri_bl_sub_domains.value, uri->host)) != NULL
|| (list_name = dnsListQueryNs(ns_bl_list, ns_ip_bl_list, data->pdq, data->ns_tested, uri->host)) != NULL
|| (list_name = dnsListQueryIP(ip_bl_list, data->pdq, NULL, uri->host)) != NULL) {
(void) snprintf(data->reply, sizeof (data->reply), black_listed_url_format, uri->host, list_name);
dnsListLog(data->work.qid, uri->host, list_name);
data->policy = *opt_uri_bl_policy.string;
statCount(&stat_uri_fail);
goto error0;
}
dnsListLog(data->work.qid, uri->host, NULL);
/* Test and follow redirections so verify that the link returns something valid. */
if (opt_links_test.value && (error = uriHttpOrigin(uri->uri, &origin)) == uriErrorLoop) {
(void) snprintf(data->reply, sizeof (data->reply), "broken URL \"%s\": %s", uri->uri, error);
data->policy = *opt_links_policy.string;
statCount(&stat_link_fail);
goto error0;
}
if (origin != NULL && origin->host != NULL && strcmp(uri->host, origin->host) != 0) {
if ((list_name = dnsListQueryName(d_bl_list, data->pdq, NULL, origin->host)) != NULL
|| (list_name = dnsListQueryDomain(uri_bl_list, data->pdq, NULL, opt_uri_bl_sub_domains.value, origin->host)) != NULL
|| (list_name = dnsListQueryNs(ns_bl_list, ns_ip_bl_list, data->pdq, data->ns_tested, origin->host)) != NULL
|| (list_name = dnsListQueryIP(ip_bl_list, data->pdq, NULL, origin->host)) != NULL) {
(void) snprintf(data->reply, sizeof (data->reply), black_listed_url_format, origin->host, list_name);
dnsListLog(data->work.qid, origin->host, list_name);
data->policy = *opt_uri_bl_policy.string;
statCount(&stat_origin_fail);
goto error1;
}
if (VectorAdd(data->uri_tested, copy = strdup(origin->host)))
free(copy);
dnsListLog(data->work.qid, origin->host, NULL);
}
rc = SMFIS_CONTINUE;
error1:
free(origin);
error0:
smfLog(SMF_LOG_DEBUG, TAG_FORMAT "testURI(%lx, \"%s\") rc=%d reply='%s'", TAG_ARGS, (long) data, uri->uri, rc, data->reply);
return rc;
}
void
mime_test_uri(URI *uri, void *_data)
{
workspace data = _data;
data->uri_found_rc = testURI(data, uri);
}
static sfsistat