1/*-
2 * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
3 * Copyright (c) 2004-2011 Dag-Erling Sm��rgrav
4 * All rights reserved.
5 *
6 * This software was developed for the FreeBSD Project by ThinkSec AS and
7 * Network Associates Laboratories, the Security Research Division of
8 * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
9 * ("CBOSS"), as part of the DARPA CHATS research program.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. The name of the author may not be used to endorse or promote
20 *    products derived from this software without specific prior written
21 *    permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 * $OpenPAM: openpam_dynamic.c 938 2017-04-30 21:34:42Z des $
36 */
37
38#ifdef HAVE_CONFIG_H
39# include "config.h"
40#endif
41
42#include <sys/param.h>
43
44#include <dlfcn.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51
52#include <security/pam_appl.h>
53
54#include "openpam_impl.h"
55#include "openpam_asprintf.h"
56#include "openpam_ctype.h"
57#include "openpam_dlfunc.h"
58
59#ifndef RTLD_NOW
60#define RTLD_NOW RTLD_LAZY
61#endif
62
63/*
64 * OpenPAM internal
65 *
66 * Perform sanity checks and attempt to load a module
67 */
68
69#ifdef HAVE_FDLOPEN
70static void *
71try_dlopen(const char *modfn)
72{
73	void *dlh;
74	int fd;
75
76	openpam_log(PAM_LOG_LIBDEBUG, "dlopen(%s)", modfn);
77	if ((fd = open(modfn, O_RDONLY)) < 0) {
78		if (errno != ENOENT)
79			openpam_log(PAM_LOG_ERROR, "%s: %m", modfn);
80		return (NULL);
81	}
82	if (OPENPAM_FEATURE(VERIFY_MODULE_FILE) &&
83	    openpam_check_desc_owner_perms(modfn, fd) != 0) {
84		close(fd);
85		return (NULL);
86	}
87	if ((dlh = fdlopen(fd, RTLD_NOW)) == NULL) {
88		openpam_log(PAM_LOG_ERROR, "%s: %s", modfn, dlerror());
89		close(fd);
90		errno = 0;
91		return (NULL);
92	}
93	close(fd);
94	return (dlh);
95}
96#else
97static void *
98try_dlopen(const char *modfn)
99{
100	int check_module_file;
101	void *dlh;
102
103	openpam_log(PAM_LOG_LIBDEBUG, "dlopen(%s)", modfn);
104	openpam_get_feature(OPENPAM_VERIFY_MODULE_FILE,
105	    &check_module_file);
106	if (check_module_file &&
107	    openpam_check_path_owner_perms(modfn) != 0)
108		return (NULL);
109	if ((dlh = dlopen(modfn, RTLD_NOW)) == NULL) {
110		openpam_log(PAM_LOG_ERROR, "%s: %s", modfn, dlerror());
111		errno = 0;
112		return (NULL);
113	}
114	return (dlh);
115}
116#endif
117
118/*
119 * Try to load a module from the suggested location.
120 */
121static pam_module_t *
122try_module(const char *modpath)
123{
124	const pam_module_t *dlmodule;
125	pam_module_t *module;
126	int i, serrno;
127
128	if ((module = calloc(1, sizeof *module)) == NULL ||
129	    (module->path = strdup(modpath)) == NULL ||
130	    (module->dlh = try_dlopen(modpath)) == NULL)
131		goto err;
132	dlmodule = dlsym(module->dlh, "_pam_module");
133	for (i = 0; i < PAM_NUM_PRIMITIVES; ++i) {
134		if (dlmodule) {
135			module->func[i] = dlmodule->func[i];
136		} else {
137			module->func[i] = (pam_func_t)dlfunc(module->dlh,
138			    pam_sm_func_name[i]);
139			/*
140			 * This openpam_log() call is a major source of
141			 * log spam, and the cases that matter are caught
142			 * and logged in openpam_dispatch().  This would
143			 * be less problematic if dlerror() returned an
144			 * error code so we could log an error only when
145			 * dlfunc() failed for a reason other than "no
146			 * such symbol".
147			 */
148#if 0
149			if (module->func[i] == NULL)
150				openpam_log(PAM_LOG_LIBDEBUG, "%s: %s(): %s",
151				    modpath, pam_sm_func_name[i], dlerror());
152#endif
153		}
154	}
155	return (module);
156err:
157	serrno = errno;
158	if (module != NULL) {
159		if (module->dlh != NULL)
160			dlclose(module->dlh);
161		if (module->path != NULL)
162			FREE(module->path);
163		FREE(module);
164	}
165	errno = serrno;
166	if (serrno != 0 && serrno != ENOENT)
167		openpam_log(PAM_LOG_ERROR, "%s: %m", modpath);
168	errno = serrno;
169	return (NULL);
170}
171
172/*
173 * OpenPAM internal
174 *
175 * Locate a dynamically linked module
176 */
177
178pam_module_t *
179openpam_dynamic(const char *modname)
180{
181	pam_module_t *module;
182	char modpath[PATH_MAX];
183	const char **path, *p;
184	int has_so, has_ver;
185	int dot, len;
186
187	/*
188	 * Simple case: module name contains path separator(s)
189	 */
190	if (strchr(modname, '/') != NULL) {
191		/*
192		 * Absolute paths are not allowed if RESTRICT_MODULE_NAME
193		 * is in effect (default off).  Relative paths are never
194		 * allowed.
195		 */
196		if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME) ||
197		    modname[0] != '/') {
198			openpam_log(PAM_LOG_ERROR,
199			    "invalid module name: %s", modname);
200			return (NULL);
201		}
202		return (try_module(modname));
203	}
204
205	/*
206	 * Check for .so and version sufixes
207	 */
208	p = strchr(modname, '\0');
209	has_ver = has_so = 0;
210	while (is_digit(*p))
211		--p;
212	if (*p == '.' && *++p != '\0') {
213		/* found a numeric suffix */
214		has_ver = 1;
215		/* assume that .so is either present or unneeded */
216		has_so = 1;
217	} else if (*p == '\0' && p >= modname + sizeof PAM_SOEXT &&
218	    strcmp(p - sizeof PAM_SOEXT + 1, PAM_SOEXT) == 0) {
219		/* found .so suffix */
220		has_so = 1;
221	}
222
223	/*
224	 * Complicated case: search for the module in the usual places.
225	 */
226	for (path = openpam_module_path; *path != NULL; ++path) {
227		/*
228		 * Assemble the full path, including the version suffix.  Take
229		 * note of where the suffix begins so we can cut it off later.
230		 */
231		if (has_ver)
232			len = snprintf(modpath, sizeof modpath, "%s/%s%n",
233			    *path, modname, &dot);
234		else if (has_so)
235			len = snprintf(modpath, sizeof modpath, "%s/%s%n.%d",
236			    *path, modname, &dot, LIB_MAJ);
237		else
238			len = snprintf(modpath, sizeof modpath, "%s/%s%s%n.%d",
239			    *path, modname, PAM_SOEXT, &dot, LIB_MAJ);
240		/* check for overflow */
241		if (len < 0 || (unsigned int)len >= sizeof modpath) {
242			errno = ENOENT;
243			continue;
244		}
245		/* try the versioned path */
246		if ((module = try_module(modpath)) != NULL)
247			return (module);
248		if (errno == ENOENT && modpath[dot] != '\0') {
249			/* no luck, try the unversioned path */
250			modpath[dot] = '\0';
251			if ((module = try_module(modpath)) != NULL)
252				return (module);
253		}
254	}
255
256	/* :( */
257	return (NULL);
258}
259
260/*
261 * NOPARSE
262 */
263