1 # include <pwd.h>
2 # include <sys/types.h>
3 # include <sys/stat.h>
4 # include "sendmail.h"
5 
6 # ifdef DBM
7 static char SccsId[] = "@(#)alias.c	3.26	10/23/81	(with DBM)";
8 # else DBM
9 static char SccsId[] = "@(#)alias.c	3.26	10/23/81	(without DBM)";
10 # endif DBM
11 
12 /*
13 **  ALIAS -- Compute aliases.
14 **
15 **	Scans the file /usr/lib/aliases for a set of aliases.
16 **	If found, it arranges to deliver to them.  Uses libdbm
17 **	database if -DDBM.
18 **
19 **	Parameters:
20 **		a -- address to alias.
21 **
22 **	Returns:
23 **		none
24 **
25 **	Side Effects:
26 **		Aliases found are expanded.
27 **
28 **	Files:
29 **		/usr/lib/aliases -- the mail aliases.  The format is
30 **			a series of lines of the form:
31 **				alias:name1,name2,name3,...
32 **			where 'alias' expands to all of
33 **			'name[i]'.  Continuations begin with
34 **			space or tab.
35 **		/usr/lib/aliases.pag, /usr/lib/aliases.dir: libdbm version
36 **			of alias file.  Keys are aliases, datums
37 **			(data?) are name1,name2, ...
38 **
39 **	Notes:
40 **		If NoAlias (the "-n" flag) is set, no aliasing is
41 **			done.
42 **
43 **	Deficiencies:
44 **		It should complain about names that are aliased to
45 **			nothing.
46 **		It is unsophisticated about line overflows.
47 */
48 
49 
50 #ifdef DBM
51 typedef struct
52 {
53 	char	*dptr;
54 	int	dsize;
55 } DATUM;
56 DATUM lhs, rhs;
57 extern DATUM fetch();
58 #endif DBM
59 
60 alias(a)
61 	register ADDRESS *a;
62 {
63 	register char *p;
64 # ifndef DBM
65 	register STAB *s;
66 # endif DBM
67 
68 	if (NoAlias)
69 		return;
70 # ifdef DEBUG
71 	if (Debug)
72 		printf("alias(%s)\n", a->q_paddr);
73 # endif
74 
75 	/* don't realias already aliased names */
76 	if (bitset(QDONTSEND, a->q_flags))
77 		return;
78 
79 	To = a->q_paddr;
80 
81 	/*
82 	**  Look up this name
83 	*/
84 
85 # ifdef DBM
86 	/* create a key for fetch */
87 	lhs.dptr = a->q_user;
88 	lhs.dsize = strlen(a->q_user) + 1;
89 	rhs = fetch(lhs);
90 
91 	/* find this alias? */
92 	p = rhs.dptr;
93 	if (p == NULL)
94 		return;
95 # else DBM
96 	s = stab(a->q_user, ST_ALIAS, ST_FIND);
97 	if (s == NULL)
98 		return;
99 	p = s->s_alias;
100 # endif DBM
101 
102 	/*
103 	**  Match on Alias.
104 	**	Deliver to the target list.
105 	*/
106 
107 # ifdef DEBUG
108 	if (Debug)
109 		printf("%s (%s, %s) aliased to %s\n",
110 		    a->q_paddr, a->q_host, a->q_user, p);
111 # endif
112 	if (Verbose)
113 		message(Arpa_Info, "aliased to %s", p);
114 	AliasLevel++;
115 	sendto(p, 1, a);
116 	AliasLevel--;
117 }
118 /*
119 **  INITALIASES -- initialize for aliasing
120 **
121 **	Very different depending on whether we are running DBM or not.
122 **
123 **	Parameters:
124 **		aliasfile -- location of aliases.
125 **		init -- if set and if DBM, initialize the DBM files.
126 **
127 **	Returns:
128 **		none.
129 **
130 **	Side Effects:
131 **		initializes aliases:
132 **		if DBM:  opens the database.
133 **		if ~DBM: reads the aliases into the symbol table.
134 */
135 
136 # define DBMMODE	0666
137 
138 initaliases(aliasfile, init)
139 	char *aliasfile;
140 	bool init;
141 {
142 	char buf[MAXNAME];
143 	struct stat stb;
144 	time_t modtime;
145 
146 	/*
147 	**  See if the DBM version of the file is out of date with
148 	**  the text version.  If so, go into 'init' mode automatically.
149 	**	This only happens if our effective userid owns the DBM
150 	**	version or if the mode of the database is 666 -- this
151 	**	is an attempt to avoid protection problems.  Note the
152 	**	unpalatable hack to see if the stat succeeded.
153 	*/
154 
155 	if (stat(aliasfile, &stb) < 0)
156 		return;
157 # ifdef DBM
158 	modtime = stb.st_mtime;
159 	(void) strcpy(buf, aliasfile);
160 	(void) strcat(buf, ".pag");
161 	stb.st_ino = 0;
162 	if ((stat(buf, &stb) < 0 || stb.st_mtime < modtime) && !init)
163 	{
164 		if (stb.st_ino != 0 &&
165 		    ((stb.st_mode & 0666) == 0666 || stb.st_uid == geteuid()))
166 		{
167 			init = TRUE;
168 			if (Verbose)
169 				message(Arpa_Info, "rebuilding alias database");
170 		}
171 		else
172 		{
173 			message(Arpa_Info, "Warning: alias database out of date");
174 		}
175 	}
176 # endif DBM
177 
178 	/*
179 	**  If initializing, create the new files.
180 	**	We should lock the alias file here to prevent other
181 	**	instantiations of sendmail from reading an incomplete
182 	**	file -- or worse yet, doing a concurrent initialize.
183 	*/
184 
185 # ifdef DBM
186 	if (init)
187 	{
188 		(void) strcpy(buf, aliasfile);
189 		(void) strcat(buf, ".dir");
190 		if (close(creat(buf, DBMMODE)) < 0)
191 		{
192 			syserr("cannot make %s", buf);
193 			return;
194 		}
195 		(void) strcpy(buf, aliasfile);
196 		(void) strcat(buf, ".pag");
197 		if (close(creat(buf, DBMMODE)) < 0)
198 		{
199 			syserr("cannot make %s", buf);
200 			return;
201 		}
202 	}
203 
204 	/*
205 	**  Open and, if necessary, load the DBM file.
206 	**	If running without DBM, load the symbol table.
207 	*/
208 
209 	dbminit(aliasfile);
210 	if (init)
211 		readaliases(aliasfile, TRUE);
212 # else DBM
213 	readaliases(aliasfile, init);
214 # endif DBM
215 }
216 /*
217 **  READALIASES -- read and process the alias file.
218 **
219 **	This routine implements the part of initaliases that occurs
220 **	when we are not going to use the DBM stuff.
221 **
222 **	Parameters:
223 **		aliasfile -- the pathname of the alias file master.
224 **		init -- if set, initialize the DBM stuff.
225 **
226 **	Returns:
227 **		none.
228 **
229 **	Side Effects:
230 **		Reads aliasfile into the symbol table.
231 **		Optionally, builds the .dir & .pag files.
232 */
233 
234 static
235 readaliases(aliasfile, init)
236 	char *aliasfile;
237 	bool init;
238 {
239 	char line[BUFSIZ];
240 	register char *p;
241 	char *p2;
242 	char *rhs;
243 	bool skipping;
244 	ADDRESS al, bl;
245 	FILE *af;
246 	int lineno;
247 	register STAB *s;
248 	int naliases, bytes, longest;
249 
250 	if ((af = fopen(aliasfile, "r")) == NULL)
251 	{
252 # ifdef DEBUG
253 		if (Debug)
254 			printf("Can't open %s\n", aliasfile);
255 # endif
256 		errno = 0;
257 		NoAlias++;
258 		return;
259 	}
260 
261 	/*
262 	**  Read and interpret lines
263 	*/
264 
265 	lineno = 0;
266 	naliases = bytes = longest = 0;
267 	skipping = FALSE;
268 	while (fgets(line, sizeof (line), af) != NULL)
269 	{
270 		int lhssize, rhssize;
271 
272 		lineno++;
273 		switch (line[0])
274 		{
275 		  case '#':
276 		  case '\n':
277 		  case '\0':
278 			skipping = FALSE;
279 			continue;
280 
281 		  case ' ':
282 		  case '\t':
283 			if (!skipping)
284 				syserr("aliases: %d: Non-continuation line starts with space", lineno);
285 			skipping = TRUE;
286 			continue;
287 		}
288 		skipping = FALSE;
289 
290 		/*
291 		**  Process the LHS
292 		**	Find the final colon, and parse the address.
293 		**	It should resolve to a local name -- this will
294 		**	be checked later (we want to optionally do
295 		**	parsing of the RHS first to maximize error
296 		**	detection).
297 		*/
298 
299 		for (p = line; *p != '\0' && *p != ':' && *p != '\n'; p++)
300 			continue;
301 		if (*p == '\0' || *p == '\n')
302 		{
303 		 syntaxerr:
304 			syserr("aliases: %d: missing colon", lineno);
305 			continue;
306 		}
307 		*p++ = '\0';
308 		if (parse(line, &al, 1) == NULL)
309 		{
310 			*--p = ':';
311 			goto syntaxerr;
312 		}
313 
314 		/*
315 		**  Process the RHS.
316 		**	'al' is the internal form of the LHS address.
317 		**	'p' points to the text of the RHS.
318 		*/
319 
320 		rhs = p;
321 		for (;;)
322 		{
323 			register char c;
324 
325 			if (init)
326 			{
327 				/* do parsing & compression of addresses */
328 				c = *p;
329 				while (c != '\0')
330 				{
331 					p2 = p;
332 					while (*p != '\n' && *p != ',' && *p != '\0')
333 						p++;
334 					c = *p;
335 					*p++ = '\0';
336 					if (c == '\n')
337 						c = '\0';
338 					if (*p2 == '\0')
339 					{
340 						p[-1] = c;
341 						continue;
342 					}
343 					(void) parse(p2, &bl, -1);
344 					p[-1] = c;
345 					while (isspace(*p))
346 						p++;
347 				}
348 			}
349 			else
350 				p = &p[strlen(p)];
351 
352 			/* see if there should be a continuation line */
353 			c = fgetc(af);
354 			if (!feof(af))
355 				(void) ungetc(c, af);
356 			if (c != ' ' && c != '\t')
357 				break;
358 
359 			/* read continuation line */
360 			p--;
361 			if (fgets(p, sizeof line - (p - line), af) == NULL)
362 				break;
363 			lineno++;
364 		}
365 		if (al.q_mailer != LocalMailer)
366 		{
367 			syserr("aliases: %d: cannot alias non-local names", lineno);
368 			continue;
369 		}
370 
371 		/*
372 		**  Insert alias into symbol table or DBM file
373 		*/
374 
375 		lhssize = strlen(al.q_user) + 1;
376 		rhssize = strlen(rhs) + 1;
377 
378 # ifdef DBM
379 		if (init)
380 		{
381 			DATUM key, content;
382 
383 			key.dsize = lhssize;
384 			key.dptr = al.q_user;
385 			content.dsize = rhssize;
386 			content.dptr = rhs;
387 			store(key, content);
388 		}
389 		else
390 # endif DBM
391 		{
392 			s = stab(al.q_user, ST_ALIAS, ST_ENTER);
393 			s->s_alias = newstr(rhs);
394 		}
395 
396 		/* statistics */
397 		naliases++;
398 		bytes += lhssize + rhssize;
399 		if (rhssize > longest)
400 			longest = rhssize;
401 	}
402 	(void) fclose(af);
403 	To = NULL;
404 	if (Verbose)
405 		message(Arpa_Info, "%d aliases, longest %d bytes, %d bytes total",
406 			naliases, longest, bytes);
407 }
408 /*
409 **  FORWARD -- Try to forward mail
410 **
411 **	This is similar but not identical to aliasing.
412 **
413 **	Parameters:
414 **		user -- the name of the user who's mail we would like
415 **			to forward to.  It must have been verified --
416 **			i.e., the q_home field must have been filled
417 **			in.
418 **
419 **	Returns:
420 **		none.
421 **
422 **	Side Effects:
423 **		New names are added to send queues.
424 */
425 
426 forward(user)
427 	ADDRESS *user;
428 {
429 	char buf[60];
430 	extern bool safefile();
431 
432 # ifdef DEBUG
433 	if (Debug)
434 		printf("forward(%s)\n", user->q_paddr);
435 # endif DEBUG
436 
437 	if (user->q_mailer != LocalMailer || bitset(QBADADDR, user->q_flags))
438 		return;
439 # ifdef DEBUG
440 	if (user->q_home == NULL)
441 		syserr("forward: no home");
442 # endif DEBUG
443 
444 	/* good address -- look for .forward file in home */
445 	define('z', user->q_home);
446 	(void) expand("$z/.forward", buf, &buf[sizeof buf - 1]);
447 	if (!safefile(buf, user->q_uid, S_IREAD))
448 		return;
449 
450 	/* we do have an address to forward to -- do it */
451 	include(buf, "forwarding", user);
452 }
453