Commit | Line | Data |
---|---|---|
b2a81223 | 1 | /* |
dc45ba8c | 2 | * Copyright (c) 1983 Eric P. Allman |
24634489 KB |
3 | * Copyright (c) 1988, 1993 |
4 | * The Regents of the University of California. All rights reserved. | |
bee79b64 | 5 | * |
417f7a11 | 6 | * %sccs.include.redist.c% |
bee79b64 KB |
7 | */ |
8 | ||
9 | # include "sendmail.h" | |
b2a81223 | 10 | |
bee79b64 KB |
11 | #ifndef lint |
12 | #ifdef SMTP | |
d51d925d | 13 | static char sccsid[] = "@(#)srvrsmtp.c 8.50 (Berkeley) %G% (with SMTP)"; |
bee79b64 | 14 | #else |
d51d925d | 15 | static char sccsid[] = "@(#)srvrsmtp.c 8.50 (Berkeley) %G% (without SMTP)"; |
bee79b64 KB |
16 | #endif |
17 | #endif /* not lint */ | |
b2a81223 | 18 | |
e6f08ab1 | 19 | # include <errno.h> |
6b861048 | 20 | |
bee79b64 | 21 | # ifdef SMTP |
d727056e | 22 | |
6b861048 EA |
23 | /* |
24 | ** SMTP -- run the SMTP protocol. | |
25 | ** | |
26 | ** Parameters: | |
27 | ** none. | |
28 | ** | |
29 | ** Returns: | |
30 | ** never. | |
31 | ** | |
32 | ** Side Effects: | |
33 | ** Reads commands from the input channel and processes | |
34 | ** them. | |
35 | */ | |
36 | ||
37 | struct cmd | |
38 | { | |
39 | char *cmdname; /* command name */ | |
40 | int cmdcode; /* internal code, see below */ | |
41 | }; | |
42 | ||
43 | /* values for cmdcode */ | |
44 | # define CMDERROR 0 /* bad command */ | |
45 | # define CMDMAIL 1 /* mail -- designate sender */ | |
4a4ebe09 | 46 | # define CMDRCPT 2 /* rcpt -- designate recipient */ |
6b861048 | 47 | # define CMDDATA 3 /* data -- send message text */ |
7d7fdf93 | 48 | # define CMDHOPS 4 /* hops -- specify hop count */ |
e6f08ab1 EA |
49 | # define CMDRSET 4 /* rset -- reset state */ |
50 | # define CMDVRFY 5 /* vrfy -- verify address */ | |
8f48def8 | 51 | # define CMDEXPN 6 /* expn -- expand address */ |
e6f08ab1 EA |
52 | # define CMDNOOP 7 /* noop -- do nothing */ |
53 | # define CMDQUIT 8 /* quit -- close connection and die */ | |
54 | # define CMDHELO 9 /* helo -- be polite */ | |
8f48def8 | 55 | # define CMDHELP 10 /* help -- give usage info */ |
34f2d20e | 56 | # define CMDEHLO 11 /* ehlo -- extended helo (RFC 1425) */ |
8f48def8 EA |
57 | /* non-standard commands */ |
58 | # define CMDONEX 16 /* onex -- sending one transaction only */ | |
59 | # define CMDVERB 17 /* verb -- go into verbose mode */ | |
3e8f46ad EA |
60 | /* use this to catch and log "door handle" attempts on your system */ |
61 | # define CMDLOGBOGUS 23 /* bogus command that should be logged */ | |
ab4889ea | 62 | /* debugging-only commands, only enabled if SMTPDEBUG is defined */ |
8f48def8 EA |
63 | # define CMDDBGQSHOW 24 /* showq -- show send queue */ |
64 | # define CMDDBGDEBUG 25 /* debug -- set debug mode */ | |
6b861048 EA |
65 | |
66 | static struct cmd CmdTab[] = | |
67 | { | |
68 | "mail", CMDMAIL, | |
4a4ebe09 | 69 | "rcpt", CMDRCPT, |
6b861048 | 70 | "data", CMDDATA, |
6b861048 EA |
71 | "rset", CMDRSET, |
72 | "vrfy", CMDVRFY, | |
8f48def8 | 73 | "expn", CMDEXPN, |
633a2e02 | 74 | "expn", CMDVRFY, |
6b861048 EA |
75 | "help", CMDHELP, |
76 | "noop", CMDNOOP, | |
77 | "quit", CMDQUIT, | |
4a4ebe09 | 78 | "helo", CMDHELO, |
34f2d20e | 79 | "ehlo", CMDEHLO, |
e8ad767d | 80 | "verb", CMDVERB, |
8fe4fb9b | 81 | "onex", CMDONEX, |
7d7fdf93 | 82 | "hops", CMDHOPS, |
ab4889ea MK |
83 | /* |
84 | * remaining commands are here only | |
85 | * to trap and log attempts to use them | |
86 | */ | |
e6f08ab1 | 87 | "showq", CMDDBGQSHOW, |
ad20d67b | 88 | "debug", CMDDBGDEBUG, |
3e8f46ad | 89 | "wiz", CMDLOGBOGUS, |
6b861048 EA |
90 | NULL, CMDERROR, |
91 | }; | |
92 | ||
7338e3d4 | 93 | bool OneXact = FALSE; /* one xaction only this run */ |
c9af7ef4 | 94 | char *CurSmtpClient; /* who's at the other end of channel */ |
1972fb40 | 95 | |
e6cb9fc4 | 96 | static char *skipword(); |
90f34501 EA |
97 | extern char RealUserName[]; |
98 | ||
e6cb9fc4 | 99 | |
f6603f3b EA |
100 | #define MAXBADCOMMANDS 25 /* maximum number of bad commands */ |
101 | ||
a4076aed EA |
102 | smtp(e) |
103 | register ENVELOPE *e; | |
6b861048 | 104 | { |
6b861048 | 105 | register char *p; |
e8ad767d | 106 | register struct cmd *c; |
6b861048 | 107 | char *cmd; |
abae7b2d EA |
108 | extern ADDRESS *sendto(); |
109 | ADDRESS *a; | |
6b861048 | 110 | |
abae7b2d | 111 | hasmail = FALSE; |
20219e3f | 112 | if (fileno(OutChannel) != fileno(stdout)) |
1f1cc003 EA |
113 | { |
114 | /* arrange for debugging output to go to remote host */ | |
20219e3f | 115 | (void) dup2(fileno(OutChannel), fileno(stdout)); |
1f1cc003 | 116 | } |
a4076aed | 117 | settime(e); |
7c8c5b90 EA |
118 | peerhostname = RealHostName; |
119 | if (peerhostname == NULL) | |
120 | peerhostname = "localhost"; | |
121 | CurHostName = peerhostname; | |
c9af7ef4 EA |
122 | CurSmtpClient = macvalue('_', e); |
123 | if (CurSmtpClient == NULL) | |
389c0d5e | 124 | CurSmtpClient = CurHostName; |
c9af7ef4 EA |
125 | |
126 | setproctitle("server %s startup", CurSmtpClient); | |
2bee003d | 127 | expand("\201e", inp, &inp[sizeof inp], e); |
53ae7b6c EA |
128 | if (BrokenSmtpPeers) |
129 | { | |
5f0e0cf2 EA |
130 | p = strchr(inp, '\n'); |
131 | if (p != NULL) | |
132 | *p = '\0'; | |
53ae7b6c EA |
133 | message("220 %s", inp); |
134 | } | |
135 | else | |
136 | { | |
fb617949 EA |
137 | char *q = inp; |
138 | ||
139 | while (q != NULL) | |
140 | { | |
5f0e0cf2 | 141 | p = strchr(q, '\n'); |
fb617949 EA |
142 | if (p != NULL) |
143 | *p++ = '\0'; | |
5f0e0cf2 EA |
144 | message("220-%s", q); |
145 | q = p; | |
fb617949 | 146 | } |
53ae7b6c EA |
147 | message("220 ESMTP spoken here"); |
148 | } | |
6fd6d536 | 149 | protocol = NULL; |
37444fe1 | 150 | sendinghost = macvalue('s', e); |
1c7897ef | 151 | gothello = FALSE; |
6fd6d536 | 152 | gotmail = FALSE; |
6b861048 EA |
153 | for (;;) |
154 | { | |
d344c0b7 | 155 | /* arrange for backout */ |
c583011b | 156 | if (setjmp(TopFrame) > 0) |
b8bf5eba | 157 | { |
c583011b EA |
158 | /* if() nesting is necessary for Cray UNICOS */ |
159 | if (InChild) | |
160 | { | |
161 | QuickAbort = FALSE; | |
162 | SuprErrs = TRUE; | |
163 | finis(); | |
164 | } | |
b8bf5eba | 165 | } |
d344c0b7 EA |
166 | QuickAbort = FALSE; |
167 | HoldErrs = FALSE; | |
f61c3c40 | 168 | LogUsrErrs = FALSE; |
4b72e6db | 169 | e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS); |
d344c0b7 | 170 | |
37eaaadb | 171 | /* setup for the read */ |
a4076aed | 172 | e->e_to = NULL; |
34d37b7d | 173 | Errors = 0; |
09eb49d8 | 174 | (void) fflush(stdout); |
37eaaadb | 175 | |
37eaaadb | 176 | /* read the input line */ |
8e948497 EA |
177 | SmtpPhase = "server cmd read"; |
178 | setproctitle("server %s cmd read", CurHostName); | |
179 | p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand, | |
180 | SmtpPhase); | |
37eaaadb | 181 | |
2439b900 | 182 | /* handle errors */ |
37eaaadb | 183 | if (p == NULL) |
6b861048 EA |
184 | { |
185 | /* end of file, just die */ | |
33cbaada | 186 | disconnect(1, e); |
b6edea3d | 187 | message("421 %s Lost input channel from %s", |
c9af7ef4 | 188 | MyHostName, CurSmtpClient); |
6d6f9196 | 189 | #ifdef LOG |
4b72e6db | 190 | if (LogLevel > (gotmail ? 1 : 19)) |
6d6f9196 | 191 | syslog(LOG_NOTICE, "lost input channel from %s", |
c9af7ef4 | 192 | CurSmtpClient); |
6d6f9196 | 193 | #endif |
074b0256 EA |
194 | if (InChild) |
195 | ExitStat = EX_QUIT; | |
6b861048 EA |
196 | finis(); |
197 | } | |
198 | ||
199 | /* clean up end of line */ | |
2768afe3 | 200 | fixcrlf(inp, TRUE); |
6b861048 | 201 | |
49086753 | 202 | /* echo command to transcript */ |
a4076aed EA |
203 | if (e->e_xfp != NULL) |
204 | fprintf(e->e_xfp, "<<< %s\n", inp); | |
49086753 | 205 | |
ade7da2a | 206 | if (e->e_id == NULL) |
e42c7981 | 207 | setproctitle("%s: %.80s", CurSmtpClient, inp); |
ade7da2a | 208 | else |
e42c7981 | 209 | setproctitle("%s %s: %.80s", e->e_id, CurSmtpClient, inp); |
ade7da2a | 210 | |
6b861048 | 211 | /* break off command */ |
2bee003d | 212 | for (p = inp; isascii(*p) && isspace(*p); p++) |
6b861048 | 213 | continue; |
f43b04ce | 214 | cmd = cmdbuf; |
2bee003d EA |
215 | while (*p != '\0' && |
216 | !(isascii(*p) && isspace(*p)) && | |
217 | cmd < &cmdbuf[sizeof cmdbuf - 2]) | |
a0225d08 EA |
218 | *cmd++ = *p++; |
219 | *cmd = '\0'; | |
6b861048 | 220 | |
a1a07282 | 221 | /* throw away leading whitespace */ |
2bee003d | 222 | while (isascii(*p) && isspace(*p)) |
a1a07282 EA |
223 | p++; |
224 | ||
6b861048 EA |
225 | /* decode command */ |
226 | for (c = CmdTab; c->cmdname != NULL; c++) | |
227 | { | |
ed73ef1d | 228 | if (!strcasecmp(c->cmdname, cmdbuf)) |
6b861048 EA |
229 | break; |
230 | } | |
231 | ||
5ae51cd5 EA |
232 | /* reset errors */ |
233 | errno = 0; | |
234 | ||
6b861048 EA |
235 | /* process command */ |
236 | switch (c->cmdcode) | |
237 | { | |
4a4ebe09 | 238 | case CMDHELO: /* hello -- introduce yourself */ |
34f2d20e EA |
239 | case CMDEHLO: /* extended hello */ |
240 | if (c->cmdcode == CMDEHLO) | |
241 | { | |
242 | protocol = "ESMTP"; | |
8e948497 | 243 | SmtpPhase = "server EHLO"; |
34f2d20e EA |
244 | } |
245 | else | |
246 | { | |
247 | protocol = "SMTP"; | |
8e948497 | 248 | SmtpPhase = "server HELO"; |
34f2d20e | 249 | } |
825424db EA |
250 | |
251 | /* check for valid domain name (re 1123 5.2.5) */ | |
252 | if (*p == '\0') | |
253 | { | |
254 | message("501 %s requires domain address", | |
255 | cmdbuf); | |
256 | break; | |
257 | } | |
258 | else | |
259 | { | |
260 | register char *q; | |
261 | ||
262 | for (q = p; *q != '\0'; q++) | |
263 | { | |
264 | if (!isascii(*q)) | |
265 | break; | |
266 | if (isalnum(*q)) | |
267 | continue; | |
268 | if (strchr("[].-_#", *q) == NULL) | |
269 | break; | |
270 | } | |
271 | if (*q != '\0') | |
272 | { | |
273 | message("501 Invalid domain name"); | |
274 | break; | |
275 | } | |
276 | } | |
277 | ||
37444fe1 | 278 | sendinghost = newstr(p); |
abae7b2d | 279 | message("250", "%s Hello %s, pleased to meet you", HostName, p); |
4a4ebe09 EA |
280 | break; |
281 | ||
6b861048 | 282 | case CMDMAIL: /* mail -- designate sender */ |
8e948497 | 283 | SmtpPhase = "server MAIL"; |
2e3062fe | 284 | |
8fe4fb9b | 285 | /* check for validity of this command */ |
94bc039a | 286 | if (!gothello) |
1c7897ef | 287 | { |
a9bac7a9 | 288 | /* set sending host to our known value */ |
37444fe1 | 289 | if (sendinghost == NULL) |
7c8c5b90 | 290 | sendinghost = peerhostname; |
a9bac7a9 | 291 | |
94bc039a | 292 | if (bitset(PRIV_NEEDMAILHELO, PrivacyFlags)) |
f967c46a | 293 | { |
94bc039a | 294 | message("503 Polite people say HELO first"); |
f967c46a EA |
295 | break; |
296 | } | |
1c7897ef | 297 | } |
3b87200d | 298 | if (gotmail) |
2768afe3 | 299 | { |
b6edea3d | 300 | message("503 Sender already specified"); |
4b72e6db EA |
301 | if (InChild) |
302 | finis(); | |
2768afe3 EA |
303 | break; |
304 | } | |
e6f08ab1 EA |
305 | if (InChild) |
306 | { | |
ab4889ea | 307 | errno = 0; |
b6edea3d | 308 | syserr("503 Nested MAIL command: MAIL %s", p); |
074b0256 | 309 | finis(); |
e6f08ab1 EA |
310 | } |
311 | ||
312 | /* fork a subprocess to process this command */ | |
a4076aed | 313 | if (runinchild("SMTP-MAIL", e) > 0) |
e6f08ab1 | 314 | break; |
8e5c6745 EA |
315 | if (!gothello) |
316 | { | |
317 | auth_warning(e, | |
7c8c5b90 EA |
318 | "Host %s didn't use HELO protocol", |
319 | peerhostname); | |
8e5c6745 | 320 | } |
3bb5d0e6 | 321 | #ifdef PICKY_HELO_CHECK |
7c8c5b90 EA |
322 | if (strcasecmp(sendinghost, peerhostname) != 0 && |
323 | (strcasecmp(peerhostname, "localhost") != 0 || | |
2d5db20a EA |
324 | strcasecmp(sendinghost, MyHostName) != 0)) |
325 | { | |
326 | auth_warning(e, "Host %s claimed to be %s", | |
7c8c5b90 | 327 | peerhostname, sendinghost); |
2d5db20a | 328 | } |
3bb5d0e6 | 329 | #endif |
2d5db20a | 330 | |
34f2d20e EA |
331 | if (protocol == NULL) |
332 | protocol = "SMTP"; | |
333 | define('r', protocol, e); | |
37444fe1 | 334 | define('s', sendinghost, e); |
a4076aed | 335 | initsys(e); |
fba945cd | 336 | nrcpts = 0; |
804e6d6d | 337 | e->e_flags |= EF_LOGSENDER; |
e42c7981 | 338 | setproctitle("%s %s: %.80s", e->e_id, CurSmtpClient, inp); |
e6f08ab1 EA |
339 | |
340 | /* child -- go do the processing */ | |
6b861048 EA |
341 | p = skipword(p, "from"); |
342 | if (p == NULL) | |
343 | break; | |
aa102c71 | 344 | if (setjmp(TopFrame) > 0) |
182c060a EA |
345 | { |
346 | /* this failed -- undo work */ | |
347 | if (InChild) | |
b8bf5eba EA |
348 | { |
349 | QuickAbort = FALSE; | |
350 | SuprErrs = TRUE; | |
fd57f063 | 351 | e->e_flags &= ~EF_FATALERRS; |
182c060a | 352 | finis(); |
b8bf5eba | 353 | } |
aa102c71 | 354 | break; |
182c060a | 355 | } |
aa102c71 | 356 | QuickAbort = TRUE; |
9e2cf26f EA |
357 | |
358 | /* must parse sender first */ | |
359 | delimptr = NULL; | |
4a2da288 | 360 | setsender(p, e, &delimptr, FALSE); |
9e2cf26f EA |
361 | p = delimptr; |
362 | if (p != NULL && *p != '\0') | |
363 | *p++ = '\0'; | |
364 | ||
90f34501 EA |
365 | /* check for possible spoofing */ |
366 | if (RealUid != 0 && OpMode == MD_SMTP && | |
2bade550 EA |
367 | !bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) && |
368 | strcmp(e->e_from.q_user, RealUserName) != 0) | |
90f34501 EA |
369 | { |
370 | auth_warning(e, "%s owned process doing -bs", | |
371 | RealUserName); | |
372 | } | |
373 | ||
9e2cf26f EA |
374 | /* now parse ESMTP arguments */ |
375 | msize = 0; | |
24e2a159 | 376 | while (p != NULL && *p != '\0') |
9e2cf26f EA |
377 | { |
378 | char *kp; | |
89346287 | 379 | char *vp = NULL; |
9e2cf26f EA |
380 | |
381 | /* locate the beginning of the keyword */ | |
382 | while (isascii(*p) && isspace(*p)) | |
383 | p++; | |
384 | if (*p == '\0') | |
385 | break; | |
386 | kp = p; | |
387 | ||
388 | /* skip to the value portion */ | |
389 | while (isascii(*p) && isalnum(*p) || *p == '-') | |
390 | p++; | |
391 | if (*p == '=') | |
392 | { | |
393 | *p++ = '\0'; | |
394 | vp = p; | |
395 | ||
396 | /* skip to the end of the value */ | |
397 | while (*p != '\0' && *p != ' ' && | |
398 | !(isascii(*p) && iscntrl(*p)) && | |
399 | *p != '=') | |
400 | p++; | |
401 | } | |
402 | ||
403 | if (*p != '\0') | |
404 | *p++ = '\0'; | |
405 | ||
406 | if (tTd(19, 1)) | |
24e2a159 | 407 | printf("MAIL: got arg %s=\"%s\"\n", kp, |
9e2cf26f EA |
408 | vp == NULL ? "<null>" : vp); |
409 | ||
410 | if (strcasecmp(kp, "size") == 0) | |
411 | { | |
96bfbc2c | 412 | if (vp == NULL) |
9e2cf26f EA |
413 | { |
414 | usrerr("501 SIZE requires a value"); | |
415 | /* NOTREACHED */ | |
416 | } | |
13b2cc41 EA |
417 | # ifdef __STDC__ |
418 | msize = strtoul(vp, (char **) NULL, 10); | |
419 | # else | |
420 | msize = strtol(vp, (char **) NULL, 10); | |
421 | # endif | |
9e2cf26f | 422 | } |
96bfbc2c EA |
423 | else if (strcasecmp(kp, "body") == 0) |
424 | { | |
425 | if (vp == NULL) | |
426 | { | |
427 | usrerr("501 BODY requires a value"); | |
428 | /* NOTREACHED */ | |
429 | } | |
df04c7c2 | 430 | e->e_bodytype = newstr(vp); |
96bfbc2c EA |
431 | if (strcasecmp(vp, "8bitmime") == 0) |
432 | { | |
c23930c0 | 433 | SevenBitInput = FALSE; |
96bfbc2c EA |
434 | } |
435 | else if (strcasecmp(vp, "7bit") == 0) | |
436 | { | |
c23930c0 | 437 | SevenBitInput = TRUE; |
96bfbc2c EA |
438 | } |
439 | else | |
440 | { | |
441 | usrerr("501 Unknown BODY type %s", | |
442 | vp); | |
df04c7c2 | 443 | /* NOTREACHED */ |
96bfbc2c | 444 | } |
96bfbc2c | 445 | } |
68d9129a EA |
446 | else if (strcasecmp(kp, "envid") == 0) |
447 | { | |
448 | if (vp == NULL) | |
449 | { | |
450 | usrerr("501 ENVID requires a value"); | |
451 | /* NOTREACHED */ | |
452 | } | |
453 | e->e_envid = newstr(vp); | |
454 | } | |
82e3dc75 EA |
455 | else if (strcasecmp(kp, "omts") == 0) |
456 | { | |
457 | if (vp == NULL) | |
458 | { | |
459 | usrerr("501 OMTS requires a value"); | |
460 | /* NOTREACHED */ | |
461 | } | |
462 | e->e_omts = newstr(vp); | |
463 | } | |
9e2cf26f EA |
464 | else |
465 | { | |
466 | usrerr("501 %s parameter unrecognized", kp); | |
467 | /* NOTREACHED */ | |
468 | } | |
469 | } | |
346fe280 EA |
470 | |
471 | if (MaxMessageSize > 0 && msize > MaxMessageSize) | |
472 | { | |
473 | usrerr("552 Message size exceeds fixed maximum message size (%ld)", | |
474 | MaxMessageSize); | |
475 | /* NOTREACHED */ | |
476 | } | |
9e2cf26f EA |
477 | |
478 | if (!enoughspace(msize)) | |
479 | { | |
480 | message("452 Insufficient disk space; try again later"); | |
481 | break; | |
482 | } | |
b6edea3d | 483 | message("250 Sender ok"); |
182c060a | 484 | gotmail = TRUE; |
506a2500 EA |
485 | |
486 | /* optimize: non-interactive, don't expand aliases */ | |
28a8a6ef | 487 | if (e->e_sendmode != SM_DELIVER) |
506a2500 EA |
488 | e->e_flags |= EF_VRFYONLY; |
489 | ||
6b861048 EA |
490 | break; |
491 | ||
4a4ebe09 | 492 | case CMDRCPT: /* rcpt -- designate recipient */ |
d7f41a7b EA |
493 | if (!gotmail) |
494 | { | |
495 | usrerr("503 Need MAIL before RCPT"); | |
496 | break; | |
497 | } | |
8e948497 | 498 | SmtpPhase = "server RCPT"; |
d344c0b7 | 499 | if (setjmp(TopFrame) > 0) |
9bfb75c1 | 500 | { |
a4076aed | 501 | e->e_flags &= ~EF_FATALERRS; |
d344c0b7 | 502 | break; |
9bfb75c1 | 503 | } |
d344c0b7 | 504 | QuickAbort = TRUE; |
f61c3c40 | 505 | LogUsrErrs = TRUE; |
6b861048 EA |
506 | p = skipword(p, "to"); |
507 | if (p == NULL) | |
508 | break; | |
abae7b2d EA |
509 | a = sendto(p, 1, (ADDRESS *) NULL, 0); |
510 | # ifdef DEBUG | |
511 | if (Debug > 1) | |
512 | printaddr(a, TRUE); | |
513 | # endif DEBUG | |
d344c0b7 EA |
514 | if (Errors != 0) |
515 | break; | |
516 | ||
517 | /* no errors during parsing, but might be a duplicate */ | |
a4076aed | 518 | e->e_to = p; |
d344c0b7 | 519 | if (!bitset(QBADADDR, a->q_flags)) |
fba945cd | 520 | { |
fe3849ea EA |
521 | message("250 Recipient ok%s", |
522 | bitset(QQUEUEUP, a->q_flags) ? | |
523 | " (will queue)" : ""); | |
fba945cd EA |
524 | nrcpts++; |
525 | } | |
d344c0b7 | 526 | else |
6b861048 | 527 | { |
d344c0b7 | 528 | /* punt -- should keep message in ADDRESS.... */ |
b6edea3d | 529 | message("550 Addressee unknown"); |
6b861048 | 530 | } |
a4076aed | 531 | e->e_to = NULL; |
6b861048 EA |
532 | break; |
533 | ||
534 | case CMDDATA: /* data -- text of mail */ | |
8e948497 | 535 | SmtpPhase = "server DATA"; |
3b87200d | 536 | if (!gotmail) |
4a4ebe09 | 537 | { |
b6edea3d | 538 | message("503 Need MAIL command"); |
4a4ebe09 EA |
539 | break; |
540 | } | |
fe3849ea | 541 | else if (nrcpts <= 0) |
6b861048 | 542 | { |
b6edea3d | 543 | message("503 Need RCPT (recipient)"); |
4a4ebe09 | 544 | break; |
6b861048 | 545 | } |
4a4ebe09 | 546 | |
959cf51d | 547 | /* check to see if we need to re-expand aliases */ |
fd57f063 EA |
548 | /* also reset QBADADDR on already-diagnosted addrs */ |
549 | doublequeue = FALSE; | |
959cf51d EA |
550 | for (a = e->e_sendqueue; a != NULL; a = a->q_next) |
551 | { | |
552 | if (bitset(QVERIFIED, a->q_flags)) | |
fd57f063 EA |
553 | { |
554 | /* need to re-expand aliases */ | |
555 | doublequeue = TRUE; | |
556 | } | |
557 | if (bitset(QBADADDR, a->q_flags)) | |
558 | { | |
559 | /* make this "go away" */ | |
560 | a->q_flags |= QDONTSEND; | |
561 | a->q_flags &= ~QBADADDR; | |
562 | } | |
959cf51d EA |
563 | } |
564 | ||
4a4ebe09 | 565 | /* collect the text of the message */ |
2e3062fe | 566 | SmtpPhase = "collect"; |
c23930c0 | 567 | collect(InChannel, TRUE, doublequeue, NULL, e); |
4f8e0a23 EA |
568 | if (Errors != 0) |
569 | goto abortmessage; | |
439fd0d9 EA |
570 | |
571 | /* from now on, we have to operate silently */ | |
4f8e0a23 | 572 | HoldErrs = TRUE; |
439fd0d9 | 573 | e->e_errormode = EM_MAIL; |
4a4ebe09 | 574 | |
8eedb496 EA |
575 | /* |
576 | ** Arrange to send to everyone. | |
577 | ** If sending to multiple people, mail back | |
578 | ** errors rather than reporting directly. | |
579 | ** In any case, don't mail back errors for | |
580 | ** anything that has happened up to | |
581 | ** now (the other end will do this). | |
bcf74f25 EA |
582 | ** Truncate our transcript -- the mail has gotten |
583 | ** to us successfully, and if we have | |
584 | ** to mail this back, it will be easier | |
585 | ** on the reader. | |
8eedb496 EA |
586 | ** Then send to everyone. |
587 | ** Finally give a reply code. If an error has | |
588 | ** already been given, don't mail a | |
589 | ** message back. | |
e6f08ab1 | 590 | ** We goose error returns by clearing error bit. |
8eedb496 EA |
591 | */ |
592 | ||
2e3062fe | 593 | SmtpPhase = "delivery"; |
a4076aed | 594 | e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); |
4aed8f3a | 595 | id = e->e_id; |
4a4ebe09 | 596 | |
439fd0d9 | 597 | if (doublequeue) |
2f6a8a78 | 598 | { |
439fd0d9 EA |
599 | /* make sure it is in the queue */ |
600 | queueup(e, TRUE, FALSE); | |
2f6a8a78 EA |
601 | } |
602 | else | |
a891bb7e | 603 | { |
439fd0d9 EA |
604 | /* send to all recipients */ |
605 | sendall(e, SM_DEFAULT); | |
606 | } | |
607 | e->e_to = NULL; | |
fba945cd | 608 | |
439fd0d9 EA |
609 | /* issue success message */ |
610 | message("250 %s Message accepted for delivery", id); | |
d0268270 | 611 | |
439fd0d9 EA |
612 | /* if we just queued, poke it */ |
613 | if (doublequeue && e->e_sendmode != SM_QUEUE) | |
614 | { | |
615 | extern pid_t dowork(); | |
616 | ||
617 | unlockqueue(e); | |
618 | (void) dowork(id, TRUE, TRUE, e); | |
a891bb7e EA |
619 | } |
620 | ||
6366413a EA |
621 | /* now make it really happen */ |
622 | if (!Verbose && e->e_sendmode != SM_QUEUE) | |
623 | dowork(id, TRUE, e); | |
624 | ||
fba945cd | 625 | abortmessage: |
e6f08ab1 EA |
626 | /* if in a child, pop back to our parent */ |
627 | if (InChild) | |
628 | finis(); | |
2e3062fe EA |
629 | |
630 | /* clean up a bit */ | |
3b87200d | 631 | gotmail = FALSE; |
a4076aed | 632 | dropenvelope(e); |
fda58daa | 633 | CurEnv = e = newenvelope(e, CurEnv); |
a4076aed | 634 | e->e_flags = BlankEnvelope.e_flags; |
6b861048 EA |
635 | break; |
636 | ||
637 | case CMDRSET: /* rset -- reset state */ | |
b6edea3d | 638 | message("250 Reset state"); |
80662ec6 | 639 | e->e_flags |= EF_CLRQUEUE; |
e6f08ab1 EA |
640 | if (InChild) |
641 | finis(); | |
3b87200d EA |
642 | |
643 | /* clean up a bit */ | |
644 | gotmail = FALSE; | |
645 | dropenvelope(e); | |
fda58daa | 646 | CurEnv = e = newenvelope(e, CurEnv); |
e6f08ab1 | 647 | break; |
6b861048 EA |
648 | |
649 | case CMDVRFY: /* vrfy -- verify address */ | |
8f48def8 EA |
650 | case CMDEXPN: /* expn -- expand address */ |
651 | vrfy = c->cmdcode == CMDVRFY; | |
652 | if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, | |
653 | PrivacyFlags)) | |
1c7897ef | 654 | { |
efae59f3 | 655 | if (vrfy) |
cb282b01 | 656 | message("252 Cannot VRFY user; try RCPT to attempt delivery (or try finger)"); |
efae59f3 | 657 | else |
c5b74eae | 658 | message("502 Sorry, we do not allow this operation"); |
c9af7ef4 EA |
659 | #ifdef LOG |
660 | if (LogLevel > 5) | |
661 | syslog(LOG_INFO, "%s: %s [rejected]", | |
662 | CurSmtpClient, inp); | |
663 | #endif | |
1c7897ef EA |
664 | break; |
665 | } | |
666 | else if (!gothello && | |
8f48def8 EA |
667 | bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO, |
668 | PrivacyFlags)) | |
1c7897ef | 669 | { |
b6edea3d | 670 | message("503 I demand that you introduce yourself first"); |
1c7897ef EA |
671 | break; |
672 | } | |
8f48def8 | 673 | if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) |
e6f08ab1 | 674 | break; |
01e8020e | 675 | #ifdef LOG |
68f7099c | 676 | if (LogLevel > 5) |
c9af7ef4 | 677 | syslog(LOG_INFO, "%s: %s", CurSmtpClient, inp); |
01e8020e | 678 | #endif |
abae7b2d | 679 | paddrtree(a); |
6b861048 EA |
680 | break; |
681 | ||
682 | case CMDHELP: /* help -- give user info */ | |
34d37b7d | 683 | help(p); |
6b861048 EA |
684 | break; |
685 | ||
686 | case CMDNOOP: /* noop -- do nothing */ | |
422d9f83 | 687 | message("250 OK"); |
6b861048 EA |
688 | break; |
689 | ||
690 | case CMDQUIT: /* quit -- leave mail */ | |
b6edea3d | 691 | message("221 %s closing connection", MyHostName); |
251126e0 | 692 | |
f6603f3b | 693 | doquit: |
251126e0 | 694 | /* avoid future 050 messages */ |
33cbaada | 695 | disconnect(1, e); |
251126e0 | 696 | |
e6f08ab1 EA |
697 | if (InChild) |
698 | ExitStat = EX_QUIT; | |
6b861048 EA |
699 | finis(); |
700 | ||
e8ad767d | 701 | case CMDVERB: /* set verbose mode */ |
de975e1b EA |
702 | if (bitset(PRIV_NOEXPN, PrivacyFlags)) |
703 | { | |
704 | /* this would give out the same info */ | |
705 | message("502 Verbose unavailable"); | |
706 | break; | |
707 | } | |
e8ad767d | 708 | Verbose = TRUE; |
8c8e8e94 | 709 | e->e_sendmode = SM_DELIVER; |
de975e1b | 710 | message("250 Verbose mode"); |
e8ad767d EA |
711 | break; |
712 | ||
8fe4fb9b | 713 | case CMDONEX: /* doing one transaction only */ |
7338e3d4 | 714 | OneXact = TRUE; |
de975e1b | 715 | message("250 Only one transaction"); |
8fe4fb9b EA |
716 | break; |
717 | ||
ab4889ea | 718 | # ifdef SMTPDEBUG |
e6f08ab1 | 719 | case CMDDBGQSHOW: /* show queues */ |
2654b031 | 720 | printf("Send Queue="); |
a4076aed | 721 | printaddr(e->e_sendqueue, TRUE); |
d4f42161 | 722 | break; |
09eb49d8 EA |
723 | |
724 | case CMDDBGDEBUG: /* set debug mode */ | |
9678c96d EA |
725 | tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); |
726 | tTflag(p); | |
b6edea3d | 727 | message("200 Debug set"); |
09eb49d8 EA |
728 | break; |
729 | ||
ab4889ea | 730 | # else /* not SMTPDEBUG */ |
ab4889ea MK |
731 | case CMDDBGQSHOW: /* show queues */ |
732 | case CMDDBGDEBUG: /* set debug mode */ | |
3e8f46ad EA |
733 | # endif /* SMTPDEBUG */ |
734 | case CMDLOGBOGUS: /* bogus command */ | |
2e15a2d8 | 735 | # ifdef LOG |
5973222c | 736 | if (LogLevel > 0) |
3e8f46ad | 737 | syslog(LOG_CRIT, |
68f7099c | 738 | "\"%s\" command from %s (%s)", |
7c8c5b90 | 739 | c->cmdname, peerhostname, |
3341995c | 740 | anynet_ntoa(&RealHostAddr)); |
2e15a2d8 | 741 | # endif |
ab4889ea | 742 | /* FALL THROUGH */ |
d4f42161 | 743 | |
6b861048 | 744 | case CMDERROR: /* unknown command */ |
f6603f3b EA |
745 | if (++badcommands > MAXBADCOMMANDS) |
746 | { | |
747 | message("421 %s Too many bad commands; closing connection", | |
748 | MyHostName); | |
749 | goto doquit; | |
750 | } | |
751 | ||
b6edea3d | 752 | message("500 Command unrecognized"); |
6b861048 EA |
753 | break; |
754 | ||
755 | default: | |
ab4889ea | 756 | errno = 0; |
b6edea3d | 757 | syserr("500 smtp: unknown code %d", c->cmdcode); |
6b861048 EA |
758 | break; |
759 | } | |
760 | } | |
761 | } | |
762 | \f/* | |
763 | ** SKIPWORD -- skip a fixed word. | |
764 | ** | |
765 | ** Parameters: | |
766 | ** p -- place to start looking. | |
767 | ** w -- word to skip. | |
768 | ** | |
769 | ** Returns: | |
770 | ** p following w. | |
771 | ** NULL on error. | |
772 | ** | |
773 | ** Side Effects: | |
774 | ** clobbers the p data area. | |
775 | */ | |
776 | ||
777 | static char * | |
778 | skipword(p, w) | |
779 | register char *p; | |
780 | char *w; | |
781 | { | |
782 | register char *q; | |
7c8c5b90 | 783 | char *firstp = p; |
6b861048 EA |
784 | |
785 | /* find beginning of word */ | |
2bee003d | 786 | while (isascii(*p) && isspace(*p)) |
6b861048 EA |
787 | p++; |
788 | q = p; | |
789 | ||
790 | /* find end of word */ | |
2bee003d | 791 | while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p))) |
6b861048 | 792 | p++; |
2bee003d | 793 | while (isascii(*p) && isspace(*p)) |
6b861048 EA |
794 | *p++ = '\0'; |
795 | if (*p != ':') | |
796 | { | |
797 | syntax: | |
7c8c5b90 EA |
798 | message("501 Syntax error in parameters scanning \"%s\"", |
799 | firstp); | |
6b861048 EA |
800 | Errors++; |
801 | return (NULL); | |
802 | } | |
803 | *p++ = '\0'; | |
2bee003d | 804 | while (isascii(*p) && isspace(*p)) |
6b861048 EA |
805 | p++; |
806 | ||
20f20f1e EA |
807 | if (*p == '\0') |
808 | goto syntax; | |
809 | ||
6b861048 | 810 | /* see if the input word matches desired word */ |
ed73ef1d | 811 | if (strcasecmp(q, w)) |
6b861048 EA |
812 | goto syntax; |
813 | ||
814 | return (p); | |
815 | } | |
34d37b7d | 816 | \f/* |
82e3dc75 EA |
817 | ** RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line |
818 | ** | |
819 | ** Parameters: | |
820 | ** a -- the address corresponding to the To: parameter. | |
821 | ** kp -- the parameter key. | |
822 | ** vp -- the value of that parameter. | |
823 | ** e -- the envelope. | |
824 | ** | |
825 | ** Returns: | |
826 | ** none. | |
827 | */ | |
828 | ||
829 | rcpt_esmtp_args(a, kp, vp, e) | |
830 | ADDRESS *a; | |
831 | char *kp; | |
832 | char *vp; | |
833 | ENVELOPE *e; | |
834 | { | |
835 | if (strcasecmp(kp, "notify") == 0) | |
836 | { | |
837 | char *p; | |
838 | ||
839 | if (vp == NULL) | |
840 | { | |
841 | usrerr("501 NOTIFY requires a value"); | |
842 | /* NOTREACHED */ | |
843 | } | |
844 | a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY); | |
845 | if (strcasecmp(vp, "never") == 0) | |
846 | return; | |
847 | for (p = vp; p != NULL; vp = p) | |
848 | { | |
849 | p = strchr(p, ','); | |
850 | if (p != NULL) | |
851 | *p++ = '\0'; | |
852 | if (strcasecmp(vp, "success") == 0) | |
853 | a->q_flags |= QPINGONSUCCESS; | |
854 | else if (strcasecmp(vp, "failure") == 0) | |
855 | a->q_flags |= QPINGONFAILURE; | |
856 | else if (strcasecmp(vp, "delay") == 0) | |
857 | a->q_flags |= QPINGONDELAY; | |
858 | else | |
859 | { | |
860 | usrerr("501 Bad argument \"%s\" to NOTIFY", | |
861 | vp); | |
862 | /* NOTREACHED */ | |
863 | } | |
864 | } | |
865 | } | |
866 | else if (strcasecmp(kp, "ret") == 0) | |
867 | { | |
868 | if (vp == NULL) | |
869 | { | |
870 | usrerr("501 RET requires a value"); | |
871 | /* NOTREACHED */ | |
872 | } | |
873 | a->q_flags |= QHAS_RET_PARAM; | |
874 | if (strcasecmp(vp, "hdrs") == 0) | |
875 | a->q_flags |= QRET_HDRS; | |
876 | else if (strcasecmp(vp, "full") != 0) | |
877 | { | |
878 | usrerr("501 Bad argument \"%s\" to RET", vp); | |
879 | /* NOTREACHED */ | |
880 | } | |
881 | } | |
882 | else if (strcasecmp(kp, "orcpt") == 0) | |
883 | { | |
884 | if (vp == NULL) | |
885 | { | |
886 | usrerr("501 ORCPT requires a value"); | |
887 | /* NOTREACHED */ | |
888 | } | |
889 | a->q_orcpt = newstr(vp); | |
890 | } | |
891 | else | |
892 | { | |
893 | usrerr("501 %s parameter unrecognized", kp); | |
894 | /* NOTREACHED */ | |
895 | } | |
896 | } | |
897 | \f/* | |
b6edea3d EA |
898 | ** PRINTVRFYADDR -- print an entry in the verify queue |
899 | ** | |
900 | ** Parameters: | |
901 | ** a -- the address to print | |
902 | ** last -- set if this is the last one. | |
903 | ** | |
904 | ** Returns: | |
905 | ** none. | |
906 | ** | |
907 | ** Side Effects: | |
908 | ** Prints the appropriate 250 codes. | |
909 | */ | |
910 | ||
911 | printvrfyaddr(a, last) | |
912 | register ADDRESS *a; | |
913 | bool last; | |
914 | { | |
915 | char fmtbuf[20]; | |
916 | ||
917 | strcpy(fmtbuf, "250"); | |
918 | fmtbuf[3] = last ? ' ' : '-'; | |
919 | ||
ac820be5 EA |
920 | if (a->q_fullname == NULL) |
921 | { | |
922 | if (strchr(a->q_user, '@') == NULL) | |
923 | strcpy(&fmtbuf[4], "<%s@%s>"); | |
924 | else | |
925 | strcpy(&fmtbuf[4], "<%s>"); | |
926 | message(fmtbuf, a->q_user, MyHostName); | |
927 | } | |
b6edea3d EA |
928 | else |
929 | { | |
ac820be5 EA |
930 | if (strchr(a->q_user, '@') == NULL) |
931 | strcpy(&fmtbuf[4], "%s <%s@%s>"); | |
932 | else | |
933 | strcpy(&fmtbuf[4], "%s <%s>"); | |
934 | message(fmtbuf, a->q_fullname, a->q_user, MyHostName); | |
b6edea3d | 935 | } |
b6edea3d EA |
936 | } |
937 | \f/* | |
34d37b7d EA |
938 | ** HELP -- implement the HELP command. |
939 | ** | |
940 | ** Parameters: | |
941 | ** topic -- the topic we want help for. | |
942 | ** | |
943 | ** Returns: | |
944 | ** none. | |
945 | ** | |
946 | ** Side Effects: | |
947 | ** outputs the help file to message output. | |
948 | */ | |
949 | ||
950 | help(topic) | |
951 | char *topic; | |
952 | { | |
953 | register FILE *hf; | |
954 | int len; | |
955 | char buf[MAXLINE]; | |
956 | bool noinfo; | |
957 | ||
c1e24818 | 958 | if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) |
34d37b7d EA |
959 | { |
960 | /* no help */ | |
1b932f66 | 961 | errno = 0; |
b6edea3d | 962 | message("502 HELP not implemented"); |
34d37b7d EA |
963 | return; |
964 | } | |
965 | ||
e97afd4a EA |
966 | if (topic == NULL || *topic == '\0') |
967 | topic = "smtp"; | |
968 | else | |
969 | makelower(topic); | |
970 | ||
34d37b7d | 971 | len = strlen(topic); |
34d37b7d EA |
972 | noinfo = TRUE; |
973 | ||
974 | while (fgets(buf, sizeof buf, hf) != NULL) | |
975 | { | |
976 | if (strncmp(buf, topic, len) == 0) | |
977 | { | |
978 | register char *p; | |
979 | ||
f3d8f6d6 | 980 | p = strchr(buf, '\t'); |
34d37b7d EA |
981 | if (p == NULL) |
982 | p = buf; | |
983 | else | |
984 | p++; | |
985 | fixcrlf(p, TRUE); | |
b6edea3d | 986 | message("214-%s", p); |
34d37b7d EA |
987 | noinfo = FALSE; |
988 | } | |
989 | } | |
990 | ||
991 | if (noinfo) | |
b6edea3d | 992 | message("504 HELP topic unknown"); |
34d37b7d | 993 | else |
b6edea3d | 994 | message("214 End of HELP info"); |
ed45aae1 | 995 | (void) fclose(hf); |
34d37b7d | 996 | } |
abae7b2d | 997 | \f/* |
e6f08ab1 EA |
998 | ** RUNINCHILD -- return twice -- once in the child, then in the parent again |
999 | ** | |
1000 | ** Parameters: | |
1001 | ** label -- a string used in error messages | |
1002 | ** | |
1003 | ** Returns: | |
1004 | ** zero in the child | |
1005 | ** one in the parent | |
1006 | ** | |
1007 | ** Side Effects: | |
1008 | ** none. | |
1009 | */ | |
1010 | ||
a4076aed | 1011 | runinchild(label, e) |
e6f08ab1 | 1012 | char *label; |
a4076aed | 1013 | register ENVELOPE *e; |
e6f08ab1 EA |
1014 | { |
1015 | int childpid; | |
1016 | ||
c1a66acf | 1017 | if (!OneXact) |
e6f08ab1 | 1018 | { |
c1a66acf EA |
1019 | childpid = dofork(); |
1020 | if (childpid < 0) | |
1021 | { | |
1022 | syserr("%s: cannot fork", label); | |
1023 | return (1); | |
1024 | } | |
1025 | if (childpid > 0) | |
1026 | { | |
1027 | auto int st; | |
e6f08ab1 | 1028 | |
c1a66acf | 1029 | /* parent -- wait for child to complete */ |
8e948497 | 1030 | setproctitle("server %s child wait", CurHostName); |
c1a66acf EA |
1031 | st = waitfor(childpid); |
1032 | if (st == -1) | |
1033 | syserr("%s: lost child", label); | |
39824631 EA |
1034 | else if (!WIFEXITED(st)) |
1035 | syserr("%s: died on signal %d", | |
1036 | label, st & 0177); | |
e6f08ab1 | 1037 | |
c1a66acf | 1038 | /* if we exited on a QUIT command, complete the process */ |
33cbaada EA |
1039 | if (WEXITSTATUS(st) == EX_QUIT) |
1040 | { | |
1041 | disconnect(1, e); | |
c1a66acf | 1042 | finis(); |
33cbaada | 1043 | } |
e6f08ab1 | 1044 | |
c1a66acf EA |
1045 | return (1); |
1046 | } | |
1047 | else | |
1048 | { | |
1049 | /* child */ | |
1050 | InChild = TRUE; | |
57c97d4a | 1051 | QuickAbort = FALSE; |
a4076aed | 1052 | clearenvelope(e, FALSE); |
c1a66acf | 1053 | } |
e6f08ab1 | 1054 | } |
55f0da62 | 1055 | |
c1a66acf | 1056 | /* open alias database */ |
36b09297 | 1057 | initmaps(FALSE, e); |
c1a66acf EA |
1058 | |
1059 | return (0); | |
e6f08ab1 EA |
1060 | } |
1061 | \f/* | |
abae7b2d EA |
1062 | ** PADDRTREE -- print address tree |
1063 | ** | |
1064 | ** Used by VRFY and EXPD to dump the tree of addresses produced. | |
1065 | ** | |
1066 | ** Parameters: | |
1067 | ** a -- address of root. | |
1068 | ** | |
1069 | ** Returns: | |
1070 | ** none. | |
1071 | ** | |
1072 | ** Side Effects: | |
1073 | ** prints the tree in a nice order. | |
1074 | */ | |
1075 | ||
1076 | paddrtree(a) | |
1077 | register ADDRESS *a; | |
1078 | { | |
1079 | static ADDRESS *prev; | |
1080 | static int lev; | |
1081 | ||
1082 | if (a == NULL) | |
1083 | return; | |
1084 | lev++; | |
1085 | if (!bitset(QDONTSEND, a->q_flags)) | |
1086 | { | |
1087 | if (prev != NULL) | |
1088 | { | |
1089 | if (prev->q_fullname != NULL) | |
1090 | message("250-", "%s <%s>", prev->q_fullname, prev->q_paddr); | |
1091 | else | |
1092 | message("250-", "<%s>", prev->q_paddr); | |
1093 | } | |
1094 | prev = a; | |
1095 | } | |
1096 | paddrtree(a->q_child); | |
1097 | paddrtree(a->q_sibling); | |
1098 | if (--lev <= 0) | |
1099 | { | |
1100 | if (prev != NULL) | |
1101 | { | |
1102 | /* last one */ | |
1103 | if (prev->q_fullname != NULL) | |
1104 | message("250", "%s <%s>", prev->q_fullname, prev->q_paddr); | |
1105 | else | |
1106 | message("250", "<%s>", prev->q_paddr); | |
1107 | prev = NULL; | |
1108 | } | |
1109 | else | |
1110 | message("550", "User unknown"); | |
1111 | } | |
1112 | } | |
884a20cb | 1113 | |
f3d8f6d6 | 1114 | # endif /* SMTP */ |