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