2 * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3 * Copyright (c) 2004-2014 Dag-Erling Smørgrav
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.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
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
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
35 * $Id: openpam_configure.c 612 2012-05-26 23:02:55Z des $
42 #include <sys/param.h>
50 #include <security/pam_appl.h>
52 #include "openpam_impl.h"
53 #include "openpam_ctype.h"
54 #include "openpam_strlcat.h"
55 #include "openpam_strlcpy.h"
57 static int openpam_load_chain(pam_handle_t
*, const char *, pam_facility_t
);
60 * Validate a service name.
62 * Returns a non-zero value if the argument points to a NUL-terminated
63 * string consisting entirely of characters in the POSIX portable filename
64 * character set, excluding the path separator character.
67 valid_service_name(const char *name
)
71 if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME
)) {
72 /* path separator not allowed */
73 for (p
= name
; *p
!= '\0'; ++p
)
77 /* path separator allowed */
78 for (p
= name
; *p
!= '\0'; ++p
)
79 if (!is_pfcs(*p
) && *p
!= '/')
86 * Parse the facility name.
88 * Returns the corresponding pam_facility_t value, or -1 if the argument
89 * is not a valid facility name.
92 parse_facility_name(const char *name
)
96 for (i
= 0; i
< PAM_NUM_FACILITIES
; ++i
)
97 if (strcmp(pam_facility_name
[i
], name
) == 0)
99 return ((pam_facility_t
)-1);
103 * Parse the control flag.
105 * Returns the corresponding pam_control_t value, or -1 if the argument is
106 * not a valid control flag name.
109 parse_control_flag(const char *name
)
113 for (i
= 0; i
< PAM_NUM_CONTROL_FLAGS
; ++i
)
114 if (strcmp(pam_control_flag_name
[i
], name
) == 0)
116 return ((pam_control_t
)-1);
120 * Validate a file name.
122 * Returns a non-zero value if the argument points to a NUL-terminated
123 * string consisting entirely of characters in the POSIX portable filename
124 * character set, including the path separator character.
127 valid_module_name(const char *name
)
131 if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME
)) {
132 /* path separator not allowed */
133 for (p
= name
; *p
!= '\0'; ++p
)
137 /* path separator allowed */
138 for (p
= name
; *p
!= '\0'; ++p
)
139 if (!is_pfcs(*p
) && *p
!= '/')
145 typedef enum { pam_conf_style
, pam_d_style
} openpam_style_t
;
148 * Extracts given chains from a policy file.
150 * Returns the number of policy entries which were found for the specified
151 * service and facility, or -1 if a system error occurred or a syntax
152 * error was encountered.
155 openpam_parse_chain(pam_handle_t
*pamh
,
157 pam_facility_t facility
,
159 const char *filename
,
160 openpam_style_t style
)
162 pam_chain_t
*this, **next
;
165 char *name
, *servicename
, *modulename
;
166 int count
, lineno
, ret
, serrno
;
176 while ((wordv
= openpam_readlinev(f
, &lineno
, &wordc
)) != NULL
) {
184 /* check service name if necessary */
185 if (style
== pam_conf_style
&&
186 strcmp(wordv
[i
++], service
) != 0) {
191 /* check facility name */
192 if ((word
= wordv
[i
++]) == NULL
||
193 (fclt
= parse_facility_name(word
)) == (pam_facility_t
)-1) {
194 openpam_log(PAM_LOG_ERROR
,
195 "%s(%d): missing or invalid facility",
200 if (facility
!= fclt
&& facility
!= PAM_FACILITY_ANY
) {
205 /* check for "include" */
206 if ((word
= wordv
[i
++]) != NULL
&&
207 strcmp(word
, "include") == 0) {
208 if ((servicename
= wordv
[i
++]) == NULL
||
209 !valid_service_name(servicename
)) {
210 openpam_log(PAM_LOG_ERROR
,
211 "%s(%d): missing or invalid service name",
216 if (wordv
[i
] != NULL
) {
217 openpam_log(PAM_LOG_ERROR
,
218 "%s(%d): garbage at end of line",
223 ret
= openpam_load_chain(pamh
, servicename
, fclt
);
227 * Bogus errno, but this ensures that the
228 * outer loop does not just ignore the
229 * error and keep searching.
238 /* get control flag */
239 if (word
== NULL
|| /* same word we compared to "include" */
240 (ctlf
= parse_control_flag(word
)) == (pam_control_t
)-1) {
241 openpam_log(PAM_LOG_ERROR
,
242 "%s(%d): missing or invalid control flag",
248 /* get module name */
249 if ((modulename
= wordv
[i
++]) == NULL
||
250 !valid_module_name(modulename
)) {
251 openpam_log(PAM_LOG_ERROR
,
252 "%s(%d): missing or invalid module name",
258 /* allocate new entry */
259 if ((this = calloc(1, sizeof *this)) == NULL
)
264 if ((this->module
= openpam_load_module(modulename
)) == NULL
) {
271 * The remaining items in wordv are the module's
272 * arguments. We could set this->optv = wordv + i, but
273 * then free(this->optv) wouldn't work. Instead, we free
274 * the words we've already consumed, shift the rest up,
275 * and clear the tail end of the array.
277 this->optc
= wordc
- i
;
278 for (i
= 0; i
< wordc
- this->optc
; ++i
) {
281 for (i
= 0; i
< this->optc
; ++i
) {
282 wordv
[i
] = wordv
[wordc
- this->optc
+ i
];
283 wordv
[wordc
- this->optc
+ i
] = NULL
;
290 for (next
= &pamh
->chains
[fclt
]; *next
!= NULL
;
291 next
= &(*next
)->next
)
298 * The loop ended because openpam_readword() returned NULL, which
299 * can happen for four different reasons: an I/O error (ferror(f)
300 * is true), a memory allocation failure (ferror(f) is false,
301 * feof(f) is false, errno is non-zero), the file ended with an
302 * unterminated quote or backslash escape (ferror(f) is false,
303 * feof(f) is true, errno is non-zero), or the end of the file was
304 * reached without error (ferror(f) is false, feof(f) is true,
307 if (ferror(f
) || errno
!= 0)
315 openpam_log(PAM_LOG_ERROR
, "%s: %m", filename
);
320 if (this && this->optc
&& this->optv
)
321 FREEV(this->optc
, this->optv
);
331 static const char *openpam_policy_path
[] = {
334 "/usr/local/etc/pam.d/",
335 "/usr/local/etc/pam.conf",
340 * Read the specified chains from the specified file.
342 * Returns 0 if the file exists but does not contain any matching lines.
344 * Returns -1 and sets errno to ENOENT if the file does not exist.
346 * Returns -1 and sets errno to some other non-zero value if the file
347 * exists but is unsafe or unreadable, or an I/O error occurs.
350 openpam_load_file(pam_handle_t
*pamh
,
352 pam_facility_t facility
,
353 const char *filename
,
354 openpam_style_t style
)
359 /* attempt to open the file */
360 if ((f
= fopen(filename
, "r")) == NULL
) {
362 openpam_log(errno
== ENOENT
? PAM_LOG_DEBUG
: PAM_LOG_ERROR
,
367 openpam_log(PAM_LOG_DEBUG
, "found %s", filename
);
370 /* verify type, ownership and permissions */
371 if (OPENPAM_FEATURE(VERIFY_POLICY_FILE
) &&
372 openpam_check_desc_owner_perms(filename
, fileno(f
)) != 0) {
373 /* already logged the cause */
381 ret
= openpam_parse_chain(pamh
, service
, facility
,
387 * Locates the policy file for a given service and reads the given chains
390 * Returns the number of policy entries which were found for the specified
391 * service and facility, or -1 if a system error occurred or a syntax
392 * error was encountered.
395 openpam_load_chain(pam_handle_t
*pamh
,
397 pam_facility_t facility
)
399 const char *p
, **path
;
400 char filename
[PATH_MAX
];
402 openpam_style_t style
;
405 ENTERS(facility
< 0 ? "any" : pam_facility_name
[facility
]);
407 /* either absolute or relative to cwd */
408 if (strchr(service
, '/') != NULL
) {
409 if ((p
= strrchr(service
, '.')) != NULL
&& strcmp(p
, ".conf") == 0)
410 style
= pam_conf_style
;
413 ret
= openpam_load_file(pamh
, service
, facility
,
418 /* search standard locations */
419 for (path
= openpam_policy_path
; *path
!= NULL
; ++path
) {
420 /* construct filename */
421 len
= strlcpy(filename
, *path
, sizeof filename
);
422 if (filename
[len
- 1] == '/') {
423 len
= strlcat(filename
, service
, sizeof filename
);
424 if (len
>= sizeof filename
) {
425 errno
= ENAMETOOLONG
;
430 style
= pam_conf_style
;
432 ret
= openpam_load_file(pamh
, service
, facility
,
437 /* the file exists, but an error occurred */
438 if (ret
== -1 && errno
!= ENOENT
)
440 /* in pam.d style, an empty file counts as a hit */
441 if (ret
== 0 && style
== pam_d_style
)
453 * Configure a service
457 openpam_configure(pam_handle_t
*pamh
,
464 if (!valid_service_name(service
)) {
465 openpam_log(PAM_LOG_ERROR
, "invalid service name");
466 RETURNC(PAM_SYSTEM_ERR
);
468 if (openpam_load_chain(pamh
, service
, PAM_FACILITY_ANY
) < 0) {
472 for (fclt
= 0; fclt
< PAM_NUM_FACILITIES
; ++fclt
) {
473 if (pamh
->chains
[fclt
] != NULL
)
475 if (openpam_load_chain(pamh
, PAM_OTHER
, fclt
) < 0)
478 RETURNC(PAM_SUCCESS
);
481 openpam_clear_chains(pamh
->chains
);
483 RETURNC(PAM_SYSTEM_ERR
);