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