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