Add an openpam_straddch() function that appends a character to a

string, reallocating the string if necessary.

Add an openpam_readword() function that reads a single word from a
file according to the usual shell quoting rules.

Add an openpam_readlinev() function that uses openpam_readword() to
read an entire line and return a list of the words it contained.

Rewrite openpam_parse_chain() using openpam_readlinev(), which greatly
simplifies the code and ensures correct parsing of module option.

Thanks to Maëlle Lesage for pointing out the issue and writing an
early version of what became the main loop in openpam_readword().


git-svn-id: svn+ssh://svn.openpam.org/svn/openpam/trunk@547 185d5e19-27fe-0310-9dcf-9bff6b9f3609
This commit is contained in:
Dag-Erling Smørgrav 2012-04-01 15:01:21 +00:00
parent 54b6b546dd
commit 96357f3c52
12 changed files with 569 additions and 362 deletions

View File

@ -27,6 +27,7 @@ ideas:
Joe Marcus Clarke <marcus@freebsd.org>
Juli Mallett <jmallett@freebsd.org>
Jörg Sonnenberger <joerg@britannica.bec.de>
Maëlle Lesage <lesage.maelle@gmail.com>
Mark Murray <markm@freebsd.org>
Matthias Drochner <drochner@netbsd.org>
Mike Petullo <mike@flyn.org>

View File

@ -42,6 +42,8 @@ OMAN = \
openpam_log.3 \
openpam_nullconv.3 \
openpam_readline.3 \
openpam_readlinev.3 \
openpam_readword.3 \
openpam_restore_cred.3 \
openpam_set_option.3 \
openpam_straddch.3 \

View File

