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