483 lines
12 KiB
C
483 lines
12 KiB
C
/*-
|
|
* Copyright (c) 2001-2003 Networks Associates Technology, Inc.
|
|
* Copyright (c) 2004-2014 Dag-Erling Smørgrav
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed for the FreeBSD Project by ThinkSec AS and
|
|
* Network Associates Laboratories, the Security Research Division of
|
|
* Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
|
|
* ("CBOSS"), as part of the DARPA CHATS research program.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $Id: openpam_configure.c 796 2014-06-03 21:30:08Z des $
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <security/pam_appl.h>
|
|
|
|
#include "openpam_impl.h"
|
|
#include "openpam_ctype.h"
|
|
#include "openpam_strlcat.h"
|
|
#include "openpam_strlcpy.h"
|
|
|
|
static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
|
|
|
|
/*
|
|
* Validate a service name.
|
|
*
|
|
* Returns a non-zero value if the argument points to a NUL-terminated
|
|
* string consisting entirely of characters in the POSIX portable filename
|
|
* character set, excluding the path separator character.
|
|
*/
|
|
static int
|
|
valid_service_name(const char *name)
|
|
{
|
|
const char *p;
|
|
|
|
if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
|
|
/* path separator not allowed */
|
|
for (p = name; *p != '\0'; ++p)
|
|
if (!is_pfcs(*p))
|
|
return (0);
|
|
} else {
|
|
/* path separator allowed */
|
|
for (p = name; *p != '\0'; ++p)
|
|
if (!is_pfcs(*p) && *p != '/')
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Parse the facility name.
|
|
*
|
|
* Returns the corresponding pam_facility_t value, or -1 if the argument
|
|
* is not a valid facility name.
|
|
*/
|
|
static pam_facility_t
|
|
parse_facility_name(const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < PAM_NUM_FACILITIES; ++i)
|
|
if (strcmp(pam_facility_name[i], name) == 0)
|
|
return (i);
|
|
return ((pam_facility_t)-1);
|
|
}
|
|
|
|
/*
|
|
* Parse the control flag.
|
|
*
|
|
* Returns the corresponding pam_control_t value, or -1 if the argument is
|
|
* not a valid control flag name.
|
|
*/
|
|
static pam_control_t
|
|
parse_control_flag(const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
|
|
if (strcmp(pam_control_flag_name[i], name) == 0)
|
|
return (i);
|
|
return ((pam_control_t)-1);
|
|
}
|
|
|
|
/*
|
|
* Validate a file name.
|
|
*
|
|
* Returns a non-zero value if the argument points to a NUL-terminated
|
|
* string consisting entirely of characters in the POSIX portable filename
|
|
* character set, including the path separator character.
|
|
*/
|
|
static int
|
|
valid_module_name(const char *name)
|
|
{
|
|
const char *p;
|
|
|
|
if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
|
|
/* path separator not allowed */
|
|
for (p = name; *p != '\0'; ++p)
|
|
if (!is_pfcs(*p))
|
|
return (0);
|
|
} else {
|
|
/* path separator allowed */
|
|
for (p = name; *p != '\0'; ++p)
|
|
if (!is_pfcs(*p) && *p != '/')
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
|
|
|
|
/*
|
|
* Extracts given chains from a policy file.
|
|
*
|
|
* Returns the number of policy entries which were found for the specified
|
|
* service and facility, or -1 if a system error occurred or a syntax
|
|
* error was encountered.
|
|
*/
|
|
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)
|
|
{
|
|
pam_chain_t *this, **next;
|
|
pam_facility_t fclt;
|
|
pam_control_t ctlf;
|
|
char *name, *servicename, *modulename;
|
|
int count, lineno, ret, serrno;
|
|
char **wordv, *word;
|
|
int i, wordc;
|
|
|
|
count = 0;
|
|
this = NULL;
|
|
name = NULL;
|
|
lineno = 0;
|
|
wordc = 0;
|
|
wordv = NULL;
|
|
while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
|
|
/* blank line? */
|
|
if (wordc == 0) {
|
|
FREEV(wordc, wordv);
|
|
continue;
|
|
}
|
|
i = 0;
|
|
|
|
/* check service name if necessary */
|
|
if (style == pam_conf_style &&
|
|
strcmp(wordv[i++], service) != 0) {
|
|
FREEV(wordc, wordv);
|
|
continue;
|
|
}
|
|
|
|
/* check facility name */
|
|
if ((word = wordv[i++]) == NULL ||
|
|
(fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
|
|
openpam_log(PAM_LOG_ERROR,
|
|
"%s(%d): missing or invalid facility",
|
|
filename, lineno);
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
if (facility != fclt && facility != PAM_FACILITY_ANY) {
|
|
FREEV(wordc, wordv);
|
|
continue;
|
|
}
|
|
|
|
/* check for "include" */
|
|
if ((word = wordv[i++]) != NULL &&
|
|
strcmp(word, "include") == 0) {
|
|
if ((servicename = wordv[i++]) == NULL ||
|
|
!valid_service_name(servicename)) {
|
|
openpam_log(PAM_LOG_ERROR,
|
|
"%s(%d): missing or invalid service name",
|
|
filename, lineno);
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
if (wordv[i] != NULL) {
|
|
openpam_log(PAM_LOG_ERROR,
|
|
"%s(%d): garbage at end of line",
|
|
filename, lineno);
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
ret = openpam_load_chain(pamh, servicename, fclt);
|
|
FREEV(wordc, wordv);
|
|
if (ret < 0) {
|
|
/*
|
|
* Bogus errno, but this ensures that the
|
|
* outer loop does not just ignore the
|
|
* error and keep searching.
|
|
*/
|
|
if (errno == ENOENT)
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* get control flag */
|
|
if (word == NULL || /* same word we compared to "include" */
|
|
(ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
|
|
openpam_log(PAM_LOG_ERROR,
|
|
"%s(%d): missing or invalid control flag",
|
|
filename, lineno);
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* get module name */
|
|
if ((modulename = wordv[i++]) == NULL ||
|
|
!valid_module_name(modulename)) {
|
|
openpam_log(PAM_LOG_ERROR,
|
|
"%s(%d): missing or invalid module name",
|
|
filename, lineno);
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* allocate new entry */
|
|
if ((this = calloc(1, sizeof *this)) == NULL)
|
|
goto syserr;
|
|
this->flag = ctlf;
|
|
|
|
/* load module */
|
|
if ((this->module = openpam_load_module(modulename)) == NULL) {
|
|
if (errno == ENOENT)
|
|
errno = ENOEXEC;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* The remaining items in wordv are the module's
|
|
* arguments. We could set this->optv = wordv + i, but
|
|
* then free(this->optv) wouldn't work. Instead, we free
|
|
* the words we've already consumed, shift the rest up,
|
|
* and clear the tail end of the array.
|
|
*/
|
|
this->optc = wordc - i;
|
|
for (i = 0; i < wordc - this->optc; ++i) {
|
|
FREE(wordv[i]);
|
|
}
|
|
for (i = 0; i < this->optc; ++i) {
|
|
wordv[i] = wordv[wordc - this->optc + i];
|
|
wordv[wordc - this->optc + i] = NULL;
|
|
}
|
|
this->optv = wordv;
|
|
wordv = NULL;
|
|
wordc = 0;
|
|
|
|
/* hook it up */
|
|
for (next = &pamh->chains[fclt]; *next != NULL;
|
|
next = &(*next)->next)
|
|
/* nothing */ ;
|
|
*next = this;
|
|
this = NULL;
|
|
++count;
|
|
}
|
|
/*
|
|
* The loop ended because openpam_readword() returned NULL, which
|
|
* can happen for four different reasons: an I/O error (ferror(f)
|
|
* is true), a memory allocation failure (ferror(f) is false,
|
|
* feof(f) is false, errno is non-zero), the file ended with an
|
|
* unterminated quote or backslash escape (ferror(f) is false,
|
|
* feof(f) is true, errno is non-zero), or the end of the file was
|
|
* reached without error (ferror(f) is false, feof(f) is true,
|
|
* errno is zero).
|
|
*/
|
|
if (ferror(f) || errno != 0)
|
|
goto syserr;
|
|
if (!feof(f))
|
|
goto fail;
|
|
fclose(f);
|
|
return (count);
|
|
syserr:
|
|
serrno = errno;
|
|
openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
|
|
errno = serrno;
|
|
/* fall through */
|
|
fail:
|
|
serrno = errno;
|
|
if (this && this->optc && this->optv)
|
|
FREEV(this->optc, this->optv);
|
|
FREE(this);
|
|
FREEV(wordc, wordv);
|
|
FREE(wordv);
|
|
FREE(name);
|
|
fclose(f);
|
|
errno = serrno;
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Read the specified chains from the specified file.
|
|
*
|
|
* Returns 0 if the file exists but does not contain any matching lines.
|
|
*
|
|
* Returns -1 and sets errno to ENOENT if the file does not exist.
|
|
*
|
|
* Returns -1 and sets errno to some other non-zero value if the file
|
|
* exists but is unsafe or unreadable, or an I/O error occurs.
|
|
*/
|
|
static int
|
|
openpam_load_file(pam_handle_t *pamh,
|
|
const char *service,
|
|
pam_facility_t facility,
|
|
const char *filename,
|
|
openpam_style_t style)
|
|
{
|
|
FILE *f;
|
|
int ret, serrno;
|
|
|
|
/* attempt to open the file */
|
|
if ((f = fopen(filename, "r")) == NULL) {
|
|
serrno = errno;
|
|
openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
|
|
"%s: %m", filename);
|
|
errno = serrno;
|
|
RETURNN(-1);
|
|
} else {
|
|
openpam_log(PAM_LOG_DEBUG, "found %s", filename);
|
|
}
|
|
|
|
/* verify type, ownership and permissions */
|
|
if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
|
|
openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
|
|
/* already logged the cause */
|
|
serrno = errno;
|
|
fclose(f);
|
|
errno = serrno;
|
|
RETURNN(-1);
|
|
}
|
|
|
|
/* parse the file */
|
|
ret = openpam_parse_chain(pamh, service, facility,
|
|
f, filename, style);
|
|
RETURNN(ret);
|
|
}
|
|
|
|
/*
|
|
* Locates the policy file for a given service and reads the given chains
|
|
* from it.
|
|
*
|
|
* Returns the number of policy entries which were found for the specified
|
|
* service and facility, or -1 if a system error occurred or a syntax
|
|
* error was encountered.
|
|
*/
|
|
static int
|
|
openpam_load_chain(pam_handle_t *pamh,
|
|
const char *service,
|
|
pam_facility_t facility)
|
|
{
|
|
const char *p, **path;
|
|
char filename[PATH_MAX];
|
|
size_t len;
|
|
openpam_style_t style;
|
|
int ret;
|
|
|
|
ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
|
|
|
|
/* either absolute or relative to cwd */
|
|
if (strchr(service, '/') != NULL) {
|
|
if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
|
|
style = pam_conf_style;
|
|
else
|
|
style = pam_d_style;
|
|
ret = openpam_load_file(pamh, service, facility,
|
|
service, style);
|
|
RETURNN(ret);
|
|
}
|
|
|
|
/* search standard locations */
|
|
for (path = openpam_policy_path; *path != NULL; ++path) {
|
|
/* construct filename */
|
|
len = strlcpy(filename, *path, sizeof filename);
|
|
if (filename[len - 1] == '/') {
|
|
len = strlcat(filename, service, sizeof filename);
|
|
if (len >= sizeof filename) {
|
|
errno = ENAMETOOLONG;
|
|
RETURNN(-1);
|
|
}
|
|
style = pam_d_style;
|
|
} else {
|
|
style = pam_conf_style;
|
|
}
|
|
ret = openpam_load_file(pamh, service, facility,
|
|
filename, style);
|
|
/* success */
|
|
if (ret > 0)
|
|
RETURNN(ret);
|
|
/* the file exists, but an error occurred */
|
|
if (ret == -1 && errno != ENOENT)
|
|
RETURNN(ret);
|
|
/* in pam.d style, an empty file counts as a hit */
|
|
if (ret == 0 && style == pam_d_style)
|
|
RETURNN(ret);
|
|
}
|
|
|
|
/* no hit */
|
|
errno = ENOENT;
|
|
RETURNN(-1);
|
|
}
|
|
|
|
/*
|
|
* OpenPAM internal
|
|
*
|
|
* Configure a service
|
|
*/
|
|
|
|
int
|
|
openpam_configure(pam_handle_t *pamh,
|
|
const char *service)
|
|
{
|
|
pam_facility_t fclt;
|
|
int serrno;
|
|
|
|
ENTERS(service);
|
|
if (!valid_service_name(service)) {
|
|
openpam_log(PAM_LOG_ERROR, "invalid service name");
|
|
RETURNC(PAM_SYSTEM_ERR);
|
|
}
|
|
if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
|
|
if (errno != ENOENT)
|
|
goto load_err;
|
|
}
|
|
for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
|
|
if (pamh->chains[fclt] != NULL)
|
|
continue;
|
|
if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
|
|
goto load_err;
|
|
}
|
|
RETURNC(PAM_SUCCESS);
|
|
load_err:
|
|
serrno = errno;
|
|
openpam_clear_chains(pamh->chains);
|
|
errno = serrno;
|
|
RETURNC(PAM_SYSTEM_ERR);
|
|
}
|
|
|
|
/*
|
|
* NODOC
|
|
*
|
|
* Error codes:
|
|
* PAM_SYSTEM_ERR
|
|
*/
|