@ -158,12 +158,25 @@ openpam_readline(FILE *_f,
size_t *_lenp)
OPENPAM_NONNULL((1));
char **
openpam_readlinev(FILE *_f,
int *_lineno,
int *_lenp)
OPENPAM_NONNULL((1));
char *
openpam_readword(FILE *_f,
int *_lineno,
size_t *_lenp)
OPENPAM_NONNULL((1));
#endif
int
openpam_straddch(char **_str,
size_t *_sizep,
size_t *_lenp,
int ch);
#endif
size_t *_sizep,
size_t *_lenp,
int ch)
OPENPAM_NONNULL((1));
/*
* Log levels

View File

@ -28,6 +28,8 @@ libpam_la_SOURCES = \
openpam_log.c \
openpam_nullconv.c \
openpam_readline.c \
openpam_readlinev.c \
openpam_readword.c \
openpam_restore_cred.c \
openpam_set_option.c \
openpam_static.c \

View File

@ -1,6 +1,6 @@
/*-
* Copyright (c) 2001-2003 Networks Associates Technology, Inc.
* Copyright (c) 2004-2011 Dag-Erling Smørgrav
* Copyright (c) 2004-2012 Dag-Erling Smørgrav
* All rights reserved.
*
* This software was developed for the FreeBSD Project by ThinkSec AS and
@ -50,301 +50,79 @@
#include <security/pam_appl.h>
#include "openpam_impl.h"
#include "openpam_ctype.h"
#include "openpam_strlcmp.h"
static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
/*
* 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.
* Validate a 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.
* 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
parse_service_name(char **line, char **service)
valid_service_name(const char *name)
{
char *b, *e;
const char *p;
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))
for (p = name; *p != '\0'; ++p)
if (!is_pfcs(*p))
return (0);
if (e == b)
return (0);
*line = e;
*service = b;
return (e - b);
return (1);
}
/*
* 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.
* 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(char **line)
parse_facility_name(const char *name)
{
char *b, *e;
int 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);
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 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.
* 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(char **line)
parse_control_flag(const char *name)
{
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);
if (strcmp(pam_control_flag_name[i], name) == 0)
return (i);
return ((pam_control_t)-1);
}
/*
* Parse a file name.
* Validate 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).
* 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
parse_filename(char **line, char **filename)
valid_filename(const char *name)
{
char *b, *e;
const char *p;
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 != '/')
for (p = name; *p != '\0'; ++p)
if (!is_pfcs(*p) && *p != '/')
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 *
parse_option(char **line)
{
char *nb, *ne, *vb, *ve;
unsigned char q = 0;
char *option;
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 = (ne - nb) + 1;
if (ve > vb)
size += (ve - vb) + 1;
if ((option = malloc(size)) == NULL)
return (NULL);
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);
}
/*
* 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
parse_eol(char **line)
{
char *p;
for (p = *line; *p && is_lws(*p); ++p)
/* nothing */ ;
if (*p)
return ((unsigned char)*p);
*line = p;
return (0);
return (1);
}
typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
@ -367,68 +145,71 @@ openpam_parse_chain(pam_handle_t *pamh,
pam_chain_t *this, **next;
pam_facility_t fclt;
pam_control_t ctlf;
char *line, *str, *name;
char *option, **optv;
int count, len, lineno, ret, serrno;
char *name, *servicename, *modulename;
int count, lineno, ret, serrno;
char **wordv, *word;
int i, wordc;
count = 0;
this = NULL;
name = NULL;
lineno = 0;
while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
/* get service name if necessary */
if (style == pam_conf_style) {
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;
}
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;
}
/* get facility name */
if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) {
/* 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);
goto fail;
}
if (facility != fclt && facility != PAM_FACILITY_ANY) {
FREE(line);
FREEV(wordc, wordv);
continue;
}
/* check for "include" */
if (parse_include(&line)) {
if ((len = parse_service_name(&line, &str)) == 0) {
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 filename",
"%s(%d): missing or invalid service name",
filename, lineno);
goto fail;
}
if ((name = strndup(str, len)) == NULL)
goto syserr;
if (parse_eol(&line) != 0) {
if (wordv[i] != NULL) {
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);
ret = openpam_load_chain(pamh, servicename, fclt);
FREEV(wordc, wordv);
if (ret < 0)
goto fail;
FREE(line);
continue;
}
/* get control flag */
if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
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);
@ -436,46 +217,40 @@ openpam_parse_chain(pam_handle_t *pamh,
}
/* get module name */
if ((len = parse_filename(&line, &str)) == 0) {
if ((modulename = wordv[i++]) == NULL ||
!valid_filename(modulename)) {
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 */
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): syntax error in module options",
filename, lineno);
goto fail;
}
/* load module */
this->module = openpam_load_module(name);
FREE(name);
if (this->module == NULL)
if ((this->module = openpam_load_module(modulename)) == NULL)
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]);
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)
@ -483,12 +258,17 @@ openpam_parse_chain(pam_handle_t *pamh,
*next = this;
this = NULL;
++count;
/* next please... */
FREE(line);
}
if (!feof(f))
/*
* 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,
* errno is non-zero)
*/
if (ferror(f) || errno != 0)
goto syserr;
if (!feof(f))
goto fail;
fclose(f);
return (count);
syserr:
@ -498,14 +278,13 @@ syserr:
/* fall through */
fail:
serrno = errno;
if (this && this->optc) {
while (this->optc--)
FREE(this->optv[this->optc]);
FREE(this->optv);
}
if (this && this->optc && this->optv)
FREEV(this->optc, this->optv);
FREE(this);
FREE(line);
FREEV(wordc, wordv);
FREE(wordv);
FREE(name);
fclose(f);
errno = serrno;
return (-1);
}
@ -538,6 +317,11 @@ openpam_load_chain(pam_handle_t *pamh,
FILE *f;
int ret, serrno;
if (!valid_service_name(service)) {
openpam_log(PAM_LOG_ERROR, "invalid service name");
errno = EINVAL;
RETURNN(-1);
}
ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
for (path = openpam_policy_path; *path != NULL; ++path) {
/* construct filename */

65
lib/openpam_ctype.h Normal file
View File

@ -0,0 +1,65 @@
/*-
* Copyright (c) 2012 Dag-Erling Smørgrav
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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.
*
* 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$
*/
#ifndef OPENPAM_CTYPE_H_INCLUDED
#define OPENPAM_CTYPE_H_INCLUDED
/*
* Evaluates to non-zero if the argument is a linear whitespace character.
* For the purposes of this macro, the definition of linear whitespace is
* extended to include the form feed and carraige return characters.
*/
#define is_lws(ch) \
(ch == ' ' || ch == '\t' || ch == '\f' || ch == '\r')
/*
* Evaluates to non-zero if the argument is a whitespace character.
*/
#define is_ws(ch) \
(is_lws(ch) || ch == '\n')
/*
* 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 == '-')
#endif

View File

@ -157,7 +157,20 @@ pam_module_t *openpam_static(const char *);
#endif
pam_module_t *openpam_dynamic(const char *);
#define FREE(p) do { free((p)); (p) = NULL; } while (0)
#define FREE(p) \
do { \
free(p); \
(p) = NULL; \
} while (0)
#define FREEV(c, v) \
do { \
while (c) { \
--(c); \
FREE((v)[(c)]); \
} \
FREE(v); \
} while (0)
#include "openpam_constants.h"
#include "openpam_debug.h"

View File

@ -108,9 +108,7 @@ openpam_destroy_chain(pam_chain_t *chain)
return;
openpam_destroy_chain(chain->next);
chain->next = NULL;
while (chain->optc--)
FREE(chain->optv[chain->optc]);
FREE(chain->optv);
FREEV(chain->optc, chain->optv);
openpam_release_module(chain->module);
chain->module = NULL;
FREE(chain);

View File

@ -44,6 +44,7 @@
#include <stdlib.h>
#include <security/pam_appl.h>
#include "openpam_impl.h"
#define MIN_LINE_LENGTH 128
@ -61,22 +62,11 @@ openpam_readline(FILE *f, int *lineno, size_t *lenp)
size_t len, size;
int ch;
if ((line = malloc(MIN_LINE_LENGTH)) == NULL)
if ((line = malloc(size = MIN_LINE_LENGTH)) == NULL) {
openpam_log(PAM_LOG_ERROR, "malloc(): %m");
return (NULL);
size = MIN_LINE_LENGTH;
}
len = 0;
#define line_putch(ch) do { \
if (len >= size - 1) { \
char *tmp = realloc(line, size *= 2); \
if (tmp == NULL) \
goto fail; \
line = tmp; \
} \
line[len++] = ch; \
line[len] = '\0'; \
} while (0)
for (;;) {
ch = fgetc(f);
/* strip comment */
@ -105,26 +95,15 @@ openpam_readline(FILE *f, int *lineno, size_t *lenp)
/* done */
break;
}
/* whitespace */
if (isspace(ch)) {
/* ignore leading whitespace */
/* collapse linear whitespace */
if (len > 0 && line[len - 1] != ' ')
line_putch(' ');
continue;
}
/* anything else */
line_putch(ch);
if (openpam_straddch(&line, &size, &len, ch) != 0)
goto fail;
}
/* remove trailing whitespace */
while (len > 0 && isspace((unsigned char)line[len - 1]))
--len;
line[len] = '\0';
if (len == 0)
goto fail;
if (lenp != NULL)
*lenp = len;
openpam_log(PAM_LOG_LIBDEBUG, "returning '%s'", line);
return (line);
fail:
FREE(line);
@ -133,15 +112,15 @@ fail:
/**
* The =openpam_readline function reads a line from a file, and returns it
* in a NUL-terminated buffer allocated with =malloc.
* in a NUL-terminated buffer allocated with =!malloc.
*
* The =openpam_readline function performs a certain amount of processing
* on the data it reads:
*
* - Comments (introduced by a hash sign) are stripped, as is leading and
* trailing whitespace.
* - Any amount of linear whitespace is collapsed to a single space.
* - Comments (introduced by a hash sign) are stripped.
*
* - Blank lines are ignored.
*
* - If a line ends in a backslash, the backslash is stripped and the
* next line is appended.
*
@ -152,5 +131,8 @@ fail:
* terminating NUL character) is stored in the variable it points to.
*
* The caller is responsible for releasing the returned buffer by passing
* it to =free.
* it to =!free.
*
*>openpam_readlinev
*>openpam_readword
*/

