/* @(#)cache_rtld.c 1.1 94/10/31 SMI */

/*
 * ld.so directory caching: run-time link-editor specific functions.
 */

/*
 * Copyright (c) 1989, 1991 by Sun Microsystems, Inc.
 */

#include <sys/param.h>
#include "link.h"
#include "../ld/dynamic.h"
#include "rtld.h"
#include "cache.h"
#include <string.h>

static	struct dd *get_next_dir();
static	char *ask_db();
static	char *get_next_name();
static	struct dd *new_dd();
static	secure_directory();

static	struct dd *ddhead = NULL;	/* master head of list pointer */
static	char *es;			/* LD_LIBRARY_PATH cache */
static	char *default_dirs[] =		/* directories searched by default */
    {"/usr/lib", "/usr/local/lib", '\0'};
static	char *secure_dirs[] = 		/* directories we trust */
    {"/usr/lib","/usr/local/lib","/usr/5lib", '\0'};
static	char **also_secure = NULL;	/* directories contributed by main */

extern	char *library_path;		/* alternate path for library search */
extern	struct link_map *mlmp;		/* main program's link map */

/*
 * Lookup a canonical link_object, return its absolute pathname (or NULL).
 */
char *
lo_lookup(lop, lmp)
	struct link_object *lop;	/* link object searched for */
	struct link_map *lmp;		/* link map in which it occurs */
{
	int i, j, k;			/* working temporaries */
	char *r;			/* rule strings from main link map */
	char *cp;			/* working pointer */
	char *so = NULL;		/* return string */
	struct dd *ddp;			/* directory loop temporary */
	static int once = 1;		/* once-only flag */

	/*
	 * Once-only processing.  Deal with security issues here.  Enable
	 * the main program to specify additional "trusted" directories
	 * through it's link map rules.  
	 *
	 * Historical note: until late in the development of 4.1, the
	 * LD_LIBRARY_PATH environment variable was simply ignored if
	 * we were running at setxid program.  However, this check was
	 * redundant, as the elements from the path are checked for trust
	 * anyway, and it inadvertantly prevented a program from allowing
c	 * the path variable to influence the search among trusted directories.
	 * Now, the library path variable is allowed, but its elements are
	 * ignored if they do not specify trusted directories.  This assumes
	 * that the trust relationship among the directories in the path is
	 * reflexive (i.e., the ordering contributes nothing to the trust.)
	 *
	 * Additional note: "ld" allows relative path names to be put into
	 * the link rules for an executable.  This, in the case of setxid
	 * programs is simply too much rope for people -- as they blithely
	 * use relative pathnames in construction but then are surprised
	 * to find them used again at execution.  This obscures the basic
	 * problem of having relative resources when building a secure
	 * executable, but it's clear that this is just too much rope for
	 * people.  So, "also secure" directories are only those that are
	 * absolute.
	 */
	if (once) {

		/*
		 * If we're secure, and the main link_map has been established,
		 * then see if it has any embedded rules.  If so, copy them
		 * onto the heap, and make a pass through counting the number
		 * of directories that are in the list.  Then allocate an
		 * array of char *'s for "also_secure" of the appropriate
		 * length, and step through the processed string to initialize
		 * the array.  Zero the last element.  N.B.: only absolute
		 * directories (those beginning with "/") are included in
		 * this list.
		 */
		if ((*is_secure)() && (mlmp != NULL)) {
			r = &TEXTBASE(mlmp)[mlmp->lm_ld->ld_un.ld_2->ld_rules];
			if (r != TEXTBASE(mlmp)) {
				cp = (*heap_malloc)(strlen(r) + 1);
				(void) strcpy(cp, r);
				for (r = cp, i = 0; get_next_name(&r); i++)
					;
				also_secure = (char **)(*heap_malloc)((i + 1) *
				    sizeof (char *));
				for (j = k = 0; j < i; j++) {
					if (*cp == '/')
						also_secure[k++] = cp;
					while (*cp++)
						;
				}
				also_secure[k] = NULL;
			}
		}

		/*
		 * Security checks finished, set up the path contributed
		 * by the environment.  As noted above, we no longer prune
		 * this if we're running "secure" -- the lower levels take
		 * care of it for us.
		 */
		if (library_path && *library_path) {
			es = (*heap_malloc)(strlen(library_path) + 1);
			(void) strcpy(es, library_path);
		} else
			es = (char *)NULL;
		once = 0;
	}
	for (ddp = get_next_dir((struct dd *)NULL, lmp); 
	    ddp; ddp = get_next_dir(ddp, lmp))
		if (so = ask_db(ddp->dd_db, TEXTBASE(lmp), lop))
			break;
	return (so);
}

