0
|
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 } |