Major overhaul of the policy parser to support quoted option values. As a

bonus, it should now be much easier to read and understand.

This also changes the way options are stored: they are now stored as a list
of { key, value } pairs rather than "key=value" strings.


git-svn-id: svn+ssh://svn.openpam.org/svn/openpam/trunk@478 185d5e19-27fe-0310-9dcf-9bff6b9f3609
This commit is contained in:
Dag-Erling Smørgrav 2011-11-03 15:39:18 +00:00
parent 11b10d0991
commit 55f6a50684
4 changed files with 354 additions and 127 deletions

View File

@ -48,6 +48,7 @@
#include <security/pam_appl.h>
#include "openpam_impl.h"
#include "openpam_strlcmp.h"
const char *pam_facility_name[PAM_NUM_FACILITIES] = {
[PAM_ACCOUNT] = "account",
@ -67,81 +68,301 @@ const char *pam_control_flag_name[PAM_NUM_CONTROL_FLAGS] = {
static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
/*
* Matches a word against the first one in a string.
* Returns non-zero if they match.
* Evaluates to non-zero if the argument is a linear whitespace character.
*/
#define is_lws(ch) \
(ch == ' ' || ch == '\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 == '-')
/*
* 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 == ' ' || *str == '\0') && *word == '\0');
}
/*
* Return a pointer to the next word (or the final NUL) in a string.
*/
static const char *
next_word(const char *str)
{
/* skip current word */
while (*str && *str != ' ')
++str;
/* skip whitespace */
while (*str == ' ')
++str;
return (str);
}
/*
* Return a malloc()ed copy of the first word in a string.
*/
static char *
dup_word(const char *str)
{
const char *end;
char *word;
for (end = str; *end && *end != ' '; ++end)
for (b = *line; *b && is_lws(*b); ++b)
/* nothing */ ;
if (asprintf(&word, "%.*s", (int)(end - str), str) < 0)
return (NULL);
return (word);
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 the length of the first word 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 int
wordlen(const char *str)
static pam_facility_t
parse_facility_name(char **line)
{
char *b, *e;
int i;
for (i = 0; str[i] && str[i] != ' '; ++i)
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);
}
/*
* 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 pointer to a dynamically allocated pam_opt_t with the name
* and value of the next module option, or NULL if the end of the string
* was reached or a disallowed non-whitespace character was encountered.
*
* An option consists of an option name optionally followed by an equal
* sign and an option value.
*
* The structure and strings are allocated as a single object, so a single
* free(3) call is sufficient to release the allocated memory.
*
* 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 for the option structure, 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 pam_opt_t *
parse_option(char **line)
{
char *nb, *ne, *vb, *ve;
unsigned char q = 0;
pam_opt_t *opt;
size_t size;
errno = 0;
for (nb = *line; *nb && is_lws(*nb); ++nb)
/* nothing */ ;
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 = sizeof *opt;
size += ne - nb + 1;
size += ve - vb + 1;
if ((opt = malloc(size)) == NULL)
return (NULL);
opt->name = (char *)opt + sizeof *opt;
strlcpy(opt->name, nb, ne - nb + 1);
opt->value = opt->name + (ne - nb) + 1;
strlcpy(opt->value, vb, ve - vb + 1);
*line = q ? ve + 1 : ve;
return (opt);
}
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;
int count, lineno;
pam_facility_t fclt;
pam_control_t ctlf;
char *line, *name;
pam_opt_t *opt;
char *line, *str, *name;
int len, ret;
FILE *f;
if ((f = fopen(filename, "r")) == NULL) {
@ -150,43 +371,45 @@ openpam_read_chain(pam_handle_t *pamh,
return (0);
}
this = NULL;
name = NULL;
count = 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_filename(&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;
ret = openpam_load_chain(pamh, name, fclt);
FREE(name);
@ -197,52 +420,49 @@ openpam_read_chain(pam_handle_t *pamh,
continue;
}
/* allocate new entry */
if ((this = calloc(1, sizeof *this)) == NULL)
goto syserr;
/* 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 control flag */
if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
openpam_log(PAM_LOG_ERROR,
"%s(%d): invalid control flag '%.*s'",
filename, lineno, wordlen(p), p);
goto fail;
}
this->flag = ctlf;
/* module name */
p = next_word(p);
if (*p == '\0') {
openpam_log(PAM_LOG_ERROR,
"%s(%d): missing module name",
"%s(%d): missing or invalid control flag",
filename, lineno);
goto fail;
}
if ((name = dup_word(p)) == NULL)
/* get module name */
if ((len = parse_filename(&line, &str)) == 0) {
openpam_log(PAM_LOG_ERROR,
"%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;
/* get module options */
/* quick and dirty, wastes a few hundred bytes */
if ((this->optv = malloc(sizeof *opt * strlen(line))) == NULL)
goto syserr;
while ((opt = parse_option(&line)) != NULL)
this->optv[this->optc++] = opt;
this->optv[this->optc] = NULL;
if (*line != '\0') {
openpam_log(PAM_LOG_ERROR,
"%s(%d): syntax error in module options",
filename, lineno);
goto fail;
}
/* 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)
@ -261,8 +481,14 @@ openpam_read_chain(pam_handle_t *pamh,
syserr:
openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
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);
}
@ -296,11 +522,11 @@ openpam_load_chain(pam_handle_t *pamh,
openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
return (-PAM_BUF_ERR);
}
r = openpam_read_chain(pamh, service, facility,
r = openpam_parse_chain(pamh, service, facility,
filename, pam_d_style);
FREE(filename);
} else {
r = openpam_read_chain(pamh, service, facility,
r = openpam_parse_chain(pamh, service, facility,
*path, pam_conf_style);
}
if (r != 0)

View File

@ -59,22 +59,15 @@ openpam_get_option(pam_handle_t *pamh,
const char *option)
{
pam_chain_t *cur;
size_t len;
int i;
ENTERS(option);
if (pamh == NULL || pamh->current == NULL || option == NULL)
RETURNS(NULL);
cur = pamh->current;
len = strlen(option);
for (i = 0; i < cur->optc; ++i) {
if (strncmp(cur->optv[i], option, len) == 0) {
if (cur->optv[i][len] == '\0')
RETURNS(&cur->optv[i][len]);
else if (cur->optv[i][len] == '=')
RETURNS(&cur->optv[i][len + 1]);
}
}
for (i = 0; i < cur->optc; ++i)
if (strcmp(cur->optv[i]->name, option) == 0)
RETURNS(cur->optv[i]->value);
RETURNS(NULL);
}

View File

@ -71,12 +71,19 @@ typedef enum {
PAM_NUM_FACILITIES
} pam_facility_t;
typedef struct pam_opt pam_opt_t;
struct pam_opt {
char *name;
char *value;
char str[];
};
typedef struct pam_chain pam_chain_t;
struct pam_chain {
pam_module_t *module;
int flag;
int optc;
char **optv;
pam_opt_t **optv;
pam_chain_t *next;
};

View File

@ -62,22 +62,16 @@ openpam_set_option(pam_handle_t *pamh,
const char *value)
{
pam_chain_t *cur;
char *opt, **optv;
size_t len;
int i;
pam_opt_t *opt, **optv;
int i, ol, vl;
ENTERS(option);
if (pamh == NULL || pamh->current == NULL || option == NULL)
RETURNC(PAM_SYSTEM_ERR);
cur = pamh->current;
for (len = 0; option[len] != '\0'; ++len)
if (option[len] == '=')
for (i = 0; i < cur->optc; ++i)
if (strcmp(cur->optv[i]->name, option) == 0)
break;
for (i = 0; i < cur->optc; ++i) {
if (strncmp(cur->optv[i], option, len) == 0 &&
(cur->optv[i][len] == '\0' || cur->optv[i][len] == '='))
break;
}
if (value == NULL) {
/* remove */
if (i == cur->optc)
@ -85,13 +79,20 @@ openpam_set_option(pam_handle_t *pamh,
for (free(cur->optv[i]); i < cur->optc; ++i)
cur->optv[i] = cur->optv[i + 1];
cur->optv[i] = NULL;
cur->optc--;
RETURNC(PAM_SUCCESS);
}
if (asprintf(&opt, "%.*s=%s", (int)len, option, value) < 0)
ol = strlen(option) + 1;
vl = strlen(value) + 1;
if ((opt = malloc(sizeof *opt + ol + vl)) == NULL)
RETURNC(PAM_BUF_ERR);
opt->name = (char *)opt + sizeof *opt;
strlcpy(opt->name, option, ol);
opt->value = opt->name + ol;
strlcpy(opt->value, value, vl);
if (i == cur->optc) {
/* add */
optv = realloc(cur->optv, sizeof(char *) * (cur->optc + 2));
optv = realloc(cur->optv, sizeof *optv * (cur->optc + 2));
if (optv == NULL) {
FREE(opt);
RETURNC(PAM_BUF_ERR);