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