comparison dccifd/dccifd.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
2 *
3 * DCC interface daemon
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.166 $Revision$
40 */
41
42 #include "dccif.h"
43 #include "dcc_paths.h"
44 #include "cmn_defs.h"
45 #include <signal.h>
46
47
48 static int stopping;
49
50 #define MAX_SMTP_LINE
51
52 u_char use_ipv6 = 0;
53
54 /* incoming proxy and bidirectional ASCII dccifd protocol connection */
55 static char *listen_addr;
56 static u_char listen_family;
57 static struct sockaddr_un listen_sun;
58
59 static const char *lhost_port;
60 static const char *rcidr;
61 static struct in6_addr raddr, rmask;
62 static SOCKET listen_soc = -1;
63
64 /* outgoing proxy connection */
65 static u_char proxy_out_family;
66 static struct sockaddr_un proxy_out_sun;
67 static char proxy_out_host[DCC_MAXDOMAINLEN];
68 static u_int16_t proxy_out_port;
69 static DCC_SOCKU proxy_out_su;
70
71 static const char *rundir = DCC_RUNDIR;
72 static DCC_PATH pidpath;
73 static const char *progpath = DCC_LIBEXECDIR"/dccifd";
74
75 static u_char background = 1;
76
77 static u_char proxy;
78
79 u_char cannot_discard = 0; /* can trim targets after DATA */
80 u_char cannot_reject = 1; /* ASCII protocol accepts all targets */
81
82
83 /* message state or context */
84 typedef struct { /* control an input buffer */
85 char *base;
86 char *in;
87 char *out;
88 char *next_line;
89 int size;
90 int line_len;
91 int *socp;
92 } IN_BC;
93 typedef struct { /* control an ouput buffer */
94 char *base;
95 char *in;
96 int len;
97 int size;
98 int *socp;
99 } OUT_BC;
100 typedef struct work {
101 int proxy_in_soc;
102 DCC_SOCKU proxy_in_su;
103 int proxy_out_soc;
104 char buf1[DCC_HDR_CK_MAX*8]; /* >=DCC_HDR_CK_MAX*2 & MAX_RCPTS */
105 char buf2[DCC_HDR_CK_MAX*8];
106 char buf3[1024];
107 char buf4[1024];
108 CMN_WORK cw;
109 enum {
110 SMTP_ST_START, /* expecting HELO */
111 SMTP_ST_HELO, /* seen HELO, expecting Mail_From */
112 SMTP_ST_TRANS, /* seen Mail_From */
113 SMTP_ST_RCPT, /* seen Rcpt_To */
114 SMTP_ST_ERROR /* no transaction until RSET or HELO */
115 } smtp_state;
116 /* from here down is zeroed when the structure is allocated */
117 #define WORK_ZERO fwd
118 struct work *fwd;
119 IN_BC msg_rd; /* incoming mail message */
120 OUT_BC msg_wt; /* outoing mail message */
121 IN_BC reply_in; /* incoming SMTP replies */
122 OUT_BC reply_out; /* outgoing SMTP replies */
123 int total_hdrs, cr_hdrs;
124 int parse_rcvd; /* which received: header to parse */
125 u_int dfgs;
126 # define DFG_WORK_LOCK 0x0001 /* hold the lock */
127 # define DFG_MISSING_BODY 0x0002 /* missing message body */
128 # define DFG_MTA_BODY 0x0004 /* MTA wants the body */
129 # define DFG_MTA_HEADER 0x0008 /* MTA wants the X-DCC header */
130 # define DFG_SEEN_HDR 0x0010 /* have at least 1 header */
131 # define DFG_PARSE_RCVD 0x0020 /* parse Received header */
132 # define DFG_XCLIENT_NAME 0x0040 /* client name via XCLIENT */
133 # define DFG_XCLIENT_ADDR 0x0080 /* client addresses via XCLIENT */
134 # define DFG_XCLIENT_HELO 0x0100 /* HELO value via XCLIENT */
135 # define DFG_RECYCLE (DFG_WORK_LOCK | DFG_MTA_BODY \
136 | DFG_XCLIENT_NAME | DFG_XCLIENT_ADDR \
137 | DFG_XCLIENT_HELO)
138 } WORK;
139
140 /* use a free list to avoid malloc() overhead */
141 static WORK *work_free;
142
143 /* each dccifd job involves
144 * a socket connected to the MTA or upstream proxy
145 * a socket connected to the downstream proxy if using SMTP
146 * a log file,
147 * and a socket to talk to the DCC server.
148 * The file descriptors for the whitelists are accounted for in EXTRA_FILES */
149 #define FILES_PER_JOB 4
150 int max_max_work = MAX_SELECT_WORK;
151
152
153 typedef struct user_domain {
154 struct user_domain *fwd;
155 const char *nm;
156 int len;
157 u_char wildcard;
158 } USER_DOMAIN;
159 static USER_DOMAIN *user_domains;
160 static char hostname[MAXHOSTNAMELEN+1] = "@";
161
162 /* max seconds to wait for MTA
163 * Longer than the longest timeout in RFC 2821 */
164 #define MAX_MTA_DELAY (10*60+5)
165
166
167 static void sigterm(int);
168 static u_char set_soc(DCC_EMSG, int, int, const char *);
169 static void bind_listen(void);
170 static void close_listen_soc(void);
171 static void unlink_listen_sun(void);
172 static void NRATTRIB *job_start(void *);
173 static void job_close(WORK *);
174 static void NRATTRIB job_exit(WORK *);
175 static void NRATTRIB proxy_msg_truncated(WORK *);
176 static void add_work(int);
177
178
179 static void
180 usage(const char* barg, const char *bvar)
181 {
182 const char str[] = {
183 "usage: [-VdbxANQ] [-G on | off | noIP | IPmask/xx] [-h homedir]"
184 " [-I user]\n"
185 " [-p /sock | host,port,rhost/bits]"
186 " [-o /sock | host,port]\n"
187 " [-D local-domain] [-r rejection-msg] [-m map] [-w whiteclnt]\n"
188 " [-U userdirs] [-a IGNORE | REJECT | DISCARD]\n"
189 " [-t type,[log-thold,][rej-thold]] [-g [not-]type]"
190 " [-S header]\n"
191 " [-l logdir] [-R rundir] [-T tmpdir] [-j maxjobs]\n"
192 " [-B dnsbl-option] [-L ltype,facility.level]\n"
193 };
194 static u_char complained;
195
196 if (!complained) {
197 if (barg)
198 dcc_error_msg("unrecognized \"%s%s\"\nusage: %s\n..."
199 " continuing",
200 barg, bvar, str);
201 else
202 dcc_error_msg("%s\n... continuing", str);
203 complained = 1;
204 }
205 }
206
207
208 int NRATTRIB
209 main(int argc, char **argv)
210 {
211 DCC_EMSG emsg;
212 #ifdef RLIMIT_NOFILE
213 struct rlimit nofile;
214 int old_rlim_cur;
215 #endif
216 long l;
217 u_char log_tgts_set = 0;
218 const char *homedir = 0;
219 const char *logdir = 0;
220 const char *tmpdir = 0;
221 WORK *wp;
222 pthread_t tid;
223 DCC_SOCKLEN_T namelen;
224 const char *cp;
225 USER_DOMAIN *udom, *udom2, **udomp;
226 char *p;
227 int error, i;
228
229 emsg[0] = '\0';
230 if (*argv[0] == '/')
231 progpath = argv[0];
232 dcc_syslog_init(1, argv[0], 0);
233 dcc_clear_tholds();
234
235 #ifdef RLIMIT_NOFILE
236 if (0 > getrlimit(RLIMIT_NOFILE, &nofile)) {
237 dcc_error_msg("getrlimit(RLIMIT_NOFILE): %s", ERROR_STR());
238 old_rlim_cur = 1000*1000;
239 } else {
240 old_rlim_cur = nofile.rlim_cur;
241 if (nofile.rlim_max < 1000*1000) {
242 i = nofile.rlim_max;
243 #ifndef USE_POLL
244 if (i > FD_SETSIZE)
245 i = FD_SETSIZE;
246 #endif
247 max_max_work = (i - EXTRA_FILES)/FILES_PER_JOB;
248 max_max_work_src = "RLIMIT_NOFILE limit";
249 }
250 }
251 #endif /* RLIMIT_NOFILE */
252 if (max_max_work <= 0) {
253 dcc_error_msg("too few open files allowed");
254 max_max_work = MIN_MAX_WORK;
255 }
256 max_work = max_max_work;
257
258 #define SLARGS "64VdbxANQW" /* fix start-dccifd if these change */
259 while (-1 != (i = getopt(argc, argv, SLARGS"G:h:I:p:o:D:r:m:w:U:"
260 "a:t:g:S:l:R:T:j:B:L:"))) {
261 switch (i) {
262 #ifndef NO_IPV6
263 case '6':
264 use_ipv6 = 1;
265 break;
266 #endif
267 case '4':
268 use_ipv6 = 0;
269 break;
270
271 case 'V':
272 fprintf(stderr, DCC_VERSION"\n");
273 exit(EX_OK);
274 break;
275
276 case 'd':
277 ++dcc_clnt_debug;
278 break;
279
280 case 'b':
281 background = 0;
282 break;
283
284 case 'x':
285 try_extra_hard = DCC_CLNT_FG_NO_FAIL;
286 break;
287
288 case 'A':
289 chghdr = ADDHDR;
290 break;
291
292 case 'N':
293 chghdr = NOHDR;
294 break;
295
296 case 'Q':
297 dcc_query_only = 1;
298 break;
299
300 case 'G':
301 if (!dcc_parse_client_grey(optarg))
302 usage("-G", optarg);
303 break;
304
305 case 'W': /* obsolete DCC off by default */
306 to_white_only = 1;
307 break;
308
309 case 'h':
310 homedir = optarg;
311 break;
312
313 case 'I':
314 dcc_daemon_su(optarg);
315 break;
316
317 case 'p':
318 listen_addr = optarg;
319 break;
320
321 case 'o':
322 proxy = 1;
323 cannot_reject = 0;
324 cannot_discard = 1;
325 p = strchr(optarg, ',');
326 if (!p) {
327 /* recognize single-ended (not-really-)-proxy */
328 if (!strcmp(optarg, _PATH_DEVNULL)) {
329 proxy_out_family = AF_UNSPEC;
330 break;
331 }
332 if (strlen(optarg)>=ISZ(proxy_out_sun.sun_path))
333 dcc_logbad(EX_USAGE, "invalid UNIX"
334 " domain socket: -o %s",
335 optarg);
336 strcpy(proxy_out_sun.sun_path, optarg);
337 #ifdef HAVE_SA_LEN
338 proxy_out_sun.sun_len = SUN_LEN(&proxy_out_sun);
339 #endif
340 proxy_out_sun.sun_family = AF_UNIX;
341 proxy_out_family = AF_UNIX;
342 } else {
343 cp = dcc_parse_nm_port(emsg, optarg,
344 DCC_GET_PORT_INVALID,
345 proxy_out_host,
346 sizeof(proxy_out_host),
347 &proxy_out_port, 0, 0,
348 0, 0);
349 if (!cp)
350 dcc_logbad(dcc_ex_code, "%s", emsg);
351 if (*cp != '\0')
352 dcc_logbad(EX_USAGE,
353 "invalid IP address: \"%s\"",
354 optarg);
355 proxy_out_family = AF_INET; /* includes IPv6 */
356 }
357 break;
358
359 case 'D':
360 /* save user domain names sorted by length
361 * so that we can apply the most restrictive */
362 i = strlen(optarg);
363 if (*optarg == '\0'
364 || ((optarg[0] == '@' || optarg[0] == '*')
365 && i < 2)
366 || strpbrk(optarg+1, "@*")) {
367 dcc_logbad(EX_USAGE,
368 "invalid local-domain \"%s\"",
369 optarg);
370 break;
371 }
372 udom2 = dcc_malloc(sizeof(*udom2));
373 memset(udom2, 0, sizeof(*udom2));
374 udom2->len = i;
375 udom2->nm = optarg;
376 if (optarg[0] == '*') {
377 udom2->wildcard = 1;
378 ++udom2->nm;
379 --udom2->len;
380 }
381 udomp = &user_domains;
382 for (;;) {
383 udom = *udomp;
384 /* insert longer names before shorter names
385 * insert wildcard before same, non-wildcard
386 * because i includes the '*' */
387 if (!udom || i > udom->len) {
388 udom2->fwd = udom;
389 *udomp = udom2;
390 break;
391 }
392 udomp = &udom->fwd;
393 }
394 break;
395
396 case 'r':
397 parse_reply_arg(optarg);
398 break;
399
400 case 'm':
401 mapfile_nm = optarg;
402 break;
403
404 case 'w':
405 main_white_nm = optarg;
406 break;
407
408 case 'U':
409 parse_userdirs(optarg);
410 break;
411
412 case 'a':
413 if (!strcasecmp(optarg, "IGNORE")) {
414 action = CMN_IGNORE;
415 } else if (!strcasecmp(optarg, "REJECT")) {
416 action = CMN_REJECT;
417 } else if (!strcasecmp(optarg, "DISCARD")) {
418 action = CMN_DISCARD;
419 } else {
420 dcc_error_msg("unrecognized -a action: %s",
421 optarg);
422 }
423 break;
424
425 case 't':
426 if (dcc_parse_tholds("-t ", optarg))
427 log_tgts_set = 1;
428 break;
429
430 case 'g': /* honor not-spam "counts" */
431 dcc_parse_honor(optarg);
432 break;
433
434 case 'S':
435 dcc_add_sub_hdr(0, optarg);
436 break;
437
438 case 'l': /* log rejected mail here */
439 logdir = optarg;
440 break;
441
442 case 'R':
443 rundir = optarg;
444 break;
445
446 case 'T':
447 tmpdir = optarg;
448 break;
449
450 case 'j': /* maximum simultaneous jobs */
451 l = strtoul(optarg, &p, 10);
452 if (*p != '\0' || l < MIN_MAX_WORK) {
453 dcc_error_msg("invalid queue length %s",
454 optarg);
455 } else if (l > max_max_work) {
456 dcc_error_msg("-j queue length %s"
457 " larger than %s; using %d",
458 optarg,
459 max_max_work_src, max_max_work);
460 max_work = max_max_work;
461 } else {
462 max_work = l;
463 }
464 break;
465
466 case 'B':
467 if (!dcc_parse_dnsbl(emsg, optarg, progpath, 0))
468 dcc_error_msg("%s", emsg);
469 break;
470
471 case 'L':
472 if (dcc_parse_log_opt(optarg))
473 helper_save_arg("-L", optarg);
474 break;
475
476 default:
477 usage(optopt2str(optopt), "");
478 }
479 }
480 argc -= optind;
481 argv += optind;
482 if (argc != 0)
483 usage(argv[0], "");
484
485 /* default -D setting to the local host name */
486 if (!user_domains) {
487 if (0 > gethostname(hostname+1, sizeof(hostname)-2)) {
488 dcc_error_msg("gethostname(): %s", ERROR_STR());
489 } else if ((i = strlen(hostname)) > 1) {
490 user_domains = dcc_malloc(sizeof(*user_domains));
491 memset(user_domains, 0, sizeof(*user_domains));
492 user_domains->len = i;
493 user_domains->nm = hostname;
494 }
495 }
496
497 dcc_cdhome(0, homedir, 0);
498 dcc_main_logdir_init(0, logdir);
499 tmp_path_init(tmpdir, logdir);
500
501 if (proxy_out_family == AF_INET) {
502 if (proxy_out_host[0] == '\0'
503 || !strcmp(proxy_out_host, "@")) {
504 /* null or "@" means incoming host */
505 ;
506 } else {
507 dcc_host_lock();
508 if (!dcc_get_host(proxy_out_host, use_ipv6, &error))
509 dcc_logbad(EX_NOHOST, "%s: %s",
510 proxy_out_host,
511 DCC_HSTRERROR(error));
512 proxy_out_su = dcc_hostaddrs[0];
513 *DCC_SU_PORTP(&proxy_out_su) = proxy_out_port;
514 dcc_host_unlock();
515 }
516 }
517
518 /* Open the incoming socket before our backgrounding fork() to
519 * minimize races and allow better error reporting. */
520 bind_listen();
521
522 if (dcc_main_logdir[0] == '\0') {
523 if (log_tgts_set)
524 dcc_error_msg("log thresholds set with -t"
525 " but no -l directory");
526 if (userdirs != '\0')
527 dcc_error_msg("no -l directory prevents per-user"
528 " logging with -U");
529 }
530
531 #ifdef RLIMIT_NOFILE
532 if (old_rlim_cur < (i = max_work*FILES_PER_JOB+EXTRA_FILES)) {
533 nofile.rlim_cur = i;
534 if (0 > setrlimit(RLIMIT_NOFILE, &nofile)) {
535 dcc_error_msg("setrlimit(RLIMIT_NOFILE,%d): %s",
536 i, ERROR_STR());
537 max_work = old_rlim_cur/FILES_PER_JOB - EXTRA_FILES;
538 if (max_work <= 0) {
539 dcc_error_msg("only %d open files allowed"
540 " by RLIMIT_NOFILE",
541 old_rlim_cur);
542 max_work = MIN_MAX_WORK;
543 }
544 }
545 }
546 #endif /* RLIMIT_NOFILE */
547
548 helper_init(max_work);
549
550 if (background) {
551 if (daemon(1, 0) < 0)
552 dcc_logbad(EX_OSERR, "daemon(): %s", ERROR_STR());
553
554 dcc_daemon_restart(rundir, 0);
555 dcc_pidfile(pidpath, rundir);
556 }
557
558 signal(SIGPIPE, SIG_IGN);
559 signal(SIGHUP, sigterm);
560 signal(SIGTERM, sigterm);
561 signal(SIGINT, sigterm);
562 #ifdef SIGXFSZ
563 signal(SIGXFSZ, SIG_IGN);
564 #endif
565
566 /* Be careful to start all threads only after the fork() in daemon(),
567 * because some POSIX threads packages (e.g. FreeBSD) get confused
568 * about threads in the parent. */
569
570 cmn_init();
571 add_work(init_work);
572
573 if (listen_family != AF_UNIX) {
574 dcc_trace_msg(DCC_VERSION" listening to %s from %s for %s",
575 lhost_port, rcidr,
576 proxy ? "SMTP commands" : "ASCII protocol");
577 } else {
578 dcc_trace_msg(DCC_VERSION" listening to %s for %s",
579 listen_addr,
580 proxy ? "SMTP commands" : "ASCII protocol");
581 }
582 if (dcc_clnt_debug)
583 dcc_trace_msg("init_work=%d max_work=%d max_max_work=%d (%s)",
584 total_work, max_work, max_max_work,
585 max_max_work_src);
586
587 while (!stopping) {
588 /* delay for 1 second instead of forever to notice
589 * when SIGTERM has said to stop */
590 i = dcc_select_poll(emsg, listen_soc, 1, DCC_US);
591 if (i < 0)
592 dcc_logbad(EX_OSERR, "%s", emsg);
593 if (i == 0)
594 continue;
595
596 /* A new connection is ready. Allocate a context
597 * block and create a thread */
598 lock_work();
599 wp = work_free;
600 if (!wp) {
601 if (total_work > max_work) {
602 /* pretend we weren't listening if we
603 * are out of context blocks */
604 unlock_work();
605 sleep(1);
606 continue;
607 }
608 if (dcc_clnt_debug > 1)
609 dcc_trace_msg("add %d work blocks to %d",
610 init_work, total_work);
611 add_work(init_work);
612 wp = work_free;
613 }
614 work_free = wp->fwd;
615 unlock_work();
616
617 /* clear most of context block for the new connection */
618 cmn_clear(&wp->cw, wp, 1);
619 wp->cw.helo[0] = '\0';
620 memset(&wp->WORK_ZERO, 0,
621 sizeof(*wp) - ((char*)&wp->WORK_ZERO - (char*)wp));
622
623 namelen = sizeof(wp->proxy_in_su);
624 wp->proxy_in_soc = accept(listen_soc,
625 &wp->proxy_in_su.sa, &namelen);
626 if (wp->proxy_in_soc < 0) {
627 dcc_error_msg("accept(): %s", ERROR_STR());
628 job_close(wp);
629 continue;
630 }
631 if (listen_family != AF_UNIX) {
632 struct in6_addr addr6, *ap;
633
634 if (wp->proxy_in_su.sa.sa_family == AF_INET6) {
635 ap = &wp->proxy_in_su.ipv6.sin6_addr;
636 } else {
637 ap = &addr6;
638 dcc_ipv4toipv6(ap,
639 wp->proxy_in_su.ipv4.sin_addr);
640 }
641 if (!DCC_IN_BLOCK(*ap, raddr, rmask)) {
642 char str[DCC_SU2STR_SIZE];
643 dcc_error_msg("unauthorized client address %s",
644 dcc_su2str(str, sizeof(str),
645 &wp->proxy_in_su));
646 job_close(wp);
647 continue;
648 }
649 }
650 if (!set_soc(emsg, wp->proxy_in_soc, listen_family, "MTA")) {
651 dcc_error_msg("%s", emsg);
652 job_close(wp);
653 continue;
654 }
655 i = pthread_create(&tid, 0, job_start, wp);
656 if (i) {
657 dcc_error_msg("pthread_create(): %s", ERROR_STR1(i));
658 job_close(wp);
659 continue;
660 }
661 i = pthread_detach(tid);
662 if (i != 0) {
663 if (i != ESRCH)
664 dcc_error_msg("pthread_detach(): %s", ERROR_STR1(i));
665 else if (dcc_clnt_debug)
666 dcc_trace_msg("pthread_detach(): %s", ERROR_STR1(i));
667 }
668 }
669
670 close_listen_soc();
671
672 totals_stop();
673
674 exit(stopping);
675 }
676
677
678
679 static void
680 unlink_listen_sun(void)
681 {
682 if (listen_family != AF_UNIX)
683 return;
684
685 /* there is an unavoidable race here */
686 if (0 > unlink(listen_sun.sun_path)
687 && errno != ENOENT)
688 dcc_error_msg("unlink(%s): %s",
689 listen_sun.sun_path, ERROR_STR());
690 }
691
692
693
694 static void
695 bind_unix_listen(void)
696 {
697 DCC_EMSG emsg;
698 struct stat sb;
699 int i;
700
701 #ifdef HP_UX_BAD_AF_UNIX
702 dcc_logbad(EX_CONFIG, "HP-UX AF_UNIX does not support shutdown()");
703 #endif
704
705 emsg[0] = '\0';
706 listen_family = AF_UNIX;
707
708 if (!listen_addr) {
709 /* use default UNIX domain socket */
710 snprintf(listen_sun.sun_path, sizeof(listen_sun.sun_path),
711 "%s/"DCC_DCCIF_UDS, dcc_homedir);
712 listen_addr = listen_sun.sun_path;
713 } else {
714 if (strlen(listen_addr) >= ISZ(listen_sun.sun_path))
715 dcc_logbad(EX_USAGE, "invalid UNIX domain socket: %s",
716 listen_addr);
717 strcpy(listen_sun.sun_path, listen_addr);
718 }
719 #ifdef HAVE_SA_LEN
720 listen_sun.sun_len = SUN_LEN(&listen_sun);
721 #endif
722 listen_sun.sun_family = AF_UNIX;
723
724 if (0 <= stat(listen_sun.sun_path, &sb)
725 && !(S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode)))
726 dcc_logbad(EX_UNAVAILABLE, "non-socket present at %s",
727 listen_sun.sun_path);
728
729 /* Look for a daemon already using our socket. Do not give up
730 * immediately in case a previous instance is slowly stopping. */
731 i = 0;
732 for (;;) {
733 listen_soc = socket(AF_UNIX, SOCK_STREAM, 0);
734 if (listen_soc < 0)
735 dcc_logbad(EX_OSERR, "socket(AF_UNIX): %s",
736 ERROR_STR());
737 /* unlink it only if it looks like a dead socket */
738 if (0 > connect(listen_soc, (struct sockaddr *)&listen_sun,
739 sizeof(listen_sun))) {
740 if (errno == ECONNREFUSED || errno == ECONNRESET
741 || errno == EACCES) {
742 unlink_listen_sun();
743 } else if (dcc_clnt_debug > 2
744 && errno != ENOENT) {
745 dcc_trace_msg("connect(old server %s): %s",
746 listen_sun.sun_path, ERROR_STR());
747 }
748 close(listen_soc);
749 break;
750 }
751 /* connect() worked so the socket is alive */
752 if (++i > 5*10)
753 dcc_logbad(EX_UNAVAILABLE,
754 "something running with socket at %s",
755 listen_sun.sun_path);
756 close(listen_soc);
757 usleep(100*1000);
758 }
759
760 listen_soc = socket(AF_UNIX, SOCK_STREAM, 0);
761 if (listen_soc < 0)
762 dcc_logbad(EX_OSERR, "socket(AF_UNIX): %s", ERROR_STR());
763 if (0 > bind(listen_soc, (struct sockaddr *)&listen_sun,
764 sizeof(listen_sun)))
765 dcc_logbad(EX_IOERR, "bind(%s) %s",
766 listen_sun.sun_path, ERROR_STR());
767 if (0 > chmod(listen_sun.sun_path, 0666))
768 dcc_error_msg("chmod(%s, 0666): %s",
769 listen_sun.sun_path, ERROR_STR());
770 }
771
772
773
774 static void
775 bind_tcp_listen(void)
776 {
777 DCC_EMSG emsg;
778 char lhost[MAXHOSTNAMELEN];
779 u_int16_t lport;
780 DCC_SOCKU su;
781 char *duparg;
782 const char *cp;
783 int on, error;
784
785 emsg[0] = '\0';
786
787 duparg = strdup(listen_addr);
788 lhost_port = duparg;
789 duparg = strchr(duparg, ',');
790 if (!duparg)
791 dcc_logbad(EX_USAGE, "missing port number in \"%s\"",
792 listen_addr);
793
794 duparg = strchr(duparg+1, ',');
795 if (!duparg)
796 dcc_logbad(EX_USAGE, "missing rhost in \"%s\"",
797 listen_addr);
798 *duparg++ = '\0';
799
800 rcidr = duparg;
801 if (0 >= dcc_str2cidr(emsg, &raddr, &rmask, 0, rcidr, 0, 0))
802 dcc_logbad(EX_USAGE, "invalid rhost and mask \"%s\"",
803 rcidr);
804
805 cp = dcc_parse_nm_port(emsg, lhost_port, DCC_GET_PORT_INVALID,
806 lhost, sizeof(lhost), &lport, 0, 0, 0, 0);
807 if (!cp)
808 dcc_logbad(dcc_ex_code, "%s", emsg);
809 if (*cp != '\0')
810 dcc_logbad(EX_USAGE, "invalid IP address: \"%s\"",
811 lhost_port);
812
813 if (lhost[0] == '\0' || !strcmp(lhost, "@")) {
814 /* null or "@" means INADDR_ANY */
815 dcc_mk_su(&su, use_ipv6 ? AF_INET6: AF_INET, 0, lport);
816 } else {
817 dcc_host_lock();
818 if (!dcc_get_host(lhost, use_ipv6, &error))
819 dcc_logbad(EX_NOHOST, "%s: %s",
820 lhost, DCC_HSTRERROR(error));
821 su = dcc_hostaddrs[0];
822 *DCC_SU_PORTP(&su) = lport;
823 dcc_host_unlock();
824 }
825
826 listen_soc = socket(su.sa.sa_family, SOCK_STREAM, 0);
827 if (listen_soc < 0)
828 dcc_logbad(EX_OSERR, "socket(): %s", ERROR_STR());
829
830 on = 1;
831 if (0 > setsockopt(listen_soc, SOL_SOCKET, SO_REUSEADDR,
832 &on, sizeof(on)))
833 dcc_error_msg("setsockopt(listen %s, SO_REUSADDR): %s",
834 dcc_su2str_err(&su), ERROR_STR());
835
836 if (0 > bind(listen_soc, &su.sa, DCC_SU_LEN(&su)))
837 dcc_logbad(EX_UNAVAILABLE, "bind(%s) %s",
838 lhost_port, ERROR_STR());
839
840 listen_family = su.sa.sa_family;
841 }
842
843
844
845 static u_char
846 set_soc(DCC_EMSG emsg, int s, int family, const char *sname)
847 {
848 int on;
849
850 if (0 > fcntl(s, F_SETFD, FD_CLOEXEC)) {
851 dcc_pemsg(EX_IOERR, emsg,
852 "fcntl(%s, F_SETFD, FD_CLOEXEC): %s",
853 sname, ERROR_STR());
854 return 0;
855 }
856
857 if (family != AF_UNIX) {
858 on = 1;
859 if (0 > setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
860 &on, sizeof(on))) {
861 dcc_pemsg(EX_IOERR, emsg,
862 "setsockopt(%s, SO_KEEPALIVE): %s",
863 sname, ERROR_STR());
864 return 0;
865 }
866 }
867
868 /* use non-blocking sockets so that we can read entire lines
869 * without knowing how big they are or expecting the operating
870 * system to allow peeking at its buffers */
871 if (-1 == fcntl(s, F_SETFL,
872 fcntl(s, F_GETFL, 0) | O_NONBLOCK)) {
873 dcc_pemsg(EX_OSERR, emsg, "fcntl(%s, O_NONBLOCK): %s",
874 sname, ERROR_STR());
875 return 0;
876 }
877
878 return 1;
879 }
880
881
882
883 static void
884 bind_listen(void)
885 {
886 DCC_EMSG emsg;
887 char *p;
888
889 /* It is a TCP address if it has a port number and mask.
890 * Otherwise it is a UNIX domain socket.
891 */
892 if (listen_addr != 0
893 && (p = strchr(listen_addr, ',')) != 0
894 && strchr(p, ',')) {
895 bind_tcp_listen();
896 } else {
897 bind_unix_listen();
898 }
899
900 if (!set_soc(emsg, listen_soc, listen_family, "main socket"))
901 dcc_logbad(dcc_ex_code, "%s", emsg);
902 if (0 > listen(listen_soc, 10))
903 dcc_logbad(EX_IOERR, "listen(): %s", ERROR_STR());
904 }
905
906
907
908 static u_char /* 0=EOF, 1=read something */
909 soc_read(WORK *wp, IN_BC *bc)
910 {
911 int fspace, len, total, i;
912
913 if (bc->out >= bc->in)
914 bc->out = bc->in = bc->base;
915
916 fspace = &bc->base[bc->size] - bc->in;
917 if (fspace < bc->size/8) {
918 if (bc->out == bc->base) {
919 thr_error_msg(&wp->cw, "buffer overrun; in=%d",
920 (int)(bc->in - bc->base));
921 job_exit(wp);
922 }
923 len = bc->in - bc->out;
924 memmove(bc->base, bc->out, len);
925 bc->out = bc->base;
926 bc->in = bc->out+len;
927 fspace = bc->size - len;
928 }
929
930 if (wp->dfgs & DFG_WORK_LOCK)
931 unlock_work();
932
933 if (!bc->socp || *bc->socp < 0)
934 dcc_logbad(EX_SOFTWARE, "attempt to read closed socket");
935
936 for (;;) {
937 i = dcc_select_poll(wp->cw.emsg, *bc->socp, 1,
938 MAX_MTA_DELAY*DCC_US);
939 if (i > 0)
940 break;
941 if (i < 0) {
942 thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
943 } else {
944 thr_error_msg(&wp->cw, "MTA read timeout");
945 }
946 job_exit(wp);
947 }
948 total = read(*bc->socp, bc->in, fspace);
949 if (total < 0) {
950 if (!proxy || dcc_clnt_debug)
951 thr_error_msg(&wp->cw, "read(sock): %s", ERROR_STR());
952 job_exit(wp);
953 }
954 bc->in += total;
955
956 if (wp->dfgs & DFG_WORK_LOCK)
957 lock_work();
958 return total != 0;
959 }
960
961
962
963 /* ensure there is another line in the buffer */
964 static u_char /* 0=eof or buffer overflow */
965 msg_read_line(WORK *wp, IN_BC *bc)
966 {
967 int i;
968 char *p;
969
970 for (;;) {
971 p = bc->out;
972 i = bc->in - p;
973 if (i != 0) {
974 /* look for <LF> for ASCII protocol
975 * or <CR><LF> for SMTP */
976 p = memchr(p, '\n', i);
977 if (p
978 && (!proxy
979 || (p > bc->out && *(p-1) == '\r'))) {
980 bc->next_line = p+1;
981 return 1;
982 }
983 }
984
985 if (!soc_read(wp, bc))
986 return 0;
987 }
988 }
989
990
991
992 static void
993 buf_write_flush(WORK *wp, OUT_BC *bc)
994 {
995 const char *buf;
996 int len, i;
997
998 len = bc->len;
999 if (!len)
1000 return;
1001 bc->len = 0;
1002
1003 if (!bc->socp || *bc->socp < 0)
1004 dcc_logbad(EX_SOFTWARE, "attempt to write closed socket");
1005
1006 buf = bc->base;
1007 do {
1008 i = dcc_select_poll(wp->cw.emsg, *bc->socp, 0,
1009 MAX_MTA_DELAY*DCC_US);
1010 if (i < 0) {
1011 thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
1012 job_exit(wp);
1013 }
1014 if (i == 0) {
1015 thr_error_msg(&wp->cw, "MTA write timeout");
1016 job_exit(wp);
1017 }
1018 i = write(*bc->socp, buf, len);
1019 if (i < 0) {
1020 if (DCC_BLOCK_ERROR())
1021 continue;
1022 thr_error_msg(&wp->cw, "write(MTA socket,%d): %s",
1023 len, ERROR_STR());
1024 job_exit(wp);
1025 }
1026 if (i == 0) {
1027 thr_error_msg(&wp->cw, "write(MTA socket,%d)=%d",
1028 len, i);
1029 job_exit(wp);
1030 }
1031 buf += i;
1032 len -= i;
1033 } while (len > 0);
1034 }
1035
1036
1037
1038 static void
1039 buf_write(WORK *wp, OUT_BC *bc, const void *buf, u_int len)
1040 {
1041 u_int n;
1042
1043 for (;;) {
1044 n = bc->size - bc->len;
1045 if (n == 0)
1046 dcc_logbad(EX_SOFTWARE, "impossible buffer space");
1047 if (n > len)
1048 n = len;
1049 memcpy(&bc->base[bc->len], buf, n);
1050 if ((bc->len += n) >= bc->size)
1051 buf_write_flush(wp, bc);
1052 if ((len -= n) == 0)
1053 return;
1054 buf = (void *)((char *)buf + n);
1055 }
1056 }
1057
1058
1059
1060 static void
1061 tmp_write(WORK *wp, const void *buf, int len)
1062 {
1063 if (!cmn_write_tmp(&wp->cw, buf, len)) {
1064 thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
1065 job_exit(wp);
1066 }
1067 }
1068
1069
1070
1071 static int
1072 tmp_read(WORK *wp, void *buf, int len)
1073 {
1074 int i;
1075
1076 if (wp->cw.tmp_fd < 0)
1077 return 0;
1078
1079 i = read(wp->cw.tmp_fd, buf, len);
1080 if (i < 0) {
1081 thr_error_msg(&wp->cw, "read(%s,%d): %s",
1082 wp->cw.tmp_nm, len, ERROR_STR());
1083 job_exit(wp);
1084 }
1085 return i;
1086 }
1087
1088
1089
1090 /* fill MTA read buffer from temporary file */
1091 static int
1092 tmp_read_msg_in(WORK *wp)
1093 {
1094 int i;
1095
1096 /* preserving what is already in it */
1097 i = wp->msg_rd.in - wp->msg_rd.out;
1098 if (i > 0)
1099 memmove(wp->msg_rd.base, wp->msg_rd.out, i);
1100 wp->msg_rd.out = wp->msg_rd.base;
1101 wp->msg_rd.in = wp->msg_rd.out+i;
1102
1103 wp->msg_rd.in += tmp_read(wp, wp->msg_rd.in, wp->msg_rd.size - i);
1104 return wp->msg_rd.in > wp->msg_rd.out;
1105 }
1106
1107
1108
1109 /* Create the contexts. */
1110 static void
1111 add_work(int i)
1112 {
1113 WORK *wp;
1114
1115 total_work += i;
1116
1117 wp = dcc_malloc(sizeof(*wp)*i);
1118 memset(wp, 0, sizeof(*wp)*i);
1119
1120 while (i-- != 0) {
1121 wp->proxy_in_soc = -1;
1122 wp->proxy_out_soc = -1;
1123 cmn_create(&wp->cw);
1124 wp->fwd = work_free;
1125 work_free = wp;
1126 ++wp;
1127 }
1128 }
1129
1130
1131
1132 void
1133 work_clean(void)
1134 {
1135 WORK *wp;
1136 int keep, delete;
1137
1138 lock_work();
1139 keep = 5;
1140 delete = init_work;
1141 for (wp = work_free; wp; wp = wp->fwd) {
1142 if (!wp->cw.dcc_ctxt)
1143 break;
1144 if (--keep > 0)
1145 continue;
1146 dcc_clnt_soc_close(wp->cw.dcc_ctxt);
1147 if (--delete <= 0)
1148 break;
1149 }
1150 unlock_work();
1151 }
1152
1153
1154
1155 static void
1156 job_close(WORK *wp)
1157 {
1158 if (wp->dfgs & DFG_WORK_LOCK) {
1159 wp->dfgs &= ~DFG_WORK_LOCK;
1160 unlock_work();
1161 }
1162
1163 wp->msg_rd.socp = 0;
1164 if (wp->msg_wt.socp) {
1165 buf_write_flush(wp, &wp->msg_wt);
1166 wp->msg_wt.socp = 0;
1167 }
1168 wp->reply_in.socp = 0;
1169 if (wp->reply_out.socp) {
1170 buf_write_flush(wp, &wp->reply_out);
1171 wp->reply_out.socp = 0;
1172 }
1173
1174 cmn_close_tmp(&wp->cw);
1175
1176 if (wp->proxy_in_soc >= 0) {
1177 if (0 > close(wp->proxy_in_soc)
1178 && (dcc_clnt_debug
1179 || (errno != ECONNRESET
1180 && errno != ENOTCONN)))
1181 thr_error_msg(&wp->cw, "close(proxy input socket): %s",
1182 ERROR_STR());
1183 wp->proxy_in_soc = -1;
1184 }
1185 if (wp->proxy_out_soc >= 0) {
1186 if (0 > close(wp->proxy_out_soc))
1187 thr_error_msg(&wp->cw, "close(proxy output socket): %s",
1188 ERROR_STR());
1189 wp->proxy_out_soc = -1;
1190 }
1191 log_stop(&wp->cw);
1192
1193 lock_work();
1194 free_rcpt_sts(&wp->cw, 0);
1195 wp->fwd = work_free;
1196 work_free = wp;
1197 unlock_work();
1198 }
1199
1200
1201
1202 static void NRATTRIB
1203 job_exit(WORK *wp)
1204 {
1205 job_close(wp);
1206 pthread_exit(0);
1207 /* mostly to suppress warning */
1208 dcc_logbad(EX_OSERR, "pthread_exit() returned");
1209 }
1210
1211
1212
1213 /* write headers or body data from the MTA read buffer to the MTA */
1214 static void
1215 mta_write(WORK *wp, char *end)
1216 {
1217 int len;
1218
1219 len = end - wp->msg_rd.out;
1220 if (len <= 0)
1221 return;
1222 buf_write(wp, &wp->msg_wt, wp->msg_rd.out, len);
1223 wp->msg_rd.out = end;
1224 }
1225
1226
1227
1228 /* copy headers from the temporary file to the MTA */
1229 static void
1230 hdrs_copy(WORK *wp)
1231 {
1232 enum {START_HDR, SKIP_HDR, COPY_HDR} cmode;
1233 const char *nl;
1234 char *bol, *eol;
1235 int i;
1236
1237 if (-1 == lseek(wp->cw.tmp_fd, 0, SEEK_SET)) {
1238 thr_error_msg(&wp->cw, "rewind %s: %s",
1239 wp->cw.tmp_nm, ERROR_STR());
1240 job_exit(wp);
1241 }
1242
1243 cmode = START_HDR;
1244 wp->msg_rd.in = wp->msg_rd.out = wp->msg_rd.base;
1245 for (;;) {
1246 /* fill wp->msg_rd while keeping anything already present */
1247 if (!tmp_read_msg_in(wp))
1248 return;
1249
1250 bol = wp->msg_rd.out;
1251 while ((i = wp->msg_rd.in - bol) > 0) {
1252 /* Find the end of the next line. */
1253 eol = memchr(bol, '\n', i);
1254 if (!eol) {
1255 /* Fill the buffer if we can't find '\n''
1256 * and the buffer has room */
1257 if (i < wp->msg_rd.size
1258 && wp->msg_rd.out != wp->msg_rd.base)
1259 break;
1260 /* pretend the header ended with the buffer
1261 * if there is no room */
1262 eol = wp->msg_rd.in-1;
1263 nl = 0;
1264 } else if (eol+1 >= wp->msg_rd.in) {
1265 /* get the character after '\n' */
1266 if (i < wp->msg_rd.size
1267 && wp->msg_rd.out != wp->msg_rd.base)
1268 break;
1269 /* pretend we could not find it if there
1270 * is no room in the buffer
1271 * or if we are at end of file.
1272 * This will also end the headers */
1273 nl = 0;
1274 } else {
1275 nl = eol+1;
1276 }
1277
1278 if (cmode == START_HDR) {
1279 /* We are at start of a header or the body.
1280 * Quit before line of "\n" or "\r\n" */
1281 if (eol == bol
1282 || (eol == bol+1 && *bol == '\r')) {
1283 /* write any preceding lines */
1284 mta_write(wp, bol);
1285 return;
1286 }
1287
1288 /* Look for our header
1289 * Assume the buffer is larger than the
1290 * largest possible X-DCC field name. */
1291 if (chghdr == SETHDR
1292 && is_xhdr(bol, eol-bol)) {
1293 /* skip it
1294 * and copy any preceding headers. */
1295 mta_write(wp, bol);
1296 cmode = SKIP_HDR;
1297 } else {
1298 cmode = COPY_HDR;
1299 }
1300 }
1301 if (cmode == SKIP_HDR)
1302 wp->msg_rd.out = eol+1;
1303
1304 /* Check the character after '\n' for
1305 * whitespace indicating continuation */
1306 if (nl && *nl != ' ' && *nl != '\t') {
1307 cmode = START_HDR;
1308 /* deal with SMTP transparency */
1309 if (proxy && *nl == '.')
1310 *eol-- = '.';
1311 }
1312
1313 bol = eol+1;
1314 }
1315
1316 mta_write(wp, bol);
1317 }
1318 }
1319
1320
1321
1322 static void
1323 add_hdr(void *wp0, const char *buf, u_int buf_len)
1324 {
1325 WORK *wp = wp0;
1326
1327 buf_write(wp, &wp->msg_wt, buf, buf_len);
1328 }
1329
1330
1331
1332 static void
1333 body_copy(WORK *wp)
1334 {
1335 u_char seen_crlf;
1336 char *p;
1337
1338 hdrs_copy(wp);
1339
1340 if (chghdr != NOHDR && wp->cw.header.buf[0] != '\0') {
1341 /* write X-DCC header
1342 * end with "\r\n" if at least
1343 * half of the header lines ended that way */
1344 xhdr_write(add_hdr, wp, wp->cw.header.buf,
1345 wp->cw.header.used,
1346 wp->cr_hdrs > wp->total_hdrs/2);
1347 }
1348
1349 /* copy body */
1350 seen_crlf = 2;
1351 do {
1352 p = wp->msg_rd.out;
1353 while (p < wp->msg_rd.in) {
1354 if (seen_crlf == 1) {
1355 seen_crlf = (*p++ == '\n') ? 2 : 0;
1356 continue;
1357 }
1358 if (seen_crlf == 2) {
1359 seen_crlf = 0;
1360 if (*p == '.' && proxy) {
1361 mta_write(wp, p);
1362 buf_write(wp, &wp->msg_wt, ".", 1);
1363 }
1364 }
1365
1366 if (!proxy)
1367 break;
1368 p = memchr(p, '\r', wp->msg_rd.in-p);
1369 if (!p)
1370 break;
1371 seen_crlf = 1;
1372 ++p;
1373 }
1374 mta_write(wp, wp->msg_rd.in);
1375 } while (tmp_read_msg_in(wp));
1376 }
1377
1378
1379
1380 static void
1381 close_listen_soc(void)
1382 {
1383 if (pidpath[0] != '\0') {
1384 unlink(pidpath);
1385 pidpath[0] = '\0';
1386 }
1387
1388 if (listen_soc >= 0) {
1389 unlink_listen_sun();
1390 if (0 > close(listen_soc))
1391 dcc_error_msg("close(main socket): %s",
1392 ERROR_STR());
1393 listen_soc = -1;
1394 }
1395 }
1396
1397
1398
1399 /* watch for fatal signals */
1400 static void
1401 sigterm(int sig)
1402 {
1403 stopping = 100 + sig;
1404 unlink_listen_sun();
1405 dcc_clnt_stop_resolve();
1406 signal(sig, SIG_DFL); /* quit on repeated signals */
1407 }
1408
1409
1410
1411 void
1412 user_reject_discard(UATTRIB CMN_WORK *cwp, UATTRIB RCPT_ST *rcpt_st)
1413 {
1414 /* dccifd has no way to tell the MTA to remove a recipient
1415 * after the Rcpt_To command */
1416 thr_error_msg(cwp, "cannot discard an individual target");
1417 }
1418
1419
1420
1421 static void
1422 get_helo(WORK *wp, const char *vp, int len)
1423 {
1424 if (len < DCC_HELO_MAX-1) {
1425 memcpy(wp->cw.helo, vp, len);
1426 wp->cw.helo[len] = '\0';
1427 } else {
1428 /* if the HELO value is too long, capture what we can
1429 * and indicate that we got only part of it */
1430 len = DCC_HELO_MAX-1;
1431 memcpy(wp->cw.helo, vp, len);
1432 strcpy(&wp->msg_rd.out[DCC_HELO_MAX-ISZ(DCC_HELO_CONT)],
1433 DCC_HELO_CONT);
1434 }
1435 }
1436
1437
1438
1439 /* Get the next header field
1440 * The line is copied to the temporary file and then null terminated
1441 * in the buffer */
1442 static u_char /* 1=have one, 0=end of headers */
1443 get_hdr(WORK *wp)
1444 {
1445 int tf_len; /* header bytes written to temp file */
1446 int hdr_len; /* total header length */
1447 int nlen; /* length of next chunk of header */
1448 char *np; /* next byte after field */
1449 char *p;
1450 u_char at_bol; /* 1=at start of a line of header */
1451
1452 tf_len = 0;
1453 hdr_len = 0;
1454 at_bol = 1;
1455 for (;;) {
1456 /* get another line of the header */
1457 np = wp->msg_rd.out + hdr_len;
1458 nlen = wp->msg_rd.in - np;
1459 if (nlen <= 1) {
1460 /* we do not have enough data */
1461 read_more:;
1462 if (hdr_len > DCC_HDR_CK_MAX) {
1463 /* we already have more than DCC_HDR_CK_MAX
1464 * bytes of the header.
1465 * Keep only DCC_HDR_CK_MAX bytes of it
1466 * in the buffer. We will eventually
1467 * return only the first DCC_HDR_CK_MAX
1468 * bytes and an arbitrary part of the tail
1469 * to our caller. */
1470 if (tf_len < hdr_len)
1471 tmp_write(wp, wp->msg_rd.out+tf_len,
1472 hdr_len - tf_len);
1473 /* discard all but the first DCC_HDR_CK_MAX
1474 * bytes of the header */
1475 hdr_len = DCC_HDR_CK_MAX;
1476 tf_len = DCC_HDR_CK_MAX;
1477 if (nlen != 0)
1478 wp->msg_rd.out[DCC_HDR_CK_MAX] = *np;
1479 wp->msg_rd.in = (wp->msg_rd.out + DCC_HDR_CK_MAX
1480 + nlen);
1481 }
1482 /* Get more data, possibly sliding what we already
1483 * have down in the buffer. That would move
1484 * wp->msg_rd.in to wp->msg_rd.base. */
1485 if (!soc_read(wp, &wp->msg_rd)) {
1486 /* EOF implies the message body including the
1487 * separating blank line is missing.
1488 * That is impossible for the ASCII dccifd
1489 * protocol because there should be at least
1490 * a Received header.
1491 * When running as a proxy, we should get
1492 * "\r\n.\r\n"
1493 * Fake newlines until we get out of here
1494 * so that we will log whatever we got */
1495 wp->dfgs |= DFG_MISSING_BODY;
1496 if (wp->cr_hdrs >= wp->total_hdrs/2)
1497 *wp->msg_rd.in++ = '\r';
1498 *wp->msg_rd.in++ = '\n';
1499 }
1500 continue;
1501 }
1502
1503 if (at_bol) {
1504 /* deal with SMTP transparency */
1505 if (*np == '.' && proxy) {
1506 if (nlen < 3)
1507 goto read_more;
1508 if (np[1] != '\r' || np[2] != '\n') {
1509 memmove(np, np+1, --nlen);
1510 } else if (hdr_len == 0) {
1511 /* ".\r\n" at the start of a line
1512 * ends the message. In this case
1513 * the message body including the
1514 * separating blank line is missing.
1515 * Stop short before the ".\r\n" */
1516 return 0;
1517 }
1518 }
1519
1520 /* stop before the next line if it is
1521 * not a continuation of the current header */
1522 if (hdr_len != 0
1523 && *np != ' '
1524 && *np != '\t') {
1525 if (tf_len < hdr_len)
1526 tmp_write(wp, wp->msg_rd.out+tf_len,
1527 hdr_len - tf_len);
1528 wp->msg_rd.out[hdr_len-1] = '\0';
1529 if (hdr_len > 1
1530 && wp->msg_rd.out[hdr_len-2] == '\r')
1531 ++wp->cr_hdrs;
1532 wp->msg_rd.next_line = &wp->msg_rd.out[hdr_len];
1533 return 1;
1534 }
1535 }
1536
1537 /* find the end of the next line */
1538 p = memchr(np, '\n', nlen);
1539 if (p) {
1540 hdr_len += ++p - np;
1541 /* quit at the end of headers */
1542 if (hdr_len == 1
1543 || (hdr_len == 2 && *wp->msg_rd.out == '\r'))
1544 return 0;
1545 at_bol = 1;
1546 } else {
1547 hdr_len += nlen;
1548 at_bol = 0;
1549 }
1550 }
1551 }
1552
1553
1554
1555 static void
1556 get_hdrs(WORK *wp)
1557 {
1558 const char *p, *rh;
1559
1560 for (;;) {
1561 /* stop at the separator between the body and headers */
1562 if (!get_hdr(wp))
1563 break;
1564 ++wp->total_hdrs;
1565
1566 #define GET_HDR_CK(h,t) { \
1567 if (!CLITCMP(wp->msg_rd.out, h)) { \
1568 dcc_get_cks(&wp->cw.cks, DCC_CK_##t, \
1569 &wp->msg_rd.out[LITZ(h)], 1); \
1570 wp->dfgs |= DFG_SEEN_HDR; \
1571 wp->msg_rd.out = wp->msg_rd.next_line; \
1572 continue;}}
1573 GET_HDR_CK(DCC_XHDR_TYPE_FROM":", FROM);
1574 GET_HDR_CK(DCC_XHDR_TYPE_MESSAGE_ID":", MESSAGE_ID);
1575 #undef GET_HDR_CK
1576
1577 /* notice UNIX From_ line */
1578 if (!(wp->dfgs & DFG_SEEN_HDR)
1579 && wp->cw.env_from[0] == '\0'
1580 && parse_unix_from(wp->msg_rd.out, wp->cw.env_from,
1581 sizeof(wp->cw.env_from))) {
1582 wp->dfgs |= DFG_SEEN_HDR;
1583 wp->msg_rd.out = wp->msg_rd.next_line;
1584 continue;
1585 }
1586
1587 if (wp->cw.env_from[0] == '\0'
1588 && parse_return_path(wp->msg_rd.out, wp->cw.env_from,
1589 sizeof(wp->cw.env_from))) {
1590 wp->dfgs |= DFG_SEEN_HDR;
1591 wp->msg_rd.out = wp->msg_rd.next_line;
1592 continue;
1593 }
1594
1595 if (!CLITCMP(wp->msg_rd.out, DCC_XHDR_TYPE_RECEIVED":")) {
1596 p = &wp->msg_rd.out[LITZ(DCC_XHDR_TYPE_RECEIVED":")];
1597
1598 /* compute checksum of the last Received: header */
1599 dcc_get_cks(&wp->cw.cks, DCC_CK_RECEIVED, p, 1);
1600
1601 wp->dfgs |= DFG_SEEN_HDR;
1602 wp->msg_rd.out = wp->msg_rd.next_line;
1603
1604 /* pick IP address out of first Received: header */
1605 if (!(wp->dfgs & DFG_PARSE_RCVD)
1606 || --wp->parse_rcvd >= 0)
1607 continue;
1608 rh = parse_received(p, &wp->cw.cks,
1609 (wp->cw.helo[0] == '\0')
1610 ? wp->cw.helo : 0,
1611 sizeof(wp->cw.helo),
1612 wp->cw.sender_str,
1613 sizeof(wp->cw.sender_str),
1614 wp->cw.sender_name,
1615 sizeof(wp->cw.sender_name));
1616 if (rh == 0) {
1617 /* to avoid being fooled by forged
1618 * headers, stop at a strange one */
1619 wp->dfgs &= ~DFG_PARSE_RCVD;
1620
1621 } else if (*rh != '\0') {
1622 thr_log_print(&wp->cw, 1,
1623 "skip %s Received: header\n", rh);
1624
1625 } else if (!check_mx_listing(&wp->cw)) {
1626 /* we know the client */
1627 wp->dfgs &= ~DFG_PARSE_RCVD;
1628 }
1629 continue;
1630 }
1631
1632 /* Notice MIME multipart boundary definitions */
1633 dcc_ck_mime_hdr(&wp->cw.cks, wp->msg_rd.out, 0);
1634
1635 if (dcc_ck_get_sub(&wp->cw.cks, wp->msg_rd.out, 0))
1636 wp->dfgs |= DFG_SEEN_HDR;
1637
1638 /* notice any sort of header */
1639 if (!(wp->dfgs & DFG_SEEN_HDR)) {
1640 for (p = wp->msg_rd.out; ; ++p) {
1641 if (*p == ':') {
1642 wp->dfgs |= DFG_SEEN_HDR;
1643 break;
1644 }
1645 if (*p <= ' ' || *p >= 0x7f)
1646 break;
1647 }
1648 }
1649
1650 wp->msg_rd.out = wp->msg_rd.next_line;
1651 }
1652
1653 /* Create a checksum for a null Message-ID header if there
1654 * was no Message-ID header. */
1655 if (wp->cw.cks.sums[DCC_CK_MESSAGE_ID].type != DCC_CK_MESSAGE_ID)
1656 dcc_get_cks(&wp->cw.cks, DCC_CK_MESSAGE_ID, "", 0);
1657 }
1658
1659
1660
1661 /* Assume for now that the sender is the SMTP client. Received: headers
1662 * might challenge that assumption */
1663 static void
1664 get_sender(WORK *wp)
1665 {
1666 strcpy(wp->cw.sender_name, wp->cw.clnt_name);
1667 strcpy(wp->cw.sender_str, wp->cw.clnt_str);
1668 if (wp->cw.sender_str[0] == '\0'
1669 || check_mx_listing(&wp->cw)) {
1670 /* try Received: header if client is unknown or MX server*/
1671 wp->dfgs |= DFG_PARSE_RCVD;
1672 }
1673 }
1674
1675
1676
1677 static u_char /* 0=temporary failure, 1=ok */
1678 get_body(WORK *wp)
1679 {
1680 char *p;
1681 char buf[1024];
1682 u_char bol;
1683 int buflen, i;
1684
1685 /* We must make a copy of the entire message in a temporary file
1686 * if the MTA wants a copy with the X-DCC header added
1687 * Copy the headers to a temporary file because the official
1688 * log file needs the SMTP client IP address and envelope information
1689 * before the header lines. The log file needs all of the header
1690 * lines including stray X-DCC lines, but those lines must be removed
1691 * from the output file. */
1692 if (!cmn_open_tmp(&wp->cw)) {
1693 if (wp->dfgs & DFG_MTA_BODY) {
1694 dcc_error_msg("fatal error: %s", wp->cw.emsg);
1695 return 0;
1696 }
1697 dcc_error_msg("%s", wp->cw.emsg);
1698 }
1699
1700 get_hdrs(wp);
1701
1702 /* log IP address and so forth that we may have collected from
1703 * the headers or Postfix XFORWARD or XCLIENT ESTMP extension */
1704 thr_log_envelope(&wp->cw, 0);
1705
1706 /* Check DNS blacklists for STMP client and envelope sender
1707 * unless DNSBL checks are turned off for all of the recipients */
1708 if (wp->cw.cks.dnsbl) {
1709 if (wp->cw.cks.sums[DCC_CK_IP].type == DCC_CK_IP)
1710 dcc_client_dnsbl(wp->cw.cks.dnsbl, &wp->cw.cks.ip_addr,
1711 wp->cw.sender_name);
1712 if (wp->cw.mail_host[0] != '\0')
1713 dcc_mail_host_dnsbl(wp->cw.cks.dnsbl, wp->cw.mail_host);
1714 }
1715
1716 /* copy headers from temporary file to log file */
1717 if (wp->cw.log_fd >= 0 && wp->cw.tmp_fd >= 0) {
1718 if (0 > lseek(wp->cw.tmp_fd, 0, SEEK_SET)) {
1719 thr_error_msg(&wp->cw, "rewind %s: %s",
1720 wp->cw.tmp_nm, ERROR_STR());
1721 job_exit(wp);
1722 }
1723 for (;;) {
1724 buflen = tmp_read(wp, buf, sizeof(buf));
1725 if (buflen <= 0)
1726 break;
1727 log_body_write(&wp->cw, buf, buflen);
1728 }
1729 }
1730
1731 if (wp->dfgs & DFG_MISSING_BODY)
1732 thr_error_msg(&wp->cw, "missing message body");
1733
1734 /* collect the body */
1735 bol = 1;
1736 for (;;) {
1737 buflen = wp->msg_rd.in - wp->msg_rd.out;
1738 if (buflen <= 0) {
1739 if (!soc_read(wp, &wp->msg_rd))
1740 break;
1741 buflen = wp->msg_rd.in - wp->msg_rd.out;
1742 }
1743
1744 /* deal with SMTP transparency and detect end of message */
1745 if (proxy) {
1746 if (bol) {
1747 /* We are at the beginning of a line.
1748 * There will always be at least 3 more bytes
1749 * in the message, ".\r\n" */
1750 if (buflen < 3) {
1751 if (!soc_read(wp, &wp->msg_rd))
1752 proxy_msg_truncated(wp);
1753 buflen = wp->msg_rd.in - wp->msg_rd.out;
1754 }
1755 /* Whether a '.' is the end of the data
1756 * or an escaped '.', we will discard it. */
1757 if (*wp->msg_rd.out == '.'
1758 && buflen >= 1) {
1759 ++wp->msg_rd.out;
1760 --buflen;
1761 /* if it is followed by "\r\n", then
1762 * we have the end of the message */
1763 if (buflen >= 2
1764 && wp->msg_rd.out[0] == '\r'
1765 && wp->msg_rd.out[1] == '\n') {
1766 wp->msg_rd.out += 2;
1767 break;
1768 }
1769 }
1770 /* we are still at the beginning of a line */
1771 }
1772
1773 /* check all of the lines in the buffer */
1774 p = wp->msg_rd.out;
1775 for (;;) {
1776 i = p - wp->msg_rd.out;
1777 p = memchr(p, '\r', buflen-i);
1778 if (!p) {
1779 /* We have checked all of the buffer.
1780 * It does not end near an end of line.
1781 * So log the block of body lines
1782 * and leave the part of a line
1783 * ending the buffer for later. */
1784 bol = 0;
1785 break;
1786 }
1787 /* We have found '\r'
1788 * Maybe it will be followed by '\n' */
1789 i = ++p - wp->msg_rd.out;
1790 if (i+2 >= buflen) {
1791 /* '\r' ends or almost ends buffer. */
1792 bol = 0;
1793 buflen = i-1;
1794 if (buflen > 0) {
1795 /* The buffer ends with "\rX"
1796 * and contains text before '\r'
1797 * Process up to the '\r' */
1798 break;
1799 }
1800 /* buffer starts with "\r" */
1801 if (!soc_read(wp, &wp->msg_rd))
1802 proxy_msg_truncated(wp);
1803 p = wp->msg_rd.out;
1804 buflen = wp->msg_rd.in - p;
1805
1806 } else if (*p == '\n') {
1807 /* We have "\r\n"
1808 * If "\r\n" is followed by '.',
1809 * then process up to the '.' */
1810 if (*++p == '.') {
1811 buflen = i+1;
1812 bol = 1;
1813 break;
1814 }
1815 }
1816 }
1817 }
1818
1819 /* Log the body block */
1820 log_body_write(&wp->cw, wp->msg_rd.out, buflen);
1821
1822 if (wp->dfgs & DFG_MTA_BODY)
1823 tmp_write(wp, wp->msg_rd.out, buflen);
1824
1825 dcc_ck_body(&wp->cw.cks, wp->msg_rd.out, buflen);
1826 wp->msg_rd.out += buflen;
1827 }
1828 dcc_cks_fin(&wp->cw.cks);
1829
1830 LOG_CAPTION(wp, DCC_LOG_MSG_SEP);
1831 thr_log_late(&wp->cw);
1832
1833 /* check the grey and white lists */
1834 cmn_ask_white(&wp->cw);
1835
1836 /* report spam to the DCC server in the case without a recipient
1837 * for the ASCII protocol,
1838 * which normally causes a query instead of a report. */
1839 if (!wp->cw.rcpt_st_first
1840 && ((wp->cw.ask_st & ASK_ST_MTA_ISSPAM)
1841 || (wp->cw.init_sws & FLTR_SW_TRAPS)
1842 || dcc_query_only
1843 || wp->cw.ask_st & ASK_ST_QUERY))
1844 ++wp->cw.tgts;
1845
1846 wp->cw.header.buf[0] = '\0';
1847 wp->cw.header.used = 0;
1848 if (wp->cw.tgts > wp->cw.white_tgts) {
1849 /* Report to the DCC and add our header if allowed.
1850 * After serious errors, act as if DCC server said not-spam
1851 * but remove our X-DCC header */
1852 i = cmn_ask_dcc(&wp->cw);
1853 if (!i && try_extra_hard)
1854 return 0;
1855
1856 } else {
1857 /* The message is whitelisted or the MTA told us there are 0
1858 * recipients, so we cannot ask the DCC server.
1859 * If it was whitelisted, add X-DCC header saying so. */
1860 if (wp->cw.tgts > 0)
1861 xhdr_whitelist(&wp->cw.header);
1862 /* Use the local target count to decide whether to log
1863 * the mail message */
1864 dcc_honor_log_cnts(&wp->cw.ask_st, &wp->cw.cks, wp->cw.tgts);
1865 }
1866
1867 totals.tgts += wp->cw.tgts;
1868
1869 return 1;
1870 }
1871
1872
1873
1874 /* ensure there is another line, trim its terminal "\r\n",
1875 * and add a terminal '\0'
1876 * This cannot be used with the proxy code because it often needs to
1877 * send the original buffer downstream. */
1878 static int /* # of bytes available */
1879 ascii_read_line(WORK *wp)
1880 {
1881 char *p;
1882
1883 if (!msg_read_line(wp, &wp->msg_rd)) {
1884 thr_error_msg(&wp->cw, "truncated request");
1885 job_exit(wp);
1886 }
1887
1888 p = wp->msg_rd.next_line-1;
1889 for (;;) {
1890 *p-- = '\0';
1891 if (p < wp->msg_rd.out)
1892 return 0;
1893 if (*p != '\r')
1894 return p+1 - wp->msg_rd.out;
1895 }
1896 }
1897
1898
1899
1900 /* Look for a string from the MTA
1901 * while ignoring case and skipping whitespace
1902 * The next line must already be in the buffer */
1903 static u_char /* 1=matched it */
1904 ascii_opt_str(WORK *wp, const char *st, int stlen)
1905 {
1906 /* skip initial white space */
1907 while (wp->msg_rd.out < wp->msg_rd.next_line
1908 && (*wp->msg_rd.out == '\t' || *wp->msg_rd.out == ' '))
1909 ++wp->msg_rd.out;
1910
1911 if (stlen <= wp->msg_rd.next_line - wp->msg_rd.out
1912 && !strncasecmp(wp->msg_rd.out, st, stlen)) {
1913 if (wp->msg_rd.out[stlen] == '\0') {
1914 wp->msg_rd.out += stlen;
1915 return 1;
1916 }
1917
1918 /* skip trailing whitespace */
1919 if (wp->msg_rd.out[stlen] == '\t'
1920 || wp->msg_rd.out[stlen] == ' ') {
1921 do {
1922 ++stlen;
1923 } while (wp->msg_rd.out[stlen] == '\t'
1924 || wp->msg_rd.out[stlen] == ' ');
1925 wp->msg_rd.out += stlen;
1926 return 1;
1927 }
1928 }
1929
1930 return 0;
1931 }
1932
1933
1934
1935 static u_char /* 0=bad recipient */
1936 check_addr(WORK *wp,
1937 const char **addrp, int *addr_lenp,
1938 const char **userp, int *user_lenp)
1939 {
1940 USER_DOMAIN *udom;
1941 const char *atchr, *addr, *cp;
1942 int addr_len, i;
1943
1944 addr = *addrp;
1945 addr_len = *addr_lenp;
1946 if (addr_len > RCTP_MAXNAME-1)
1947 addr_len = *addr_lenp = RCTP_MAXNAME-1;
1948 if (*addr == '<' && addr_len >= 2 && addr[addr_len-1] == '>') {
1949 ++addr;
1950 addr_len -= 2;
1951 if (addr_len == 0) {
1952 if (wp->dfgs & DFG_WORK_LOCK)
1953 unlock_work();
1954 thr_error_msg(&wp->cw, "null recipient address");
1955 if (wp->dfgs & DFG_WORK_LOCK)
1956 lock_work();
1957 return 0;
1958 }
1959 }
1960
1961 /* strip source route from recipient */
1962 while (addr_len != 0
1963 && (cp = memchr(addr, ',', addr_len)) != 0) {
1964 ++cp;
1965 addr_len -= cp-addr;
1966 addr = cp;
1967 *addr_lenp = addr_len;
1968 *addrp = addr;
1969 }
1970
1971 if (addr_len >= RCTP_MAXNAME) {
1972 if (wp->dfgs & DFG_WORK_LOCK)
1973 unlock_work();
1974 thr_error_msg(&wp->cw, "recipient \"%s\" is too long", addr);
1975 if (wp->dfgs & DFG_WORK_LOCK)
1976 lock_work();
1977 return 0;
1978 }
1979 if (addr_len <= 0) {
1980 if (wp->dfgs & DFG_WORK_LOCK)
1981 unlock_work();
1982 thr_error_msg(&wp->cw, "null recipient");
1983 if (wp->dfgs & DFG_WORK_LOCK)
1984 lock_work();
1985 return 0;
1986 }
1987
1988 if (*user_lenp) {
1989 if (*user_lenp > RCTP_MAXNAME-1)
1990 *user_lenp = RCTP_MAXNAME-1;
1991 } else {
1992 /* Use the list of local domain names to guess a user name
1993 * from the recipient address
1994 * Take the whole recipient name if it does not
1995 * include a domain name */
1996 atchr = memchr(addr, '@', addr_len);
1997 if (!atchr) {
1998 *userp = addr;
1999 *user_lenp = addr_len;
2000 return 1;
2001 }
2002
2003 for (udom = user_domains; udom; udom = udom->fwd) {
2004 i = addr_len - udom->len;
2005 if (i > 0 && !strncasecmp(addr+i, udom->nm,
2006 udom->len)) {
2007 if (*udom->nm == '@'
2008 || *udom->nm == '.') {
2009 ; /* got it */
2010 } else {
2011 /* match must start at '.' or '@'
2012 * if target did not start with
2013 * '.' or '@' then the name must
2014 * end with '.' or '@' */
2015 if (i-- < 2
2016 || (addr[i] != '.'
2017 && addr[i] != '@'))
2018 continue;
2019 }
2020 /* we have something like user or user@sub
2021 * from user@sub.domain.com */
2022 *userp = addr;
2023 if (!udom->wildcard) {
2024 *user_lenp = i;
2025 } else {
2026 *user_lenp = atchr - addr;
2027 }
2028 return 1;
2029 }
2030 }
2031 }
2032
2033 return 1;
2034 }
2035
2036
2037
2038 /* The mutex must be locked */
2039 static RCPT_ST *
2040 set_rcpt(WORK *wp,
2041 const char *rcpt, int rcpt_len,
2042 const char *user, int user_len)
2043 {
2044 RCPT_ST *rcpt_st;
2045
2046 rcpt_st = alloc_rcpt_st(&wp->cw, 0);
2047 if (!rcpt_st)
2048 return 0;
2049
2050 ++wp->cw.tgts;
2051 memcpy(rcpt_st->env_to, rcpt, rcpt_len);
2052 rcpt_st->env_to[rcpt_len] = '\0';
2053 if (user_len)
2054 memcpy(rcpt_st->user, user, user_len);
2055 rcpt_st->user[user_len] = '\0';
2056
2057 rcpt_st->dir[0] = '\0';
2058 if (user_len != 0
2059 && !get_user_dir(rcpt_st, user, user_len, 0, 0))
2060 thr_trace_msg(&wp->cw, "%s", wp->cw.emsg);
2061
2062 return rcpt_st;
2063 }
2064
2065
2066
2067 /* read lines of pairs if (env_To, username) values from the MTA */
2068 static void
2069 get_ascii_rcpts(WORK *wp)
2070 {
2071 const char *addr, *user, *user_end;
2072 char c;
2073 int addr_len, user_len;
2074 RCPT_ST *rcpt_st;
2075
2076 lock_work();
2077 wp->dfgs |= DFG_WORK_LOCK;
2078 for (;;) {
2079 ascii_read_line(wp);
2080
2081 /* stop after the empty line */
2082 addr = wp->msg_rd.out;
2083 if (*addr == '\0') {
2084 wp->msg_rd.out = wp->msg_rd.next_line;
2085 break;
2086 }
2087
2088 user_end = wp->msg_rd.next_line;
2089 while (user_end > addr
2090 && ((c = *(user_end-1)) == ' ' || c == '\t'))
2091 --user_end;
2092
2093 /* bytes up to '\r' are the env_To value
2094 * and bytes after are the local recipient */
2095 user = strchr(addr, '\r');
2096 if (!user) {
2097 addr_len = user_end - wp->msg_rd.out;
2098 user = user_end;
2099 } else {
2100 addr_len = user - addr;
2101 ++user;
2102 }
2103
2104 user_len = user_end - user;
2105 if (!check_addr(wp, &addr, &addr_len, &user, &user_len))
2106 job_exit(wp);
2107 rcpt_st = set_rcpt(wp, addr, addr_len, user, user_len);
2108 if (!rcpt_st)
2109 job_exit(wp);
2110
2111 /* Don't worry if this recipient is incompatible with preceding
2112 * recipients, because there is nothing we can do.
2113 * We cannot reject a recipient when the ASCII protocol is used,
2114 * but side effects of the check are required */
2115 cmn_compat_whitelist(&wp->cw, rcpt_st);
2116
2117 wp->msg_rd.out = wp->msg_rd.next_line;
2118 }
2119 unlock_work();
2120 wp->dfgs &= ~DFG_WORK_LOCK;
2121 }
2122
2123
2124
2125 /* We are finished with one SMTP message with the ASCII protocol
2126 * Send the result to the MTA and end this thread */
2127 static void NRATTRIB
2128 ascii_done(WORK *wp, DCCIF_RESULT_CHAR result_char)
2129 {
2130 const char *result_str;
2131 RCPT_ST *rcpt_st;
2132 char *p;
2133
2134 result_str = wp->cw.reply.log_result;
2135 if (!result_str)
2136 thr_error_msg(&wp->cw, "rejection reason undecided");
2137 else
2138 thr_log_print(&wp->cw, 0, DCC_XHDR_RESULT"%s\n", result_str);
2139
2140 /* tell MTA the overall result */
2141 p = wp->msg_rd.base;
2142 *p++ = result_char;
2143 *p++ = '\n';
2144
2145 /* output list of recipients */
2146 for (rcpt_st = wp->cw.rcpt_st_first; rcpt_st; rcpt_st = rcpt_st->fwd) {
2147 if (p >= &wp->msg_rd.base[wp->msg_rd.size-1]) {
2148 buf_write(wp, &wp->msg_wt,
2149 wp->msg_rd.base, wp->msg_rd.size-1);
2150 p = wp->msg_rd.base;
2151 }
2152 if (result_char == DCCIF_RESULT_GREY) {
2153 *p++ = DCCIF_RCPT_GREY;
2154 } else if (rcpt_st->fgs & RCPT_FG_ISSPAM) {
2155 *p++ = DCCIF_RCPT_REJECT;
2156 } else {
2157 *p++ = DCCIF_RCPT_ACCEPT;
2158 }
2159 }
2160 *p++ = '\n';
2161 buf_write(wp, &wp->msg_wt, wp->msg_rd.base, p-wp->msg_rd.base);
2162
2163 /* send the body from the temporary file to the MTA */
2164 if (wp->dfgs & DFG_MTA_BODY) {
2165 body_copy(wp);
2166
2167 } else if (wp->dfgs & DFG_MTA_HEADER) {
2168 /* MTA wants only the header, if we have it */
2169 if (wp->cw.header.used != 0)
2170 buf_write(wp, &wp->msg_wt, wp->cw.header.buf,
2171 wp->cw.header.used);
2172 buf_write(wp, &wp->msg_wt, "\n", 1);
2173 }
2174
2175 job_exit(wp);
2176 }
2177
2178
2179
2180 /* use the dccifd ASCII protocol to talk to an MTA */
2181 static void NRATTRIB
2182 ascii_job(WORK *wp)
2183 {
2184 # define AOPT(s) ascii_opt_str(wp, s, LITZ(s))
2185 char *p;
2186 int i;
2187
2188 log_start(&wp->cw);
2189
2190 wp->msg_rd.base = wp->buf1;
2191 wp->msg_rd.size = sizeof(wp->buf1);
2192 wp->msg_rd.socp = &wp->proxy_in_soc;
2193
2194 wp->msg_wt.base = wp->buf2;
2195 wp->msg_wt.size = sizeof(wp->buf2);
2196 wp->msg_wt.socp = &wp->proxy_in_soc;
2197
2198 /* get any options */
2199 ascii_read_line(wp);
2200 while (*wp->msg_rd.out != '\0') {
2201 if (AOPT(DCCIF_OPT_LOG)) {
2202 wp->cw.ask_st |= ASK_ST_LOGIT;
2203 continue;
2204 }
2205 if (AOPT(DCCIF_OPT_SPAM)) {
2206 wp->cw.ask_st |= ASK_ST_MTA_ISSPAM;
2207 continue;
2208 }
2209 if (AOPT(DCCIF_OPT_BODY)) {
2210 wp->dfgs |= DFG_MTA_BODY;
2211 continue;
2212 }
2213 if (AOPT(DCCIF_OPT_HEADER)) {
2214 wp->dfgs |= DFG_MTA_HEADER;
2215 continue;
2216 }
2217 if (AOPT(DCCIF_OPT_QUERY)) {
2218 wp->cw.ask_st |= ASK_ST_QUERY;
2219 continue;
2220 }
2221 if (AOPT(DCCIF_OPT_GREY_QUERY)) {
2222 wp->cw.ask_st |= ASK_ST_QUERY_GREY;
2223 continue;
2224 }
2225 if (AOPT(DCCIF_OPT_NO_REJECT)) {
2226 wp->cw.action = CMN_IGNORE;
2227 continue;
2228 }
2229 if (AOPT(DCCIF_OPT_RCVD_NXT)
2230 || AOPT(DCCIF_OPT_RCVD_NEXT)) {
2231 ++wp->parse_rcvd;
2232 continue;
2233 }
2234
2235 thr_error_msg(&wp->cw, "unrecognized option value: \"%s\"",
2236 wp->msg_rd.out);
2237 job_exit(wp);
2238 }
2239 if ((wp->cw.ask_st & ASK_ST_MTA_ISSPAM)
2240 && (wp->cw.ask_st & ASK_ST_QUERY)) {
2241 thr_error_msg(&wp->cw, DCCIF_OPT_SPAM" and "DCCIF_OPT_QUERY
2242 " are incompatible");
2243 wp->cw.ask_st &= ~ASK_ST_MTA_ISSPAM;
2244 }
2245 if ((wp->cw.ask_st & ASK_ST_MTA_ISSPAM)
2246 && (wp->cw.ask_st & ASK_ST_QUERY_GREY)) {
2247 thr_error_msg(&wp->cw, DCCIF_OPT_SPAM" and "DCCIF_OPT_GREY_QUERY
2248 " are incompatible");
2249 wp->cw.ask_st &= ~ASK_ST_MTA_ISSPAM;
2250 }
2251 if (dcc_query_only
2252 && (wp->cw.ask_st & ASK_ST_MTA_ISSPAM)) {
2253 thr_error_msg(&wp->cw, DCCIF_OPT_SPAM" and -Q"
2254 " are incompatible");
2255 wp->cw.ask_st &= ~ASK_ST_MTA_ISSPAM;
2256 }
2257 wp->msg_rd.out = wp->msg_rd.next_line;
2258
2259 /* open the connection to the nearest DCC server
2260 * and figure out our X-DCC header */
2261 if (!ck_dcc_ctxt(&wp->cw)) {
2262 /* failed to create context */
2263 make_reply(&wp->cw.reply, &dcc_fail_reply, &wp->cw, 0);
2264 ascii_done(wp, DCCIF_RESULT_TEMP);
2265 }
2266
2267 dcc_cks_init(&wp->cw.cks);
2268 dcc_dnsbl_init(&wp->cw.cks, wp->cw.dcc_ctxt, &wp->cw, wp->cw.id);
2269 wp->cw.cmn_fgs |= CMN_FG_LOG_EARLY;
2270
2271 /* get the SMTP client IP address and host name */
2272 i = ascii_read_line(wp);
2273 if (i == 0
2274 || !strcmp("0.0.0.0", wp->msg_rd.out)
2275 || !strcmp("0.0.0.0\r0.0.0.0", wp->msg_rd.out)) {
2276 /* it is absent or the SpamAssassin junk */
2277 wp->dfgs |= DFG_PARSE_RCVD; /* try a Received header */
2278 } else {
2279 /* the host name follows the IP address */
2280 p = strchr(wp->msg_rd.out, '\r');
2281 if (p) {
2282 *p++ = '\0';
2283 BUFCPY(wp->cw.clnt_name, p);
2284 }
2285 /* convert ASCCI representation of IP address to a
2286 * canonical form and to a checksum */
2287 if (!dcc_get_str_ip_ck(&wp->cw.cks, wp->msg_rd.out)) {
2288 thr_error_msg(&wp->cw, "unrecognized IP address \"%s\"",
2289 wp->msg_rd.out);
2290 } else {
2291 /* convert IPv6 address to a canonical string */
2292 wp->cw.clnt_addr = wp->cw.cks.ip_addr;
2293 dcc_ipv6tostr(wp->cw.clnt_str, sizeof(wp->cw.clnt_str),
2294 &wp->cw.clnt_addr);
2295 }
2296 }
2297 wp->msg_rd.out = wp->msg_rd.next_line;
2298
2299 /* get the HELO value */
2300 i = ascii_read_line(wp);
2301 get_helo(wp, wp->msg_rd.out, i);
2302 wp->msg_rd.out = wp->msg_rd.next_line;
2303
2304 /* get the envelope Mail_From value */
2305 i = ascii_read_line(wp);
2306 if (i > ISZ(wp->cw.env_from)-1)
2307 i = ISZ(wp->cw.env_from)-1;
2308 memcpy(wp->cw.env_from, wp->msg_rd.out, i);
2309 wp->cw.env_from[i] = '\0';
2310 wp->msg_rd.out = wp->msg_rd.next_line;
2311
2312 get_sender(wp);
2313
2314 /* get the list of recipients from the MTA */
2315 get_ascii_rcpts(wp);
2316
2317 if (!get_body(wp)) {
2318 /* something wrong while collecting the message body
2319 * such as contacting the DCC server */
2320 ascii_done(wp, DCCIF_RESULT_TEMP);
2321 }
2322
2323 /* get consensus of targets' wishes */
2324 users_process(&wp->cw);
2325 totals.tgts_rejected += wp->cw.reject_tgts;
2326 /* log the consensus & generate SMTP rejection message if needed */
2327 users_log_result(&wp->cw, 0);
2328
2329 if (wp->cw.ask_st & ASK_ST_GREY_EMBARGO) {
2330 totals.tgts_embargoed += wp->cw.tgts;
2331 ascii_done(wp, DCCIF_RESULT_GREY);
2332 }
2333
2334 if (wp->cw.reject_tgts != 0
2335 || (wp->cw.tgts == 0 && (wp->cw.ask_st & ASK_ST_CLNT_ISSPAM))) {
2336 if (wp->cw.action != CMN_IGNORE) {
2337 if (!wp->cw.reply.log_result)
2338 wp->cw.reply.log_result=DCC_XHDR_RESULT_REJECT;
2339 ascii_done(wp, DCCIF_RESULT_REJECT);
2340 } else {
2341 wp->cw.reply.log_result = DCC_XHDR_RESULT_I_A;
2342 ascii_done(wp, DCCIF_RESULT_OK);
2343 }
2344 }
2345
2346 wp->cw.reply.log_result = DCC_XHDR_RESULT_ACCEPT;
2347 ascii_done(wp, DCCIF_RESULT_OK);
2348
2349 # undef AOPT
2350 }
2351
2352
2353
2354 #define SMTP_REPLY_221 "221 dccifd closing connection"
2355 #define SMTP_REPLY_220 "220 dccifd proxy ready"
2356 #define SMTP_REPLY_250_DATA "250 dccifd mail ok"
2357 #define SMTP_REPLY_250_RSET "250 dccifd RSET ok"
2358 #define SMTP_REPLY_250_HELO "250 dccifd HELO ok"
2359 #define SMTP_REPLY_250_POSTFIX "250 dccifd Postfix extension ok"
2360 #define SMTP_REPLY_250_RCPT "250 dccifd Recipient ok"
2361 #define SMTP_REPLY_250_MAIL "250 dccifd Sender ok"
2362 #define SMTP_REPLY_350 "354 Enter mail to dccifd"
2363 #define SMTP_REPLY_452_WLIST "452 4.5.3 "DCC_XHDR_INCOMPAT_WLIST
2364 #define SMTP_REPLY_452_2MANY "452 4.5.3 "DCC_XHDR_TOO_MANY_RCPTS
2365 #define SMTP_REPLY_500 "500 5.0.0 dccifd command unrecognized"
2366 #define SMTP_REPLY_501_NO_ARG "501 5.5.4 dccifd command arg required"
2367 #define SMTP_REPLY_501_BAD_ARG "501 5.5.1 dccifd command unrecognized"
2368 #define SMTP_REPLY_501_RCPT "501 5.5.2 dccifd RCPT command syntax error"
2369 #define SMTP_REPLY_501_POSTFIX "501 5.5.2 dccifd Postfix extension syntax error"
2370 #define SMTP_REPLY_503_DATA "503 5.0.0 dccifd need RCPT"
2371 #define SMTP_REPLY_503 "503 5.0.0 bad dccifd command sequence"
2372
2373 #define SMTP_DEBUG_TRACE 3
2374
2375
2376 static void
2377 smtp_trace(WORK *wp, const char *type, const char *buf, int len)
2378 {
2379 if (dcc_clnt_debug < SMTP_DEBUG_TRACE)
2380 return;
2381
2382 log_start(&wp->cw);
2383 if (len > 0 && buf[len-1] == '\n')
2384 --len;
2385 if (len > 0 && buf[len-1] == '\r')
2386 --len;
2387 thr_trace_msg(&wp->cw, "%s %s: %.*s", wp->cw.id, type, len, buf);
2388 }
2389
2390
2391
2392 /* send an SMTP reply upstream to the SMTP client of our proxy */
2393 static void
2394 smtp_send_reply(WORK *wp, const char *reply, int len)
2395 {
2396 if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
2397 smtp_trace(wp, "SMTP response", reply, len);
2398 buf_write(wp, &wp->reply_out, reply, len);
2399 buf_write(wp, &wp->reply_out, "\r\n", 2);
2400 }
2401
2402 #define SMTP_REPLY(r) smtp_send_reply(wp,SMTP_REPLY_##r,LITZ(SMTP_REPLY_##r))
2403
2404 static void
2405 smtp_reply_error(WORK *wp, const char *reply, int len)
2406 {
2407 smtp_send_reply(wp, reply, len);
2408 wp->msg_rd.out = wp->msg_rd.next_line;
2409 wp->smtp_state = SMTP_ST_ERROR;
2410 if (dcc_clnt_debug)
2411 wp->cw.ask_st |= ASK_ST_LOGIT;
2412 /* depend on '\n' ending the SMTP message */
2413 thr_log_print(&wp->cw, 1, "%s", reply);
2414 }
2415
2416 #define SMTP_ERROR(r) smtp_reply_error(wp,SMTP_REPLY_##r,LITZ(SMTP_REPLY_##r))
2417
2418
2419
2420 /* look for an SMTP verb */
2421 typedef enum {
2422 SMTP_VERB_ERR, /* bad command */
2423 SMTP_VERB_UNREC, /* unrecognized command */
2424 SMTP_VERB_RSET,
2425 SMTP_VERB_HELO,
2426 SMTP_VERB_XCLIENT, /* Postfix extension */
2427 SMTP_VERB_XFORWARD, /* Postfix extension */
2428 SMTP_VERB_MAIL, /* Mail From */
2429 SMTP_VERB_RCPT, /* Rcpt To */
2430 SMTP_VERB_DATA,
2431 SMTP_VERB_QUIT
2432 } VERB_SMTP;
2433 typedef struct {
2434 const char *str;
2435 int str_len;
2436 const char *parm;
2437 int parm_len;
2438 VERB_SMTP verb;
2439 u_char arg_required;
2440 } PT;
2441 PT pt[] = {
2442 #define PT_M(s,p,v,r) {s,LITZ(s),p,LITZ(p),SMTP_VERB_##v,r}
2443 PT_M("HELO","", HELO, 1),
2444 PT_M("EHLO","", HELO, 1),
2445 PT_M("Mail","From:", MAIL, 1),
2446 PT_M("Rcpt","To:", RCPT, 1),
2447 PT_M("DATA","", DATA, 0),
2448 PT_M("XFORWARD","", XFORWARD, 1),
2449 PT_M("XCLIENT","", XCLIENT, 1),
2450 PT_M("RSET","", RSET, 0),
2451 PT_M("QUIT","", QUIT, 0),
2452 #undef PT_M
2453 };
2454
2455 #define RESP_DIGIT(c) ((c) >= '0' && (c) <= '9')
2456
2457 static VERB_SMTP
2458 get_smtp_verb(WORK *wp,
2459 const char **ppp, /* parameter after command */
2460 const char **pep) /* end of parameter */
2461 {
2462 PT *ptp;
2463 const char *lp, *pp;
2464 int i, len;
2465
2466 /* skip leading whitespace */
2467 wp->msg_rd.out += strspn(wp->msg_rd.out, " \t");
2468 lp = wp->msg_rd.out;
2469
2470 if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
2471 smtp_trace(wp, "SMTP command", lp, wp->msg_rd.next_line - lp);
2472
2473 for (ptp = pt; ptp < &pt[DIM(pt)]; ++ptp) {
2474 len = ptp->str_len;
2475 pp = lp+len;
2476 if (pp+2 > wp->msg_rd.next_line
2477 || strncasecmp(lp, ptp->str, len))
2478 continue;
2479
2480 if (pp+2 == wp->msg_rd.next_line) {
2481 /* SMTP command by itself on the line */
2482 *ppp = 0;
2483 *pep = 0;
2484 if (ptp->arg_required) {
2485 SMTP_REPLY(501_NO_ARG);
2486 return SMTP_VERB_ERR;
2487 }
2488 /* finished, having matched the command */
2489 return ptp->verb;
2490 }
2491
2492 /* all other commands require following text separated
2493 * from the command with whitespace, as in "Mail From:" */
2494 i = strspn(pp, " \t");
2495 if (i == 0)
2496 continue;
2497 pp += i;
2498
2499 /* skip the initial part of the parameter, such as "From:" */
2500 if (ptp->parm_len) {
2501 if (*pp == '\r') {
2502 SMTP_REPLY(501_NO_ARG);
2503 return SMTP_VERB_ERR;
2504 }
2505 if (strncasecmp(pp, ptp->parm, ptp->parm_len)) {
2506 SMTP_REPLY(501_BAD_ARG);
2507 return SMTP_VERB_ERR;
2508 }
2509 pp += ptp->parm_len;
2510 pp += strspn(pp, " \t");
2511 }
2512
2513 *ppp = pp;
2514 *pep = strpbrk(pp, " \t\r");
2515 return ptp->verb;
2516 }
2517
2518 *ppp = 0;
2519 return SMTP_VERB_UNREC;
2520 }
2521
2522
2523
2524 /* reset and restart the SMTP proxy state */
2525 static void
2526 proxy_rset(WORK *wp)
2527 {
2528 if (wp->smtp_state == SMTP_ST_START)
2529 return;
2530
2531 cmn_clear(&wp->cw, wp, 0);
2532 wp->dfgs &= DFG_RECYCLE;
2533 wp->smtp_state = SMTP_ST_START;
2534
2535 dcc_cks_init(&wp->cw.cks);
2536 dcc_dnsbl_init(&wp->cw.cks, wp->cw.dcc_ctxt, &wp->cw, wp->cw.id);
2537 wp->cw.cmn_fgs |= CMN_FG_LOG_EARLY;
2538
2539 /* Values from the Postfix XCLIENT ESMTP extension endure for the
2540 * entire session. Values from the XFORWARD extension or guesses from
2541 * a HELO command are cleared at the end of the SMTP transaction. */
2542 if (!(wp->dfgs & DFG_XCLIENT_NAME))
2543 wp->cw.clnt_name[0] = '\0';
2544 if (wp->dfgs & DFG_XCLIENT_ADDR) {
2545 dcc_get_ipv6_ck(&wp->cw.cks, &wp->cw.clnt_addr);
2546 } else {
2547 memset(&wp->cw.clnt_addr, 0, sizeof(wp->cw.clnt_addr));
2548 wp->cw.clnt_str[0] = '\0';
2549 }
2550 if (!(wp->dfgs & DFG_XCLIENT_HELO))
2551 wp->cw.helo[0] = '\0';
2552 }
2553
2554
2555
2556 /* deal with aborted SMTP sessions */
2557 static void
2558 proxy_abort(WORK *wp, const char *log_msg)
2559 {
2560 if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
2561 smtp_trace(wp, "reset", log_msg, strlen(log_msg));
2562
2563 if (wp->smtp_state != SMTP_ST_START
2564 && wp->smtp_state != SMTP_ST_HELO
2565 && wp->smtp_state != SMTP_ST_ERROR) {
2566
2567 wp->cw.ask_st |= ASK_ST_INVALID_MSG;
2568
2569 if (!(wp->cw.cmn_fgs & CMN_FG_ENV_LOGGED))
2570 thr_log_envelope(&wp->cw, 1);
2571 dcc_cks_fin(&wp->cw.cks);
2572 LOG_CAPTION(wp, "\n"DCC_LOG_MSG_SEP);
2573 thr_log_late(&wp->cw);
2574 cmn_ask_white(&wp->cw);
2575
2576 users_process(&wp->cw);
2577 users_log_result(&wp->cw, log_msg);
2578
2579 /* create log files for -d
2580 * and without any recipents but with "option log-all" */
2581 if (dcc_clnt_debug
2582 || (wp->cw.init_sws & FLTR_SW_LOG_ALL))
2583 wp->cw.ask_st |= ASK_ST_LOGIT;
2584
2585 if (wp->cw.ask_st & ASK_ST_LOGIT)
2586 thr_log_print(&wp->cw, 0,
2587 DCC_XHDR_RESULT"%s\n", log_msg);
2588 }
2589 proxy_rset(wp);
2590 }
2591
2592
2593
2594 /* quit a truncated message */
2595 static void NRATTRIB
2596 proxy_msg_truncated(WORK *wp)
2597 {
2598 if (!proxy) {
2599 thr_error_msg(&wp->cw, "truncated message");
2600 } else {
2601 proxy_abort(wp, "SMTP session aborted");
2602 }
2603 job_exit(wp);
2604 }
2605
2606
2607
2608 /* pass a line from the downstream proxy to our upstream */
2609 static void
2610 smtp_pass_line(WORK *wp)
2611 {
2612 int len;
2613
2614 len = wp->reply_in.next_line - wp->reply_in.out;
2615 if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
2616 smtp_trace(wp, "pass SMTP response", wp->reply_in.out, len);
2617
2618 buf_write(wp, &wp->reply_out, wp->reply_in.out,
2619 wp->reply_in.next_line - wp->reply_in.out);
2620
2621 wp->reply_in.out = wp->reply_in.next_line;
2622 }
2623
2624
2625
2626 static int /* -1 or 1st digit of response */
2627 smtp_get_resp(WORK *wp,
2628 u_char pass, /* 1=pass it upstream */
2629 char *logbuf, /* log response here */
2630 int logbuflen)
2631 {
2632 int len;
2633 char dig, cont;
2634
2635 for (;;) {
2636 if (!msg_read_line(wp, &wp->reply_in)) {
2637 if (pass)
2638 job_exit(wp);
2639 return -1;
2640 }
2641 len = wp->reply_in.next_line - wp->reply_in.out;
2642 if (len < 5) {
2643 thr_error_msg(&wp->cw, "short SMTP response"
2644 " \"%s\" from proxy server",
2645 wp->reply_in.out);
2646 return -1;
2647 }
2648 dig = wp->reply_in.out[0];
2649 cont = wp->reply_in.out[3];
2650 if ((dig < '1' || dig > '5')
2651 || !RESP_DIGIT(wp->reply_in.out[1])
2652 || !RESP_DIGIT(wp->reply_in.out[2])
2653 || (cont != ' ' && cont != '-')) {
2654 *wp->reply_in.next_line = '\0';
2655 thr_error_msg(&wp->cw, "unrecognized SMTP response"
2656 " \"%s\" from proxy",
2657 wp->reply_in.out);
2658 return -1;
2659 }
2660
2661 if (logbuflen != 0) {
2662 if (logbuflen > len)
2663 logbuflen = len;
2664 memcpy(logbuf, wp->reply_in.out, logbuflen);
2665 while (logbuflen > 0
2666 && (logbuf[logbuflen-1] == '\r'
2667 || logbuf[logbuflen-1] == '\n'))
2668 --logbuflen;
2669 logbuf[logbuflen] = '\0';
2670
2671 /* log only the first line */
2672 logbuflen = 0;
2673 }
2674
2675 if (pass) {
2676 /* pass the next line upstream */
2677 smtp_pass_line(wp);
2678 if (cont == ' ')
2679 return dig;
2680 } else {
2681 /* we don't like multi-line responses */
2682 if (cont == ' ')
2683 return dig;
2684
2685 *wp->reply_in.next_line = '\0';
2686 thr_error_msg(&wp->cw, "unacceptable multi-line"
2687 " SMTP response"
2688 " \"%s\" from proxy",
2689 wp->reply_in.out);
2690 return -1;
2691 }
2692 }
2693 }
2694
2695
2696
2697 /* pass an SMTP command from upstream or the SMTP client of our proxy
2698 * downstream to the SMTP server of our proxy.
2699 * Then relay the SMTP server's response upstream to the SMTP client */
2700 static u_char /* 0=server was unhappy */
2701 smtp_pass_cmd(WORK *wp,
2702 const char *reply, /* send this response upstream if */
2703 int reply_len, /* if downstream is happy */
2704 char *logbuf, /* logbuf response here */
2705 int logbuflen)
2706 {
2707 int len, result;
2708
2709 /* fake it if the SMTP server is /dev/null */
2710 if (proxy_out_family == AF_UNSPEC) {
2711 smtp_send_reply(wp, reply, reply_len);
2712 return 1;
2713 }
2714
2715 len = wp->msg_rd.next_line - wp->msg_rd.out;
2716 if (len != 0) {
2717 buf_write(wp, &wp->msg_wt, wp->msg_rd.out, len);
2718 buf_write_flush(wp, &wp->msg_wt);
2719 }
2720
2721 /* wait for & pass on response */
2722 result = smtp_get_resp(wp, 1, logbuf, logbuflen);
2723 if (result < 0)
2724 job_exit(wp);
2725 return (result == reply[0]);
2726 }
2727
2728 #define SMTP_PASS_CMD(r) smtp_pass_cmd(wp,SMTP_REPLY_##r,LITZ(SMTP_REPLY_##r), \
2729 0, 0)
2730
2731
2732
2733 /* parse Postfix XFORWARD and XCLIENT commands
2734 * see http://www.postfix.org/XCLIENT_README.html
2735 * and http://www.postfix.org/XFORWARD_README.html
2736 * Depend on the Postfix downstream to answer EHLO with XFORWRD */
2737 static void
2738 smtp_xpostfix(WORK *wp,
2739 VERB_SMTP verb, /* SMTP_VERB_XFORWARD or _XCLIENT */
2740 const char *parm)
2741 {
2742 const char *val, *end_parm;
2743 char addr[INET6_ADDRSTRLEN+1];
2744 int i;
2745
2746 wp->smtp_state = SMTP_ST_HELO;
2747
2748 for (; ; parm = end_parm + strspn(end_parm, " \t")) {
2749 if (*parm == '\r' || *parm == '\n') {
2750 SMTP_PASS_CMD(250_POSTFIX);
2751 break;
2752 }
2753
2754 /* find the value */
2755 val = strpbrk(parm, "= \t\r\n");
2756 if (*val++ != '=') {
2757 SMTP_REPLY(501_POSTFIX);
2758 break;
2759 }
2760
2761 end_parm = strpbrk(val, " \t\r\n");
2762
2763 if (!CLITCMP(parm, "NAME=")) {
2764 if (!CLITCMP(val, "[UNAVAILABLE]")
2765 || !CLITCMP(val, "[TEMPUNAVAIL]")) {
2766 wp->cw.clnt_name[0] = '\0';
2767 continue;
2768 }
2769 i = min(ISZ(wp->cw.clnt_name)-1, end_parm-val);
2770 memcpy(wp->cw.clnt_name, val, i);
2771 wp->cw.clnt_name[i] = '\0';
2772 if (verb == SMTP_VERB_XCLIENT)
2773 wp->dfgs |= DFG_XCLIENT_NAME;
2774 else
2775 wp->dfgs &= ~DFG_XCLIENT_NAME;
2776 continue;
2777 }
2778
2779 if (!CLITCMP(parm, "ADDR=")) {
2780 if (!CLITCMP(val, "[UNAVAILABLE]")
2781 || !CLITCMP(val, "[TEMPUNAVAIL]")) {
2782 wp->cw.clnt_str[0] = '\0';
2783 continue;
2784 }
2785 if (!CLITCMP(val, "IPV6"))
2786 val += LITZ("IPV6");
2787 i = min(ISZ(addr)-1, end_parm-val);
2788 memcpy(addr, val, i);
2789 addr[i] = '\0';
2790 /* try to convert ASCCI representation of IP address to
2791 * a canonical form and to a checksum */
2792 if (!dcc_get_str_ip_ck(&wp->cw.cks, addr)) {
2793 thr_error_msg(&wp->cw,
2794 "unrecognized IP address \"%s\"",
2795 addr);
2796 wp->cw.clnt_str[0] = '\0';
2797 continue;
2798 }
2799 wp->cw.clnt_addr = wp->cw.cks.ip_addr;
2800 dcc_ipv6tostr(wp->cw.clnt_str, sizeof(wp->cw.clnt_str),
2801 &wp->cw.clnt_addr);
2802 if (verb == SMTP_VERB_XCLIENT) {
2803 wp->dfgs |= DFG_XCLIENT_ADDR;
2804 } else {
2805 wp->dfgs &= ~DFG_XCLIENT_ADDR;
2806 }
2807 continue;
2808 }
2809
2810 if (!CLITCMP(parm, "HELO=")) {
2811 if (!CLITCMP(val, "[UNAVAILABLE]")
2812 || !CLITCMP(val, "[TEMPUNAVAIL]")) {
2813 wp->cw.helo[0] = '\0';
2814 } else {
2815 get_helo(wp, val, end_parm-val);
2816 }
2817 if (verb == SMTP_VERB_XCLIENT)
2818 wp->dfgs |= DFG_XCLIENT_HELO;
2819 else
2820 wp->dfgs &= ~DFG_XCLIENT_HELO;
2821 continue;
2822 }
2823 }
2824
2825 wp->msg_rd.out = wp->msg_rd.next_line;
2826 }
2827
2828
2829
2830 /* parse SMTP Rcpt_To command */
2831 static u_char /* 1=now seen >=1 good Rcpt command*/
2832 get_smtp_rcpt(WORK *wp,
2833 const char *path,
2834 const char *epath)
2835 {
2836 const char *user;
2837 int path_len, user_len;
2838 RCPT_ST *rcpt_st;
2839
2840 path_len = epath - path;
2841 user = "";
2842 user_len = 0;
2843 if (!check_addr(wp, &path, &path_len, &user, &user_len)) {
2844 SMTP_REPLY(501_RCPT);
2845 wp->msg_rd.out = wp->msg_rd.next_line;
2846 return 0;
2847 }
2848
2849 lock_work();
2850 wp->dfgs |= DFG_WORK_LOCK;
2851 rcpt_st = set_rcpt(wp, path, path_len, user, user_len);
2852 unlock_work();
2853 wp->dfgs &= ~DFG_WORK_LOCK;
2854 if (!rcpt_st) {
2855 SMTP_REPLY(452_2MANY);
2856 wp->msg_rd.out = wp->msg_rd.next_line;
2857 return 0;
2858 }
2859
2860 /* if this recipient is incompatible with preceding recipients
2861 * then reject it and remember to put something into the log */
2862 if (!cmn_compat_whitelist(&wp->cw, rcpt_st)) {
2863 --wp->cw.tgts;
2864 SMTP_REPLY(452_WLIST);
2865 wp->msg_rd.out = wp->msg_rd.next_line;
2866 BUFCPY(rcpt_st->rej_msg, SMTP_REPLY_452_WLIST);
2867 rcpt_st->rej_result = DCC_XHDR_INCOMPAT_WLIST;
2868 rcpt_st->fgs |= RCPT_FG_REJ_FILTER;
2869 return 0;
2870 }
2871
2872 /* Try to pass the command in the buffer downstream.
2873 * Forget this recipient if it is not good enough downstream.
2874 * Let the downstream proxy do its own logging.
2875 * That way we need save only one bit instead of an SMTP
2876 * rejection message for our eventual per-user log file. */
2877 if (!smtp_pass_cmd(wp, SMTP_REPLY_250_RCPT, LITZ(SMTP_REPLY_250_RCPT),
2878 rcpt_st->rej_msg, sizeof(rcpt_st->rej_msg))) {
2879 --wp->cw.tgts;
2880 wp->msg_rd.out = wp->msg_rd.next_line;
2881 rcpt_st->rej_result = rcpt_st->rej_msg;
2882 rcpt_st->rej_result += strspn(rcpt_st->rej_result,
2883 "0123456789. ");
2884 if (rcpt_st->rej_msg[0] != '4')
2885 ++wp->cw.mta_rej_tgts;
2886 wp->cw.ask_st |= ASK_ST_LOGIT;
2887 rcpt_st->fgs |= RCPT_FG_REJ_FILTER;
2888 return 0;
2889 }
2890
2891 /* there was no problem if the downstream was happy */
2892 rcpt_st->rej_msg[0] = '\0';
2893
2894 wp->msg_rd.out = wp->msg_rd.next_line;
2895 return 1;
2896 }
2897
2898
2899
2900 static void
2901 smtp_data_abort(WORK *wp)
2902 {
2903 /* After a rejection Postfix wants a before-queue proxy to
2904 * "abort the connnection" to the SMTP server downstream.
2905 * That might mean a simple TCP shutdown, but for testing with
2906 * sendmail, send an SMTP RSET command. */
2907 if (proxy_out_family != AF_UNSPEC) {
2908 buf_write(wp, &wp->msg_wt, "RSET\r\n", LITZ("RSET\r\n"));
2909 buf_write_flush(wp, &wp->msg_wt);
2910 /* sendmail will respond, but what about Postfix? */
2911 if (smtp_get_resp(wp, 0, 0, 0) >= 0
2912 && dcc_clnt_debug >= SMTP_DEBUG_TRACE) {
2913 smtp_trace(wp, "ignore response to RSET",
2914 wp->reply_in.out,
2915 wp->reply_in.next_line
2916 - wp->reply_in.out);
2917 wp->reply_in.out = wp->reply_in.next_line;
2918 }
2919 }
2920 }
2921
2922
2923
2924 /* We are rejecting the transaction with our generated reply */
2925 static void
2926 smtp_trans_reject(WORK *wp)
2927 {
2928 if (dcc_clnt_debug >= SMTP_DEBUG_TRACE)
2929 thr_trace_msg(&wp->cw, "%s SMTP response: %s %s %s",
2930 wp->cw.id,
2931 wp->cw.reply.rcode, wp->cw.reply.xcode,
2932 wp->cw.reply.str);
2933
2934 if (wp->proxy_in_soc >= 0) {
2935 buf_write(wp, &wp->reply_out,
2936 wp->cw.reply.rcode, strlen(wp->cw.reply.rcode));
2937 buf_write(wp, &wp->reply_out, " ", 1);
2938 buf_write(wp, &wp->reply_out,
2939 wp->cw.reply.xcode, strlen(wp->cw.reply.xcode));
2940 buf_write(wp, &wp->reply_out, " ", 1);
2941 buf_write(wp, &wp->reply_out,
2942 wp->cw.reply.str, strlen(wp->cw.reply.str));
2943 buf_write(wp, &wp->reply_out, "\r\n", 2);
2944 buf_write_flush(wp, &wp->reply_out);
2945 log_smtp_reply(&wp->cw);
2946 }
2947
2948 if (wp->proxy_out_soc >= 0)
2949 smtp_data_abort(wp);
2950 }
2951
2952
2953
2954 static void NRATTRIB
2955 smtp_temp_fail(WORK *wp)
2956 {
2957 make_reply(&wp->cw.reply, &dcc_fail_reply, &wp->cw, 0);
2958 smtp_trans_reject(wp);
2959 job_exit(wp);
2960 }
2961
2962
2963
2964 /* Send the mail message to the SMTP server for our proxy. */
2965 static const char *
2966 smtp_data_send(WORK *wp)
2967 {
2968 int class;
2969 int len;
2970 const char *result;
2971
2972 /* First send the DATA command. */
2973 buf_write(wp, &wp->msg_wt, "DATA\r\n", LITZ("DATA\r\n"));
2974 buf_write_flush(wp, &wp->msg_wt);
2975
2976 /* the server should respond with a 350 result */
2977 class = smtp_get_resp(wp, 0, 0, 0);
2978 if (class < 0)
2979 job_exit(wp);
2980
2981 if (class == '3') {
2982 wp->reply_in.out = wp->reply_in.next_line;
2983
2984 /* send the mail message to the SMTP server */
2985 body_copy(wp);
2986 buf_write(wp, &wp->msg_wt, ".\r\n", 3);
2987 buf_write_flush(wp, &wp->msg_wt);
2988
2989 /* see what it has to say */
2990 class = smtp_get_resp(wp, 0, 0, 0);
2991
2992 } else if (class != '4' && class != '5') {
2993 /* or quit if response to DATA was not 4yz or 5yz */
2994 thr_error_msg(&wp->cw, "unrecognized SMTP response"
2995 " \"%s\" from proxy to DATA command",
2996 wp->reply_in.out);
2997 job_exit(wp);
2998 }
2999
3000 if (class == '2') {
3001 result = DCC_XHDR_RESULT_ACCEPT;
3002 } else {
3003 len = wp->reply_in.next_line - wp->reply_in.out;
3004 while (len > 0 && (wp->reply_in.out[len-1] == '\r'
3005 || wp->reply_in.out[len-1] == '\n'))
3006 --len;
3007 if (len > ISZ(wp->cw.reply.str)-2)
3008 len = ISZ(wp->cw.reply.str)-2;
3009 memcpy(wp->cw.reply.str_buf, wp->reply_in.out, len);
3010 wp->cw.reply.str_buf[len++] = '\n';
3011 wp->cw.reply.str_buf[len] = '\0';
3012 wp->cw.reply.str = wp->cw.reply.str_buf;
3013 thr_log_print(&wp->cw, 0,
3014 "SMTP server "DCC_XHDR_REJ_DATA_MSG"%.*s",
3015 len, wp->cw.reply.str);
3016 if (dcc_clnt_debug)
3017 wp->cw.ask_st |= ASK_ST_LOGIT;
3018 result = "rejected by SMTP server";
3019 }
3020
3021 /* pass SMTP server's response to the SMTP client */
3022 smtp_pass_line(wp);
3023
3024 return result;
3025 }
3026
3027
3028
3029 static void
3030 smtp_data(WORK *wp)
3031 {
3032 const char *global_result, *user_result;
3033
3034 if (!wp->cw.num_rcpts) {
3035 /* never got SMTP DATA command */
3036 SMTP_ERROR(503_DATA);
3037 return;
3038 }
3039
3040 /* tell STMP client to send the mail message */
3041 wp->msg_rd.out = wp->msg_rd.next_line;
3042 SMTP_REPLY(350);
3043 buf_write_flush(wp, &wp->reply_out);
3044
3045 if (!get_body(wp)) {
3046 /* something went wrong while collecting the message body
3047 * such as contacting the DCC server */
3048 smtp_temp_fail(wp);
3049 return;
3050 }
3051
3052 /* get consensus of targets' wishes */
3053 users_process(&wp->cw);
3054
3055 if (wp->cw.ask_st & ASK_ST_GREY_EMBARGO) {
3056 totals.tgts_embargoed += wp->cw.tgts;
3057 smtp_trans_reject(wp);
3058 global_result = wp->cw.reply.log_result;
3059 user_result = global_result;
3060
3061 } else if (wp->cw.reject_tgts != 0) {
3062 switch (wp->cw.action) {
3063 case CMN_IGNORE:
3064 totals.tgts_ignored += wp->cw.reject_tgts;
3065 ++totals.msgs_spam;
3066 if (proxy_out_family == AF_UNSPEC) {
3067 SMTP_REPLY(250_DATA);
3068 global_result = DCC_XHDR_RESULT_I_A;
3069 } else {
3070 global_result = smtp_data_send(wp);
3071 }
3072 user_result = global_result;
3073 break;
3074
3075 case CMN_DISCARD:
3076 totals.tgts_discarded += wp->cw.reject_tgts;
3077 ++totals.msgs_spam;
3078 /* discard the message by aborting the downstream
3079 * SMTP session and telling the upstream 250-OK */
3080 if (proxy_out_family != AF_UNSPEC)
3081 smtp_data_abort(wp);
3082 SMTP_REPLY(250_DATA);
3083 global_result = DCC_XHDR_RESULT_DISCARD;
3084 user_result = global_result;
3085 break;
3086
3087 case CMN_REJECT:
3088 default:
3089 totals.tgts_rejected += wp->cw.reject_tgts;
3090 ++totals.msgs_spam;
3091 smtp_trans_reject(wp);
3092 global_result = wp->cw.reply.log_result;
3093 user_result = 0;
3094 break;
3095 }
3096
3097 } else if (proxy_out_family == AF_UNSPEC) {
3098 SMTP_REPLY(250_DATA);
3099 global_result = DCC_XHDR_RESULT_ACCEPT;
3100 user_result = 0;
3101
3102 } else {
3103 global_result = smtp_data_send(wp);
3104 user_result = 0;
3105 }
3106
3107 /* log the consensus & generate SMTP rejection message if needed */
3108 users_log_result(&wp->cw, user_result);
3109 thr_log_print(&wp->cw, 0, DCC_XHDR_RESULT"%s\n", global_result);
3110
3111 proxy_rset(wp);
3112 }
3113
3114
3115
3116 /* use a subset of SMTP to talk to an MTA */
3117 static void NRATTRIB
3118 proxy_job(WORK *wp)
3119 {
3120 char str[DCC_SU2STR_SIZE];
3121 DCC_SOCKU su;
3122 int out_soc;
3123 VERB_SMTP verb;
3124 const char *parmp, *eparmp;
3125 int i;
3126
3127 wp->msg_rd.base = wp->buf1;
3128 wp->msg_rd.size = sizeof(wp->buf1);
3129 wp->msg_rd.socp = &wp->proxy_in_soc;
3130
3131 wp->msg_wt.base = wp->buf2;
3132 wp->msg_wt.size = sizeof(wp->buf2);
3133 wp->msg_wt.socp = &wp->proxy_out_soc;
3134
3135 wp->reply_in.base = wp->buf3;
3136 wp->reply_in.size = sizeof(wp->buf3);
3137 wp->reply_in.socp = &wp->proxy_out_soc;
3138
3139 wp->reply_out.base = wp->buf4;
3140 wp->reply_out.size = sizeof(wp->buf4);
3141 wp->reply_out.socp = &wp->proxy_in_soc;
3142
3143 if (proxy_out_family == AF_UNSPEC) {
3144 /* single-ended */
3145 ;
3146
3147 } else if (proxy_out_family == AF_UNIX) {
3148 out_soc = socket(AF_UNIX, SOCK_STREAM, 0);
3149 if (out_soc < 0) {
3150 thr_error_msg(&wp->cw, "proxy_out socket(AF_UNIX): %s",
3151 ERROR_STR());
3152 smtp_temp_fail(wp);
3153 }
3154 if (0 > connect(out_soc,
3155 (struct sockaddr *)&proxy_out_sun,
3156 sizeof(listen_sun))) {
3157 thr_error_msg(&wp->cw,
3158 "proxy_out connect(AF_UNIX,%s): %s",
3159 proxy_out_sun.sun_path,
3160 ERROR_STR());
3161 close(out_soc);
3162 smtp_temp_fail(wp);
3163 }
3164 if (!set_soc(wp->cw.emsg, out_soc, AF_UNIX,
3165 "proxy output")) {
3166 thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
3167 close(out_soc);
3168 smtp_temp_fail(wp);
3169 }
3170 wp->proxy_out_soc = out_soc;
3171 wp->dfgs |= DFG_MTA_BODY;
3172
3173 } else {
3174 if (proxy_out_su.sa.sa_family == AF_UNSPEC) {
3175 /* use SMTP client's IP address */
3176 if (wp->proxy_in_su.sa.sa_family == AF_INET)
3177 dcc_mk_su(&su, AF_INET,
3178 &wp->proxy_in_su.ipv4.sin_addr,
3179 proxy_out_port);
3180 else
3181 dcc_mk_su(&su, AF_INET6,
3182 &wp->proxy_in_su.ipv6.sin6_addr,
3183 proxy_out_port);
3184 } else {
3185 su = proxy_out_su;
3186 }
3187 out_soc = socket(su.sa.sa_family, SOCK_STREAM, 0);
3188 if (out_soc < 0) {
3189 thr_error_msg(&wp->cw, "proxy_out socket(%s): %s",
3190 dcc_su2str(str, sizeof(str), &su),
3191 ERROR_STR());
3192 close(out_soc);
3193 smtp_temp_fail(wp);
3194 }
3195 if (0 > connect(out_soc, &su.sa, DCC_SU_LEN(&su))) {
3196 thr_error_msg(&wp->cw,
3197 "proxy_out connect(%s): %s",
3198 dcc_su2str(str, sizeof(str), &su),
3199 ERROR_STR());
3200 close(out_soc);
3201 smtp_temp_fail(wp);
3202 }
3203 if (!set_soc(wp->cw.emsg, out_soc, su.sa.sa_family,
3204 "proxy output")) {
3205 thr_error_msg(&wp->cw, "%s", wp->cw.emsg);
3206 close(out_soc);
3207 smtp_temp_fail(wp);
3208 }
3209 wp->proxy_out_soc = out_soc;
3210 wp->dfgs |= DFG_MTA_BODY;
3211 }
3212
3213 /* open the connection to the nearest DCC server
3214 * and figure out our X-DCC header */
3215 if (!ck_dcc_ctxt(&wp->cw)) {
3216 /* failed to create context */
3217 smtp_temp_fail(wp);
3218 }
3219 dcc_cks_init(&wp->cw.cks);
3220 dcc_dnsbl_init(&wp->cw.cks, wp->cw.dcc_ctxt, &wp->cw, wp->cw.id);
3221 wp->cw.cmn_fgs |= CMN_FG_LOG_EARLY;
3222
3223 /* pass the banner upstream */
3224 wp->smtp_state = SMTP_ST_START;
3225 SMTP_PASS_CMD(220);
3226
3227 /* assume for now that the IP address is the SMTP client to our proxy */
3228 if (wp->proxy_in_su.sa.sa_family != AF_UNIX) {
3229 if (wp->proxy_in_su.sa.sa_family == AF_INET) {
3230 dcc_ipv4toipv6(&wp->cw.clnt_addr,
3231 wp->proxy_in_su.ipv4.sin_addr);
3232 } else {
3233 wp->cw.clnt_addr = (wp->proxy_in_su.ipv6.sin6_addr);
3234 }
3235 dcc_ipv6tostr(wp->cw.clnt_str, sizeof(wp->cw.clnt_str),
3236 &wp->cw.clnt_addr);
3237 }
3238
3239 for (;;) {
3240 buf_write_flush(wp, &wp->reply_out);
3241 if (!msg_read_line(wp, &wp->msg_rd)) {
3242 proxy_abort(wp, "SMTP session aborted");
3243 job_exit(wp);
3244 }
3245 verb = get_smtp_verb(wp, &parmp, &eparmp);
3246
3247 /* deal with common SMTP commands */
3248 switch (verb) {
3249 case SMTP_VERB_ERR:
3250 wp->msg_rd.out = wp->msg_rd.next_line;
3251 continue;
3252 case SMTP_VERB_UNREC:
3253 /* pass anything we don't recognize to the SMTP server
3254 * for our proxy.
3255 * If we don't have a server, reject it */
3256 SMTP_PASS_CMD(500);
3257 wp->msg_rd.out = wp->msg_rd.next_line;
3258 continue;
3259 case SMTP_VERB_QUIT:
3260 SMTP_PASS_CMD(221);
3261 proxy_abort(wp, "SMTP session aborted with QUIT");
3262 job_exit(wp);
3263 case SMTP_VERB_RSET:
3264 SMTP_PASS_CMD(250_RSET);
3265 proxy_abort(wp, "SMTP transaction aborted with RSET");
3266 wp->msg_rd.out = wp->msg_rd.next_line;
3267 continue;
3268 case SMTP_VERB_HELO:
3269 proxy_abort(wp, "SMTP transaction aborted with HELO");
3270 wp->smtp_state = SMTP_ST_HELO;
3271 /* save helo value in case there is no XPOSTFIX */
3272 if (SMTP_PASS_CMD(250_HELO)
3273 && wp->cw.helo[0] == '\0'
3274 && !(wp->dfgs & DFG_XCLIENT_HELO))
3275 get_helo(wp, parmp, eparmp - parmp);
3276 wp->msg_rd.out = wp->msg_rd.next_line;
3277 continue;
3278 case SMTP_VERB_XCLIENT:
3279 case SMTP_VERB_XFORWARD:
3280 case SMTP_VERB_MAIL:
3281 case SMTP_VERB_RCPT:
3282 case SMTP_VERB_DATA:
3283 break;
3284 }
3285
3286 switch (wp->smtp_state) {
3287 case SMTP_ST_START: /* expecting HELO or perhaps MAIL */
3288 case SMTP_ST_HELO: /* seen HELO */
3289 if (verb == SMTP_VERB_XCLIENT
3290 || verb == SMTP_VERB_XFORWARD) {
3291 smtp_xpostfix(wp, verb, parmp);
3292 continue;
3293 }
3294 if (verb == SMTP_VERB_MAIL) {
3295 /* This might be a second message for the
3296 * connection.
3297 * Get sender, start logging, etc. at
3298 * the Mail_From command. */
3299 if (SMTP_PASS_CMD(250_MAIL)) {
3300 log_start(&wp->cw);
3301 i = eparmp - parmp;
3302 if (i > ISZ(wp->cw.env_from)-1)
3303 i = ISZ(wp->cw.env_from)-1;
3304 memcpy(wp->cw.env_from, parmp, i);
3305 wp->cw.env_from[i] = '\0';
3306 wp->smtp_state = SMTP_ST_TRANS;
3307 }
3308 wp->msg_rd.out = wp->msg_rd.next_line;
3309 get_sender(wp);
3310 continue;
3311 }
3312 break;
3313
3314 case SMTP_ST_TRANS: /* seen Mail_From */
3315 if (verb == SMTP_VERB_RCPT) {
3316 if (get_smtp_rcpt(wp, parmp, eparmp))
3317 wp->smtp_state = SMTP_ST_RCPT;
3318 continue;
3319 }
3320 break;
3321
3322 case SMTP_ST_RCPT: /* seen Rctp_To */
3323 if (verb == SMTP_VERB_RCPT) {
3324 get_smtp_rcpt(wp, parmp, eparmp);
3325 continue;
3326 }
3327 if (verb == SMTP_VERB_DATA) {
3328 smtp_data(wp);
3329 continue;
3330 }
3331 break;
3332
3333 case SMTP_ST_ERROR: /* no transaction until RSET or HELO */
3334 break;
3335 }
3336
3337 /* reject anything out of sequence */
3338 SMTP_ERROR(503);
3339 }
3340 }
3341
3342
3343
3344 /* start a new connection to an MTA */
3345 static void * NRATTRIB
3346 job_start(void *wp0)
3347 {
3348 WORK *wp = wp0;
3349
3350 /* working threads do not deal with signals */
3351 clnt_sigs_off(0);
3352
3353 if (proxy)
3354 proxy_job(wp);
3355 else
3356 ascii_job(wp);
3357 }