1 | /* |
---|
2 | * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers. |
---|
3 | * All rights reserved. |
---|
4 | * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. |
---|
5 | * Copyright (c) 1988, 1993 |
---|
6 | * The Regents of the University of California. All rights reserved. |
---|
7 | * |
---|
8 | * By using this file, you agree to the terms and conditions set |
---|
9 | * forth in the LICENSE file which can be found at the top level of |
---|
10 | * the sendmail distribution. |
---|
11 | * |
---|
12 | */ |
---|
13 | |
---|
14 | #include <sendmail.h> |
---|
15 | |
---|
16 | SM_RCSID("@(#)$Id: usersmtp.c,v 1.3 2006-03-23 21:02:46 zacheiss Exp $") |
---|
17 | |
---|
18 | #include <sysexits.h> |
---|
19 | |
---|
20 | |
---|
21 | extern void markfailure __P((ENVELOPE *, ADDRESS *, MCI *, int, bool)); |
---|
22 | static void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *)); |
---|
23 | static void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *)); |
---|
24 | static int smtprcptstat __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *)); |
---|
25 | |
---|
26 | #if SASL |
---|
27 | extern void *sm_sasl_malloc __P((unsigned long)); |
---|
28 | extern void sm_sasl_free __P((void *)); |
---|
29 | #endif /* SASL */ |
---|
30 | |
---|
31 | extern int noauthentication; |
---|
32 | |
---|
33 | /* |
---|
34 | ** USERSMTP -- run SMTP protocol from the user end. |
---|
35 | ** |
---|
36 | ** This protocol is described in RFC821. |
---|
37 | */ |
---|
38 | |
---|
39 | #define REPLYTYPE(r) ((r) / 100) /* first digit of reply code */ |
---|
40 | #define REPLYCLASS(r) (((r) / 10) % 10) /* second digit of reply code */ |
---|
41 | #define SMTPCLOSING 421 /* "Service Shutting Down" */ |
---|
42 | |
---|
43 | #define ENHSCN(e, d) ((e) == NULL ? (d) : (e)) |
---|
44 | |
---|
45 | #define ENHSCN_RPOOL(e, d, rpool) \ |
---|
46 | ((e) == NULL ? (d) : sm_rpool_strdup_x(rpool, e)) |
---|
47 | |
---|
48 | static char SmtpMsgBuffer[MAXLINE]; /* buffer for commands */ |
---|
49 | static char SmtpReplyBuffer[MAXLINE]; /* buffer for replies */ |
---|
50 | static bool SmtpNeedIntro; /* need "while talking" in transcript */ |
---|
51 | /* |
---|
52 | ** SMTPINIT -- initialize SMTP. |
---|
53 | ** |
---|
54 | ** Opens the connection and sends the initial protocol. |
---|
55 | ** |
---|
56 | ** Parameters: |
---|
57 | ** m -- mailer to create connection to. |
---|
58 | ** mci -- the mailer connection info. |
---|
59 | ** e -- the envelope. |
---|
60 | ** onlyhelo -- send only helo command? |
---|
61 | ** |
---|
62 | ** Returns: |
---|
63 | ** none. |
---|
64 | ** |
---|
65 | ** Side Effects: |
---|
66 | ** creates connection and sends initial protocol. |
---|
67 | */ |
---|
68 | |
---|
69 | void |
---|
70 | smtpinit(m, mci, e, onlyhelo) |
---|
71 | MAILER *m; |
---|
72 | register MCI *mci; |
---|
73 | ENVELOPE *e; |
---|
74 | bool onlyhelo; |
---|
75 | { |
---|
76 | register int r; |
---|
77 | int state; |
---|
78 | register char *p; |
---|
79 | register char *hn; |
---|
80 | char *enhsc; |
---|
81 | |
---|
82 | enhsc = NULL; |
---|
83 | if (tTd(18, 1)) |
---|
84 | { |
---|
85 | sm_dprintf("smtpinit "); |
---|
86 | mci_dump(mci, false); |
---|
87 | } |
---|
88 | |
---|
89 | /* |
---|
90 | ** Open the connection to the mailer. |
---|
91 | */ |
---|
92 | |
---|
93 | SmtpError[0] = '\0'; |
---|
94 | CurHostName = mci->mci_host; /* XXX UGLY XXX */ |
---|
95 | if (CurHostName == NULL) |
---|
96 | CurHostName = MyHostName; |
---|
97 | SmtpNeedIntro = true; |
---|
98 | state = mci->mci_state; |
---|
99 | switch (state) |
---|
100 | { |
---|
101 | case MCIS_MAIL: |
---|
102 | case MCIS_RCPT: |
---|
103 | case MCIS_DATA: |
---|
104 | /* need to clear old information */ |
---|
105 | smtprset(m, mci, e); |
---|
106 | /* FALLTHROUGH */ |
---|
107 | |
---|
108 | case MCIS_OPEN: |
---|
109 | if (!onlyhelo) |
---|
110 | return; |
---|
111 | break; |
---|
112 | |
---|
113 | case MCIS_ERROR: |
---|
114 | case MCIS_QUITING: |
---|
115 | case MCIS_SSD: |
---|
116 | /* shouldn't happen */ |
---|
117 | smtpquit(m, mci, e); |
---|
118 | /* FALLTHROUGH */ |
---|
119 | |
---|
120 | case MCIS_CLOSED: |
---|
121 | syserr("451 4.4.0 smtpinit: state CLOSED (was %d)", state); |
---|
122 | return; |
---|
123 | |
---|
124 | case MCIS_OPENING: |
---|
125 | break; |
---|
126 | } |
---|
127 | if (onlyhelo) |
---|
128 | goto helo; |
---|
129 | |
---|
130 | mci->mci_state = MCIS_OPENING; |
---|
131 | clrsessenvelope(e); |
---|
132 | |
---|
133 | /* |
---|
134 | ** Get the greeting message. |
---|
135 | ** This should appear spontaneously. Give it five minutes to |
---|
136 | ** happen. |
---|
137 | */ |
---|
138 | |
---|
139 | SmtpPhase = mci->mci_phase = "client greeting"; |
---|
140 | sm_setproctitle(true, e, "%s %s: %s", |
---|
141 | qid_printname(e), CurHostName, mci->mci_phase); |
---|
142 | r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL); |
---|
143 | if (r < 0) |
---|
144 | goto tempfail1; |
---|
145 | if (REPLYTYPE(r) == 4) |
---|
146 | goto tempfail2; |
---|
147 | if (REPLYTYPE(r) != 2) |
---|
148 | goto unavailable; |
---|
149 | |
---|
150 | /* |
---|
151 | ** Send the HELO command. |
---|
152 | ** My mother taught me to always introduce myself. |
---|
153 | */ |
---|
154 | |
---|
155 | helo: |
---|
156 | if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags)) |
---|
157 | mci->mci_flags |= MCIF_ESMTP; |
---|
158 | hn = mci->mci_heloname ? mci->mci_heloname : MyHostName; |
---|
159 | |
---|
160 | tryhelo: |
---|
161 | #if _FFR_IGNORE_EXT_ON_HELO |
---|
162 | mci->mci_flags &= ~MCIF_HELO; |
---|
163 | #endif /* _FFR_IGNORE_EXT_ON_HELO */ |
---|
164 | if (bitnset(M_LMTP, m->m_flags)) |
---|
165 | { |
---|
166 | smtpmessage("LHLO %s", m, mci, hn); |
---|
167 | SmtpPhase = mci->mci_phase = "client LHLO"; |
---|
168 | } |
---|
169 | else if (bitset(MCIF_ESMTP, mci->mci_flags) && |
---|
170 | !bitnset(M_FSMTP, m->m_flags)) |
---|
171 | { |
---|
172 | smtpmessage("EHLO %s", m, mci, hn); |
---|
173 | SmtpPhase = mci->mci_phase = "client EHLO"; |
---|
174 | } |
---|
175 | else |
---|
176 | { |
---|
177 | smtpmessage("HELO %s", m, mci, hn); |
---|
178 | SmtpPhase = mci->mci_phase = "client HELO"; |
---|
179 | #if _FFR_IGNORE_EXT_ON_HELO |
---|
180 | mci->mci_flags |= MCIF_HELO; |
---|
181 | #endif /* _FFR_IGNORE_EXT_ON_HELO */ |
---|
182 | } |
---|
183 | sm_setproctitle(true, e, "%s %s: %s", qid_printname(e), |
---|
184 | CurHostName, mci->mci_phase); |
---|
185 | r = reply(m, mci, e, |
---|
186 | bitnset(M_LMTP, m->m_flags) ? TimeOuts.to_lhlo |
---|
187 | : TimeOuts.to_helo, |
---|
188 | helo_options, NULL); |
---|
189 | if (r < 0) |
---|
190 | goto tempfail1; |
---|
191 | else if (REPLYTYPE(r) == 5) |
---|
192 | { |
---|
193 | if (bitset(MCIF_ESMTP, mci->mci_flags) && |
---|
194 | !bitnset(M_LMTP, m->m_flags)) |
---|
195 | { |
---|
196 | /* try old SMTP instead */ |
---|
197 | mci->mci_flags &= ~MCIF_ESMTP; |
---|
198 | goto tryhelo; |
---|
199 | } |
---|
200 | goto unavailable; |
---|
201 | } |
---|
202 | else if (REPLYTYPE(r) != 2) |
---|
203 | goto tempfail2; |
---|
204 | |
---|
205 | /* |
---|
206 | ** Check to see if we actually ended up talking to ourself. |
---|
207 | ** This means we didn't know about an alias or MX, or we managed |
---|
208 | ** to connect to an echo server. |
---|
209 | */ |
---|
210 | |
---|
211 | p = strchr(&SmtpReplyBuffer[4], ' '); |
---|
212 | if (p != NULL) |
---|
213 | *p = '\0'; |
---|
214 | if (!bitnset(M_NOLOOPCHECK, m->m_flags) && |
---|
215 | !bitnset(M_LMTP, m->m_flags) && |
---|
216 | sm_strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0) |
---|
217 | { |
---|
218 | syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)", |
---|
219 | CurHostName); |
---|
220 | mci_setstat(mci, EX_CONFIG, "5.3.5", |
---|
221 | "553 5.3.5 system config error"); |
---|
222 | mci->mci_errno = 0; |
---|
223 | smtpquit(m, mci, e); |
---|
224 | return; |
---|
225 | } |
---|
226 | |
---|
227 | /* |
---|
228 | ** If this is expected to be another sendmail, send some internal |
---|
229 | ** commands. |
---|
230 | */ |
---|
231 | |
---|
232 | if (false |
---|
233 | # if !_FFR_DEPRECATE_MAILER_FLAG_I |
---|
234 | || bitnset(M_INTERNAL, m->m_flags) |
---|
235 | # endif /* !_FFR_DEPRECATE_MAILER_FLAG_I */ |
---|
236 | # if _FFR_MSP_VERBOSE |
---|
237 | /* If we're running as MSP, "propagate" -v flag if possible. */ |
---|
238 | || (UseMSP && Verbose && bitset(MCIF_VERB, mci->mci_flags)) |
---|
239 | # endif /* _FFR_MSP_VERBOSE */ |
---|
240 | ) |
---|
241 | { |
---|
242 | /* tell it to be verbose */ |
---|
243 | smtpmessage("VERB", m, mci); |
---|
244 | r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc); |
---|
245 | if (r < 0) |
---|
246 | goto tempfail1; |
---|
247 | } |
---|
248 | |
---|
249 | if (mci->mci_state != MCIS_CLOSED) |
---|
250 | { |
---|
251 | mci->mci_state = MCIS_OPEN; |
---|
252 | return; |
---|
253 | } |
---|
254 | |
---|
255 | /* got a 421 error code during startup */ |
---|
256 | |
---|
257 | tempfail1: |
---|
258 | mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL); |
---|
259 | if (mci->mci_state != MCIS_CLOSED) |
---|
260 | smtpquit(m, mci, e); |
---|
261 | return; |
---|
262 | |
---|
263 | tempfail2: |
---|
264 | /* XXX should use code from other end iff ENHANCEDSTATUSCODES */ |
---|
265 | mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"), |
---|
266 | SmtpReplyBuffer); |
---|
267 | if (mci->mci_state != MCIS_CLOSED) |
---|
268 | smtpquit(m, mci, e); |
---|
269 | return; |
---|
270 | |
---|
271 | unavailable: |
---|
272 | mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer); |
---|
273 | smtpquit(m, mci, e); |
---|
274 | return; |
---|
275 | } |
---|
276 | /* |
---|
277 | ** ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol |
---|
278 | ** |
---|
279 | ** Parameters: |
---|
280 | ** line -- the response line. |
---|
281 | ** firstline -- set if this is the first line of the reply. |
---|
282 | ** m -- the mailer. |
---|
283 | ** mci -- the mailer connection info. |
---|
284 | ** e -- the envelope. |
---|
285 | ** |
---|
286 | ** Returns: |
---|
287 | ** none. |
---|
288 | */ |
---|
289 | |
---|
290 | static void |
---|
291 | esmtp_check(line, firstline, m, mci, e) |
---|
292 | char *line; |
---|
293 | bool firstline; |
---|
294 | MAILER *m; |
---|
295 | register MCI *mci; |
---|
296 | ENVELOPE *e; |
---|
297 | { |
---|
298 | if (strstr(line, "ESMTP") != NULL) |
---|
299 | mci->mci_flags |= MCIF_ESMTP; |
---|
300 | |
---|
301 | /* |
---|
302 | ** Dirty hack below. Quoting the author: |
---|
303 | ** This was a response to people who wanted SMTP transmission to be |
---|
304 | ** just-send-8 by default. Essentially, you could put this tag into |
---|
305 | ** your greeting message to behave as though the F=8 flag was set on |
---|
306 | ** the mailer. |
---|
307 | */ |
---|
308 | |
---|
309 | if (strstr(line, "8BIT-OK") != NULL) |
---|
310 | mci->mci_flags |= MCIF_8BITOK; |
---|
311 | } |
---|
312 | |
---|
313 | #if SASL |
---|
314 | /* specify prototype so compiler can check calls */ |
---|
315 | static char *str_union __P((char *, char *, SM_RPOOL_T *)); |
---|
316 | |
---|
317 | /* |
---|
318 | ** STR_UNION -- create the union of two lists |
---|
319 | ** |
---|
320 | ** Parameters: |
---|
321 | ** s1, s2 -- lists of items (separated by single blanks). |
---|
322 | ** rpool -- resource pool from which result is allocated. |
---|
323 | ** |
---|
324 | ** Returns: |
---|
325 | ** the union of both lists. |
---|
326 | */ |
---|
327 | |
---|
328 | static char * |
---|
329 | str_union(s1, s2, rpool) |
---|
330 | char *s1, *s2; |
---|
331 | SM_RPOOL_T *rpool; |
---|
332 | { |
---|
333 | char *hr, *h1, *h, *res; |
---|
334 | int l1, l2, rl; |
---|
335 | |
---|
336 | if (s1 == NULL || *s1 == '\0') |
---|
337 | return s2; |
---|
338 | if (s2 == NULL || *s2 == '\0') |
---|
339 | return s1; |
---|
340 | l1 = strlen(s1); |
---|
341 | l2 = strlen(s2); |
---|
342 | rl = l1 + l2; |
---|
343 | res = (char *) sm_rpool_malloc(rpool, rl + 2); |
---|
344 | if (res == NULL) |
---|
345 | { |
---|
346 | if (l1 > l2) |
---|
347 | return s1; |
---|
348 | return s2; |
---|
349 | } |
---|
350 | (void) sm_strlcpy(res, s1, rl); |
---|
351 | hr = res + l1; |
---|
352 | h1 = s2; |
---|
353 | h = s2; |
---|
354 | |
---|
355 | /* walk through s2 */ |
---|
356 | while (h != NULL && *h1 != '\0') |
---|
357 | { |
---|
358 | /* is there something after the current word? */ |
---|
359 | if ((h = strchr(h1, ' ')) != NULL) |
---|
360 | *h = '\0'; |
---|
361 | l1 = strlen(h1); |
---|
362 | |
---|
363 | /* does the current word appear in s1 ? */ |
---|
364 | if (iteminlist(h1, s1, " ") == NULL) |
---|
365 | { |
---|
366 | /* add space as delimiter */ |
---|
367 | *hr++ = ' '; |
---|
368 | |
---|
369 | /* copy the item */ |
---|
370 | memcpy(hr, h1, l1); |
---|
371 | |
---|
372 | /* advance pointer in result list */ |
---|
373 | hr += l1; |
---|
374 | *hr = '\0'; |
---|
375 | } |
---|
376 | if (h != NULL) |
---|
377 | { |
---|
378 | /* there are more items */ |
---|
379 | *h = ' '; |
---|
380 | h1 = h + 1; |
---|
381 | } |
---|
382 | } |
---|
383 | return res; |
---|
384 | } |
---|
385 | #endif /* SASL */ |
---|
386 | |
---|
387 | /* |
---|
388 | ** HELO_OPTIONS -- process the options on a HELO line. |
---|
389 | ** |
---|
390 | ** Parameters: |
---|
391 | ** line -- the response line. |
---|
392 | ** firstline -- set if this is the first line of the reply. |
---|
393 | ** m -- the mailer. |
---|
394 | ** mci -- the mailer connection info. |
---|
395 | ** e -- the envelope (unused). |
---|
396 | ** |
---|
397 | ** Returns: |
---|
398 | ** none. |
---|
399 | */ |
---|
400 | |
---|
401 | static void |
---|
402 | helo_options(line, firstline, m, mci, e) |
---|
403 | char *line; |
---|
404 | bool firstline; |
---|
405 | MAILER *m; |
---|
406 | register MCI *mci; |
---|
407 | ENVELOPE *e; |
---|
408 | { |
---|
409 | register char *p; |
---|
410 | #if _FFR_IGNORE_EXT_ON_HELO |
---|
411 | static bool logged = false; |
---|
412 | #endif /* _FFR_IGNORE_EXT_ON_HELO */ |
---|
413 | |
---|
414 | if (firstline) |
---|
415 | { |
---|
416 | #if SASL |
---|
417 | mci->mci_saslcap = NULL; |
---|
418 | #endif /* SASL */ |
---|
419 | #if _FFR_IGNORE_EXT_ON_HELO |
---|
420 | logged = false; |
---|
421 | #endif /* _FFR_IGNORE_EXT_ON_HELO */ |
---|
422 | return; |
---|
423 | } |
---|
424 | #if _FFR_IGNORE_EXT_ON_HELO |
---|
425 | else if (bitset(MCIF_HELO, mci->mci_flags)) |
---|
426 | { |
---|
427 | if (LogLevel > 8 && !logged) |
---|
428 | { |
---|
429 | sm_syslog(LOG_WARNING, NOQID, |
---|
430 | "server=%s [%s] returned extensions despite HELO command", |
---|
431 | macvalue(macid("{server_name}"), e), |
---|
432 | macvalue(macid("{server_addr}"), e)); |
---|
433 | logged = true; |
---|
434 | } |
---|
435 | return; |
---|
436 | } |
---|
437 | #endif /* _FFR_IGNORE_EXT_ON_HELO */ |
---|
438 | |
---|
439 | if (strlen(line) < 5) |
---|
440 | return; |
---|
441 | line += 4; |
---|
442 | p = strpbrk(line, " ="); |
---|
443 | if (p != NULL) |
---|
444 | *p++ = '\0'; |
---|
445 | if (sm_strcasecmp(line, "size") == 0) |
---|
446 | { |
---|
447 | mci->mci_flags |= MCIF_SIZE; |
---|
448 | if (p != NULL) |
---|
449 | mci->mci_maxsize = atol(p); |
---|
450 | } |
---|
451 | else if (sm_strcasecmp(line, "8bitmime") == 0) |
---|
452 | { |
---|
453 | mci->mci_flags |= MCIF_8BITMIME; |
---|
454 | mci->mci_flags &= ~MCIF_7BIT; |
---|
455 | } |
---|
456 | else if (sm_strcasecmp(line, "expn") == 0) |
---|
457 | mci->mci_flags |= MCIF_EXPN; |
---|
458 | else if (sm_strcasecmp(line, "dsn") == 0) |
---|
459 | mci->mci_flags |= MCIF_DSN; |
---|
460 | else if (sm_strcasecmp(line, "enhancedstatuscodes") == 0) |
---|
461 | mci->mci_flags |= MCIF_ENHSTAT; |
---|
462 | else if (sm_strcasecmp(line, "pipelining") == 0) |
---|
463 | mci->mci_flags |= MCIF_PIPELINED; |
---|
464 | else if (sm_strcasecmp(line, "verb") == 0) |
---|
465 | mci->mci_flags |= MCIF_VERB; |
---|
466 | #if STARTTLS |
---|
467 | else if (sm_strcasecmp(line, "starttls") == 0) |
---|
468 | mci->mci_flags |= MCIF_TLS; |
---|
469 | #endif /* STARTTLS */ |
---|
470 | else if (sm_strcasecmp(line, "deliverby") == 0) |
---|
471 | { |
---|
472 | mci->mci_flags |= MCIF_DLVR_BY; |
---|
473 | if (p != NULL) |
---|
474 | mci->mci_min_by = atol(p); |
---|
475 | } |
---|
476 | #if SASL |
---|
477 | else if (sm_strcasecmp(line, "auth") == 0) |
---|
478 | { |
---|
479 | if (p != NULL && *p != '\0') |
---|
480 | { |
---|
481 | if (mci->mci_saslcap != NULL) |
---|
482 | { |
---|
483 | /* |
---|
484 | ** Create the union with previous auth |
---|
485 | ** offerings because we recognize "auth " |
---|
486 | ** and "auth=" (old format). |
---|
487 | */ |
---|
488 | |
---|
489 | mci->mci_saslcap = str_union(mci->mci_saslcap, |
---|
490 | p, mci->mci_rpool); |
---|
491 | mci->mci_flags |= MCIF_AUTH; |
---|
492 | } |
---|
493 | else |
---|
494 | { |
---|
495 | int l; |
---|
496 | |
---|
497 | l = strlen(p) + 1; |
---|
498 | mci->mci_saslcap = (char *) |
---|
499 | sm_rpool_malloc(mci->mci_rpool, l); |
---|
500 | if (mci->mci_saslcap != NULL) |
---|
501 | { |
---|
502 | (void) sm_strlcpy(mci->mci_saslcap, p, |
---|
503 | l); |
---|
504 | mci->mci_flags |= MCIF_AUTH; |
---|
505 | } |
---|
506 | } |
---|
507 | } |
---|
508 | } |
---|
509 | #endif /* SASL */ |
---|
510 | } |
---|
511 | #if SASL |
---|
512 | |
---|
513 | static int getsimple __P((void *, int, const char **, unsigned *)); |
---|
514 | static int getsecret __P((sasl_conn_t *, void *, int, sasl_secret_t **)); |
---|
515 | static int saslgetrealm __P((void *, int, const char **, const char **)); |
---|
516 | static int readauth __P((char *, bool, SASL_AI_T *m, SM_RPOOL_T *)); |
---|
517 | static int getauth __P((MCI *, ENVELOPE *, SASL_AI_T *)); |
---|
518 | static char *removemech __P((char *, char *, SM_RPOOL_T *)); |
---|
519 | static int attemptauth __P((MAILER *, MCI *, ENVELOPE *, SASL_AI_T *)); |
---|
520 | |
---|
521 | static sasl_callback_t callbacks[] = |
---|
522 | { |
---|
523 | { SASL_CB_GETREALM, &saslgetrealm, NULL }, |
---|
524 | #define CB_GETREALM_IDX 0 |
---|
525 | { SASL_CB_PASS, &getsecret, NULL }, |
---|
526 | #define CB_PASS_IDX 1 |
---|
527 | { SASL_CB_USER, &getsimple, NULL }, |
---|
528 | #define CB_USER_IDX 2 |
---|
529 | { SASL_CB_AUTHNAME, &getsimple, NULL }, |
---|
530 | #define CB_AUTHNAME_IDX 3 |
---|
531 | { SASL_CB_VERIFYFILE, &safesaslfile, NULL }, |
---|
532 | #define CB_SAFESASL_IDX 4 |
---|
533 | { SASL_CB_LIST_END, NULL, NULL } |
---|
534 | }; |
---|
535 | |
---|
536 | /* |
---|
537 | ** INIT_SASL_CLIENT -- initialize client side of Cyrus-SASL |
---|
538 | ** |
---|
539 | ** Parameters: |
---|
540 | ** none. |
---|
541 | ** |
---|
542 | ** Returns: |
---|
543 | ** SASL_OK -- if successful. |
---|
544 | ** SASL error code -- otherwise. |
---|
545 | ** |
---|
546 | ** Side Effects: |
---|
547 | ** checks/sets sasl_clt_init. |
---|
548 | */ |
---|
549 | |
---|
550 | static bool sasl_clt_init = false; |
---|
551 | |
---|
552 | static int |
---|
553 | init_sasl_client() |
---|
554 | { |
---|
555 | int result; |
---|
556 | |
---|
557 | if (sasl_clt_init) |
---|
558 | return SASL_OK; |
---|
559 | result = sasl_client_init(callbacks); |
---|
560 | |
---|
561 | /* should we retry later again or just remember that it failed? */ |
---|
562 | if (result == SASL_OK) |
---|
563 | sasl_clt_init = true; |
---|
564 | return result; |
---|
565 | } |
---|
566 | /* |
---|
567 | ** STOP_SASL_CLIENT -- shutdown client side of Cyrus-SASL |
---|
568 | ** |
---|
569 | ** Parameters: |
---|
570 | ** none. |
---|
571 | ** |
---|
572 | ** Returns: |
---|
573 | ** none. |
---|
574 | ** |
---|
575 | ** Side Effects: |
---|
576 | ** checks/sets sasl_clt_init. |
---|
577 | */ |
---|
578 | |
---|
579 | void |
---|
580 | stop_sasl_client() |
---|
581 | { |
---|
582 | if (!sasl_clt_init) |
---|
583 | return; |
---|
584 | sasl_clt_init = false; |
---|
585 | sasl_done(); |
---|
586 | } |
---|
587 | /* |
---|
588 | ** GETSASLDATA -- process the challenges from the SASL protocol |
---|
589 | ** |
---|
590 | ** This gets the relevant sasl response data out of the reply |
---|
591 | ** from the server. |
---|
592 | ** |
---|
593 | ** Parameters: |
---|
594 | ** line -- the response line. |
---|
595 | ** firstline -- set if this is the first line of the reply. |
---|
596 | ** m -- the mailer. |
---|
597 | ** mci -- the mailer connection info. |
---|
598 | ** e -- the envelope (unused). |
---|
599 | ** |
---|
600 | ** Returns: |
---|
601 | ** none. |
---|
602 | */ |
---|
603 | |
---|
604 | static void getsasldata __P((char *, bool, MAILER *, MCI *, ENVELOPE *)); |
---|
605 | |
---|
606 | static void |
---|
607 | getsasldata(line, firstline, m, mci, e) |
---|
608 | char *line; |
---|
609 | bool firstline; |
---|
610 | MAILER *m; |
---|
611 | register MCI *mci; |
---|
612 | ENVELOPE *e; |
---|
613 | { |
---|
614 | int len; |
---|
615 | int result; |
---|
616 | # if SASL < 20000 |
---|
617 | char *out; |
---|
618 | # endif /* SASL < 20000 */ |
---|
619 | |
---|
620 | /* if not a continue we don't care about it */ |
---|
621 | len = strlen(line); |
---|
622 | if ((len <= 4) || |
---|
623 | (line[0] != '3') || |
---|
624 | !isascii(line[1]) || !isdigit(line[1]) || |
---|
625 | !isascii(line[2]) || !isdigit(line[2])) |
---|
626 | { |
---|
627 | SM_FREE_CLR(mci->mci_sasl_string); |
---|
628 | return; |
---|
629 | } |
---|
630 | |
---|
631 | /* forget about "334 " */ |
---|
632 | line += 4; |
---|
633 | len -= 4; |
---|
634 | # if SASL >= 20000 |
---|
635 | /* XXX put this into a macro/function? It's duplicated below */ |
---|
636 | if (mci->mci_sasl_string != NULL) |
---|
637 | { |
---|
638 | if (mci->mci_sasl_string_len <= len) |
---|
639 | { |
---|
640 | sm_free(mci->mci_sasl_string); /* XXX */ |
---|
641 | mci->mci_sasl_string = xalloc(len + 1); |
---|
642 | } |
---|
643 | } |
---|
644 | else |
---|
645 | mci->mci_sasl_string = xalloc(len + 1); |
---|
646 | |
---|
647 | result = sasl_decode64(line, len, mci->mci_sasl_string, len + 1, |
---|
648 | (unsigned int *) &mci->mci_sasl_string_len); |
---|
649 | if (result != SASL_OK) |
---|
650 | { |
---|
651 | mci->mci_sasl_string_len = 0; |
---|
652 | *mci->mci_sasl_string = '\0'; |
---|
653 | } |
---|
654 | # else /* SASL >= 20000 */ |
---|
655 | out = (char *) sm_rpool_malloc_x(mci->mci_rpool, len + 1); |
---|
656 | result = sasl_decode64(line, len, out, (unsigned int *) &len); |
---|
657 | if (result != SASL_OK) |
---|
658 | { |
---|
659 | len = 0; |
---|
660 | *out = '\0'; |
---|
661 | } |
---|
662 | |
---|
663 | /* |
---|
664 | ** mci_sasl_string is "shared" with Cyrus-SASL library; hence |
---|
665 | ** it can't be in an rpool unless we use the same memory |
---|
666 | ** management mechanism (with same rpool!) for Cyrus SASL. |
---|
667 | */ |
---|
668 | |
---|
669 | if (mci->mci_sasl_string != NULL) |
---|
670 | { |
---|
671 | if (mci->mci_sasl_string_len <= len) |
---|
672 | { |
---|
673 | sm_free(mci->mci_sasl_string); /* XXX */ |
---|
674 | mci->mci_sasl_string = xalloc(len + 1); |
---|
675 | } |
---|
676 | } |
---|
677 | else |
---|
678 | mci->mci_sasl_string = xalloc(len + 1); |
---|
679 | |
---|
680 | memcpy(mci->mci_sasl_string, out, len); |
---|
681 | mci->mci_sasl_string[len] = '\0'; |
---|
682 | mci->mci_sasl_string_len = len; |
---|
683 | # endif /* SASL >= 20000 */ |
---|
684 | return; |
---|
685 | } |
---|
686 | /* |
---|
687 | ** READAUTH -- read auth values from a file |
---|
688 | ** |
---|
689 | ** Parameters: |
---|
690 | ** filename -- name of file to read. |
---|
691 | ** safe -- if set, this is a safe read. |
---|
692 | ** sai -- where to store auth_info. |
---|
693 | ** rpool -- resource pool for sai. |
---|
694 | ** |
---|
695 | ** Returns: |
---|
696 | ** EX_OK -- data succesfully read. |
---|
697 | ** EX_UNAVAILABLE -- no valid filename. |
---|
698 | ** EX_TEMPFAIL -- temporary failure. |
---|
699 | */ |
---|
700 | |
---|
701 | static char *sasl_info_name[] = |
---|
702 | { |
---|
703 | "user id", |
---|
704 | "authentication id", |
---|
705 | "password", |
---|
706 | "realm", |
---|
707 | "mechlist" |
---|
708 | }; |
---|
709 | static int |
---|
710 | readauth(filename, safe, sai, rpool) |
---|
711 | char *filename; |
---|
712 | bool safe; |
---|
713 | SASL_AI_T *sai; |
---|
714 | SM_RPOOL_T *rpool; |
---|
715 | { |
---|
716 | SM_FILE_T *f; |
---|
717 | long sff; |
---|
718 | pid_t pid; |
---|
719 | int lc; |
---|
720 | char *s; |
---|
721 | char buf[MAXLINE]; |
---|
722 | |
---|
723 | if (filename == NULL || filename[0] == '\0') |
---|
724 | return EX_UNAVAILABLE; |
---|
725 | |
---|
726 | #if !_FFR_ALLOW_SASLINFO |
---|
727 | /* |
---|
728 | ** make sure we don't use a program that is not |
---|
729 | ** accesible to the user who specified a different authinfo file. |
---|
730 | ** However, currently we don't pass this info (authinfo file |
---|
731 | ** specified by user) around, so we just turn off program access. |
---|
732 | */ |
---|
733 | |
---|
734 | if (filename[0] == '|') |
---|
735 | { |
---|
736 | auto int fd; |
---|
737 | int i; |
---|
738 | char *p; |
---|
739 | char *argv[MAXPV + 1]; |
---|
740 | |
---|
741 | i = 0; |
---|
742 | for (p = strtok(&filename[1], " \t"); p != NULL; |
---|
743 | p = strtok(NULL, " \t")) |
---|
744 | { |
---|
745 | if (i >= MAXPV) |
---|
746 | break; |
---|
747 | argv[i++] = p; |
---|
748 | } |
---|
749 | argv[i] = NULL; |
---|
750 | pid = prog_open(argv, &fd, CurEnv); |
---|
751 | if (pid < 0) |
---|
752 | f = NULL; |
---|
753 | else |
---|
754 | f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, |
---|
755 | (void *) &fd, SM_IO_RDONLY, NULL); |
---|
756 | } |
---|
757 | else |
---|
758 | #endif /* !_FFR_ALLOW_SASLINFO */ |
---|
759 | { |
---|
760 | pid = -1; |
---|
761 | sff = SFF_REGONLY|SFF_SAFEDIRPATH|SFF_NOWLINK |
---|
762 | |SFF_NOGWFILES|SFF_NOWWFILES|SFF_NOWRFILES; |
---|
763 | # if _FFR_GROUPREADABLEAUTHINFOFILE |
---|
764 | if (!bitnset(DBS_GROUPREADABLEAUTHINFOFILE, DontBlameSendmail)) |
---|
765 | # endif /* _FFR_GROUPREADABLEAUTHINFOFILE */ |
---|
766 | sff |= SFF_NOGRFILES; |
---|
767 | if (DontLockReadFiles) |
---|
768 | sff |= SFF_NOLOCK; |
---|
769 | |
---|
770 | #if _FFR_ALLOW_SASLINFO |
---|
771 | /* |
---|
772 | ** XXX: make sure we don't read or open files that are not |
---|
773 | ** accesible to the user who specified a different authinfo |
---|
774 | ** file. |
---|
775 | */ |
---|
776 | |
---|
777 | sff |= SFF_MUSTOWN; |
---|
778 | #else /* _FFR_ALLOW_SASLINFO */ |
---|
779 | if (safe) |
---|
780 | sff |= SFF_OPENASROOT; |
---|
781 | #endif /* _FFR_ALLOW_SASLINFO */ |
---|
782 | |
---|
783 | f = safefopen(filename, O_RDONLY, 0, sff); |
---|
784 | } |
---|
785 | if (f == NULL) |
---|
786 | { |
---|
787 | if (LogLevel > 5) |
---|
788 | sm_syslog(LOG_ERR, NOQID, |
---|
789 | "AUTH=client, error: can't open %s: %s", |
---|
790 | filename, sm_errstring(errno)); |
---|
791 | return EX_TEMPFAIL; |
---|
792 | } |
---|
793 | |
---|
794 | lc = 0; |
---|
795 | while (lc <= SASL_MECHLIST && |
---|
796 | sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL) |
---|
797 | { |
---|
798 | if (buf[0] != '#') |
---|
799 | { |
---|
800 | (*sai)[lc] = sm_rpool_strdup_x(rpool, buf); |
---|
801 | if ((s = strchr((*sai)[lc], '\n')) != NULL) |
---|
802 | *s = '\0'; |
---|
803 | lc++; |
---|
804 | } |
---|
805 | } |
---|
806 | |
---|
807 | (void) sm_io_close(f, SM_TIME_DEFAULT); |
---|
808 | if (pid > 0) |
---|
809 | (void) waitfor(pid); |
---|
810 | if (lc < SASL_PASSWORD) |
---|
811 | { |
---|
812 | if (LogLevel > 8) |
---|
813 | sm_syslog(LOG_ERR, NOQID, |
---|
814 | "AUTH=client, error: can't read %s from %s", |
---|
815 | sasl_info_name[lc + 1], filename); |
---|
816 | return EX_TEMPFAIL; |
---|
817 | } |
---|
818 | return EX_OK; |
---|
819 | } |
---|
820 | |
---|
821 | /* |
---|
822 | ** GETAUTH -- get authinfo from ruleset call |
---|
823 | ** |
---|
824 | ** {server_name}, {server_addr} must be set |
---|
825 | ** |
---|
826 | ** Parameters: |
---|
827 | ** mci -- the mailer connection structure. |
---|
828 | ** e -- the envelope (including the sender to specify). |
---|
829 | ** sai -- pointer to authinfo (result). |
---|
830 | ** |
---|
831 | ** Returns: |
---|
832 | ** EX_OK -- ruleset was succesfully called, data may not |
---|
833 | ** be available, sai must be checked. |
---|
834 | ** EX_UNAVAILABLE -- ruleset unavailable (or failed). |
---|
835 | ** EX_TEMPFAIL -- temporary failure (from ruleset). |
---|
836 | ** |
---|
837 | ** Side Effects: |
---|
838 | ** Fills in sai if successful. |
---|
839 | */ |
---|
840 | |
---|
841 | static int |
---|
842 | getauth(mci, e, sai) |
---|
843 | MCI *mci; |
---|
844 | ENVELOPE *e; |
---|
845 | SASL_AI_T *sai; |
---|
846 | { |
---|
847 | int i, r, l, got, ret; |
---|
848 | char **pvp; |
---|
849 | char pvpbuf[PSBUFSIZE]; |
---|
850 | |
---|
851 | r = rscap("authinfo", macvalue(macid("{server_name}"), e), |
---|
852 | macvalue(macid("{server_addr}"), e), e, |
---|
853 | &pvp, pvpbuf, sizeof(pvpbuf)); |
---|
854 | |
---|
855 | if (r != EX_OK) |
---|
856 | return EX_UNAVAILABLE; |
---|
857 | |
---|
858 | /* other than expected return value: ok (i.e., no auth) */ |
---|
859 | if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET) |
---|
860 | return EX_OK; |
---|
861 | if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0) |
---|
862 | return EX_TEMPFAIL; |
---|
863 | |
---|
864 | /* |
---|
865 | ** parse the data, put it into sai |
---|
866 | ** format: "TDstring" (including the '"' !) |
---|
867 | ** where T is a tag: 'U', ... |
---|
868 | ** D is a delimiter: ':' or '=' |
---|
869 | */ |
---|
870 | |
---|
871 | ret = EX_OK; /* default return value */ |
---|
872 | i = 0; |
---|
873 | got = 0; |
---|
874 | while (i < SASL_ENTRIES) |
---|
875 | { |
---|
876 | if (pvp[i + 1] == NULL) |
---|
877 | break; |
---|
878 | if (pvp[i + 1][0] != '"') |
---|
879 | break; |
---|
880 | switch (pvp[i + 1][1]) |
---|
881 | { |
---|
882 | case 'U': |
---|
883 | case 'u': |
---|
884 | r = SASL_USER; |
---|
885 | break; |
---|
886 | case 'I': |
---|
887 | case 'i': |
---|
888 | r = SASL_AUTHID; |
---|
889 | break; |
---|
890 | case 'P': |
---|
891 | case 'p': |
---|
892 | r = SASL_PASSWORD; |
---|
893 | break; |
---|
894 | case 'R': |
---|
895 | case 'r': |
---|
896 | r = SASL_DEFREALM; |
---|
897 | break; |
---|
898 | case 'M': |
---|
899 | case 'm': |
---|
900 | r = SASL_MECHLIST; |
---|
901 | break; |
---|
902 | default: |
---|
903 | goto fail; |
---|
904 | } |
---|
905 | l = strlen(pvp[i + 1]); |
---|
906 | |
---|
907 | /* check syntax */ |
---|
908 | if (l <= 3 || pvp[i + 1][l - 1] != '"') |
---|
909 | goto fail; |
---|
910 | |
---|
911 | /* remove closing quote */ |
---|
912 | pvp[i + 1][l - 1] = '\0'; |
---|
913 | |
---|
914 | /* remove "TD and " */ |
---|
915 | l -= 4; |
---|
916 | (*sai)[r] = (char *) sm_rpool_malloc(mci->mci_rpool, l + 1); |
---|
917 | if ((*sai)[r] == NULL) |
---|
918 | goto tempfail; |
---|
919 | if (pvp[i + 1][2] == ':') |
---|
920 | { |
---|
921 | /* ':text' (just copy) */ |
---|
922 | (void) sm_strlcpy((*sai)[r], pvp[i + 1] + 3, l + 1); |
---|
923 | got |= 1 << r; |
---|
924 | } |
---|
925 | else if (pvp[i + 1][2] == '=') |
---|
926 | { |
---|
927 | unsigned int len; |
---|
928 | |
---|
929 | /* '=base64' (decode) */ |
---|
930 | # if SASL >= 20000 |
---|
931 | ret = sasl_decode64(pvp[i + 1] + 3, |
---|
932 | (unsigned int) l, (*sai)[r], |
---|
933 | (unsigned int) l + 1, &len); |
---|
934 | # else /* SASL >= 20000 */ |
---|
935 | ret = sasl_decode64(pvp[i + 1] + 3, |
---|
936 | (unsigned int) l, (*sai)[r], &len); |
---|
937 | # endif /* SASL >= 20000 */ |
---|
938 | if (ret != SASL_OK) |
---|
939 | goto fail; |
---|
940 | got |= 1 << r; |
---|
941 | } |
---|
942 | else |
---|
943 | goto fail; |
---|
944 | if (tTd(95, 5)) |
---|
945 | sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s", |
---|
946 | sasl_info_name[r], (*sai)[r]); |
---|
947 | ++i; |
---|
948 | } |
---|
949 | |
---|
950 | /* did we get the expected data? */ |
---|
951 | /* XXX: EXTERNAL mechanism only requires (and only uses) SASL_USER */ |
---|
952 | if (!(bitset(SASL_USER_BIT|SASL_AUTHID_BIT, got) && |
---|
953 | bitset(SASL_PASSWORD_BIT, got))) |
---|
954 | goto fail; |
---|
955 | |
---|
956 | /* no authid? copy uid */ |
---|
957 | if (!bitset(SASL_AUTHID_BIT, got)) |
---|
958 | { |
---|
959 | l = strlen((*sai)[SASL_USER]) + 1; |
---|
960 | (*sai)[SASL_AUTHID] = (char *) sm_rpool_malloc(mci->mci_rpool, |
---|
961 | l + 1); |
---|
962 | if ((*sai)[SASL_AUTHID] == NULL) |
---|
963 | goto tempfail; |
---|
964 | (void) sm_strlcpy((*sai)[SASL_AUTHID], (*sai)[SASL_USER], l); |
---|
965 | } |
---|
966 | |
---|
967 | /* no uid? copy authid */ |
---|
968 | if (!bitset(SASL_USER_BIT, got)) |
---|
969 | { |
---|
970 | l = strlen((*sai)[SASL_AUTHID]) + 1; |
---|
971 | (*sai)[SASL_USER] = (char *) sm_rpool_malloc(mci->mci_rpool, |
---|
972 | l + 1); |
---|
973 | if ((*sai)[SASL_USER] == NULL) |
---|
974 | goto tempfail; |
---|
975 | (void) sm_strlcpy((*sai)[SASL_USER], (*sai)[SASL_AUTHID], l); |
---|
976 | } |
---|
977 | return EX_OK; |
---|
978 | |
---|
979 | tempfail: |
---|
980 | ret = EX_TEMPFAIL; |
---|
981 | fail: |
---|
982 | if (LogLevel > 8) |
---|
983 | sm_syslog(LOG_WARNING, NOQID, |
---|
984 | "AUTH=client, relay=%.64s [%.16s], authinfo %sfailed", |
---|
985 | macvalue(macid("{server_name}"), e), |
---|
986 | macvalue(macid("{server_addr}"), e), |
---|
987 | ret == EX_TEMPFAIL ? "temp" : ""); |
---|
988 | for (i = 0; i <= SASL_MECHLIST; i++) |
---|
989 | (*sai)[i] = NULL; /* just clear; rpool */ |
---|
990 | return ret; |
---|
991 | } |
---|
992 | |
---|
993 | # if SASL >= 20000 |
---|
994 | /* |
---|
995 | ** GETSIMPLE -- callback to get userid or authid |
---|
996 | ** |
---|
997 | ** Parameters: |
---|
998 | ** context -- sai |
---|
999 | ** id -- what to do |
---|
1000 | ** result -- (pointer to) result |
---|
1001 | ** len -- (pointer to) length of result |
---|
1002 | ** |
---|
1003 | ** Returns: |
---|
1004 | ** OK/failure values |
---|
1005 | */ |
---|
1006 | |
---|
1007 | static int |
---|
1008 | getsimple(context, id, result, len) |
---|
1009 | void *context; |
---|
1010 | int id; |
---|
1011 | const char **result; |
---|
1012 | unsigned *len; |
---|
1013 | { |
---|
1014 | SASL_AI_T *sai; |
---|
1015 | |
---|
1016 | if (result == NULL || context == NULL) |
---|
1017 | return SASL_BADPARAM; |
---|
1018 | sai = (SASL_AI_T *) context; |
---|
1019 | |
---|
1020 | switch (id) |
---|
1021 | { |
---|
1022 | case SASL_CB_USER: |
---|
1023 | *result = (*sai)[SASL_USER]; |
---|
1024 | if (tTd(95, 5)) |
---|
1025 | sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'", |
---|
1026 | *result); |
---|
1027 | if (len != NULL) |
---|
1028 | *len = *result != NULL ? strlen(*result) : 0; |
---|
1029 | break; |
---|
1030 | |
---|
1031 | case SASL_CB_AUTHNAME: |
---|
1032 | *result = (*sai)[SASL_AUTHID]; |
---|
1033 | if (tTd(95, 5)) |
---|
1034 | sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'", |
---|
1035 | *result); |
---|
1036 | if (len != NULL) |
---|
1037 | *len = *result != NULL ? strlen(*result) : 0; |
---|
1038 | break; |
---|
1039 | |
---|
1040 | case SASL_CB_LANGUAGE: |
---|
1041 | *result = NULL; |
---|
1042 | if (len != NULL) |
---|
1043 | *len = 0; |
---|
1044 | break; |
---|
1045 | |
---|
1046 | default: |
---|
1047 | return SASL_BADPARAM; |
---|
1048 | } |
---|
1049 | return SASL_OK; |
---|
1050 | } |
---|
1051 | /* |
---|
1052 | ** GETSECRET -- callback to get password |
---|
1053 | ** |
---|
1054 | ** Parameters: |
---|
1055 | ** conn -- connection information |
---|
1056 | ** context -- sai |
---|
1057 | ** id -- what to do |
---|
1058 | ** psecret -- (pointer to) result |
---|
1059 | ** |
---|
1060 | ** Returns: |
---|
1061 | ** OK/failure values |
---|
1062 | */ |
---|
1063 | |
---|
1064 | static int |
---|
1065 | getsecret(conn, context, id, psecret) |
---|
1066 | sasl_conn_t *conn; |
---|
1067 | SM_UNUSED(void *context); |
---|
1068 | int id; |
---|
1069 | sasl_secret_t **psecret; |
---|
1070 | { |
---|
1071 | int len; |
---|
1072 | char *authpass; |
---|
1073 | MCI *mci; |
---|
1074 | |
---|
1075 | if (conn == NULL || psecret == NULL || id != SASL_CB_PASS) |
---|
1076 | return SASL_BADPARAM; |
---|
1077 | |
---|
1078 | mci = (MCI *) context; |
---|
1079 | authpass = mci->mci_sai[SASL_PASSWORD]; |
---|
1080 | len = strlen(authpass); |
---|
1081 | |
---|
1082 | /* |
---|
1083 | ** use an rpool because we are responsible for free()ing the secret, |
---|
1084 | ** but we can't free() it until after the auth completes |
---|
1085 | */ |
---|
1086 | |
---|
1087 | *psecret = (sasl_secret_t *) sm_rpool_malloc(mci->mci_rpool, |
---|
1088 | sizeof(sasl_secret_t) + |
---|
1089 | len + 1); |
---|
1090 | if (*psecret == NULL) |
---|
1091 | return SASL_FAIL; |
---|
1092 | (void) sm_strlcpy((*psecret)->data, authpass, len + 1); |
---|
1093 | (*psecret)->len = (unsigned long) len; |
---|
1094 | return SASL_OK; |
---|
1095 | } |
---|
1096 | # else /* SASL >= 20000 */ |
---|
1097 | /* |
---|
1098 | ** GETSIMPLE -- callback to get userid or authid |
---|
1099 | ** |
---|
1100 | ** Parameters: |
---|
1101 | ** context -- sai |
---|
1102 | ** id -- what to do |
---|
1103 | ** result -- (pointer to) result |
---|
1104 | ** len -- (pointer to) length of result |
---|
1105 | ** |
---|
1106 | ** Returns: |
---|
1107 | ** OK/failure values |
---|
1108 | */ |
---|
1109 | |
---|
1110 | static int |
---|
1111 | getsimple(context, id, result, len) |
---|
1112 | void *context; |
---|
1113 | int id; |
---|
1114 | const char **result; |
---|
1115 | unsigned *len; |
---|
1116 | { |
---|
1117 | char *h, *s; |
---|
1118 | # if SASL > 10509 |
---|
1119 | bool addrealm; |
---|
1120 | # endif /* SASL > 10509 */ |
---|
1121 | size_t l; |
---|
1122 | SASL_AI_T *sai; |
---|
1123 | char *authid = NULL; |
---|
1124 | |
---|
1125 | if (result == NULL || context == NULL) |
---|
1126 | return SASL_BADPARAM; |
---|
1127 | sai = (SASL_AI_T *) context; |
---|
1128 | |
---|
1129 | /* |
---|
1130 | ** Unfortunately it is not clear whether this routine should |
---|
1131 | ** return a copy of a string or just a pointer to a string. |
---|
1132 | ** The Cyrus-SASL plugins treat these return values differently, e.g., |
---|
1133 | ** plugins/cram.c free()s authid, plugings/digestmd5.c does not. |
---|
1134 | ** The best solution to this problem is to fix Cyrus-SASL, but it |
---|
1135 | ** seems there is nobody who creates patches... Hello CMU!? |
---|
1136 | ** The second best solution is to have flags that tell this routine |
---|
1137 | ** whether to return an malloc()ed copy. |
---|
1138 | ** The next best solution is to always return an malloc()ed copy, |
---|
1139 | ** and suffer from some memory leak, which is ugly for persistent |
---|
1140 | ** queue runners. |
---|
1141 | ** For now we go with the last solution... |
---|
1142 | ** We can't use rpools (which would avoid this particular problem) |
---|
1143 | ** as explained in sasl.c. |
---|
1144 | */ |
---|
1145 | |
---|
1146 | switch (id) |
---|
1147 | { |
---|
1148 | case SASL_CB_USER: |
---|
1149 | l = strlen((*sai)[SASL_USER]) + 1; |
---|
1150 | s = sm_sasl_malloc(l); |
---|
1151 | if (s == NULL) |
---|
1152 | { |
---|
1153 | if (len != NULL) |
---|
1154 | *len = 0; |
---|
1155 | *result = NULL; |
---|
1156 | return SASL_NOMEM; |
---|
1157 | } |
---|
1158 | (void) sm_strlcpy(s, (*sai)[SASL_USER], l); |
---|
1159 | *result = s; |
---|
1160 | if (tTd(95, 5)) |
---|
1161 | sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'", |
---|
1162 | *result); |
---|
1163 | if (len != NULL) |
---|
1164 | *len = *result != NULL ? strlen(*result) : 0; |
---|
1165 | break; |
---|
1166 | |
---|
1167 | case SASL_CB_AUTHNAME: |
---|
1168 | h = (*sai)[SASL_AUTHID]; |
---|
1169 | # if SASL > 10509 |
---|
1170 | /* XXX maybe other mechanisms too?! */ |
---|
1171 | addrealm = (*sai)[SASL_MECH] != NULL && |
---|
1172 | sm_strcasecmp((*sai)[SASL_MECH], "CRAM-MD5") == 0; |
---|
1173 | |
---|
1174 | /* |
---|
1175 | ** Add realm to authentication id unless authid contains |
---|
1176 | ** '@' (i.e., a realm) or the default realm is empty. |
---|
1177 | */ |
---|
1178 | |
---|
1179 | if (addrealm && h != NULL && strchr(h, '@') == NULL) |
---|
1180 | { |
---|
1181 | /* has this been done before? */ |
---|
1182 | if ((*sai)[SASL_ID_REALM] == NULL) |
---|
1183 | { |
---|
1184 | char *realm; |
---|
1185 | |
---|
1186 | realm = (*sai)[SASL_DEFREALM]; |
---|
1187 | |
---|
1188 | /* do not add an empty realm */ |
---|
1189 | if (*realm == '\0') |
---|
1190 | { |
---|
1191 | authid = h; |
---|
1192 | (*sai)[SASL_ID_REALM] = NULL; |
---|
1193 | } |
---|
1194 | else |
---|
1195 | { |
---|
1196 | l = strlen(h) + strlen(realm) + 2; |
---|
1197 | |
---|
1198 | /* should use rpool, but from where? */ |
---|
1199 | authid = sm_sasl_malloc(l); |
---|
1200 | if (authid != NULL) |
---|
1201 | { |
---|
1202 | (void) sm_snprintf(authid, l, |
---|
1203 | "%s@%s", |
---|
1204 | h, realm); |
---|
1205 | (*sai)[SASL_ID_REALM] = authid; |
---|
1206 | } |
---|
1207 | else |
---|
1208 | { |
---|
1209 | authid = h; |
---|
1210 | (*sai)[SASL_ID_REALM] = NULL; |
---|
1211 | } |
---|
1212 | } |
---|
1213 | } |
---|
1214 | else |
---|
1215 | authid = (*sai)[SASL_ID_REALM]; |
---|
1216 | } |
---|
1217 | else |
---|
1218 | # endif /* SASL > 10509 */ |
---|
1219 | authid = h; |
---|
1220 | l = strlen(authid) + 1; |
---|
1221 | s = sm_sasl_malloc(l); |
---|
1222 | if (s == NULL) |
---|
1223 | { |
---|
1224 | if (len != NULL) |
---|
1225 | *len = 0; |
---|
1226 | *result = NULL; |
---|
1227 | return SASL_NOMEM; |
---|
1228 | } |
---|
1229 | (void) sm_strlcpy(s, authid, l); |
---|
1230 | *result = s; |
---|
1231 | if (tTd(95, 5)) |
---|
1232 | sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'", |
---|
1233 | *result); |
---|
1234 | if (len != NULL) |
---|
1235 | *len = authid ? strlen(authid) : 0; |
---|
1236 | break; |
---|
1237 | |
---|
1238 | case SASL_CB_LANGUAGE: |
---|
1239 | *result = NULL; |
---|
1240 | if (len != NULL) |
---|
1241 | *len = 0; |
---|
1242 | break; |
---|
1243 | |
---|
1244 | default: |
---|
1245 | return SASL_BADPARAM; |
---|
1246 | } |
---|
1247 | return SASL_OK; |
---|
1248 | } |
---|
1249 | /* |
---|
1250 | ** GETSECRET -- callback to get password |
---|
1251 | ** |
---|
1252 | ** Parameters: |
---|
1253 | ** conn -- connection information |
---|
1254 | ** context -- sai |
---|
1255 | ** id -- what to do |
---|
1256 | ** psecret -- (pointer to) result |
---|
1257 | ** |
---|
1258 | ** Returns: |
---|
1259 | ** OK/failure values |
---|
1260 | */ |
---|
1261 | |
---|
1262 | static int |
---|
1263 | getsecret(conn, context, id, psecret) |
---|
1264 | sasl_conn_t *conn; |
---|
1265 | SM_UNUSED(void *context); |
---|
1266 | int id; |
---|
1267 | sasl_secret_t **psecret; |
---|
1268 | { |
---|
1269 | int len; |
---|
1270 | char *authpass; |
---|
1271 | SASL_AI_T *sai; |
---|
1272 | |
---|
1273 | if (conn == NULL || psecret == NULL || id != SASL_CB_PASS) |
---|
1274 | return SASL_BADPARAM; |
---|
1275 | |
---|
1276 | sai = (SASL_AI_T *) context; |
---|
1277 | authpass = (*sai)[SASL_PASSWORD]; |
---|
1278 | len = strlen(authpass); |
---|
1279 | *psecret = (sasl_secret_t *) sm_sasl_malloc(sizeof(sasl_secret_t) + |
---|
1280 | len + 1); |
---|
1281 | if (*psecret == NULL) |
---|
1282 | return SASL_FAIL; |
---|
1283 | (void) sm_strlcpy((*psecret)->data, authpass, len + 1); |
---|
1284 | (*psecret)->len = (unsigned long) len; |
---|
1285 | return SASL_OK; |
---|
1286 | } |
---|
1287 | # endif /* SASL >= 20000 */ |
---|
1288 | |
---|
1289 | /* |
---|
1290 | ** SAFESASLFILE -- callback for sasl: is file safe? |
---|
1291 | ** |
---|
1292 | ** Parameters: |
---|
1293 | ** context -- pointer to context between invocations (unused) |
---|
1294 | ** file -- name of file to check |
---|
1295 | ** type -- type of file to check |
---|
1296 | ** |
---|
1297 | ** Returns: |
---|
1298 | ** SASL_OK -- file can be used |
---|
1299 | ** SASL_CONTINUE -- don't use file |
---|
1300 | ** SASL_FAIL -- failure (not used here) |
---|
1301 | ** |
---|
1302 | */ |
---|
1303 | |
---|
1304 | int |
---|
1305 | #if SASL > 10515 |
---|
1306 | safesaslfile(context, file, type) |
---|
1307 | #else /* SASL > 10515 */ |
---|
1308 | safesaslfile(context, file) |
---|
1309 | #endif /* SASL > 10515 */ |
---|
1310 | void *context; |
---|
1311 | # if SASL >= 20000 |
---|
1312 | const char *file; |
---|
1313 | # else /* SASL >= 20000 */ |
---|
1314 | char *file; |
---|
1315 | # endif /* SASL >= 20000 */ |
---|
1316 | #if SASL > 10515 |
---|
1317 | # if SASL >= 20000 |
---|
1318 | sasl_verify_type_t type; |
---|
1319 | # else /* SASL >= 20000 */ |
---|
1320 | int type; |
---|
1321 | # endif /* SASL >= 20000 */ |
---|
1322 | #endif /* SASL > 10515 */ |
---|
1323 | { |
---|
1324 | long sff; |
---|
1325 | int r; |
---|
1326 | #if SASL <= 10515 |
---|
1327 | size_t len; |
---|
1328 | #endif /* SASL <= 10515 */ |
---|
1329 | char *p; |
---|
1330 | |
---|
1331 | if (file == NULL || *file == '\0') |
---|
1332 | return SASL_OK; |
---|
1333 | sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOWWFILES|SFF_ROOTOK; |
---|
1334 | #if SASL <= 10515 |
---|
1335 | if ((p = strrchr(file, '/')) == NULL) |
---|
1336 | p = file; |
---|
1337 | else |
---|
1338 | ++p; |
---|
1339 | |
---|
1340 | /* everything beside libs and .conf files must not be readable */ |
---|
1341 | len = strlen(p); |
---|
1342 | if ((len <= 3 || strncmp(p, "lib", 3) != 0) && |
---|
1343 | (len <= 5 || strncmp(p + len - 5, ".conf", 5) != 0)) |
---|
1344 | { |
---|
1345 | if (!bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail)) |
---|
1346 | sff |= SFF_NORFILES; |
---|
1347 | if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail)) |
---|
1348 | sff |= SFF_NOGWFILES; |
---|
1349 | } |
---|
1350 | #else /* SASL <= 10515 */ |
---|
1351 | /* files containing passwords should be not readable */ |
---|
1352 | if (type == SASL_VRFY_PASSWD) |
---|
1353 | { |
---|
1354 | if (bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail)) |
---|
1355 | sff |= SFF_NOWRFILES; |
---|
1356 | else |
---|
1357 | sff |= SFF_NORFILES; |
---|
1358 | if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail)) |
---|
1359 | sff |= SFF_NOGWFILES; |
---|
1360 | } |
---|
1361 | #endif /* SASL <= 10515 */ |
---|
1362 | |
---|
1363 | p = (char *) file; |
---|
1364 | if ((r = safefile(p, RunAsUid, RunAsGid, RunAsUserName, sff, |
---|
1365 | S_IRUSR, NULL)) == 0) |
---|
1366 | return SASL_OK; |
---|
1367 | if (LogLevel > (r != ENOENT ? 8 : 10)) |
---|
1368 | sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s", |
---|
1369 | p, sm_errstring(r)); |
---|
1370 | return SASL_CONTINUE; |
---|
1371 | } |
---|
1372 | |
---|
1373 | /* |
---|
1374 | ** SASLGETREALM -- return the realm for SASL |
---|
1375 | ** |
---|
1376 | ** return the realm for the client |
---|
1377 | ** |
---|
1378 | ** Parameters: |
---|
1379 | ** context -- context shared between invocations |
---|
1380 | ** availrealms -- list of available realms |
---|
1381 | ** {realm, realm, ...} |
---|
1382 | ** result -- pointer to result |
---|
1383 | ** |
---|
1384 | ** Returns: |
---|
1385 | ** failure/success |
---|
1386 | */ |
---|
1387 | |
---|
1388 | static int |
---|
1389 | saslgetrealm(context, id, availrealms, result) |
---|
1390 | void *context; |
---|
1391 | int id; |
---|
1392 | const char **availrealms; |
---|
1393 | const char **result; |
---|
1394 | { |
---|
1395 | char *r; |
---|
1396 | SASL_AI_T *sai; |
---|
1397 | |
---|
1398 | sai = (SASL_AI_T *) context; |
---|
1399 | if (sai == NULL) |
---|
1400 | return SASL_FAIL; |
---|
1401 | r = (*sai)[SASL_DEFREALM]; |
---|
1402 | |
---|
1403 | if (LogLevel > 12) |
---|
1404 | sm_syslog(LOG_INFO, NOQID, |
---|
1405 | "AUTH=client, realm=%s, available realms=%s", |
---|
1406 | r == NULL ? "<No Realm>" : r, |
---|
1407 | (availrealms == NULL || *availrealms == NULL) |
---|
1408 | ? "<No Realms>" : *availrealms); |
---|
1409 | |
---|
1410 | /* check whether context is in list */ |
---|
1411 | if (availrealms != NULL && *availrealms != NULL) |
---|
1412 | { |
---|
1413 | if (iteminlist(context, (char *)(*availrealms + 1), " ,}") == |
---|
1414 | NULL) |
---|
1415 | { |
---|
1416 | if (LogLevel > 8) |
---|
1417 | sm_syslog(LOG_ERR, NOQID, |
---|
1418 | "AUTH=client, realm=%s not in list=%s", |
---|
1419 | r, *availrealms); |
---|
1420 | return SASL_FAIL; |
---|
1421 | } |
---|
1422 | } |
---|
1423 | *result = r; |
---|
1424 | return SASL_OK; |
---|
1425 | } |
---|
1426 | /* |
---|
1427 | ** ITEMINLIST -- does item appear in list? |
---|
1428 | ** |
---|
1429 | ** Check whether item appears in list (which must be separated by a |
---|
1430 | ** character in delim) as a "word", i.e. it must appear at the begin |
---|
1431 | ** of the list or after a space, and it must end with a space or the |
---|
1432 | ** end of the list. |
---|
1433 | ** |
---|
1434 | ** Parameters: |
---|
1435 | ** item -- item to search. |
---|
1436 | ** list -- list of items. |
---|
1437 | ** delim -- list of delimiters. |
---|
1438 | ** |
---|
1439 | ** Returns: |
---|
1440 | ** pointer to occurrence (NULL if not found). |
---|
1441 | */ |
---|
1442 | |
---|
1443 | char * |
---|
1444 | iteminlist(item, list, delim) |
---|
1445 | char *item; |
---|
1446 | char *list; |
---|
1447 | char *delim; |
---|
1448 | { |
---|
1449 | char *s; |
---|
1450 | int len; |
---|
1451 | |
---|
1452 | if (list == NULL || *list == '\0') |
---|
1453 | return NULL; |
---|
1454 | if (item == NULL || *item == '\0') |
---|
1455 | return NULL; |
---|
1456 | s = list; |
---|
1457 | len = strlen(item); |
---|
1458 | while (s != NULL && *s != '\0') |
---|
1459 | { |
---|
1460 | if (sm_strncasecmp(s, item, len) == 0 && |
---|
1461 | (s[len] == '\0' || strchr(delim, s[len]) != NULL)) |
---|
1462 | return s; |
---|
1463 | s = strpbrk(s, delim); |
---|
1464 | if (s != NULL) |
---|
1465 | while (*++s == ' ') |
---|
1466 | continue; |
---|
1467 | } |
---|
1468 | return NULL; |
---|
1469 | } |
---|
1470 | /* |
---|
1471 | ** REMOVEMECH -- remove item [rem] from list [list] |
---|
1472 | ** |
---|
1473 | ** Parameters: |
---|
1474 | ** rem -- item to remove |
---|
1475 | ** list -- list of items |
---|
1476 | ** rpool -- resource pool from which result is allocated. |
---|
1477 | ** |
---|
1478 | ** Returns: |
---|
1479 | ** pointer to new list (NULL in case of error). |
---|
1480 | */ |
---|
1481 | |
---|
1482 | static char * |
---|
1483 | removemech(rem, list, rpool) |
---|
1484 | char *rem; |
---|
1485 | char *list; |
---|
1486 | SM_RPOOL_T *rpool; |
---|
1487 | { |
---|
1488 | char *ret; |
---|
1489 | char *needle; |
---|
1490 | int len; |
---|
1491 | |
---|
1492 | if (list == NULL) |
---|
1493 | return NULL; |
---|
1494 | if (rem == NULL || *rem == '\0') |
---|
1495 | { |
---|
1496 | /* take out what? */ |
---|
1497 | return NULL; |
---|
1498 | } |
---|
1499 | |
---|
1500 | /* find the item in the list */ |
---|
1501 | if ((needle = iteminlist(rem, list, " ")) == NULL) |
---|
1502 | { |
---|
1503 | /* not in there: return original */ |
---|
1504 | return list; |
---|
1505 | } |
---|
1506 | |
---|
1507 | /* length of string without rem */ |
---|
1508 | len = strlen(list) - strlen(rem); |
---|
1509 | if (len <= 0) |
---|
1510 | { |
---|
1511 | ret = (char *) sm_rpool_malloc_x(rpool, 1); |
---|
1512 | *ret = '\0'; |
---|
1513 | return ret; |
---|
1514 | } |
---|
1515 | ret = (char *) sm_rpool_malloc_x(rpool, len); |
---|
1516 | memset(ret, '\0', len); |
---|
1517 | |
---|
1518 | /* copy from start to removed item */ |
---|
1519 | memcpy(ret, list, needle - list); |
---|
1520 | |
---|
1521 | /* length of rest of string past removed item */ |
---|
1522 | len = strlen(needle) - strlen(rem) - 1; |
---|
1523 | if (len > 0) |
---|
1524 | { |
---|
1525 | /* not last item -- copy into string */ |
---|
1526 | memcpy(ret + (needle - list), |
---|
1527 | list + (needle - list) + strlen(rem) + 1, |
---|
1528 | len); |
---|
1529 | } |
---|
1530 | else |
---|
1531 | ret[(needle - list) - 1] = '\0'; |
---|
1532 | return ret; |
---|
1533 | } |
---|
1534 | /* |
---|
1535 | ** ATTEMPTAUTH -- try to AUTHenticate using one mechanism |
---|
1536 | ** |
---|
1537 | ** Parameters: |
---|
1538 | ** m -- the mailer. |
---|
1539 | ** mci -- the mailer connection structure. |
---|
1540 | ** e -- the envelope (including the sender to specify). |
---|
1541 | ** sai - sasl authinfo |
---|
1542 | ** |
---|
1543 | ** Returns: |
---|
1544 | ** EX_OK -- authentication was successful. |
---|
1545 | ** EX_NOPERM -- authentication failed. |
---|
1546 | ** EX_IOERR -- authentication dialogue failed (I/O problem?). |
---|
1547 | ** EX_TEMPFAIL -- temporary failure. |
---|
1548 | ** |
---|
1549 | */ |
---|
1550 | |
---|
1551 | static int |
---|
1552 | attemptauth(m, mci, e, sai) |
---|
1553 | MAILER *m; |
---|
1554 | MCI *mci; |
---|
1555 | ENVELOPE *e; |
---|
1556 | SASL_AI_T *sai; |
---|
1557 | { |
---|
1558 | int saslresult, smtpresult; |
---|
1559 | # if SASL >= 20000 |
---|
1560 | sasl_ssf_t ssf; |
---|
1561 | const char *auth_id; |
---|
1562 | const char *out; |
---|
1563 | # else /* SASL >= 20000 */ |
---|
1564 | sasl_external_properties_t ssf; |
---|
1565 | char *out; |
---|
1566 | # endif /* SASL >= 20000 */ |
---|
1567 | unsigned int outlen; |
---|
1568 | sasl_interact_t *client_interact = NULL; |
---|
1569 | char *mechusing; |
---|
1570 | sasl_security_properties_t ssp; |
---|
1571 | char in64[MAXOUTLEN]; |
---|
1572 | #if NETINET || (NETINET6 && SASL >= 20000) |
---|
1573 | extern SOCKADDR CurHostAddr; |
---|
1574 | #endif /* NETINET || (NETINET6 && SASL >= 20000) */ |
---|
1575 | |
---|
1576 | /* no mechanism selected (yet) */ |
---|
1577 | (*sai)[SASL_MECH] = NULL; |
---|
1578 | |
---|
1579 | /* dispose old connection */ |
---|
1580 | if (mci->mci_conn != NULL) |
---|
1581 | sasl_dispose(&(mci->mci_conn)); |
---|
1582 | |
---|
1583 | /* make a new client sasl connection */ |
---|
1584 | # if SASL >= 20000 |
---|
1585 | saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp" |
---|
1586 | : "smtp", |
---|
1587 | CurHostName, NULL, NULL, NULL, 0, |
---|
1588 | &mci->mci_conn); |
---|
1589 | # else /* SASL >= 20000 */ |
---|
1590 | saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp" |
---|
1591 | : "smtp", |
---|
1592 | CurHostName, NULL, 0, &mci->mci_conn); |
---|
1593 | # endif /* SASL >= 20000 */ |
---|
1594 | if (saslresult != SASL_OK) |
---|
1595 | return EX_TEMPFAIL; |
---|
1596 | |
---|
1597 | /* set properties */ |
---|
1598 | (void) memset(&ssp, '\0', sizeof ssp); |
---|
1599 | |
---|
1600 | /* XXX should these be options settable via .cf ? */ |
---|
1601 | # if STARTTLS |
---|
1602 | #endif /* STARTTLS */ |
---|
1603 | { |
---|
1604 | ssp.max_ssf = MaxSLBits; |
---|
1605 | ssp.maxbufsize = MAXOUTLEN; |
---|
1606 | # if 0 |
---|
1607 | ssp.security_flags = SASL_SEC_NOPLAINTEXT; |
---|
1608 | # endif /* 0 */ |
---|
1609 | } |
---|
1610 | saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp); |
---|
1611 | if (saslresult != SASL_OK) |
---|
1612 | return EX_TEMPFAIL; |
---|
1613 | |
---|
1614 | # if SASL >= 20000 |
---|
1615 | /* external security strength factor, authentication id */ |
---|
1616 | ssf = 0; |
---|
1617 | auth_id = NULL; |
---|
1618 | # if STARTTLS |
---|
1619 | out = macvalue(macid("{cert_subject}"), e); |
---|
1620 | if (out != NULL && *out != '\0') |
---|
1621 | auth_id = out; |
---|
1622 | out = macvalue(macid("{cipher_bits}"), e); |
---|
1623 | if (out != NULL && *out != '\0') |
---|
1624 | ssf = atoi(out); |
---|
1625 | # endif /* STARTTLS */ |
---|
1626 | saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf); |
---|
1627 | if (saslresult != SASL_OK) |
---|
1628 | return EX_TEMPFAIL; |
---|
1629 | saslresult = sasl_setprop(mci->mci_conn, SASL_AUTH_EXTERNAL, auth_id); |
---|
1630 | if (saslresult != SASL_OK) |
---|
1631 | return EX_TEMPFAIL; |
---|
1632 | |
---|
1633 | # if NETINET || NETINET6 |
---|
1634 | /* set local/remote ipv4 addresses */ |
---|
1635 | if (mci->mci_out != NULL && ( |
---|
1636 | # if NETINET6 |
---|
1637 | CurHostAddr.sa.sa_family == AF_INET6 || |
---|
1638 | # endif /* NETINET6 */ |
---|
1639 | CurHostAddr.sa.sa_family == AF_INET)) |
---|
1640 | { |
---|
1641 | SOCKADDR_LEN_T addrsize; |
---|
1642 | SOCKADDR saddr_l; |
---|
1643 | char localip[60], remoteip[60]; |
---|
1644 | |
---|
1645 | switch (CurHostAddr.sa.sa_family) |
---|
1646 | { |
---|
1647 | case AF_INET: |
---|
1648 | addrsize = sizeof(struct sockaddr_in); |
---|
1649 | break; |
---|
1650 | # if NETINET6 |
---|
1651 | case AF_INET6: |
---|
1652 | addrsize = sizeof(struct sockaddr_in6); |
---|
1653 | break; |
---|
1654 | # endif /* NETINET6 */ |
---|
1655 | default: |
---|
1656 | break; |
---|
1657 | } |
---|
1658 | if (iptostring(&CurHostAddr, addrsize, |
---|
1659 | remoteip, sizeof remoteip)) |
---|
1660 | { |
---|
1661 | if (sasl_setprop(mci->mci_conn, SASL_IPREMOTEPORT, |
---|
1662 | remoteip) != SASL_OK) |
---|
1663 | return EX_TEMPFAIL; |
---|
1664 | } |
---|
1665 | addrsize = sizeof(saddr_l); |
---|
1666 | if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD, |
---|
1667 | NULL), |
---|
1668 | (struct sockaddr *) &saddr_l, &addrsize) == 0) |
---|
1669 | { |
---|
1670 | if (iptostring(&saddr_l, addrsize, |
---|
1671 | localip, sizeof localip)) |
---|
1672 | { |
---|
1673 | if (sasl_setprop(mci->mci_conn, |
---|
1674 | SASL_IPLOCALPORT, |
---|
1675 | localip) != SASL_OK) |
---|
1676 | return EX_TEMPFAIL; |
---|
1677 | } |
---|
1678 | } |
---|
1679 | } |
---|
1680 | # endif /* NETINET || NETINET6 */ |
---|
1681 | |
---|
1682 | /* start client side of sasl */ |
---|
1683 | saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap, |
---|
1684 | &client_interact, |
---|
1685 | &out, &outlen, |
---|
1686 | (const char **) &mechusing); |
---|
1687 | # else /* SASL >= 20000 */ |
---|
1688 | /* external security strength factor, authentication id */ |
---|
1689 | ssf.ssf = 0; |
---|
1690 | ssf.auth_id = NULL; |
---|
1691 | # if STARTTLS |
---|
1692 | out = macvalue(macid("{cert_subject}"), e); |
---|
1693 | if (out != NULL && *out != '\0') |
---|
1694 | ssf.auth_id = out; |
---|
1695 | out = macvalue(macid("{cipher_bits}"), e); |
---|
1696 | if (out != NULL && *out != '\0') |
---|
1697 | ssf.ssf = atoi(out); |
---|
1698 | # endif /* STARTTLS */ |
---|
1699 | saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf); |
---|
1700 | if (saslresult != SASL_OK) |
---|
1701 | return EX_TEMPFAIL; |
---|
1702 | |
---|
1703 | # if NETINET |
---|
1704 | /* set local/remote ipv4 addresses */ |
---|
1705 | if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET) |
---|
1706 | { |
---|
1707 | SOCKADDR_LEN_T addrsize; |
---|
1708 | struct sockaddr_in saddr_l; |
---|
1709 | |
---|
1710 | if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE, |
---|
1711 | (struct sockaddr_in *) &CurHostAddr) |
---|
1712 | != SASL_OK) |
---|
1713 | return EX_TEMPFAIL; |
---|
1714 | addrsize = sizeof(struct sockaddr_in); |
---|
1715 | if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD, |
---|
1716 | NULL), |
---|
1717 | (struct sockaddr *) &saddr_l, &addrsize) == 0) |
---|
1718 | { |
---|
1719 | if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL, |
---|
1720 | &saddr_l) != SASL_OK) |
---|
1721 | return EX_TEMPFAIL; |
---|
1722 | } |
---|
1723 | } |
---|
1724 | # endif /* NETINET */ |
---|
1725 | |
---|
1726 | /* start client side of sasl */ |
---|
1727 | saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap, |
---|
1728 | NULL, &client_interact, |
---|
1729 | &out, &outlen, |
---|
1730 | (const char **) &mechusing); |
---|
1731 | # endif /* SASL >= 20000 */ |
---|
1732 | |
---|
1733 | if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) |
---|
1734 | { |
---|
1735 | if (saslresult == SASL_NOMECH && LogLevel > 8) |
---|
1736 | { |
---|
1737 | sm_syslog(LOG_NOTICE, e->e_id, |
---|
1738 | "AUTH=client, available mechanisms do not fulfill requirements"); |
---|
1739 | } |
---|
1740 | return EX_TEMPFAIL; |
---|
1741 | } |
---|
1742 | |
---|
1743 | /* just point current mechanism to the data in the sasl library */ |
---|
1744 | (*sai)[SASL_MECH] = mechusing; |
---|
1745 | |
---|
1746 | /* send the info across the wire */ |
---|
1747 | if (out == NULL |
---|
1748 | #if _FFR_SASL_INITIAL_WORKAROUND |
---|
1749 | /* login and digest-md5 up to 1.5.28 set out="" */ |
---|
1750 | || (outlen == 0 && |
---|
1751 | (sm_strcasecmp(mechusing, "LOGIN") == 0 || |
---|
1752 | sm_strcasecmp(mechusing, "DIGEST-MD5") == 0)) |
---|
1753 | #endif /* _FFR_SASL_INITIAL_WORKAROUND */ |
---|
1754 | ) |
---|
1755 | { |
---|
1756 | /* no initial response */ |
---|
1757 | smtpmessage("AUTH %s", m, mci, mechusing); |
---|
1758 | } |
---|
1759 | else if (outlen == 0) |
---|
1760 | { |
---|
1761 | /* |
---|
1762 | ** zero-length initial response, per RFC 2554 4.: |
---|
1763 | ** "Unlike a zero-length client answer to a 334 reply, a zero- |
---|
1764 | ** length initial response is sent as a single equals sign" |
---|
1765 | */ |
---|
1766 | |
---|
1767 | smtpmessage("AUTH %s =", m, mci, mechusing); |
---|
1768 | } |
---|
1769 | else |
---|
1770 | { |
---|
1771 | saslresult = sasl_encode64(out, outlen, in64, MAXOUTLEN, NULL); |
---|
1772 | if (saslresult != SASL_OK) /* internal error */ |
---|
1773 | { |
---|
1774 | if (LogLevel > 8) |
---|
1775 | sm_syslog(LOG_ERR, e->e_id, |
---|
1776 | "encode64 for AUTH failed"); |
---|
1777 | return EX_TEMPFAIL; |
---|
1778 | } |
---|
1779 | smtpmessage("AUTH %s %s", m, mci, mechusing, in64); |
---|
1780 | } |
---|
1781 | # if SASL < 20000 |
---|
1782 | sm_sasl_free(out); /* XXX only if no rpool is used */ |
---|
1783 | # endif /* SASL < 20000 */ |
---|
1784 | |
---|
1785 | /* get the reply */ |
---|
1786 | smtpresult = reply(m, mci, e, TimeOuts.to_auth, getsasldata, NULL); |
---|
1787 | |
---|
1788 | for (;;) |
---|
1789 | { |
---|
1790 | /* check return code from server */ |
---|
1791 | if (smtpresult == 235) |
---|
1792 | { |
---|
1793 | macdefine(&mci->mci_macro, A_TEMP, macid("{auth_type}"), |
---|
1794 | mechusing); |
---|
1795 | return EX_OK; |
---|
1796 | } |
---|
1797 | if (smtpresult == -1) |
---|
1798 | return EX_IOERR; |
---|
1799 | if (REPLYTYPE(smtpresult) == 5) |
---|
1800 | return EX_NOPERM; /* ugly, but ... */ |
---|
1801 | if (REPLYTYPE(smtpresult) != 3) |
---|
1802 | { |
---|
1803 | /* should we fail deliberately, see RFC 2554 4. ? */ |
---|
1804 | /* smtpmessage("*", m, mci); */ |
---|
1805 | return EX_TEMPFAIL; |
---|
1806 | } |
---|
1807 | |
---|
1808 | saslresult = sasl_client_step(mci->mci_conn, |
---|
1809 | mci->mci_sasl_string, |
---|
1810 | mci->mci_sasl_string_len, |
---|
1811 | &client_interact, |
---|
1812 | &out, &outlen); |
---|
1813 | |
---|
1814 | if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) |
---|
1815 | { |
---|
1816 | if (tTd(95, 5)) |
---|
1817 | sm_dprintf("AUTH FAIL=%s (%d)\n", |
---|
1818 | sasl_errstring(saslresult, NULL, NULL), |
---|
1819 | saslresult); |
---|
1820 | |
---|
1821 | /* fail deliberately, see RFC 2554 4. */ |
---|
1822 | smtpmessage("*", m, mci); |
---|
1823 | |
---|
1824 | /* |
---|
1825 | ** but we should only fail for this authentication |
---|
1826 | ** mechanism; how to do that? |
---|
1827 | */ |
---|
1828 | |
---|
1829 | smtpresult = reply(m, mci, e, TimeOuts.to_auth, |
---|
1830 | getsasldata, NULL); |
---|
1831 | return EX_NOPERM; |
---|
1832 | } |
---|
1833 | |
---|
1834 | if (outlen > 0) |
---|
1835 | { |
---|
1836 | saslresult = sasl_encode64(out, outlen, in64, |
---|
1837 | MAXOUTLEN, NULL); |
---|
1838 | if (saslresult != SASL_OK) |
---|
1839 | { |
---|
1840 | /* give an error reply to the other side! */ |
---|
1841 | smtpmessage("*", m, mci); |
---|
1842 | return EX_TEMPFAIL; |
---|
1843 | } |
---|
1844 | } |
---|
1845 | else |
---|
1846 | in64[0] = '\0'; |
---|
1847 | # if SASL < 20000 |
---|
1848 | sm_sasl_free(out); /* XXX only if no rpool is used */ |
---|
1849 | # endif /* SASL < 20000 */ |
---|
1850 | smtpmessage("%s", m, mci, in64); |
---|
1851 | smtpresult = reply(m, mci, e, TimeOuts.to_auth, |
---|
1852 | getsasldata, NULL); |
---|
1853 | } |
---|
1854 | /* NOTREACHED */ |
---|
1855 | } |
---|
1856 | /* |
---|
1857 | ** SMTPAUTH -- try to AUTHenticate |
---|
1858 | ** |
---|
1859 | ** This will try mechanisms in the order the sasl library decided until: |
---|
1860 | ** - there are no more mechanisms |
---|
1861 | ** - a mechanism succeeds |
---|
1862 | ** - the sasl library fails initializing |
---|
1863 | ** |
---|
1864 | ** Parameters: |
---|
1865 | ** m -- the mailer. |
---|
1866 | ** mci -- the mailer connection info. |
---|
1867 | ** e -- the envelope. |
---|
1868 | ** |
---|
1869 | ** Returns: |
---|
1870 | ** EX_OK -- authentication was successful |
---|
1871 | ** EX_UNAVAILABLE -- authentication not possible, e.g., |
---|
1872 | ** no data available. |
---|
1873 | ** EX_NOPERM -- authentication failed. |
---|
1874 | ** EX_TEMPFAIL -- temporary failure. |
---|
1875 | ** |
---|
1876 | ** Notice: AuthInfo is used for all connections, hence we must |
---|
1877 | ** return EX_TEMPFAIL only if we really want to retry, i.e., |
---|
1878 | ** iff getauth() tempfailed or getauth() was used and |
---|
1879 | ** authentication tempfailed. |
---|
1880 | */ |
---|
1881 | |
---|
1882 | int |
---|
1883 | smtpauth(m, mci, e) |
---|
1884 | MAILER *m; |
---|
1885 | MCI *mci; |
---|
1886 | ENVELOPE *e; |
---|
1887 | { |
---|
1888 | int result; |
---|
1889 | int i; |
---|
1890 | bool usedgetauth; |
---|
1891 | |
---|
1892 | if (noauthentication) |
---|
1893 | return EX_UNAVAILABLE; |
---|
1894 | |
---|
1895 | mci->mci_sasl_auth = false; |
---|
1896 | for (i = 0; i < SASL_MECH ; i++) |
---|
1897 | mci->mci_sai[i] = NULL; |
---|
1898 | |
---|
1899 | result = getauth(mci, e, &(mci->mci_sai)); |
---|
1900 | if (result == EX_TEMPFAIL) |
---|
1901 | return result; |
---|
1902 | usedgetauth = true; |
---|
1903 | |
---|
1904 | /* no data available: don't try to authenticate */ |
---|
1905 | if (result == EX_OK && mci->mci_sai[SASL_AUTHID] == NULL) |
---|
1906 | return result; |
---|
1907 | if (result != EX_OK) |
---|
1908 | { |
---|
1909 | if (SASLInfo == NULL) |
---|
1910 | return EX_UNAVAILABLE; |
---|
1911 | |
---|
1912 | /* read authinfo from file */ |
---|
1913 | result = readauth(SASLInfo, true, &(mci->mci_sai), |
---|
1914 | mci->mci_rpool); |
---|
1915 | if (result != EX_OK) |
---|
1916 | return result; |
---|
1917 | usedgetauth = false; |
---|
1918 | } |
---|
1919 | |
---|
1920 | /* check whether sufficient data is available */ |
---|
1921 | if (mci->mci_sai[SASL_PASSWORD] == NULL || |
---|
1922 | *(mci->mci_sai)[SASL_PASSWORD] == '\0') |
---|
1923 | return EX_UNAVAILABLE; |
---|
1924 | if ((mci->mci_sai[SASL_AUTHID] == NULL || |
---|
1925 | *(mci->mci_sai)[SASL_AUTHID] == '\0') && |
---|
1926 | (mci->mci_sai[SASL_USER] == NULL || |
---|
1927 | *(mci->mci_sai)[SASL_USER] == '\0')) |
---|
1928 | return EX_UNAVAILABLE; |
---|
1929 | |
---|
1930 | /* set the context for the callback function to sai */ |
---|
1931 | # if SASL >= 20000 |
---|
1932 | callbacks[CB_PASS_IDX].context = (void *) mci; |
---|
1933 | # else /* SASL >= 20000 */ |
---|
1934 | callbacks[CB_PASS_IDX].context = (void *) &mci->mci_sai; |
---|
1935 | # endif /* SASL >= 20000 */ |
---|
1936 | callbacks[CB_USER_IDX].context = (void *) &mci->mci_sai; |
---|
1937 | callbacks[CB_AUTHNAME_IDX].context = (void *) &mci->mci_sai; |
---|
1938 | callbacks[CB_GETREALM_IDX].context = (void *) &mci->mci_sai; |
---|
1939 | #if 0 |
---|
1940 | callbacks[CB_SAFESASL_IDX].context = (void *) &mci->mci_sai; |
---|
1941 | #endif /* 0 */ |
---|
1942 | |
---|
1943 | /* set default value for realm */ |
---|
1944 | if ((mci->mci_sai)[SASL_DEFREALM] == NULL) |
---|
1945 | (mci->mci_sai)[SASL_DEFREALM] = sm_rpool_strdup_x(e->e_rpool, |
---|
1946 | macvalue('j', CurEnv)); |
---|
1947 | |
---|
1948 | /* set default value for list of mechanism to use */ |
---|
1949 | if ((mci->mci_sai)[SASL_MECHLIST] == NULL || |
---|
1950 | *(mci->mci_sai)[SASL_MECHLIST] == '\0') |
---|
1951 | (mci->mci_sai)[SASL_MECHLIST] = AuthMechanisms; |
---|
1952 | |
---|
1953 | /* create list of mechanisms to try */ |
---|
1954 | mci->mci_saslcap = intersect((mci->mci_sai)[SASL_MECHLIST], |
---|
1955 | mci->mci_saslcap, mci->mci_rpool); |
---|
1956 | |
---|
1957 | /* initialize sasl client library */ |
---|
1958 | result = init_sasl_client(); |
---|
1959 | if (result != SASL_OK) |
---|
1960 | return usedgetauth ? EX_TEMPFAIL : EX_UNAVAILABLE; |
---|
1961 | do |
---|
1962 | { |
---|
1963 | result = attemptauth(m, mci, e, &(mci->mci_sai)); |
---|
1964 | if (result == EX_OK) |
---|
1965 | mci->mci_sasl_auth = true; |
---|
1966 | else if (result == EX_TEMPFAIL || result == EX_NOPERM) |
---|
1967 | { |
---|
1968 | mci->mci_saslcap = removemech((mci->mci_sai)[SASL_MECH], |
---|
1969 | mci->mci_saslcap, |
---|
1970 | mci->mci_rpool); |
---|
1971 | if (mci->mci_saslcap == NULL || |
---|
1972 | *(mci->mci_saslcap) == '\0') |
---|
1973 | return usedgetauth ? result |
---|
1974 | : EX_UNAVAILABLE; |
---|
1975 | } |
---|
1976 | else |
---|
1977 | return result; |
---|
1978 | } while (result != EX_OK); |
---|
1979 | return result; |
---|
1980 | } |
---|
1981 | #endif /* SASL */ |
---|
1982 | |
---|
1983 | /* |
---|
1984 | ** SMTPMAILFROM -- send MAIL command |
---|
1985 | ** |
---|
1986 | ** Parameters: |
---|
1987 | ** m -- the mailer. |
---|
1988 | ** mci -- the mailer connection structure. |
---|
1989 | ** e -- the envelope (including the sender to specify). |
---|
1990 | */ |
---|
1991 | |
---|
1992 | int |
---|
1993 | smtpmailfrom(m, mci, e) |
---|
1994 | MAILER *m; |
---|
1995 | MCI *mci; |
---|
1996 | ENVELOPE *e; |
---|
1997 | { |
---|
1998 | int r; |
---|
1999 | char *bufp; |
---|
2000 | char *bodytype; |
---|
2001 | char *enhsc; |
---|
2002 | char buf[MAXNAME + 1]; |
---|
2003 | char optbuf[MAXLINE]; |
---|
2004 | |
---|
2005 | if (tTd(18, 2)) |
---|
2006 | sm_dprintf("smtpmailfrom: CurHost=%s\n", CurHostName); |
---|
2007 | enhsc = NULL; |
---|
2008 | |
---|
2009 | /* |
---|
2010 | ** Check if connection is gone, if so |
---|
2011 | ** it's a tempfail and we use mci_errno |
---|
2012 | ** for the reason. |
---|
2013 | */ |
---|
2014 | |
---|
2015 | if (mci->mci_state == MCIS_CLOSED) |
---|
2016 | { |
---|
2017 | errno = mci->mci_errno; |
---|
2018 | return EX_TEMPFAIL; |
---|
2019 | } |
---|
2020 | |
---|
2021 | /* set up appropriate options to include */ |
---|
2022 | if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0) |
---|
2023 | { |
---|
2024 | (void) sm_snprintf(optbuf, sizeof optbuf, " SIZE=%ld", |
---|
2025 | e->e_msgsize); |
---|
2026 | bufp = &optbuf[strlen(optbuf)]; |
---|
2027 | } |
---|
2028 | else |
---|
2029 | { |
---|
2030 | optbuf[0] = '\0'; |
---|
2031 | bufp = optbuf; |
---|
2032 | } |
---|
2033 | |
---|
2034 | bodytype = e->e_bodytype; |
---|
2035 | if (bitset(MCIF_8BITMIME, mci->mci_flags)) |
---|
2036 | { |
---|
2037 | if (bodytype == NULL && |
---|
2038 | bitset(MM_MIME8BIT, MimeMode) && |
---|
2039 | bitset(EF_HAS8BIT, e->e_flags) && |
---|
2040 | !bitset(EF_DONT_MIME, e->e_flags) && |
---|
2041 | !bitnset(M_8BITS, m->m_flags)) |
---|
2042 | bodytype = "8BITMIME"; |
---|
2043 | if (bodytype != NULL && |
---|
2044 | SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7) |
---|
2045 | { |
---|
2046 | (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp), |
---|
2047 | " BODY=%s", bodytype); |
---|
2048 | bufp += strlen(bufp); |
---|
2049 | } |
---|
2050 | } |
---|
2051 | else if (bitnset(M_8BITS, m->m_flags) || |
---|
2052 | !bitset(EF_HAS8BIT, e->e_flags) || |
---|
2053 | bitset(MCIF_8BITOK, mci->mci_flags)) |
---|
2054 | { |
---|
2055 | /* EMPTY */ |
---|
2056 | /* just pass it through */ |
---|
2057 | } |
---|
2058 | #if MIME8TO7 |
---|
2059 | else if (bitset(MM_CVTMIME, MimeMode) && |
---|
2060 | !bitset(EF_DONT_MIME, e->e_flags) && |
---|
2061 | (!bitset(MM_PASS8BIT, MimeMode) || |
---|
2062 | bitset(EF_IS_MIME, e->e_flags))) |
---|
2063 | { |
---|
2064 | /* must convert from 8bit MIME format to 7bit encoded */ |
---|
2065 | mci->mci_flags |= MCIF_CVT8TO7; |
---|
2066 | } |
---|
2067 | #endif /* MIME8TO7 */ |
---|
2068 | else if (!bitset(MM_PASS8BIT, MimeMode)) |
---|
2069 | { |
---|
2070 | /* cannot just send a 8-bit version */ |
---|
2071 | extern char MsgBuf[]; |
---|
2072 | |
---|
2073 | usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName); |
---|
2074 | mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf); |
---|
2075 | return EX_DATAERR; |
---|
2076 | } |
---|
2077 | |
---|
2078 | if (bitset(MCIF_DSN, mci->mci_flags)) |
---|
2079 | { |
---|
2080 | if (e->e_envid != NULL && |
---|
2081 | SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7) |
---|
2082 | { |
---|
2083 | (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp), |
---|
2084 | " ENVID=%s", e->e_envid); |
---|
2085 | bufp += strlen(bufp); |
---|
2086 | } |
---|
2087 | |
---|
2088 | /* RET= parameter */ |
---|
2089 | if (bitset(EF_RET_PARAM, e->e_flags) && |
---|
2090 | SPACELEFT(optbuf, bufp) > 9) |
---|
2091 | { |
---|
2092 | (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp), |
---|
2093 | " RET=%s", |
---|
2094 | bitset(EF_NO_BODY_RETN, e->e_flags) ? |
---|
2095 | "HDRS" : "FULL"); |
---|
2096 | bufp += strlen(bufp); |
---|
2097 | } |
---|
2098 | } |
---|
2099 | |
---|
2100 | if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL && |
---|
2101 | SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7 |
---|
2102 | #if SASL |
---|
2103 | && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth) |
---|
2104 | #endif /* SASL */ |
---|
2105 | ) |
---|
2106 | { |
---|
2107 | (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp), |
---|
2108 | " AUTH=%s", e->e_auth_param); |
---|
2109 | bufp += strlen(bufp); |
---|
2110 | } |
---|
2111 | |
---|
2112 | /* |
---|
2113 | ** 17 is the max length required, we could use log() to compute |
---|
2114 | ** the exact length (and check IS_DLVR_TRACE()) |
---|
2115 | */ |
---|
2116 | |
---|
2117 | if (bitset(MCIF_DLVR_BY, mci->mci_flags) && |
---|
2118 | IS_DLVR_BY(e) && SPACELEFT(optbuf, bufp) > 17) |
---|
2119 | { |
---|
2120 | long dby; |
---|
2121 | |
---|
2122 | /* |
---|
2123 | ** Avoid problems with delays (for R) since the check |
---|
2124 | ** in deliver() whether min-deliver-time is sufficient. |
---|
2125 | ** Alternatively we could pass the computed time to this |
---|
2126 | ** function. |
---|
2127 | */ |
---|
2128 | |
---|
2129 | dby = e->e_deliver_by - (curtime() - e->e_ctime); |
---|
2130 | if (dby <= 0 && IS_DLVR_RETURN(e)) |
---|
2131 | dby = mci->mci_min_by <= 0 ? 1 : mci->mci_min_by; |
---|
2132 | (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp), |
---|
2133 | " BY=%ld;%c%s", |
---|
2134 | dby, |
---|
2135 | IS_DLVR_RETURN(e) ? 'R' : 'N', |
---|
2136 | IS_DLVR_TRACE(e) ? "T" : ""); |
---|
2137 | bufp += strlen(bufp); |
---|
2138 | } |
---|
2139 | |
---|
2140 | /* |
---|
2141 | ** Send the MAIL command. |
---|
2142 | ** Designates the sender. |
---|
2143 | */ |
---|
2144 | |
---|
2145 | mci->mci_state = MCIS_MAIL; |
---|
2146 | |
---|
2147 | if (bitset(EF_RESPONSE, e->e_flags) && |
---|
2148 | !bitnset(M_NO_NULL_FROM, m->m_flags)) |
---|
2149 | buf[0] = '\0'; |
---|
2150 | else |
---|
2151 | expand("\201g", buf, sizeof buf, e); |
---|
2152 | if (buf[0] == '<') |
---|
2153 | { |
---|
2154 | /* strip off <angle brackets> (put back on below) */ |
---|
2155 | bufp = &buf[strlen(buf) - 1]; |
---|
2156 | if (*bufp == '>') |
---|
2157 | *bufp = '\0'; |
---|
2158 | bufp = &buf[1]; |
---|
2159 | } |
---|
2160 | else |
---|
2161 | bufp = buf; |
---|
2162 | if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) || |
---|
2163 | !bitnset(M_FROMPATH, m->m_flags)) |
---|
2164 | { |
---|
2165 | smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf); |
---|
2166 | } |
---|
2167 | else |
---|
2168 | { |
---|
2169 | smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName, |
---|
2170 | *bufp == '@' ? ',' : ':', bufp, optbuf); |
---|
2171 | } |
---|
2172 | SmtpPhase = mci->mci_phase = "client MAIL"; |
---|
2173 | sm_setproctitle(true, e, "%s %s: %s", qid_printname(e), |
---|
2174 | CurHostName, mci->mci_phase); |
---|
2175 | r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc); |
---|
2176 | if (r < 0) |
---|
2177 | { |
---|
2178 | /* communications failure */ |
---|
2179 | mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL); |
---|
2180 | return EX_TEMPFAIL; |
---|
2181 | } |
---|
2182 | else if (r == SMTPCLOSING) |
---|
2183 | { |
---|
2184 | /* service shutting down: handled by reply() */ |
---|
2185 | return EX_TEMPFAIL; |
---|
2186 | } |
---|
2187 | else if (REPLYTYPE(r) == 4) |
---|
2188 | { |
---|
2189 | mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)), |
---|
2190 | SmtpReplyBuffer); |
---|
2191 | return EX_TEMPFAIL; |
---|
2192 | } |
---|
2193 | else if (REPLYTYPE(r) == 2) |
---|
2194 | { |
---|
2195 | return EX_OK; |
---|
2196 | } |
---|
2197 | else if (r == 501) |
---|
2198 | { |
---|
2199 | /* syntax error in arguments */ |
---|
2200 | mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"), |
---|
2201 | SmtpReplyBuffer); |
---|
2202 | return EX_DATAERR; |
---|
2203 | } |
---|
2204 | else if (r == 553) |
---|
2205 | { |
---|
2206 | /* mailbox name not allowed */ |
---|
2207 | mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"), |
---|
2208 | SmtpReplyBuffer); |
---|
2209 | return EX_DATAERR; |
---|
2210 | } |
---|
2211 | else if (r == 552) |
---|
2212 | { |
---|
2213 | /* exceeded storage allocation */ |
---|
2214 | mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"), |
---|
2215 | SmtpReplyBuffer); |
---|
2216 | if (bitset(MCIF_SIZE, mci->mci_flags)) |
---|
2217 | e->e_flags |= EF_NO_BODY_RETN; |
---|
2218 | return EX_UNAVAILABLE; |
---|
2219 | } |
---|
2220 | else if (REPLYTYPE(r) == 5) |
---|
2221 | { |
---|
2222 | /* unknown error */ |
---|
2223 | mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"), |
---|
2224 | SmtpReplyBuffer); |
---|
2225 | return EX_UNAVAILABLE; |
---|
2226 | } |
---|
2227 | |
---|
2228 | if (LogLevel > 1) |
---|
2229 | { |
---|
2230 | sm_syslog(LOG_CRIT, e->e_id, |
---|
2231 | "%.100s: SMTP MAIL protocol error: %s", |
---|
2232 | CurHostName, |
---|
2233 | shortenstring(SmtpReplyBuffer, 403)); |
---|
2234 | } |
---|
2235 | |
---|
2236 | /* protocol error -- close up */ |
---|
2237 | mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"), |
---|
2238 | SmtpReplyBuffer); |
---|
2239 | smtpquit(m, mci, e); |
---|
2240 | return EX_PROTOCOL; |
---|
2241 | } |
---|
2242 | /* |
---|
2243 | ** SMTPRCPT -- designate recipient. |
---|
2244 | ** |
---|
2245 | ** Parameters: |
---|
2246 | ** to -- address of recipient. |
---|
2247 | ** m -- the mailer we are sending to. |
---|
2248 | ** mci -- the connection info for this transaction. |
---|
2249 | ** e -- the envelope for this transaction. |
---|
2250 | ** |
---|
2251 | ** Returns: |
---|
2252 | ** exit status corresponding to recipient status. |
---|
2253 | ** |
---|
2254 | ** Side Effects: |
---|
2255 | ** Sends the mail via SMTP. |
---|
2256 | */ |
---|
2257 | |
---|
2258 | int |
---|
2259 | smtprcpt(to, m, mci, e, ctladdr, xstart) |
---|
2260 | ADDRESS *to; |
---|
2261 | register MAILER *m; |
---|
2262 | MCI *mci; |
---|
2263 | ENVELOPE *e; |
---|
2264 | ADDRESS *ctladdr; |
---|
2265 | time_t xstart; |
---|
2266 | { |
---|
2267 | char *bufp; |
---|
2268 | char optbuf[MAXLINE]; |
---|
2269 | |
---|
2270 | #if PIPELINING |
---|
2271 | /* |
---|
2272 | ** If there is status waiting from the other end, read it. |
---|
2273 | ** This should normally happen because of SMTP pipelining. |
---|
2274 | */ |
---|
2275 | |
---|
2276 | while (mci->mci_nextaddr != NULL && |
---|
2277 | sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0) |
---|
2278 | { |
---|
2279 | int r; |
---|
2280 | |
---|
2281 | r = smtprcptstat(mci->mci_nextaddr, m, mci, e); |
---|
2282 | if (r != EX_OK) |
---|
2283 | { |
---|
2284 | markfailure(e, mci->mci_nextaddr, mci, r, false); |
---|
2285 | giveresponse(r, mci->mci_nextaddr->q_status, m, mci, |
---|
2286 | ctladdr, xstart, e, to); |
---|
2287 | } |
---|
2288 | mci->mci_nextaddr = mci->mci_nextaddr->q_pchain; |
---|
2289 | } |
---|
2290 | #endif /* PIPELINING */ |
---|
2291 | |
---|
2292 | /* |
---|
2293 | ** Check if connection is gone, if so |
---|
2294 | ** it's a tempfail and we use mci_errno |
---|
2295 | ** for the reason. |
---|
2296 | */ |
---|
2297 | |
---|
2298 | if (mci->mci_state == MCIS_CLOSED) |
---|
2299 | { |
---|
2300 | errno = mci->mci_errno; |
---|
2301 | return EX_TEMPFAIL; |
---|
2302 | } |
---|
2303 | |
---|
2304 | optbuf[0] = '\0'; |
---|
2305 | bufp = optbuf; |
---|
2306 | |
---|
2307 | /* |
---|
2308 | ** Warning: in the following it is assumed that the free space |
---|
2309 | ** in bufp is sizeof optbuf |
---|
2310 | */ |
---|
2311 | |
---|
2312 | if (bitset(MCIF_DSN, mci->mci_flags)) |
---|
2313 | { |
---|
2314 | if (IS_DLVR_NOTIFY(e) && |
---|
2315 | !bitset(MCIF_DLVR_BY, mci->mci_flags)) |
---|
2316 | { |
---|
2317 | /* RFC 2852: 4.1.4.2 */ |
---|
2318 | if (!bitset(QHASNOTIFY, to->q_flags)) |
---|
2319 | to->q_flags |= QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY; |
---|
2320 | else if (bitset(QPINGONSUCCESS, to->q_flags) || |
---|
2321 | bitset(QPINGONFAILURE, to->q_flags) || |
---|
2322 | bitset(QPINGONDELAY, to->q_flags)) |
---|
2323 | to->q_flags |= QPINGONDELAY; |
---|
2324 | } |
---|
2325 | |
---|
2326 | /* NOTIFY= parameter */ |
---|
2327 | if (bitset(QHASNOTIFY, to->q_flags) && |
---|
2328 | bitset(QPRIMARY, to->q_flags) && |
---|
2329 | !bitnset(M_LOCALMAILER, m->m_flags)) |
---|
2330 | { |
---|
2331 | bool firstone = true; |
---|
2332 | |
---|
2333 | (void) sm_strlcat(bufp, " NOTIFY=", sizeof optbuf); |
---|
2334 | if (bitset(QPINGONSUCCESS, to->q_flags)) |
---|
2335 | { |
---|
2336 | (void) sm_strlcat(bufp, "SUCCESS", sizeof optbuf); |
---|
2337 | firstone = false; |
---|
2338 | } |
---|
2339 | if (bitset(QPINGONFAILURE, to->q_flags)) |
---|
2340 | { |
---|
2341 | if (!firstone) |
---|
2342 | (void) sm_strlcat(bufp, ",", |
---|
2343 | sizeof optbuf); |
---|
2344 | (void) sm_strlcat(bufp, "FAILURE", sizeof optbuf); |
---|
2345 | firstone = false; |
---|
2346 | } |
---|
2347 | if (bitset(QPINGONDELAY, to->q_flags)) |
---|
2348 | { |
---|
2349 | if (!firstone) |
---|
2350 | (void) sm_strlcat(bufp, ",", |
---|
2351 | sizeof optbuf); |
---|
2352 | (void) sm_strlcat(bufp, "DELAY", sizeof optbuf); |
---|
2353 | firstone = false; |
---|
2354 | } |
---|
2355 | if (firstone) |
---|
2356 | (void) sm_strlcat(bufp, "NEVER", sizeof optbuf); |
---|
2357 | bufp += strlen(bufp); |
---|
2358 | } |
---|
2359 | |
---|
2360 | /* ORCPT= parameter */ |
---|
2361 | if (to->q_orcpt != NULL && |
---|
2362 | SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7) |
---|
2363 | { |
---|
2364 | (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp), |
---|
2365 | " ORCPT=%s", to->q_orcpt); |
---|
2366 | bufp += strlen(bufp); |
---|
2367 | } |
---|
2368 | } |
---|
2369 | |
---|
2370 | smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf); |
---|
2371 | mci->mci_state = MCIS_RCPT; |
---|
2372 | |
---|
2373 | SmtpPhase = mci->mci_phase = "client RCPT"; |
---|
2374 | sm_setproctitle(true, e, "%s %s: %s", qid_printname(e), |
---|
2375 | CurHostName, mci->mci_phase); |
---|
2376 | |
---|
2377 | #if PIPELINING |
---|
2378 | /* |
---|
2379 | ** If running SMTP pipelining, we will pick up status later |
---|
2380 | */ |
---|
2381 | |
---|
2382 | if (bitset(MCIF_PIPELINED, mci->mci_flags)) |
---|
2383 | return EX_OK; |
---|
2384 | #endif /* PIPELINING */ |
---|
2385 | |
---|
2386 | return smtprcptstat(to, m, mci, e); |
---|
2387 | } |
---|
2388 | /* |
---|
2389 | ** SMTPRCPTSTAT -- get recipient status |
---|
2390 | ** |
---|
2391 | ** This is only called during SMTP pipelining |
---|
2392 | ** |
---|
2393 | ** Parameters: |
---|
2394 | ** to -- address of recipient. |
---|
2395 | ** m -- mailer being sent to. |
---|
2396 | ** mci -- the mailer connection information. |
---|
2397 | ** e -- the envelope for this message. |
---|
2398 | ** |
---|
2399 | ** Returns: |
---|
2400 | ** EX_* -- protocol status |
---|
2401 | */ |
---|
2402 | |
---|
2403 | static int |
---|
2404 | smtprcptstat(to, m, mci, e) |
---|
2405 | ADDRESS *to; |
---|
2406 | MAILER *m; |
---|
2407 | register MCI *mci; |
---|
2408 | register ENVELOPE *e; |
---|
2409 | { |
---|
2410 | int r; |
---|
2411 | int save_errno; |
---|
2412 | char *enhsc; |
---|
2413 | |
---|
2414 | /* |
---|
2415 | ** Check if connection is gone, if so |
---|
2416 | ** it's a tempfail and we use mci_errno |
---|
2417 | ** for the reason. |
---|
2418 | */ |
---|
2419 | |
---|
2420 | if (mci->mci_state == MCIS_CLOSED) |
---|
2421 | { |
---|
2422 | errno = mci->mci_errno; |
---|
2423 | return EX_TEMPFAIL; |
---|
2424 | } |
---|
2425 | |
---|
2426 | enhsc = NULL; |
---|
2427 | r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc); |
---|
2428 | save_errno = errno; |
---|
2429 | to->q_rstatus = sm_rpool_strdup_x(e->e_rpool, SmtpReplyBuffer); |
---|
2430 | to->q_status = ENHSCN_RPOOL(enhsc, smtptodsn(r), e->e_rpool); |
---|
2431 | if (!bitnset(M_LMTP, m->m_flags)) |
---|
2432 | to->q_statmta = mci->mci_host; |
---|
2433 | if (r < 0 || REPLYTYPE(r) == 4) |
---|
2434 | { |
---|
2435 | mci->mci_retryrcpt = true; |
---|
2436 | errno = save_errno; |
---|
2437 | return EX_TEMPFAIL; |
---|
2438 | } |
---|
2439 | else if (REPLYTYPE(r) == 2) |
---|
2440 | { |
---|
2441 | char *t; |
---|
2442 | |
---|
2443 | if ((t = mci->mci_tolist) != NULL) |
---|
2444 | { |
---|
2445 | char *p; |
---|
2446 | |
---|
2447 | *t++ = ','; |
---|
2448 | for (p = to->q_paddr; *p != '\0'; *t++ = *p++) |
---|
2449 | continue; |
---|
2450 | *t = '\0'; |
---|
2451 | mci->mci_tolist = t; |
---|
2452 | } |
---|
2453 | #if PIPELINING |
---|
2454 | mci->mci_okrcpts++; |
---|
2455 | #endif /* PIPELINING */ |
---|
2456 | return EX_OK; |
---|
2457 | } |
---|
2458 | else if (r == 550) |
---|
2459 | { |
---|
2460 | to->q_status = ENHSCN_RPOOL(enhsc, "5.1.1", e->e_rpool); |
---|
2461 | return EX_NOUSER; |
---|
2462 | } |
---|
2463 | else if (r == 551) |
---|
2464 | { |
---|
2465 | to->q_status = ENHSCN_RPOOL(enhsc, "5.1.6", e->e_rpool); |
---|
2466 | return EX_NOUSER; |
---|
2467 | } |
---|
2468 | else if (r == 553) |
---|
2469 | { |
---|
2470 | to->q_status = ENHSCN_RPOOL(enhsc, "5.1.3", e->e_rpool); |
---|
2471 | return EX_NOUSER; |
---|
2472 | } |
---|
2473 | else if (REPLYTYPE(r) == 5) |
---|
2474 | { |
---|
2475 | return EX_UNAVAILABLE; |
---|
2476 | } |
---|
2477 | |
---|
2478 | if (LogLevel > 1) |
---|
2479 | { |
---|
2480 | sm_syslog(LOG_CRIT, e->e_id, |
---|
2481 | "%.100s: SMTP RCPT protocol error: %s", |
---|
2482 | CurHostName, |
---|
2483 | shortenstring(SmtpReplyBuffer, 403)); |
---|
2484 | } |
---|
2485 | |
---|
2486 | mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"), |
---|
2487 | SmtpReplyBuffer); |
---|
2488 | return EX_PROTOCOL; |
---|
2489 | } |
---|
2490 | /* |
---|
2491 | ** SMTPDATA -- send the data and clean up the transaction. |
---|
2492 | ** |
---|
2493 | ** Parameters: |
---|
2494 | ** m -- mailer being sent to. |
---|
2495 | ** mci -- the mailer connection information. |
---|
2496 | ** e -- the envelope for this message. |
---|
2497 | ** |
---|
2498 | ** Returns: |
---|
2499 | ** exit status corresponding to DATA command. |
---|
2500 | */ |
---|
2501 | |
---|
2502 | int |
---|
2503 | smtpdata(m, mci, e, ctladdr, xstart) |
---|
2504 | MAILER *m; |
---|
2505 | register MCI *mci; |
---|
2506 | register ENVELOPE *e; |
---|
2507 | ADDRESS *ctladdr; |
---|
2508 | time_t xstart; |
---|
2509 | { |
---|
2510 | register int r; |
---|
2511 | int rstat; |
---|
2512 | int xstat; |
---|
2513 | int timeout; |
---|
2514 | char *enhsc; |
---|
2515 | |
---|
2516 | /* |
---|
2517 | ** Check if connection is gone, if so |
---|
2518 | ** it's a tempfail and we use mci_errno |
---|
2519 | ** for the reason. |
---|
2520 | */ |
---|
2521 | |
---|
2522 | if (mci->mci_state == MCIS_CLOSED) |
---|
2523 | { |
---|
2524 | errno = mci->mci_errno; |
---|
2525 | return EX_TEMPFAIL; |
---|
2526 | } |
---|
2527 | |
---|
2528 | enhsc = NULL; |
---|
2529 | |
---|
2530 | /* |
---|
2531 | ** Send the data. |
---|
2532 | ** First send the command and check that it is ok. |
---|
2533 | ** Then send the data (if there are valid recipients). |
---|
2534 | ** Follow it up with a dot to terminate. |
---|
2535 | ** Finally get the results of the transaction. |
---|
2536 | */ |
---|
2537 | |
---|
2538 | /* send the command and check ok to proceed */ |
---|
2539 | smtpmessage("DATA", m, mci); |
---|
2540 | |
---|
2541 | #if PIPELINING |
---|
2542 | if (mci->mci_nextaddr != NULL) |
---|
2543 | { |
---|
2544 | char *oldto = e->e_to; |
---|
2545 | |
---|
2546 | /* pick up any pending RCPT responses for SMTP pipelining */ |
---|
2547 | while (mci->mci_nextaddr != NULL) |
---|
2548 | { |
---|
2549 | int r; |
---|
2550 | |
---|
2551 | e->e_to = mci->mci_nextaddr->q_paddr; |
---|
2552 | r = smtprcptstat(mci->mci_nextaddr, m, mci, e); |
---|
2553 | if (r != EX_OK) |
---|
2554 | { |
---|
2555 | markfailure(e, mci->mci_nextaddr, mci, r, |
---|
2556 | false); |
---|
2557 | giveresponse(r, mci->mci_nextaddr->q_status, m, |
---|
2558 | mci, ctladdr, xstart, e, |
---|
2559 | mci->mci_nextaddr); |
---|
2560 | if (r == EX_TEMPFAIL) |
---|
2561 | mci->mci_nextaddr->q_state = QS_RETRY; |
---|
2562 | } |
---|
2563 | mci->mci_nextaddr = mci->mci_nextaddr->q_pchain; |
---|
2564 | } |
---|
2565 | e->e_to = oldto; |
---|
2566 | } |
---|
2567 | #endif /* PIPELINING */ |
---|
2568 | |
---|
2569 | /* now proceed with DATA phase */ |
---|
2570 | SmtpPhase = mci->mci_phase = "client DATA 354"; |
---|
2571 | mci->mci_state = MCIS_DATA; |
---|
2572 | sm_setproctitle(true, e, "%s %s: %s", |
---|
2573 | qid_printname(e), CurHostName, mci->mci_phase); |
---|
2574 | r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc); |
---|
2575 | if (r < 0 || REPLYTYPE(r) == 4) |
---|
2576 | { |
---|
2577 | if (r >= 0) |
---|
2578 | smtpquit(m, mci, e); |
---|
2579 | errno = mci->mci_errno; |
---|
2580 | return EX_TEMPFAIL; |
---|
2581 | } |
---|
2582 | else if (REPLYTYPE(r) == 5) |
---|
2583 | { |
---|
2584 | smtprset(m, mci, e); |
---|
2585 | #if PIPELINING |
---|
2586 | if (mci->mci_okrcpts <= 0) |
---|
2587 | return mci->mci_retryrcpt ? EX_TEMPFAIL |
---|
2588 | : EX_UNAVAILABLE; |
---|
2589 | #endif /* PIPELINING */ |
---|
2590 | return EX_UNAVAILABLE; |
---|
2591 | } |
---|
2592 | else if (REPLYTYPE(r) != 3) |
---|
2593 | { |
---|
2594 | if (LogLevel > 1) |
---|
2595 | { |
---|
2596 | sm_syslog(LOG_CRIT, e->e_id, |
---|
2597 | "%.100s: SMTP DATA-1 protocol error: %s", |
---|
2598 | CurHostName, |
---|
2599 | shortenstring(SmtpReplyBuffer, 403)); |
---|
2600 | } |
---|
2601 | smtprset(m, mci, e); |
---|
2602 | mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"), |
---|
2603 | SmtpReplyBuffer); |
---|
2604 | #if PIPELINING |
---|
2605 | if (mci->mci_okrcpts <= 0) |
---|
2606 | return mci->mci_retryrcpt ? EX_TEMPFAIL |
---|
2607 | : EX_PROTOCOL; |
---|
2608 | #endif /* PIPELINING */ |
---|
2609 | return EX_PROTOCOL; |
---|
2610 | } |
---|
2611 | |
---|
2612 | #if PIPELINING |
---|
2613 | if (mci->mci_okrcpts > 0) |
---|
2614 | { |
---|
2615 | #endif /* PIPELINING */ |
---|
2616 | |
---|
2617 | /* |
---|
2618 | ** Set timeout around data writes. Make it at least large |
---|
2619 | ** enough for DNS timeouts on all recipients plus some fudge |
---|
2620 | ** factor. The main thing is that it should not be infinite. |
---|
2621 | */ |
---|
2622 | |
---|
2623 | if (tTd(18, 101)) |
---|
2624 | { |
---|
2625 | /* simulate a DATA timeout */ |
---|
2626 | timeout = 10; |
---|
2627 | } |
---|
2628 | else |
---|
2629 | timeout = DATA_PROGRESS_TIMEOUT * 1000; |
---|
2630 | sm_io_setinfo(mci->mci_out, SM_IO_WHAT_TIMEOUT, &timeout); |
---|
2631 | |
---|
2632 | |
---|
2633 | /* |
---|
2634 | ** Output the actual message. |
---|
2635 | */ |
---|
2636 | |
---|
2637 | if (!(*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER)) |
---|
2638 | goto writeerr; |
---|
2639 | |
---|
2640 | if (tTd(18, 101)) |
---|
2641 | { |
---|
2642 | /* simulate a DATA timeout */ |
---|
2643 | (void) sleep(2); |
---|
2644 | } |
---|
2645 | |
---|
2646 | if (!(*e->e_putbody)(mci, e, NULL)) |
---|
2647 | goto writeerr; |
---|
2648 | |
---|
2649 | /* |
---|
2650 | ** Cleanup after sending message. |
---|
2651 | */ |
---|
2652 | |
---|
2653 | |
---|
2654 | #if PIPELINING |
---|
2655 | } |
---|
2656 | #endif /* PIPELINING */ |
---|
2657 | |
---|
2658 | #if _FFR_CATCH_BROKEN_MTAS |
---|
2659 | if (sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0) |
---|
2660 | { |
---|
2661 | /* terminate the message */ |
---|
2662 | (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s", |
---|
2663 | m->m_eol); |
---|
2664 | if (TrafficLogFile != NULL) |
---|
2665 | (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT, |
---|
2666 | "%05d >>> .\n", (int) CurrentPid); |
---|
2667 | if (Verbose) |
---|
2668 | nmessage(">>> ."); |
---|
2669 | |
---|
2670 | sm_syslog(LOG_CRIT, e->e_id, |
---|
2671 | "%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot", |
---|
2672 | CurHostName); |
---|
2673 | mci->mci_errno = EIO; |
---|
2674 | mci->mci_state = MCIS_ERROR; |
---|
2675 | mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL); |
---|
2676 | smtpquit(m, mci, e); |
---|
2677 | return EX_PROTOCOL; |
---|
2678 | } |
---|
2679 | #endif /* _FFR_CATCH_BROKEN_MTAS */ |
---|
2680 | |
---|
2681 | if (sm_io_error(mci->mci_out)) |
---|
2682 | { |
---|
2683 | /* error during processing -- don't send the dot */ |
---|
2684 | mci->mci_errno = EIO; |
---|
2685 | mci->mci_state = MCIS_ERROR; |
---|
2686 | mci_setstat(mci, EX_IOERR, "4.4.2", NULL); |
---|
2687 | smtpquit(m, mci, e); |
---|
2688 | return EX_IOERR; |
---|
2689 | } |
---|
2690 | |
---|
2691 | /* terminate the message */ |
---|
2692 | if (sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s", m->m_eol) == |
---|
2693 | SM_IO_EOF) |
---|
2694 | goto writeerr; |
---|
2695 | if (TrafficLogFile != NULL) |
---|
2696 | (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT, |
---|
2697 | "%05d >>> .\n", (int) CurrentPid); |
---|
2698 | if (Verbose) |
---|
2699 | nmessage(">>> ."); |
---|
2700 | |
---|
2701 | /* check for the results of the transaction */ |
---|
2702 | SmtpPhase = mci->mci_phase = "client DATA status"; |
---|
2703 | sm_setproctitle(true, e, "%s %s: %s", qid_printname(e), |
---|
2704 | CurHostName, mci->mci_phase); |
---|
2705 | if (bitnset(M_LMTP, m->m_flags)) |
---|
2706 | return EX_OK; |
---|
2707 | r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc); |
---|
2708 | if (r < 0) |
---|
2709 | return EX_TEMPFAIL; |
---|
2710 | mci->mci_state = MCIS_OPEN; |
---|
2711 | xstat = EX_NOTSTICKY; |
---|
2712 | if (r == 452) |
---|
2713 | rstat = EX_TEMPFAIL; |
---|
2714 | else if (REPLYTYPE(r) == 4) |
---|
2715 | rstat = xstat = EX_TEMPFAIL; |
---|
2716 | else if (REPLYTYPE(r) == 2) |
---|
2717 | rstat = xstat = EX_OK; |
---|
2718 | else if (REPLYCLASS(r) != 5) |
---|
2719 | rstat = xstat = EX_PROTOCOL; |
---|
2720 | else if (REPLYTYPE(r) == 5) |
---|
2721 | rstat = EX_UNAVAILABLE; |
---|
2722 | else |
---|
2723 | rstat = EX_PROTOCOL; |
---|
2724 | mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)), |
---|
2725 | SmtpReplyBuffer); |
---|
2726 | if (bitset(MCIF_ENHSTAT, mci->mci_flags) && |
---|
2727 | (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0) |
---|
2728 | r += 5; |
---|
2729 | else |
---|
2730 | r = 4; |
---|
2731 | e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]); |
---|
2732 | SmtpPhase = mci->mci_phase = "idle"; |
---|
2733 | sm_setproctitle(true, e, "%s: %s", CurHostName, mci->mci_phase); |
---|
2734 | if (rstat != EX_PROTOCOL) |
---|
2735 | return rstat; |
---|
2736 | if (LogLevel > 1) |
---|
2737 | { |
---|
2738 | sm_syslog(LOG_CRIT, e->e_id, |
---|
2739 | "%.100s: SMTP DATA-2 protocol error: %s", |
---|
2740 | CurHostName, |
---|
2741 | shortenstring(SmtpReplyBuffer, 403)); |
---|
2742 | } |
---|
2743 | return rstat; |
---|
2744 | |
---|
2745 | writeerr: |
---|
2746 | mci->mci_errno = errno; |
---|
2747 | mci->mci_state = MCIS_ERROR; |
---|
2748 | mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL); |
---|
2749 | |
---|
2750 | /* |
---|
2751 | ** If putbody() couldn't finish due to a timeout, |
---|
2752 | ** rewind it here in the timeout handler. See |
---|
2753 | ** comments at the end of putbody() for reasoning. |
---|
2754 | */ |
---|
2755 | |
---|
2756 | if (e->e_dfp != NULL) |
---|
2757 | (void) bfrewind(e->e_dfp); |
---|
2758 | |
---|
2759 | errno = mci->mci_errno; |
---|
2760 | syserr("451 4.4.1 timeout writing message to %s", CurHostName); |
---|
2761 | smtpquit(m, mci, e); |
---|
2762 | return EX_TEMPFAIL; |
---|
2763 | } |
---|
2764 | |
---|
2765 | /* |
---|
2766 | ** SMTPGETSTAT -- get status code from DATA in LMTP |
---|
2767 | ** |
---|
2768 | ** Parameters: |
---|
2769 | ** m -- the mailer to which we are sending the message. |
---|
2770 | ** mci -- the mailer connection structure. |
---|
2771 | ** e -- the current envelope. |
---|
2772 | ** |
---|
2773 | ** Returns: |
---|
2774 | ** The exit status corresponding to the reply code. |
---|
2775 | */ |
---|
2776 | |
---|
2777 | int |
---|
2778 | smtpgetstat(m, mci, e) |
---|
2779 | MAILER *m; |
---|
2780 | MCI *mci; |
---|
2781 | ENVELOPE *e; |
---|
2782 | { |
---|
2783 | int r; |
---|
2784 | int off; |
---|
2785 | int status, xstat; |
---|
2786 | char *enhsc; |
---|
2787 | |
---|
2788 | enhsc = NULL; |
---|
2789 | |
---|
2790 | /* check for the results of the transaction */ |
---|
2791 | r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc); |
---|
2792 | if (r < 0) |
---|
2793 | return EX_TEMPFAIL; |
---|
2794 | xstat = EX_NOTSTICKY; |
---|
2795 | if (REPLYTYPE(r) == 4) |
---|
2796 | status = EX_TEMPFAIL; |
---|
2797 | else if (REPLYTYPE(r) == 2) |
---|
2798 | status = xstat = EX_OK; |
---|
2799 | else if (REPLYCLASS(r) != 5) |
---|
2800 | status = xstat = EX_PROTOCOL; |
---|
2801 | else if (REPLYTYPE(r) == 5) |
---|
2802 | status = EX_UNAVAILABLE; |
---|
2803 | else |
---|
2804 | status = EX_PROTOCOL; |
---|
2805 | if (bitset(MCIF_ENHSTAT, mci->mci_flags) && |
---|
2806 | (off = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0) |
---|
2807 | off += 5; |
---|
2808 | else |
---|
2809 | off = 4; |
---|
2810 | e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[off]); |
---|
2811 | mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)), SmtpReplyBuffer); |
---|
2812 | if (LogLevel > 1 && status == EX_PROTOCOL) |
---|
2813 | { |
---|
2814 | sm_syslog(LOG_CRIT, e->e_id, |
---|
2815 | "%.100s: SMTP DATA-3 protocol error: %s", |
---|
2816 | CurHostName, |
---|
2817 | shortenstring(SmtpReplyBuffer, 403)); |
---|
2818 | } |
---|
2819 | return status; |
---|
2820 | } |
---|
2821 | /* |
---|
2822 | ** SMTPQUIT -- close the SMTP connection. |
---|
2823 | ** |
---|
2824 | ** Parameters: |
---|
2825 | ** m -- a pointer to the mailer. |
---|
2826 | ** mci -- the mailer connection information. |
---|
2827 | ** e -- the current envelope. |
---|
2828 | ** |
---|
2829 | ** Returns: |
---|
2830 | ** none. |
---|
2831 | ** |
---|
2832 | ** Side Effects: |
---|
2833 | ** sends the final protocol and closes the connection. |
---|
2834 | */ |
---|
2835 | |
---|
2836 | void |
---|
2837 | smtpquit(m, mci, e) |
---|
2838 | register MAILER *m; |
---|
2839 | register MCI *mci; |
---|
2840 | ENVELOPE *e; |
---|
2841 | { |
---|
2842 | bool oldSuprErrs = SuprErrs; |
---|
2843 | int rcode; |
---|
2844 | char *oldcurhost; |
---|
2845 | |
---|
2846 | if (mci->mci_state == MCIS_CLOSED) |
---|
2847 | return; |
---|
2848 | |
---|
2849 | oldcurhost = CurHostName; |
---|
2850 | CurHostName = mci->mci_host; /* XXX UGLY XXX */ |
---|
2851 | if (CurHostName == NULL) |
---|
2852 | CurHostName = MyHostName; |
---|
2853 | |
---|
2854 | #if PIPELINING |
---|
2855 | mci->mci_okrcpts = 0; |
---|
2856 | #endif /* PIPELINING */ |
---|
2857 | |
---|
2858 | /* |
---|
2859 | ** Suppress errors here -- we may be processing a different |
---|
2860 | ** job when we do the quit connection, and we don't want the |
---|
2861 | ** new job to be penalized for something that isn't it's |
---|
2862 | ** problem. |
---|
2863 | */ |
---|
2864 | |
---|
2865 | SuprErrs = true; |
---|
2866 | |
---|
2867 | /* send the quit message if we haven't gotten I/O error */ |
---|
2868 | if (mci->mci_state != MCIS_ERROR && |
---|
2869 | mci->mci_state != MCIS_QUITING) |
---|
2870 | { |
---|
2871 | SmtpPhase = "client QUIT"; |
---|
2872 | mci->mci_state = MCIS_QUITING; |
---|
2873 | smtpmessage("QUIT", m, mci); |
---|
2874 | (void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL); |
---|
2875 | SuprErrs = oldSuprErrs; |
---|
2876 | if (mci->mci_state == MCIS_CLOSED) |
---|
2877 | goto end; |
---|
2878 | } |
---|
2879 | |
---|
2880 | /* now actually close the connection and pick up the zombie */ |
---|
2881 | rcode = endmailer(mci, e, NULL); |
---|
2882 | if (rcode != EX_OK) |
---|
2883 | { |
---|
2884 | char *mailer = NULL; |
---|
2885 | |
---|
2886 | if (mci->mci_mailer != NULL && |
---|
2887 | mci->mci_mailer->m_name != NULL) |
---|
2888 | mailer = mci->mci_mailer->m_name; |
---|
2889 | |
---|
2890 | /* look for naughty mailers */ |
---|
2891 | sm_syslog(LOG_ERR, e->e_id, |
---|
2892 | "smtpquit: mailer%s%s exited with exit value %d", |
---|
2893 | mailer == NULL ? "" : " ", |
---|
2894 | mailer == NULL ? "" : mailer, |
---|
2895 | rcode); |
---|
2896 | } |
---|
2897 | |
---|
2898 | SuprErrs = oldSuprErrs; |
---|
2899 | |
---|
2900 | end: |
---|
2901 | CurHostName = oldcurhost; |
---|
2902 | return; |
---|
2903 | } |
---|
2904 | /* |
---|
2905 | ** SMTPRSET -- send a RSET (reset) command |
---|
2906 | ** |
---|
2907 | ** Parameters: |
---|
2908 | ** m -- a pointer to the mailer. |
---|
2909 | ** mci -- the mailer connection information. |
---|
2910 | ** e -- the current envelope. |
---|
2911 | ** |
---|
2912 | ** Returns: |
---|
2913 | ** none. |
---|
2914 | ** |
---|
2915 | ** Side Effects: |
---|
2916 | ** closes the connection if there is no reply to RSET. |
---|
2917 | */ |
---|
2918 | |
---|
2919 | void |
---|
2920 | smtprset(m, mci, e) |
---|
2921 | register MAILER *m; |
---|
2922 | register MCI *mci; |
---|
2923 | ENVELOPE *e; |
---|
2924 | { |
---|
2925 | int r; |
---|
2926 | |
---|
2927 | CurHostName = mci->mci_host; /* XXX UGLY XXX */ |
---|
2928 | if (CurHostName == NULL) |
---|
2929 | CurHostName = MyHostName; |
---|
2930 | |
---|
2931 | #if PIPELINING |
---|
2932 | mci->mci_okrcpts = 0; |
---|
2933 | #endif /* PIPELINING */ |
---|
2934 | |
---|
2935 | /* |
---|
2936 | ** Check if connection is gone, if so |
---|
2937 | ** it's a tempfail and we use mci_errno |
---|
2938 | ** for the reason. |
---|
2939 | */ |
---|
2940 | |
---|
2941 | if (mci->mci_state == MCIS_CLOSED) |
---|
2942 | { |
---|
2943 | errno = mci->mci_errno; |
---|
2944 | return; |
---|
2945 | } |
---|
2946 | |
---|
2947 | SmtpPhase = "client RSET"; |
---|
2948 | smtpmessage("RSET", m, mci); |
---|
2949 | r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL); |
---|
2950 | if (r < 0) |
---|
2951 | return; |
---|
2952 | |
---|
2953 | /* |
---|
2954 | ** Any response is deemed to be acceptable. |
---|
2955 | ** The standard does not state the proper action |
---|
2956 | ** to take when a value other than 250 is received. |
---|
2957 | ** |
---|
2958 | ** However, if 421 is returned for the RSET, leave |
---|
2959 | ** mci_state as MCIS_SSD (set in reply()). |
---|
2960 | */ |
---|
2961 | |
---|
2962 | if (mci->mci_state != MCIS_SSD) |
---|
2963 | mci->mci_state = MCIS_OPEN; |
---|
2964 | } |
---|
2965 | /* |
---|
2966 | ** SMTPPROBE -- check the connection state |
---|
2967 | ** |
---|
2968 | ** Parameters: |
---|
2969 | ** mci -- the mailer connection information. |
---|
2970 | ** |
---|
2971 | ** Returns: |
---|
2972 | ** none. |
---|
2973 | ** |
---|
2974 | ** Side Effects: |
---|
2975 | ** closes the connection if there is no reply to RSET. |
---|
2976 | */ |
---|
2977 | |
---|
2978 | int |
---|
2979 | smtpprobe(mci) |
---|
2980 | register MCI *mci; |
---|
2981 | { |
---|
2982 | int r; |
---|
2983 | MAILER *m = mci->mci_mailer; |
---|
2984 | ENVELOPE *e; |
---|
2985 | extern ENVELOPE BlankEnvelope; |
---|
2986 | |
---|
2987 | CurHostName = mci->mci_host; /* XXX UGLY XXX */ |
---|
2988 | if (CurHostName == NULL) |
---|
2989 | CurHostName = MyHostName; |
---|
2990 | |
---|
2991 | e = &BlankEnvelope; |
---|
2992 | SmtpPhase = "client probe"; |
---|
2993 | smtpmessage("RSET", m, mci); |
---|
2994 | r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL); |
---|
2995 | if (REPLYTYPE(r) != 2) |
---|
2996 | smtpquit(m, mci, e); |
---|
2997 | return r; |
---|
2998 | } |
---|
2999 | /* |
---|
3000 | ** REPLY -- read arpanet reply |
---|
3001 | ** |
---|
3002 | ** Parameters: |
---|
3003 | ** m -- the mailer we are reading the reply from. |
---|
3004 | ** mci -- the mailer connection info structure. |
---|
3005 | ** e -- the current envelope. |
---|
3006 | ** timeout -- the timeout for reads. |
---|
3007 | ** pfunc -- processing function called on each line of response. |
---|
3008 | ** If null, no special processing is done. |
---|
3009 | ** enhstat -- optional, returns enhanced error code string (if set) |
---|
3010 | ** |
---|
3011 | ** Returns: |
---|
3012 | ** reply code it reads. |
---|
3013 | ** |
---|
3014 | ** Side Effects: |
---|
3015 | ** flushes the mail file. |
---|
3016 | */ |
---|
3017 | |
---|
3018 | int |
---|
3019 | reply(m, mci, e, timeout, pfunc, enhstat) |
---|
3020 | MAILER *m; |
---|
3021 | MCI *mci; |
---|
3022 | ENVELOPE *e; |
---|
3023 | time_t timeout; |
---|
3024 | void (*pfunc)(); |
---|
3025 | char **enhstat; |
---|
3026 | { |
---|
3027 | register char *bufp; |
---|
3028 | register int r; |
---|
3029 | bool firstline = true; |
---|
3030 | char junkbuf[MAXLINE]; |
---|
3031 | static char enhstatcode[ENHSCLEN]; |
---|
3032 | int save_errno; |
---|
3033 | |
---|
3034 | /* |
---|
3035 | ** Flush the output before reading response. |
---|
3036 | ** |
---|
3037 | ** For SMTP pipelining, it would be better if we didn't do |
---|
3038 | ** this if there was already data waiting to be read. But |
---|
3039 | ** to do it properly means pushing it to the I/O library, |
---|
3040 | ** since it really needs to be done below the buffer layer. |
---|
3041 | */ |
---|
3042 | |
---|
3043 | if (mci->mci_out != NULL) |
---|
3044 | (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT); |
---|
3045 | |
---|
3046 | if (tTd(18, 1)) |
---|
3047 | sm_dprintf("reply\n"); |
---|
3048 | |
---|
3049 | /* |
---|
3050 | ** Read the input line, being careful not to hang. |
---|
3051 | */ |
---|
3052 | |
---|
3053 | bufp = SmtpReplyBuffer; |
---|
3054 | for (;;) |
---|
3055 | { |
---|
3056 | register char *p; |
---|
3057 | |
---|
3058 | /* actually do the read */ |
---|
3059 | if (e->e_xfp != NULL) /* for debugging */ |
---|
3060 | (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT); |
---|
3061 | |
---|
3062 | /* if we are in the process of closing just give the code */ |
---|
3063 | if (mci->mci_state == MCIS_CLOSED) |
---|
3064 | return SMTPCLOSING; |
---|
3065 | |
---|
3066 | /* don't try to read from a non-existant fd */ |
---|
3067 | if (mci->mci_in == NULL) |
---|
3068 | { |
---|
3069 | if (mci->mci_errno == 0) |
---|
3070 | mci->mci_errno = EBADF; |
---|
3071 | |
---|
3072 | /* errors on QUIT should be ignored */ |
---|
3073 | if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0) |
---|
3074 | { |
---|
3075 | errno = mci->mci_errno; |
---|
3076 | return -1; |
---|
3077 | } |
---|
3078 | mci->mci_state = MCIS_ERROR; |
---|
3079 | smtpquit(m, mci, e); |
---|
3080 | errno = mci->mci_errno; |
---|
3081 | return -1; |
---|
3082 | } |
---|
3083 | |
---|
3084 | if (mci->mci_out != NULL) |
---|
3085 | (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT); |
---|
3086 | |
---|
3087 | /* get the line from the other side */ |
---|
3088 | p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase); |
---|
3089 | save_errno = errno; |
---|
3090 | mci->mci_lastuse = curtime(); |
---|
3091 | |
---|
3092 | if (p == NULL) |
---|
3093 | { |
---|
3094 | bool oldholderrs; |
---|
3095 | extern char MsgBuf[]; |
---|
3096 | |
---|
3097 | /* errors on QUIT should be ignored */ |
---|
3098 | if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0) |
---|
3099 | return -1; |
---|
3100 | |
---|
3101 | /* if the remote end closed early, fake an error */ |
---|
3102 | errno = save_errno; |
---|
3103 | if (errno == 0) |
---|
3104 | { |
---|
3105 | (void) sm_snprintf(SmtpReplyBuffer, |
---|
3106 | sizeof SmtpReplyBuffer, |
---|
3107 | "421 4.4.1 Connection reset by %s", |
---|
3108 | CURHOSTNAME); |
---|
3109 | #ifdef ECONNRESET |
---|
3110 | errno = ECONNRESET; |
---|
3111 | #else /* ECONNRESET */ |
---|
3112 | errno = EPIPE; |
---|
3113 | #endif /* ECONNRESET */ |
---|
3114 | } |
---|
3115 | |
---|
3116 | mci->mci_errno = errno; |
---|
3117 | oldholderrs = HoldErrs; |
---|
3118 | HoldErrs = true; |
---|
3119 | usrerr("451 4.4.1 reply: read error from %s", |
---|
3120 | CURHOSTNAME); |
---|
3121 | mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf); |
---|
3122 | |
---|
3123 | /* if debugging, pause so we can see state */ |
---|
3124 | if (tTd(18, 100)) |
---|
3125 | (void) pause(); |
---|
3126 | mci->mci_state = MCIS_ERROR; |
---|
3127 | smtpquit(m, mci, e); |
---|
3128 | #if XDEBUG |
---|
3129 | { |
---|
3130 | char wbuf[MAXLINE]; |
---|
3131 | |
---|
3132 | p = wbuf; |
---|
3133 | if (e->e_to != NULL) |
---|
3134 | { |
---|
3135 | (void) sm_snprintf(p, |
---|
3136 | SPACELEFT(wbuf, p), |
---|
3137 | "%s... ", |
---|
3138 | shortenstring(e->e_to, MAXSHORTSTR)); |
---|
3139 | p += strlen(p); |
---|
3140 | } |
---|
3141 | (void) sm_snprintf(p, SPACELEFT(wbuf, p), |
---|
3142 | "reply(%.100s) during %s", |
---|
3143 | CURHOSTNAME, SmtpPhase); |
---|
3144 | checkfd012(wbuf); |
---|
3145 | } |
---|
3146 | #endif /* XDEBUG */ |
---|
3147 | HoldErrs = oldholderrs; |
---|
3148 | errno = save_errno; |
---|
3149 | return -1; |
---|
3150 | } |
---|
3151 | fixcrlf(bufp, true); |
---|
3152 | |
---|
3153 | /* EHLO failure is not a real error */ |
---|
3154 | if (e->e_xfp != NULL && (bufp[0] == '4' || |
---|
3155 | (bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0))) |
---|
3156 | { |
---|
3157 | /* serious error -- log the previous command */ |
---|
3158 | if (SmtpNeedIntro) |
---|
3159 | { |
---|
3160 | /* inform user who we are chatting with */ |
---|
3161 | (void) sm_io_fprintf(CurEnv->e_xfp, |
---|
3162 | SM_TIME_DEFAULT, |
---|
3163 | "... while talking to %s:\n", |
---|
3164 | CURHOSTNAME); |
---|
3165 | SmtpNeedIntro = false; |
---|
3166 | } |
---|
3167 | if (SmtpMsgBuffer[0] != '\0') |
---|
3168 | (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, |
---|
3169 | ">>> %s\n", SmtpMsgBuffer); |
---|
3170 | SmtpMsgBuffer[0] = '\0'; |
---|
3171 | |
---|
3172 | /* now log the message as from the other side */ |
---|
3173 | (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, |
---|
3174 | "<<< %s\n", bufp); |
---|
3175 | } |
---|
3176 | |
---|
3177 | /* display the input for verbose mode */ |
---|
3178 | if (Verbose) |
---|
3179 | nmessage("050 %s", bufp); |
---|
3180 | |
---|
3181 | /* ignore improperly formatted input */ |
---|
3182 | if (!ISSMTPREPLY(bufp)) |
---|
3183 | continue; |
---|
3184 | |
---|
3185 | if (bitset(MCIF_ENHSTAT, mci->mci_flags) && |
---|
3186 | enhstat != NULL && |
---|
3187 | extenhsc(bufp + 4, ' ', enhstatcode) > 0) |
---|
3188 | *enhstat = enhstatcode; |
---|
3189 | |
---|
3190 | /* process the line */ |
---|
3191 | if (pfunc != NULL) |
---|
3192 | (*pfunc)(bufp, firstline, m, mci, e); |
---|
3193 | |
---|
3194 | firstline = false; |
---|
3195 | |
---|
3196 | /* decode the reply code */ |
---|
3197 | r = atoi(bufp); |
---|
3198 | |
---|
3199 | /* extra semantics: 0xx codes are "informational" */ |
---|
3200 | if (r < 100) |
---|
3201 | continue; |
---|
3202 | |
---|
3203 | /* if no continuation lines, return this line */ |
---|
3204 | if (bufp[3] != '-') |
---|
3205 | break; |
---|
3206 | |
---|
3207 | /* first line of real reply -- ignore rest */ |
---|
3208 | bufp = junkbuf; |
---|
3209 | } |
---|
3210 | |
---|
3211 | /* |
---|
3212 | ** Now look at SmtpReplyBuffer -- only care about the first |
---|
3213 | ** line of the response from here on out. |
---|
3214 | */ |
---|
3215 | |
---|
3216 | /* save temporary failure messages for posterity */ |
---|
3217 | if (SmtpReplyBuffer[0] == '4') |
---|
3218 | (void) sm_strlcpy(SmtpError, SmtpReplyBuffer, sizeof SmtpError); |
---|
3219 | |
---|
3220 | /* reply code 421 is "Service Shutting Down" */ |
---|
3221 | if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD && |
---|
3222 | mci->mci_state != MCIS_QUITING) |
---|
3223 | { |
---|
3224 | /* send the quit protocol */ |
---|
3225 | mci->mci_state = MCIS_SSD; |
---|
3226 | smtpquit(m, mci, e); |
---|
3227 | } |
---|
3228 | |
---|
3229 | return r; |
---|
3230 | } |
---|
3231 | /* |
---|
3232 | ** SMTPMESSAGE -- send message to server |
---|
3233 | ** |
---|
3234 | ** Parameters: |
---|
3235 | ** f -- format |
---|
3236 | ** m -- the mailer to control formatting. |
---|
3237 | ** a, b, c -- parameters |
---|
3238 | ** |
---|
3239 | ** Returns: |
---|
3240 | ** none. |
---|
3241 | ** |
---|
3242 | ** Side Effects: |
---|
3243 | ** writes message to mci->mci_out. |
---|
3244 | */ |
---|
3245 | |
---|
3246 | /*VARARGS1*/ |
---|
3247 | void |
---|
3248 | #ifdef __STDC__ |
---|
3249 | smtpmessage(char *f, MAILER *m, MCI *mci, ...) |
---|
3250 | #else /* __STDC__ */ |
---|
3251 | smtpmessage(f, m, mci, va_alist) |
---|
3252 | char *f; |
---|
3253 | MAILER *m; |
---|
3254 | MCI *mci; |
---|
3255 | va_dcl |
---|
3256 | #endif /* __STDC__ */ |
---|
3257 | { |
---|
3258 | SM_VA_LOCAL_DECL |
---|
3259 | |
---|
3260 | SM_VA_START(ap, mci); |
---|
3261 | (void) sm_vsnprintf(SmtpMsgBuffer, sizeof SmtpMsgBuffer, f, ap); |
---|
3262 | SM_VA_END(ap); |
---|
3263 | |
---|
3264 | if (tTd(18, 1) || Verbose) |
---|
3265 | nmessage(">>> %s", SmtpMsgBuffer); |
---|
3266 | if (TrafficLogFile != NULL) |
---|
3267 | (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT, |
---|
3268 | "%05d >>> %s\n", (int) CurrentPid, |
---|
3269 | SmtpMsgBuffer); |
---|
3270 | if (mci->mci_out != NULL) |
---|
3271 | { |
---|
3272 | (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s%s", |
---|
3273 | SmtpMsgBuffer, m == NULL ? "\r\n" |
---|
3274 | : m->m_eol); |
---|
3275 | } |
---|
3276 | else if (tTd(18, 1)) |
---|
3277 | { |
---|
3278 | sm_dprintf("smtpmessage: NULL mci_out\n"); |
---|
3279 | } |
---|
3280 | } |
---|