1 # include <stdio.h>
2 # include <pwd.h>
3 # include <signal.h>
4 # include <ctype.h>
5 # include "dlvrmail.h"
6 # ifdef LOG
7 # include <syslog.h>
8 # endif LOG
9 
10 static char SccsId[] = "@(#)deliver.c	3.1	03/04/81";
11 
12 /*
13 **  DELIVER -- Deliver a message to a particular address.
14 **
15 **	Algorithm:
16 **		Compute receiving network (i.e., mailer), host, & user.
17 **		If local, see if this is really a program name.
18 **		Build argument for the mailer.
19 **		Create pipe through edit fcn if appropriate.
20 **		Fork.
21 **			Child: call mailer
22 **		Parent: call editfcn if specified.
23 **		Wait for mailer to finish.
24 **		Interpret exit status.
25 **
26 **	Parameters:
27 **		to -- the address to deliver the message to.
28 **		editfcn -- if non-NULL, we want to call this function
29 **			to output the letter (instead of just out-
30 **			putting it raw).
31 **
32 **	Returns:
33 **		zero -- successfully delivered.
34 **		else -- some failure, see ExitStat for more info.
35 **
36 **	Side Effects:
37 **		The standard input is passed off to someone.
38 **
39 **	WARNING:
40 **		The standard input is shared amongst all children,
41 **		including the file pointer.  It is critical that the
42 **		parent waits for the child to finish before forking
43 **		another child.
44 **
45 **	Called By:
46 **		main
47 **		savemail
48 **
49 **	Files:
50 **		standard input -- must be opened to the message to
51 **			deliver.
52 */
53 
54 deliver(to, editfcn)
55 	addrq *to;
56 	int (*editfcn)();
57 {
58 	register struct mailer *m;
59 	char *host;
60 	char *user;
61 	extern struct passwd *getpwnam();
62 	char **pvp;
63 	extern char **buildargv();
64 	auto int st;
65 	register int i;
66 	register char *p;
67 	int pid;
68 	int pvect[2];
69 	extern FILE *fdopen();
70 	extern int errno;
71 	FILE *mfile;
72 	extern putmessage();
73 	extern pipesig();
74 	extern char *index();
75 
76 	/*
77 	**  Compute receiving mailer, host, and to addreses.
78 	**	Do some initialization first.  To is the to address
79 	**	for error messages.
80 	*/
81 
82 	To = to->q_paddr;
83 	m = to->q_mailer;
84 	user = to->q_user;
85 	host = to->q_host;
86 	Errors = 0;
87 	errno = 0;
88 # ifdef DEBUG
89 	if (Debug)
90 		printf("deliver(%s [%d, `%s', `%s'])\n", To, m, host, user);
91 # endif DEBUG
92 
93 	/*
94 	**  Remove quote bits from user/host.
95 	*/
96 
97 	for (p = user; (*p++ &= 0177) != '\0'; )
98 		continue;
99 	if (host != NULL)
100 		for (p = host; (*p++ &= 0177) != '\0'; )
101 			continue;
102 
103 	/*
104 	**  Strip quote bits from names if the mailer wants it.
105 	*/
106 
107 	if (flagset(M_STRIPQ, m->m_flags))
108 	{
109 		stripquotes(user);
110 		stripquotes(host);
111 	}
112 
113 	/*
114 	**  See if this user name is "special".
115 	**	If the user is a program, diddle with the mailer spec.
116 	**	If the user name has a slash in it, assume that this
117 	**		is a file -- send it off without further ado.
118 	**		Note that this means that editfcn's will not
119 	**		be applied to the message.
120 	*/
121 
122 	if (m == &Mailer[0])
123 	{
124 		if (*user == '|')
125 		{
126 			user++;
127 			m = &Mailer[1];
128 		}
129 		else
130 		{
131 			if (index(user, '/') != NULL)
132 			{
133 				i = mailfile(user);
134 				giveresponse(i, TRUE, m);
135 				return (i);
136 			}
137 		}
138 	}
139 
140 	/*
141 	**  See if the user exists.
142 	**	Strictly, this is only needed to print a pretty
143 	**	error message.
144 	**
145 	**	>>>>>>>>>> This clause assumes that the local mailer
146 	**	>> NOTE >> cannot do any further aliasing; that
147 	**	>>>>>>>>>> function is subsumed by delivermail.
148 	*/
149 
150 	if (m == &Mailer[0])
151 	{
152 		if (getpwnam(user) == NULL)
153 		{
154 			giveresponse(EX_NOUSER, TRUE, m);
155 			return (EX_NOUSER);
156 		}
157 	}
158 
159 	/*
160 	**  Call the mailer.
161 	**	The argument vector gets built, pipes
162 	**	are created as necessary, and we fork & exec as
163 	**	appropriate.
164 	*/
165 
166 	pvp = buildargv(m, host, user, From.q_paddr);
167 	if (pvp == NULL)
168 	{
169 		usrerr("name too long");
170 		return (-1);
171 	}
172 	rewind(stdin);
173 
174 	/* create a pipe to shove the mail through */
175 	if (pipe(pvect) < 0)
176 	{
177 		syserr("pipe");
178 		return (-1);
179 	}
180 # ifdef VFORK
181 	pid = vfork();
182 # else
183 	pid = fork();
184 # endif
185 	if (pid < 0)
186 	{
187 		syserr("Cannot fork");
188 		close(pvect[0]);
189 		close(pvect[1]);
190 		return (-1);
191 	}
192 	else if (pid == 0)
193 	{
194 		/* child -- set up input & exec mailer */
195 		/* make diagnostic output be standard output */
196 		close(2);
197 		dup(1);
198 		signal(SIGINT, SIG_IGN);
199 		close(0);
200 		if (dup(pvect[0]) < 0)
201 		{
202 			syserr("Cannot dup to zero!");
203 			_exit(EX_OSERR);
204 		}
205 		close(pvect[0]);
206 		close(pvect[1]);
207 		if (!flagset(M_RESTR, m->m_flags))
208 			setuid(getuid());
209 # ifndef VFORK
210 		/*
211 		**  We have to be careful with vfork - we can't mung up the
212 		**  memory but we don't want the mailer to inherit any extra
213 		**  open files.  Chances are the mailer won't
214 		**  care about an extra file, but then again you never know.
215 		**  Actually, we would like to close(fileno(pwf)), but it's
216 		**  declared static so we can't.  But if we fclose(pwf), which
217 		**  is what endpwent does, it closes it in the parent too and
218 		**  the next getpwnam will be slower.  If you have a weird
219 		**  mailer that chokes on the extra file you should do the
220 		**  endpwent().
221 		**
222 		**  Similar comments apply to log.  However, openlog is
223 		**  clever enough to set the FIOCLEX mode on the file,
224 		**  so it will be closed automatically on the exec.
225 		*/
226 
227 		endpwent();
228 # ifdef LOG
229 		closelog();
230 # endif LOG
231 # endif VFORK
232 		execv(m->m_mailer, pvp);
233 		/* syserr fails because log is closed */
234 		/* syserr("Cannot exec %s", m->m_mailer); */
235 		printf("Cannot exec %s\n", m->m_mailer);
236 		fflush(stdout);
237 		_exit(EX_UNAVAILABLE);
238 	}
239 
240 	/* write out message to mailer */
241 	close(pvect[0]);
242 	signal(SIGPIPE, pipesig);
243 	mfile = fdopen(pvect[1], "w");
244 	if (editfcn == NULL)
245 		editfcn = putmessage;
246 	(*editfcn)(mfile, m);
247 	fclose(mfile);
248 
249 	/*
250 	**  Wait for child to die and report status.
251 	**	We should never get fatal errors (e.g., segmentation
252 	**	violation), so we report those specially.  For other
253 	**	errors, we choose a status message (into statmsg),
254 	**	and if it represents an error, we print it.
255 	*/
256 
257 	while ((i = wait(&st)) > 0 && i != pid)
258 		continue;
259 	if (i < 0)
260 	{
261 		syserr("wait");
262 		return (-1);
263 	}
264 	if ((st & 0377) != 0)
265 	{
266 		syserr("%s: stat %o", pvp[0], st);
267 		ExitStat = EX_UNAVAILABLE;
268 		return (-1);
269 	}
270 	i = (st >> 8) & 0377;
271 	giveresponse(i, TRUE, m);
272 	return (i);
273 }
274 /*
275 **  GIVERESPONSE -- Interpret an error response from a mailer
276 **
277 **	Parameters:
278 **		stat -- the status code from the mailer (high byte
279 **			only; core dumps must have been taken care of
280 **			already).
281 **		force -- if set, force an error message output, even
282 **			if the mailer seems to like to print its own
283 **			messages.
284 **		m -- the mailer descriptor for this mailer.
285 **
286 **	Returns:
287 **		none.
288 **
289 **	Side Effects:
290 **		Errors may be incremented.
291 **		ExitStat may be set.
292 **
293 **	Called By:
294 **		deliver
295 */
296 
297 giveresponse(stat, force, m)
298 	int stat;
299 	int force;
300 	register struct mailer *m;
301 {
302 	register char *statmsg;
303 	extern char *SysExMsg[];
304 	register int i;
305 	extern int N_SysEx;
306 	extern long MsgSize;
307 	char buf[30];
308 
309 	i = stat - EX__BASE;
310 	if (i < 0 || i > N_SysEx)
311 		statmsg = NULL;
312 	else
313 		statmsg = SysExMsg[i];
314 	if (stat == 0)
315 		statmsg = "ok";
316 	else
317 	{
318 		Errors++;
319 		if (statmsg == NULL && m->m_badstat != 0)
320 		{
321 			stat = m->m_badstat;
322 			i = stat - EX__BASE;
323 # ifdef DEBUG
324 			if (i < 0 || i >= N_SysEx)
325 				syserr("Bad m_badstat %d", stat);
326 			else
327 # endif DEBUG
328 			statmsg = SysExMsg[i];
329 		}
330 		if (statmsg == NULL)
331 			usrerr("unknown mailer response %d", stat);
332 		else if (force || !flagset(M_QUIET, m->m_flags))
333 			usrerr("%s", statmsg);
334 	}
335 
336 	/*
337 	**  Final cleanup.
338 	**	Log a record of the transaction.  Compute the new
339 	**	ExitStat -- if we already had an error, stick with
340 	**	that.
341 	*/
342 
343 	if (statmsg == NULL)
344 	{
345 		sprintf(buf, "error %d", stat);
346 		statmsg = buf;
347 	}
348 
349 # ifdef LOG
350 	syslog(LOG_INFO, "%s->%s: %ld: %s", From.q_paddr, To, MsgSize, statmsg);
351 # endif LOG
352 	setstat(stat);
353 	return (stat);
354 }
355 /*
356 **  PUTMESSAGE -- output a message to the final mailer.
357 **
358 **	This routine takes care of recreating the header from the
359 **	in-core copy, etc.
360 **
361 **	Parameters:
362 **		fp -- file to output onto.
363 **		m -- a mailer descriptor.
364 **
365 **	Returns:
366 **		none.
367 **
368 **	Side Effects:
369 **		The message is written onto fp.
370 */
371 
372 putmessage(fp, m)
373 	FILE *fp;
374 	struct mailer *m;
375 {
376 	char buf[BUFSIZ];
377 	register int i;
378 	HDR *h;
379 	register char *p;
380 	extern char *arpadate();
381 	extern char *hvalue();
382 	bool anyheader = FALSE;
383 	extern char *translate();
384 
385 	/* clear all "used" bits */
386 	for (h = Header; h != NULL; h = h->h_link)
387 		h->h_flags &= ~H_USED;
388 
389 	/* output date if needed by mailer */
390 	p = hvalue("date");
391 	if (flagset(M_NEEDDATE, m->m_flags) && p == NULL)
392 		p = arpadate(Date);
393 	if (p != NULL)
394 	{
395 		fprintf(fp, "Date: %s\n", p);
396 		anyheader = TRUE;
397 	}
398 
399 	/* output from line if needed by mailer */
400 	p = hvalue("from");
401 	if (flagset(M_NEEDFROM, m->m_flags) && p == NULL)
402 	{
403 		char frombuf[MAXLINE];
404 		extern char *FullName;
405 
406 		p = translate("$f", From.q_mailer, From.q_paddr, NULL, NULL);
407 		if (FullName != NULL)
408 			fprintf(fp, "From: %s <%s>\n", FullName, p);
409 		else
410 			fprintf(fp, "From: %s\n", p);
411 		free(p);
412 		anyheader = TRUE;
413 	}
414 	else if (p != NULL)
415 	{
416 		fprintf(fp, "From: %s\n", p);
417 		anyheader = TRUE;
418 	}
419 
420 	/* output message-id field if needed */
421 	p = hvalue("message-id");
422 	if (flagset(M_MSGID, m->m_flags) && p == NULL)
423 		p = MsgId;
424 	if (p != NULL)
425 	{
426 		fprintf(fp, "Message-Id: %s\n", p);
427 		anyheader = TRUE;
428 	}
429 
430 	/* output any other header lines */
431 	for (h = Header; h != NULL; h = h->h_link)
432 	{
433 		if (flagset(H_USED, h->h_flags))
434 			continue;
435 		fprintf(fp, "%s: %s\n", capitalize(h->h_field), h->h_value);
436 		h->h_flags |= H_USED;
437 		anyheader = TRUE;
438 	}
439 
440 	if (anyheader)
441 		fprintf(fp, "\n");
442 
443 	/* output the body of the message */
444 	while (!ferror(fp) && (i = read(0, buf, BUFSIZ)) > 0)
445 		fwrite(buf, 1, i, fp);
446 
447 	if (ferror(fp))
448 	{
449 		syserr("putmessage: write error");
450 		setstat(EX_IOERR);
451 	}
452 }
453 /*
454 **  PIPESIG -- Handle broken pipe signals
455 **
456 **	This just logs an error.
457 **
458 **	Parameters:
459 **		none
460 **
461 **	Returns:
462 **		none
463 **
464 **	Side Effects:
465 **		logs an error message.
466 */
467 
468 pipesig()
469 {
470 	syserr("Broken pipe");
471 	signal(SIGPIPE, SIG_IGN);
472 }
473 /*
474 **  SENDTO -- Designate a send list.
475 **
476 **	The parameter is a comma-separated list of people to send to.
477 **	This routine arranges to send to all of them.
478 **
479 **	Parameters:
480 **		list -- the send list.
481 **		copyf -- the copy flag; passed to parse.
482 **
483 **	Returns:
484 **		none
485 **
486 **	Side Effects:
487 **		none.
488 **
489 **	Called By:
490 **		main
491 **		alias
492 */
493 
494 sendto(list, copyf)
495 	char *list;
496 	int copyf;
497 {
498 	register char *p;
499 	register char *q;
500 	register char c;
501 	addrq *a;
502 	extern addrq *parse();
503 	bool more;
504 
505 	/* more keeps track of what the previous delimiter was */
506 	more = TRUE;
507 	for (p = list; more; )
508 	{
509 		/* find the end of this address */
510 		q = p;
511 		while ((c = *p++) != '\0' && c != ',' && c != '\n')
512 			continue;
513 		more = c != '\0';
514 		*--p = '\0';
515 		if (more)
516 			p++;
517 
518 		/* parse the address */
519 		if ((a = parse(q, (addrq *) NULL, copyf)) == NULL)
520 			continue;
521 
522 		/* arrange to send to this person */
523 		recipient(a, &SendQ);
524 	}
525 	To = NULL;
526 }
527 /*
528 **  RECIPIENT -- Designate a message recipient
529 **
530 **	Saves the named person for future mailing.
531 **
532 **	Designates a person as a recipient.  This routine
533 **	does the initial parsing, and checks to see if
534 **	this person has already received the mail.
535 **	It also supresses local network names and turns them into
536 **	local names.
537 **
538 **	Parameters:
539 **		a -- the (preparsed) address header for the recipient.
540 **		targetq -- the queue to add the name to.
541 **
542 **	Returns:
543 **		none.
544 **
545 **	Side Effects:
546 **		none.
547 **
548 **	Called By:
549 **		sendto
550 **		main
551 */
552 
553 recipient(a, targetq)
554 	register addrq *a;
555 	addrq *targetq;
556 {
557 	register addrq *q;
558 	register struct mailer *m;
559 	register char **pvp;
560 	extern char *xalloc();
561 	extern bool forward();
562 	extern int errno;
563 	extern bool sameaddr();
564 
565 	To = a->q_paddr;
566 	m = a->q_mailer;
567 	errno = 0;
568 # ifdef DEBUG
569 	if (Debug)
570 		printf("recipient(%s)\n", To);
571 # endif DEBUG
572 
573 	/*
574 	**  Look up this person in the recipient list.  If they
575 	**  are there already, return, otherwise continue.
576 	*/
577 
578 	if (!ForceMail)
579 	{
580 		for (q = &SendQ; (q = nxtinq(q)) != NULL; )
581 			if (sameaddr(q, a, FALSE))
582 			{
583 # ifdef DEBUG
584 				if (Debug)
585 					printf("(%s in SendQ)\n", a->q_paddr);
586 # endif DEBUG
587 				return;
588 			}
589 		for (q = &AliasQ; (q = nxtinq(q)) != NULL; )
590 			if (sameaddr(q, a, FALSE))
591 			{
592 # ifdef DEBUG
593 				if (Debug)
594 					printf("(%s in AliasQ)\n", a->q_paddr);
595 # endif DEBUG
596 				return;
597 			}
598 	}
599 
600 	/*
601 	**  See if the user wants hir mail forwarded.
602 	**	`Forward' must do the forwarding recursively.
603 	*/
604 
605 	if (m == &Mailer[0] && !NoAlias && targetq == &SendQ && forward(a))
606 		return;
607 
608 	/*
609 	**  Put the user onto the target queue.
610 	*/
611 
612 	if (targetq != NULL)
613 	{
614 		putonq(a, targetq);
615 	}
616 
617 	return;
618 }
619 /*
620 **  BUILDARGV -- Build an argument vector for a mail server.
621 **
622 **	Using a template defined in config.c, an argv is built.
623 **	The format of the template is already a vector.  The
624 **	items of this vector are copied, unless a dollar sign
625 **	is encountered.  In this case, the next character
626 **	specifies something else to copy in.  These can be
627 **		$f	The from address.
628 **		$h	The host.
629 **		$u	The user.
630 **		$c	The hop count.
631 **	The vector is built in a local buffer.  A pointer to
632 **	the static argv is returned.
633 **
634 **	Parameters:
635 **		m -- a pointer to the mailer descriptor.
636 **		host -- the host name to send to.
637 **		user -- the user name to send to.
638 **		from -- the person this mail is from.
639 **
640 **	Returns:
641 **		A pointer to an argv.
642 **
643 **	Side Effects:
644 **		none
645 **
646 **	WARNING:
647 **		Since the argv is staticly allocated, any subsequent
648 **		calls will clobber the old argv.
649 **
650 **	Called By:
651 **		deliver
652 */
653 
654 char **
655 buildargv(m, host, user, from)
656 	struct mailer *m;
657 	char *host;
658 	char *user;
659 	char *from;
660 {
661 	register char *p;
662 	register char *q;
663 	static char *pv[MAXPV+1];
664 	char **pvp;
665 	char **mvp;
666 	static char buf[512];
667 	register char *bp;
668 	extern char *translate();
669 
670 	/*
671 	**  Do initial argv setup.
672 	**	Insert the mailer name.  Notice that $x expansion is
673 	**	NOT done on the mailer name.  Then, if the mailer has
674 	**	a picky -f flag, we insert it as appropriate.  This
675 	**	code does not check for 'pv' overflow; this places a
676 	**	manifest lower limit of 4 for MAXPV.
677 	*/
678 
679 	pvp = pv;
680 	bp = buf;
681 
682 	*pvp++ = m->m_argv[0];
683 
684 	/* insert -f or -r flag as appropriate */
685 	if (flagset(M_FOPT|M_ROPT, m->m_flags) && FromFlag)
686 	{
687 		if (flagset(M_FOPT, m->m_flags))
688 			*pvp++ = "-f";
689 		else
690 			*pvp++ = "-r";
691 		*pvp++ = translate(from, m, from, user, host);
692 	}
693 
694 	/*
695 	**  Build the rest of argv.
696 	**	For each prototype parameter, the prototype is
697 	**	scanned character at a time.  If a dollar-sign is
698 	**	found, 'q' is set to the appropriate expansion,
699 	**	otherwise it is null.  Then either the string
700 	**	pointed to by q, or the original character, is
701 	**	interpolated into the buffer.  Buffer overflow is
702 	**	checked.
703 	*/
704 
705 	for (mvp = m->m_argv; (p = *++mvp) != NULL; )
706 	{
707 		if (pvp >= &pv[MAXPV])
708 		{
709 			syserr("Too many parameters to %s", pv[0]);
710 			return (NULL);
711 		}
712 		*pvp++ = translate(p, m, from, user, host);
713 	}
714 	*pvp = NULL;
715 
716 # ifdef DEBUG
717 	if (Debug)
718 	{
719 		printf("Interpolated argv is:\n");
720 		for (mvp = pv; *mvp != NULL; mvp++)
721 			printf("\t%s\n", *mvp);
722 	}
723 # endif DEBUG
724 
725 	return (pv);
726 }
727 /*
728 **  TRANSLATE -- translate a string using $x escapes.
729 **
730 **	Parameters:
731 **		s -- string to translate.
732 **		m -- pointer to mailer descriptor.
733 **
734 **	Returns:
735 **		pointer to translated string.
736 **
737 **	Side Effects:
738 **		none.
739 */
740 
741 char *
742 translate(s, m, from, user, host)
743 	register char *s;
744 	struct mailer *m;
745 	char *from;
746 	char *user;
747 	char *host;
748 {
749 	register char *q;
750 	char buf[MAXNAME];
751 	register char *bp;
752 	char *stack = NULL;
753 	char pbuf[10];
754 	extern char *newstr();
755 	extern char *Macro[];
756 
757 	bp = buf;
758 restart:
759 
760 # ifdef DEBUG
761 	if (Debug)
762 		printf("translate(%s)\n", s);
763 # endif DEBUG
764 	for (; *s != '\0'; s++)
765 	{
766 		/* q will be the interpolated quantity */
767 		q = NULL;
768 		if (*s == '$')
769 		{
770 			if (isupper(*++s))
771 				q = Macro[*s - 'A'];
772 			else
773 			{
774 				switch (*s)
775 				{
776 				  case 'f':	/* from person */
777 					if (stack == NULL && m != NULL)
778 					{
779 						stack = s;
780 						s = m->m_from;
781 						goto restart;
782 					}
783 					q = from;
784 					break;
785 
786 				  case 'u':	/* user */
787 					q = user;
788 					break;
789 
790 				  case 'h':	/* host */
791 					q = host;
792 					break;
793 
794 				  case 'c':	/* hop count */
795 					sprintf(pbuf, "%d", HopCount);
796 					q = pbuf;
797 					break;
798 				}
799 			}
800 		}
801 
802 		/*
803 		**  Interpolate q or output one character
804 		**	Strip quote bits as we proceed.....
805 		*/
806 
807 		if (q != NULL)
808 		{
809 			while (bp < &buf[sizeof buf - 1] && (*bp++ = *q++) != '\0')
810 				continue;
811 			bp--;
812 		}
813 		else if (bp < &buf[sizeof buf - 1])
814 			*bp++ = *s;
815 	}
816 	if (stack != NULL)
817 	{
818 		s = stack;
819 		s++;
820 		stack = NULL;
821 		goto restart;
822 	}
823 	*bp++ = '\0';
824 	if (bp >= &buf[sizeof buf - 1])
825 		return (NULL);
826 # ifdef DEBUG
827 	if (Debug)
828 		printf("translate ==> '%s'\n", buf);
829 # endif DEBUG
830 	return (newstr(buf));
831 }
832 /*
833 **  MAILFILE -- Send a message to a file.
834 **
835 **	Parameters:
836 **		filename -- the name of the file to send to.
837 **
838 **	Returns:
839 **		The exit code associated with the operation.
840 **
841 **	Side Effects:
842 **		none.
843 **
844 **	Called By:
845 **		deliver
846 */
847 
848 mailfile(filename)
849 	char *filename;
850 {
851 	char buf[MAXLINE];
852 	register FILE *f;
853 	auto long tim;
854 	extern char *ctime();
855 
856 	f = fopen(filename, "a");
857 	if (f == NULL)
858 		return (EX_CANTCREAT);
859 
860 	/* output the timestamp */
861 	time(&tim);
862 	fprintf(f, "From %s %s", From.q_paddr, ctime(&tim));
863 	rewind(stdin);
864 	while (fgets(buf, sizeof buf, stdin) != NULL)
865 	{
866 		fputs(buf, f);
867 		if (ferror(f))
868 		{
869 			fclose(f);
870 			return (EX_IOERR);
871 		}
872 	}
873 	fputs("\n", f);
874 	fclose(f);
875 	return (EX_OK);
876 }
877