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