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