xref: /csrg-svn/usr.sbin/sendmail/src/alias.c (revision 50576)
122694Sdist /*
235073Sbostic  * Copyright (c) 1983 Eric P. Allman
333728Sbostic  * Copyright (c) 1988 Regents of the University of California.
433728Sbostic  * All rights reserved.
533728Sbostic  *
642824Sbostic  * %sccs.include.redist.c%
733728Sbostic  */
822694Sdist 
933728Sbostic #ifndef lint
1033728Sbostic #ifdef DBM
11*50576Seric static char sccsid[] = "@(#)alias.c	5.24 (Berkeley) 07/26/91 (with DBM)";
1233728Sbostic #else
1350575Seric #ifdef NEWDB
14*50576Seric static char sccsid[] = "@(#)alias.c	5.24 (Berkeley) 07/26/91 (with NEWDB)";
1550575Seric #else
16*50576Seric static char sccsid[] = "@(#)alias.c	5.24 (Berkeley) 07/26/91 (without DBM)";
1733728Sbostic #endif
1850575Seric #endif
1933728Sbostic #endif /* not lint */
2033728Sbostic 
2150575Seric # ifdef DBM
2250575Seric # ifdef NEWDB
2350575Seric #   ERROR: must choose one of DBM or NEWDB compilation flags
2450575Seric # endif
2550575Seric # endif
2650575Seric 
274212Seric # include <sys/types.h>
284212Seric # include <sys/stat.h>
298437Seric # include <signal.h>
3019784Seric # include <errno.h>
313309Seric # include "sendmail.h"
3219784Seric # include <sys/file.h>
3336928Sbostic # include <pwd.h>
34292Seric 
3550575Seric # ifdef NEWDB
3650575Seric # include <db.h>
3750575Seric # endif
3850575Seric 
39292Seric /*
40292Seric **  ALIAS -- Compute aliases.
41292Seric **
429368Seric **	Scans the alias file for an alias for the given address.
439368Seric **	If found, it arranges to deliver to the alias list instead.
449368Seric **	Uses libdbm database if -DDBM.
45292Seric **
46292Seric **	Parameters:
474097Seric **		a -- address to alias.
484999Seric **		sendq -- a pointer to the head of the send queue
494999Seric **			to put the aliases in.
50292Seric **
51292Seric **	Returns:
52292Seric **		none
53292Seric **
54292Seric **	Side Effects:
553185Seric **		Aliases found are expanded.
56292Seric **
57292Seric **	Notes:
58292Seric **		If NoAlias (the "-n" flag) is set, no aliasing is
59292Seric **			done.
60292Seric **
61292Seric **	Deficiencies:
62292Seric **		It should complain about names that are aliased to
63292Seric **			nothing.
64292Seric */
65292Seric 
66292Seric 
671503Smark #ifdef DBM
682966Seric typedef struct
692966Seric {
7050575Seric 	char	*data;
7150575Seric 	int	size;
7250575Seric } DBT;
7350575Seric extern DBT fetch();
7450575Seric #endif /* DBM */
75292Seric 
7650575Seric #ifdef NEWDB
7750575Seric static DB	*AliasDBptr;
7850575Seric #endif
7950575Seric 
804999Seric alias(a, sendq)
814097Seric 	register ADDRESS *a;
824999Seric 	ADDRESS **sendq;
83292Seric {
844081Seric 	register char *p;
855701Seric 	extern char *aliaslookup();
86292Seric 
877671Seric 	if (tTd(27, 1))
884098Seric 		printf("alias(%s)\n", a->q_paddr);
89292Seric 
904098Seric 	/* don't realias already aliased names */
914098Seric 	if (bitset(QDONTSEND, a->q_flags))
924098Seric 		return;
934098Seric 
946898Seric 	CurEnv->e_to = a->q_paddr;
954098Seric 
964314Seric 	/*
974314Seric 	**  Look up this name
984314Seric 	*/
994314Seric 
10024944Seric 	if (NoAlias)
10124944Seric 		p = NULL;
10224944Seric 	else
10324944Seric 		p = aliaslookup(a->q_user);
1044098Seric 	if (p == NULL)
1054098Seric 		return;
106292Seric 
107292Seric 	/*
1084098Seric 	**  Match on Alias.
1094098Seric 	**	Deliver to the target list.
1101515Seric 	*/
1111515Seric 
1127671Seric 	if (tTd(27, 1))
1134098Seric 		printf("%s (%s, %s) aliased to %s\n",
1144098Seric 		    a->q_paddr, a->q_host, a->q_user, p);
1157051Seric 	message(Arpa_Info, "aliased to %s", p);
1164098Seric 	AliasLevel++;
1179614Seric 	sendtolist(p, a, sendq);
1184098Seric 	AliasLevel--;
1194098Seric }
1204098Seric /*
1215701Seric **  ALIASLOOKUP -- look up a name in the alias file.
1225701Seric **
1235701Seric **	Parameters:
1245701Seric **		name -- the name to look up.
1255701Seric **
1265701Seric **	Returns:
1275701Seric **		the value of name.
1285701Seric **		NULL if unknown.
1295701Seric **
1305701Seric **	Side Effects:
1315701Seric **		none.
1325701Seric **
1335701Seric **	Warnings:
1345701Seric **		The return value will be trashed across calls.
1355701Seric */
1365701Seric 
1375701Seric char *
1385701Seric aliaslookup(name)
1395701Seric 	char *name;
1405701Seric {
14150575Seric # if defined(NEWDB) || defined(DBM)
14250575Seric 	DBT rhs, lhs;
14350575Seric 	int s;
1445701Seric 
1455701Seric 	/* create a key for fetch */
14650575Seric 	lhs.data = name;
14750575Seric 	lhs.size = strlen(name) + 1;
14850575Seric # ifdef NEWDB
14950575Seric 	s = AliasDBptr->get(AliasDBptr, &lhs, &rhs, 0);
15050575Seric 	if (s != 0)
15150575Seric 		return (NULL);
15250575Seric # else
1535701Seric 	rhs = fetch(lhs);
15450575Seric # endif
15550575Seric 	return (rhs.data);
1565701Seric # else DBM
1575701Seric 	register STAB *s;
1585701Seric 
1595701Seric 	s = stab(name, ST_ALIAS, ST_FIND);
1605701Seric 	if (s == NULL)
1615701Seric 		return (NULL);
1625701Seric 	return (s->s_alias);
1635701Seric # endif DBM
1645701Seric }
1655701Seric /*
1664098Seric **  INITALIASES -- initialize for aliasing
1674098Seric **
1684098Seric **	Very different depending on whether we are running DBM or not.
1694098Seric **
1704098Seric **	Parameters:
1714098Seric **		aliasfile -- location of aliases.
1724157Seric **		init -- if set and if DBM, initialize the DBM files.
1734098Seric **
1744098Seric **	Returns:
1754098Seric **		none.
1764098Seric **
1774098Seric **	Side Effects:
1784098Seric **		initializes aliases:
1794098Seric **		if DBM:  opens the database.
1804098Seric **		if ~DBM: reads the aliases into the symbol table.
1814098Seric */
1824098Seric 
18340559Sbostic # define DBMMODE	0644
1844157Seric 
1854157Seric initaliases(aliasfile, init)
1864098Seric 	char *aliasfile;
1874157Seric 	bool init;
1884098Seric {
18950575Seric #if defined(DBM) || defined(NEWDB)
1908437Seric 	int atcnt;
19125522Seric 	time_t modtime;
19225522Seric 	bool automatic = FALSE;
1934322Seric 	char buf[MAXNAME];
19450575Seric #endif
1959368Seric 	struct stat stb;
19627176Seric 	static bool initialized = FALSE;
19746928Sbostic 	static int readaliases();
1984322Seric 
19927176Seric 	if (initialized)
20027176Seric 		return;
20127176Seric 	initialized = TRUE;
20227176Seric 
20317984Seric 	if (aliasfile == NULL || stat(aliasfile, &stb) < 0)
2048437Seric 	{
20525522Seric 		if (aliasfile != NULL && init)
20625522Seric 			syserr("Cannot open %s", aliasfile);
2078437Seric 		NoAlias = TRUE;
20811937Seric 		errno = 0;
2098437Seric 		return;
2108437Seric 	}
2118437Seric 
21250575Seric # if defined(DBM) || defined(NEWDB)
2134322Seric 	/*
2148437Seric 	**  Check to see that the alias file is complete.
2158437Seric 	**	If not, we will assume that someone died, and it is up
2168437Seric 	**	to us to rebuild it.
2178437Seric 	*/
2188437Seric 
21925689Seric 	if (!init)
22050575Seric 	{
22150575Seric # ifdef NEWDB
22250575Seric 		(void) strcpy(buf, aliasfile);
22350575Seric 		(void) strcat(buf, ".db");
22450575Seric 		AliasDBptr = hash_open(buf, O_RDONLY, DBMMODE, NULL);
225*50576Seric 		if (AliasDBptr == NULL)
226*50576Seric 		{
227*50576Seric 			syserr("initaliases: cannot open %s", buf);
228*50576Seric 			NoAlias = TRUE;
229*50576Seric 			return;
230*50576Seric 		}
23150575Seric # else
23225689Seric 		dbminit(aliasfile);
23350575Seric # endif
23450575Seric 	}
23517471Seric 	atcnt = SafeAlias * 2;
23617471Seric 	if (atcnt > 0)
23717471Seric 	{
23817471Seric 		while (!init && atcnt-- >= 0 && aliaslookup("@") == NULL)
23925689Seric 		{
24025689Seric 			/*
24125689Seric 			**  Reinitialize alias file in case the new
24225689Seric 			**  one is mv'ed in instead of cp'ed in.
24325689Seric 			**
24425689Seric 			**	Only works with new DBM -- old one will
24525689Seric 			**	just consume file descriptors forever.
24625689Seric 			**	If you have a dbmclose() it can be
24725689Seric 			**	added before the sleep(30).
24825689Seric 			*/
24925689Seric 
25050575Seric # ifdef NEWDB
25150575Seric 			AliasDBptr->close(AliasDBptr);
25250575Seric # endif
25350575Seric 
25417471Seric 			sleep(30);
25550575Seric # ifdef NEWDB
25650575Seric 			(void) strcpy(buf, aliasfile);
25750575Seric 			(void) strcat(buf, ".db");
25850575Seric 			AliasDBptr = hash_open(buf, O_RDONLY, DBMMODE, NULL);
259*50576Seric 			if (AliasDBptr == NULL)
260*50576Seric 			{
261*50576Seric 				syserr("initaliases: cannot open %s", buf);
262*50576Seric 				NoAlias = TRUE;
263*50576Seric 				return;
264*50576Seric 			}
26550575Seric # else
26625689Seric # ifdef NDBM
26725689Seric 			dbminit(aliasfile);
26850575Seric # endif
26950575Seric # endif
27025689Seric 		}
27117471Seric 	}
27217471Seric 	else
27317471Seric 		atcnt = 1;
2748437Seric 
2758437Seric 	/*
2764322Seric 	**  See if the DBM version of the file is out of date with
2774322Seric 	**  the text version.  If so, go into 'init' mode automatically.
27840559Sbostic 	**	This only happens if our effective userid owns the DBM.
27940559Sbostic 	**	Note the unpalatable hack to see if the stat succeeded.
2804322Seric 	*/
2814322Seric 
2824322Seric 	modtime = stb.st_mtime;
2834322Seric 	(void) strcpy(buf, aliasfile);
28450575Seric # ifdef NEWDB
28550575Seric 	(void) strcat(buf, ".db");
28650575Seric # else
2874322Seric 	(void) strcat(buf, ".pag");
28850575Seric # endif
2894322Seric 	stb.st_ino = 0;
29019039Seric 	if (!init && (stat(buf, &stb) < 0 || stb.st_mtime < modtime || atcnt < 0))
2914322Seric 	{
29211937Seric 		errno = 0;
29340559Sbostic 		if (AutoRebuild && stb.st_ino != 0 && stb.st_uid == geteuid())
2944322Seric 		{
2954322Seric 			init = TRUE;
29625522Seric 			automatic = TRUE;
2977051Seric 			message(Arpa_Info, "rebuilding alias database");
29824944Seric #ifdef LOG
29924944Seric 			if (LogLevel >= 7)
30024944Seric 				syslog(LOG_INFO, "rebuilding alias database");
30124944Seric #endif LOG
3024322Seric 		}
3034322Seric 		else
3044322Seric 		{
30519039Seric #ifdef LOG
30624944Seric 			if (LogLevel >= 7)
30724944Seric 				syslog(LOG_INFO, "alias database out of date");
30819039Seric #endif LOG
3094322Seric 			message(Arpa_Info, "Warning: alias database out of date");
3104322Seric 		}
3114322Seric 	}
3124322Seric 
3134322Seric 
3144322Seric 	/*
3158437Seric 	**  If necessary, load the DBM file.
3164322Seric 	**	If running without DBM, load the symbol table.
3174322Seric 	*/
3184322Seric 
3194157Seric 	if (init)
3208437Seric 	{
32125522Seric #ifdef LOG
32225522Seric 		if (LogLevel >= 6)
32325522Seric 		{
32425522Seric 			extern char *username();
32525522Seric 
32625522Seric 			syslog(LOG_NOTICE, "alias database %srebuilt by %s",
32725522Seric 				automatic ? "auto" : "", username());
32825522Seric 		}
32925522Seric #endif LOG
3304157Seric 		readaliases(aliasfile, TRUE);
3318437Seric 	}
3324098Seric # else DBM
3334157Seric 	readaliases(aliasfile, init);
3344157Seric # endif DBM
3354157Seric }
3364157Seric /*
3374157Seric **  READALIASES -- read and process the alias file.
3384157Seric **
3394157Seric **	This routine implements the part of initaliases that occurs
3404157Seric **	when we are not going to use the DBM stuff.
3414157Seric **
3424157Seric **	Parameters:
3434157Seric **		aliasfile -- the pathname of the alias file master.
3444157Seric **		init -- if set, initialize the DBM stuff.
3454157Seric **
3464157Seric **	Returns:
3474157Seric **		none.
3484157Seric **
3494157Seric **	Side Effects:
3504157Seric **		Reads aliasfile into the symbol table.
3514157Seric **		Optionally, builds the .dir & .pag files.
3524157Seric */
3534157Seric 
3544157Seric static
3554157Seric readaliases(aliasfile, init)
3564157Seric 	char *aliasfile;
3574157Seric 	bool init;
3584157Seric {
3594098Seric 	register char *p;
3604098Seric 	char *rhs;
3614098Seric 	bool skipping;
3629368Seric 	int naliases, bytes, longest;
3639368Seric 	FILE *af;
36440970Sbostic 	void (*oldsigint)();
3654098Seric 	ADDRESS al, bl;
3664106Seric 	register STAB *s;
36750575Seric # ifdef NEWDB
36850575Seric 	DB *dbp;
36950575Seric # endif
3709368Seric 	char line[BUFSIZ];
3714098Seric 
3724098Seric 	if ((af = fopen(aliasfile, "r")) == NULL)
3731515Seric 	{
3747671Seric 		if (tTd(27, 1))
3754106Seric 			printf("Can't open %s\n", aliasfile);
3764098Seric 		errno = 0;
3774098Seric 		NoAlias++;
3784098Seric 		return;
3794098Seric 	}
3804314Seric 
38150575Seric # if defined(DBM) || defined(NEWDB)
38219784Seric 	/* see if someone else is rebuilding the alias file already */
38319784Seric 	if (flock(fileno(af), LOCK_EX | LOCK_NB) < 0 && errno == EWOULDBLOCK)
38419784Seric 	{
38519784Seric 		/* yes, they are -- wait until done and then return */
38619784Seric 		message(Arpa_Info, "Alias file is already being rebuilt");
38719784Seric 		if (OpMode != MD_INITALIAS)
38819784Seric 		{
38919784Seric 			/* wait for other rebuild to complete */
39019784Seric 			(void) flock(fileno(af), LOCK_EX);
39119784Seric 		}
39223108Seric 		(void) fclose(af);
39319784Seric 		errno = 0;
39419784Seric 		return;
39519784Seric 	}
39619784Seric # endif DBM
39719784Seric 
3984314Seric 	/*
39919784Seric 	**  If initializing, create the new DBM files.
40019784Seric 	*/
40119784Seric 
40219784Seric 	if (init)
40319784Seric 	{
40419784Seric 		oldsigint = signal(SIGINT, SIG_IGN);
40550575Seric # ifdef DBM
40619784Seric 		(void) strcpy(line, aliasfile);
40719784Seric 		(void) strcat(line, ".dir");
40819784Seric 		if (close(creat(line, DBMMODE)) < 0)
40919784Seric 		{
41019784Seric 			syserr("cannot make %s", line);
41119784Seric 			(void) signal(SIGINT, oldsigint);
41219784Seric 			return;
41319784Seric 		}
41419784Seric 		(void) strcpy(line, aliasfile);
41519784Seric 		(void) strcat(line, ".pag");
41619784Seric 		if (close(creat(line, DBMMODE)) < 0)
41719784Seric 		{
41819784Seric 			syserr("cannot make %s", line);
41919784Seric 			(void) signal(SIGINT, oldsigint);
42019784Seric 			return;
42119784Seric 		}
42226501Seric 		dbminit(aliasfile);
42350575Seric # endif
42450575Seric # ifdef NEWDB
42550575Seric 		(void) strcpy(line, aliasfile);
42650575Seric 		(void) strcat(line, ".db");
42750575Seric 		dbp = hash_open(line, O_RDWR|O_CREAT|O_TRUNC, DBMMODE, NULL);
428*50576Seric 		if (dbp == NULL)
429*50576Seric 		{
430*50576Seric 			syserr("readaliases: cannot create %s", line);
431*50576Seric 			(void) signal(SIGINT, oldsigint);
432*50576Seric 			return;
433*50576Seric 		}
43450575Seric # endif
43519784Seric 	}
43619784Seric 
43719784Seric 	/*
4384314Seric 	**  Read and interpret lines
4394314Seric 	*/
4404314Seric 
4419368Seric 	FileName = aliasfile;
4429368Seric 	LineNumber = 0;
4434322Seric 	naliases = bytes = longest = 0;
4444098Seric 	skipping = FALSE;
4454098Seric 	while (fgets(line, sizeof (line), af) != NULL)
4464098Seric 	{
4474322Seric 		int lhssize, rhssize;
4484322Seric 
4499368Seric 		LineNumber++;
45025278Seric 		p = index(line, '\n');
45125278Seric 		if (p != NULL)
45225278Seric 			*p = '\0';
4534098Seric 		switch (line[0])
4544098Seric 		{
4554098Seric 		  case '#':
4564098Seric 		  case '\0':
4574098Seric 			skipping = FALSE;
4584098Seric 			continue;
4594065Seric 
4604098Seric 		  case ' ':
4614098Seric 		  case '\t':
4624098Seric 			if (!skipping)
4639368Seric 				syserr("Non-continuation line starts with space");
4644098Seric 			skipping = TRUE;
4654097Seric 			continue;
4664098Seric 		}
4674098Seric 		skipping = FALSE;
4681874Seric 
4694314Seric 		/*
4704314Seric 		**  Process the LHS
4714314Seric 		**	Find the final colon, and parse the address.
47216898Seric 		**	It should resolve to a local name -- this will
47316898Seric 		**	be checked later (we want to optionally do
47416898Seric 		**	parsing of the RHS first to maximize error
47516898Seric 		**	detection).
4764314Seric 		*/
4774314Seric 
4784098Seric 		for (p = line; *p != '\0' && *p != ':' && *p != '\n'; p++)
4794097Seric 			continue;
48016898Seric 		if (*p++ != ':')
4814098Seric 		{
4829368Seric 			syserr("missing colon");
4834097Seric 			continue;
4844098Seric 		}
48516898Seric 		if (parseaddr(line, &al, 1, ':') == NULL)
4864098Seric 		{
48716898Seric 			syserr("illegal alias name");
48816898Seric 			continue;
4894098Seric 		}
49016898Seric 		loweraddr(&al);
4914314Seric 
4924314Seric 		/*
4934314Seric 		**  Process the RHS.
4944314Seric 		**	'al' is the internal form of the LHS address.
4954314Seric 		**	'p' points to the text of the RHS.
4964314Seric 		*/
4974314Seric 
4984098Seric 		rhs = p;
4994098Seric 		for (;;)
5004098Seric 		{
5014098Seric 			register char c;
5021515Seric 
50325821Seric 			if (init && CheckAliases)
5044098Seric 			{
5054157Seric 				/* do parsing & compression of addresses */
50625278Seric 				while (*p != '\0')
5074098Seric 				{
50825278Seric 					extern char *DelimChar;
50925278Seric 
51025278Seric 					while (isspace(*p) || *p == ',')
5114157Seric 						p++;
51225278Seric 					if (*p == '\0')
51325278Seric 						break;
51425278Seric 					if (parseaddr(p, &bl, -1, ',') == NULL)
51525278Seric 						usrerr("%s... bad address", p);
51625278Seric 					p = DelimChar;
5174098Seric 				}
5184098Seric 			}
5194157Seric 			else
52015769Seric 			{
52116898Seric 				p = &p[strlen(p)];
52216898Seric 				if (p[-1] == '\n')
52316898Seric 					*--p = '\0';
52415769Seric 			}
5251515Seric 
5264098Seric 			/* see if there should be a continuation line */
5274106Seric 			c = fgetc(af);
5284106Seric 			if (!feof(af))
5294314Seric 				(void) ungetc(c, af);
5304106Seric 			if (c != ' ' && c != '\t')
5314098Seric 				break;
5324098Seric 
5334098Seric 			/* read continuation line */
5344098Seric 			if (fgets(p, sizeof line - (p - line), af) == NULL)
5354098Seric 				break;
5369368Seric 			LineNumber++;
5374098Seric 		}
53816898Seric 		if (al.q_mailer != LocalMailer)
53916898Seric 		{
54016898Seric 			syserr("cannot alias non-local names");
54116898Seric 			continue;
54216898Seric 		}
5434314Seric 
5444314Seric 		/*
5454314Seric 		**  Insert alias into symbol table or DBM file
5464314Seric 		*/
5474314Seric 
54816898Seric 		lhssize = strlen(al.q_user) + 1;
5494322Seric 		rhssize = strlen(rhs) + 1;
5504322Seric 
55150575Seric # if defined(DBM) || defined(NEWDB)
5524157Seric 		if (init)
5534157Seric 		{
55450575Seric 			DBT key, content;
5554157Seric 
55650575Seric 			key.size = lhssize;
55750575Seric 			key.data = al.q_user;
55850575Seric 			content.size = rhssize;
55950575Seric 			content.data = rhs;
56050575Seric # ifdef DBM
5614157Seric 			store(key, content);
56250575Seric # else
56350575Seric 			if (dbp->put(dbp, &key, &content, R_PUT) != 0)
564*50576Seric 				syserr("readaliases: db put (%s)", al.q_user);
56550575Seric # endif
5664157Seric 		}
5674157Seric 		else
5684157Seric # endif DBM
5694157Seric 		{
5704157Seric 			s = stab(al.q_user, ST_ALIAS, ST_ENTER);
5714157Seric 			s->s_alias = newstr(rhs);
5724157Seric 		}
5734322Seric 
5744322Seric 		/* statistics */
5754322Seric 		naliases++;
5764322Seric 		bytes += lhssize + rhssize;
5774322Seric 		if (rhssize > longest)
5784322Seric 			longest = rhssize;
5791515Seric 	}
58019784Seric 
58150575Seric # if defined(DBM) || defined(NEWDB)
58219784Seric 	if (init)
58319784Seric 	{
58419784Seric 		/* add the distinquished alias "@" */
58550575Seric 		DBT key;
58619784Seric 
58750575Seric 		key.size = 2;
58850575Seric 		key.data = "@";
58950575Seric # ifdef NEWDB
590*50576Seric 		if (dbp->sync(dbp) != 0 ||
591*50576Seric 		    dbp->put(dbp, &key, &key, R_PUT) != 0 ||
592*50576Seric 		    dbp->close(dbp) != 0)
593*50576Seric 			syserr("readaliases: db close failure");
59450575Seric # else
59519784Seric 		store(key, key);
59650575Seric # endif
59719784Seric 
59819784Seric 		/* restore the old signal */
59919784Seric 		(void) signal(SIGINT, oldsigint);
60019784Seric 	}
60119784Seric # endif DBM
60219784Seric 
60319784Seric 	/* closing the alias file drops the lock */
6044098Seric 	(void) fclose(af);
6056898Seric 	CurEnv->e_to = NULL;
6069368Seric 	FileName = NULL;
6077051Seric 	message(Arpa_Info, "%d aliases, longest %d bytes, %d bytes total",
6084322Seric 			naliases, longest, bytes);
60924944Seric # ifdef LOG
61024944Seric 	if (LogLevel >= 8)
61124944Seric 		syslog(LOG_INFO, "%d aliases, longest %d bytes, %d bytes total",
61224944Seric 			naliases, longest, bytes);
61324944Seric # endif LOG
614292Seric }
615292Seric /*
616292Seric **  FORWARD -- Try to forward mail
617292Seric **
618292Seric **	This is similar but not identical to aliasing.
619292Seric **
620292Seric **	Parameters:
6214314Seric **		user -- the name of the user who's mail we would like
6224314Seric **			to forward to.  It must have been verified --
6234314Seric **			i.e., the q_home field must have been filled
6244314Seric **			in.
6254999Seric **		sendq -- a pointer to the head of the send queue to
6264999Seric **			put this user's aliases in.
627292Seric **
628292Seric **	Returns:
6294098Seric **		none.
630292Seric **
631292Seric **	Side Effects:
6323185Seric **		New names are added to send queues.
633292Seric */
634292Seric 
6354999Seric forward(user, sendq)
6362966Seric 	ADDRESS *user;
6374999Seric 	ADDRESS **sendq;
638292Seric {
6394078Seric 	char buf[60];
6404536Seric 	extern bool safefile();
6414069Seric 
6427671Seric 	if (tTd(27, 1))
6434098Seric 		printf("forward(%s)\n", user->q_paddr);
6444098Seric 
6454594Seric 	if (user->q_mailer != LocalMailer || bitset(QBADADDR, user->q_flags))
6464098Seric 		return;
6474314Seric 	if (user->q_home == NULL)
6484314Seric 		syserr("forward: no home");
6494069Seric 
6504069Seric 	/* good address -- look for .forward file in home */
6519368Seric 	define('z', user->q_home, CurEnv);
65216154Seric 	expand("\001z/.forward", buf, &buf[sizeof buf - 1], CurEnv);
6534536Seric 	if (!safefile(buf, user->q_uid, S_IREAD))
6544098Seric 		return;
6554069Seric 
6564069Seric 	/* we do have an address to forward to -- do it */
6574999Seric 	include(buf, "forwarding", user, sendq);
658292Seric }
659