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