1 # include "sendmail.h"
2 
3 # ifndef SMTP
4 SCCSID(@(#)srvrsmtp.c	3.33		09/26/82	(no SMTP));
5 # else SMTP
6 
7 SCCSID(@(#)srvrsmtp.c	3.33		09/26/82);
8 
9 /*
10 **  SMTP -- run the SMTP protocol.
11 **
12 **	Parameters:
13 **		none.
14 **
15 **	Returns:
16 **		never.
17 **
18 **	Side Effects:
19 **		Reads commands from the input channel and processes
20 **			them.
21 */
22 
23 struct cmd
24 {
25 	char	*cmdname;	/* command name */
26 	int	cmdcode;	/* internal code, see below */
27 };
28 
29 /* values for cmdcode */
30 # define CMDERROR	0	/* bad command */
31 # define CMDMAIL	1	/* mail -- designate sender */
32 # define CMDRCPT	2	/* rcpt -- designate recipient */
33 # define CMDDATA	3	/* data -- send message text */
34 # define CMDRSET	5	/* rset -- reset state */
35 # define CMDVRFY	6	/* vrfy -- verify address */
36 # define CMDHELP	7	/* help -- give usage info */
37 # define CMDNOOP	8	/* noop -- do nothing */
38 # define CMDQUIT	9	/* quit -- close connection and die */
39 # define CMDMRSQ	10	/* mrsq -- for old mtp compat only */
40 # define CMDHELO	11	/* helo -- be polite */
41 # define CMDDBGSHOWQ	12	/* _showq -- show send queue (DEBUG) */
42 # define CMDDBGDEBUG	13	/* _debug -- set debug mode */
43 # define CMDDBGVERBOSE	14	/* _verbose -- go into verbose mode */
44 # define CMDDBGKILL	15	/* _kill -- kill sendmail */
45 
46 static struct cmd	CmdTab[] =
47 {
48 	"mail",		CMDMAIL,
49 	"rcpt",		CMDRCPT,
50 	"mrcp",		CMDRCPT,	/* for old MTP compatability */
51 	"data",		CMDDATA,
52 	"rset",		CMDRSET,
53 	"vrfy",		CMDVRFY,
54 	"expn",		CMDVRFY,
55 	"help",		CMDHELP,
56 	"noop",		CMDNOOP,
57 	"quit",		CMDQUIT,
58 	"mrsq",		CMDMRSQ,
59 	"helo",		CMDHELO,
60 # ifdef DEBUG
61 	"_showq",	CMDDBGSHOWQ,
62 	"_debug",	CMDDBGDEBUG,
63 	"_verbose",	CMDDBGVERBOSE,
64 	"_kill",	CMDDBGKILL,
65 # endif DEBUG
66 	NULL,		CMDERROR,
67 };
68 
69 smtp()
70 {
71 	char inp[MAXLINE];
72 	register char *p;
73 	struct cmd *c;
74 	char *cmd;
75 	extern char *skipword();
76 	extern bool sameword();
77 	bool hasmail;			/* mail command received */
78 	int rcps;			/* number of recipients */
79 	auto ADDRESS *vrfyqueue;
80 	extern char Version[];
81 	extern tick();
82 
83 	hasmail = FALSE;
84 	rcps = 0;
85 	if (OutChannel != stdout)
86 	{
87 		/* arrange for debugging output to go to remote host */
88 		(void) close(1);
89 		(void) dup(fileno(OutChannel));
90 	}
91 	message("220", "%s Sendmail %s ready at %s", HostName,
92 			Version, arpadate(NULL));
93 	(void) setjmp(TopFrame);
94 	QuickAbort = FALSE;
95 	for (;;)
96 	{
97 		/* setup for the read */
98 		CurEnv->e_to = NULL;
99 		Errors = 0;
100 		(void) fflush(stdout);
101 
102 		/* read the input line */
103 		p = sfgets(inp, sizeof inp, InChannel);
104 
105 		/* handle errors */
106 		if (p == NULL)
107 		{
108 			/* end of file, just die */
109 			message("421", "%s Lost input channel", HostName);
110 			finis();
111 		}
112 
113 		/* clean up end of line */
114 		fixcrlf(inp, TRUE);
115 
116 		/* echo command to transcript */
117 		fprintf(Xscript, "<<< %s\n", inp);
118 
119 		/* break off command */
120 		for (p = inp; isspace(*p); p++)
121 			continue;
122 		cmd = p;
123 		while (*++p != '\0' && !isspace(*p))
124 			continue;
125 		if (*p != '\0')
126 			*p++ = '\0';
127 
128 		/* decode command */
129 		for (c = CmdTab; c->cmdname != NULL; c++)
130 		{
131 			if (sameword(c->cmdname, cmd))
132 				break;
133 		}
134 
135 		/* process command */
136 		switch (c->cmdcode)
137 		{
138 		  case CMDHELO:		/* hello -- introduce yourself */
139 			define('s', newstr(p));
140 			message("250", "%s Hello %s, pleased to meet you",
141 				HostName, p);
142 			break;
143 
144 		  case CMDMAIL:		/* mail -- designate sender */
145 			if (hasmail)
146 			{
147 				message("503", "Sender already specified");
148 				break;
149 			}
150 			p = skipword(p, "from");
151 			if (p == NULL)
152 				break;
153 			if (index(p, ',') != NULL)
154 			{
155 				message("501", "Source routing not implemented");
156 				Errors++;
157 				break;
158 			}
159 			setsender(p);
160 			if (Errors == 0)
161 			{
162 				message("250", "Sender ok");
163 				hasmail = TRUE;
164 			}
165 			break;
166 
167 		  case CMDRCPT:		/* rcpt -- designate recipient */
168 			p = skipword(p, "to");
169 			if (p == NULL)
170 				break;
171 			sendto(p, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
172 			if (Errors == 0)
173 			{
174 				message("250", "%s... Recipient ok", p);
175 				rcps++;
176 			}
177 			break;
178 
179 		  case CMDDATA:		/* data -- text of mail */
180 			if (!hasmail)
181 			{
182 				message("503", "Need MAIL command");
183 				break;
184 			}
185 			else if (rcps <= 0)
186 			{
187 				message("503", "Need RCPT (recipient)");
188 				break;
189 			}
190 
191 			/* collect the text of the message */
192 			collect(TRUE);
193 			if (Errors != 0)
194 				break;
195 
196 			/*
197 			**  Arrange to send to everyone.
198 			**	If sending to multiple people, mail back
199 			**		errors rather than reporting directly.
200 			**	In any case, don't mail back errors for
201 			**		anything that has happened up to
202 			**		now (the other end will do this).
203 			**	Then send to everyone.
204 			**	Finally give a reply code.  If an error has
205 			**		already been given, don't mail a
206 			**		message back.
207 			**	We goose error returns by clearing FatalErrors.
208 			*/
209 
210 			if (rcps != 1)
211 				HoldErrs = MailBack = TRUE;
212 			FatalErrors = FALSE;
213 
214 			/* send to all recipients */
215 			sendall(CurEnv, FALSE);
216 			CurEnv->e_to = NULL;
217 
218 			/* issue success if appropriate and reset */
219 			if (Errors == 0 || HoldErrs)
220 			{
221 				HoldErrs = FALSE;
222 				message("250", "Sent");
223 			}
224 			else
225 				FatalErrors = FALSE;
226 			break;
227 
228 		  case CMDRSET:		/* rset -- reset state */
229 			message("250", "Reset state");
230 			finis();
231 
232 		  case CMDVRFY:		/* vrfy -- verify address */
233 			vrfyqueue = NULL;
234 			QuickAbort = TRUE;
235 			sendto(p, (ADDRESS *) NULL, &vrfyqueue);
236 			if (Errors != 0)
237 				break;
238 			while (vrfyqueue != NULL)
239 			{
240 				register ADDRESS *a = vrfyqueue->q_next;
241 				char *code;
242 
243 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
244 					a = a->q_next;
245 
246 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
247 				{
248 					if (a != NULL)
249 						code = "250-";
250 					else
251 						code = "250";
252 					if (vrfyqueue->q_fullname == NULL)
253 						message(code, "<%s>", vrfyqueue->q_paddr);
254 					else
255 						message(code, "%s <%s>",
256 						    vrfyqueue->q_fullname, vrfyqueue->q_paddr);
257 				}
258 				else if (a == NULL)
259 					message("554", "Self destructive alias loop");
260 				vrfyqueue = a;
261 			}
262 			break;
263 
264 		  case CMDHELP:		/* help -- give user info */
265 			if (*p == '\0')
266 				p = "SMTP";
267 			help(p);
268 			break;
269 
270 		  case CMDNOOP:		/* noop -- do nothing */
271 			message("200", "OK");
272 			break;
273 
274 		  case CMDQUIT:		/* quit -- leave mail */
275 			message("221", "%s closing connection", HostName);
276 			finis();
277 
278 		  case CMDMRSQ:		/* mrsq -- negotiate protocol */
279 			if (*p == 'R' || *p == 'T')
280 			{
281 				/* recipients first or text first */
282 				message("200", "%c ok, please continue", *p);
283 			}
284 			else if (*p == '?')
285 			{
286 				/* what do I prefer?  anything, anytime */
287 				message("215", "R Recipients first is my choice");
288 			}
289 			else if (*p == '\0')
290 			{
291 				/* no meaningful scheme */
292 				message("200", "okey dokie boobie");
293 			}
294 			else
295 			{
296 				/* bad argument */
297 				message("504", "Scheme unknown");
298 			}
299 			break;
300 
301 # ifdef DEBUG
302 		  case CMDDBGSHOWQ:	/* show queues */
303 			printf("Send Queue=");
304 			printaddr(CurEnv->e_sendqueue, TRUE);
305 			break;
306 
307 		  case CMDDBGDEBUG:	/* set debug mode */
308 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
309 			tTflag(p);
310 			message("200", "Debug set");
311 			break;
312 
313 		  case CMDDBGVERBOSE:	/* set verbose mode */
314 			Verbose = TRUE;
315 			message("200", "Verbose mode");
316 			break;
317 
318 		  case CMDDBGKILL:	/* kill the parent */
319 			if (kill(MotherPid, SIGTERM) >= 0)
320 				message("200", "Mother is dead");
321 			else
322 				message("500", "Can't kill Mom");
323 			break;
324 # endif DEBUG
325 
326 		  case CMDERROR:	/* unknown command */
327 			message("500", "Command unrecognized");
328 			break;
329 
330 		  default:
331 			syserr("smtp: unknown code %d", c->cmdcode);
332 			break;
333 		}
334 	}
335 }
336 /*
337 **  SKIPWORD -- skip a fixed word.
338 **
339 **	Parameters:
340 **		p -- place to start looking.
341 **		w -- word to skip.
342 **
343 **	Returns:
344 **		p following w.
345 **		NULL on error.
346 **
347 **	Side Effects:
348 **		clobbers the p data area.
349 */
350 
351 static char *
352 skipword(p, w)
353 	register char *p;
354 	char *w;
355 {
356 	register char *q;
357 	extern bool sameword();
358 
359 	/* find beginning of word */
360 	while (isspace(*p))
361 		p++;
362 	q = p;
363 
364 	/* find end of word */
365 	while (*p != '\0' && *p != ':' && !isspace(*p))
366 		p++;
367 	while (isspace(*p))
368 		*p++ = '\0';
369 	if (*p != ':')
370 	{
371 	  syntax:
372 		message("501", "Syntax error");
373 		Errors++;
374 		return (NULL);
375 	}
376 	*p++ = '\0';
377 	while (isspace(*p))
378 		p++;
379 
380 	/* see if the input word matches desired word */
381 	if (!sameword(q, w))
382 		goto syntax;
383 
384 	return (p);
385 }
386 /*
387 **  HELP -- implement the HELP command.
388 **
389 **	Parameters:
390 **		topic -- the topic we want help for.
391 **
392 **	Returns:
393 **		none.
394 **
395 **	Side Effects:
396 **		outputs the help file to message output.
397 */
398 
399 help(topic)
400 	char *topic;
401 {
402 	register FILE *hf;
403 	int len;
404 	char buf[MAXLINE];
405 	bool noinfo;
406 
407 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
408 	{
409 		/* no help */
410 		message("502", "HELP not implemented");
411 		return;
412 	}
413 
414 	len = strlen(topic);
415 	makelower(topic);
416 	noinfo = TRUE;
417 
418 	while (fgets(buf, sizeof buf, hf) != NULL)
419 	{
420 		if (strncmp(buf, topic, len) == 0)
421 		{
422 			register char *p;
423 
424 			p = index(buf, '\t');
425 			if (p == NULL)
426 				p = buf;
427 			else
428 				p++;
429 			fixcrlf(p, TRUE);
430 			message("214-", p);
431 			noinfo = FALSE;
432 		}
433 	}
434 
435 	if (noinfo)
436 		message("504", "HELP topic unknown");
437 	else
438 		message("214", "End of HELP info");
439 	(void) fclose(hf);
440 }
441 
442 # endif SMTP
443