154
lib/openpam_readlinev.c Normal file
View File

@ -0,0 +1,154 @@
/*-
* Copyright (c) 2012 Dag-Erling Smørgrav
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <security/pam_appl.h>
#include "openpam_impl.h"
#define MIN_WORDV_SIZE 32
/*
* OpenPAM extension
*
* Read a line from a file and split it into words.
*/
char **
openpam_readlinev(FILE *f, int *lineno, int *lenp)
{
char *word, **wordv, **tmp;
size_t wordlen, wordvsize;
int ch, serrno, wordvlen;
wordvsize = MIN_WORDV_SIZE;
wordvlen = 0;
if ((wordv = malloc(wordvsize * sizeof *wordv)) == NULL) {
openpam_log(PAM_LOG_ERROR, "malloc(): %m");
errno = ENOMEM;
return (NULL);
}
wordv[wordvlen] = NULL;
while ((word = openpam_readword(f, lineno, &wordlen)) != NULL) {
if ((unsigned int)wordvlen + 1 >= wordvsize) {
/* need to expand the array */
wordvsize *= 2;
tmp = realloc(wordv, wordvsize * sizeof *wordv);
if (tmp == NULL) {
openpam_log(PAM_LOG_ERROR, "malloc(): %m");
errno = ENOMEM;
break;
}
wordv = tmp;
}
/* insert our word */
wordv[wordvlen++] = word;
wordv[wordvlen] = NULL;
}
if (errno != 0) {
/* I/O error or out of memory */
serrno = errno;
while (wordvlen--)
free(wordv[wordvlen]);
free(wordv);
errno = serrno;
return (NULL);
}
/* assert(!ferror(f)) */
ch = fgetc(f);
/* assert(ch == EOF || ch == '\n') */
if (ch == EOF && wordvlen == 0) {
free(wordv);
return (NULL);
}
if (ch == '\n' && lineno != NULL)
++*lineno;
if (lenp != NULL)
*lenp = wordvlen;
return (wordv);
}
/**
* The =openpam_readlinev function reads a line from a file, splits it
* into words according to the rules described in the =openpam_readword
* manual page, and returns a list of those words.
*
* If =lineno is not =NULL, the integer variable it points to is
* incremented every time a newline character is read.
* This includes quoted or escaped newline characters and the newline
* character at the end of the line.
*
* If =lenp is not =NULL, the number of words on the line is stored in the
* variable to which it points.
*
*RETURN VALUES
*
* If successful, the =openpam_readlinev function returns a pointer to a
* dynamically allocated array of pointers to individual dynamically
* allocated NUL-terminated strings, each containing a single word, in the
* order in which they were encountered on the line.
* The array is terminated by a =NULL pointer.
*
* The caller is responsible for freeing both the array and the individual
* strings by passing each of them to =!free.
*
* If the end of the line was reached before any words were read,
* =openpam_readlinev returns a pointer to a dynamically allocated array
* containing a single =NULL pointer.
*
* The =openpam_readlinev function can fail and return =NULL for one of
* four reasons:
*
* - The end of the file was reached before any words were read; :errno is
* zero, =!ferror returns zero, and =!feof returns a non-zero value.
*
* - The end of the file was reached while a quote or backslash escape
* was in effect; :errno is set to =EINVAL, =!ferror returns zero, and
* =!feof returns a non-zero value.
*
* - An error occurred while reading from the file; :errno is non-zero,
* =!ferror returns a non-zero value and =!feof returns zero.
*
* - A =!malloc or =!realloc call failed; :errno is set to =ENOMEM,
* =!ferror returns a non-zero value, and =!feof may or may not return
* a non-zero value.
*
*>openpam_readline
*>openpam_readword
*/

