xref: /freebsd/contrib/openpam/lib/libpam/openpam_configure.c (revision 45538173c120eadca115fd7e56c657905bf73771)
1 /*-
2  * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2025 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 
36 #ifdef HAVE_CONFIG_H
37 # include "config.h"
38 #endif
39 
40 #include <sys/param.h>
41 
42 #include <errno.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 
47 #include <security/pam_appl.h>
48 
49 #include "openpam_impl.h"
50 #include "openpam_ctype.h"
51 #include "openpam_strlcat.h"
52 #include "openpam_strlcpy.h"
53 
54 static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
55 
56 /*
57  * Validate a service name.
58  *
59  * Returns a non-zero value if the argument points to a NUL-terminated
60  * string consisting entirely of characters in the POSIX portable filename
61  * character set, excluding the path separator character.
62  */
63 static int
valid_service_name(const char * name)64 valid_service_name(const char *name)
65 {
66 	const char *p;
67 
68 	if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
69 		/* path separator not allowed */
70 		for (p = name; *p != '\0'; ++p)
71 			if (!is_pfcs(*p))
72 				return (0);
73 	} else {
74 		/* path separator allowed */
75 		for (p = name; *p != '\0'; ++p)
76 			if (!is_pfcs(*p) && *p != '/')
77 				return (0);
78 	}
79 	return (1);
80 }
81 
82 /*
83  * Parse the facility name.
84  *
85  * Returns the corresponding pam_facility_t value, or -1 if the argument
86  * is not a valid facility name.
87  */
88 static pam_facility_t
parse_facility_name(const char * name)89 parse_facility_name(const char *name)
90 {
91 	int i;
92 
93 	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
94 		if (strcmp(pam_facility_name[i], name) == 0)
95 			return (i);
96 	return ((pam_facility_t)-1);
97 }
98 
99 /*
100  * Parse the control flag.
101  *
102  * Returns the corresponding pam_control_t value, or -1 if the argument is
103  * not a valid control flag name.
104  */
105 static pam_control_t
parse_control_flag(const char * name)106 parse_control_flag(const char *name)
107 {
108 	int i;
109 
110 	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
111 		if (strcmp(pam_control_flag_name[i], name) == 0)
112 			return (i);
113 	return ((pam_control_t)-1);
114 }
115 
116 /*
117  * Validate a file name.
118  *
119  * Returns a non-zero value if the argument points to a NUL-terminated
120  * string consisting entirely of characters in the POSIX portable filename
121  * character set, including the path separator character.
122  */
123 static int
valid_module_name(const char * name)124 valid_module_name(const char *name)
125 {
126 	const char *p;
127 
128 	if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
129 		/* path separator not allowed */
130 		for (p = name; *p != '\0'; ++p)
131 			if (!is_pfcs(*p))
132 				return (0);
133 	} else {
134 		/* path separator allowed */
135 		for (p = name; *p != '\0'; ++p)
136 			if (!is_pfcs(*p) && *p != '/')
137 				return (0);
138 	}
139 	return (1);
140 }
141 
142 typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
143 
144 /*
145  * Extracts given chains from a policy file.
146  *
147  * Returns the number of policy entries which were found for the specified
148  * service and facility, or -1 if a system error occurred or a syntax
149  * error was encountered.
150  */
151 static int
openpam_parse_chain(pam_handle_t * pamh,const char * service,pam_facility_t facility,FILE * f,const char * filename,openpam_style_t style)152 openpam_parse_chain(pam_handle_t *pamh,
153 	const char *service,
154 	pam_facility_t facility,
155 	FILE *f,
156 	const char *filename,
157 	openpam_style_t style)
158 {
159 	pam_chain_t *this, **next;
160 	pam_module_t *module;
161 	pam_facility_t fclt;
162 	pam_control_t ctlf;
163 	char *name, *servicename, *modulename;
164 	int count, lineno, nonfatal, ret, serrno;
165 	char **wordv, *word;
166 	int i, wordc;
167 
168 	count = 0;
169 	this = NULL;
170 	name = NULL;
171 	lineno = 0;
172 	wordc = 0;
173 	wordv = NULL;
174 	while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
175 		/* blank line? */
176 		if (wordc == 0) {
177 			FREEV(wordc, wordv);
178 			continue;
179 		}
180 		i = 0;
181 
182 		/* check service name if necessary */
183 		if (style == pam_conf_style &&
184 		    strcmp(wordv[i++], service) != 0) {
185 			FREEV(wordc, wordv);
186 			continue;
187 		}
188 
189 		/* check facility name */
190 		if ((word = wordv[i++]) == NULL) {
191 			openpam_log(PAM_LOG_ERROR,
192 			    "%s(%d): missing facility",
193 			    filename, lineno);
194 			errno = EINVAL;
195 			goto fail;
196 		}
197 		if (*word == '-') {
198 			nonfatal = 1;
199 			word++;
200 		} else {
201 			nonfatal = 0;
202 		}
203 		if ((fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
204 			openpam_log(PAM_LOG_ERROR,
205 			    "%s(%d): invalid facility",
206 			    filename, lineno);
207 			errno = EINVAL;
208 			goto fail;
209 		}
210 		if (facility != fclt && facility != PAM_FACILITY_ANY) {
211 			FREEV(wordc, wordv);
212 			continue;
213 		}
214 
215 		/* control flag or "include" */
216 		if ((word = wordv[i++]) == NULL) {
217 			openpam_log(PAM_LOG_ERROR,
218 			    "%s(%d): missing control flag",
219 			    filename, lineno);
220 			errno = EINVAL;
221 			goto fail;
222 		}
223 		if (strcmp(word, "include") == 0) {
224 			if ((servicename = wordv[i++]) == NULL) {
225 				openpam_log(PAM_LOG_ERROR,
226 				    "%s(%d): missing service name",
227 				    filename, lineno);
228 				errno = EINVAL;
229 				goto fail;
230 			}
231 			if (!valid_service_name(servicename)) {
232 				openpam_log(PAM_LOG_ERROR,
233 				    "%s(%d): invalid service name",
234 				    filename, lineno);
235 				errno = EINVAL;
236 				goto fail;
237 			}
238 			if (wordv[i] != NULL) {
239 				openpam_log(PAM_LOG_ERROR,
240 				    "%s(%d): garbage at end of line",
241 				    filename, lineno);
242 				errno = EINVAL;
243 				goto fail;
244 			}
245 			ret = openpam_load_chain(pamh, servicename, fclt);
246 			FREEV(wordc, wordv);
247 			if (ret < 0) {
248 				/*
249 				 * Bogus errno, but this ensures that the
250 				 * outer loop does not just ignore the
251 				 * error and keep searching.
252 				 */
253 				if (errno == ENOENT) {
254 					if (nonfatal)
255 						continue;
256 					errno = EINVAL;
257 				}
258 				goto fail;
259 			}
260 			continue;
261 		}
262 		if ((ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
263 			openpam_log(PAM_LOG_ERROR,
264 			    "%s(%d): invalid control flag",
265 			    filename, lineno);
266 			errno = EINVAL;
267 			goto fail;
268 		}
269 
270 		/* get module name */
271 		if ((modulename = wordv[i++]) == NULL) {
272 			openpam_log(PAM_LOG_ERROR,
273 			    "%s(%d): missing module name",
274 			    filename, lineno);
275 			errno = EINVAL;
276 			goto fail;
277 		}
278 		if (!valid_module_name(modulename)) {
279 			openpam_log(PAM_LOG_ERROR,
280 			    "%s(%d): invalid module name",
281 			    filename, lineno);
282 			errno = EINVAL;
283 			goto fail;
284 		}
285 
286 		/* load module */
287 		if ((module = openpam_load_module(modulename)) == NULL) {
288 			if (errno == ENOENT) {
289 				if (nonfatal) {
290 					FREEV(wordc, wordv);
291 					continue;
292 				}
293 				errno = ENOEXEC;
294 			}
295 			goto fail;
296 		}
297 
298 		/* allocate new entry */
299 		if ((this = calloc(1, sizeof *this)) == NULL)
300 			goto syserr;
301 		this->flag = ctlf;
302 		this->module = module;
303 
304 		/*
305 		 * The remaining items in wordv are the module's
306 		 * arguments.  We could set this->optv = wordv + i, but
307 		 * then free(this->optv) wouldn't work.  Instead, we free
308 		 * the words we've already consumed, shift the rest up,
309 		 * and clear the tail end of the array.
310 		 */
311 		this->optc = wordc - i;
312 		for (i = 0; i < wordc - this->optc; ++i) {
313 			FREE(wordv[i]);
314 		}
315 		for (i = 0; i < this->optc; ++i) {
316 			wordv[i] = wordv[wordc - this->optc + i];
317 			wordv[wordc - this->optc + i] = NULL;
318 		}
319 		this->optv = wordv;
320 		wordv = NULL;
321 		wordc = 0;
322 
323 		/* hook it up */
324 		for (next = &pamh->chains[fclt]; *next != NULL;
325 		     next = &(*next)->next)
326 			/* nothing */ ;
327 		*next = this;
328 		this = NULL;
329 		++count;
330 	}
331 	/*
332 	 * The loop ended because openpam_readword() returned NULL, which
333 	 * can happen for four different reasons: an I/O error (ferror(f)
334 	 * is true), a memory allocation failure (ferror(f) is false,
335 	 * feof(f) is false, errno is non-zero), the file ended with an
336 	 * unterminated quote or backslash escape (ferror(f) is false,
337 	 * feof(f) is true, errno is non-zero), or the end of the file was
338 	 * reached without error (ferror(f) is false, feof(f) is true,
339 	 * errno is zero).
340 	 */
341 	if (ferror(f) || errno != 0)
342 		goto syserr;
343 	if (!feof(f))
344 		goto fail;
345 	fclose(f);
346 	return (count);
347 syserr:
348 	serrno = errno;
349 	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
350 	errno = serrno;
351 	/* fall through */
352 fail:
353 	serrno = errno;
354 	if (this && this->optc && this->optv)
355 		FREEV(this->optc, this->optv);
356 	FREE(this);
357 	FREEV(wordc, wordv);
358 	FREE(wordv);
359 	FREE(name);
360 	fclose(f);
361 	errno = serrno;
362 	return (-1);
363 }
364 
365 /*
366  * Read the specified chains from the specified file.
367  *
368  * Returns 0 if the file exists but does not contain any matching lines.
369  *
370  * Returns -1 and sets errno to ENOENT if the file does not exist.
371  *
372  * Returns -1 and sets errno to some other non-zero value if the file
373  * exists but is unsafe or unreadable, or an I/O error occurs.
374  */
375 static int
openpam_load_file(pam_handle_t * pamh,const char * service,pam_facility_t facility,const char * filename,openpam_style_t style)376 openpam_load_file(pam_handle_t *pamh,
377 	const char *service,
378 	pam_facility_t facility,
379 	const char *filename,
380 	openpam_style_t style)
381 {
382 	FILE *f;
383 	int ret, serrno;
384 
385 	/* attempt to open the file */
386 	if ((f = fopen(filename, "r")) == NULL) {
387 		serrno = errno;
388 		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
389 		    "%s: %m", filename);
390 		errno = serrno;
391 		RETURNN(-1);
392 	} else {
393 		openpam_log(PAM_LOG_DEBUG, "found %s", filename);
394 	}
395 
396 	/* verify type, ownership and permissions */
397 	if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
398 	    openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
399 		/* already logged the cause */
400 		serrno = errno;
401 		fclose(f);
402 		errno = serrno;
403 		RETURNN(-1);
404 	}
405 
406 	/* parse the file */
407 	ret = openpam_parse_chain(pamh, service, facility,
408 	    f, filename, style);
409 	RETURNN(ret);
410 }
411 
412 /*
413  * Locates the policy file for a given service and reads the given chains
414  * from it.
415  *
416  * Returns the number of policy entries which were found for the specified
417  * service and facility, or -1 if a system error occurred or a syntax
418  * error was encountered.
419  */
420 static int
openpam_load_chain(pam_handle_t * pamh,const char * service,pam_facility_t facility)421 openpam_load_chain(pam_handle_t *pamh,
422 	const char *service,
423 	pam_facility_t facility)
424 {
425 	const char *p, **path;
426 	char filename[PATH_MAX];
427 	size_t len;
428 	openpam_style_t style;
429 	int ret;
430 
431 	ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
432 
433 	/* either absolute or relative to cwd */
434 	if (strchr(service, '/') != NULL) {
435 		if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
436 			style = pam_conf_style;
437 		else
438 			style = pam_d_style;
439 		ret = openpam_load_file(pamh, service, facility,
440 		    service, style);
441 		RETURNN(ret);
442 	}
443 
444 	/* search standard locations */
445 	for (path = openpam_policy_path; *path != NULL; ++path) {
446 		/* construct filename */
447 		len = strlcpy(filename, *path, sizeof filename);
448 		if (len >= sizeof filename) {
449 			errno = ENAMETOOLONG;
450 			RETURNN(-1);
451 		}
452 		if (filename[len - 1] == '/') {
453 			len = strlcat(filename, service, sizeof filename);
454 			if (len >= sizeof filename) {
455 				errno = ENAMETOOLONG;
456 				RETURNN(-1);
457 			}
458 			style = pam_d_style;
459 		} else {
460 			style = pam_conf_style;
461 		}
462 		ret = openpam_load_file(pamh, service, facility,
463 		    filename, style);
464 		/* success */
465 		if (ret > 0)
466 			RETURNN(ret);
467 		/* the file exists, but an error occurred */
468 		if (ret == -1 && errno != ENOENT)
469 			RETURNN(ret);
470 		/* in pam.d style, an empty file counts as a hit */
471 		if (ret == 0 && style == pam_d_style)
472 			RETURNN(ret);
473 	}
474 
475 	/* no hit */
476 	errno = ENOENT;
477 	RETURNN(-1);
478 }
479 
480 /*
481  * OpenPAM internal
482  *
483  * Configure a service
484  */
485 
486 int
openpam_configure(pam_handle_t * pamh,const char * service)487 openpam_configure(pam_handle_t *pamh,
488 	const char *service)
489 {
490 	pam_facility_t fclt;
491 	int serrno;
492 
493 	ENTERS(service);
494 	if (!valid_service_name(service)) {
495 		openpam_log(PAM_LOG_ERROR, "invalid service name");
496 		RETURNC(PAM_SYSTEM_ERR);
497 	}
498 	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
499 		if (errno != ENOENT)
500 			goto load_err;
501 	}
502 	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
503 		if (pamh->chains[fclt] != NULL)
504 			continue;
505 		if (OPENPAM_FEATURE(FALLBACK_TO_OTHER)) {
506 			if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
507 				goto load_err;
508 		}
509 	}
510 	RETURNC(PAM_SUCCESS);
511 load_err:
512 	serrno = errno;
513 	openpam_clear_chains(pamh->chains);
514 	errno = serrno;
515 	RETURNC(PAM_SYSTEM_ERR);
516 }
517 
518 /*
519  * NODOC
520  *
521  * Error codes:
522  *	PAM_SYSTEM_ERR
523  */
524