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