summaryrefslogtreecommitdiffstats
path: root/lib/openpam_configure.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/openpam_configure.c')
-rw-r--r--lib/openpam_configure.c487
1 files changed, 368 insertions, 119 deletions
diff --git a/lib/openpam_configure.c b/lib/openpam_configure.c
index f9197ad..d395565 100644
--- a/lib/openpam_configure.c
+++ b/lib/openpam_configure.c
@@ -1,6 +1,6 @@
/*-
* Copyright (c) 2001-2003 Networks Associates Technology, Inc.
- * Copyright (c) 2004-2007 Dag-Erling Smørgrav
+ * Copyright (c) 2004-2011 Dag-Erling Smørgrav
* All rights reserved.
*
* This software was developed for the FreeBSD Project by ThinkSec AS and
@@ -32,9 +32,13 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $Id: openpam_configure.c 408 2007-12-21 11:36:24Z des $
+ * $Id: openpam_configure.c 500 2011-11-22 12:07:03Z des $
*/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
@@ -44,80 +48,301 @@
#include <security/pam_appl.h>
#include "openpam_impl.h"
+#include "openpam_strlcmp.h"
-const char *_pam_facility_name[PAM_NUM_FACILITIES] = {
- [PAM_ACCOUNT] = "account",
- [PAM_AUTH] = "auth",
- [PAM_PASSWORD] = "password",
- [PAM_SESSION] = "session",
-};
+static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
-const char *_pam_control_flag_name[PAM_NUM_CONTROL_FLAGS] = {
- [PAM_BINDING] = "binding",
- [PAM_OPTIONAL] = "optional",
- [PAM_REQUIRED] = "required",
- [PAM_REQUISITE] = "requisite",
- [PAM_SUFFICIENT] = "sufficient",
-};
+/*
+ * Evaluates to non-zero if the argument is a linear whitespace character.
+ */
+#define is_lws(ch) \
+ (ch == ' ' || ch == '\t')
-static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
+/*
+ * Evaluates to non-zero if the argument is a printable ASCII character.
+ * Assumes that the execution character set is a superset of ASCII.
+ */
+#define is_p(ch) \
+ (ch >= '!' && ch <= '~')
+
+/*
+ * Returns non-zero if the argument belongs to the POSIX Portable Filename
+ * Character Set. Assumes that the execution character set is a superset
+ * of ASCII.
+ */
+#define is_pfcs(ch) \
+ ((ch >= '0' && ch <= '9') || \
+ (ch >= 'A' && ch <= 'Z') || \
+ (ch >= 'a' && ch <= 'z') || \
+ ch == '.' || ch == '_' || ch == '-')
/*
- * Matches a word against the first one in a string.
- * Returns non-zero if they match.
+ * Parse the service name.
+ *
+ * Returns the length of the service name, or 0 if the end of the string
+ * was reached or a disallowed non-whitespace character was encountered.
+ *
+ * If parse_service_name() is successful, it updates *service to point to
+ * the first character of the service name and *line to point one
+ * character past the end. If it reaches the end of the string, it
+ * updates *line to point to the terminating NUL character and leaves
+ * *service unmodified. In all other cases, it leaves both *line and
+ * *service unmodified.
+ *
+ * Allowed characters are all characters in the POSIX portable filename
+ * character set.
*/
static int
-match_word(const char *str, const char *word)
+parse_service_name(char **line, char **service)
{
+ char *b, *e;
- while (*str && tolower(*str) == tolower(*word))
- ++str, ++word;
- return (*str == ' ' && *word == '\0');
+ for (b = *line; *b && is_lws(*b); ++b)
+ /* nothing */ ;
+ if (!*b) {
+ *line = b;
+ return (0);
+ }
+ for (e = b; *e && !is_lws(*e); ++e)
+ if (!is_pfcs(*e))
+ return (0);
+ if (e == b)
+ return (0);
+ *line = e;
+ *service = b;
+ return (e - b);
}
/*
- * Return a pointer to the next word (or the final NUL) in a string.
+ * Parse the facility name.
+ *
+ * Returns the corresponding pam_facility_t value, or -1 if the end of the
+ * string was reached, a disallowed non-whitespace character was
+ * encountered, or the first word was not a recognized facility name.
+ *
+ * If parse_facility_name() is successful, it updates *line to point one
+ * character past the end of the facility name. If it reaches the end of
+ * the string, it updates *line to point to the terminating NUL character.
+ * In all other cases, it leaves *line unmodified.
*/
-static const char *
-next_word(const char *str)
+static pam_facility_t
+parse_facility_name(char **line)
{
+ char *b, *e;
+ int i;
- /* skip current word */
- while (*str && *str != ' ')
- ++str;
- /* skip whitespace */
- while (*str == ' ')
- ++str;
- return (str);
+ for (b = *line; *b && is_lws(*b); ++b)
+ /* nothing */ ;
+ if (!*b) {
+ *line = b;
+ return ((pam_facility_t)-1);
+ }
+ for (e = b; *e && !is_lws(*e); ++e)
+ /* nothing */ ;
+ if (e == b)
+ return ((pam_facility_t)-1);
+ for (i = 0; i < PAM_NUM_FACILITIES; ++i)
+ if (strlcmp(pam_facility_name[i], b, e - b) == 0)
+ break;
+ if (i == PAM_NUM_FACILITIES)
+ return ((pam_facility_t)-1);
+ *line = e;
+ return (i);
}
/*
- * Return a malloc()ed copy of the first word in a string.
+ * Parse the word "include".
+ *
+ * If the next word on the line is "include", parse_include() updates
+ * *line to point one character past "include" and returns 1. Otherwise,
+ * it leaves *line unmodified and returns 0.
+ */
+static int
+parse_include(char **line)
+{
+ char *b, *e;
+
+ for (b = *line; *b && is_lws(*b); ++b)
+ /* nothing */ ;
+ if (!*b) {
+ *line = b;
+ return (-1);
+ }
+ for (e = b; *e && !is_lws(*e); ++e)
+ /* nothing */ ;
+ if (e == b)
+ return (0);
+ if (strlcmp("include", b, e - b) != 0)
+ return (0);
+ *line = e;
+ return (1);
+}
+
+/*
+ * Parse the control flag.
+ *
+ * Returns the corresponding pam_control_t value, or -1 if the end of the
+ * string was reached, a disallowed non-whitespace character was
+ * encountered, or the first word was not a recognized control flag.
+ *
+ * If parse_control_flag() is successful, it updates *line to point one
+ * character past the end of the control flag. If it reaches the end of
+ * the string, it updates *line to point to the terminating NUL character.
+ * In all other cases, it leaves *line unmodified.
+ */
+static pam_control_t
+parse_control_flag(char **line)
+{
+ char *b, *e;
+ int i;
+
+ for (b = *line; *b && is_lws(*b); ++b)
+ /* nothing */ ;
+ if (!*b) {
+ *line = b;
+ return ((pam_control_t)-1);
+ }
+ for (e = b; *e && !is_lws(*e); ++e)
+ /* nothing */ ;
+ if (e == b)
+ return ((pam_control_t)-1);
+ for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
+ if (strlcmp(pam_control_flag_name[i], b, e - b) == 0)
+ break;
+ if (i == PAM_NUM_CONTROL_FLAGS)
+ return ((pam_control_t)-1);
+ *line = e;
+ return (i);
+}
+
+/*
+ * Parse a file name.
+ *
+ * Returns the length of the file name, or 0 if the end of the string was
+ * reached or a disallowed non-whitespace character was encountered.
+ *
+ * If parse_filename() is successful, it updates *filename to point to the
+ * first character of the filename and *line to point one character past
+ * the end. If it reaches the end of the string, it updates *line to
+ * point to the terminating NUL character and leaves *filename unmodified.
+ * In all other cases, it leaves both *line and *filename unmodified.
+ *
+ * Allowed characters are all characters in the POSIX portable filename
+ * character set, plus the path separator (forward slash).
+ */
+static int
+parse_filename(char **line, char **filename)
+{
+ char *b, *e;
+
+ for (b = *line; *b && is_lws(*b); ++b)
+ /* nothing */ ;
+ if (!*b) {
+ *line = b;
+ return (0);
+ }
+ for (e = b; *e && !is_lws(*e); ++e)
+ if (!is_pfcs(*e) && *e != '/')
+ return (0);
+ if (e == b)
+ return (0);
+ *line = e;
+ *filename = b;
+ return (e - b);
+}
+
+/*
+ * Parse an option.
+ *
+ * Returns a dynamically allocated string containing the next module
+ * option, or NULL if the end of the string was reached or a disallowed
+ * non-whitespace character was encountered.
+ *
+ * If parse_option() is successful, it updates *line to point one
+ * character past the end of the option. If it reaches the end of the
+ * string, it updates *line to point to the terminating NUL character. In
+ * all other cases, it leaves *line unmodified.
+ *
+ * If parse_option() fails to allocate memory, it will return NULL and set
+ * errno to a non-zero value.
+ *
+ * Allowed characters for option names are all characters in the POSIX
+ * portable filename character set. Allowed characters for option values
+ * are any printable non-whitespace characters. The option value may be
+ * quoted in either single or double quotes, in which case space
+ * characters and whichever quote character was not used are allowed.
+ * Note that the entire value must be quoted, not just part of it.
*/
static char *
-dup_word(const char *str)
+parse_option(char **line)
{
- const char *end;
- char *word;
+ char *nb, *ne, *vb, *ve;
+ unsigned char q = 0;
+ char *option;
+ size_t size;
- for (end = str; *end && *end != ' '; ++end)
+ errno = 0;
+ for (nb = *line; *nb && is_lws(*nb); ++nb)
/* nothing */ ;
- if (asprintf(&word, "%.*s", (int)(end - str), str) < 0)
+ if (!*nb) {
+ *line = nb;
+ return (NULL);
+ }
+ for (ne = nb; *ne && !is_lws(*ne) && *ne != '='; ++ne)
+ if (!is_pfcs(*ne))
+ return (NULL);
+ if (ne == nb)
+ return (NULL);
+ if (*ne == '=') {
+ vb = ne + 1;
+ if (*vb == '"' || *vb == '\'')
+ q = *vb++;
+ for (ve = vb;
+ *ve && *ve != q && (is_p(*ve) || (q && is_lws(*ve)));
+ ++ve)
+ /* nothing */ ;
+ if (q && *ve != q)
+ /* non-printable character or missing endquote */
+ return (NULL);
+ if (q && *(ve + 1) && !is_lws(*(ve + 1)))
+ /* garbage after value */
+ return (NULL);
+ } else {
+ vb = ve = ne;
+ }
+ size = (ne - nb) + 1;
+ if (ve > vb)
+ size += (ve - vb) + 1;
+ if ((option = malloc(size)) == NULL)
return (NULL);
- return (word);
+ strncpy(option, nb, ne - nb);
+ if (ve > vb) {
+ option[ne - nb] = '=';
+ strncpy(option + (ne - nb) + 1, vb, ve - vb);
+ }
+ option[size - 1] = '\0';
+ *line = q ? ve + 1 : ve;
+ return (option);
}
/*
- * Return the length of the first word in a string.
+ * Consume trailing whitespace.
+ *
+ * If there are no non-whitespace characters left on the line, parse_eol()
+ * updates *line to point at the terminating NUL character and returns 0.
+ * Otherwise, it leaves *line unmodified and returns a non-zero value.
*/
static int
-wordlen(const char *str)
+parse_eol(char **line)
{
- int i;
+ char *p;
- for (i = 0; str[i] && str[i] != ' '; ++i)
+ for (p = *line; *p && is_lws(*p); ++p)
/* nothing */ ;
- return (i);
+ if (*p)
+ return ((unsigned char)*p);
+ *line = p;
+ return (0);
}
typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
@@ -126,126 +351,139 @@ typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
* Extracts given chains from a policy file.
*/
static int
-openpam_read_chain(pam_handle_t *pamh,
+openpam_parse_chain(pam_handle_t *pamh,
const char *service,
pam_facility_t facility,
const char *filename,
openpam_style_t style)
{
pam_chain_t *this, **next;
- const char *p, *q;
- int count, i, lineno, ret;
pam_facility_t fclt;
pam_control_t ctlf;
- char *line, *name;
+ char *line, *str, *name;
+ char *option, **optv;
+ int len, lineno, ret;
FILE *f;
if ((f = fopen(filename, "r")) == NULL) {
openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
"%s: %m", filename);
- return (0);
+ return (PAM_SUCCESS);
+ }
+ if (openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
+ fclose(f);
+ return (PAM_SYSTEM_ERR);
}
this = NULL;
- count = lineno = 0;
+ name = NULL;
+ lineno = 0;
while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
- p = line;
-
- /* match service name */
+ /* get service name if necessary */
if (style == pam_conf_style) {
- if (!match_word(p, service)) {
+ if ((len = parse_service_name(&line, &str)) == 0) {
+ openpam_log(PAM_LOG_NOTICE,
+ "%s(%d): invalid service name (ignored)",
+ filename, lineno);
+ FREE(line);
+ continue;
+ }
+ if (strlcmp(service, str, len) != 0) {
FREE(line);
continue;
}
- p = next_word(p);
}
- /* match facility name */
- for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt)
- if (match_word(p, _pam_facility_name[fclt]))
- break;
- if (fclt == PAM_NUM_FACILITIES) {
- openpam_log(PAM_LOG_NOTICE,
- "%s(%d): invalid facility '%.*s' (ignored)",
- filename, lineno, wordlen(p), p);
+ /* get facility name */
+ if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) {
+ openpam_log(PAM_LOG_ERROR,
+ "%s(%d): missing or invalid facility",
+ filename, lineno);
goto fail;
}
if (facility != fclt && facility != PAM_FACILITY_ANY) {
FREE(line);
continue;
}
- p = next_word(p);
- /* include other chain */
- if (match_word(p, "include")) {
- p = next_word(p);
- if (*next_word(p) != '\0')
- openpam_log(PAM_LOG_NOTICE,
- "%s(%d): garbage at end of 'include' line",
+ /* check for "include" */
+ if (parse_include(&line)) {
+ if ((len = parse_service_name(&line, &str)) == 0) {
+ openpam_log(PAM_LOG_ERROR,
+ "%s(%d): missing or invalid filename",
filename, lineno);
- if ((name = dup_word(p)) == NULL)
+ goto fail;
+ }
+ if ((name = strndup(str, len)) == NULL)
goto syserr;
+ if (parse_eol(&line) != 0) {
+ openpam_log(PAM_LOG_ERROR,
+ "%s(%d): garbage at end of line",
+ filename, lineno);
+ goto fail;
+ }
ret = openpam_load_chain(pamh, name, fclt);
FREE(name);
- if (ret < 0)
+ if (ret != PAM_SUCCESS)
goto fail;
- count += ret;
FREE(line);
continue;
}
- /* allocate new entry */
- if ((this = calloc(1, sizeof *this)) == NULL)
- goto syserr;
+ /* get control flag */
+ if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
+ openpam_log(PAM_LOG_ERROR,
+ "%s(%d): missing or invalid control flag",
+ filename, lineno);
+ goto fail;
+ }
- /* control flag */
- for (ctlf = 0; ctlf < PAM_NUM_CONTROL_FLAGS; ++ctlf)
- if (match_word(p, _pam_control_flag_name[ctlf]))
- break;
- if (ctlf == PAM_NUM_CONTROL_FLAGS) {
+ /* get module name */
+ if ((len = parse_filename(&line, &str)) == 0) {
openpam_log(PAM_LOG_ERROR,
- "%s(%d): invalid control flag '%.*s'",
- filename, lineno, wordlen(p), p);
+ "%s(%d): missing or invalid module name",
+ filename, lineno);
goto fail;
}
+ if ((name = strndup(str, len)) == NULL)
+ goto syserr;
+
+ /* allocate new entry */
+ if ((this = calloc(1, sizeof *this)) == NULL)
+ goto syserr;
this->flag = ctlf;
- /* module name */
- p = next_word(p);
- if (*p == '\0') {
+ /* get module options */
+ if ((this->optv = malloc(sizeof *optv)) == NULL)
+ goto syserr;
+ this->optc = 0;
+ while ((option = parse_option(&line)) != NULL) {
+ optv = realloc(this->optv,
+ (this->optc + 2) * sizeof *optv);
+ if (optv == NULL)
+ goto syserr;
+ this->optv = optv;
+ this->optv[this->optc++] = option;
+ }
+ this->optv[this->optc] = NULL;
+ if (*line != '\0') {
openpam_log(PAM_LOG_ERROR,
- "%s(%d): missing module name",
+ "%s(%d): syntax error in module options",
filename, lineno);
goto fail;
}
- if ((name = dup_word(p)) == NULL)
- goto syserr;
+
+ /* load module */
this->module = openpam_load_module(name);
FREE(name);
if (this->module == NULL)
goto fail;
- /* module options */
- p = q = next_word(p);
- while (*q != '\0') {
- ++this->optc;
- q = next_word(q);
- }
- this->optv = calloc(this->optc + 1, sizeof(char *));
- if (this->optv == NULL)
- goto syserr;
- for (i = 0; i < this->optc; ++i) {
- if ((this->optv[i] = dup_word(p)) == NULL)
- goto syserr;
- p = next_word(p);
- }
-
/* hook it up */
for (next = &pamh->chains[fclt]; *next != NULL;
next = &(*next)->next)
/* nothing */ ;
*next = this;
this = NULL;
- ++count;
/* next please... */
FREE(line);
@@ -253,14 +491,20 @@ openpam_read_chain(pam_handle_t *pamh,
if (!feof(f))
goto syserr;
fclose(f);
- return (count);
- syserr:
+ return (PAM_SUCCESS);
+syserr:
openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
- fail:
+fail:
+ if (this && this->optc) {
+ while (this->optc--)
+ FREE(this->optv[this->optc]);
+ FREE(this->optv);
+ }
FREE(this);
FREE(line);
+ FREE(name);
fclose(f);
- return (-1);
+ return (PAM_SYSTEM_ERR);
}
static const char *openpam_policy_path[] = {
@@ -283,26 +527,26 @@ openpam_load_chain(pam_handle_t *pamh,
const char **path;
char *filename;
size_t len;
- int r;
+ int ret;
for (path = openpam_policy_path; *path != NULL; ++path) {
len = strlen(*path);
if ((*path)[len - 1] == '/') {
if (asprintf(&filename, "%s%s", *path, service) < 0) {
openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
- return (-PAM_BUF_ERR);
+ return (PAM_BUF_ERR);
}
- r = openpam_read_chain(pamh, service, facility,
+ ret = openpam_parse_chain(pamh, service, facility,
filename, pam_d_style);
FREE(filename);
} else {
- r = openpam_read_chain(pamh, service, facility,
+ ret = openpam_parse_chain(pamh, service, facility,
*path, pam_conf_style);
}
- if (r != 0)
- return (r);
+ if (ret != PAM_SUCCESS)
+ return (ret);
}
- return (0);
+ return (PAM_SUCCESS);
}
/*
@@ -316,18 +560,23 @@ openpam_configure(pam_handle_t *pamh,
const char *service)
{
pam_facility_t fclt;
+ const char *p;
+
+ for (p = service; *p; ++p)
+ if (!is_pfcs(*p))
+ return (PAM_SYSTEM_ERR);
- if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0)
+ if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) != PAM_SUCCESS)
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)
+ if (openpam_load_chain(pamh, PAM_OTHER, fclt) != PAM_SUCCESS)
goto load_err;
}
return (PAM_SUCCESS);
- load_err:
+load_err:
openpam_clear_chains(pamh->chains);
return (PAM_SYSTEM_ERR);
}
OpenPOWER on IntegriCloud