X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/2511d104ca3d0f27a8db74ca3709a2b2b34e9d75..32560f077f93eb11a8d199ada25e7f7dfec79af4:/auth-pam.c diff --git a/auth-pam.c b/auth-pam.c index 9f0bec03..d25cff37 100644 --- a/auth-pam.c +++ b/auth-pam.c @@ -28,6 +28,22 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +/* + * Copyright (c) 2003,2004 Damien Miller + * Copyright (c) 2003,2004 Darren Tucker + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ /* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */ #include "includes.h" @@ -40,6 +56,13 @@ RCSID("$Id$"); #include #endif +/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */ +#ifdef PAM_SUN_CODEBASE +# define sshpam_const /* Solaris, HP-UX, AIX */ +#else +# define sshpam_const const /* LinuxPAM, OpenPAM */ +#endif + #include "auth.h" #include "auth-pam.h" #include "buffer.h" @@ -49,7 +72,7 @@ RCSID("$Id$"); #include "monitor_wrap.h" #include "msg.h" #include "packet.h" -#include "readpass.h" +#include "misc.h" #include "servconf.h" #include "ssh2.h" #include "xmalloc.h" @@ -58,10 +81,19 @@ RCSID("$Id$"); extern ServerOptions options; extern Buffer loginmsg; extern int compat20; +extern u_int utmp_len; -#define __unused - +/* so we don't silently change behaviour */ #ifdef USE_POSIX_THREADS +# error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK" +#endif + +/* + * Formerly known as USE_POSIX_THREADS, using this is completely unsupported + * and generally a bad idea. Use at own risk and do not expect support if + * this breaks. + */ +#ifdef UNSUPPORTED_POSIX_THREADS_HACK #include /* * Avoid namespace clash when *not* using pthreads for systems *with* @@ -70,10 +102,49 @@ extern int compat20; */ typedef pthread_t sp_pthread_t; #else +typedef pid_t sp_pthread_t; +#endif + +struct pam_ctxt { + sp_pthread_t pam_thread; + int pam_psock; + int pam_csock; + int pam_done; +}; + +static void sshpam_free_ctx(void *); +static struct pam_ctxt *cleanup_ctxt; + +#ifndef UNSUPPORTED_POSIX_THREADS_HACK /* * Simulate threads with processes. */ -typedef pid_t sp_pthread_t; + +static int sshpam_thread_status = -1; +static mysig_t sshpam_oldsig; + +static void +sshpam_sigchld_handler(int sig) +{ + signal(SIGCHLD, SIG_DFL); + if (cleanup_ctxt == NULL) + return; /* handler called after PAM cleanup, shouldn't happen */ + if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG) + <= 0) { + /* PAM thread has not exitted, privsep slave must have */ + kill(cleanup_ctxt->pam_thread, SIGTERM); + if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0) + <= 0) + return; /* could not wait */ + } + if (WIFSIGNALED(sshpam_thread_status) && + WTERMSIG(sshpam_thread_status) == SIGTERM) + return; /* terminated by pthread_cancel */ + if (!WIFEXITED(sshpam_thread_status)) + fatal("PAM: authentication thread exited unexpectedly"); + if (WEXITSTATUS(sshpam_thread_status) != 0) + fatal("PAM: authentication thread exited uncleanly"); +} static void pthread_exit(void *value __unused) @@ -87,6 +158,7 @@ pthread_create(sp_pthread_t *thread, const void *attr __unused, { pid_t pid; + sshpam_thread_status = -1; switch ((pid = fork())) { case -1: error("fork(): %s", strerror(errno)); @@ -96,6 +168,7 @@ pthread_create(sp_pthread_t *thread, const void *attr __unused, _exit(1); default: *thread = pid; + sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler); return (0); } } @@ -103,6 +176,7 @@ pthread_create(sp_pthread_t *thread, const void *attr __unused, static int pthread_cancel(sp_pthread_t thread) { + signal(SIGCHLD, sshpam_oldsig); return (kill(thread, SIGTERM)); } @@ -111,6 +185,9 @@ pthread_join(sp_pthread_t thread, void **value __unused) { int status; + if (sshpam_thread_status != -1) + return (sshpam_thread_status); + signal(SIGCHLD, sshpam_oldsig); waitpid(thread, &status, 0); return (status); } @@ -120,21 +197,13 @@ pthread_join(sp_pthread_t thread, void **value __unused) static pam_handle_t *sshpam_handle = NULL; static int sshpam_err = 0; static int sshpam_authenticated = 0; -static int sshpam_new_authtok_reqd = 0; static int sshpam_session_open = 0; static int sshpam_cred_established = 0; static int sshpam_account_status = -1; static char **sshpam_env = NULL; - -struct pam_ctxt { - sp_pthread_t pam_thread; - int pam_psock; - int pam_csock; - int pam_done; -}; - -static void sshpam_free_ctx(void *); -static struct pam_ctxt *cleanup_ctxt; +static Authctxt *sshpam_authctxt = NULL; +static const char *sshpam_password = NULL; +static char badpw[] = "\b\n\r\177INCORRECT"; /* Some PAM implementations don't implement this */ #ifndef HAVE_PAM_GETENVLIST @@ -150,10 +219,38 @@ pam_getenvlist(pam_handle_t *pamh) } #endif +/* + * Some platforms, notably Solaris, do not enforce password complexity + * rules during pam_chauthtok() if the real uid of the calling process + * is 0, on the assumption that it's being called by "passwd" run by root. + * This wraps pam_chauthtok and sets/restore the real uid so PAM will do + * the right thing. + */ +#ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID +static int +sshpam_chauthtok_ruid(pam_handle_t *pamh, int flags) +{ + int result; + + if (sshpam_authctxt == NULL) + fatal("PAM: sshpam_authctxt not initialized"); + if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1) + fatal("%s: setreuid failed: %s", __func__, strerror(errno)); + result = pam_chauthtok(pamh, flags); + if (setreuid(0, -1) == -1) + fatal("%s: setreuid failed: %s", __func__, strerror(errno)); + return result; +} +# define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b))) +#endif + void -pam_password_change_required(int reqd) +sshpam_password_change_required(int reqd) { - sshpam_new_authtok_reqd = reqd; + debug3("%s %d", __func__, reqd); + if (sshpam_authctxt == NULL) + fatal("%s: PAM authctxt not initialized", __func__); + sshpam_authctxt->force_pwchange = reqd; if (reqd) { no_port_forwarding_flag |= 2; no_agent_forwarding_flag |= 2; @@ -162,9 +259,9 @@ pam_password_change_required(int reqd) no_port_forwarding_flag &= ~2; no_agent_forwarding_flag &= ~2; no_x11_forwarding_flag &= ~2; - } } + /* Import regular and PAM environment from subprocess */ static void import_environments(Buffer *b) @@ -173,9 +270,12 @@ import_environments(Buffer *b) u_int i, num_env; int err; + debug3("PAM: %s entering", __func__); + +#ifndef UNSUPPORTED_POSIX_THREADS_HACK /* Import variables set by do_pam_account */ sshpam_account_status = buffer_get_int(b); - pam_password_change_required(buffer_get_int(b)); + sshpam_password_change_required(buffer_get_int(b)); /* Import environment from subprocess */ num_env = buffer_get_int(b); @@ -200,13 +300,14 @@ import_environments(Buffer *b) } #endif } +#endif } /* * Conversation function for authentication thread. */ static int -sshpam_thread_conv(int n, const struct pam_message **msg, +sshpam_thread_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { Buffer buffer; @@ -214,8 +315,13 @@ sshpam_thread_conv(int n, const struct pam_message **msg, struct pam_response *reply; int i; + debug3("PAM: %s entering, %d messages", __func__, n); *resp = NULL; + if (data == NULL) { + error("PAM: conversation function passed a null context"); + return (PAM_CONV_ERR); + } ctxt = data; if (n <= 0 || n > PAM_MAX_NUM_MSG) return (PAM_CONV_ERR); @@ -293,47 +399,58 @@ sshpam_thread(void *ctxtp) struct pam_ctxt *ctxt = ctxtp; Buffer buffer; struct pam_conv sshpam_conv; -#ifndef USE_POSIX_THREADS + int flags = (options.permit_empty_passwd == 0 ? + PAM_DISALLOW_NULL_AUTHTOK : 0); +#ifndef UNSUPPORTED_POSIX_THREADS_HACK extern char **environ; char **env_from_pam; u_int i; const char *pam_user; + const char **ptr_pam_user = &pam_user; - pam_get_item(sshpam_handle, PAM_USER, (const void **)&pam_user); - setproctitle("%s [pam]", pam_user); + pam_get_item(sshpam_handle, PAM_USER, + (sshpam_const void **)ptr_pam_user); environ[0] = NULL; + + if (sshpam_authctxt != NULL) { + setproctitle("%s [pam]", + sshpam_authctxt->valid ? pam_user : "unknown"); + } #endif sshpam_conv.conv = sshpam_thread_conv; sshpam_conv.appdata_ptr = ctxt; + if (sshpam_authctxt == NULL) + fatal("%s: PAM authctxt not initialized", __func__); + buffer_init(&buffer); sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&sshpam_conv); if (sshpam_err != PAM_SUCCESS) goto auth_fail; - sshpam_err = pam_authenticate(sshpam_handle, 0); + sshpam_err = pam_authenticate(sshpam_handle, flags); if (sshpam_err != PAM_SUCCESS) goto auth_fail; if (compat20) { if (!do_pam_account()) goto auth_fail; - if (sshpam_new_authtok_reqd) { + if (sshpam_authctxt->force_pwchange) { sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); if (sshpam_err != PAM_SUCCESS) goto auth_fail; - pam_password_change_required(0); + sshpam_password_change_required(0); } } buffer_put_cstring(&buffer, "OK"); -#ifndef USE_POSIX_THREADS +#ifndef UNSUPPORTED_POSIX_THREADS_HACK /* Export variables set by do_pam_account */ buffer_put_int(&buffer, sshpam_account_status); - buffer_put_int(&buffer, sshpam_new_authtok_reqd); + buffer_put_int(&buffer, sshpam_authctxt->force_pwchange); /* Export any environment strings set in child */ for(i = 0; environ[i] != NULL; i++) @@ -349,7 +466,7 @@ sshpam_thread(void *ctxtp) buffer_put_int(&buffer, i); for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) buffer_put_cstring(&buffer, env_from_pam[i]); -#endif /* USE_POSIX_THREADS */ +#endif /* UNSUPPORTED_POSIX_THREADS_HACK */ /* XXX - can't do much about an error here */ ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer); @@ -372,6 +489,7 @@ sshpam_thread_cleanup(void) { struct pam_ctxt *ctxt = cleanup_ctxt; + debug3("PAM: %s entering", __func__); if (ctxt != NULL && ctxt->pam_thread != 0) { pthread_cancel(ctxt->pam_thread); pthread_join(ctxt->pam_thread, NULL); @@ -383,14 +501,60 @@ sshpam_thread_cleanup(void) } static int -sshpam_null_conv(int n, const struct pam_message **msg, +sshpam_null_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { + debug3("PAM: %s entering, %d messages", __func__, n); return (PAM_CONV_ERR); } static struct pam_conv null_conv = { sshpam_null_conv, NULL }; +static int +sshpam_store_conv(int n, sshpam_const struct pam_message **msg, + struct pam_response **resp, void *data) +{ + struct pam_response *reply; + int i; + size_t len; + + debug3("PAM: %s called with %d messages", __func__, n); + *resp = NULL; + + if (n <= 0 || n > PAM_MAX_NUM_MSG) + return (PAM_CONV_ERR); + + if ((reply = malloc(n * sizeof(*reply))) == NULL) + return (PAM_CONV_ERR); + memset(reply, 0, n * sizeof(*reply)); + + for (i = 0; i < n; ++i) { + switch (PAM_MSG_MEMBER(msg, i, msg_style)) { + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + len = strlen(PAM_MSG_MEMBER(msg, i, msg)); + buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len); + buffer_append(&loginmsg, "\n", 1 ); + reply[i].resp_retcode = PAM_SUCCESS; + break; + default: + goto fail; + } + } + *resp = reply; + return (PAM_SUCCESS); + + fail: + for(i = 0; i < n; i++) { + if (reply[i].resp != NULL) + xfree(reply[i].resp); + } + xfree(reply); + return (PAM_CONV_ERR); +} + +static struct pam_conv store_conv = { sshpam_store_conv, NULL }; + void sshpam_cleanup(void) { @@ -406,22 +570,22 @@ sshpam_cleanup(void) pam_close_session(sshpam_handle, PAM_SILENT); sshpam_session_open = 0; } - sshpam_authenticated = sshpam_new_authtok_reqd = 0; + sshpam_authenticated = 0; pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; } static int -sshpam_init(const char *user) +sshpam_init(Authctxt *authctxt) { - extern u_int utmp_len; extern char *__progname; - const char *pam_rhost, *pam_user; + const char *pam_rhost, *pam_user, *user = authctxt->user; + const char **ptr_pam_user = &pam_user; if (sshpam_handle != NULL) { /* We already have a PAM context; check if the user matches */ sshpam_err = pam_get_item(sshpam_handle, - PAM_USER, (const void **)&pam_user); + PAM_USER, (sshpam_const void **)ptr_pam_user); if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) return (0); pam_end(sshpam_handle, sshpam_err); @@ -429,7 +593,9 @@ sshpam_init(const char *user) } debug("PAM: initializing for \"%s\"", user); sshpam_err = - pam_start(SSHD_PAM_SERVICE, user, &null_conv, &sshpam_handle); + pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle); + sshpam_authctxt = authctxt; + if (sshpam_err != PAM_SUCCESS) { pam_end(sshpam_handle, sshpam_err); sshpam_handle = NULL; @@ -466,12 +632,13 @@ sshpam_init_ctx(Authctxt *authctxt) struct pam_ctxt *ctxt; int socks[2]; + debug3("PAM: %s entering", __func__); /* Refuse to start if we don't have PAM enabled */ if (!options.use_pam) return NULL; /* Initialize PAM */ - if (sshpam_init(authctxt->user) == -1) { + if (sshpam_init(authctxt) == -1) { error("PAM: initialization failed"); return (NULL); } @@ -508,8 +675,9 @@ sshpam_query(void *ctx, char **name, char **info, size_t plen; u_char type; char *msg; - size_t len; + size_t len, mlen; + debug3("PAM: %s entering", __func__); buffer_init(&buffer); *name = xstrdup(""); *info = xstrdup(""); @@ -520,22 +688,27 @@ sshpam_query(void *ctx, char **name, char **info, while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) { type = buffer_get_char(&buffer); msg = buffer_get_string(&buffer, NULL); + mlen = strlen(msg); switch (type) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: *num = 1; - len = plen + strlen(msg) + 1; + len = plen + mlen + 1; **prompts = xrealloc(**prompts, len); - plen += snprintf(**prompts + plen, len, "%s", msg); + strlcpy(**prompts + plen, msg, len - plen); + plen += mlen; **echo_on = (type == PAM_PROMPT_ECHO_ON); xfree(msg); return (0); case PAM_ERROR_MSG: case PAM_TEXT_INFO: /* accumulate messages */ - len = plen + strlen(msg) + 2; + len = plen + mlen + 2; **prompts = xrealloc(**prompts, len); - plen += snprintf(**prompts + plen, len, "%s\n", msg); + strlcpy(**prompts + plen, msg, len - plen); + plen += mlen; + strlcat(**prompts + plen, "\n", len - plen); + plen++; xfree(msg); break; case PAM_SUCCESS: @@ -549,6 +722,12 @@ sshpam_query(void *ctx, char **name, char **info, **prompts = NULL; } if (type == PAM_SUCCESS) { + if (!sshpam_authctxt->valid || + (sshpam_authctxt->pw->pw_uid == 0 && + options.permit_root_login != PERMIT_YES)) + fatal("Internal error: PAM auth " + "succeeded when it should have " + "failed"); import_environments(&buffer); *num = 0; **echo_on = 0; @@ -556,7 +735,10 @@ sshpam_query(void *ctx, char **name, char **info, xfree(msg); return (0); } - error("PAM: %s", msg); + error("PAM: %s for %s%.100s from %.100s", msg, + sshpam_authctxt->valid ? "" : "illegal user ", + sshpam_authctxt->user, + get_remote_name_or_ip(utmp_len, options.use_dns)); /* FALLTHROUGH */ default: *num = 0; @@ -576,7 +758,7 @@ sshpam_respond(void *ctx, u_int num, char **resp) Buffer buffer; struct pam_ctxt *ctxt = ctx; - debug2("PAM: %s", __func__); + debug2("PAM: %s entering, %d responses", __func__, num); switch (ctxt->pam_done) { case 1: sshpam_authenticated = 1; @@ -591,7 +773,12 @@ sshpam_respond(void *ctx, u_int num, char **resp) return (-1); } buffer_init(&buffer); - buffer_put_cstring(&buffer, *resp); + if (sshpam_authctxt->valid && + (sshpam_authctxt->pw->pw_uid != 0 || + options.permit_root_login == PERMIT_YES)) + buffer_put_cstring(&buffer, *resp); + else + buffer_put_cstring(&buffer, badpw); if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) { buffer_free(&buffer); return (-1); @@ -605,6 +792,7 @@ sshpam_free_ctx(void *ctxtp) { struct pam_ctxt *ctxt = ctxtp; + debug3("PAM: %s entering", __func__); sshpam_thread_cleanup(); xfree(ctxt); /* @@ -635,12 +823,12 @@ KbdintDevice mm_sshpam_device = { * This replaces auth-pam.c */ void -start_pam(const char *user) +start_pam(Authctxt *authctxt) { if (!options.use_pam) fatal("PAM: initialisation requested when UsePAM=no"); - if (sshpam_init(user) == -1) + if (sshpam_init(authctxt) == -1) fatal("PAM: initialisation failed"); } @@ -653,11 +841,13 @@ finish_pam(void) u_int do_pam_account(void) { + debug("%s: called", __func__); if (sshpam_account_status != -1) return (sshpam_account_status); sshpam_err = pam_acct_mgmt(sshpam_handle, 0); - debug3("%s: pam_acct_mgmt = %d", __func__, sshpam_err); + debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, + pam_strerror(sshpam_handle, sshpam_err)); if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) { sshpam_account_status = 0; @@ -665,7 +855,7 @@ do_pam_account(void) } if (sshpam_err == PAM_NEW_AUTHTOK_REQD) - pam_password_change_required(1); + sshpam_password_change_required(1); sshpam_account_status = 1; return (sshpam_account_status); @@ -687,7 +877,7 @@ void do_pam_setcred(int init) { sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, - (const void *)&null_conv); + (const void *)&store_conv); if (sshpam_err != PAM_SUCCESS) fatal("PAM: failed to set PAM_CONV: %s", pam_strerror(sshpam_handle, sshpam_err)); @@ -710,20 +900,16 @@ do_pam_setcred(int init) pam_strerror(sshpam_handle, sshpam_err)); } -int -is_pam_password_change_required(void) -{ - return (sshpam_new_authtok_reqd); -} - static int -pam_tty_conv(int n, const struct pam_message **msg, +sshpam_tty_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { char input[PAM_MAX_MSG_SIZE]; struct pam_response *reply; int i; + debug3("PAM: %s called with %d messages", __func__, n); + *resp = NULL; if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO)) @@ -744,7 +930,8 @@ pam_tty_conv(int n, const struct pam_message **msg, case PAM_PROMPT_ECHO_ON: fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); fgets(input, sizeof input, stdin); - reply[i].resp = xstrdup(input); + if ((reply[i].resp = strdup(input)) == NULL) + goto fail; reply[i].resp_retcode = PAM_SUCCESS; break; case PAM_ERROR_MSG: @@ -768,7 +955,7 @@ pam_tty_conv(int n, const struct pam_message **msg, return (PAM_CONV_ERR); } -static struct pam_conv tty_conv = { pam_tty_conv, NULL }; +static struct pam_conv tty_conv = { sshpam_tty_conv, NULL }; /* * XXX this should be done in the authentication phase, but ssh1 doesn't @@ -794,16 +981,28 @@ do_pam_chauthtok(void) void do_pam_session(void) { + debug3("PAM: opening session"); sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, - (const void *)&tty_conv); + (const void *)&store_conv); if (sshpam_err != PAM_SUCCESS) fatal("PAM: failed to set PAM_CONV: %s", pam_strerror(sshpam_handle, sshpam_err)); sshpam_err = pam_open_session(sshpam_handle, 0); - if (sshpam_err != PAM_SUCCESS) - fatal("PAM: pam_open_session(): %s", + if (sshpam_err == PAM_SUCCESS) + sshpam_session_open = 1; + else { + sshpam_session_open = 0; + disable_forwarding(); + error("PAM: pam_open_session(): %s", pam_strerror(sshpam_handle, sshpam_err)); - sshpam_session_open = 1; + } + +} + +int +is_pam_session_open(void) +{ + return sshpam_session_open; } /* @@ -830,12 +1029,6 @@ do_pam_putenv(char *name, char *value) return (ret); } -void -print_pam_messages(void) -{ - /* XXX */ -} - char ** fetch_pam_child_environment(void) { @@ -861,4 +1054,111 @@ free_pam_environment(char **env) xfree(env); } +/* + * "Blind" conversation function for password authentication. Assumes that + * echo-off prompts are for the password and stores messages for later + * display. + */ +static int +sshpam_passwd_conv(int n, sshpam_const struct pam_message **msg, + struct pam_response **resp, void *data) +{ + struct pam_response *reply; + int i; + size_t len; + + debug3("PAM: %s called with %d messages", __func__, n); + + *resp = NULL; + + if (n <= 0 || n > PAM_MAX_NUM_MSG) + return (PAM_CONV_ERR); + + if ((reply = malloc(n * sizeof(*reply))) == NULL) + return (PAM_CONV_ERR); + memset(reply, 0, n * sizeof(*reply)); + + for (i = 0; i < n; ++i) { + switch (PAM_MSG_MEMBER(msg, i, msg_style)) { + case PAM_PROMPT_ECHO_OFF: + if (sshpam_password == NULL) + goto fail; + if ((reply[i].resp = strdup(sshpam_password)) == NULL) + goto fail; + reply[i].resp_retcode = PAM_SUCCESS; + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + len = strlen(PAM_MSG_MEMBER(msg, i, msg)); + if (len > 0) { + buffer_append(&loginmsg, + PAM_MSG_MEMBER(msg, i, msg), len); + buffer_append(&loginmsg, "\n", 1); + } + if ((reply[i].resp = strdup("")) == NULL) + goto fail; + reply[i].resp_retcode = PAM_SUCCESS; + break; + default: + goto fail; + } + } + *resp = reply; + return (PAM_SUCCESS); + + fail: + for(i = 0; i < n; i++) { + if (reply[i].resp != NULL) + xfree(reply[i].resp); + } + xfree(reply); + return (PAM_CONV_ERR); +} + +static struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL }; + +/* + * Attempt password authentication via PAM + */ +int +sshpam_auth_passwd(Authctxt *authctxt, const char *password) +{ + int flags = (options.permit_empty_passwd == 0 ? + PAM_DISALLOW_NULL_AUTHTOK : 0); + + if (!options.use_pam || sshpam_handle == NULL) + fatal("PAM: %s called when PAM disabled or failed to " + "initialise.", __func__); + + sshpam_password = password; + sshpam_authctxt = authctxt; + + /* + * If the user logging in is invalid, or is root but is not permitted + * by PermitRootLogin, use an invalid password to prevent leaking + * information via timing (eg if the PAM config has a delay on fail). + */ + if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && + options.permit_root_login != PERMIT_YES)) + sshpam_password = badpw; + + sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, + (const void *)&passwd_conv); + if (sshpam_err != PAM_SUCCESS) + fatal("PAM: %s: failed to set PAM_CONV: %s", __func__, + pam_strerror(sshpam_handle, sshpam_err)); + + sshpam_err = pam_authenticate(sshpam_handle, flags); + sshpam_password = NULL; + if (sshpam_err == PAM_SUCCESS && authctxt->valid) { + debug("PAM: password authentication accepted for %.100s", + authctxt->user); + return 1; + } else { + debug("PAM: password authentication failed for %.100s: %s", + authctxt->valid ? authctxt->user : "an illegal user", + pam_strerror(sshpam_handle, sshpam_err)); + return 0; + } +} #endif /* USE_PAM */