Add copyright
[unix-history] / usr / src / usr.sbin / sendmail / src / srvrsmtp.c
... / ...
CommitLineData
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
15# include <errno.h>
16# include "sendmail.h"
17# include <signal.h>
18
19# ifndef SMTP
20SCCSID(@(#)srvrsmtp.c 5.1 %G% (no SMTP));
21# else SMTP
22
23SCCSID(@(#)srvrsmtp.c 5.1 %G%);
24
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 */
48# define CMDRCPT 2 /* rcpt -- designate recipient */
49# define CMDDATA 3 /* data -- send message text */
50# define CMDHOPS 4 /* hops -- specify hop count */
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 */
63
64static struct cmd CmdTab[] =
65{
66 "mail", CMDMAIL,
67 "rcpt", CMDRCPT,
68 "data", CMDDATA,
69 "rset", CMDRSET,
70 "vrfy", CMDVRFY,
71 "expn", CMDVRFY,
72 "expn", CMDVRFY,
73 "help", CMDHELP,
74 "noop", CMDNOOP,
75 "quit", CMDQUIT,
76 "helo", CMDHELO,
77 "verb", CMDVERB,
78 "onex", CMDONEX,
79 "hops", CMDHOPS,
80# ifdef DEBUG
81 "showq", CMDDBGQSHOW,
82 "debug", CMDDBGDEBUG,
83 "kill", CMDDBGKILL,
84 "wiz", CMDDBGWIZ,
85# endif DEBUG
86 NULL, CMDERROR,
87};
88
89bool IsWiz = FALSE; /* set if we are a wizard */
90char *WizWord; /* the wizard word to compare against */
91bool InChild = FALSE; /* true if running in a subprocess */
92bool OneXact = FALSE; /* one xaction only this run */
93char *RealHostName = NULL; /* verified hostname, set in daemon.c */
94
95#define EX_QUIT 22 /* special code for QUIT command */
96
97smtp()
98{
99 register char *p;
100 register struct cmd *c;
101 char *cmd;
102 extern char *skipword();
103 extern bool sameword();
104 bool hasmail; /* mail command received */
105 int rcps; /* number of recipients */
106 extern ADDRESS *sendto();
107 ADDRESS *a;
108
109 hasmail = FALSE;
110 rcps = 0;
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 }
117 settime();
118 expand("\001e", inp, &inp[sizeof inp], CurEnv);
119 message("220", inp);
120 for (;;)
121 {
122 /* arrange for backout */
123 if (setjmp(TopFrame) > 0 && InChild)
124 finis();
125 QuickAbort = FALSE;
126 HoldErrs = FALSE;
127
128 /* setup for the read */
129 CurEnv->e_to = NULL;
130 Errors = 0;
131 (void) fflush(stdout);
132
133 /* read the input line */
134 p = sfgets(inp, sizeof inp, InChannel);
135
136 /* handle errors */
137 if (p == NULL)
138 {
139 /* end of file, just die */
140 message("421", "%s Lost input channel", HostName);
141 finis();
142 }
143
144 /* clean up end of line */
145 fixcrlf(inp, TRUE);
146
147 /* echo command to transcript */
148 if (CurEnv->e_xfp != NULL)
149 fprintf(CurEnv->e_xfp, "<<< %s\n", inp);
150
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 {
170 case CMDHELO: /* hello -- introduce yourself */
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 }
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);
187 message("250", "%s Hello %s, pleased to meet you", HostName, p);
188 break;
189
190 case CMDMAIL: /* mail -- designate sender */
191 /* force a sending host even if no HELO given */
192 if (RealHostName != NULL && macvalue('s', CurEnv) == NULL)
193 define('s', RealHostName, CurEnv);
194
195 /* check for validity of this command */
196 if (hasmail)
197 {
198 message("503", "Sender already specified");
199 break;
200 }
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 */
213 p = skipword(p, "from");
214 if (p == NULL)
215 break;
216 setsender(p);
217 if (Errors == 0)
218 {
219 message("250", "Sender ok");
220 hasmail = TRUE;
221 }
222 else if (InChild)
223 finis();
224 break;
225
226 case CMDRCPT: /* rcpt -- designate recipient */
227 if (setjmp(TopFrame) > 0)
228 {
229 CurEnv->e_flags &= ~EF_FATALERRS;
230 break;
231 }
232 QuickAbort = TRUE;
233 p = skipword(p, "to");
234 if (p == NULL)
235 break;
236 a = sendto(p, 1, (ADDRESS *) NULL, 0);
237# ifdef DEBUG
238 if (Debug > 1)
239 printaddr(a, TRUE);
240# endif DEBUG
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
249 {
250 /* punt -- should keep message in ADDRESS.... */
251 message("550", "Addressee unknown");
252 }
253 CurEnv->e_to = NULL;
254 rcps++;
255 break;
256
257 case CMDDATA: /* data -- text of mail */
258 if (!hasmail)
259 {
260 message("503", "Need MAIL command");
261 break;
262 }
263 else if (rcps <= 0)
264 {
265 message("503", "Need RCPT (recipient)");
266 break;
267 }
268
269 /* collect the text of the message */
270 collect(TRUE);
271 if (Errors != 0)
272 break;
273
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).
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.
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.
289 ** We goose error returns by clearing error bit.
290 */
291
292 if (rcps != 1)
293 {
294 HoldErrs = TRUE;
295 ErrorMode = EM_MAIL;
296 }
297 CurEnv->e_flags &= ~EF_FATALERRS;
298 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp);
299
300 /* send to all recipients */
301 sendall(CurEnv, SM_DEFAULT);
302 CurEnv->e_to = NULL;
303
304 /* issue success if appropriate and reset */
305 if (Errors == 0 || HoldErrs)
306 message("250", "Ok");
307 else
308 CurEnv->e_flags &= ~EF_FATALERRS;
309
310 /* if in a child, pop back to our parent */
311 if (InChild)
312 finis();
313 break;
314
315 case CMDRSET: /* rset -- reset state */
316 message("250", "Reset state");
317 if (InChild)
318 finis();
319 break;
320
321 case CMDVRFY: /* vrfy -- verify address */
322 if (runinchild("SMTP-VRFY") > 0)
323 break;
324 paddrtree(a);
325 break;
326
327 case CMDHELP: /* help -- give user info */
328 if (*p == '\0')
329 p = "SMTP";
330 help(p);
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);
339 if (InChild)
340 ExitStat = EX_QUIT;
341 finis();
342
343 case CMDVERB: /* set verbose mode */
344 Verbose = TRUE;
345 message("200", "Verbose mode");
346 break;
347
348 case CMDONEX: /* doing one transaction only */
349 OneXact = TRUE;
350 message("200", "Only one transaction");
351 break;
352
353# ifdef DEBUG
354 case CMDDBGQSHOW: /* show queues */
355 printf("Send Queue=");
356 printaddr(CurEnv->e_sendqueue, TRUE);
357 break;
358
359 case CMDDBGDEBUG: /* set debug mode */
360 tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
361 tTflag(p);
362 message("200", "Debug set");
363 break;
364
365 case CMDDBGKILL: /* kill the parent */
366 if (!iswiz())
367 break;
368 if (kill(MotherPid, SIGTERM) >= 0)
369 message("200", "Mother is dead");
370 else
371 message("500", "Can't kill Mom");
372 break;
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);
381 if (strcmp(WizWord, crypt(p, seed)) == 0)
382 {
383 IsWiz = TRUE;
384 message("200", "Please pass, oh mighty wizard");
385 break;
386 }
387 }
388 message("500", "You are no wizard!");
389 break;
390# endif DEBUG
391
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}
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
473 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
474 {
475 /* no help */
476 errno = 0;
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");
506 (void) fclose(hf);
507}
508\f/*
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
524#ifdef DEBUG
525
526bool
527iswiz()
528{
529 if (!IsWiz)
530 message("500", "Mere mortals musn't mutter that mantra");
531 return (IsWiz);
532}
533
534#endif DEBUG
535\f/*
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
554 if (!OneXact)
555 {
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;
565
566 /* parent -- wait for child to complete */
567 st = waitfor(childpid);
568 if (st == -1)
569 syserr("%s: lost child", label);
570
571 /* if we exited on a QUIT command, complete the process */
572 if (st == (EX_QUIT << 8))
573 finis();
574
575 return (1);
576 }
577 else
578 {
579 /* child */
580 InChild = TRUE;
581 }
582 }
583
584 /* child (or ONEX command specified) */
585 clearenvelope(CurEnv);
586
587 /* open alias database */
588 initaliases(AliasFile, FALSE);
589
590 return (0);
591}
592\f/*
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}
644
645# endif SMTP