comparison dccproc/dccproc.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c7f6b056b673
1 /* Distributed Checksum Clearinghouse server
2 *
3 * report a message for such as procmail
4 *
5 * Copyright (c) 2008 by Rhyolite Software, LLC
6 *
7 * This agreement is not applicable to any entity which sells anti-spam
8 * solutions to others or provides an anti-spam solution as part of a
9 * security solution sold to other entities, or to a private network
10 * which employs the DCC or uses data provided by operation of the DCC
11 * but does not provide corresponding data to other users.
12 *
13 * Permission to use, copy, modify, and distribute this software without
14 * changes for any purpose with or without fee is hereby granted, provided
15 * that the above copyright notice and this permission notice appear in all
16 * copies and any distributed versions or copies are either unchanged
17 * or not called anything similar to "DCC" or "Distributed Checksum
18 * Clearinghouse".
19 *
20 * Parties not eligible to receive a license under this agreement can
21 * obtain a commercial license to use DCC by contacting Rhyolite Software
22 * at sales@rhyolite.com.
23 *
24 * A commercial license would be for Distributed Checksum and Reputation
25 * Clearinghouse software. That software includes additional features. This
26 * free license for Distributed ChecksumClearinghouse Software does not in any
27 * way grant permision to use Distributed Checksum and Reputation Clearinghouse
28 * software
29 *
30 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
31 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
33 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
34 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
35 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
36 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
37 * SOFTWARE.
38 *
39 * Rhyolite Software DCC 1.3.103-1.189 $Revision$
40 */
41
42 #include "dcc_ck.h"
43 #include "dcc_xhdr.h"
44 #include "dcc_heap_debug.h"
45 #include <signal.h> /* for Linux and SunOS*/
46 #ifndef DCC_WIN32
47 #include <arpa/inet.h>
48 #endif
49
50
51 static DCC_EMSG dcc_emsg;
52
53 static const char *mapfile_nm = DCC_MAP_NM_DEF;
54
55 static u_char priv_logdir;
56 static DCC_PATH log_path;
57 static int lfd = -1;
58 static struct timeval ldate;
59
60 static u_char logging = 1; /* 0=no log, 1=have file, 2=used it */
61 static size_t log_size;
62
63 static char id[DCC_MSG_ID_LEN+1];
64 static DCC_PATH tmp_nm;
65 static int tmp_fd = -1;
66 static u_char tmp_rewound;
67 static int hdrs_len, body_len;
68 static u_char seen_hdr;
69
70 static int exit_code = EX_NOUSER;
71 static DCC_TGTS local_tgts;
72 static u_char local_tgts_spam, local_tgts_set;
73 static int total_hdrs, cr_hdrs;
74
75 static const char* white_nm;
76 static const char *ifile_nm = "stdin", *ofile_nm = "stdout";
77 static FILE *ifile, *ofile;
78
79 static DCC_CLNT_CTXT *ctxt;
80 static char xhdr_fname[sizeof(DCC_XHDR_START)+sizeof(DCC_BRAND)+1];
81 static int xhdr_fname_len;
82 static u_char add_xhdr; /* add instead of replace header */
83 static u_char cksums_only; /* output only checksums */
84 static u_char x_dcc_only; /* output only the X-DCC header */
85 static u_char fake_envelope; /* fake envelope log lines */
86 static int std_received; /* Received: line is standard */
87
88 static ASK_ST ask_st;
89 static FLTR_SWS rcpt_sws;
90 static DCC_GOT_CKS cks;
91 static DCC_CKS_WTGTS wtgts;
92 static char helo[DCC_HELO_MAX];
93 static char sender_name[DCC_MAXDOMAINLEN];
94 static char sender_str[INET6_ADDRSTRLEN];
95 static struct in6_addr clnt_addr;
96
97 static char env_from_buf[DCC_HDR_CK_MAX+1];
98 static const char *env_from = 0;
99
100 static char mail_host[DCC_MAXDOMAINLEN];
101
102 static DCC_HEADER_BUF header;
103
104 static EARLY_LOG early_log;
105
106 static void start_dccifd(void);
107 static u_char check_mx_listing(void);
108 static int get_hdr(char *, int);
109 static void add_hdr(void *, const char *, u_int);
110 static void tmp_write(const void *, int);
111 static void tmp_close(void);
112 static void scopy(int, u_char);
113 static void log_write(const void *, int);
114 static void log_body_write(const char *, u_int);
115 static void thr_log_write(void *, const char *, u_int);
116 static void log_late(void);
117 static int log_print(u_char, const char *, ...) PATTRIB(2,3);
118 #define LOG_CAPTION(s) log_write((s), LITZ(s))
119 #define LOG_EOL() LOG_CAPTION("\n")
120 static void log_fin(void);
121 static void log_ck(void *, const char *, u_int);
122 static void dccproc_error_msg(const char *, ...) PATTRIB(1,2);
123 static void sigterm(int);
124
125
126 static const char *usage_str =
127 "[-VdAQCHER] [-h homedir] [-m map] [-w whiteclnt] [-T tmpdir]\n"
128 " [-a IP-address] [-f env_from] [-t targets] [-x exitcode]\n"
129 " [-c type,[log-thold,][spam-thold]] [-g [not-]type] [-S header]\n"
130 " [-i infile] [-o outfile] [-l logdir] [-B dnsbl-option]\n"
131 " [-L ltype,facility.level]";
132
133 static void NRATTRIB
134 usage(const char* barg)
135 {
136 if (barg) {
137 dcc_logbad(EX_USAGE, "unrecognized \"%s\"\nusage: %s\n",
138 barg, usage_str);
139 } else {
140 dcc_logbad(EX_USAGE, "%s\n", usage_str);
141 }
142 }
143
144
145
146 int NRATTRIB
147 main(int argc, char **argv)
148 {
149 char buf[20*DCC_HDR_CK_MAX]; /* at least DCC_HDR_CK_MAX*3 */
150 u_char log_tgts_set = 0;
151 const char *homedir = 0;
152 const char *logdir = 0;
153 const char *tmpdir = 0;
154 u_char ask_result;
155 char *p;
156 const char *p2;
157 u_long l;
158 int error, blen, i;
159
160 /* because stderr is often mixed with stdout and effectively
161 * invisible, also complain to syslog */
162 dcc_syslog_init(1, argv[0], 0);
163 dcc_clear_tholds();
164
165 /* get ready for the IP and From header checksums */
166 dcc_cks_init(&cks);
167
168 /* we must be SUID to read and write the system's common connection
169 * parameter memory mapped file. We also need to read the common
170 * local white list and write the mmap()'ed hash file */
171 dcc_init_priv();
172
173 ofile = stdout;
174 ifile = stdin;
175 opterr = 0;
176 while ((i = getopt(argc, argv, "VdAQCHER"
177 "r:h:m:w:T:a:f:g:S:t:x:c:i:o:l:B:L:")) != -1) {
178 switch (i) {
179 case 'V':
180 fprintf(stderr, DCC_VERSION"\n");
181 exit(EX_OK);
182 break;
183
184 case 'd':
185 ++dcc_clnt_debug;
186 break;
187
188 case 'A':
189 add_xhdr = 1;
190 break;
191
192 case 'Q':
193 ask_st |= ASK_ST_QUERY;
194 break;
195
196 case 'C':
197 cksums_only = 1;
198 break;
199
200 case 'H':
201 x_dcc_only = 1;
202 break;
203
204 case 'E':
205 fake_envelope = 1;
206 break;
207
208 case 'R':
209 if (!std_received)
210 std_received = 1;
211 break;
212
213 case 'r': /* a bad idea replacment for -R */
214 std_received = strtoul(optarg, &p, 10);
215 if (*p != '\0' || i == 0) {
216 dccproc_error_msg("invalid count"
217 " \"-e %s\"", optarg);
218 std_received = 1;
219 }
220 break;
221
222 case 'h':
223 homedir = optarg;
224 break;
225
226 case 'm':
227 mapfile_nm = optarg;
228 break;
229
230 case 'w':
231 white_nm = optarg;
232 break;
233
234 case 'T':
235 tmpdir = optarg;
236 break;
237
238 case 'a':
239 /* ignore SpamAssassin noise */
240 if (!strcmp("0.0.0.0", optarg))
241 break;
242 dcc_host_lock();
243 if (!dcc_get_host(optarg, 2, &error)) {
244 dccproc_error_msg("\"-a %s\": %s",
245 optarg, DCC_HSTRERROR(error));
246 } else {
247 if (dcc_hostaddrs[0].sa.sa_family == AF_INET)
248 dcc_ipv4toipv6(&clnt_addr,
249 dcc_hostaddrs[0]
250 .ipv4.sin_addr);
251 else
252 clnt_addr = (dcc_hostaddrs[0].ipv6
253 .sin6_addr);
254 dcc_get_ipv6_ck(&cks, &clnt_addr);
255 dcc_ipv6tostr(sender_str, sizeof(sender_str),
256 &clnt_addr);
257 }
258 dcc_host_unlock();
259 break;
260
261 case 'f':
262 env_from = optarg;
263 break;
264
265 case 'g': /* honor not-spam "counts" */
266 dcc_parse_honor(optarg);
267 break;
268
269 case 'S':
270 if (!dcc_add_sub_hdr(dcc_emsg, optarg))
271 dcc_logbad(EX_USAGE, "%s", dcc_emsg);
272 break;
273
274 case 't':
275 if (!strcasecmp(optarg, "many")) {
276 local_tgts = 1;
277 local_tgts_spam = 1;
278 local_tgts_set = 1;
279 } else {
280 l = strtoul(optarg, &p, 10);
281 if (*p != '\0' || l > DCC_TGTS_RPT_MAX) {
282 dccproc_error_msg("invalid count"
283 " \"-t %s\"", optarg);
284 } else {
285 local_tgts = l;
286 local_tgts_spam = 0;
287 local_tgts_set = 1;
288 }
289 }
290 break;
291
292 case 'x':
293 l = strtoul(optarg, &p, 10);
294 if (*p != '\0') {
295 dccproc_error_msg("invalid exit code \"-x %s\"",
296 optarg);
297 } else {
298 exit_code = l;
299 }
300 break;
301
302 case 'c':
303 if (dcc_parse_tholds("-c ", optarg))
304 log_tgts_set = 1;
305 break;
306
307 case 'i':
308 /* open the input file now, before changing to the
309 * home DCC directory */
310 ifile_nm = optarg;
311 ifile = fopen(ifile_nm, "r");
312 if (!ifile)
313 dcc_logbad(EX_USAGE,
314 "bad input file \"%s\": %s",
315 ifile_nm, ERROR_STR());
316 break;
317
318 case 'o':
319 /* open the output file now, before changing to the
320 * home DCC directory */
321 ofile_nm = optarg;
322 ofile = fopen(ofile_nm, "w");
323 if (!ofile)
324 dcc_logbad(EX_USAGE,
325 "bad output file \"%s\": %s",
326 ofile_nm, ERROR_STR());
327 break;
328
329 case 'l':
330 logdir = optarg;
331 break;
332
333 case 'B':
334 if (!dcc_parse_dnsbl(dcc_emsg, optarg, 0, 1))
335 dccproc_error_msg("%s", dcc_emsg);
336 break;
337
338 #ifndef DCC_WIN32
339 case 'L':
340 dcc_parse_log_opt(optarg);
341 break;
342 #endif
343
344 default:
345 usage(optopt2str(optopt));
346 }
347 }
348 if (argc != optind)
349 usage(argv[optind]);
350
351 #ifdef SIGPIPE
352 signal(SIGPIPE, SIG_IGN);
353 #endif
354 #ifdef SIGHUP
355 signal(SIGHUP, sigterm);
356 #endif
357 signal(SIGTERM, sigterm);
358 signal(SIGINT, sigterm);
359 #ifdef SIGXFSZ
360 signal(SIGXFSZ, SIG_IGN);
361 #endif
362
363 /* Close STDERR to keep it from being mixed with the message,
364 * unless we are not going to output the message to STDOUT.
365 * Ensure that stderr and file descriptor 2 are open to something
366 * to prevent surprises from busybody libraries. */
367 if (!dcc_clnt_debug && !cksums_only && !x_dcc_only && !ofile_nm) {
368 close(STDERR_FILENO);
369 clean_stdio();
370 }
371
372 dcc_clnt_unthread_init();
373 dcc_cdhome(0, homedir, 0);
374 if (!dcc_main_logdir_init(dcc_emsg, logdir)) {
375 dcc_error_msg("%s", dcc_emsg);
376 /* dccproc will not be around as a daemon
377 * when and if the log directory is created
378 * so forget about a directory that might
379 * someday be ok */
380 dcc_main_logdir[0] = '\0';
381 }
382 tmp_path_init(tmpdir, logdir);
383
384 if (dcc_main_logdir[0] == '\0') {
385 if (log_tgts_set)
386 dccproc_error_msg("log thresholds set with -c"
387 " but no -l directory");
388 logging = 0;
389 } else {
390 #ifndef DCC_WIN32
391 /* use privileges to make log files in the built-in home
392 * directory */
393 if (!homedir
394 && 0 > access(dcc_main_logdir, R_OK|W_OK|X_OK)) {
395 priv_logdir = 1;
396 dcc_get_priv_home(dcc_main_logdir);
397 }
398 #endif
399 lfd = dcc_main_log_open(dcc_emsg, log_path, id, sizeof(id));
400 if (priv_logdir)
401 dcc_rel_priv();
402 if (lfd < 0) {
403 dccproc_error_msg("%s", dcc_emsg);
404 logging = 0;
405 }
406 }
407
408 if (fake_envelope && lfd >= 0) {
409 char date_buf[40];
410
411 gettimeofday(&ldate, 0);
412 log_print(0, DCC_LOG_DATE_PAT"\n",
413 dcc_time2str(date_buf, sizeof(date_buf),
414 DCC_LOG_DATE_FMT,
415 ldate.tv_sec));
416 }
417
418 if (!local_tgts_set) {
419 local_tgts = (ask_st & ASK_ST_QUERY) ? 0 : 1;
420 local_tgts_spam = 0;
421 } else if (local_tgts == 0) {
422 ask_st |= ASK_ST_QUERY;
423 local_tgts_spam = 0;
424 } else if (ask_st & ASK_ST_QUERY) {
425 dcc_error_msg("\"-t %s\" is incompatible with \"-Q\"",
426 local_tgts_spam
427 ? "many"
428 : dcc_tgts2str(buf, sizeof(buf), local_tgts, 0));
429 local_tgts = 0;
430 local_tgts_spam = 0;
431 }
432 if (local_tgts == DCC_TGTS_TOO_MANY) {
433 local_tgts = 1;
434 local_tgts_spam = 1;
435 }
436
437 if (logging
438 || (!cksums_only && !x_dcc_only)) {
439 tmp_fd = dcc_mkstemp(dcc_emsg, tmp_nm, sizeof(tmp_nm),
440 id, sizeof(id), 0,
441 0, DCC_TMP_LOG_PREFIX, 1, 0);
442 if (tmp_fd < 0)
443 dcc_logbad(EX_IOERR, "%s", dcc_emsg);
444 }
445
446 /* start a connection to a DCC server
447 * we need the server's name for the X-DCC header. */
448 ctxt = dcc_clnt_start(dcc_emsg, 0, mapfile_nm,
449 DCC_CLNT_FG_BAD_SRVR_OK
450 | DCC_CLNT_FG_NO_PICK_SRVR
451 | DCC_CLNT_FG_NO_FAIL);
452 if (ctxt) {
453 if (!homedir)
454 start_dccifd();
455 dcc_emsg[0] = '\0';
456 ctxt = dcc_clnt_start_fin(dcc_emsg, ctxt);
457 }
458 if (!ctxt) {
459 dccproc_error_msg("%s", dcc_emsg);
460 } else {
461 xhdr_fname_len = get_xhdr_fname(xhdr_fname, sizeof(xhdr_fname),
462 dcc_clnt_info);
463 }
464
465 /* get the local whitelist ready */
466 dcc_wf_init(&cmn_wf, DCC_WF_EITHER);
467 if (white_nm
468 && !dcc_new_white_nm(dcc_emsg, &cmn_wf, white_nm)) {
469 dccproc_error_msg("%s", dcc_emsg);
470 white_nm = 0;
471 }
472 /* look past the SMTP client if it is a listed MX server */
473 if (sender_str[0] != '\0' && white_nm)
474 check_mx_listing();
475
476 dcc_dnsbl_init(&cks, ctxt, 0, id);
477
478 /* get the headers */
479 for (;;) {
480 int hlen;
481
482 hlen = get_hdr(buf, sizeof(buf));
483 if (hlen <= 2
484 && (buf[0] == '\n'
485 || (buf[0] == '\r' && buf[1] == '\n'))) {
486 /* stop at the separator between the body and headers */
487 if (!seen_hdr)
488 dcc_logbad(EX_DATAERR,
489 "missing SMTP header lines");
490 hdrs_len -= hlen;
491 body_len = hlen;
492 break;
493 }
494
495 #define GET_HDR_CK(h,t) { \
496 if (!CLITCMP(buf, h)) { \
497 dcc_get_cks(&cks,DCC_CK_##t, &buf[LITZ(h)], 1);\
498 seen_hdr = 1; \
499 continue;}}
500 GET_HDR_CK(DCC_XHDR_TYPE_FROM":", FROM);
501 GET_HDR_CK(DCC_XHDR_TYPE_MESSAGE_ID":", MESSAGE_ID);
502 #undef GET_HDR_CK
503
504 /* notice UNIX From_ line */
505 if (!seen_hdr
506 && !env_from
507 && parse_unix_from(buf, env_from_buf,
508 sizeof(env_from_buf))) {
509 env_from = env_from_buf;
510 seen_hdr = 1;
511 continue;
512 }
513
514 if (!env_from && parse_return_path(buf, env_from_buf,
515 sizeof(env_from_buf))) {
516 env_from = env_from_buf;
517 seen_hdr = 1;
518 continue;
519 }
520
521 if (!CLITCMP(buf, DCC_XHDR_TYPE_RECEIVED":")) {
522 seen_hdr = 1;
523
524 p2 = &buf[LITZ(DCC_XHDR_TYPE_RECEIVED":")];
525
526 /* compute checksum of the last Received: header */
527 dcc_get_cks(&cks, DCC_CK_RECEIVED, p2, 1);
528
529 /* pick IP address out of Nth Received: header
530 * unless we had a good -a value */
531 if (sender_str[0] != '\0')
532 std_received = 0;
533 if (!std_received)
534 continue;
535 if (--std_received > 0)
536 continue;
537
538 p2 = parse_received(p2, &cks, helo, sizeof(helo),
539 sender_str, sizeof(sender_str),
540 sender_name, sizeof(sender_name));
541 if (p2 == 0) {
542 /* to avoid being fooled by forged Received:
543 * fields, do not skip unrecognized forms */
544 std_received = 0;
545 } else if (*p2 != '\0') {
546 log_print(1, "skip %s Received: header\n", p2);
547 std_received = 1;
548 } else {
549 std_received = check_mx_listing();
550 }
551 continue;
552 }
553
554 /* Notice MIME multipart boundary definitions */
555 dcc_ck_mime_hdr(&cks, buf, 0);
556
557 if (dcc_ck_get_sub(&cks, buf, 0))
558 seen_hdr = 1;
559
560 /* notice any sort of header */
561 if (!seen_hdr) {
562 for (p = buf; ; ++p) {
563 if (*p == ':') {
564 seen_hdr = 1;
565 break;
566 }
567 if (*p <= ' ' || *p >= 0x7f)
568 break;
569 }
570 }
571 }
572 /* Create a checksum for a null Message-ID header if there
573 * was no Message-ID header. */
574 if (cks.sums[DCC_CK_MESSAGE_ID].type != DCC_CK_MESSAGE_ID)
575 dcc_get_cks(&cks, DCC_CK_MESSAGE_ID, "", 0);
576
577 /* Check DNS blacklists for STMP client and envelope sender
578 * before collecting the body to avoid wasting time DNS resolving
579 * URLs if the envelope answers the question. Much of the DNS
580 * work for the envelope has probably already been done. */
581 if (cks.sums[DCC_CK_IP].type == DCC_CK_IP)
582 dcc_client_dnsbl(cks.dnsbl, &cks.ip_addr, sender_name);
583
584 if (env_from) {
585 dcc_get_cks(&cks, DCC_CK_ENV_FROM, env_from, 1);
586 if (parse_mail_host(env_from, mail_host, sizeof(mail_host))) {
587 dcc_ck_get_sub(&cks, "mail_host", mail_host);
588 dcc_mail_host_dnsbl(cks.dnsbl, mail_host);
589 }
590 }
591
592 /* collect the body */
593 do {
594 blen = fread(buf, 1, sizeof(buf), ifile);
595 if (blen != sizeof(buf)) {
596 if (ferror(ifile))
597 dcc_logbad(EX_DATAERR, "fgets(%s): %s",
598 ifile_nm, ERROR_STR());
599 if (!blen)
600 break;
601 }
602
603 tmp_write(buf, blen);
604 body_len += blen;
605 dcc_ck_body(&cks, buf, blen);
606 } while (!feof(ifile));
607 fclose(ifile);
608
609 dcc_cks_fin(&cks);
610
611 if (!unthr_ask_white(dcc_emsg, &ask_st, &rcpt_sws,
612 white_nm, &cks, wtgts))
613 dccproc_error_msg("%s", dcc_emsg);
614
615 dcc_dnsbl_result(&ask_st, cks.dnsbl);
616
617 /* Unlike dccm and dccifd, no "option DNSBL-on" line is required in
618 * the whiteclnt file. A -B argument is sufficient to show that
619 * DNSBL filtering is wanted. */
620 if (ask_st & ASK_ST_DNSBL_HIT_M)
621 ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);
622
623 if (ctxt) {
624 if (ask_st & ASK_ST_QUERY) {
625 local_tgts_spam = 0;
626 local_tgts = 0;
627 }
628 if (local_tgts != 0
629 && (ask_st & ASK_ST_CLNT_ISSPAM))
630 local_tgts_spam = 1;
631
632 ask_result = unthr_ask_dcc(dcc_emsg, ctxt, &header, &ask_st,
633 &cks, local_tgts_spam, local_tgts);
634 if (!ask_result)
635 dccproc_error_msg("%s", dcc_emsg);
636 }
637
638 if (fake_envelope && lfd >= 0) {
639 if (sender_str[0] != '\0') {
640 LOG_CAPTION(DCC_XHDR_TYPE_IP": ");
641 log_write(sender_name, strlen(sender_name));
642 LOG_CAPTION(" ");
643 log_write(sender_str, strlen(sender_str));
644 LOG_EOL();
645 }
646 if (helo[0] != '\0') {
647 LOG_CAPTION("HELO: ");
648 log_write(helo, strlen(helo));
649 LOG_EOL();
650 dcc_ck_get_sub(&cks, "helo", helo);
651 }
652 if (env_from) {
653 LOG_CAPTION(DCC_XHDR_TYPE_ENV_FROM": ");
654 log_write(env_from, strlen(env_from));
655 log_print(0, " mail_host=%s", mail_host);
656 LOG_EOL();
657 }
658 LOG_EOL();
659 }
660
661 /* copy the headers to the log file and the output */
662 scopy(hdrs_len, 1);
663
664 /* emit the X-DCC and external filter headers
665 * End them with "\r\n" if at least half of the header lines
666 * ended that way. Otherwise use "\n" */
667 if (header.buf[0] != '\0')
668 xhdr_write(add_hdr, 0, header.buf, header.used,
669 cr_hdrs > total_hdrs/2);
670
671 /* emit body */
672 scopy(body_len, 1);
673
674 LOG_CAPTION(DCC_LOG_MSG_SEP);
675
676 log_late();
677
678 /* make the log file look like a dccm or dccifd log file */
679 if (fake_envelope) {
680 DCC_PATH abs_nm;
681
682 if (ask_st & ASK_ST_WLIST_NOTSPAM)
683 log_print(0, "%s"DCC_XHDR_ISOK"\n",
684 fnm2abs_err(abs_nm, white_nm));
685 else if (ask_st & ASK_ST_WLIST_ISSPAM)
686 log_print(0, "%s%s\n",
687 fnm2abs_err(abs_nm, white_nm),
688 (rcpt_sws & FLTR_SW_TRAP_ACC)
689 ? "-->"DCC_XHDR_TRAP_ACC
690 : (rcpt_sws & FLTR_SW_TRAP_REJ)
691 ? "-->"DCC_XHDR_TRAP_REJ
692 : DCC_XHDR_ISSPAM);
693 log_ask_st(thr_log_write, 0, ask_st, rcpt_sws, 0, &header);
694 }
695
696 /* log error message for most prematurely closed output pipes before
697 * logging the checksums or sending them to the pipe for -C */
698 if (EOF == fflush(ofile)) {
699 dcc_error_msg("fflush(%s): %s", ofile_nm, ERROR_STR());
700 fclose(ofile);
701 ofile = 0;
702 }
703
704 dcc_print_cks(log_ck, 0, local_tgts_spam, local_tgts, &cks, wtgts, 0);
705
706 if (ofile) {
707 if (fclose(ofile))
708 dcc_logbad(EX_IOERR, "fclose(%s): %s",
709 ofile_nm, ERROR_STR());
710 ofile = 0;
711 }
712
713 /* Exit saying it was spam unless this is an accepting spam trap.
714 * Spam traps report all mail as spam, but expect to receive it for
715 * logging or analysis. */
716 if (((ask_st & ASK_ST_CLNT_ISSPAM)
717 || ((ask_st & ASK_ST_SRVR_ISSPAM)
718 && !(rcpt_sws & FLTR_SW_DCC_OFF))
719 || ((ask_st & ASK_ST_REP_ISSPAM)
720 && (rcpt_sws & FLTR_SW_REP_ON)))
721 && !(rcpt_sws & FLTR_SW_TRAP_ACC)) {
722 p2 = "\n"DCC_XHDR_RESULT DCC_XHDR_RESULT_REJECT"\n";
723
724 } else {
725 p2 = "\n"DCC_XHDR_RESULT DCC_XHDR_RESULT_ACCEPT"\n";
726 exit_code = EX_OK;
727 }
728 if (fake_envelope)
729 log_write(p2, strlen(p2));
730
731 log_fin();
732 exit(exit_code);
733
734 #ifdef DCC_WIN32
735 return 0;
736 #endif
737 }
738
739
740
741 static void
742 start_dccifd(void)
743 {
744 #ifndef DCC_WIN32
745 time_t t;
746 int c;
747 pid_t pid;
748
749 assert_info_locked();
750
751 /* once an hour,
752 * start dccifd if dccproc is run more often than
753 * DCCPROC_MAX_CREDITS times at an average rate of at least
754 * DCCPROC_COST times per second */
755
756 t = (ctxt->start.tv_sec/DCCPROC_COST
757 - dcc_clnt_info->dccproc_last/DCCPROC_COST);
758 if (t > DCCPROC_MAX_CREDITS*2) /* don't overflow */
759 t = DCCPROC_MAX_CREDITS*2;
760 else if (t < 0)
761 t = 0;
762 c = t + dcc_clnt_info->dccproc_c;
763 if (c > DCCPROC_MAX_CREDITS)
764 c = DCCPROC_MAX_CREDITS;
765 --c;
766 if (c < -DCCPROC_MAX_CREDITS)
767 c = -DCCPROC_MAX_CREDITS;
768 dcc_clnt_info->dccproc_c = c;
769 dcc_clnt_info->dccproc_last = ctxt->start.tv_sec;
770
771 if (dcc_clnt_info->dccproc_c >= 0)
772 return;
773
774 if (!DCC_IS_TIME(ctxt->start.tv_sec,
775 dcc_clnt_info->dccproc_dccifd_try,
776 DCCPROC_TRY_DCCIFD))
777 return;
778 dcc_clnt_info->dccproc_dccifd_try = (ctxt->start.tv_sec
779 + DCCPROC_TRY_DCCIFD);
780 pid = fork();
781 if (pid) {
782 if (pid < 0)
783 dccproc_error_msg("fork(): %s", ERROR_STR());
784 return;
785 }
786
787 close(STDIN_FILENO);
788 close(STDOUT_FILENO);
789 close(STDERR_FILENO);
790 clean_stdio();
791
792 dcc_get_priv();
793 setuid(dcc_effective_uid);
794 setgid(dcc_effective_gid);
795
796 dcc_trace_msg("try to start dccifd");
797 execl(DCC_LIBEXECDIR"/start-dccifd",
798 "start-dccifd", "-A", (const char *)0);
799 dcc_trace_msg("exec("DCC_LIBEXECDIR"/start-dccifd): %s", ERROR_STR());
800 exit(0);
801 #endif /* DCC_WIN32 */
802 }
803
804
805
806 /* If the immediate SMTP client because it is a listed MX server,
807 * then we must ignore its IP address and keep looking for the
808 * real SMTP client. */
809 static u_char /* 1=listed MX server */
810 check_mx_listing(void)
811 {
812 DCC_TGTS tgts;
813
814 if (!dcc_white_mx(dcc_emsg, &tgts, &cks))
815 dccproc_error_msg("%s", dcc_emsg);
816
817 if (tgts == DCC_TGTS_OK) {
818 return 0;
819 }
820
821 if (tgts == DCC_TGTS_SUBMIT_CLIENT) {
822 log_print(1, "%s is a listed 'submit' client\n",
823 dcc_trim_ffff(sender_str));
824 return 0;
825 }
826
827 if (tgts == DCC_TGTS_OK_MXDCC) {
828 log_print(1, "%s is a whitelisted MX server with DCC client\n",
829 dcc_trim_ffff(sender_str));
830 ask_st |= ASK_ST_QUERY;
831 } else if (tgts == DCC_TGTS_OK_MX) {
832 log_print(1, "%s is a whitelisted MX server\n",
833 dcc_trim_ffff(sender_str));
834 } else {
835 return 0;
836 }
837
838 sender_str[0] = '\0';
839 dcc_unget_ip_ck(&cks);
840
841 /* tell caller to look at the next Received: header */
842 return 1;
843 }
844
845
846
847 /* send a new header to the output and the log */
848 static void
849 add_hdr(void *wp0 UATTRIB, const char *buf, u_int buf_len)
850 {
851 u_int i;
852 const char *estr;
853
854 log_body_write(buf, buf_len);
855 if (ofile) {
856 i = fwrite(buf, 1, buf_len, ofile);
857 if (i != buf_len) {
858 estr = ERROR_STR();
859 fclose(ofile);
860 ofile = 0;
861 dcc_logbad(EX_IOERR, "fwrite(add_hdr %s,%d)=%d: %s",
862 ofile_nm, buf_len, i, estr);
863 }
864 }
865 }
866
867
868
869 /* get the next header line */
870 static int /* header length */
871 get_hdr(char *buf,
872 int buflen) /* >DCC_HDR_CK_MAX*3 */
873 {
874 u_char no_copy;
875 int hlen, wpos;
876 const char *line;
877 char c;
878 int llen, i;
879
880 no_copy = 0;
881 hlen = wpos = 0;
882 for (;;) {
883 line = fgets(&buf[hlen], buflen-hlen, ifile);
884 if (!line) {
885 if (ferror(ifile))
886 dcc_logbad(EX_DATAERR, "fgets(%s): %s",
887 ifile_nm, ERROR_STR());
888 else
889 dcc_logbad(EX_DATAERR, "missing message body");
890 }
891 llen = strlen(line);
892
893 /* delete our X-DCC header at the start of a field */
894 if (hlen == 0 && !add_xhdr
895 && is_xhdr(buf, llen)) {
896 seen_hdr = 1;
897 no_copy = 1;
898 }
899
900 /* do not crash on too-long headers */
901 hlen += llen;
902 if (hlen > DCC_HDR_CK_MAX*2) {
903 /* truncate headers too big for our buffer */
904 if (!no_copy
905 && ((i = (hlen - wpos)) > 0)) {
906 tmp_write(&buf[wpos], i);
907 hdrs_len += i;
908 }
909 c = buf[hlen-1];
910 hlen = DCC_HDR_CK_MAX;
911 buf[hlen++] = '\r';
912 buf[hlen++] = '\n';
913 wpos = hlen;
914 if (c != '\n')
915 continue;
916 }
917
918 /* get the next character after the end-of-line to see if
919 * the next line is a continuation */
920 if (hlen > 2) {
921 i = getc(ifile);
922 if (i != EOF)
923 ungetc(i, ifile);
924 if (i == ' ' || i == '\t')
925 continue;
926 }
927
928 /* not a continuation, so stop reading the field */
929 ++total_hdrs;
930 /* notice if this line ended with "\r\n" */
931 if (hlen > 1 && buf[hlen-2] == '\r')
932 ++cr_hdrs;
933
934 if (!no_copy) {
935 i = hlen - wpos;
936 if (i > 0) {
937 tmp_write(&buf[wpos], hlen-wpos);
938 hdrs_len += i;
939 }
940 return hlen;
941 }
942
943 /* at the end of our X-DCC header, look for another */
944 no_copy = 0;
945 hlen = wpos = 0;
946 }
947 }
948
949
950
951 static void
952 tmp_write(const void *buf, int len)
953 {
954 int i;
955
956 if (tmp_fd < 0)
957 return;
958
959 if (tmp_rewound)
960 dcc_logbad(EX_SOFTWARE, "writing to rewound temp file");
961
962 i = write(tmp_fd, buf, len);
963 if (i != len) {
964 if (i < 0)
965 dcc_logbad(EX_IOERR, "write(%s,%d): %s",
966 tmp_nm, len, ERROR_STR());
967 else
968 dcc_logbad(EX_IOERR, "write(%s,%d)=%d",
969 tmp_nm, len, i);
970 }
971 }
972
973
974
975 static void
976 tmp_close(void)
977 {
978 if (tmp_fd >= 0) {
979 if (0 < close(tmp_fd))
980 dcc_error_msg("close(%s): %s", tmp_nm, ERROR_STR());
981 tmp_fd = -1;
982 }
983 }
984
985
986
987 /* copy some of the temporary file to the output */
988 static void
989 scopy(int total_len, /* copy this much of temporary file */
990 u_char complain) /* 1=ok to complain about problems */
991 {
992 char buf[BUFSIZ];
993 const char *estr;
994 int buf_len, data_len, i;
995
996 if (tmp_fd < 0)
997 return;
998
999 /* if the temporary file has not been rewound,
1000 * then rewind it now */
1001 if (!tmp_rewound) {
1002 tmp_rewound = 1;
1003 if (0 > lseek(tmp_fd, 0, SEEK_SET)) {
1004 estr = ERROR_STR();
1005 tmp_close();
1006 if (complain)
1007 dcc_logbad(EX_IOERR, "rewind(%s): %s",
1008 tmp_nm, estr);
1009 }
1010 }
1011
1012 while (total_len > 0) {
1013 buf_len = sizeof(buf);
1014 if (buf_len > total_len) {
1015 buf_len = total_len;
1016 }
1017
1018 data_len = read(tmp_fd, buf, buf_len);
1019 if (data_len <= 0) {
1020 estr = ERROR_STR();
1021 tmp_close();
1022 if (data_len < 0)
1023 dcc_logbad(EX_IOERR, "read(%s, %d): %s",
1024 tmp_nm, data_len, estr);
1025 if (complain)
1026 dcc_logbad(EX_IOERR, "premature end of %s",
1027 tmp_nm);
1028 return;
1029 }
1030
1031 log_body_write(buf, data_len);
1032
1033 if (ofile && (!cksums_only && !x_dcc_only)) {
1034 i = fwrite(buf, 1, data_len, ofile);
1035 if (i != data_len) {
1036 estr = ERROR_STR();
1037 fclose(ofile);
1038 ofile = 0;
1039 if (complain)
1040 dcc_logbad(EX_IOERR,
1041 "fwrite(scopy %s, %d)=%d:"
1042 " %s",
1043 ofile_nm, data_len, i, estr);
1044 tmp_close();
1045 return;
1046 }
1047 }
1048
1049 total_len -= data_len;
1050 }
1051 }
1052
1053
1054
1055 static void
1056 log_write(const void *buf, int len)
1057 {
1058 int i;
1059
1060 if (lfd < 0)
1061 return;
1062
1063 i = write(lfd, buf, len);
1064 if (i == len) {
1065 logging = 2;
1066 log_size += len;
1067 } else {
1068 dcc_error_msg("write(log %s): %s", log_path, ERROR_STR());
1069 dcc_log_close(0, log_path, lfd, &ldate);
1070 lfd = -1;
1071 logging = 0;
1072 log_path[0] = '\0';
1073 }
1074 }
1075
1076
1077
1078 static void
1079 log_body_write(const char *buf, u_int buflen)
1080 {
1081 int trimlen;
1082 const char *p, *lim;
1083
1084 if (lfd < 0)
1085 return;
1086
1087 /* just write if there is room */
1088 trimlen = MAX_LOG_KBYTE*1024 - log_size;
1089 if (trimlen >= (int)buflen) {
1090 log_write(buf, buflen);
1091 return;
1092 }
1093
1094 /* do nothing if too much already written */
1095 if (trimlen < 0)
1096 return;
1097
1098 /* look for and end-of-line near the end of the buffer
1099 * so that we can make the truncation pretty */
1100 lim = buf;
1101 p = lim+trimlen;
1102 if (trimlen > 90)
1103 lim += trimlen-90;
1104 while (--p > lim) {
1105 if (*p == '\n') {
1106 trimlen = p-buf+1;
1107 break;
1108 }
1109 }
1110 log_write(buf, trimlen);
1111 if (buf[trimlen-1] != '\n')
1112 LOG_EOL();
1113 LOG_CAPTION(DCC_LOG_TRN_MSG_CR);
1114 log_size = MAX_LOG_KBYTE*1024+1;
1115 }
1116
1117
1118
1119 static void
1120 thr_log_write(void *context UATTRIB, const char *buf, u_int len)
1121 {
1122 log_write(buf, len);
1123 }
1124
1125
1126
1127 /* does not append '\n' */
1128 static int
1129 vlog_print(u_char error, const char *p, va_list args)
1130 {
1131 char logbuf[LOGBUF_SIZE];
1132 int i;
1133
1134 /* buffer the message if we cannot write to the log file */
1135 if (error && (lfd < 0 || !tmp_rewound))
1136 return dcc_vearly_log(&early_log, p, args);
1137
1138 if (lfd < 0)
1139 return 0;
1140 i = vsnprintf(logbuf, sizeof(logbuf), p, args);
1141 if (i >= ISZ(logbuf))
1142 i = sizeof(logbuf)-1;
1143 log_write(logbuf, i);
1144 return i;
1145 }
1146
1147
1148
1149 static void
1150 log_late(void)
1151 {
1152 if (early_log.len) {
1153 log_write(early_log.buf, early_log.len);
1154 early_log.len = 0;
1155 }
1156 }
1157
1158
1159
1160 /* does not append '\n' */
1161 static int PATTRIB(2,3)
1162 log_print(u_char error, const char *p, ...)
1163 {
1164 va_list args;
1165 int i;
1166
1167 va_start(args, p);
1168 i = vlog_print(error, p, args);
1169 va_end(args);
1170 return i;
1171 }
1172
1173
1174
1175 /* does not append '\n' */
1176 int
1177 thr_log_print(void *cp UATTRIB, u_char error, const char *p, ...)
1178 {
1179 va_list args;
1180 int i;
1181
1182 va_start(args, p);
1183 i = vlog_print(error, p, args);
1184 va_end(args);
1185 return i;
1186 }
1187
1188
1189
1190 static void
1191 log_fin(void)
1192 {
1193 if (log_path[0] == '\0')
1194 return;
1195
1196 /* Close before renaming to accomodate WIN32 foolishness.
1197 * Assuming dcc_mkstemp() works properly, there is no race */
1198 dcc_log_close(0, log_path, lfd, &ldate);
1199 lfd = -1;
1200 #ifndef DCC_WIN32
1201 if (priv_logdir)
1202 dcc_get_priv_home(dcc_main_logdir);
1203 #endif
1204 if (!(ask_st & ASK_ST_LOGIT)
1205 || !dcc_log_keep(0, log_path)) {
1206 if (0 > unlink(log_path))
1207 dccproc_error_msg("unlink(%s): %s",
1208 log_path, ERROR_STR());
1209 log_path[0] = '\0';
1210 }
1211 if (priv_logdir)
1212 dcc_rel_priv();
1213 }
1214
1215
1216
1217 static void
1218 log_ck(void *arg UATTRIB, const char *buf, u_int buf_len)
1219 {
1220 if (cksums_only && ofile)
1221 fputs(buf, ofile);
1222 log_write(buf, buf_len);
1223 }
1224
1225
1226
1227 /* try to send error message to dccproc log file as well as sendmail */
1228 static int
1229 dccproc_verror_msg(const char *p, va_list args)
1230 {
1231 char logbuf[LOGBUF_SIZE];
1232
1233 /* Some systems including Linux with gcc 3.4.2 on AMD 64 processors
1234 * do not allow two uses of a va_list but requires va_copy()
1235 * Other systems do not have any notion of va_copy(). */
1236 if (vsnprintf(logbuf, sizeof(logbuf), p, args) >= ISZ(logbuf))
1237 strcpy(&logbuf[ISZ(logbuf)-sizeof("...")], "...");
1238
1239 dcc_error_msg(logbuf);
1240
1241 ask_st |= ASK_ST_LOGIT;
1242 return log_print(1, "%s\n", logbuf);
1243 }
1244
1245
1246
1247 /* try to send error message to dccproc log file as well as sendmail */
1248 static void PATTRIB(1,2)
1249 dccproc_error_msg(const char *p, ...)
1250 {
1251 va_list args;
1252
1253 va_start(args, p);
1254 dccproc_verror_msg(p, args);
1255 va_end(args);
1256 }
1257
1258
1259
1260 int
1261 thr_error_msg(void *cp UATTRIB, const char *p, ...)
1262 {
1263 va_list args;
1264 int i;
1265
1266 va_start(args, p);
1267 i = dccproc_verror_msg(p, args);
1268 va_end(args);
1269
1270 return i;
1271 }
1272
1273
1274
1275 void
1276 thr_trace_msg(void *cp UATTRIB, const char *p, ...)
1277 {
1278 va_list args;
1279
1280 va_start(args, p);
1281 dccproc_verror_msg(p, args);
1282 va_end(args);
1283 }
1284
1285
1286
1287 /* things are so sick that we must bail out */
1288 void NRATTRIB
1289 dcc_logbad(int ex_code, const char *p, ...)
1290 {
1291 char buf[BUFSIZ];
1292 va_list args;
1293 size_t len;
1294
1295 log_late();
1296 if (*p >= ' ' && !tmp_rewound) {
1297 va_start(args, p);
1298 dcc_vfatal_msg(p, args);
1299 va_end(args);
1300
1301 ask_st |= ASK_ST_LOGIT;
1302 if (logging > 1)
1303 log_write("\n", 1);
1304 va_start(args, p);
1305 vlog_print(0, p, args);
1306 va_end(args);
1307 log_write("\n\n", 2);
1308 p = 0;
1309 }
1310
1311 /* copy first from the temporary file and then the input
1312 * to try to ensure that we don't lose mail */
1313 scopy(INT_MAX, 0);
1314 if (ifile && ofile && !cksums_only && !x_dcc_only) {
1315 do {
1316 len = fread(buf, 1, sizeof(buf), ifile);
1317 if (!len)
1318 break;
1319 log_write(buf, len);
1320 } while (len == fwrite(buf, 1, len, ofile));
1321 }
1322
1323 if (p && *p >= ' ') {
1324 va_start(args, p);
1325 dcc_vfatal_msg(p, args);
1326 va_end(args);
1327
1328 log_write("\n\n", 2);
1329 va_start(args, p);
1330 vlog_print(0,p, args);
1331 va_end(args);
1332 log_write("\n", 1);
1333 }
1334 log_fin();
1335
1336 if (ex_code == EX_SOFTWARE)
1337 abort();
1338 exit(EX_OK); /* don't tell procmail to reject mail */
1339 }
1340
1341
1342
1343 /* watch for fatal signals */
1344 static void NRATTRIB
1345 sigterm(int sig)
1346 {
1347 log_fin();
1348 exit(-sig);
1349 }