/* 
 * Delete the databases that came from the mmapped file and foreach 
 * dd element reset the database pointer
 */
void
lo_flush()
{
	struct dd *ddp;				/* working dd pointer */
	
	dbd_flush();
	for (ddp = ddhead; ddp; ddp = ddp->dd_next) 
		ddp->dd_db = lo_cache((char *)&AP(ddp->dd_db)[ddp->dd_db->db_name]);
}

/* 
 * Given a db - find the highest shared versioned object. The 
 * highest versioned object is the .so  with a matching major number
 * but the highest minor number
 */
static char *
ask_db(dbp, base, lop)
	struct db *dbp;
	caddr_t base;
	struct link_object *lop;
{
	char *name = (char *)&(base[(int)lop->lo_name]);
	struct dbe *ep;
	struct link_object *tlop;
	int index;
	char l[20];

	/*
	 * Search appropriate hash bucket for a matching entry.
	 */
	index = hash(name, strlen(name), lop->lo_major);
	for (ep = (struct dbe *)&(dbp->db_hash[index]); (ep && ep->dbe_lop); 
	    ep = ep->dbe_next == 0 ? NULL : 
	    (struct dbe *)&AP(dbp)[ep->dbe_next]) {
		tlop = (struct link_object *)&AP(dbp)[ep->dbe_lop];
		if (tlop->lo_major == lop->lo_major)
			if (!strcmp((char *)&AP(dbp)[tlop->lo_name], name))
				break;
	}

	/*
	 * If no entry was found, we've lost.
	 */
	if (!(ep && ep->dbe_lop))
		return (NULL);
	sprintf(l, "%d", lop->lo_minor);
	if (verscmp(l, &AP(dbp)[ep->dbe_name] + tlop->lo_minor) > 0)
		fprintf(stderr, 
		    "ld.so: warning: %s has older revision than expected %d\n",
		    &AP(dbp)[ep->dbe_name], lop->lo_minor);
	return (&AP(dbp)[ep->dbe_name]);
}

/*
 * Given a directory descriptor, find the next one in a list.  List
 * is lazily evaluated, and is sensitive to changes in link maps.
 */
static struct dd *
get_next_dir(ddp, lmp)
	struct dd *ddp;			/* previous directory (or NULL) */
	struct link_map *lmp;		/* link map in which it occurs */
{
	struct dd *rddp;		/* return value */
	struct dd **ddpp;		/* insertion point temporary */
	struct db *dbp;			/* points to db */
	char *ds;			/* directory name string */
	static char *ldr;		/* link_dynamic ld_rules cache */
	static int dhused = 0;		/* default list inserted */
	static int rsused = 0;		/* lmp rules have been copied */
	static char *rs;		/* lmp rules string */
	static char **dcpp = default_dirs;
					/* default directory list position */

	static struct dd *etddp = NULL;
					/* environment tail pointer */
	static struct dd *dhddp = NULL; /* defaults head pointer */
	static struct link_map *plmp = NULL;
					/* previous link map pointer */

