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