1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988 Regents of the University of California.
4  * All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 # include "sendmail.h"
10 
11 #ifndef lint
12 #ifdef SMTP
13 static char sccsid[] = "@(#)srvrsmtp.c	6.21 (Berkeley) 03/01/93 (with SMTP)";
14 #else
15 static char sccsid[] = "@(#)srvrsmtp.c	6.21 (Berkeley) 03/01/93 (without SMTP)";
16 #endif
17 #endif /* not lint */
18 
19 # include <errno.h>
20 # include <signal.h>
21 
22 # ifdef SMTP
23 
24 /*
25 **  SMTP -- run the SMTP protocol.
26 **
27 **	Parameters:
28 **		none.
29 **
30 **	Returns:
31 **		never.
32 **
33 **	Side Effects:
34 **		Reads commands from the input channel and processes
35 **			them.
36 */
37 
38 struct cmd
39 {
40 	char	*cmdname;	/* command name */
41 	int	cmdcode;	/* internal code, see below */
42 };
43 
44 /* values for cmdcode */
45 # define CMDERROR	0	/* bad command */
46 # define CMDMAIL	1	/* mail -- designate sender */
47 # define CMDRCPT	2	/* rcpt -- designate recipient */
48 # define CMDDATA	3	/* data -- send message text */
49 # define CMDRSET	4	/* rset -- reset state */
50 # define CMDVRFY	5	/* vrfy -- verify address */
51 # define CMDEXPN	6	/* expn -- expand address */
52 # define CMDNOOP	7	/* noop -- do nothing */
53 # define CMDQUIT	8	/* quit -- close connection and die */
54 # define CMDHELO	9	/* helo -- be polite */
55 # define CMDHELP	10	/* help -- give usage info */
56 # define CMDEHLO	11	/* ehlo -- extended helo (RFC 1425) */
57 /* non-standard commands */
58 # define CMDONEX	16	/* onex -- sending one transaction only */
59 # define CMDVERB	17	/* verb -- go into verbose mode */
60 /* debugging-only commands, only enabled if SMTPDEBUG is defined */
61 # define CMDDBGQSHOW	24	/* showq -- show send queue */
62 # define CMDDBGDEBUG	25	/* debug -- set debug mode */
63 
64 static struct cmd	CmdTab[] =
65 {
66 	"mail",		CMDMAIL,
67 	"rcpt",		CMDRCPT,
68 	"data",		CMDDATA,
69 	"rset",		CMDRSET,
70 	"vrfy",		CMDVRFY,
71 	"expn",		CMDEXPN,
72 	"help",		CMDHELP,
73 	"noop",		CMDNOOP,
74 	"quit",		CMDQUIT,
75 	"helo",		CMDHELO,
76 	"ehlo",		CMDEHLO,
77 	"verb",		CMDVERB,
78 	"onex",		CMDONEX,
79 	/*
80 	 * remaining commands are here only
81 	 * to trap and log attempts to use them
82 	 */
83 	"showq",	CMDDBGQSHOW,
84 	"debug",	CMDDBGDEBUG,
85 	NULL,		CMDERROR,
86 };
87 
88 bool	InChild = FALSE;		/* true if running in a subprocess */
89 bool	OneXact = FALSE;		/* one xaction only this run */
90 
91 #define EX_QUIT		22		/* special code for QUIT command */
92 
93 smtp(e)
94 	register ENVELOPE *e;
95 {
96 	register char *p;
97 	register struct cmd *c;
98 	char *cmd;
99 	static char *skipword();
100 	auto ADDRESS *vrfyqueue;
101 	ADDRESS *a;
102 	char *sendinghost;
103 	bool gotmail;			/* mail command received */
104 	bool gothello;			/* helo command received */
105 	bool vrfy;			/* set if this is a vrfy command */
106 	char *protocol;			/* sending protocol */
107 	long msize;			/* approximate maximum message size */
108 	auto char *delimptr;
109 	char inp[MAXLINE];
110 	char cmdbuf[MAXLINE];
111 	extern char Version[];
112 	extern char *macvalue();
113 	extern ADDRESS *recipient();
114 	extern ENVELOPE BlankEnvelope;
115 	extern ENVELOPE *newenvelope();
116 
117 	if (OutChannel != stdout)
118 	{
119 		/* arrange for debugging output to go to remote host */
120 		(void) close(1);
121 		(void) dup(fileno(OutChannel));
122 	}
123 	settime(e);
124 	CurHostName = RealHostName;
125 	setproctitle("srvrsmtp %s", CurHostName);
126 	expand("\201e", inp, &inp[sizeof inp], e);
127 	message("220 %s", inp);
128 	SmtpPhase = "startup";
129 	sendinghost = NULL;
130 	protocol = NULL;
131 	gothello = FALSE;
132 	gotmail = FALSE;
133 	for (;;)
134 	{
135 		/* arrange for backout */
136 		if (setjmp(TopFrame) > 0 && InChild)
137 			finis();
138 		QuickAbort = FALSE;
139 		HoldErrs = FALSE;
140 		LogUsrErrs = FALSE;
141 		e->e_flags &= ~EF_VRFYONLY;
142 
143 		/* setup for the read */
144 		e->e_to = NULL;
145 		Errors = 0;
146 		(void) fflush(stdout);
147 
148 		/* read the input line */
149 		p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand);
150 
151 		/* handle errors */
152 		if (p == NULL)
153 		{
154 			/* end of file, just die */
155 			message("421 %s Lost input channel from %s",
156 				MyHostName, CurHostName);
157 #ifdef LOG
158 			if (LogLevel > 1)
159 				syslog(LOG_NOTICE, "lost input channel from %s",
160 					CurHostName);
161 #endif
162 			if (InChild)
163 				ExitStat = EX_QUIT;
164 			finis();
165 		}
166 
167 		/* clean up end of line */
168 		fixcrlf(inp, TRUE);
169 
170 		/* echo command to transcript */
171 		if (e->e_xfp != NULL)
172 			fprintf(e->e_xfp, "<<< %s\n", inp);
173 
174 		/* break off command */
175 		for (p = inp; isascii(*p) && isspace(*p); p++)
176 			continue;
177 		cmd = cmdbuf;
178 		while (*p != '\0' &&
179 		       !(isascii(*p) && isspace(*p)) &&
180 		       cmd < &cmdbuf[sizeof cmdbuf - 2])
181 			*cmd++ = *p++;
182 		*cmd = '\0';
183 
184 		/* throw away leading whitespace */
185 		while (isascii(*p) && isspace(*p))
186 			p++;
187 
188 		/* decode command */
189 		for (c = CmdTab; c->cmdname != NULL; c++)
190 		{
191 			if (!strcasecmp(c->cmdname, cmdbuf))
192 				break;
193 		}
194 
195 		/* reset errors */
196 		errno = 0;
197 
198 		/* process command */
199 		switch (c->cmdcode)
200 		{
201 		  case CMDHELO:		/* hello -- introduce yourself */
202 		  case CMDEHLO:		/* extended hello */
203 			if (c->cmdcode == CMDEHLO)
204 			{
205 				protocol = "ESMTP";
206 				SmtpPhase = "EHLO";
207 			}
208 			else
209 			{
210 				protocol = "SMTP";
211 				SmtpPhase = "HELO";
212 			}
213 			setproctitle("%s: %s", CurHostName, inp);
214 			if (strcasecmp(p, MyHostName) == 0)
215 			{
216 				/*
217 				**  Didn't know about alias or MX,
218 				**  or connected to an echo server
219 				*/
220 
221 				message("553 %s config error: mail loops back to myself",
222 					MyHostName);
223 				break;
224 			}
225 			if (strcasecmp(p, RealHostName) != 0)
226 			{
227 				char hostbuf[MAXNAME];
228 
229 				(void) sprintf(hostbuf, "%s (%s)", p, RealHostName);
230 				sendinghost = newstr(hostbuf);
231 			}
232 			else
233 				sendinghost = newstr(p);
234 
235 			/* send ext. message -- old systems must ignore */
236 			message("250-%s Hello %s, pleased to meet you",
237 				MyHostName, sendinghost);
238 			if (!bitset(PRIV_NOEXPN, PrivacyFlags))
239 				message("250-EXPN");
240 			message("250 HELP");
241 			gothello = TRUE;
242 			break;
243 
244 		  case CMDMAIL:		/* mail -- designate sender */
245 			SmtpPhase = "MAIL";
246 
247 			/* force a sending host even if no HELO given */
248 			if (sendinghost == NULL && macvalue('s', e) == NULL)
249 				sendinghost = RealHostName;
250 
251 			/* check for validity of this command */
252 			if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags))
253 			{
254 				message("503 Polite people say HELO first");
255 				break;
256 			}
257 			if (gotmail)
258 			{
259 				message("503 Sender already specified");
260 				break;
261 			}
262 			if (InChild)
263 			{
264 				errno = 0;
265 				syserr("503 Nested MAIL command: MAIL %s", p);
266 				finis();
267 			}
268 
269 			/* fork a subprocess to process this command */
270 			if (runinchild("SMTP-MAIL", e) > 0)
271 				break;
272 			if (sendinghost != NULL)
273 				define('s', sendinghost, e);
274 			if (protocol == NULL)
275 				protocol = "SMTP";
276 			define('r', protocol, e);
277 			initsys(e);
278 			setproctitle("%s %s: %s", e->e_id, CurHostName, inp);
279 
280 			/* child -- go do the processing */
281 			p = skipword(p, "from");
282 			if (p == NULL)
283 				break;
284 			if (setjmp(TopFrame) > 0)
285 			{
286 				/* this failed -- undo work */
287 				if (InChild)
288 					finis();
289 				break;
290 			}
291 			QuickAbort = TRUE;
292 
293 			/* must parse sender first */
294 			delimptr = NULL;
295 			setsender(p, e, &delimptr);
296 			p = delimptr;
297 			if (p != NULL && *p != '\0')
298 				*p++ = '\0';
299 
300 			/* now parse ESMTP arguments */
301 			msize = 0;
302 			for (; p != NULL && *p != '\0'; p++)
303 			{
304 				char *kp;
305 				char *vp;
306 
307 				/* locate the beginning of the keyword */
308 				while (isascii(*p) && isspace(*p))
309 					p++;
310 				if (*p == '\0')
311 					break;
312 				kp = p;
313 
314 				/* skip to the value portion */
315 				while (isascii(*p) && isalnum(*p) || *p == '-')
316 					p++;
317 				if (*p == '=')
318 				{
319 					*p++ = '\0';
320 					vp = p;
321 
322 					/* skip to the end of the value */
323 					while (*p != '\0' && *p != ' ' &&
324 					       !(isascii(*p) && iscntrl(*p)) &&
325 					       *p != '=')
326 						p++;
327 				}
328 
329 				if (*p != '\0')
330 					*p++ = '\0';
331 
332 				if (tTd(19, 1))
333 					printf("MAIL: got arg %s=%s\n", kp,
334 						vp == NULL ? "<null>" : vp);
335 
336 				if (strcasecmp(kp, "size") == 0)
337 				{
338 					if (kp == NULL)
339 					{
340 						usrerr("501 SIZE requires a value");
341 						/* NOTREACHED */
342 					}
343 					msize = atol(vp);
344 				}
345 				else
346 				{
347 					usrerr("501 %s parameter unrecognized", kp);
348 					/* NOTREACHED */
349 				}
350 			}
351 
352 			if (!enoughspace(msize))
353 			{
354 				message("452 Insufficient disk space; try again later");
355 				break;
356 			}
357 			message("250 Sender ok");
358 			gotmail = TRUE;
359 			break;
360 
361 		  case CMDRCPT:		/* rcpt -- designate recipient */
362 			SmtpPhase = "RCPT";
363 			setproctitle("%s %s: %s", e->e_id, CurHostName, inp);
364 			if (setjmp(TopFrame) > 0)
365 			{
366 				e->e_flags &= ~EF_FATALERRS;
367 				break;
368 			}
369 			QuickAbort = TRUE;
370 			LogUsrErrs = TRUE;
371 
372 			/* optimization -- if queueing, don't expand aliases */
373 			if (SendMode == SM_QUEUE)
374 				e->e_flags |= EF_VRFYONLY;
375 
376 			p = skipword(p, "to");
377 			if (p == NULL)
378 				break;
379 			a = parseaddr(p, (ADDRESS *) NULL, 1, ' ', NULL, e);
380 			if (a == NULL)
381 				break;
382 			a->q_flags |= QPRIMARY;
383 			a = recipient(a, &e->e_sendqueue, e);
384 			if (Errors != 0)
385 				break;
386 
387 			/* no errors during parsing, but might be a duplicate */
388 			e->e_to = p;
389 			if (!bitset(QBADADDR, a->q_flags))
390 				message("250 Recipient ok");
391 			else
392 			{
393 				/* punt -- should keep message in ADDRESS.... */
394 				message("550 Addressee unknown");
395 			}
396 			e->e_to = NULL;
397 			break;
398 
399 		  case CMDDATA:		/* data -- text of mail */
400 			SmtpPhase = "DATA";
401 			if (!gotmail)
402 			{
403 				message("503 Need MAIL command");
404 				break;
405 			}
406 			else if (e->e_nrcpts <= 0)
407 			{
408 				message("503 Need RCPT (recipient)");
409 				break;
410 			}
411 
412 			/* collect the text of the message */
413 			SmtpPhase = "collect";
414 			setproctitle("%s %s: %s", e->e_id, CurHostName, inp);
415 			collect(TRUE, e);
416 			if (Errors != 0)
417 				break;
418 
419 			/*
420 			**  Arrange to send to everyone.
421 			**	If sending to multiple people, mail back
422 			**		errors rather than reporting directly.
423 			**	In any case, don't mail back errors for
424 			**		anything that has happened up to
425 			**		now (the other end will do this).
426 			**	Truncate our transcript -- the mail has gotten
427 			**		to us successfully, and if we have
428 			**		to mail this back, it will be easier
429 			**		on the reader.
430 			**	Then send to everyone.
431 			**	Finally give a reply code.  If an error has
432 			**		already been given, don't mail a
433 			**		message back.
434 			**	We goose error returns by clearing error bit.
435 			*/
436 
437 			SmtpPhase = "delivery";
438 			if (e->e_nrcpts != 1)
439 			{
440 				HoldErrs = TRUE;
441 				ErrorMode = EM_MAIL;
442 			}
443 			e->e_flags &= ~EF_FATALERRS;
444 			e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp);
445 
446 			/* send to all recipients */
447 			sendall(e, SM_DEFAULT);
448 			e->e_to = NULL;
449 
450 			/* save statistics */
451 			markstats(e, (ADDRESS *) NULL);
452 
453 			/* issue success if appropriate and reset */
454 			if (Errors == 0 || HoldErrs)
455 				message("250 Ok");
456 			else
457 				e->e_flags &= ~EF_FATALERRS;
458 
459 			/* if in a child, pop back to our parent */
460 			if (InChild)
461 				finis();
462 
463 			/* clean up a bit */
464 			gotmail = FALSE;
465 			dropenvelope(e);
466 			CurEnv = e = newenvelope(e, CurEnv);
467 			e->e_flags = BlankEnvelope.e_flags;
468 			break;
469 
470 		  case CMDRSET:		/* rset -- reset state */
471 			message("250 Reset state");
472 			if (InChild)
473 				finis();
474 
475 			/* clean up a bit */
476 			gotmail = FALSE;
477 			dropenvelope(e);
478 			CurEnv = e = newenvelope(e, CurEnv);
479 			break;
480 
481 		  case CMDVRFY:		/* vrfy -- verify address */
482 		  case CMDEXPN:		/* expn -- expand address */
483 			vrfy = c->cmdcode == CMDVRFY;
484 			if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN,
485 						PrivacyFlags))
486 			{
487 				message("502 That's none of your business");
488 				break;
489 			}
490 			else if (!gothello &&
491 				 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO,
492 						PrivacyFlags))
493 			{
494 				message("503 I demand that you introduce yourself first");
495 				break;
496 			}
497 			if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0)
498 				break;
499 			setproctitle("%s: %s", CurHostName, inp);
500 #ifdef LOG
501 			if (LogLevel > 5)
502 				syslog(LOG_INFO, "%s: %s", CurHostName, inp);
503 #endif
504 			vrfyqueue = NULL;
505 			QuickAbort = TRUE;
506 			if (vrfy)
507 				e->e_flags |= EF_VRFYONLY;
508 			(void) sendtolist(p, (ADDRESS *) NULL, &vrfyqueue, e);
509 			if (Errors != 0)
510 			{
511 				if (InChild)
512 					finis();
513 				break;
514 			}
515 			while (vrfyqueue != NULL)
516 			{
517 				register ADDRESS *a = vrfyqueue->q_next;
518 				char *code;
519 
520 				while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
521 					a = a->q_next;
522 
523 				if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
524 					printvrfyaddr(vrfyqueue, a == NULL);
525 				else if (a == NULL)
526 					message("554 Self destructive alias loop");
527 				vrfyqueue = a;
528 			}
529 			if (InChild)
530 				finis();
531 			break;
532 
533 		  case CMDHELP:		/* help -- give user info */
534 			help(p);
535 			break;
536 
537 		  case CMDNOOP:		/* noop -- do nothing */
538 			message("200 OK");
539 			break;
540 
541 		  case CMDQUIT:		/* quit -- leave mail */
542 			message("221 %s closing connection", MyHostName);
543 			if (InChild)
544 				ExitStat = EX_QUIT;
545 			finis();
546 
547 		  case CMDVERB:		/* set verbose mode */
548 			Verbose = TRUE;
549 			SendMode = SM_DELIVER;
550 			message("200 Verbose mode");
551 			break;
552 
553 		  case CMDONEX:		/* doing one transaction only */
554 			OneXact = TRUE;
555 			message("200 Only one transaction");
556 			break;
557 
558 # ifdef SMTPDEBUG
559 		  case CMDDBGQSHOW:	/* show queues */
560 			printf("Send Queue=");
561 			printaddr(e->e_sendqueue, TRUE);
562 			break;
563 
564 		  case CMDDBGDEBUG:	/* set debug mode */
565 			tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
566 			tTflag(p);
567 			message("200 Debug set");
568 			break;
569 
570 # else /* not SMTPDEBUG */
571 
572 		  case CMDDBGQSHOW:	/* show queues */
573 		  case CMDDBGDEBUG:	/* set debug mode */
574 # ifdef LOG
575 			if (LogLevel > 0)
576 				syslog(LOG_NOTICE,
577 				    "\"%s\" command from %s (%s)",
578 				    c->cmdname, RealHostName,
579 				    inet_ntoa(RealHostAddr.sin_addr));
580 # endif
581 			/* FALL THROUGH */
582 # endif /* SMTPDEBUG */
583 
584 		  case CMDERROR:	/* unknown command */
585 			message("500 Command unrecognized");
586 			break;
587 
588 		  default:
589 			errno = 0;
590 			syserr("500 smtp: unknown code %d", c->cmdcode);
591 			break;
592 		}
593 	}
594 }
595 /*
596 **  SKIPWORD -- skip a fixed word.
597 **
598 **	Parameters:
599 **		p -- place to start looking.
600 **		w -- word to skip.
601 **
602 **	Returns:
603 **		p following w.
604 **		NULL on error.
605 **
606 **	Side Effects:
607 **		clobbers the p data area.
608 */
609 
610 static char *
611 skipword(p, w)
612 	register char *p;
613 	char *w;
614 {
615 	register char *q;
616 
617 	/* find beginning of word */
618 	while (isascii(*p) && isspace(*p))
619 		p++;
620 	q = p;
621 
622 	/* find end of word */
623 	while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p)))
624 		p++;
625 	while (isascii(*p) && isspace(*p))
626 		*p++ = '\0';
627 	if (*p != ':')
628 	{
629 	  syntax:
630 		message("501 Syntax error");
631 		Errors++;
632 		return (NULL);
633 	}
634 	*p++ = '\0';
635 	while (isascii(*p) && isspace(*p))
636 		p++;
637 
638 	/* see if the input word matches desired word */
639 	if (strcasecmp(q, w))
640 		goto syntax;
641 
642 	return (p);
643 }
644 /*
645 **  PRINTVRFYADDR -- print an entry in the verify queue
646 **
647 **	Parameters:
648 **		a -- the address to print
649 **		last -- set if this is the last one.
650 **
651 **	Returns:
652 **		none.
653 **
654 **	Side Effects:
655 **		Prints the appropriate 250 codes.
656 */
657 
658 printvrfyaddr(a, last)
659 	register ADDRESS *a;
660 	bool last;
661 {
662 	char fmtbuf[20];
663 
664 	strcpy(fmtbuf, "250");
665 	fmtbuf[3] = last ? ' ' : '-';
666 
667 	if (strchr(a->q_paddr, '<') != NULL)
668 		strcpy(&fmtbuf[4], "%s");
669 	else if (a->q_fullname == NULL)
670 		strcpy(&fmtbuf[4], "<%s>");
671 	else
672 	{
673 		strcpy(&fmtbuf[4], "%s <%s>");
674 		message(fmtbuf, a->q_fullname, a->q_paddr);
675 		return;
676 	}
677 	message(fmtbuf, a->q_paddr);
678 }
679 /*
680 **  HELP -- implement the HELP command.
681 **
682 **	Parameters:
683 **		topic -- the topic we want help for.
684 **
685 **	Returns:
686 **		none.
687 **
688 **	Side Effects:
689 **		outputs the help file to message output.
690 */
691 
692 help(topic)
693 	char *topic;
694 {
695 	register FILE *hf;
696 	int len;
697 	char buf[MAXLINE];
698 	bool noinfo;
699 
700 	if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
701 	{
702 		/* no help */
703 		errno = 0;
704 		message("502 HELP not implemented");
705 		return;
706 	}
707 
708 	if (topic == NULL || *topic == '\0')
709 		topic = "smtp";
710 	else
711 		makelower(topic);
712 
713 	len = strlen(topic);
714 	noinfo = TRUE;
715 
716 	while (fgets(buf, sizeof buf, hf) != NULL)
717 	{
718 		if (strncmp(buf, topic, len) == 0)
719 		{
720 			register char *p;
721 
722 			p = strchr(buf, '\t');
723 			if (p == NULL)
724 				p = buf;
725 			else
726 				p++;
727 			fixcrlf(p, TRUE);
728 			message("214-%s", p);
729 			noinfo = FALSE;
730 		}
731 	}
732 
733 	if (noinfo)
734 		message("504 HELP topic unknown");
735 	else
736 		message("214 End of HELP info");
737 	(void) fclose(hf);
738 }
739 /*
740 **  RUNINCHILD -- return twice -- once in the child, then in the parent again
741 **
742 **	Parameters:
743 **		label -- a string used in error messages
744 **
745 **	Returns:
746 **		zero in the child
747 **		one in the parent
748 **
749 **	Side Effects:
750 **		none.
751 */
752 
753 runinchild(label, e)
754 	char *label;
755 	register ENVELOPE *e;
756 {
757 	int childpid;
758 
759 	if (!OneXact)
760 	{
761 		childpid = dofork();
762 		if (childpid < 0)
763 		{
764 			syserr("%s: cannot fork", label);
765 			return (1);
766 		}
767 		if (childpid > 0)
768 		{
769 			auto int st;
770 
771 			/* parent -- wait for child to complete */
772 			st = waitfor(childpid);
773 			if (st == -1)
774 				syserr("%s: lost child", label);
775 
776 			/* if we exited on a QUIT command, complete the process */
777 			if (st == (EX_QUIT << 8))
778 				finis();
779 
780 			return (1);
781 		}
782 		else
783 		{
784 			/* child */
785 			InChild = TRUE;
786 			QuickAbort = FALSE;
787 			clearenvelope(e, FALSE);
788 		}
789 	}
790 
791 	/* open alias database */
792 	initaliases(AliasFile, FALSE, e);
793 
794 	return (0);
795 }
796 
797 # endif /* SMTP */
798