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