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