--- /dev/null
+/*
+ * Copyright (c) 2015 Andreas Schneider <asn@samba.org>
+ * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <stdint.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#ifndef discard_const
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
+#endif
+
+#ifndef discard_const_p
+#define discard_const_p(type, ptr) ((type *)discard_const(ptr))
+#endif
+
+#ifdef HAVE_SECURITY_PAM_APPL_H
+#include <security/pam_appl.h>
+#endif
+#ifdef HAVE_SECURITY_PAM_MODULES_H
+#include <security/pam_modules.h>
+#endif
+#ifdef HAVE_SECURITY_PAM_EXT_H
+#include <security/pam_ext.h>
+#endif
+
+#include "pwrap_compat.h"
+
+#define HOME_VAR "HOMEDIR"
+#define HOME_VAR_SZ sizeof(HOME_VAR)-1
+
+#define CRED_VAR "CRED"
+#define CRED_VAR_SZ sizeof(CRED_VAR)-1
+
+#define PAM_EXAMPLE_AUTH_DATA "pam_matrix:auth_data"
+
+#define PASSDB_KEY "passdb="
+#define VERBOSE_KEY "verbose"
+#define ECHO_KEY "echo"
+
+#define PAM_MATRIX_FLG_VERBOSE (1 << 0)
+#define PAM_MATRIX_FLG_ECHO (1 << 1)
+
+#define MAX_AUTHTOK_SIZE 1024
+
+/* Walks over the key until a colon (:) is find
+ */
+#define NEXT_KEY(buf, key) do { \
+ (key) = (buf) ? strpbrk((buf), ":") : NULL; \
+ if ((key) != NULL) { \
+ (key)[0] = '\0'; \
+ (key)++; \
+ } \
+ while ((key) != NULL \
+ && (isblank((int)(key)[0]))) { \
+ (key)++; \
+ } \
+} while(0);
+
+#define wipe_authtok(tok) do { \
+ if (tok != NULL) { \
+ char *__tk = tok; \
+ while(*__tk != '\0') { \
+ *__tk = '\0'; \
+ } \
+ } \
+} while(0);
+
+struct pam_lib_items {
+ const char *username;
+ const char *service;
+ char *password;
+};
+
+struct pam_matrix_mod_items {
+ char *password;
+ char *service;
+};
+
+struct pam_matrix_ctx {
+ const char *passdb;
+ int flags;
+
+ struct pam_lib_items pli;
+ struct pam_matrix_mod_items pmi;
+};
+
+/* Search the passdb for user entry and fill his info into pmi */
+static int pam_matrix_mod_items_get(const char *db,
+ const char *username,
+ struct pam_matrix_mod_items *pmi)
+{
+ int rv;
+ FILE *fp = NULL;
+ char buf[BUFSIZ];
+ char *file_user = NULL;
+ char *file_password = NULL;
+ char *file_svc = NULL;
+
+ fp = fopen(db, "r");
+ if (fp == NULL) {
+ rv = errno;
+ goto fail;
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *q;
+
+ /* Find the user, his password and allowed service */
+ file_user = buf;
+ file_password = NULL;
+
+ /* Skip comments */
+ if (file_user[0] == '#') {
+ continue;
+ }
+
+ NEXT_KEY(file_user, file_password);
+ NEXT_KEY(file_password, file_svc);
+
+ q = file_svc;
+ while(q[0] != '\n' && q[0] != '\0') {
+ q++;
+ }
+ q[0] = '\0';
+
+ if (file_password == NULL) {
+ continue;
+ }
+
+ if (strcmp(file_user, username) == 0) {
+ pmi->password = strdup(file_password);
+ if (pmi->password == NULL) {
+ rv = errno;
+ goto fail;
+ }
+
+ pmi->service = strdup(file_svc);
+ if (pmi->service == NULL) {
+ rv = errno;
+ goto fail;
+ }
+
+ break;
+ }
+ }
+
+ fclose(fp);
+ return 0;
+
+fail:
+ free(pmi->password);
+ free(pmi->service);
+ if (fp) {
+ fclose(fp);
+ }
+ return rv;
+}
+
+/* Replace authtok of user in the database with the one from pli */
+static int pam_matrix_lib_items_put(const char *db,
+ struct pam_lib_items *pli)
+{
+ int rv;
+ mode_t old_mask;
+ FILE *fp = NULL;
+ FILE *fp_tmp = NULL;
+ char buf[BUFSIZ];
+ char template[PATH_MAX] = { '\0' };
+ char *file_user = NULL;
+ char *file_password = NULL;
+ char *file_svc = NULL;
+
+ rv = snprintf(template, sizeof(template),
+ "%s.XXXXXX", db);
+ if (rv <= 0) {
+ rv = PAM_BUF_ERR;
+ goto done;
+ }
+
+ /* We don't support concurrent runs.. */
+ old_mask = umask(S_IRWXO | S_IRWXG);
+ rv = mkstemp(template);
+ umask(old_mask);
+ if (rv <= 0) {
+ rv = PAM_BUF_ERR;
+ goto done;
+ }
+
+ fp = fopen(db, "r");
+ fp_tmp = fopen(template, "w");
+ if (fp == NULL || fp_tmp == NULL) {
+ rv = errno;
+ goto done;
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *q;
+
+ file_user = buf;
+ file_password = NULL;
+
+ /* Skip comments */
+ if (file_user[0] == '#') {
+ continue;
+ }
+
+ /* Find the user, his password and allowed service */
+ NEXT_KEY(file_user, file_password);
+ NEXT_KEY(file_password, file_svc);
+
+ q = file_svc;
+ while(q[0] != '\n' && q[0] != '\0') {
+ q++;
+ }
+ q[0] = '\0';
+
+ if (file_password == NULL) {
+ continue;
+ }
+
+ if (strcmp(file_user, pli->username) == 0) {
+ if (pli->password) {
+ file_password = pli->password;
+ }
+ }
+
+ rv = fprintf(fp_tmp, "%s:%s:%s\n",
+ file_user, file_password, file_svc);
+ if (rv < 0) {
+ rv = PAM_CRED_ERR;
+ goto done;
+ }
+ }
+
+ rv = PAM_SUCCESS;
+done:
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (fp_tmp != NULL) {
+ fflush(fp_tmp);
+ fclose(fp_tmp);
+ }
+
+ if (rv == PAM_SUCCESS) {
+ rv = rename(template, db);
+ if (rv == -1) {
+ rv = PAM_SYSTEM_ERR;
+ }
+ }
+
+ if (template[0] != '\0') {
+ unlink(template);
+ };
+ return rv;
+}
+
+static void pam_matrix_mod_items_free(struct pam_matrix_mod_items *pmi)
+{
+ if (pmi == NULL) {
+ return;
+ }
+
+ free(pmi->password);
+ free(pmi->service);
+}
+
+static int pam_matrix_conv(pam_handle_t *pamh,
+ const int msg_style,
+ const char *msg,
+ char **answer)
+{
+ int ret;
+ const struct pam_conv *conv;
+ const struct pam_message *mesg[1];
+ struct pam_response *resp = NULL;
+ struct pam_response **r = NULL;
+ struct pam_message *pam_msg;
+
+ ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
+ if (ret != PAM_SUCCESS) {
+ return ret;
+ }
+
+ pam_msg = malloc(sizeof(struct pam_message));
+ if (pam_msg == NULL) {
+ return PAM_BUF_ERR;
+ }
+
+ pam_msg->msg_style = msg_style;
+ pam_msg->msg = discard_const_p(char, msg);
+
+ if (msg_style == PAM_PROMPT_ECHO_ON ||
+ msg_style == PAM_PROMPT_ECHO_OFF) {
+ r = &resp;
+ }
+
+ mesg[0] = (const struct pam_message *) pam_msg;
+ ret = conv->conv(1, mesg, r, conv->appdata_ptr);
+ free(pam_msg);
+ if (ret != PAM_SUCCESS) {
+ free(resp);
+ return ret;
+ }
+
+ if (msg_style == PAM_PROMPT_ECHO_OFF ||
+ msg_style == PAM_PROMPT_ECHO_ON) {
+ if (resp == NULL) {
+ /* Response expected, but none find! */
+ return PAM_SYSTEM_ERR;
+ }
+
+ if (resp[0].resp == NULL) {
+ /* Empty password */
+ *answer = NULL;
+ free(resp);
+ return PAM_SUCCESS;
+ }
+
+ *answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
+ wipe_authtok(resp[0].resp);
+ free(resp[0].resp);
+ free(resp);
+ if (*answer == NULL) {
+ return PAM_BUF_ERR;
+ }
+ }
+
+ return PAM_SUCCESS;
+}
+
+/* Read user password. If both prompts are provided, then ask twice and
+ * assert that both passwords match.
+ *
+ * The authtok would be returned in _out_tok if needed and set in
+ * authtok_item as well
+ */
+static int pam_matrix_read_password(pam_handle_t *pamh,
+ int flags,
+ int authtok_item,
+ const char *prompt1,
+ const char *prompt2,
+ const void **_out_tok)
+{
+ int rv = PAM_AUTHTOK_RECOVERY_ERR;
+ char *authtok1 = NULL;
+ char *authtok2 = NULL;
+ const void *item;
+ int read_flg = PAM_PROMPT_ECHO_OFF;
+
+ if (flags & PAM_MATRIX_FLG_ECHO) {
+ read_flg = PAM_PROMPT_ECHO_ON;
+ }
+
+ rv = pam_matrix_conv(pamh, read_flg, prompt1, &authtok1);
+ if (authtok1 == NULL) {
+ goto done;
+ }
+
+ if (rv == PAM_SUCCESS && prompt2 != NULL) {
+ rv = pam_matrix_conv(pamh, read_flg,
+ prompt2, &authtok2);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ if (authtok2 == NULL) {
+ rv = PAM_AUTHTOK_RECOVERY_ERR;
+ goto done;
+ }
+
+ if (strcmp(authtok1, authtok2) != 0) {
+ pam_matrix_conv(pamh, PAM_ERROR_MSG,
+ "Passwords do not match",
+ NULL);
+ rv = PAM_AUTHTOK_RECOVERY_ERR;
+ goto done;
+ }
+ wipe_authtok(authtok2);
+ free(authtok2);
+ authtok2 = NULL;
+ }
+
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = pam_set_item(pamh, authtok_item, authtok1);
+ wipe_authtok(authtok1);
+ free(authtok1);
+ authtok1 = NULL;
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = pam_get_item(pamh, authtok_item, &item);
+ if (_out_tok) {
+ *_out_tok = item;
+ }
+ item = NULL;
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = PAM_SUCCESS;
+done:
+ wipe_authtok(authtok1);
+ wipe_authtok(authtok2);
+ return rv;
+}
+
+/* Retrieve user info -- username and service that were provided by
+ * pam_start */
+static int pam_lib_items_get(pam_handle_t *pamh,
+ struct pam_lib_items *pli)
+{
+ int rv;
+
+ rv = pam_get_item(pamh, PAM_USER, (const void **) &(pli->username));
+ if (rv != PAM_SUCCESS) {
+ return rv;
+ }
+
+ if (pli->username == NULL) {
+ return PAM_BAD_ITEM;
+ }
+
+ rv = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pli->service));
+ if (rv != PAM_SUCCESS) {
+ return rv;
+ }
+
+ return PAM_SUCCESS;
+}
+
+/* Evaluate command line arguments and store info about them in the
+ * pam_matrix context
+ */
+static void eval_args(struct pam_matrix_ctx *pe_ctx,
+ int argc,
+ const char *argv[])
+{
+ pe_ctx->flags = 0;
+
+ for (; argc-- > 0; ++argv) {
+ if (strncmp(*argv, PASSDB_KEY, strlen(PASSDB_KEY)) == 0) {
+ if (*(*argv+strlen(PASSDB_KEY)) == '\0') {
+ pe_ctx->passdb = NULL;
+ } else {
+ pe_ctx->passdb = *argv+strlen(PASSDB_KEY);
+ }
+ } else if (strncmp(*argv, VERBOSE_KEY,
+ strlen(VERBOSE_KEY)) == 0) {
+ pe_ctx->flags |= PAM_MATRIX_FLG_VERBOSE;
+ } else if (strncmp(*argv, ECHO_KEY,
+ strlen(ECHO_KEY)) == 0) {
+ pe_ctx->flags |= PAM_MATRIX_FLG_ECHO;
+ }
+ }
+}
+
+/* Retrieve info about the user who is logging in and find his
+ * record in the database
+ */
+static int pam_matrix_get(pam_handle_t *pamh,
+ int argc,
+ const char *argv[],
+ struct pam_matrix_ctx *pe_ctx)
+{
+ int rv;
+
+ eval_args(pe_ctx, argc, argv);
+
+ /* If no db is provided as argument, fall back to environment variable */
+ if (pe_ctx->passdb == NULL) {
+ pe_ctx->passdb = getenv("PAM_MATRIX_PASSWD");
+ if (pe_ctx->passdb == NULL) {
+ return PAM_AUTHINFO_UNAVAIL;
+ }
+ }
+
+
+ rv = pam_lib_items_get(pamh, &pe_ctx->pli);
+ if (rv != PAM_SUCCESS) {
+ return rv;
+ }
+
+ rv = pam_matrix_mod_items_get(pe_ctx->passdb,
+ pe_ctx->pli.username,
+ &pe_ctx->pmi);
+ if (rv != PAM_SUCCESS) {
+ return PAM_AUTHINFO_UNAVAIL;
+ }
+
+ return PAM_SUCCESS;
+}
+
+static void pam_matrix_free(struct pam_matrix_ctx *pe_ctx)
+{
+ pam_matrix_mod_items_free(&pe_ctx->pmi);
+}
+
+static int _pam_matrix_auth(struct pam_matrix_ctx *pctx)
+{
+ int rv = PAM_AUTH_ERR;
+
+ if (pctx->pli.password == NULL) {
+ /* NULL passwords are not allowed */
+ return PAM_CRED_ERR;
+ }
+
+ if (pctx->pli.password != NULL &&
+ pctx->pmi.password != NULL &&
+ strcmp(pctx->pli.password, pctx->pmi.password) == 0) {
+ rv = PAM_SUCCESS;
+ }
+
+ return rv;
+}
+
+static int pam_matrix_auth(pam_handle_t *pamh, struct pam_matrix_ctx *pctx)
+{
+ int rv = PAM_AUTH_ERR;
+
+ rv = _pam_matrix_auth(pctx);
+
+ wipe_authtok(pctx->pli.password);
+ wipe_authtok(pctx->pmi.password);
+
+ if (pctx->flags & PAM_MATRIX_FLG_VERBOSE) {
+ if (rv == PAM_SUCCESS) {
+ pam_matrix_conv(pamh,
+ PAM_TEXT_INFO,
+ "Authentication succeeded",
+ NULL);
+ } else {
+ pam_matrix_conv(pamh,
+ PAM_ERROR_MSG,
+ "Authentication failed",
+ NULL);
+ }
+ }
+
+ return rv;
+}
+
+PAM_EXTERN int
+pam_sm_authenticate(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ struct pam_matrix_ctx pctx;
+ int rv;
+
+ (void) flags; /* unused */
+
+ memset(&pctx, 0, sizeof(struct pam_matrix_ctx));
+
+ /* Search the user info in database */
+ rv = pam_matrix_get(pamh, argc, argv, &pctx);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = pam_matrix_read_password(pamh, pctx.flags, PAM_AUTHTOK, "Password: ",
+ NULL, (const void **) &pctx.pli.password);
+ if (rv != PAM_SUCCESS) {
+ rv = PAM_AUTHINFO_UNAVAIL;
+ goto done;
+ }
+
+ /* Auth and get rid of the authtok */
+ rv = pam_matrix_auth(pamh, &pctx);
+done:
+ pam_matrix_free(&pctx);
+ return rv;
+}
+
+/* Really silly setcred function that just sets a pam environment variable */
+PAM_EXTERN int
+pam_sm_setcred(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ struct pam_matrix_ctx pctx;
+ int rv;
+ char cred[PATH_MAX + CRED_VAR_SZ];
+
+ (void) flags; /* unused */
+
+ memset(&pctx, 0, sizeof(struct pam_matrix_ctx));
+
+ rv = pam_matrix_get(pamh, argc, argv, &pctx);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = snprintf(cred, sizeof(cred),
+ "%s=/tmp/%s",
+ CRED_VAR, pctx.pli.username);
+ if (rv <= 0) {
+ rv = PAM_BUF_ERR;
+ goto done;
+ }
+
+ rv = pam_putenv(pamh, cred);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = PAM_SUCCESS;
+done:
+ pam_matrix_free(&pctx);
+ return rv;
+}
+
+PAM_EXTERN int
+pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ struct pam_matrix_ctx pctx;
+ int rv;
+
+ (void) flags; /* unused */
+
+ memset(&pctx, 0, sizeof(struct pam_matrix_ctx));
+
+ /* Search the user info in database */
+ rv = pam_matrix_get(pamh, argc, argv, &pctx);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ /* Check if the allowed service matches the PAM service */
+ if (pctx.pli.service != NULL &&
+ pctx.pmi.service != NULL &&
+ strcmp(pctx.pli.service, pctx.pmi.service) == 0) {
+ rv = PAM_SUCCESS;
+ goto done;
+ }
+
+ rv = PAM_PERM_DENIED;
+done:
+ pam_matrix_free(&pctx);
+ return rv;
+}
+
+/* Really silly session function that just sets a pam environment variable */
+PAM_EXTERN int
+pam_sm_open_session(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ struct pam_matrix_ctx pctx;
+ int rv;
+ char home[PATH_MAX + HOME_VAR_SZ];
+
+ (void) flags; /* unused */
+
+ memset(&pctx, 0, sizeof(struct pam_matrix_ctx));
+
+ rv = pam_matrix_get(pamh, argc, argv, &pctx);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = snprintf(home, sizeof(home),
+ "%s=/home/%s",
+ HOME_VAR, pctx.pli.username);
+ if (rv <= 0) {
+ rv = PAM_BUF_ERR;
+ goto done;
+ }
+
+ rv = pam_putenv(pamh, home);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = PAM_SUCCESS;
+done:
+ pam_matrix_free(&pctx);
+ return rv;
+}
+
+/* Just unsets whatever session set */
+PAM_EXTERN int
+pam_sm_close_session(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ struct pam_matrix_ctx pctx;
+ int rv;
+
+ (void) flags; /* unused */
+
+ memset(&pctx, 0, sizeof(struct pam_matrix_ctx));
+
+ rv = pam_matrix_get(pamh, argc, argv, &pctx);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+#if HAVE_OPENPAM
+ /* OpenPAM does not support unsetting variable, set it to
+ * and empty string instead
+ */
+ rv = pam_putenv(pamh, HOME_VAR"=");
+#else
+ rv = pam_putenv(pamh, HOME_VAR);
+#endif
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = PAM_SUCCESS;
+done:
+ pam_matrix_free(&pctx);
+ return rv;
+}
+
+static void pam_matrix_stamp_destructor(pam_handle_t *pamh,
+ void *data,
+ int error_status)
+{
+ (void) pamh; /* unused */
+ (void) error_status; /* unused */
+
+ free(data);
+}
+
+PAM_EXTERN int
+pam_sm_chauthtok(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ struct pam_matrix_ctx pctx;
+ const char *old_pass;
+ int rv;
+ time_t *auth_stamp = NULL;
+ const time_t *auth_stamp_out = NULL;
+
+ (void) flags; /* unused */
+
+ memset(&pctx, 0, sizeof(struct pam_matrix_ctx));
+
+ rv = pam_matrix_get(pamh, argc, argv, &pctx);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ if (flags & PAM_PRELIM_CHECK) {
+ rv = pam_matrix_read_password(
+ pamh, pctx.flags, PAM_OLDAUTHTOK,
+ "Old password: ", NULL,
+ (const void **) &pctx.pli.password);
+ if (rv != PAM_SUCCESS) {
+ rv = PAM_AUTHINFO_UNAVAIL;
+ goto done;
+ }
+
+ auth_stamp = malloc(sizeof(time_t));
+ if (auth_stamp == NULL) {
+ rv = PAM_BUF_ERR;
+ goto done;
+ }
+ *auth_stamp = time(NULL);
+
+ /* Not really useful, just test that between the two phases,
+ * data can be passed
+ */
+ rv = pam_set_data(pamh, PAM_EXAMPLE_AUTH_DATA,
+ auth_stamp, pam_matrix_stamp_destructor);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = pam_matrix_auth(pamh, &pctx);
+ } else if (flags & PAM_UPDATE_AUTHTOK) {
+ rv = pam_get_item(pamh,
+ PAM_OLDAUTHTOK,
+ (const void **) &old_pass);
+ if (rv != PAM_SUCCESS || old_pass == NULL) {
+ rv = PAM_AUTHINFO_UNAVAIL;
+ goto done;
+ }
+
+
+ rv = pam_get_data(pamh, PAM_EXAMPLE_AUTH_DATA,
+ (const void **) &auth_stamp_out);
+ if (rv != PAM_SUCCESS) {
+ goto done;
+ }
+
+ rv = pam_matrix_read_password(pamh,
+ pctx.flags,
+ PAM_AUTHTOK,
+ "New Password :",
+ "Verify New Password :",
+ (const void **) &pctx.pli.password);
+ if (rv != PAM_SUCCESS) {
+ rv = PAM_AUTHINFO_UNAVAIL;
+ goto done;
+ }
+
+ /* Write the new password to the db */
+ rv = pam_matrix_lib_items_put(pctx.passdb, &pctx.pli);
+ } else {
+ rv = PAM_SYSTEM_ERR;
+ }
+
+done:
+ pam_matrix_free(&pctx);
+ return rv;
+}