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