adding GNU dc ("desk calculator")
[unix-history] / usr.sbin / sendmail / src / srvrsmtp.c
CommitLineData
15637ed4
RG
1/*
2 * Copyright (c) 1983 Eric P. Allman
3 * Copyright (c) 1988 Regents of the University of California.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 * must display the following acknowledgement:
16 * This product includes software developed by the University of
17 * California, Berkeley and its contributors.
18 * 4. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35# include "sendmail.h"
36
37#ifndef lint
38#ifdef SMTP
39static char sccsid[] = "@(#)srvrsmtp.c 5.31 (Berkeley) 5/10/91 (with SMTP)";
40#else
41static char sccsid[] = "@(#)srvrsmtp.c 5.31 (Berkeley) 5/10/91 (without SMTP)";
42#endif
43#endif /* not lint */
44
45# include <errno.h>
46# include <signal.h>
47
48# ifdef SMTP
49
50/*
51** SMTP -- run the SMTP protocol.
52**
53** Parameters:
54** none.
55**
56** Returns:
57** never.
58**
59** Side Effects:
60** Reads commands from the input channel and processes
61** them.
62*/
63
64struct cmd
65{
66 char *cmdname; /* command name */
67 int cmdcode; /* internal code, see below */
68};
69
70/* values for cmdcode */
71# define CMDERROR 0 /* bad command */
72# define CMDMAIL 1 /* mail -- designate sender */
73# define CMDRCPT 2 /* rcpt -- designate recipient */
74# define CMDDATA 3 /* data -- send message text */
75# define CMDRSET 4 /* rset -- reset state */
76# define CMDVRFY 5 /* vrfy -- verify address */
77# define CMDHELP 6 /* help -- give usage info */
78# define CMDNOOP 7 /* noop -- do nothing */
79# define CMDQUIT 8 /* quit -- close connection and die */
80# define CMDHELO 9 /* helo -- be polite */
81# define CMDONEX 10 /* onex -- sending one transaction only */
82# define CMDVERB 11 /* verb -- go into verbose mode */
83/* debugging-only commands, only enabled if SMTPDEBUG is defined */
84# define CMDDBGQSHOW 12 /* showq -- show send queue */
85# define CMDDBGDEBUG 13 /* debug -- set debug mode */
86
87static struct cmd CmdTab[] =
88{
89 "mail", CMDMAIL,
90 "rcpt", CMDRCPT,
91 "data", CMDDATA,
92 "rset", CMDRSET,
93 "vrfy", CMDVRFY,
94 "expn", CMDVRFY,
95 "help", CMDHELP,
96 "noop", CMDNOOP,
97 "quit", CMDQUIT,
98 "helo", CMDHELO,
99 "verb", CMDVERB,
100 "onex", CMDONEX,
101 /*
102 * remaining commands are here only
103 * to trap and log attempts to use them
104 */
105 "showq", CMDDBGQSHOW,
106 "debug", CMDDBGDEBUG,
107 NULL, CMDERROR,
108};
109
110bool InChild = FALSE; /* true if running in a subprocess */
111bool OneXact = FALSE; /* one xaction only this run */
112
113#define EX_QUIT 22 /* special code for QUIT command */
114
115smtp()
116{
117 register char *p;
118 register struct cmd *c;
119 char *cmd;
120 static char *skipword();
121 bool hasmail; /* mail command received */
122 auto ADDRESS *vrfyqueue;
123 ADDRESS *a;
124 char *sendinghost;
125 char inp[MAXLINE];
126 char cmdbuf[100];
127 extern char Version[];
128 extern char *macvalue();
129 extern ADDRESS *recipient();
130 extern ENVELOPE BlankEnvelope;
131 extern ENVELOPE *newenvelope();
132
133 hasmail = FALSE;
134 if (OutChannel != stdout)
135 {
136 /* arrange for debugging output to go to remote host */
137 (void) close(1);
138 (void) dup(fileno(OutChannel));
139 }
140 settime();
141 if (RealHostName != NULL)
142 {
143 CurHostName = RealHostName;
144 setproctitle("srvrsmtp %s", CurHostName);
145 }
146 else
147 {
148 /* this must be us!! */
149 CurHostName = MyHostName;
150 }
151 expand("\001e", inp, &inp[sizeof inp], CurEnv);
152 message("220", inp);
153 SmtpPhase = "startup";
154 sendinghost = NULL;
155 for (;;)
156 {
157 /* arrange for backout */
158 if (setjmp(TopFrame) > 0 && InChild)
159 finis();
160 QuickAbort = FALSE;
161 HoldErrs = FALSE;
162
163 /* setup for the read */
164 CurEnv->e_to = NULL;
165 Errors = 0;
166 (void) fflush(stdout);
167
168 /* read the input line */
169 p = sfgets(inp, sizeof inp, InChannel);
170
171 /* handle errors */
172 if (p == NULL)
173 {
174 /* end of file, just die */
175 message("421", "%s Lost input channel from %s",
176 MyHostName, CurHostName);
177 finis();
178 }
179
180 /* clean up end of line */
181 fixcrlf(inp, TRUE);
182
183 /* echo command to transcript */
184 if (CurEnv->e_xfp != NULL)
185 fprintf(CurEnv->e_xfp, "<<< %s\n", inp);
186
187 /* break off command */
188 for (p = inp; isspace(*p); p++)
189 continue;
190 cmd = p;
191 for (cmd = cmdbuf; *p != '\0' && !isspace(*p); )
192 *cmd++ = *p++;
193 *cmd = '\0';
194
195 /* throw away leading whitespace */
196 while (isspace(*p))
197 p++;
198
199 /* decode command */
200 for (c = CmdTab; c->cmdname != NULL; c++)
201 {
202 if (!strcasecmp(c->cmdname, cmdbuf))
203 break;
204 }
205
206 /* process command */
207 switch (c->cmdcode)
208 {
209 case CMDHELO: /* hello -- introduce yourself */
210 SmtpPhase = "HELO";
211 setproctitle("%s: %s", CurHostName, inp);
212 if (!strcasecmp(p, MyHostName))
213 {
214 /*
215 * didn't know about alias,
216 * or connected to an echo server
217 */
218 message("553", "%s config error: mail loops back to myself",
219 MyHostName);
220 break;
221 }
222 if (RealHostName != NULL && strcasecmp(p, RealHostName))
223 {
224 char hostbuf[MAXNAME];
225
226 (void) sprintf(hostbuf, "%s (%s)", p, RealHostName);
227 sendinghost = newstr(hostbuf);
228 }
229 else
230 sendinghost = newstr(p);
231 message("250", "%s Hello %s, pleased to meet you",
232 MyHostName, sendinghost);
233 break;
234
235 case CMDMAIL: /* mail -- designate sender */
236 SmtpPhase = "MAIL";
237
238 /* force a sending host even if no HELO given */
239 if (RealHostName != NULL && macvalue('s', CurEnv) == NULL)
240 sendinghost = RealHostName;
241
242 /* check for validity of this command */
243 if (hasmail)
244 {
245 message("503", "Sender already specified");
246 break;
247 }
248 if (InChild)
249 {
250 errno = 0;
251 syserr("Nested MAIL command");
252 exit(0);
253 }
254
255 /* fork a subprocess to process this command */
256 if (runinchild("SMTP-MAIL") > 0)
257 break;
258 define('s', sendinghost, CurEnv);
259 define('r', "SMTP", CurEnv);
260 initsys();
261 setproctitle("%s %s: %s", CurEnv->e_id,
262 CurHostName, inp);
263
264 /* child -- go do the processing */
265 p = skipword(p, "from");
266 if (p == NULL)
267 break;
268 setsender(p);
269 if (Errors == 0)
270 {
271 message("250", "Sender ok");
272 hasmail = TRUE;
273 }
274 else if (InChild)
275 finis();
276 break;
277
278 case CMDRCPT: /* rcpt -- designate recipient */
279 SmtpPhase = "RCPT";
280 setproctitle("%s %s: %s", CurEnv->e_id,
281 CurHostName, inp);
282 if (setjmp(TopFrame) > 0)
283 {
284 CurEnv->e_flags &= ~EF_FATALERRS;
285 break;
286 }
287 QuickAbort = TRUE;
288 p = skipword(p, "to");
289 if (p == NULL)
290 break;
291 a = parseaddr(p, (ADDRESS *) NULL, 1, '\0');
292 if (a == NULL)
293 break;
294 a->q_flags |= QPRIMARY;
295 a = recipient(a, &CurEnv->e_sendqueue);
296 if (Errors != 0)
297 break;
298
299 /* no errors during parsing, but might be a duplicate */
300 CurEnv->e_to = p;
301 if (!bitset(QBADADDR, a->q_flags))
302 message("250", "Recipient ok");
303 else
304 {
305 /* punt -- should keep message in ADDRESS.... */
306 message("550", "Addressee unknown");
307 }
308 CurEnv->e_to = NULL;
309 break;
310
311 case CMDDATA: /* data -- text of mail */
312 SmtpPhase = "DATA";
313 if (!hasmail)
314 {
315 message("503", "Need MAIL command");
316 break;
317 }
318 else if (CurEnv->e_nrcpts <= 0)
319 {
320 message("503", "Need RCPT (recipient)");
321 break;
322 }
323
324 /* collect the text of the message */
325 SmtpPhase = "collect";
326 setproctitle("%s %s: %s", CurEnv->e_id,
327 CurHostName, inp);
328 collect(TRUE);
329 if (Errors != 0)
330 break;
331
332 /*
333 ** Arrange to send to everyone.
334 ** If sending to multiple people, mail back
335 ** errors rather than reporting directly.
336 ** In any case, don't mail back errors for
337 ** anything that has happened up to
338 ** now (the other end will do this).
339 ** Truncate our transcript -- the mail has gotten
340 ** to us successfully, and if we have
341 ** to mail this back, it will be easier
342 ** on the reader.
343 ** Then send to everyone.
344 ** Finally give a reply code. If an error has
345 ** already been given, don't mail a
346 ** message back.
347 ** We goose error returns by clearing error bit.
348 */
349
350 SmtpPhase = "delivery";
351 if (CurEnv->e_nrcpts != 1)
352 {
353 HoldErrs = TRUE;
354 ErrorMode = EM_MAIL;
355 }
356 CurEnv->e_flags &= ~EF_FATALERRS;
357 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp);
358
359 /* send to all recipients */
360 sendall(CurEnv, SM_DEFAULT);
361 CurEnv->e_to = NULL;
362
363 /* save statistics */
364 markstats(CurEnv, (ADDRESS *) NULL);
365
366 /* issue success if appropriate and reset */
367 if (Errors == 0 || HoldErrs)
368 message("250", "Ok");
369 else
370 CurEnv->e_flags &= ~EF_FATALERRS;
371
372 /* if in a child, pop back to our parent */
373 if (InChild)
374 finis();
375
376 /* clean up a bit */
377 hasmail = 0;
378 dropenvelope(CurEnv);
379 CurEnv = newenvelope(CurEnv);
380 CurEnv->e_flags = BlankEnvelope.e_flags;
381 break;
382
383 case CMDRSET: /* rset -- reset state */
384 message("250", "Reset state");
385 if (InChild)
386 finis();
387 break;
388
389 case CMDVRFY: /* vrfy -- verify address */
390 if (runinchild("SMTP-VRFY") > 0)
391 break;
392 setproctitle("%s: %s", CurHostName, inp);
393 vrfyqueue = NULL;
394 QuickAbort = TRUE;
395 sendtolist(p, (ADDRESS *) NULL, &vrfyqueue);
396 if (Errors != 0)
397 {
398 if (InChild)
399 finis();
400 break;
401 }
402 while (vrfyqueue != NULL)
403 {
404 register ADDRESS *a = vrfyqueue->q_next;
405 char *code;
406
407 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
408 a = a->q_next;
409
410 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
411 {
412 if (a != NULL)
413 code = "250-";
414 else
415 code = "250";
416 if (vrfyqueue->q_fullname == NULL)
417 message(code, "<%s>", vrfyqueue->q_paddr);
418 else
419 message(code, "%s <%s>",
420 vrfyqueue->q_fullname, vrfyqueue->q_paddr);
421 }
422 else if (a == NULL)
423 message("554", "Self destructive alias loop");
424 vrfyqueue = a;
425 }
426 if (InChild)
427 finis();
428 break;
429
430 case CMDHELP: /* help -- give user info */
431 help(p);
432 break;
433
434 case CMDNOOP: /* noop -- do nothing */
435 message("200", "OK");
436 break;
437
438 case CMDQUIT: /* quit -- leave mail */
439 message("221", "%s closing connection", MyHostName);
440 if (InChild)
441 ExitStat = EX_QUIT;
442 finis();
443
444 case CMDVERB: /* set verbose mode */
445 Verbose = TRUE;
446 SendMode = SM_DELIVER;
447 message("200", "Verbose mode");
448 break;
449
450 case CMDONEX: /* doing one transaction only */
451 OneXact = TRUE;
452 message("200", "Only one transaction");
453 break;
454
455# ifdef SMTPDEBUG
456 case CMDDBGQSHOW: /* show queues */
457 printf("Send Queue=");
458 printaddr(CurEnv->e_sendqueue, TRUE);
459 break;
460
461 case CMDDBGDEBUG: /* set debug mode */
462 tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
463 tTflag(p);
464 message("200", "Debug set");
465 break;
466
467# else /* not SMTPDEBUG */
468
469 case CMDDBGQSHOW: /* show queues */
470 case CMDDBGDEBUG: /* set debug mode */
471# ifdef LOG
472 if (RealHostName != NULL && LogLevel > 0)
473 syslog(LOG_NOTICE,
474 "\"%s\" command from %s (%s)\n",
475 c->cmdname, RealHostName,
476 inet_ntoa(RealHostAddr.sin_addr));
477# endif
478 /* FALL THROUGH */
479# endif /* SMTPDEBUG */
480
481 case CMDERROR: /* unknown command */
482 message("500", "Command unrecognized");
483 break;
484
485 default:
486 errno = 0;
487 syserr("smtp: unknown code %d", c->cmdcode);
488 break;
489 }
490 }
491}
492\f/*
493** SKIPWORD -- skip a fixed word.
494**
495** Parameters:
496** p -- place to start looking.
497** w -- word to skip.
498**
499** Returns:
500** p following w.
501** NULL on error.
502**
503** Side Effects:
504** clobbers the p data area.
505*/
506
507static char *
508skipword(p, w)
509 register char *p;
510 char *w;
511{
512 register char *q;
513
514 /* find beginning of word */
515 while (isspace(*p))
516 p++;
517 q = p;
518
519 /* find end of word */
520 while (*p != '\0' && *p != ':' && !isspace(*p))
521 p++;
522 while (isspace(*p))
523 *p++ = '\0';
524 if (*p != ':')
525 {
526 syntax:
527 message("501", "Syntax error");
528 Errors++;
529 return (NULL);
530 }
531 *p++ = '\0';
532 while (isspace(*p))
533 p++;
534
535 /* see if the input word matches desired word */
536 if (strcasecmp(q, w))
537 goto syntax;
538
539 return (p);
540}
541\f/*
542** HELP -- implement the HELP command.
543**
544** Parameters:
545** topic -- the topic we want help for.
546**
547** Returns:
548** none.
549**
550** Side Effects:
551** outputs the help file to message output.
552*/
553
554help(topic)
555 char *topic;
556{
557 register FILE *hf;
558 int len;
559 char buf[MAXLINE];
560 bool noinfo;
561
562 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
563 {
564 /* no help */
565 errno = 0;
566 message("502", "HELP not implemented");
567 return;
568 }
569
570 if (topic == NULL || *topic == '\0')
571 topic = "smtp";
572 else
573 makelower(topic);
574
575 len = strlen(topic);
576 noinfo = TRUE;
577
578 while (fgets(buf, sizeof buf, hf) != NULL)
579 {
580 if (strncmp(buf, topic, len) == 0)
581 {
582 register char *p;
583
584 p = index(buf, '\t');
585 if (p == NULL)
586 p = buf;
587 else
588 p++;
589 fixcrlf(p, TRUE);
590 message("214-", p);
591 noinfo = FALSE;
592 }
593 }
594
595 if (noinfo)
596 message("504", "HELP topic unknown");
597 else
598 message("214", "End of HELP info");
599 (void) fclose(hf);
600}
601\f/*
602** RUNINCHILD -- return twice -- once in the child, then in the parent again
603**
604** Parameters:
605** label -- a string used in error messages
606**
607** Returns:
608** zero in the child
609** one in the parent
610**
611** Side Effects:
612** none.
613*/
614
615runinchild(label)
616 char *label;
617{
618 int childpid;
619
620 if (!OneXact)
621 {
622 childpid = dofork();
623 if (childpid < 0)
624 {
625 syserr("%s: cannot fork", label);
626 return (1);
627 }
628 if (childpid > 0)
629 {
630 auto int st;
631
632 /* parent -- wait for child to complete */
633 st = waitfor(childpid);
634 if (st == -1)
635 syserr("%s: lost child", label);
636
637 /* if we exited on a QUIT command, complete the process */
638 if (st == (EX_QUIT << 8))
639 finis();
640
641 return (1);
642 }
643 else
644 {
645 /* child */
646 InChild = TRUE;
647 QuickAbort = FALSE;
648 clearenvelope(CurEnv, FALSE);
649 }
650 }
651
652 /* open alias database */
653 initaliases(AliasFile, FALSE);
654
655 return (0);
656}
657
658# endif SMTP