187
lib/openpam_readword.c Normal file
View File

@ -0,0 +1,187 @@
/*-
* Copyright (c) 2012 Dag-Erling Smørgrav
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <security/pam_appl.h>
#include "openpam_impl.h"
#include "openpam_ctype.h"
#define MIN_WORD_SIZE 32
/*
* OpenPAM extension
*
* Read a word from a file, respecting shell quoting rules.
*/
char *
openpam_readword(FILE *f, int *lineno, size_t *lenp)
{
char *word;
size_t size, len;
int ch, comment, escape, quote;
int serrno;
errno = 0;
/* skip initial whitespace */
comment = 0;
while ((ch = getc(f)) != EOF && ch != '\n') {
if (ch == '#')
comment = 1;
if (!is_lws(ch) && !comment)
break;
}
if (ch == EOF)
return (NULL);
ungetc(ch, f);
if (ch == '\n')
return (NULL);
word = NULL;
size = len = 0;
escape = quote = 0;
while ((ch = fgetc(f)) != EOF && (!is_ws(ch) || quote || escape)) {
if (ch == '\\' && !escape) {
/* escape next character */
escape = ch;
} else if ((ch == '\'' || ch == '"') && !quote && !escape) {
/* begin quote */
quote = ch;
} else if (ch == quote && !escape) {
/* end quote */
quote = 0;
} else {
if (escape && quote && ch != '\\' && ch != quote &&
openpam_straddch(&word, &size, &len, '\\') != 0) {
free(word);
errno = ENOMEM;
return (NULL);
}
if (openpam_straddch(&word, &size, &len, ch) != 0) {
free(word);
errno = ENOMEM;
return (NULL);
}
escape = 0;
}
if (lineno != NULL && ch == '\n')
++*lineno;
}
if (ch == EOF && ferror(f)) {
serrno = errno;
free(word);
errno = serrno;
return (NULL);
}
if (ch == EOF && (escape || quote)) {
/* Missing escaped character or closing quote. */
openpam_log(PAM_LOG_ERROR, "unexpected end of file");
free(word);
errno = EINVAL;
return (NULL);
}
if (ch == '\n')
ungetc(ch, f);
if (lenp != NULL)
*lenp = len;
return (word);
}
/**
* The =openpam_readword function reads the next word from a file, and
* returns it in a NUL-terminated buffer allocated with =!malloc.
*
* A word is a sequence of non-whitespace characters.
* However, whitespace characters can be included in a word if quoted or
* escaped according to the following rules:
*
* - An unescaped single or double quote introduces a quoted string,
* which ends when the same quote character is encountered, unescaped,
* a second time.
* The quotes themselves are stripped.
*
* - Within a single- or double-quoted string, all whitespace characters,
* including the newline character, are preserved as-is.
*
* - Outside a string, a backslash escapes the next character, which is
* preserved as-is.
* The backslash itself is stripped.
*
* - Within a single-quoted string, a double quote is preserved as-is,
* and a backslash is preserved as-is unless used to escape a single
* quote.
*
* - Within a double-quoted string, a single quote is preserved as-is,
* and a backslash is preserved as-is unless used to escape a double
* quote.
*
* In addition, if the first non-whitespace character on the line is a
* hash character (#), the rest of the line is discarded.
* If a hash character occurs within a word, however, it is preserved
* as-is.
*
* If =openpam_readword reaches the end of the line or file before any
* characters are copied to the word, it returns =NULL. In the latter
* case, the newline is pushed back to the file.
*
* If =openpam_readword reaches the end of the file while a quote or
* backslash escape is in effect, it sets :errno to =EINVAL and returns
* =NULL.
*
* If =lineno is not =NULL, the integer variable it points to is
* incremented every time a quoted or escaped newline character is read.
*
* If =lenp is not =NULL, the length of the word (after quotes and
* backslashes have been removed) is stored in the variable it points to.
*
* The caller is responsible for releasing the returned buffer by passing
* it to =!free.
*
*IMPLEMENTATION NOTES
*
* The parsing rules are intended to be equivalent to those used by the
* shell to parse a command line when =IFS is unset.
* Any discrepancy is a bug and should be reported to the author along
* with sample input that can be used to reproduce the error.
*
*>openpam_readline
*>openpam_readlinev
*/

