From e0815d7416bba2097fc2b94ab92a3c0ea3a4f338 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Wed, 31 Jul 2013 14:36:13 +0300 Subject: schema-compat: add support for authenticating users through PAM src/back-sch-pam.c implements PAM authentication for users not found in the LDAP tree using system-auth system service when running on FreeIPA master server. --- src/back-sch-pam.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 src/back-sch-pam.c (limited to 'src') diff --git a/src/back-sch-pam.c b/src/back-sch-pam.c new file mode 100644 index 0000000..9d206e3 --- /dev/null +++ b/src/back-sch-pam.c @@ -0,0 +1,290 @@ +/* This code is originated from pam_passthru plugin of 389-ds, + * thus its copyright statement is introduced below: */ + +/** BEGIN COPYRIGHT BLOCK + * 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; version 2 of the License. + * + * 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, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA. + * + * In addition, as a special exception, Red Hat, Inc. gives You the additional + * right to link the code of this Program with code not covered under the GNU + * General Public License ("Non-GPL Code") and to distribute linked combinations + * including the two, subject to the limitations in this paragraph. Non-GPL Code + * permitted under this exception must only link to the code of this Program + * through those well defined interfaces identified in the file named EXCEPTION + * found in the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline functions from + * the Approved Interfaces without causing the resulting work to be covered by + * the GNU General Public License. Only Red Hat, Inc. may make changes or + * additions to the list of Approved Interfaces. You must obey the GNU General + * Public License in all respects for all of the Program code and other code used + * in conjunction with the Program except the Non-GPL Code covered by this + * exception. If you modify this file, you may extend this exception to your + * version of the file, but you are not obligated to do so. If you do not wish to + * provide this exception without modification, you must delete this exception + * statement from your version and license this file solely under the GPL without + * exception. + * + * + * Copyright (C) 2005 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H +#include +#include +#include +#else +#include +#endif + + +#include + +/* for third arg to pam_start */ +struct my_pam_conv_str { + Slapi_PBlock *pb; + const char *pam_identity; +}; + +static void +free_pam_response(int nresp, struct pam_response *resp) +{ + int ii; + for (ii = 0; ii < nresp; ++ii) { + if (resp[ii].resp) { + free(resp[ii].resp); + } + } + free(resp); +} + +/* + * This is the conversation function passed into pam_start(). This is what sets the password + * that PAM uses to authenticate. This function is sort of stupid - it assumes all echo off + * or binary prompts are for the password, and other prompts are for the username. Time will + * tell if this is actually the case. + */ +static int +pam_conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *mydata) +{ + int ii; + struct berval *creds; + struct my_pam_conv_str *my_data = (struct my_pam_conv_str *)mydata; + struct pam_response *reply; + int ret = PAM_SUCCESS; + + if (num_msg <= 0) { + return PAM_CONV_ERR; + } + + /* empty reply structure. We have to use malloc/free due to the caller freeing the response */ + reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); + slapi_pblock_get(my_data->pb, SLAPI_BIND_CREDENTIALS, &creds); /* the password */ + for (ii = 0; ii < num_msg; ++ii) { + /* hard to tell what prompt is for . . . */ + /* assume prompts for password are either BINARY or ECHO_OFF */ + switch (msg[ii]->msg_style) { + case PAM_PROMPT_ECHO_OFF: +#ifdef LINUX + case PAM_BINARY_PROMPT: +#endif + reply[ii].resp = malloc(creds->bv_len + 1); + if (reply[ii].resp != NULL) { + memcpy(reply[ii].resp, creds->bv_val, creds->bv_len); + reply[ii].resp[creds->bv_len] = '\0'; + } else { + ret = PAM_CONV_ERR; + } + break; + case PAM_PROMPT_ECHO_ON: + reply[ii].resp = strdup(my_data->pam_identity); + if (reply[ii].resp == NULL) { + ret = PAM_CONV_ERR; + } + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + break; + default: + ret = PAM_CONV_ERR; + break; + } + } + + if (ret == PAM_CONV_ERR) { + free_pam_response(num_msg, reply); + reply = NULL; + } + + *resp = reply; + + return ret; +} + +/* + * Do the actual work of authenticating with PAM. + * Set up the structures that pam_start needs and call pam_start(). After + * that, call pam_authenticate and pam_acct_mgmt. Check the various return + * values from these functions and map them to their corresponding LDAP BIND + * return values. Return the appropriate LDAP error code. + * This function will also set the appropriate LDAP response controls in + * the given pblock. + */ +static int +do_pam_auth(Slapi_PBlock *pb, const char *pam_service, /* name of service for pam_start() */ + int pw_response_requested, /* do we need to send pwd policy resp control */ + const char *username) +{ + const char *binddn = NULL; + Slapi_DN *bindsdn = NULL; + int rc = PAM_SUCCESS; + int retcode = LDAP_SUCCESS; + pam_handle_t *pam_handle; + struct my_pam_conv_str my_data; + struct pam_conv my_pam_conv = {pam_conv_func, NULL}; + char *errmsg = NULL; /* free with PR_smprintf_free */ + + slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &bindsdn); + if (NULL == bindsdn) { + errmsg = PR_smprintf("Null bind dn"); + retcode = LDAP_OPERATIONS_ERROR; + goto done; /* skip the pam stuff */ + } + binddn = slapi_sdn_get_dn(bindsdn); + + /* do the pam stuff */ + my_data.pb = pb; + my_data.pam_identity = username; + my_pam_conv.appdata_ptr = &my_data; + rc = pam_start(pam_service, username, &my_pam_conv, &pam_handle); + + if (rc == PAM_SUCCESS) { + /* use PAM_SILENT - there is no user interaction at this point */ + rc = pam_authenticate(pam_handle, 0); + /* check different types of errors here */ + switch (rc) { + case PAM_USER_UNKNOWN: + errmsg = PR_smprintf("User id [%s] for bind DN [%s] does not exist in PAM", + username, binddn); + retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */ + break; + case PAM_AUTH_ERR: + errmsg = PR_smprintf("Invalid PAM password for user id [%s], bind DN [%s]", + username, binddn); + retcode = LDAP_INVALID_CREDENTIALS; /* invalid creds */ + break; + case PAM_MAXTRIES: + errmsg = PR_smprintf("Authentication retry limit exceeded in PAM for " + "user id [%s], bind DN [%s]", username, binddn); + if (pw_response_requested) { + slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_ACCTLOCKED); + } + retcode = LDAP_CONSTRAINT_VIOLATION; /* max retries */ + break; + case PAM_SUCCESS: + break; + default: + errmsg = PR_smprintf("Unknown PAM error [%s] for user id [%s], bind DN [%s]", + pam_strerror(pam_handle, rc), username, binddn); + retcode = LDAP_OPERATIONS_ERROR; /* pam config or network problem */ + break; + } + } + + /* if user authenticated successfully, see if there is anything we need + to report back w.r.t. password or account lockout */ + if (rc == PAM_SUCCESS) { + rc = pam_acct_mgmt(pam_handle, 0); + /* check different types of errors here */ + switch (rc) { + case PAM_USER_UNKNOWN: + errmsg = PR_smprintf("User id [%s] for bind DN [%s] does not exist in PAM", + username, binddn); + retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */ + break; + case PAM_AUTH_ERR: + errmsg = PR_smprintf("Invalid PAM password for user id [%s], bind DN [%s]", + username, binddn); + retcode = LDAP_INVALID_CREDENTIALS; /* invalid creds */ + break; + case PAM_PERM_DENIED: + errmsg = PR_smprintf("Access denied for PAM user id [%s], bind DN [%s]" + " - see administrator", username, binddn); + if (pw_response_requested) { + slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_ACCTLOCKED); + } + retcode = LDAP_UNWILLING_TO_PERFORM; + break; + case PAM_ACCT_EXPIRED: + case PAM_NEW_AUTHTOK_REQD: + errmsg = PR_smprintf("Expired PAM password for user id [%s], bind DN [%s]: " + "reset required", username, binddn); + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); + if (pw_response_requested) { + slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED); + } + retcode = LDAP_INVALID_CREDENTIALS; + break; + case PAM_SUCCESS: + break; + default: + errmsg = PR_smprintf("Unknown PAM error [%s] for user id [%s], bind DN [%s]", + pam_strerror(pam_handle, rc), username, binddn); + retcode = LDAP_OPERATIONS_ERROR; /* unknown */ + break; + } + } + + rc = pam_end(pam_handle, rc); + +done: + if ((retcode == LDAP_SUCCESS) && (rc != PAM_SUCCESS)) { + errmsg = PR_smprintf("Unknown PAM error [%d] for user id [%s], bind DN [%s]", + rc, username, binddn); + retcode = LDAP_OPERATIONS_ERROR; + } + + if (retcode != LDAP_SUCCESS) { + slapi_send_ldap_result(pb, retcode, NULL, errmsg, 0, NULL); + } + + if (errmsg) { + PR_smprintf_free(errmsg); + } + + return retcode; +} + +int +backend_sch_do_pam_auth(Slapi_PBlock *pb, const char *username) +{ + int rc = LDAP_SUCCESS; + int pw_response_requested; + LDAPControl **reqctrls = NULL; + + slapi_pblock_get (pb, SLAPI_REQCONTROLS, &reqctrls); + slapi_pblock_get (pb, SLAPI_PWPOLICY, &pw_response_requested); + + rc = do_pam_auth(pb, "system-auth", pw_response_requested, username); + + return rc; +} -- cgit