openpam_configure.c revision 1.3.8.1 1 /* $NetBSD: openpam_configure.c,v 1.3.8.1 2018/05/21 04:35:51 pgoyette Exp $ */
2
3 /*-
4 * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
5 * Copyright (c) 2004-2015 Dag-Erling Smrgrav
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.3.8.1 2018/05/21 04:35:51 pgoyette 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