Mercurial > notdcc
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 } |