diff options
Diffstat (limited to 'source4/nsswitch/pam_winbind.c')
-rw-r--r-- | source4/nsswitch/pam_winbind.c | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/source4/nsswitch/pam_winbind.c b/source4/nsswitch/pam_winbind.c new file mode 100644 index 0000000000..123f670366 --- /dev/null +++ b/source4/nsswitch/pam_winbind.c @@ -0,0 +1,754 @@ +/* pam_winbind module + + Copyright Andrew Tridgell <tridge@samba.org> 2000 + Copyright Tim Potter <tpot@samba.org> 2000 + Copyright Andrew Bartlett <abartlet@samba.org> 2002 + + largely based on pam_userdb by Christian Gafton <gafton@redhat.com> + also contains large slabs of code from pam_unix by Elliot Lee <sopwith@redhat.com> + (see copyright below for full details) +*/ + +#include "pam_winbind.h" + +/* data tokens */ + +#define MAX_PASSWD_TRIES 3 + +/* some syslogging */ +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl; + /* step through arguments */ + for (ctrl = 0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= WINBIND_DEBUG_ARG; + else if (!strcasecmp(*argv, "use_authtok")) + ctrl |= WINBIND_USE_AUTHTOK_ARG; + else if (!strcasecmp(*argv, "use_first_pass")) + ctrl |= WINBIND_USE_FIRST_PASS_ARG; + else if (!strcasecmp(*argv, "try_first_pass")) + ctrl |= WINBIND_TRY_FIRST_PASS_ARG; + else if (!strcasecmp(*argv, "unknown_ok")) + ctrl |= WINBIND_UNKNOWN_OK_ARG; + else { + _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv); + } + } + + return ctrl; +} + +/* --- authentication management functions --- */ + +/* Attempt a conversation */ + +static int converse(pam_handle_t *pamh, int nargs, + struct pam_message **message, + struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ; + if (retval == PAM_SUCCESS) { + retval = conv->conv(nargs, (const struct pam_message **)message, + response, conv->appdata_ptr); + } + + return retval; /* propagate error status */ +} + + +static int _make_remark(pam_handle_t * pamh, int type, const char *text) +{ + int retval = PAM_SUCCESS; + + struct pam_message *pmsg[1], msg[1]; + struct pam_response *resp; + + pmsg[0] = &msg[0]; + msg[0].msg = text; + msg[0].msg_style = type; + + resp = NULL; + retval = converse(pamh, 1, pmsg, &resp); + + if (resp) { + _pam_drop_reply(resp, 1); + } + return retval; +} + +static int pam_winbind_request(enum winbindd_cmd req_type, + struct winbindd_request *request, + struct winbindd_response *response) +{ + + /* Fill in request and send down pipe */ + init_request(request, req_type); + + if (write_sock(request, sizeof(*request)) == -1) { + _pam_log(LOG_ERR, "write to socket failed!"); + close_sock(); + return PAM_SERVICE_ERR; + } + + /* Wait for reply */ + if (read_reply(response) == -1) { + _pam_log(LOG_ERR, "read from socket failed!"); + close_sock(); + return PAM_SERVICE_ERR; + } + + /* We are done with the socket - close it and avoid mischeif */ + close_sock(); + + /* Copy reply data from socket */ + if (response->result != WINBINDD_OK) { + if (response->data.auth.pam_error != PAM_SUCCESS) { + _pam_log(LOG_ERR, "request failed: %s, PAM error was %d, NT error was %s", + response->data.auth.error_string, + response->data.auth.pam_error, + response->data.auth.nt_status_string); + return response->data.auth.pam_error; + } else { + _pam_log(LOG_ERR, "request failed, but PAM error 0!"); + return PAM_SERVICE_ERR; + } + } + + return PAM_SUCCESS; +} + +static int pam_winbind_request_log(enum winbindd_cmd req_type, + struct winbindd_request *request, + struct winbindd_response *response, + int ctrl, + const char *user) +{ + int retval; + + retval = pam_winbind_request(req_type, request, response); + + switch (retval) { + case PAM_AUTH_ERR: + /* incorrect password */ + _pam_log(LOG_WARNING, "user `%s' denied access (incorrect password)", user); + return retval; + case PAM_ACCT_EXPIRED: + /* account expired */ + _pam_log(LOG_WARNING, "user `%s' account expired", user); + return retval; + case PAM_AUTHTOK_EXPIRED: + /* password expired */ + _pam_log(LOG_WARNING, "user `%s' password expired", user); + return retval; + case PAM_NEW_AUTHTOK_REQD: + /* password expired */ + _pam_log(LOG_WARNING, "user `%s' new password required", user); + return retval; + case PAM_USER_UNKNOWN: + /* the user does not exist */ + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_NOTICE, "user `%s' not found", + user); + if (ctrl & WINBIND_UNKNOWN_OK_ARG) { + return PAM_IGNORE; + } + return retval; + case PAM_SUCCESS: + if (req_type == WINBINDD_PAM_AUTH) { + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' granted acces", user); + } else if (req_type == WINBINDD_PAM_CHAUTHTOK) { + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' password changed", user); + } else { + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' OK", user); + } + return retval; + default: + /* we don't know anything about this return value */ + _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'", + retval, user); + return retval; + } +} + +/* talk to winbindd */ +static int winbind_auth_request(const char *user, const char *pass, int ctrl) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + + strncpy(request.data.auth.user, user, + sizeof(request.data.auth.user)-1); + + strncpy(request.data.auth.pass, pass, + sizeof(request.data.auth.pass)-1); + + + return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user); +} + +/* talk to winbindd */ +static int winbind_chauthtok_request(const char *user, const char *oldpass, + const char *newpass, int ctrl) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + + if (request.data.chauthtok.user == NULL) return -2; + + strncpy(request.data.chauthtok.user, user, + sizeof(request.data.chauthtok.user) - 1); + + if (oldpass != NULL) { + strncpy(request.data.chauthtok.oldpass, oldpass, + sizeof(request.data.chauthtok.oldpass) - 1); + } else { + request.data.chauthtok.oldpass[0] = '\0'; + } + + if (newpass != NULL) { + strncpy(request.data.chauthtok.newpass, newpass, + sizeof(request.data.chauthtok.newpass) - 1); + } else { + request.data.chauthtok.newpass[0] = '\0'; + } + + return pam_winbind_request_log(WINBINDD_PAM_CHAUTHTOK, &request, &response, ctrl, user); +} + +/* + * Checks if a user has an account + * + * return values: + * 1 = User not found + * 0 = OK + * -1 = System error + */ +static int valid_user(const char *user) +{ + if (getpwnam(user)) return 0; + return 1; +} + +static char *_pam_delete(register char *xx) +{ + _pam_overwrite(xx); + _pam_drop(xx); + return NULL; +} + +/* + * obtain a password from the user + */ + +static int _winbind_read_password(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *comment + ,const char *prompt1 + ,const char *prompt2 + ,const char **pass) +{ + int authtok_flag; + int retval; + const char *item; + char *token; + + /* + * make sure nothing inappropriate gets returned + */ + + *pass = token = NULL; + + /* + * which authentication token are we getting? + */ + + authtok_flag = on(WINBIND__OLD_PASSWORD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK; + + /* + * should we obtain the password from a PAM item ? + */ + + if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) || on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) { + retval = pam_get_item(pamh, authtok_flag, (const void **) &item); + if (retval != PAM_SUCCESS) { + /* very strange. */ + _pam_log(LOG_ALERT, + "pam_get_item returned error to unix-read-password" + ); + return retval; + } else if (item != NULL) { /* we have a password! */ + *pass = item; + item = NULL; + return PAM_SUCCESS; + } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ + } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl) + && off(WINBIND__OLD_PASSWORD, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; + } + } + /* + * getting here implies we will have to get the password from the + * user directly. + */ + + { + struct pam_message msg[3], *pmsg[3]; + struct pam_response *resp; + int i, replies; + + /* prepare to converse */ + + if (comment != NULL) { + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = comment; + i = 1; + } else { + i = 0; + } + + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt1; + replies = 1; + + if (prompt2 != NULL) { + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt2; + ++replies; + } + /* so call the conversation expecting i responses */ + resp = NULL; + retval = converse(pamh, i, pmsg, &resp); + + if (resp != NULL) { + + /* interpret the response */ + + if (retval == PAM_SUCCESS) { /* a good conversation */ + + token = x_strdup(resp[i - replies].resp); + if (token != NULL) { + if (replies == 2) { + + /* verify that password entered correctly */ + if (!resp[i - 1].resp + || strcmp(token, resp[i - 1].resp)) { + _pam_delete(token); /* mistyped */ + retval = PAM_AUTHTOK_RECOVER_ERR; + _make_remark(pamh ,PAM_ERROR_MSG, MISTYPED_PASS); + } + } + } else { + _pam_log(LOG_NOTICE + ,"could not recover authentication token"); + } + + } + /* + * tidy up the conversation (resp_retcode) is ignored + * -- what is it for anyway? AGM + */ + + _pam_drop_reply(resp, i); + + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR : retval; + } + } + + if (retval != PAM_SUCCESS) { + if (on(WINBIND_DEBUG_ARG, ctrl)) + _pam_log(LOG_DEBUG, + "unable to obtain a password"); + return retval; + } + /* 'token' is the entered password */ + + /* we store this password as an item */ + + retval = pam_set_item(pamh, authtok_flag, token); + _pam_delete(token); /* clean it up */ + if (retval != PAM_SUCCESS + || (retval = pam_get_item(pamh, authtok_flag + ,(const void **) &item)) + != PAM_SUCCESS) { + + _pam_log(LOG_CRIT, "error manipulating password"); + return retval; + + } + + *pass = item; + item = NULL; /* break link to password */ + + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username; + const char *password; + int retval = PAM_AUTH_ERR; + + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + + /* Get the username */ + retval = pam_get_user(pamh, &username, NULL); + if ((retval != PAM_SUCCESS) || (!username)) { + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"can not get the username"); + return PAM_SERVICE_ERR; + } + + retval = _winbind_read_password(pamh, ctrl, NULL, + "Password: ", NULL, + &password); + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "Could not retrieve user's password"); + return PAM_AUTHTOK_ERR; + } + + if (ctrl & WINBIND_DEBUG_ARG) { + + /* Let's not give too much away in the log file */ + +#ifdef DEBUG_PASSWORD + _pam_log(LOG_INFO, "Verify user `%s' with password `%s'", + username, password); +#else + _pam_log(LOG_INFO, "Verify user `%s'", username); +#endif + } + + /* Now use the username to look up password */ + return winbind_auth_request(username, password, ctrl); +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return PAM_SUCCESS; +} + +/* + * Account management. We want to verify that the account exists + * before returning PAM_SUCCESS + */ +PAM_EXTERN +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username; + int retval = PAM_USER_UNKNOWN; + + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + + /* Get the username */ + retval = pam_get_user(pamh, &username, NULL); + if ((retval != PAM_SUCCESS) || (!username)) { + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"can not get the username"); + return PAM_SERVICE_ERR; + } + + /* Verify the username */ + retval = valid_user(username); + switch (retval) { + case -1: + /* some sort of system error. The log was already printed */ + return PAM_SERVICE_ERR; + case 1: + /* the user does not exist */ + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_NOTICE, "user `%s' not found", + username); + if (ctrl & WINBIND_UNKNOWN_OK_ARG) + return PAM_IGNORE; + return PAM_USER_UNKNOWN; + case 0: + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' granted acces", username); + return PAM_SUCCESS; + default: + /* we don't know anything about this return value */ + _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'", + retval, username); + return PAM_SERVICE_ERR; + } + + /* should not be reached */ + return PAM_IGNORE; +} +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_open_session handler"); + return PAM_SUCCESS; +} +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_close_session handler"); + return PAM_SUCCESS; +} + + + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + unsigned int lctrl; + int retval; + unsigned int ctrl = _pam_parse(argc, argv); + + /* <DO NOT free() THESE> */ + const char *user; + char *pass_old, *pass_new; + /* </DO NOT free() THESE> */ + + char *Announce; + + int retry = 0; + + /* + * First get the name of a user + */ + retval = pam_get_user(pamh, &user, "Username: "); + if (retval == PAM_SUCCESS) { + if (user == NULL) { + _pam_log(LOG_ERR, "username was NULL!"); + return PAM_USER_UNKNOWN; + } + if (retval == PAM_SUCCESS && on(WINBIND_DEBUG_ARG, ctrl)) + _pam_log(LOG_DEBUG, "username [%s] obtained", + user); + } else { + if (on(WINBIND_DEBUG_ARG, ctrl)) + _pam_log(LOG_DEBUG, + "password - could not identify user"); + return retval; + } + + /* + * obtain and verify the current password (OLDAUTHTOK) for + * the user. + */ + + if (flags & PAM_PRELIM_CHECK) { + + /* instruct user what is happening */ +#define greeting "Changing password for " + Announce = (char *) malloc(sizeof(greeting) + strlen(user)); + if (Announce == NULL) { + _pam_log(LOG_CRIT, + "password - out of memory"); + return PAM_BUF_ERR; + } + (void) strcpy(Announce, greeting); + (void) strcpy(Announce + sizeof(greeting) - 1, user); +#undef greeting + + lctrl = ctrl | WINBIND__OLD_PASSWORD; + retval = _winbind_read_password(pamh, lctrl + ,Announce + ,"(current) NT password: " + ,NULL + ,(const char **) &pass_old); + free(Announce); + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_NOTICE + ,"password - (old) token not obtained"); + return retval; + } + /* verify that this is the password for this user */ + + retval = winbind_auth_request(user, pass_old, ctrl); + + if (retval != PAM_ACCT_EXPIRED + && retval != PAM_AUTHTOK_EXPIRED + && retval != PAM_NEW_AUTHTOK_REQD + && retval != PAM_SUCCESS) { + pass_old = NULL; + return retval; + } + + retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); + pass_old = NULL; + if (retval != PAM_SUCCESS) { + _pam_log(LOG_CRIT, + "failed to set PAM_OLDAUTHTOK"); + } + } else if (flags & PAM_UPDATE_AUTHTOK) { + + /* + * obtain the proposed password + */ + + /* + * get the old token back. + */ + + retval = pam_get_item(pamh, PAM_OLDAUTHTOK + ,(const void **) &pass_old); + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_NOTICE, "user not authenticated"); + return retval; + } + + lctrl = ctrl; + + if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) { + ctrl = WINBIND_USE_FIRST_PASS_ARG | lctrl; + } + retry = 0; + retval = PAM_AUTHTOK_ERR; + while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { + /* + * use_authtok is to force the use of a previously entered + * password -- needed for pluggable password strength checking + */ + + retval = _winbind_read_password(pamh, lctrl + ,NULL + ,"Enter new NT password: " + ,"Retype new NT password: " + ,(const char **) &pass_new); + + if (retval != PAM_SUCCESS) { + if (on(WINBIND_DEBUG_ARG, ctrl)) { + _pam_log(LOG_ALERT + ,"password - new password not obtained"); + } + pass_old = NULL;/* tidy up */ + return retval; + } + + /* + * At this point we know who the user is and what they + * propose as their new password. Verify that the new + * password is acceptable. + */ + + if (pass_new[0] == '\0') {/* "\0" password = NULL */ + pass_new = NULL; + } + } + + /* + * By reaching here we have approved the passwords and must now + * rebuild the password database file. + */ + + retval = winbind_chauthtok_request(user, pass_old, pass_new, ctrl); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_old = pass_new = NULL; + } else { + retval = PAM_SERVICE_ERR; + } + + return retval; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_winbind_modstruct = { + MODULE_NAME, + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif + +/* + * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000 + * Copyright (c) Tim Potter <tpot@samba.org> 2000 + * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002 + * Copyright (c) Jan Rêkorajski 1999. + * Copyright (c) Andrew G. Morgan 1996-8. + * Copyright (c) Alex O. Yuriev, 1996. + * Copyright (c) Cristian Gafton 1996. + * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software. + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 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. + */ |