Commit | Line | Data |
---|---|---|
e6f08ab1 | 1 | # include <errno.h> |
6b861048 | 2 | # include "sendmail.h" |
671d4b5c | 3 | # include <signal.h> |
6b861048 | 4 | |
884a20cb | 5 | # ifndef SMTP |
d6de42cf | 6 | SCCSID(@(#)srvrsmtp.c 4.11 %G% (no SMTP)); |
884a20cb EA |
7 | # else SMTP |
8 | ||
d6de42cf | 9 | SCCSID(@(#)srvrsmtp.c 4.11 %G%); |
d727056e | 10 | |
6b861048 EA |
11 | /* |
12 | ** SMTP -- run the SMTP protocol. | |
13 | ** | |
14 | ** Parameters: | |
15 | ** none. | |
16 | ** | |
17 | ** Returns: | |
18 | ** never. | |
19 | ** | |
20 | ** Side Effects: | |
21 | ** Reads commands from the input channel and processes | |
22 | ** them. | |
23 | */ | |
24 | ||
25 | struct cmd | |
26 | { | |
27 | char *cmdname; /* command name */ | |
28 | int cmdcode; /* internal code, see below */ | |
29 | }; | |
30 | ||
31 | /* values for cmdcode */ | |
32 | # define CMDERROR 0 /* bad command */ | |
33 | # define CMDMAIL 1 /* mail -- designate sender */ | |
4a4ebe09 | 34 | # define CMDRCPT 2 /* rcpt -- designate recipient */ |
6b861048 | 35 | # define CMDDATA 3 /* data -- send message text */ |
7d7fdf93 | 36 | # define CMDHOPS 4 /* hops -- specify hop count */ |
e6f08ab1 EA |
37 | # define CMDRSET 4 /* rset -- reset state */ |
38 | # define CMDVRFY 5 /* vrfy -- verify address */ | |
39 | # define CMDHELP 6 /* help -- give usage info */ | |
40 | # define CMDNOOP 7 /* noop -- do nothing */ | |
41 | # define CMDQUIT 8 /* quit -- close connection and die */ | |
42 | # define CMDHELO 9 /* helo -- be polite */ | |
43 | # define CMDDBGQSHOW 10 /* showq -- show send queue (DEBUG) */ | |
44 | # define CMDDBGDEBUG 11 /* debug -- set debug mode */ | |
45 | # define CMDVERB 12 /* verb -- go into verbose mode */ | |
46 | # define CMDDBGKILL 13 /* kill -- kill sendmail */ | |
47 | # define CMDDBGWIZ 14 /* wiz -- become a wizard */ | |
48 | # define CMDONEX 15 /* onex -- sending one transaction only */ | |
6b861048 EA |
49 | |
50 | static struct cmd CmdTab[] = | |
51 | { | |
52 | "mail", CMDMAIL, | |
4a4ebe09 | 53 | "rcpt", CMDRCPT, |
6b861048 | 54 | "data", CMDDATA, |
6b861048 EA |
55 | "rset", CMDRSET, |
56 | "vrfy", CMDVRFY, | |
abae7b2d | 57 | "expn", CMDVRFY, |
633a2e02 | 58 | "expn", CMDVRFY, |
6b861048 EA |
59 | "help", CMDHELP, |
60 | "noop", CMDNOOP, | |
61 | "quit", CMDQUIT, | |
4a4ebe09 | 62 | "helo", CMDHELO, |
e8ad767d | 63 | "verb", CMDVERB, |
8fe4fb9b | 64 | "onex", CMDONEX, |
7d7fdf93 | 65 | "hops", CMDHOPS, |
d4f42161 | 66 | # ifdef DEBUG |
e6f08ab1 | 67 | "showq", CMDDBGQSHOW, |
e8ad767d EA |
68 | "debug", CMDDBGDEBUG, |
69 | "kill", CMDDBGKILL, | |
70 | "wiz", CMDDBGWIZ, | |
d4f42161 | 71 | # endif DEBUG |
6b861048 EA |
72 | NULL, CMDERROR, |
73 | }; | |
74 | ||
e8ad767d | 75 | bool IsWiz = FALSE; /* set if we are a wizard */ |
c50c55e6 | 76 | char *WizWord; /* the wizard word to compare against */ |
e6f08ab1 | 77 | bool InChild = FALSE; /* true if running in a subprocess */ |
7338e3d4 | 78 | bool OneXact = FALSE; /* one xaction only this run */ |
1972fb40 EA |
79 | char *RealHostName = NULL; /* verified hostname, set in daemon.c */ |
80 | ||
e6f08ab1 | 81 | #define EX_QUIT 22 /* special code for QUIT command */ |
e8ad767d | 82 | |
6b861048 EA |
83 | smtp() |
84 | { | |
6b861048 | 85 | register char *p; |
e8ad767d | 86 | register struct cmd *c; |
6b861048 EA |
87 | char *cmd; |
88 | extern char *skipword(); | |
89 | extern bool sameword(); | |
90 | bool hasmail; /* mail command received */ | |
49086753 | 91 | int rcps; /* number of recipients */ |
abae7b2d EA |
92 | extern ADDRESS *sendto(); |
93 | ADDRESS *a; | |
6b861048 | 94 | |
abae7b2d | 95 | hasmail = FALSE; |
49086753 | 96 | rcps = 0; |
1f1cc003 EA |
97 | if (OutChannel != stdout) |
98 | { | |
99 | /* arrange for debugging output to go to remote host */ | |
100 | (void) close(1); | |
101 | (void) dup(fileno(OutChannel)); | |
102 | } | |
1b932f66 | 103 | settime(); |
a73ae8ac | 104 | expand("\001e", inp, &inp[sizeof inp], CurEnv); |
378e8da7 | 105 | message("220", inp); |
6b861048 EA |
106 | for (;;) |
107 | { | |
d344c0b7 EA |
108 | /* arrange for backout */ |
109 | if (setjmp(TopFrame) > 0 && InChild) | |
110 | finis(); | |
111 | QuickAbort = FALSE; | |
112 | HoldErrs = FALSE; | |
113 | ||
37eaaadb | 114 | /* setup for the read */ |
2654b031 | 115 | CurEnv->e_to = NULL; |
34d37b7d | 116 | Errors = 0; |
09eb49d8 | 117 | (void) fflush(stdout); |
37eaaadb | 118 | |
37eaaadb | 119 | /* read the input line */ |
2439b900 | 120 | p = sfgets(inp, sizeof inp, InChannel); |
37eaaadb | 121 | |
2439b900 | 122 | /* handle errors */ |
37eaaadb | 123 | if (p == NULL) |
6b861048 EA |
124 | { |
125 | /* end of file, just die */ | |
2768afe3 | 126 | message("421", "%s Lost input channel", HostName); |
6b861048 EA |
127 | finis(); |
128 | } | |
129 | ||
130 | /* clean up end of line */ | |
2768afe3 | 131 | fixcrlf(inp, TRUE); |
6b861048 | 132 | |
49086753 | 133 | /* echo command to transcript */ |
912acb74 EA |
134 | if (CurEnv->e_xfp != NULL) |
135 | fprintf(CurEnv->e_xfp, "<<< %s\n", inp); | |
49086753 | 136 | |
6b861048 EA |
137 | /* break off command */ |
138 | for (p = inp; isspace(*p); p++) | |
139 | continue; | |
140 | cmd = p; | |
141 | while (*++p != '\0' && !isspace(*p)) | |
142 | continue; | |
143 | if (*p != '\0') | |
144 | *p++ = '\0'; | |
145 | ||
146 | /* decode command */ | |
147 | for (c = CmdTab; c->cmdname != NULL; c++) | |
148 | { | |
149 | if (sameword(c->cmdname, cmd)) | |
150 | break; | |
151 | } | |
152 | ||
153 | /* process command */ | |
154 | switch (c->cmdcode) | |
155 | { | |
4a4ebe09 | 156 | case CMDHELO: /* hello -- introduce yourself */ |
f7e74083 EA |
157 | if (sameword(p, HostName)) |
158 | { | |
159 | /* connected to an echo server */ | |
160 | message("553", "%s I refuse to talk to myself", | |
161 | HostName); | |
162 | break; | |
163 | } | |
1972fb40 EA |
164 | if (RealHostName != NULL && !sameword(p, RealHostName)) |
165 | { | |
166 | char buf[MAXNAME]; | |
167 | ||
168 | (void) sprintf(buf, "%s (%s)", p, RealHostName); | |
169 | define('s', newstr(buf), CurEnv); | |
170 | } | |
171 | else | |
172 | define('s', newstr(p), CurEnv); | |
abae7b2d | 173 | message("250", "%s Hello %s, pleased to meet you", HostName, p); |
4a4ebe09 EA |
174 | break; |
175 | ||
6b861048 | 176 | case CMDMAIL: /* mail -- designate sender */ |
0c6a0070 EA |
177 | /* force a sending host even if no HELO given */ |
178 | if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) | |
179 | define('s', RealHostName, CurEnv); | |
180 | ||
8fe4fb9b | 181 | /* check for validity of this command */ |
2768afe3 EA |
182 | if (hasmail) |
183 | { | |
184 | message("503", "Sender already specified"); | |
185 | break; | |
186 | } | |
e6f08ab1 EA |
187 | if (InChild) |
188 | { | |
189 | syserr("Nested MAIL command"); | |
190 | exit(0); | |
191 | } | |
192 | ||
193 | /* fork a subprocess to process this command */ | |
194 | if (runinchild("SMTP-MAIL") > 0) | |
195 | break; | |
196 | initsys(); | |
197 | ||
198 | /* child -- go do the processing */ | |
6b861048 EA |
199 | p = skipword(p, "from"); |
200 | if (p == NULL) | |
201 | break; | |
6b861048 | 202 | setsender(p); |
34d37b7d | 203 | if (Errors == 0) |
6b861048 EA |
204 | { |
205 | message("250", "Sender ok"); | |
206 | hasmail = TRUE; | |
207 | } | |
e6f08ab1 EA |
208 | else if (InChild) |
209 | finis(); | |
6b861048 EA |
210 | break; |
211 | ||
4a4ebe09 | 212 | case CMDRCPT: /* rcpt -- designate recipient */ |
d344c0b7 | 213 | if (setjmp(TopFrame) > 0) |
9bfb75c1 EA |
214 | { |
215 | CurEnv->e_flags &= ~EF_FATALERRS; | |
d344c0b7 | 216 | break; |
9bfb75c1 | 217 | } |
d344c0b7 | 218 | QuickAbort = TRUE; |
6b861048 EA |
219 | p = skipword(p, "to"); |
220 | if (p == NULL) | |
221 | break; | |
abae7b2d EA |
222 | a = sendto(p, 1, (ADDRESS *) NULL, 0); |
223 | # ifdef DEBUG | |
224 | if (Debug > 1) | |
225 | printaddr(a, TRUE); | |
226 | # endif DEBUG | |
d344c0b7 EA |
227 | if (Errors != 0) |
228 | break; | |
229 | ||
230 | /* no errors during parsing, but might be a duplicate */ | |
231 | CurEnv->e_to = p; | |
232 | if (!bitset(QBADADDR, a->q_flags)) | |
233 | message("250", "Recipient ok"); | |
234 | else | |
6b861048 | 235 | { |
d344c0b7 EA |
236 | /* punt -- should keep message in ADDRESS.... */ |
237 | message("550", "Addressee unknown"); | |
6b861048 | 238 | } |
d344c0b7 EA |
239 | CurEnv->e_to = NULL; |
240 | rcps++; | |
6b861048 EA |
241 | break; |
242 | ||
243 | case CMDDATA: /* data -- text of mail */ | |
6b861048 | 244 | if (!hasmail) |
4a4ebe09 | 245 | { |
6b861048 | 246 | message("503", "Need MAIL command"); |
4a4ebe09 EA |
247 | break; |
248 | } | |
49086753 | 249 | else if (rcps <= 0) |
6b861048 | 250 | { |
4a4ebe09 EA |
251 | message("503", "Need RCPT (recipient)"); |
252 | break; | |
6b861048 | 253 | } |
4a4ebe09 EA |
254 | |
255 | /* collect the text of the message */ | |
256 | collect(TRUE); | |
257 | if (Errors != 0) | |
258 | break; | |
259 | ||
8eedb496 EA |
260 | /* |
261 | ** Arrange to send to everyone. | |
262 | ** If sending to multiple people, mail back | |
263 | ** errors rather than reporting directly. | |
264 | ** In any case, don't mail back errors for | |
265 | ** anything that has happened up to | |
266 | ** now (the other end will do this). | |
bcf74f25 EA |
267 | ** Truncate our transcript -- the mail has gotten |
268 | ** to us successfully, and if we have | |
269 | ** to mail this back, it will be easier | |
270 | ** on the reader. | |
8eedb496 EA |
271 | ** Then send to everyone. |
272 | ** Finally give a reply code. If an error has | |
273 | ** already been given, don't mail a | |
274 | ** message back. | |
e6f08ab1 | 275 | ** We goose error returns by clearing error bit. |
8eedb496 EA |
276 | */ |
277 | ||
4a4ebe09 | 278 | if (rcps != 1) |
7338e3d4 EA |
279 | { |
280 | HoldErrs = TRUE; | |
83f674d8 | 281 | ErrorMode = EM_MAIL; |
7338e3d4 | 282 | } |
e6f08ab1 | 283 | CurEnv->e_flags &= ~EF_FATALERRS; |
bcf74f25 | 284 | CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); |
4a4ebe09 EA |
285 | |
286 | /* send to all recipients */ | |
f7e74083 | 287 | sendall(CurEnv, SM_DEFAULT); |
2654b031 | 288 | CurEnv->e_to = NULL; |
4a4ebe09 | 289 | |
8eedb496 EA |
290 | /* issue success if appropriate and reset */ |
291 | if (Errors == 0 || HoldErrs) | |
75f95954 | 292 | message("250", "Ok"); |
8eedb496 | 293 | else |
e6f08ab1 EA |
294 | CurEnv->e_flags &= ~EF_FATALERRS; |
295 | ||
296 | /* if in a child, pop back to our parent */ | |
297 | if (InChild) | |
298 | finis(); | |
6b861048 EA |
299 | break; |
300 | ||
301 | case CMDRSET: /* rset -- reset state */ | |
302 | message("250", "Reset state"); | |
e6f08ab1 EA |
303 | if (InChild) |
304 | finis(); | |
305 | break; | |
6b861048 EA |
306 | |
307 | case CMDVRFY: /* vrfy -- verify address */ | |
e6f08ab1 EA |
308 | if (runinchild("SMTP-VRFY") > 0) |
309 | break; | |
abae7b2d | 310 | paddrtree(a); |
6b861048 EA |
311 | break; |
312 | ||
313 | case CMDHELP: /* help -- give user info */ | |
34d37b7d EA |
314 | if (*p == '\0') |
315 | p = "SMTP"; | |
316 | help(p); | |
6b861048 EA |
317 | break; |
318 | ||
319 | case CMDNOOP: /* noop -- do nothing */ | |
320 | message("200", "OK"); | |
321 | break; | |
322 | ||
323 | case CMDQUIT: /* quit -- leave mail */ | |
324 | message("221", "%s closing connection", HostName); | |
e6f08ab1 EA |
325 | if (InChild) |
326 | ExitStat = EX_QUIT; | |
6b861048 EA |
327 | finis(); |
328 | ||
e8ad767d EA |
329 | case CMDVERB: /* set verbose mode */ |
330 | Verbose = TRUE; | |
331 | message("200", "Verbose mode"); | |
332 | break; | |
333 | ||
8fe4fb9b | 334 | case CMDONEX: /* doing one transaction only */ |
7338e3d4 | 335 | OneXact = TRUE; |
8fe4fb9b EA |
336 | message("200", "Only one transaction"); |
337 | break; | |
338 | ||
d4f42161 | 339 | # ifdef DEBUG |
e6f08ab1 | 340 | case CMDDBGQSHOW: /* show queues */ |
2654b031 EA |
341 | printf("Send Queue="); |
342 | printaddr(CurEnv->e_sendqueue, TRUE); | |
d4f42161 | 343 | break; |
09eb49d8 EA |
344 | |
345 | case CMDDBGDEBUG: /* set debug mode */ | |
9678c96d EA |
346 | tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); |
347 | tTflag(p); | |
348 | message("200", "Debug set"); | |
09eb49d8 EA |
349 | break; |
350 | ||
e673aad7 | 351 | case CMDDBGKILL: /* kill the parent */ |
e8ad767d EA |
352 | if (!iswiz()) |
353 | break; | |
e673aad7 EA |
354 | if (kill(MotherPid, SIGTERM) >= 0) |
355 | message("200", "Mother is dead"); | |
356 | else | |
357 | message("500", "Can't kill Mom"); | |
358 | break; | |
e8ad767d EA |
359 | |
360 | case CMDDBGWIZ: /* become a wizard */ | |
361 | if (WizWord != NULL) | |
362 | { | |
363 | char seed[3]; | |
364 | extern char *crypt(); | |
365 | ||
366 | strncpy(seed, WizWord, 2); | |
c50c55e6 | 367 | if (strcmp(WizWord, crypt(p, seed)) == 0) |
e8ad767d | 368 | { |
c50c55e6 EA |
369 | IsWiz = TRUE; |
370 | message("200", "Please pass, oh mighty wizard"); | |
e8ad767d EA |
371 | break; |
372 | } | |
373 | } | |
c50c55e6 | 374 | message("500", "You are no wizard!"); |
e8ad767d | 375 | break; |
d4f42161 EA |
376 | # endif DEBUG |
377 | ||
6b861048 EA |
378 | case CMDERROR: /* unknown command */ |
379 | message("500", "Command unrecognized"); | |
380 | break; | |
381 | ||
382 | default: | |
383 | syserr("smtp: unknown code %d", c->cmdcode); | |
384 | break; | |
385 | } | |
386 | } | |
387 | } | |
388 | \f/* | |
389 | ** SKIPWORD -- skip a fixed word. | |
390 | ** | |
391 | ** Parameters: | |
392 | ** p -- place to start looking. | |
393 | ** w -- word to skip. | |
394 | ** | |
395 | ** Returns: | |
396 | ** p following w. | |
397 | ** NULL on error. | |
398 | ** | |
399 | ** Side Effects: | |
400 | ** clobbers the p data area. | |
401 | */ | |
402 | ||
403 | static char * | |
404 | skipword(p, w) | |
405 | register char *p; | |
406 | char *w; | |
407 | { | |
408 | register char *q; | |
409 | extern bool sameword(); | |
410 | ||
411 | /* find beginning of word */ | |
412 | while (isspace(*p)) | |
413 | p++; | |
414 | q = p; | |
415 | ||
416 | /* find end of word */ | |
417 | while (*p != '\0' && *p != ':' && !isspace(*p)) | |
418 | p++; | |
419 | while (isspace(*p)) | |
420 | *p++ = '\0'; | |
421 | if (*p != ':') | |
422 | { | |
423 | syntax: | |
424 | message("501", "Syntax error"); | |
425 | Errors++; | |
426 | return (NULL); | |
427 | } | |
428 | *p++ = '\0'; | |
429 | while (isspace(*p)) | |
430 | p++; | |
431 | ||
432 | /* see if the input word matches desired word */ | |
433 | if (!sameword(q, w)) | |
434 | goto syntax; | |
435 | ||
436 | return (p); | |
437 | } | |
34d37b7d EA |
438 | \f/* |
439 | ** HELP -- implement the HELP command. | |
440 | ** | |
441 | ** Parameters: | |
442 | ** topic -- the topic we want help for. | |
443 | ** | |
444 | ** Returns: | |
445 | ** none. | |
446 | ** | |
447 | ** Side Effects: | |
448 | ** outputs the help file to message output. | |
449 | */ | |
450 | ||
451 | help(topic) | |
452 | char *topic; | |
453 | { | |
454 | register FILE *hf; | |
455 | int len; | |
456 | char buf[MAXLINE]; | |
457 | bool noinfo; | |
458 | ||
c1e24818 | 459 | if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) |
34d37b7d EA |
460 | { |
461 | /* no help */ | |
1b932f66 | 462 | errno = 0; |
34d37b7d EA |
463 | message("502", "HELP not implemented"); |
464 | return; | |
465 | } | |
466 | ||
467 | len = strlen(topic); | |
468 | makelower(topic); | |
469 | noinfo = TRUE; | |
470 | ||
471 | while (fgets(buf, sizeof buf, hf) != NULL) | |
472 | { | |
473 | if (strncmp(buf, topic, len) == 0) | |
474 | { | |
475 | register char *p; | |
476 | ||
477 | p = index(buf, '\t'); | |
478 | if (p == NULL) | |
479 | p = buf; | |
480 | else | |
481 | p++; | |
482 | fixcrlf(p, TRUE); | |
483 | message("214-", p); | |
484 | noinfo = FALSE; | |
485 | } | |
486 | } | |
487 | ||
488 | if (noinfo) | |
489 | message("504", "HELP topic unknown"); | |
490 | else | |
491 | message("214", "End of HELP info"); | |
ed45aae1 | 492 | (void) fclose(hf); |
34d37b7d | 493 | } |
abae7b2d | 494 | \f/* |
e8ad767d EA |
495 | ** ISWIZ -- tell us if we are a wizard |
496 | ** | |
497 | ** If not, print a nasty message. | |
498 | ** | |
499 | ** Parameters: | |
500 | ** none. | |
501 | ** | |
502 | ** Returns: | |
503 | ** TRUE if we are a wizard. | |
504 | ** FALSE if we are not a wizard. | |
505 | ** | |
506 | ** Side Effects: | |
507 | ** Prints a 500 exit stat if we are not a wizard. | |
508 | */ | |
509 | ||
d6de42cf EA |
510 | #ifdef DEBUG |
511 | ||
e8ad767d EA |
512 | bool |
513 | iswiz() | |
514 | { | |
515 | if (!IsWiz) | |
516 | message("500", "Mere mortals musn't mutter that mantra"); | |
517 | return (IsWiz); | |
518 | } | |
d6de42cf EA |
519 | |
520 | #endif DEBUG | |
e8ad767d | 521 | \f/* |
e6f08ab1 EA |
522 | ** RUNINCHILD -- return twice -- once in the child, then in the parent again |
523 | ** | |
524 | ** Parameters: | |
525 | ** label -- a string used in error messages | |
526 | ** | |
527 | ** Returns: | |
528 | ** zero in the child | |
529 | ** one in the parent | |
530 | ** | |
531 | ** Side Effects: | |
532 | ** none. | |
533 | */ | |
534 | ||
535 | runinchild(label) | |
536 | char *label; | |
537 | { | |
538 | int childpid; | |
539 | ||
c1a66acf | 540 | if (!OneXact) |
e6f08ab1 | 541 | { |
c1a66acf EA |
542 | childpid = dofork(); |
543 | if (childpid < 0) | |
544 | { | |
545 | syserr("%s: cannot fork", label); | |
546 | return (1); | |
547 | } | |
548 | if (childpid > 0) | |
549 | { | |
550 | auto int st; | |
e6f08ab1 | 551 | |
c1a66acf EA |
552 | /* parent -- wait for child to complete */ |
553 | st = waitfor(childpid); | |
554 | if (st == -1) | |
555 | syserr("%s: lost child", label); | |
e6f08ab1 | 556 | |
c1a66acf EA |
557 | /* if we exited on a QUIT command, complete the process */ |
558 | if (st == (EX_QUIT << 8)) | |
559 | finis(); | |
e6f08ab1 | 560 | |
c1a66acf EA |
561 | return (1); |
562 | } | |
563 | else | |
564 | { | |
565 | /* child */ | |
566 | InChild = TRUE; | |
567 | } | |
e6f08ab1 | 568 | } |
55f0da62 | 569 | |
c1a66acf EA |
570 | /* child (or ONEX command specified) */ |
571 | clearenvelope(CurEnv); | |
55f0da62 | 572 | |
c1a66acf EA |
573 | /* open alias database */ |
574 | initaliases(AliasFile, FALSE); | |
575 | ||
576 | return (0); | |
e6f08ab1 EA |
577 | } |
578 | \f/* | |
abae7b2d EA |
579 | ** PADDRTREE -- print address tree |
580 | ** | |
581 | ** Used by VRFY and EXPD to dump the tree of addresses produced. | |
582 | ** | |
583 | ** Parameters: | |
584 | ** a -- address of root. | |
585 | ** | |
586 | ** Returns: | |
587 | ** none. | |
588 | ** | |
589 | ** Side Effects: | |
590 | ** prints the tree in a nice order. | |
591 | */ | |
592 | ||
593 | paddrtree(a) | |
594 | register ADDRESS *a; | |
595 | { | |
596 | static ADDRESS *prev; | |
597 | static int lev; | |
598 | ||
599 | if (a == NULL) | |
600 | return; | |
601 | lev++; | |
602 | if (!bitset(QDONTSEND, a->q_flags)) | |
603 | { | |
604 | if (prev != NULL) | |
605 | { | |
606 | if (prev->q_fullname != NULL) | |
607 | message("250-", "%s <%s>", prev->q_fullname, prev->q_paddr); | |
608 | else | |
609 | message("250-", "<%s>", prev->q_paddr); | |
610 | } | |
611 | prev = a; | |
612 | } | |
613 | paddrtree(a->q_child); | |
614 | paddrtree(a->q_sibling); | |
615 | if (--lev <= 0) | |
616 | { | |
617 | if (prev != NULL) | |
618 | { | |
619 | /* last one */ | |
620 | if (prev->q_fullname != NULL) | |
621 | message("250", "%s <%s>", prev->q_fullname, prev->q_paddr); | |
622 | else | |
623 | message("250", "<%s>", prev->q_paddr); | |
624 | prev = NULL; | |
625 | } | |
626 | else | |
627 | message("550", "User unknown"); | |
628 | } | |
629 | } | |
884a20cb EA |
630 | |
631 | # endif SMTP |