From 0fe6f41f7ca3e47befbea77a518c8c7d14e7b402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag-Erling=20Sm=C3=B8rgrav?= Date: Sun, 25 May 2003 18:34:31 +0000 Subject: [PATCH] Overhaul the configuration parser. This adds support for continuation lines and policy inclusion. git-svn-id: svn+ssh://svn.openpam.org/svn/openpam/trunk@240 185d5e19-27fe-0310-9dcf-9bff6b9f3609 --- MANIFEST | 3 +- doc/man/Makefile | 3 +- include/security/openpam.h | 13 +- lib/Makefile | 3 +- lib/openpam_configure.c | 369 +++++++++++++++++++------------------ lib/openpam_impl.h | 17 +- lib/openpam_load.c | 46 +---- lib/openpam_readline.c | 148 +++++++++++++++ 8 files changed, 368 insertions(+), 234 deletions(-) create mode 100644 lib/openpam_readline.c diff --git a/MANIFEST b/MANIFEST index c6b9480..a29433b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,5 +1,5 @@ # -# $P4: //depot/projects/openpam/MANIFEST#14 $ +# $P4: //depot/projects/openpam/MANIFEST#15 $ # CREDITS HISTORY @@ -78,6 +78,7 @@ lib/openpam_impl.h lib/openpam_load.c lib/openpam_log.c lib/openpam_nullconv.c +lib/openpam_readline.c lib/openpam_restore_cred.c lib/openpam_set_option.c lib/openpam_static.c diff --git a/doc/man/Makefile b/doc/man/Makefile index 4d8b394..982d050 100644 --- a/doc/man/Makefile +++ b/doc/man/Makefile @@ -32,7 +32,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $P4: //depot/projects/openpam/doc/man/Makefile#11 $ +# $P4: //depot/projects/openpam/doc/man/Makefile#12 $ # GENDOC = ${.CURDIR}/../../misc/gendoc.pl @@ -73,6 +73,7 @@ OMAN += openpam_free_data.3 OMAN += openpam_get_option.3 OMAN += openpam_log.3 OMAN += openpam_nullconv.3 +OMAN += openpam_readline.3 OMAN += openpam_restore_cred.3 OMAN += openpam_set_option.3 OMAN += openpam_ttyconv.3 diff --git a/include/security/openpam.h b/include/security/openpam.h index 57dab04..46d62ad 100644 --- a/include/security/openpam.h +++ b/include/security/openpam.h @@ -31,7 +31,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $P4: //depot/projects/openpam/include/security/openpam.h#21 $ + * $P4: //depot/projects/openpam/include/security/openpam.h#22 $ */ #ifndef _SECURITY_OPENPAM_H_INCLUDED @@ -118,6 +118,17 @@ pam_vprompt(pam_handle_t *_pamh, const char *_fmt, va_list _ap); +/* + * Read cooked lines. + * Checking for FOPEN_MAX is a fairly reliable way to detect the presence + * of + */ +#ifdef FOPEN_MAX +char * +openpam_readline(FILE *_f, + size_t *_lenp); +#endif + /* * Log levels */ diff --git a/lib/Makefile b/lib/Makefile index 415cc6d..f5fd5ed 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -31,7 +31,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $P4: //depot/projects/openpam/lib/Makefile#19 $ +# $P4: //depot/projects/openpam/lib/Makefile#20 $ # LIB = pam @@ -57,6 +57,7 @@ SRCS += openpam_get_option.c SRCS += openpam_load.c SRCS += openpam_log.c SRCS += openpam_nullconv.c +SRCS += openpam_readline.c SRCS += openpam_restore_cred.c SRCS += openpam_set_option.c SRCS += openpam_static.c diff --git a/lib/openpam_configure.c b/lib/openpam_configure.c index a6a36e3..6a10519 100644 --- a/lib/openpam_configure.c +++ b/lib/openpam_configure.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2002 Networks Associates Technology, Inc. + * Copyright (c) 2001-2003 Networks Associates Technology, Inc. * All rights reserved. * * This software was developed for the FreeBSD Project by ThinkSec AS and @@ -31,7 +31,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $P4: //depot/projects/openpam/lib/openpam_configure.c#7 $ + * $P4: //depot/projects/openpam/lib/openpam_configure.c#8 $ */ #include @@ -44,169 +44,191 @@ #include "openpam_impl.h" -#define PAM_CONF_STYLE 0 -#define PAM_D_STYLE 1 -#define MAX_LINE_LEN 1024 -#define MAX_OPTIONS 256 +static int openpam_load_chain(pam_chain_t **, const char *, const char *); +/* + * Matches a word against the first one in a string. + * Returns non-zero if they match. + */ static int -openpam_read_policy_file(pam_chain_t *policy[], - const char *service, - const char *filename, - int style) +match_word(const char *str, const char *word) { - char buf[MAX_LINE_LEN], *p, *q; - const char *optv[MAX_OPTIONS + 1]; - int ch, chain, flag, line, optc, n, r; - size_t len; + + while (*str && *str == *word) + ++str, ++word; + return (*str == ' ' && *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 && !isspace(*str)) + ++str; + /* skip whitespace */ + while (isspace(*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 && !isspace(*end); ++end) + /* nothing */ ; + if (asprintf(&word, "%.*s", (int)(end - str), str) < 0) + return (NULL); + return (word); +} + +typedef enum { pam_conf_style, pam_d_style } openpam_style_t; + +/* + * Extracts a given chain from a policy file. + */ +static int +openpam_read_chain(pam_chain_t **chain, + const char *service, + const char *facility, + const char *filename, + openpam_style_t style) +{ + pam_chain_t *this, **next; + const char *p, *q; + int count, i, ret; + char *line, *name; FILE *f; - n = 0; - if ((f = fopen(filename, "r")) == NULL) { - openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE, + openpam_log(errno == ENOENT ? PAM_LOG_NOTICE : PAM_LOG_ERROR, "%s: %m", filename); return (0); } - openpam_log(PAM_LOG_DEBUG, "looking for '%s' in %s", - service, filename); + next = chain; + this = *next = NULL; + count = 0; + while ((line = openpam_readline(f, NULL)) != NULL) { + p = line; - for (line = 1; fgets(buf, MAX_LINE_LEN, f) != NULL; ++line) { - if ((len = strlen(buf)) == 0) - continue; - - /* check for overflow */ - if (buf[--len] != '\n' && !feof(f)) { - openpam_log(PAM_LOG_ERROR, "%s: line %d too long", - filename, line); - openpam_log(PAM_LOG_ERROR, "%s: ignoring line %d", - filename, line); - while ((ch = fgetc(f)) != EOF) - if (ch == '\n') - break; - continue; - } - - /* strip comments and trailing whitespace */ - if ((p = strchr(buf, '#')) != NULL) - len = p - buf ? p - buf - 1 : p - buf; - while (len > 0 && isspace(buf[len - 1])) - --len; - if (len == 0) - continue; - buf[len] = '\0'; - p = q = buf; - - /* check service name */ - if (style == PAM_CONF_STYLE) { - for (q = p = buf; *q != '\0' && !isspace(*q); ++q) - /* nothing */; - if (*q == '\0') - goto syntax_error; - *q++ = '\0'; - if (strcmp(p, service) != 0) + /* match service name */ + if (style == pam_conf_style) { + if (!match_word(p, service)) { + FREE(line); continue; - openpam_log(PAM_LOG_DEBUG, "%s: line %d matches '%s'", - filename, line, service); + } + p = next_word(p); } + /* match facility name */ + if (!match_word(p, facility)) { + FREE(line); + continue; + } + p = next_word(p); - /* get module type */ - for (p = q; isspace(*p); ++p) - /* nothing */; - for (q = p; *q != '\0' && !isspace(*q); ++q) - /* nothing */; - if (q == p || *q == '\0') - goto syntax_error; - *q++ = '\0'; - if (strcmp(p, "auth") == 0) { - chain = PAM_AUTH; - } else if (strcmp(p, "account") == 0) { - chain = PAM_ACCOUNT; - } else if (strcmp(p, "session") == 0) { - chain = PAM_SESSION; - } else if (strcmp(p, "password") == 0) { - chain = PAM_PASSWORD; - } else { - openpam_log(PAM_LOG_ERROR, - "%s: invalid module type on line %d: '%s'", - filename, line, p); + /* include other chain */ + if (match_word(p, "include")) { + p = next_word(p); + if (*next_word(p) != '\0') + openpam_log(PAM_LOG_NOTICE, + "%s: garbage at end of 'include' line", + filename); + if ((name = dup_word(p)) == NULL) + goto syserr; + ret = openpam_load_chain(next, name, facility); + FREE(name); + while (*next != NULL) { + next = &(*next)->next; + ++count; + } + FREE(line); + if (ret < 0) + goto fail; continue; } - /* get control flag */ - for (p = q; isspace(*p); ++p) - /* nothing */; - for (q = p; *q != '\0' && !isspace(*q); ++q) - /* nothing */; - if (q == p || *q == '\0') - goto syntax_error; - *q++ = '\0'; - if (strcmp(p, "required") == 0) { - flag = PAM_REQUIRED; - } else if (strcmp(p, "requisite") == 0) { - flag = PAM_REQUISITE; - } else if (strcmp(p, "sufficient") == 0) { - flag = PAM_SUFFICIENT; - } else if (strcmp(p, "optional") == 0) { - flag = PAM_OPTIONAL; - } else if (strcmp(p, "binding") == 0) { - flag = PAM_BINDING; + /* allocate new entry */ + if ((this = calloc(1, sizeof *this)) == NULL) + goto syserr; + + /* control flag */ + if (match_word(p, "required")) { + this->flag = PAM_REQUIRED; + } else if (match_word(p, "requisite")) { + this->flag = PAM_REQUISITE; + } else if (match_word(p, "sufficient")) { + this->flag = PAM_SUFFICIENT; + } else if (match_word(p, "optional")) { + this->flag = PAM_OPTIONAL; + } else if (match_word(p, "binding")) { + this->flag = PAM_BINDING; } else { + q = next_word(p); openpam_log(PAM_LOG_ERROR, - "%s: invalid control flag on line %d: '%s'", - filename, line, p); - continue; + "%s: invalid control flag '%.*s'", + filename, (int)(q - p), p); + goto fail; } - /* get module name */ - for (p = q; isspace(*p); ++p) - /* nothing */; - for (q = p; *q != '\0' && !isspace(*q); ++q) - /* nothing */; - if (q == p) - goto syntax_error; - - /* get options */ - for (optc = 0; *q != '\0' && optc < MAX_OPTIONS; ++optc) { - *q++ = '\0'; - while (isspace(*q)) - ++q; - optv[optc] = q; - while (*q != '\0' && !isspace(*q)) - ++q; - } - optv[optc] = NULL; - if (*q != '\0') { - *q = '\0'; + /* module name */ + p = next_word(p); + q = next_word(p); + if (*p == '\0') { openpam_log(PAM_LOG_ERROR, - "%s: too many options on line %d", - filename, line); + "%s: missing module name", filename); + goto fail; + } + if ((name = dup_word(p)) == NULL) + goto syserr; + this->module = openpam_load_module(name); + FREE(name); + if (this->module == NULL) + goto fail; + + /* module options */ + 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) { + p = next_word(p); + if ((this->optv[i] = dup_word(p)) == NULL) + goto syserr; } - /* - * Finally, add the module at the end of the - * appropriate chain and bump the counter. - */ - r = openpam_add_module(policy, chain, flag, p, optc, optv); - if (r != PAM_SUCCESS) - return (-r); - ++n; - continue; - syntax_error: - openpam_log(PAM_LOG_ERROR, "%s: syntax error on line %d", - filename, line); - openpam_log(PAM_LOG_DEBUG, "%s: line %d: [%s]", - filename, line, q); - openpam_log(PAM_LOG_ERROR, "%s: ignoring line %d", - filename, line); + /* hook it up */ + *next = this; + next = &this->next; + this = NULL; + ++count; + + /* next please... */ + FREE(line); } - - if (ferror(f)) - openpam_log(PAM_LOG_ERROR, "%s: %m", filename); - + if (!feof(f)) + goto syserr; fclose(f); - return (n); + return (count); + syserr: + openpam_log(PAM_LOG_ERROR, "%s: %m", filename); + fail: + FREE(this); + FREE(line); + fclose(f); + return (-1); } static const char *openpam_policy_path[] = { @@ -217,9 +239,14 @@ static const char *openpam_policy_path[] = { NULL }; +/* + * Locates the policy file for a given service and reads the given chain + * from it. + */ static int -openpam_load_policy(pam_chain_t *policy[], - const char *service) +openpam_load_chain(pam_chain_t **chain, + const char *service, + const char *facility) { const char **path; char *filename; @@ -229,27 +256,30 @@ openpam_load_policy(pam_chain_t *policy[], for (path = openpam_policy_path; *path != NULL; ++path) { len = strlen(*path); if ((*path)[len - 1] == '/') { - filename = malloc(len + strlen(service) + 1); - if (filename == NULL) { - openpam_log(PAM_LOG_ERROR, "malloc(): %m"); + if (asprintf(&filename, "%s%s", *path, service) < 0) { + openpam_log(PAM_LOG_ERROR, "asprintf(): %m"); return (-PAM_BUF_ERR); } - strcpy(filename, *path); - strcat(filename, service); - r = openpam_read_policy_file(policy, - service, filename, PAM_D_STYLE); + r = openpam_read_chain(chain, service, facility, + filename, pam_d_style); FREE(filename); } else { - r = openpam_read_policy_file(policy, - service, *path, PAM_CONF_STYLE); + r = openpam_read_chain(chain, service, facility, + *path, pam_conf_style); } if (r != 0) return (r); } - return (0); } +const char *_pam_chain_name[PAM_NUM_CHAINS] = { + [PAM_AUTH] = "auth", + [PAM_ACCOUNT] = "account", + [PAM_SESSION] = "session", + [PAM_PASSWORD] = "password" +}; + /* * OpenPAM internal * @@ -260,34 +290,20 @@ int openpam_configure(pam_handle_t *pamh, const char *service) { - pam_chain_t *other[PAM_NUM_CHAINS] = { 0 }; - int i, n, r; + int i, ret; - /* try own configuration first */ - r = openpam_load_policy(pamh->chains, service); - if (r < 0) - return (-r); - for (i = n = 0; i < PAM_NUM_CHAINS; ++i) { - if (pamh->chains[i] != NULL) - ++n; - } - if (n == PAM_NUM_CHAINS) - return (PAM_SUCCESS); - - /* fill in the blanks with "other" */ - openpam_load_policy(other, PAM_OTHER); - if (r < 0) - return (-r); - for (i = n = 0; i < PAM_NUM_CHAINS; ++i) { - if (pamh->chains[i] == NULL) { - pamh->chains[i] = other[i]; - other[i] = NULL; + for (i = 0; i < PAM_NUM_CHAINS; ++i) { + ret = openpam_load_chain(&pamh->chains[i], + service, _pam_chain_name[i]); + if (ret == 0) + ret = openpam_load_chain(&pamh->chains[i], + PAM_OTHER, _pam_chain_name[i]); + if (ret < 0) { + openpam_clear_chains(pamh->chains); + return (PAM_SYSTEM_ERR); } - if (pamh->chains[i] != NULL) - ++n; } - openpam_clear_chains(other); - return (n > 0 ? PAM_SUCCESS : PAM_SYSTEM_ERR); + return (PAM_SUCCESS); } /* @@ -295,5 +311,4 @@ openpam_configure(pam_handle_t *pamh, * * Error codes: * PAM_SYSTEM_ERR - * PAM_BUF_ERR */ diff --git a/lib/openpam_impl.h b/lib/openpam_impl.h index 52111ce..cf28234 100644 --- a/lib/openpam_impl.h +++ b/lib/openpam_impl.h @@ -31,7 +31,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $P4: //depot/projects/openpam/lib/openpam_impl.h#24 $ + * $P4: //depot/projects/openpam/lib/openpam_impl.h#25 $ */ #ifndef _OPENPAM_IMPL_H_INCLUDED @@ -112,17 +112,16 @@ struct pam_saved_cred { #define PAM_OTHER "other" -int openpam_configure(pam_handle_t *, const char *); -int openpam_dispatch(pam_handle_t *, int, int); -int openpam_findenv(pam_handle_t *, const char *, size_t); -int openpam_add_module(pam_chain_t **, int, int, - const char *, int, const char **); -void openpam_clear_chains(pam_chain_t **); +int openpam_configure(pam_handle_t *, const char *); +int openpam_dispatch(pam_handle_t *, int, int); +int openpam_findenv(pam_handle_t *, const char *, size_t); +pam_module_t *openpam_load_module(const char *); +void openpam_clear_chains(pam_chain_t **); #ifdef OPENPAM_STATIC_MODULES -pam_module_t *openpam_static(const char *); +pam_module_t *openpam_static(const char *); #endif -pam_module_t *openpam_dynamic(const char *); +pam_module_t *openpam_dynamic(const char *); #define FREE(p) do { free((p)); (p) = NULL; } while (0) diff --git a/lib/openpam_load.c b/lib/openpam_load.c index 911526a..6a96c3e 100644 --- a/lib/openpam_load.c +++ b/lib/openpam_load.c @@ -31,7 +31,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $P4: //depot/projects/openpam/lib/openpam_load.c#16 $ + * $P4: //depot/projects/openpam/lib/openpam_load.c#17 $ */ #include @@ -67,7 +67,7 @@ static pam_module_t *modules; * found modules to speed up the process. */ -static pam_module_t * +pam_module_t * openpam_load_module(const char *path) { pam_module_t *module; @@ -160,48 +160,6 @@ openpam_destroy_chain(pam_chain_t *chain) FREE(chain); } -/* - * Add a module to a chain. - */ - -int -openpam_add_module(pam_chain_t *policy[], - int chain, - int flag, - const char *modpath, - int optc, - const char *optv[]) -{ - pam_chain_t *new, *iterator; - - if ((new = calloc(1, sizeof *new)) == NULL) - goto buf_err; - if ((new->optv = malloc(sizeof(char *) * (optc + 1))) == NULL) - goto buf_err; - while (optc--) - if ((new->optv[new->optc++] = strdup(*optv++)) == NULL) - goto buf_err; - new->optv[new->optc] = NULL; - new->flag = flag; - if ((new->module = openpam_load_module(modpath)) == NULL) { - openpam_destroy_chain(new); - return (PAM_OPEN_ERR); - } - if ((iterator = policy[chain]) != NULL) { - while (iterator->next != NULL) - iterator = iterator->next; - iterator->next = new; - } else { - policy[chain] = new; - } - return (PAM_SUCCESS); - - buf_err: - openpam_log(PAM_LOG_ERROR, "%m"); - openpam_destroy_chain(new); - return (PAM_BUF_ERR); -} - /* * Clear the chains and release the modules diff --git a/lib/openpam_readline.c b/lib/openpam_readline.c new file mode 100644 index 0000000..1c41cf9 --- /dev/null +++ b/lib/openpam_readline.c @@ -0,0 +1,148 @@ +/*- + * Copyright (c) 2003 Networks Associates Technology, Inc. + * 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. + * + * $P4: //depot/projects/openpam/lib/openpam_readline.c#1 $ + */ + +#include +#include +#include + +#include +#include "openpam_impl.h" + +#define MIN_LINE_LENGTH 128 + +/* + * OpenPAM extension + * + * Read a line from a file. + */ + +char * +openpam_readline(FILE *f, size_t *lenp) +{ + char *line; + size_t len, size; + int ch; + + if ((line = malloc(MIN_LINE_LENGTH)) == NULL) + 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 */ + if (ch == '#') { + do { + ch = fgetc(f); + } while (ch != EOF && ch != '\n'); + } + /* eof */ + if (ch == EOF) { + /* remove trailing whitespace */ + while (len > 0 && isspace(line[len - 1])) + --len; + line[len] = '\0'; + if (len == 0) + goto fail; + break; + } + /* eol */ + if (ch == '\n') { + /* remove trailing whitespace */ + while (len > 0 && isspace(line[len - 1])) + --len; + line[len] = '\0'; + /* skip blank lines */ + if (len == 0) + continue; + /* continuation */ + if (line[len - 1] == '\\') { + line[--len] = '\0'; + /* fall through to whitespace case */ + } else { + 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 (lenp != NULL) + *lenp = len; + return (line); + fail: + FREE(line); + return (NULL); +} + +/** + * The =openpam_readline function reads a line from a file, and returns it + * 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. + * Blank lines are ignored. + * If a line ends in a backslash, the backslash is stripped and the next + * line is appended. + * + * If =lenp is not =NULL, the length of the line (not including the + * 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. + */