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