View File

@ -11,6 +11,9 @@
* 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
@ -31,6 +34,7 @@
# include "config.h"
#endif
#include <errno.h>
#include <stdlib.h>
#include <security/pam_appl.h>
@ -56,6 +60,7 @@ openpam_straddch(char **str, size_t *size, size_t *len, int ch)
tmpsize = MIN_STR_SIZE;
if ((tmpstr = malloc(tmpsize)) == NULL) {
openpam_log(PAM_LOG_ERROR, "malloc(): %m");
errno = ENOMEM;
return (-1);
}
*str = tmpstr;
@ -66,6 +71,7 @@ openpam_straddch(char **str, size_t *size, size_t *len, int ch)
tmpsize = *size * 2;
if ((tmpstr = realloc(*str, tmpsize)) == NULL) {
openpam_log(PAM_LOG_ERROR, "realloc(): %m");
errno = ENOMEM;
return (-1);
}
*size = tmpsize;
@ -99,5 +105,5 @@ openpam_straddch(char **str, size_t *size, size_t *len, int ch)
* If the =openpam_straddch function is successful, it increments the
* integer variable pointed to by =len and returns 0.
* Otherwise, it leaves the variables pointed to by =str, =size and =len
* unmodified and returns -1.
* unmodified, sets :errno to =ENOMEM and returns -1.
*/