xref: /csrg-svn/usr.sbin/sendmail/src/alias.c (revision 57948)
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 <sys/types.h>
10 # include <sys/stat.h>
11 # include <sys/file.h>
12 # include <signal.h>
13 # include "sendmail.h"
14 # include <fcntl.h>
15 # include <pwd.h>
16 
17 # ifdef DBM
18 ERROR: DBM is no longer supported -- use NDBM instead.
19 # endif
20 
21 # ifdef NEWDB
22 # include <db.h>
23 # endif
24 
25 # ifdef NDBM
26 # include <ndbm.h>
27 # endif
28 
29 #ifndef lint
30 #ifdef NEWDB
31 #ifdef NDBM
32 static char sccsid[] = "@(#)alias.c	6.7 (Berkeley) 02/12/93 (with NEWDB and NDBM)";
33 #else
34 static char sccsid[] = "@(#)alias.c	6.7 (Berkeley) 02/12/93 (with NEWDB)";
35 #endif
36 #else
37 #ifdef NDBM
38 static char sccsid[] = "@(#)alias.c	6.7 (Berkeley) 02/12/93 (with NDBM)";
39 #else
40 static char sccsid[] = "@(#)alias.c	6.7 (Berkeley) 02/12/93 (without NEWDB or NDBM)";
41 #endif
42 #endif
43 #endif /* not lint */
44 /*
45 **  ALIAS -- Compute aliases.
46 **
47 **	Scans the alias file for an alias for the given address.
48 **	If found, it arranges to deliver to the alias list instead.
49 **	Uses libdbm database if -DDBM.
50 **
51 **	Parameters:
52 **		a -- address to alias.
53 **		sendq -- a pointer to the head of the send queue
54 **			to put the aliases in.
55 **
56 **	Returns:
57 **		none
58 **
59 **	Side Effects:
60 **		Aliases found are expanded.
61 **
62 **	Notes:
63 **		If NoAlias (the "-n" flag) is set, no aliasing is
64 **			done.
65 **
66 **	Deficiencies:
67 **		It should complain about names that are aliased to
68 **			nothing.
69 */
70 
71 
72 /*
73 **  Sun YP servers read the dbm files directly, so we have to build them
74 **  even if NEWDB
75 */
76 
77 #ifdef NDBM
78 # ifndef NEWDB
79 #  define IF_MAKEDBMFILES
80 # else
81 #  ifdef YPCOMPAT
82 #   define IF_MAKEDBMFILES		if (makedbmfiles)
83 #  endif
84 # endif
85 #endif
86 
87 typedef union
88 {
89 #ifdef NDBM
90 	datum	dbm;
91 #endif
92 #ifdef NEWDB
93 	DBT	dbt;
94 #endif
95 	struct
96 	{
97 		char	*data;
98 		int	size;
99 	} xx;
100 } DBdatum;
101 
102 #ifdef NEWDB
103 static DB	*AliasDBptr;
104 #endif
105 #ifdef NDBM
106 static DBM	*AliasDBMptr;
107 #endif
108 
109 alias(a, sendq, e)
110 	register ADDRESS *a;
111 	ADDRESS **sendq;
112 	register ENVELOPE *e;
113 {
114 	register char *p;
115 	extern char *aliaslookup();
116 
117 	if (tTd(27, 1))
118 		printf("alias(%s)\n", a->q_paddr);
119 
120 	/* don't realias already aliased names */
121 	if (bitset(QDONTSEND, a->q_flags))
122 		return;
123 
124 	e->e_to = a->q_paddr;
125 
126 	/*
127 	**  Look up this name
128 	*/
129 
130 	if (NoAlias)
131 		p = NULL;
132 	else
133 		p = aliaslookup(a->q_user);
134 	if (p == NULL)
135 		return;
136 
137 	/*
138 	**  Match on Alias.
139 	**	Deliver to the target list.
140 	*/
141 
142 	if (tTd(27, 1))
143 		printf("%s (%s, %s) aliased to %s\n",
144 		    a->q_paddr, a->q_host, a->q_user, p);
145 	message(Arpa_Info, "aliased to %s", p);
146 	AliasLevel++;
147 	sendtolist(p, a, sendq, e);
148 	AliasLevel--;
149 }
150 /*
151 **  ALIASLOOKUP -- look up a name in the alias file.
152 **
153 **	Parameters:
154 **		name -- the name to look up.
155 **
156 **	Returns:
157 **		the value of name.
158 **		NULL if unknown.
159 **
160 **	Side Effects:
161 **		none.
162 **
163 **	Warnings:
164 **		The return value will be trashed across calls.
165 */
166 
167 char *
168 aliaslookup(name)
169 	char *name;
170 {
171 	int i;
172 	char keybuf[MAXNAME + 1];
173 # if defined(NEWDB) || defined(NDBM)
174 	DBdatum rhs, lhs;
175 	int s;
176 # else /* neither NEWDB nor NDBM */
177 	register STAB *s;
178 # endif
179 
180 	/* create a key for fetch */
181 	i = strlen(name) + 1;
182 	if (i > sizeof keybuf)
183 		i = sizeof keybuf;
184 	bcopy(name, keybuf, i);
185 	if (!bitnset(M_USR_UPPER, LocalMailer->m_flags))
186 		makelower(keybuf);
187 
188 # if defined(NEWDB) || defined(NDBM)
189 	lhs.xx.size = i;
190 	lhs.xx.data = keybuf;
191 # ifdef NEWDB
192 	if (AliasDBptr != NULL)
193 	{
194 		i = AliasDBptr->get(AliasDBptr, &lhs.dbt, &rhs.dbt, 0);
195 		if (i == 0)
196 			return (rhs.dbt.data);
197 	}
198 # ifdef NDBM
199 	else if (AliasDBMptr != NULL)
200 	{
201 		rhs.dbm = dbm_fetch(AliasDBMptr, lhs.dbm);
202 		return (rhs.dbm.dptr);
203 	}
204 # endif /* NDBM */
205 	return (NULL);
206 # else /* not NEWDB */
207 	rhs.dbm = dbm_fetch(AliasDBMptr, lhs.dbm);
208 	return (rhs.dbm.dptr);
209 # endif /* NEWDB */
210 # else /* neither NEWDB nor NDBM */
211 	s = stab(keybuf, ST_ALIAS, ST_FIND);
212 	if (s != NULL)
213 		return (s->s_alias);
214 	return (NULL);
215 # endif
216 }
217 /*
218 **  INITALIASES -- initialize for aliasing
219 **
220 **	Very different depending on whether we are running NDBM or not.
221 **
222 **	Parameters:
223 **		aliasfile -- location of aliases.
224 **		init -- if set and if NDBM, initialize the NDBM files.
225 **
226 **	Returns:
227 **		none.
228 **
229 **	Side Effects:
230 **		initializes aliases:
231 **		if NDBM:  opens the database.
232 **		if ~NDBM: reads the aliases into the symbol table.
233 */
234 
235 # define DBMMODE	0644
236 
237 initaliases(aliasfile, init, e)
238 	char *aliasfile;
239 	bool init;
240 	register ENVELOPE *e;
241 {
242 #if defined(NDBM) || defined(NEWDB)
243 	int atcnt;
244 	time_t modtime;
245 	bool automatic = FALSE;
246 	char buf[MAXNAME];
247 #endif
248 	struct stat stb;
249 	static bool initialized = FALSE;
250 	static int readaliases();
251 
252 	if (initialized)
253 		return;
254 	initialized = TRUE;
255 
256 	if (aliasfile == NULL || stat(aliasfile, &stb) < 0)
257 	{
258 		if (aliasfile != NULL && init)
259 			syserr("Cannot open %s", aliasfile);
260 		NoAlias = TRUE;
261 		errno = 0;
262 		return;
263 	}
264 
265 # if defined(NDBM) || defined(NEWDB)
266 	/*
267 	**  Check to see that the alias file is complete.
268 	**	If not, we will assume that someone died, and it is up
269 	**	to us to rebuild it.
270 	*/
271 
272 	if (!init)
273 	{
274 # ifdef NEWDB
275 		(void) strcpy(buf, aliasfile);
276 		(void) strcat(buf, ".db");
277 		AliasDBptr = dbopen(buf, O_RDONLY, DBMMODE, DB_HASH, NULL);
278 		if (AliasDBptr == NULL)
279 		{
280 # ifdef NDBM
281 			AliasDBMptr = dbm_open(aliasfile, O_RDONLY, DBMMODE);
282 			if (AliasDBMptr == NULL)
283 			{
284 				syserr("initaliases: cannot open %s", buf);
285 				NoAlias = TRUE;
286 				return;
287 			}
288 # else
289 			syserr("initaliases: cannot open %s", buf);
290 			NoAlias = TRUE;
291 			return;
292 # endif
293 		}
294 # else
295 		AliasDBMptr = dbm_open(aliasfile, O_RDONLY, DBMMODE);
296 		if (AliasDBMptr == NULL)
297 		{
298 			syserr("initaliases: cannot open %s", buf);
299 			NoAlias = TRUE;
300 			return;
301 		}
302 # endif
303 	}
304 	atcnt = SafeAlias * 2;
305 	if (atcnt > 0)
306 	{
307 		while (!init && atcnt-- >= 0 && aliaslookup("@") == NULL)
308 		{
309 			/*
310 			**  Reinitialize alias file in case the new
311 			**  one is mv'ed in instead of cp'ed in.
312 			**
313 			**	Only works with new DBM -- old one will
314 			**	just consume file descriptors forever.
315 			**	If you have a dbmclose() it can be
316 			**	added before the sleep(30).
317 			*/
318 
319 # ifdef NEWDB
320 			if (AliasDBptr != NULL)
321 				AliasDBptr->close(AliasDBptr);
322 # endif
323 # ifdef NDBM
324 			if (AliasDBMptr != NULL)
325 				dbm_close(AliasDBMptr);
326 # endif
327 
328 			sleep(30);
329 # ifdef NEWDB
330 			(void) strcpy(buf, aliasfile);
331 			(void) strcat(buf, ".db");
332 			AliasDBptr =
333 			    dbopen(buf, O_RDONLY, DBMMODE, DB_HASH, NULL);
334 			if (AliasDBptr == NULL)
335 			{
336 # ifdef NDBM
337 				AliasDBMptr = dbm_open(aliasfile, O_RDONLY, DBMMODE);
338 # else
339 				syserr("initaliases: cannot open %s", buf);
340 				NoAlias = TRUE;
341 				return;
342 # endif
343 			}
344 # else
345 # ifdef NDBM
346 			AliasDBMptr = dbm_open(aliasfile, O_RDONLY, DBMMODE);
347 			if (AliasDBMptr == NULL)
348 			{
349 				syserr("initaliases: cannot open %s", buf);
350 				NoAlias = TRUE;
351 				return;
352 			}
353 # endif
354 # endif
355 		}
356 	}
357 	else
358 		atcnt = 1;
359 
360 	/*
361 	**  See if the NDBM version of the file is out of date with
362 	**  the text version.  If so, go into 'init' mode automatically.
363 	**	This only happens if our effective userid owns the DBM.
364 	**	Note the unpalatable hack to see if the stat succeeded.
365 	*/
366 
367 	modtime = stb.st_mtime;
368 	(void) strcpy(buf, aliasfile);
369 # ifdef NEWDB
370 	(void) strcat(buf, ".db");
371 # else
372 	(void) strcat(buf, ".pag");
373 # endif
374 	stb.st_ino = 0;
375 	if (!init && (stat(buf, &stb) < 0 || stb.st_mtime < modtime || atcnt < 0))
376 	{
377 		errno = 0;
378 		if (AutoRebuild && stb.st_ino != 0 && stb.st_uid == geteuid())
379 		{
380 			init = TRUE;
381 			automatic = TRUE;
382 			message(Arpa_Info, "rebuilding alias database");
383 #ifdef LOG
384 			if (LogLevel >= 7)
385 				syslog(LOG_INFO, "rebuilding alias database");
386 #endif /* LOG */
387 		}
388 		else
389 		{
390 #ifdef LOG
391 			if (LogLevel >= 7)
392 				syslog(LOG_INFO, "alias database out of date");
393 #endif /* LOG */
394 			message(Arpa_Info, "Warning: alias database out of date");
395 		}
396 	}
397 
398 
399 	/*
400 	**  If necessary, load the NDBM file.
401 	**	If running without NDBM, load the symbol table.
402 	*/
403 
404 	if (init)
405 	{
406 #ifdef LOG
407 		if (LogLevel >= 6)
408 		{
409 			extern char *username();
410 
411 			syslog(LOG_NOTICE, "alias database %srebuilt by %s",
412 				automatic ? "auto" : "", username());
413 		}
414 #endif /* LOG */
415 		readaliases(aliasfile, TRUE, e);
416 	}
417 # else /* NDBM */
418 	readaliases(aliasfile, init, e);
419 # endif /* NDBM */
420 }
421 /*
422 **  READALIASES -- read and process the alias file.
423 **
424 **	This routine implements the part of initaliases that occurs
425 **	when we are not going to use the DBM stuff.
426 **
427 **	Parameters:
428 **		aliasfile -- the pathname of the alias file master.
429 **		init -- if set, initialize the NDBM stuff.
430 **
431 **	Returns:
432 **		none.
433 **
434 **	Side Effects:
435 **		Reads aliasfile into the symbol table.
436 **		Optionally, builds the .dir & .pag files.
437 */
438 
439 static
440 readaliases(aliasfile, init, e)
441 	char *aliasfile;
442 	bool init;
443 	register ENVELOPE *e;
444 {
445 	register char *p;
446 	char *rhs;
447 	bool skipping;
448 	int naliases, bytes, longest;
449 	FILE *af;
450 	bool makedbmfiles;
451 	void (*oldsigint)();
452 	ADDRESS al, bl;
453 	register STAB *s;
454 # ifdef NEWDB
455 	DB *dbp;
456 # endif
457 # ifdef NDBM
458 	DBM *dbmp;
459 # endif
460 # ifdef LOCKF
461 	struct flock fld;
462 # endif
463 	char line[BUFSIZ];
464 
465 	if ((af = fopen(aliasfile, "r+")) == NULL)
466 	{
467 		if (init)
468 			syserr("Can't open %s", aliasfile);
469 		else if (tTd(27, 1))
470 			printf("Can't open %s\n", aliasfile);
471 		errno = 0;
472 		NoAlias++;
473 		return;
474 	}
475 
476 # if defined(NDBM) || defined(NEWDB)
477 	/* see if someone else is rebuilding the alias file already */
478 # ifdef LOCKF
479 	fld.l_type = F_WRLCK;
480 	fld.l_whence = fld.l_start = fld.l_len = 0;
481 	if (fcntl(fileno(af), F_SETLK, &fld) < 0)
482 # else
483 	if (flock(fileno(af), LOCK_EX | LOCK_NB) < 0 && errno == EWOULDBLOCK)
484 # endif
485 	{
486 		/* yes, they are -- wait until done and then return */
487 		message(Arpa_Info, "Alias file is already being rebuilt");
488 		if (OpMode != MD_INITALIAS)
489 		{
490 			/* wait for other rebuild to complete */
491 # ifdef LOCKF
492 			(void) fcntl(fileno(af), F_SETLKW, &fld);
493 # else
494 			(void) flock(fileno(af), LOCK_EX);
495 # endif
496 		}
497 		(void) fclose(af);
498 		errno = 0;
499 		return;
500 	}
501 # endif /* NDBM */
502 
503 	/*
504 	**  If initializing, create the new DBM files.
505 	*/
506 
507 	if (init)
508 	{
509 		oldsigint = signal(SIGINT, SIG_IGN);
510 # ifdef NEWDB
511 		(void) strcpy(line, aliasfile);
512 		(void) strcat(line, ".db");
513 		dbp = dbopen(line,
514 		    O_RDWR|O_CREAT|O_TRUNC, DBMMODE, DB_HASH, NULL);
515 		if (dbp == NULL)
516 		{
517 			syserr("readaliases: cannot create %s", line);
518 			(void) signal(SIGINT, oldsigint);
519 			return;
520 		}
521 # endif
522 # ifdef IF_MAKEDBMFILES
523 # ifdef NEWDB
524 		makedbmfiles = access("/var/yp/Makefile", R_OK) == 0;
525 # endif
526 		IF_MAKEDBMFILES
527 		{
528 			dbmp = dbm_open(aliasfile,
529 					       O_RDWR|O_CREAT|O_TRUNC, DBMMODE);
530 			if (dbmp == NULL)
531 			{
532 				syserr("readaliases: cannot create %s.{dir,pag}",
533 					aliasfile);
534 				(void) signal(SIGINT, oldsigint);
535 				return;
536 			}
537 		}
538 # endif
539 	}
540 
541 	/*
542 	**  Read and interpret lines
543 	*/
544 
545 	FileName = aliasfile;
546 	LineNumber = 0;
547 	naliases = bytes = longest = 0;
548 	skipping = FALSE;
549 	while (fgets(line, sizeof (line), af) != NULL)
550 	{
551 		int lhssize, rhssize;
552 
553 		LineNumber++;
554 		p = strchr(line, '\n');
555 		if (p != NULL)
556 			*p = '\0';
557 		switch (line[0])
558 		{
559 		  case '#':
560 		  case '\0':
561 			skipping = FALSE;
562 			continue;
563 
564 		  case ' ':
565 		  case '\t':
566 			if (!skipping)
567 				syserr("Non-continuation line starts with space");
568 			skipping = TRUE;
569 			continue;
570 		}
571 		skipping = FALSE;
572 
573 		/*
574 		**  Process the LHS
575 		**	Find the colon separator, and parse the address.
576 		**	It should resolve to a local name -- this will
577 		**	be checked later (we want to optionally do
578 		**	parsing of the RHS first to maximize error
579 		**	detection).
580 		*/
581 
582 		for (p = line; *p != '\0' && *p != ':' && *p != '\n'; p++)
583 			continue;
584 		if (*p++ != ':')
585 		{
586 			syserr("missing colon");
587 			continue;
588 		}
589 		if (parseaddr(line, &al, 1, ':', e) == NULL)
590 		{
591 			syserr("illegal alias name");
592 			continue;
593 		}
594 		loweraddr(&al);
595 
596 		/*
597 		**  Process the RHS.
598 		**	'al' is the internal form of the LHS address.
599 		**	'p' points to the text of the RHS.
600 		*/
601 
602 		rhs = p;
603 		for (;;)
604 		{
605 			register char c;
606 
607 			if (init && CheckAliases)
608 			{
609 				/* do parsing & compression of addresses */
610 				while (*p != '\0')
611 				{
612 					extern char *DelimChar;
613 
614 					while (isspace(*p) || *p == ',')
615 						p++;
616 					if (*p == '\0')
617 						break;
618 					if (parseaddr(p, &bl, -1, ',', e) == NULL)
619 						usrerr("%s... bad address", p);
620 					p = DelimChar;
621 				}
622 			}
623 			else
624 			{
625 				p = &p[strlen(p)];
626 				if (p[-1] == '\n')
627 					*--p = '\0';
628 			}
629 
630 			/* see if there should be a continuation line */
631 			c = fgetc(af);
632 			if (!feof(af))
633 				(void) ungetc(c, af);
634 			if (c != ' ' && c != '\t')
635 				break;
636 
637 			/* read continuation line */
638 			if (fgets(p, sizeof line - (p - line), af) == NULL)
639 				break;
640 			LineNumber++;
641 
642 			/* check for line overflow */
643 			if (strchr(p, '\n') == NULL)
644 			{
645 				usrerr("alias too long");
646 				break;
647 			}
648 		}
649 		if (al.q_mailer != LocalMailer)
650 		{
651 			syserr("cannot alias non-local names");
652 			continue;
653 		}
654 
655 		/*
656 		**  Insert alias into symbol table or DBM file
657 		*/
658 
659 		lhssize = strlen(al.q_user) + 1;
660 		if (!bitnset(M_USR_UPPER, al.q_mailer->m_flags))
661 			makelower(al.q_user);
662 		rhssize = strlen(rhs) + 1;
663 
664 # if defined(NDBM) || defined(NEWDB)
665 		if (init)
666 		{
667 			DBdatum key, content;
668 			int putstat;
669 
670 			key.xx.size = lhssize;
671 			key.xx.data = al.q_user;
672 			content.xx.size = rhssize;
673 			content.xx.data = rhs;
674 # ifdef NEWDB
675 			putstat = dbp->put(dbp, &key.dbt, &content.dbt,
676 					   R_NOOVERWRITE);
677 			if (putstat > 0)
678 			{
679 				usrerr("Warning: duplicate alias name %s",
680 					al.q_user);
681 				putstat = dbp->put(dbp, &key.dbt,
682 						   &content.dbt, 0);
683 			}
684 			if (putstat != 0)
685 				syserr("readaliases: db put (%s)", al.q_user);
686 # endif
687 # ifdef IF_MAKEDBMFILES
688 			IF_MAKEDBMFILES
689 			{
690 				putstat = dbm_store(dbmp, key.dbm, content.dbm,
691 						    DBM_INSERT);
692 				if (putstat > 0)
693 				{
694 					usrerr("Warning: duplicate alias name %s",
695 						al.q_user);
696 					putstat = dbm_store(dbmp, key.dbm,
697 							content.dbm, DBM_REPLACE);
698 				}
699 				if (putstat != 0)
700 					syserr("readaliases: dbm store (%s)",
701 						al.q_user);
702 			}
703 # endif
704 			if (al.q_paddr != NULL)
705 				free(al.q_paddr);
706 			if (al.q_host != NULL)
707 				free(al.q_host);
708 			if (al.q_user != NULL)
709 				free(al.q_user);
710 		}
711 		else
712 # endif /* NDBM */
713 		{
714 			s = stab(al.q_user, ST_ALIAS, ST_ENTER);
715 			s->s_alias = newstr(rhs);
716 		}
717 
718 		/* statistics */
719 		naliases++;
720 		bytes += lhssize + rhssize;
721 		if (rhssize > longest)
722 			longest = rhssize;
723 	}
724 
725 # if defined(NDBM) || defined(NEWDB)
726 	if (init)
727 	{
728 		/* add the distinquished alias "@" */
729 		DBdatum key;
730 
731 		key.xx.size = 2;
732 		key.xx.data = "@";
733 # ifdef NEWDB
734 		if (dbp->sync(dbp) != 0 ||
735 		    dbp->put(dbp, &key.dbt, &key.dbt, 0) != 0 ||
736 		    dbp->close(dbp) != 0)
737 			syserr("readaliases: db close failure");
738 # endif
739 # ifdef IF_MAKEDBMFILES
740 		IF_MAKEDBMFILES
741 		{
742 			if (dbm_store(dbmp, key.dbm, key.dbm, DBM_REPLACE) != 0 ||
743 			    dbm_error(dbmp))
744 				syserr("readaliases: dbm close failure");
745 			dbm_close(dbmp);
746 		}
747 # endif
748 
749 		/* restore the old signal */
750 		(void) signal(SIGINT, oldsigint);
751 	}
752 # endif /* NDBM */
753 
754 	/* closing the alias file drops the lock */
755 	(void) fclose(af);
756 	e->e_to = NULL;
757 	FileName = NULL;
758 	message(Arpa_Info, "%d aliases, longest %d bytes, %d bytes total",
759 			naliases, longest, bytes);
760 # ifdef LOG
761 	if (LogLevel >= 8)
762 		syslog(LOG_INFO, "%d aliases, longest %d bytes, %d bytes total",
763 			naliases, longest, bytes);
764 # endif /* LOG */
765 }
766 /*
767 **  FORWARD -- Try to forward mail
768 **
769 **	This is similar but not identical to aliasing.
770 **
771 **	Parameters:
772 **		user -- the name of the user who's mail we would like
773 **			to forward to.  It must have been verified --
774 **			i.e., the q_home field must have been filled
775 **			in.
776 **		sendq -- a pointer to the head of the send queue to
777 **			put this user's aliases in.
778 **
779 **	Returns:
780 **		none.
781 **
782 **	Side Effects:
783 **		New names are added to send queues.
784 */
785 
786 forward(user, sendq, e)
787 	ADDRESS *user;
788 	ADDRESS **sendq;
789 	register ENVELOPE *e;
790 {
791 	char *pp;
792 	char *ep;
793 	extern bool safefile();
794 
795 	if (tTd(27, 1))
796 		printf("forward(%s)\n", user->q_paddr);
797 
798 	if (user->q_mailer != LocalMailer || bitset(QBADADDR, user->q_flags))
799 		return;
800 	if (user->q_home == NULL)
801 		syserr("forward: no home");
802 
803 	/* good address -- look for .forward file in home */
804 	define('z', user->q_home, e);
805 	define('u', user->q_user, e);
806 	define('h', user->q_host, e);
807 	if (ForwardPath == NULL)
808 		ForwardPath = newstr("\001z/.forward");
809 
810 	for (pp = ForwardPath; pp != NULL; pp = ep)
811 	{
812 		char buf[MAXPATHLEN+1];
813 
814 		ep = strchr(pp, ':');
815 		if (ep != NULL)
816 			*ep = '\0';
817 		expand(pp, buf, &buf[sizeof buf - 1], e);
818 		if (ep != NULL)
819 			*ep++ = ':';
820 		if (tTd(27, 3))
821 			printf("forward: trying %s\n", buf);
822 		if (include(buf, TRUE, user, sendq, e) == 0)
823 			break;
824 	}
825 }
826