implement SMTP auto-shutdown on 421 codes; clean up some error processing
[unix-history] / usr / src / usr.sbin / sendmail / src / srvrsmtp.c
CommitLineData
e6f08ab1 1# include <errno.h>
6b861048
EA
2# include "sendmail.h"
3
884a20cb 4# ifndef SMTP
80482eb5 5SCCSID(@(#)srvrsmtp.c 3.40 %G% (no SMTP));
884a20cb
EA
6# else SMTP
7
80482eb5 8SCCSID(@(#)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
24struct 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
50static 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
77bool IsWiz = FALSE; /* set if we are a wizard */
78char *WizWord = NULL; /* the wizard word to compare against */
79# endif DEBUG
e6f08ab1 80bool InChild = FALSE; /* true if running in a subprocess */
7338e3d4 81bool OneXact = FALSE; /* one xaction only this run */
e6f08ab1 82#define EX_QUIT 22 /* special code for QUIT command */
e8ad767d 83
6b861048
EA
84smtp()
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
383static char *
384skipword(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
431help(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
489bool
490iswiz()
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
510runinchild(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
561paddrtree(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