xref: /netbsd-src/external/bsd/openpam/dist/lib/libpam/openpam_dynamic.c (revision 0d9d0fd8a30be9a1924e715bbcf67a4a83efd262)
1*0d9d0fd8Schristos /*	$NetBSD: openpam_dynamic.c,v 1.4 2023/06/30 21:46:20 christos Exp $	*/
2201780c4Schristos 
376e8c542Schristos /*-
476e8c542Schristos  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
576e8c542Schristos  * Copyright (c) 2004-2011 Dag-Erling Smørgrav
676e8c542Schristos  * All rights reserved.
776e8c542Schristos  *
876e8c542Schristos  * This software was developed for the FreeBSD Project by ThinkSec AS and
976e8c542Schristos  * Network Associates Laboratories, the Security Research Division of
1076e8c542Schristos  * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
1176e8c542Schristos  * ("CBOSS"), as part of the DARPA CHATS research program.
1276e8c542Schristos  *
1376e8c542Schristos  * Redistribution and use in source and binary forms, with or without
1476e8c542Schristos  * modification, are permitted provided that the following conditions
1576e8c542Schristos  * are met:
1676e8c542Schristos  * 1. Redistributions of source code must retain the above copyright
1776e8c542Schristos  *    notice, this list of conditions and the following disclaimer.
1876e8c542Schristos  * 2. Redistributions in binary form must reproduce the above copyright
1976e8c542Schristos  *    notice, this list of conditions and the following disclaimer in the
2076e8c542Schristos  *    documentation and/or other materials provided with the distribution.
2176e8c542Schristos  * 3. The name of the author may not be used to endorse or promote
2276e8c542Schristos  *    products derived from this software without specific prior written
2376e8c542Schristos  *    permission.
2476e8c542Schristos  *
2576e8c542Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2676e8c542Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2776e8c542Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2876e8c542Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2976e8c542Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3076e8c542Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
3176e8c542Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3276e8c542Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3376e8c542Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3476e8c542Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3576e8c542Schristos  * SUCH DAMAGE.
3676e8c542Schristos  */
3776e8c542Schristos 
3876e8c542Schristos #ifdef HAVE_CONFIG_H
3976e8c542Schristos # include "config.h"
4076e8c542Schristos #endif
4176e8c542Schristos 
42201780c4Schristos #include <sys/cdefs.h>
43*0d9d0fd8Schristos __RCSID("$NetBSD: openpam_dynamic.c,v 1.4 2023/06/30 21:46:20 christos Exp $");
44201780c4Schristos 
4576e8c542Schristos #include <sys/param.h>
4676e8c542Schristos 
4776e8c542Schristos #include <dlfcn.h>
4876e8c542Schristos #include <errno.h>
4976e8c542Schristos #include <fcntl.h>
5076e8c542Schristos #include <stdio.h>
5176e8c542Schristos #include <stdlib.h>
5276e8c542Schristos #include <string.h>
5376e8c542Schristos #include <unistd.h>
5476e8c542Schristos 
5576e8c542Schristos #include <security/pam_appl.h>
5676e8c542Schristos 
5776e8c542Schristos #include "openpam_impl.h"
5876e8c542Schristos #include "openpam_asprintf.h"
5976e8c542Schristos #include "openpam_ctype.h"
6076e8c542Schristos #include "openpam_dlfunc.h"
6176e8c542Schristos 
6276e8c542Schristos #ifndef RTLD_NOW
6376e8c542Schristos #define RTLD_NOW RTLD_LAZY
6476e8c542Schristos #endif
6576e8c542Schristos 
6676e8c542Schristos /*
6776e8c542Schristos  * OpenPAM internal
6876e8c542Schristos  *
6976e8c542Schristos  * Perform sanity checks and attempt to load a module
7076e8c542Schristos  */
7176e8c542Schristos 
7276e8c542Schristos #ifdef HAVE_FDLOPEN
7376e8c542Schristos static void *
try_dlopen(const char * modfn,int * error)74201780c4Schristos try_dlopen(const char *modfn, int *error)
7576e8c542Schristos {
7676e8c542Schristos 	void *dlh;
7776e8c542Schristos 	int fd;
7876e8c542Schristos 
7976e8c542Schristos 	openpam_log(PAM_LOG_LIBDEBUG, "dlopen(%s)", modfn);
8076e8c542Schristos 	if ((fd = open(modfn, O_RDONLY)) < 0) {
8176e8c542Schristos 		if (errno != ENOENT)
8276e8c542Schristos 			openpam_log(PAM_LOG_ERROR, "%s: %m", modfn);
8376e8c542Schristos 		return (NULL);
8476e8c542Schristos 	}
8576e8c542Schristos 	if (OPENPAM_FEATURE(VERIFY_MODULE_FILE) &&
8676e8c542Schristos 	    openpam_check_desc_owner_perms(modfn, fd) != 0) {
8776e8c542Schristos 		close(fd);
8876e8c542Schristos 		return (NULL);
8976e8c542Schristos 	}
9076e8c542Schristos 	if ((dlh = fdlopen(fd, RTLD_NOW)) == NULL) {
9176e8c542Schristos 		openpam_log(PAM_LOG_ERROR, "%s: %s", modfn, dlerror());
9276e8c542Schristos 		close(fd);
9376e8c542Schristos 		errno = 0;
9476e8c542Schristos 		return (NULL);
9576e8c542Schristos 	}
9676e8c542Schristos 	close(fd);
9776e8c542Schristos 	return (dlh);
9876e8c542Schristos }
9976e8c542Schristos #else
10076e8c542Schristos static void *
try_dlopen(const char * modfn)10176e8c542Schristos try_dlopen(const char *modfn)
10276e8c542Schristos {
10376e8c542Schristos 	int check_module_file;
10476e8c542Schristos 	void *dlh;
10576e8c542Schristos 
10676e8c542Schristos 	openpam_log(PAM_LOG_LIBDEBUG, "dlopen(%s)", modfn);
10776e8c542Schristos 	openpam_get_feature(OPENPAM_VERIFY_MODULE_FILE,
10876e8c542Schristos 	    &check_module_file);
10976e8c542Schristos 	if (check_module_file &&
11076e8c542Schristos 	    openpam_check_path_owner_perms(modfn) != 0)
11176e8c542Schristos 		return (NULL);
11276e8c542Schristos 	if ((dlh = dlopen(modfn, RTLD_NOW)) == NULL) {
11376e8c542Schristos 		openpam_log(PAM_LOG_ERROR, "%s: %s", modfn, dlerror());
11476e8c542Schristos 		errno = 0;
11576e8c542Schristos 		return (NULL);
11676e8c542Schristos 	}
11776e8c542Schristos 	return (dlh);
11876e8c542Schristos }
11976e8c542Schristos #endif
12076e8c542Schristos 
12176e8c542Schristos /*
12276e8c542Schristos  * Try to load a module from the suggested location.
12376e8c542Schristos  */
12476e8c542Schristos static pam_module_t *
try_module(const char * modpath)12576e8c542Schristos try_module(const char *modpath)
12676e8c542Schristos {
12776e8c542Schristos 	const pam_module_t *dlmodule;
12876e8c542Schristos 	pam_module_t *module;
12976e8c542Schristos 	int i, serrno;
13076e8c542Schristos 
13176e8c542Schristos 	if ((module = calloc(1, sizeof *module)) == NULL ||
13276e8c542Schristos 	    (module->path = strdup(modpath)) == NULL ||
13376e8c542Schristos 	    (module->dlh = try_dlopen(modpath)) == NULL)
13476e8c542Schristos 		goto err;
13576e8c542Schristos 	dlmodule = dlsym(module->dlh, "_pam_module");
13676e8c542Schristos 	for (i = 0; i < PAM_NUM_PRIMITIVES; ++i) {
13776e8c542Schristos 		if (dlmodule) {
13876e8c542Schristos 			module->func[i] = dlmodule->func[i];
13976e8c542Schristos 		} else {
14076e8c542Schristos 			module->func[i] = (pam_func_t)dlfunc(module->dlh,
14176e8c542Schristos 			    pam_sm_func_name[i]);
14276e8c542Schristos 			/*
14376e8c542Schristos 			 * This openpam_log() call is a major source of
14476e8c542Schristos 			 * log spam, and the cases that matter are caught
14576e8c542Schristos 			 * and logged in openpam_dispatch().  This would
14676e8c542Schristos 			 * be less problematic if dlerror() returned an
14776e8c542Schristos 			 * error code so we could log an error only when
14876e8c542Schristos 			 * dlfunc() failed for a reason other than "no
14976e8c542Schristos 			 * such symbol".
15076e8c542Schristos 			 */
15176e8c542Schristos #if 0
15276e8c542Schristos 			if (module->func[i] == NULL)
15376e8c542Schristos 				openpam_log(PAM_LOG_LIBDEBUG, "%s: %s(): %s",
15476e8c542Schristos 				    modpath, pam_sm_func_name[i], dlerror());
15576e8c542Schristos #endif
15676e8c542Schristos 		}
15776e8c542Schristos 	}
15876e8c542Schristos 	return (module);
15976e8c542Schristos err:
16076e8c542Schristos 	serrno = errno;
16176e8c542Schristos 	if (module != NULL) {
16276e8c542Schristos 		if (module->dlh != NULL)
16376e8c542Schristos 			dlclose(module->dlh);
16476e8c542Schristos 		if (module->path != NULL)
16576e8c542Schristos 			FREE(module->path);
16676e8c542Schristos 		FREE(module);
16776e8c542Schristos 	}
16876e8c542Schristos 	errno = serrno;
16976e8c542Schristos 	if (serrno != 0 && serrno != ENOENT)
17076e8c542Schristos 		openpam_log(PAM_LOG_ERROR, "%s: %m", modpath);
17176e8c542Schristos 	errno = serrno;
17276e8c542Schristos 	return (NULL);
17376e8c542Schristos }
17476e8c542Schristos 
17576e8c542Schristos /*
17676e8c542Schristos  * OpenPAM internal
17776e8c542Schristos  *
17876e8c542Schristos  * Locate a dynamically linked module
17976e8c542Schristos  */
18076e8c542Schristos 
18176e8c542Schristos pam_module_t *
openpam_dynamic(const char * modname)18276e8c542Schristos openpam_dynamic(const char *modname)
18376e8c542Schristos {
18476e8c542Schristos 	pam_module_t *module;
18576e8c542Schristos 	char modpath[PATH_MAX];
18676e8c542Schristos 	const char **path, *p;
18776e8c542Schristos 	int has_so, has_ver;
18876e8c542Schristos 	int dot, len;
18976e8c542Schristos 
19076e8c542Schristos 	/*
19176e8c542Schristos 	 * Simple case: module name contains path separator(s)
19276e8c542Schristos 	 */
19376e8c542Schristos 	if (strchr(modname, '/') != NULL) {
19476e8c542Schristos 		/*
19576e8c542Schristos 		 * Absolute paths are not allowed if RESTRICT_MODULE_NAME
19676e8c542Schristos 		 * is in effect (default off).  Relative paths are never
19776e8c542Schristos 		 * allowed.
19876e8c542Schristos 		 */
19976e8c542Schristos 		if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME) ||
20076e8c542Schristos 		    modname[0] != '/') {
20176e8c542Schristos 			openpam_log(PAM_LOG_ERROR,
20276e8c542Schristos 			    "invalid module name: %s", modname);
20376e8c542Schristos 			return (NULL);
20476e8c542Schristos 		}
20576e8c542Schristos 		return (try_module(modname));
20676e8c542Schristos 	}
20776e8c542Schristos 
20876e8c542Schristos 	/*
20976e8c542Schristos 	 * Check for .so and version sufixes
21076e8c542Schristos 	 */
21176e8c542Schristos 	p = strchr(modname, '\0');
21276e8c542Schristos 	has_ver = has_so = 0;
21376e8c542Schristos 	while (is_digit(*p))
21476e8c542Schristos 		--p;
21576e8c542Schristos 	if (*p == '.' && *++p != '\0') {
21676e8c542Schristos 		/* found a numeric suffix */
21776e8c542Schristos 		has_ver = 1;
21876e8c542Schristos 		/* assume that .so is either present or unneeded */
21976e8c542Schristos 		has_so = 1;
22076e8c542Schristos 	} else if (*p == '\0' && p >= modname + sizeof PAM_SOEXT &&
22176e8c542Schristos 	    strcmp(p - sizeof PAM_SOEXT + 1, PAM_SOEXT) == 0) {
22276e8c542Schristos 		/* found .so suffix */
22376e8c542Schristos 		has_so = 1;
22476e8c542Schristos 	}
22576e8c542Schristos 
22676e8c542Schristos 	/*
22776e8c542Schristos 	 * Complicated case: search for the module in the usual places.
22876e8c542Schristos 	 */
22976e8c542Schristos 	for (path = openpam_module_path; *path != NULL; ++path) {
23076e8c542Schristos 		/*
23176e8c542Schristos 		 * Assemble the full path, including the version suffix.  Take
23276e8c542Schristos 		 * note of where the suffix begins so we can cut it off later.
23376e8c542Schristos 		 */
23476e8c542Schristos 		if (has_ver)
23576e8c542Schristos 			len = snprintf(modpath, sizeof modpath, "%s/%s%n",
23676e8c542Schristos 			    *path, modname, &dot);
23776e8c542Schristos 		else if (has_so)
23876e8c542Schristos 			len = snprintf(modpath, sizeof modpath, "%s/%s%n.%d",
23976e8c542Schristos 			    *path, modname, &dot, LIB_MAJ);
24076e8c542Schristos 		else
24176e8c542Schristos 			len = snprintf(modpath, sizeof modpath, "%s/%s%s%n.%d",
24276e8c542Schristos 			    *path, modname, PAM_SOEXT, &dot, LIB_MAJ);
24376e8c542Schristos 		/* check for overflow */
24476e8c542Schristos 		if (len < 0 || (unsigned int)len >= sizeof modpath) {
24576e8c542Schristos 			errno = ENOENT;
24676e8c542Schristos 			continue;
24776e8c542Schristos 		}
24876e8c542Schristos 		/* try the versioned path */
24976e8c542Schristos 		if ((module = try_module(modpath)) != NULL)
25076e8c542Schristos 			return (module);
25176e8c542Schristos 		if (errno == ENOENT && modpath[dot] != '\0') {
25276e8c542Schristos 			/* no luck, try the unversioned path */
25376e8c542Schristos 			modpath[dot] = '\0';
25476e8c542Schristos 			if ((module = try_module(modpath)) != NULL)
25576e8c542Schristos 				return (module);
25676e8c542Schristos 		}
25776e8c542Schristos 	}
25876e8c542Schristos 
25976e8c542Schristos 	/* :( */
26076e8c542Schristos 	return (NULL);
26176e8c542Schristos }
26276e8c542Schristos 
26376e8c542Schristos /*
26476e8c542Schristos  * NOPARSE
26576e8c542Schristos  */
266