summaryrefslogtreecommitdiff
path: root/lib/openpam_configure.c
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2012-05-26 14:23:18 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2012-05-26 14:23:18 +0000
commit55b76c4090c6a27c9bc8126a6fcfb7572e59041c (patch)
tree64307889a4ba20119258d81c0ac0b3060d7a10f0 /lib/openpam_configure.c
parent9b171ba819bc3eef8f0063725b3b8aada84613e0 (diff)
Notes
Diffstat (limited to 'lib/openpam_configure.c')
-rw-r--r--lib/openpam_configure.c592
1 files changed, 233 insertions, 359 deletions
diff --git a/lib/openpam_configure.c b/lib/openpam_configure.c
index d395565aefd4..778bec899961 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-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
@@ -32,13 +32,15 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $Id: openpam_configure.c 500 2011-11-22 12:07:03Z des $
+ * $Id: openpam_configure.c 601 2012-04-14 20:37:45Z des $
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
+#include <sys/param.h>
+
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
@@ -48,389 +50,183 @@
#include <security/pam_appl.h>
#include "openpam_impl.h"
-#include "openpam_strlcmp.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);
/*
- * 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.
+ * Validate a service name.
*
- * 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);
+ 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);
}
- 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 (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.
- *
- * 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.
+ * Validate a file name.
*
- * 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_module_name(const char *name)
{
- 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);
-}
+ const char *p;
-/*
- * 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);
+ if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
+ /* path separator not allowed */
+ for (p = name; *p != '\0'; ++p)
+ if (!is_pfcs(*p))
+ return (0);
} else {
- vb = ve = ne;
+ /* path separator allowed */
+ for (p = name; *p != '\0'; ++p)
+ if (!is_pfcs(*p) && *p != '/')
+ return (0);
}
- 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;
/*
* 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 *line, *str, *name;
- char *option, **optv;
- int len, lineno, ret;
- FILE *f;
+ char *name, *servicename, *modulename;
+ int count, lineno, ret, serrno;
+ char **wordv, *word;
+ int i, wordc;
- if ((f = fopen(filename, "r")) == NULL) {
- openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
- "%s: %m", filename);
- return (PAM_SUCCESS);
- }
- if (openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
- fclose(f);
- return (PAM_SYSTEM_ERR);
- }
+ 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);
- if (ret != PAM_SUCCESS)
+ 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);
@@ -438,73 +234,76 @@ openpam_parse_chain(pam_handle_t *pamh,
}
/* get module name */
- if ((len = parse_filename(&line, &str)) == 0) {
+ if ((modulename = wordv[i++]) == NULL ||
+ !valid_module_name(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)
/* nothing */ ;
*next = this;
this = NULL;
-
- /* next please... */
- FREE(line);
+ ++count;
}
- 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 (PAM_SUCCESS);
+ return (count);
syserr:
+ serrno = errno;
openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
+ errno = serrno;
+ /* fall through */
fail:
- if (this && this->optc) {
- while (this->optc--)
- FREE(this->optv[this->optc]);
- FREE(this->optv);
- }
+ serrno = errno;
+ if (this && this->optc && this->optv)
+ FREEV(this->optc, this->optv);
FREE(this);
- FREE(line);
+ FREEV(wordc, wordv);
+ FREE(wordv);
FREE(name);
fclose(f);
- return (PAM_SYSTEM_ERR);
+ errno = serrno;
+ return (-1);
}
static const char *openpam_policy_path[] = {
@@ -516,37 +315,110 @@ static const char *openpam_policy_path[] = {
};
/*
+ * 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 **path;
- char *filename;
+ 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) {
- 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);
+ /* 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);
}
- ret = openpam_parse_chain(pamh, service, facility,
- filename, pam_d_style);
- FREE(filename);
+ style = pam_d_style;
} else {
- ret = openpam_parse_chain(pamh, service, facility,
- *path, pam_conf_style);
+ style = pam_conf_style;
}
- if (ret != PAM_SUCCESS)
- return (ret);
+ ret = openpam_load_file(pamh, service, facility,
+ filename, style);
+ /* 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);
}
- return (PAM_SUCCESS);
+
+ /* no hit */
+ RETURNN(0);
}
/*
@@ -560,25 +432,27 @@ openpam_configure(pam_handle_t *pamh,
const char *service)
{
pam_facility_t fclt;
- const char *p;
+ int serrno;
- for (p = service; *p; ++p)
- if (!is_pfcs(*p))
- return (PAM_SYSTEM_ERR);
-
- if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) != PAM_SUCCESS)
+ 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)
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) != PAM_SUCCESS)
+ if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
goto load_err;
}
- return (PAM_SUCCESS);
+ RETURNC(PAM_SUCCESS);
load_err:
+ serrno = errno;
openpam_clear_chains(pamh->chains);
- return (PAM_SYSTEM_ERR);
+ errno = serrno;
+ RETURNC(PAM_SYSTEM_ERR);
}
/*