	/*
	 * If link map has changed, must discard any old values for
	 * its entries and start over.
	 */
	if (plmp != lmp) {
		if (etddp)
			etddp->dd_next = (struct dd *)NULL;
		else
			ddhead = 0;
		plmp = lmp;
		dhused = 0;
		rs = NULL;
		if ((ldr = &TEXTBASE(lmp)[lmp->lm_ld->ld_un.ld_2->ld_rules])
		    == TEXTBASE(lmp)) 
			rsused = 1;
		else
			rsused = 0;
	}

	/*
	 * Get the next entry on the list if any.  If we're starting
	 * at the front, set pointers accordingly.
	 */
	if (ddp) {
		rddp = ddp->dd_next;
		ddpp = &ddp->dd_next;
	} else {
		rddp = ddhead;
		ddpp = &ddhead;
	}

	/*
	 * If we have a block, we're done -- return it.
	 */
	if (rddp)
		return (rddp);

	/*
	 * Ran off the end of the list, have to get another block if
	 * possible.  Try first with the environment.
	 */
	while (es)
		if (ds = get_next_name(&es))
			if (secure_directory(ds))
				if (dbp = lo_cache(ds)) 
					 return (etddp = new_dd(ddpp, dbp));

	/*
	 * Environment is exhausted, search the link_map specific rules.
	 */
	if (!rsused) {
		rs = (*heap_malloc)(strlen(ldr) + 1);
		(void) strcpy(rs, ldr);
		rsused = 1;
	}
	while (rs)
		if (ds = get_next_name(&rs))
			if (secure_directory(ds))
				if (dbp = lo_cache(ds))
					return (new_dd(ddpp, dbp));

	/*
	 * Environment and link_map specific rules exhausted, now examine
	 * the default directories.  If we've got a head and it has not
	 * yet been inserted, just use that.
	 */
	if (dhddp && !dhused) {
		*ddpp = dhddp;
		dhused = 1;
		return (dhddp);
	}

	/*
	 * Only hope now is that we have more default directories to use.
	 */
	while (ds = *dcpp++)
		if (secure_directory(ds))
			if (dbp = lo_cache(ds)) {
				rddp = new_dd(ddpp, dbp);
				/*
				 * dhused = 1 since ddhead is also
				 * dhddp
				 */
				if (!dhddp) {
					dhddp = rddp;
					dhused = 1;
				}
				return (rddp);
			}

	/*
	 * Out of directories, they lose.
	 */
	return ((struct dd *)NULL);
}

/*
 * Extract list of directories needed from colon separated string.
 */
static char *
get_next_name(list)
	char **list;
{
	char *lp = *list;
	char *cp = *list;;

	if (lp != NULL && *lp != '\0') {
		while (*lp != '\0' && *lp != ':')
			lp++;
		if (*lp == ':') {
			*lp = '\0';
			*list = lp + 1;
			if (**list == '\0')
				*list = NULL;
		} else 
			*list = NULL;
	}
	return(cp);
}

/*
 * Allocate a new dd, append it after ddpp and set the dd_dbp to dbp.
 */
static struct dd *
new_dd(ddpp, dbp)
	struct dd **ddpp;		/* insertion point */
	struct db *dbp;			/* db associated with this dd */
{
	struct dd *ddp;			/* working dd ptr. */

	ddp = (struct dd *)(*heap_malloc)(sizeof(struct dd));
	ddp->dd_db = dbp;
	ddp->dd_next = NULL;
	*ddpp = ddp;
	return (ddp);
}

/*
 * Indicate whether "ds" is a directory we trust.  If
 * security function indicates we don't care, then just return true.
 */
static int
secure_directory(ds)
	char *ds;			/* directory string */
{
	char **cpp;			/* working (char **)string */

	if ((*is_secure)()) {
		for (cpp = secure_dirs; *cpp; cpp++)
			if (strcmp(ds, *cpp) == 0)
				return (1);
		if (also_secure)
			for (cpp = also_secure; *cpp; cpp++)
				if (strcmp(ds, *cpp) == 0)
					return (1);
		return (0);
	} else 
		return (1);
}

