diff options
Diffstat (limited to 'auth-pam.c')
-rw-r--r-- | auth-pam.c | 522 |
1 files changed, 345 insertions, 177 deletions
@@ -45,7 +45,8 @@ * 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 $ */ +/* Based on FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des */ + #include "includes.h" #include <sys/types.h> @@ -55,6 +56,7 @@ #include <errno.h> #include <signal.h> #include <stdarg.h> +#include <stdlib.h> #include <string.h> #include <unistd.h> @@ -65,11 +67,16 @@ #include <pam/pam_appl.h> #endif +#if !defined(SSHD_PAM_SERVICE) +extern char *__progname; +# define SSHD_PAM_SERVICE __progname +#endif + /* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */ #ifdef PAM_SUN_CODEBASE -# define sshpam_const /* Solaris, HP-UX, AIX */ +# define sshpam_const /* Solaris, HP-UX, SunOS */ #else -# define sshpam_const const /* LinuxPAM, OpenPAM */ +# define sshpam_const const /* LinuxPAM, OpenPAM, AIX */ #endif /* Ambiguity in spec: is it an array of pointers or a pointer to an array? */ @@ -80,8 +87,8 @@ #endif #include "xmalloc.h" -#include "buffer.h" -#include "key.h" +#include "sshbuf.h" +#include "ssherr.h" #include "hostfile.h" #include "auth.h" #include "auth-pam.h" @@ -93,14 +100,14 @@ #include "servconf.h" #include "ssh2.h" #include "auth-options.h" +#include "misc.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" extern ServerOptions options; -extern Buffer loginmsg; -extern int compat20; +extern struct sshbuf *loginmsg; extern u_int utmp_len; /* so we don't silently change behaviour */ @@ -123,6 +130,10 @@ extern u_int utmp_len; typedef pthread_t sp_pthread_t; #else typedef pid_t sp_pthread_t; +#define pthread_exit fake_pthread_exit +#define pthread_create fake_pthread_create +#define pthread_cancel fake_pthread_cancel +#define pthread_join fake_pthread_join #endif struct pam_ctxt { @@ -141,21 +152,24 @@ static struct pam_ctxt *cleanup_ctxt; */ static int sshpam_thread_status = -1; -static mysig_t sshpam_oldsig; +static sshsig_t sshpam_oldsig; static void sshpam_sigchld_handler(int sig) { - signal(SIGCHLD, SIG_DFL); + ssh_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 */ + while (waitpid(cleanup_ctxt->pam_thread, + &sshpam_thread_status, 0) == -1) { + if (errno == EINTR) + continue; + return; + } } if (WIFSIGNALED(sshpam_thread_status) && WTERMSIG(sshpam_thread_status) == SIGTERM) @@ -185,7 +199,7 @@ pthread_create(sp_pthread_t *thread, const void *attr, switch ((pid = fork())) { case -1: error("fork(): %s", strerror(errno)); - return (-1); + return errno; case 0: close(ctx->pam_psock); ctx->pam_psock = -1; @@ -195,7 +209,7 @@ pthread_create(sp_pthread_t *thread, const void *attr, *thread = pid; close(ctx->pam_csock); ctx->pam_csock = -1; - sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler); + sshpam_oldsig = ssh_signal(SIGCHLD, sshpam_sigchld_handler); return (0); } } @@ -203,7 +217,7 @@ pthread_create(sp_pthread_t *thread, const void *attr, static int pthread_cancel(sp_pthread_t thread) { - signal(SIGCHLD, sshpam_oldsig); + ssh_signal(SIGCHLD, sshpam_oldsig); return (kill(thread, SIGTERM)); } @@ -215,8 +229,12 @@ pthread_join(sp_pthread_t thread, void **value) if (sshpam_thread_status != -1) return (sshpam_thread_status); - signal(SIGCHLD, sshpam_oldsig); - waitpid(thread, &status, 0); + ssh_signal(SIGCHLD, sshpam_oldsig); + while (waitpid(thread, &status, 0) == -1) { + if (errno == EINTR) + continue; + fatal("%s: waitpid: %s", __func__, strerror(errno)); + } return (status); } #endif @@ -228,10 +246,13 @@ static int sshpam_authenticated = 0; static int sshpam_session_open = 0; static int sshpam_cred_established = 0; static int sshpam_account_status = -1; +static int sshpam_maxtries_reached = 0; static char **sshpam_env = NULL; static Authctxt *sshpam_authctxt = NULL; static const char *sshpam_password = NULL; -static char badpw[] = "\b\n\r\177INCORRECT"; +static char *sshpam_rhost = NULL; +static char *sshpam_laddr = NULL; +static char *sshpam_conninfo = NULL; /* Some PAM implementations don't implement this */ #ifndef HAVE_PAM_GETENVLIST @@ -239,7 +260,7 @@ static char ** pam_getenvlist(pam_handle_t *pamh) { /* - * XXX - If necessary, we can still support envrionment passing + * XXX - If necessary, we can still support environment passing * for platforms without pam_getenvlist by searching for known * env vars (e.g. KRB5CCNAME) from the PAM environment. */ @@ -247,6 +268,14 @@ pam_getenvlist(pam_handle_t *pamh) } #endif +#ifndef HAVE_PAM_PUTENV +static int +pam_putenv(pam_handle_t *pamh, const char *name_value) +{ + return PAM_SUCCESS; +} +#endif /* HAVE_PAM_PUTENV */ + /* * Some platforms, notably Solaris, do not enforce password complexity * rules during pam_chauthtok() if the real uid of the calling process @@ -272,64 +301,81 @@ sshpam_chauthtok_ruid(pam_handle_t *pamh, int flags) # define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b))) #endif -void +static void sshpam_password_change_required(int reqd) { + extern struct sshauthopt *auth_opts; + static int saved_port, saved_agent, saved_x11; + 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; - no_x11_forwarding_flag |= 2; + saved_port = auth_opts->permit_port_forwarding_flag; + saved_agent = auth_opts->permit_agent_forwarding_flag; + saved_x11 = auth_opts->permit_x11_forwarding_flag; + auth_opts->permit_port_forwarding_flag = 0; + auth_opts->permit_agent_forwarding_flag = 0; + auth_opts->permit_x11_forwarding_flag = 0; } else { - no_port_forwarding_flag &= ~2; - no_agent_forwarding_flag &= ~2; - no_x11_forwarding_flag &= ~2; + if (saved_port) + auth_opts->permit_port_forwarding_flag = saved_port; + if (saved_agent) + auth_opts->permit_agent_forwarding_flag = saved_agent; + if (saved_x11) + auth_opts->permit_x11_forwarding_flag = saved_x11; } } /* Import regular and PAM environment from subprocess */ static void -import_environments(Buffer *b) +import_environments(struct sshbuf *b) { char *env; - u_int i, num_env; - int err; + u_int n, i, num_env; + int r; debug3("PAM: %s entering", __func__); #ifndef UNSUPPORTED_POSIX_THREADS_HACK /* Import variables set by do_pam_account */ - sshpam_account_status = buffer_get_int(b); - sshpam_password_change_required(buffer_get_int(b)); + if ((r = sshbuf_get_u32(b, &n)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + if (n > INT_MAX) + fatal("%s: invalid PAM account status %u", __func__, n); + sshpam_account_status = (int)n; + if ((r = sshbuf_get_u32(b, &n)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + sshpam_password_change_required(n != 0); /* Import environment from subprocess */ - num_env = buffer_get_int(b); + if ((r = sshbuf_get_u32(b, &num_env)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (num_env > 1024) fatal("%s: received %u environment variables, expected <= 1024", __func__, num_env); sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env)); debug3("PAM: num env strings %d", num_env); - for(i = 0; i < num_env; i++) - sshpam_env[i] = buffer_get_string(b, NULL); - + for(i = 0; i < num_env; i++) { + if ((r = sshbuf_get_cstring(b, &(sshpam_env[i]), NULL)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + } sshpam_env[num_env] = NULL; /* Import PAM environment from subprocess */ - num_env = buffer_get_int(b); + if ((r = sshbuf_get_u32(b, &num_env)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); debug("PAM: num PAM env strings %d", num_env); - for(i = 0; i < num_env; i++) { - env = buffer_get_string(b, NULL); - -#ifdef HAVE_PAM_PUTENV + for (i = 0; i < num_env; i++) { + if ((r = sshbuf_get_cstring(b, &env, NULL)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* Errors are not fatal here */ - if ((err = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) { + if ((r = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) { error("PAM: pam_putenv: %s", - pam_strerror(sshpam_handle, sshpam_err)); + pam_strerror(sshpam_handle, r)); } -#endif + /* XXX leak env? */ } #endif } @@ -341,10 +387,11 @@ static int sshpam_thread_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { - Buffer buffer; + struct sshbuf *buffer; struct pam_ctxt *ctxt; struct pam_response *reply; - int i; + int r, i; + u_char status; debug3("PAM: %s entering, %d messages", __func__, n); *resp = NULL; @@ -358,55 +405,52 @@ sshpam_thread_conv(int n, sshpam_const struct pam_message **msg, return (PAM_CONV_ERR); if ((reply = calloc(n, sizeof(*reply))) == NULL) - return (PAM_CONV_ERR); + return PAM_CONV_ERR; + if ((buffer = sshbuf_new()) == NULL) { + free(reply); + return PAM_CONV_ERR; + } - buffer_init(&buffer); for (i = 0; i < n; ++i) { switch (PAM_MSG_MEMBER(msg, i, msg_style)) { case PAM_PROMPT_ECHO_OFF: - buffer_put_cstring(&buffer, - PAM_MSG_MEMBER(msg, i, msg)); - if (ssh_msg_send(ctxt->pam_csock, - PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) - goto fail; - if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1) - goto fail; - if (buffer_get_char(&buffer) != PAM_AUTHTOK) - goto fail; - reply[i].resp = buffer_get_string(&buffer, NULL); - break; case PAM_PROMPT_ECHO_ON: - buffer_put_cstring(&buffer, - PAM_MSG_MEMBER(msg, i, msg)); + if ((r = sshbuf_put_cstring(buffer, + PAM_MSG_MEMBER(msg, i, msg))) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); if (ssh_msg_send(ctxt->pam_csock, - PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) + PAM_MSG_MEMBER(msg, i, msg_style), buffer) == -1) goto fail; - if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1) + + if (ssh_msg_recv(ctxt->pam_csock, buffer) == -1) goto fail; - if (buffer_get_char(&buffer) != PAM_AUTHTOK) + if ((r = sshbuf_get_u8(buffer, &status)) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); + if (status != PAM_AUTHTOK) goto fail; - reply[i].resp = buffer_get_string(&buffer, NULL); + if ((r = sshbuf_get_cstring(buffer, + &reply[i].resp, NULL)) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); break; case PAM_ERROR_MSG: - buffer_put_cstring(&buffer, - PAM_MSG_MEMBER(msg, i, msg)); - if (ssh_msg_send(ctxt->pam_csock, - PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) - goto fail; - break; case PAM_TEXT_INFO: - buffer_put_cstring(&buffer, - PAM_MSG_MEMBER(msg, i, msg)); + if ((r = sshbuf_put_cstring(buffer, + PAM_MSG_MEMBER(msg, i, msg))) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); if (ssh_msg_send(ctxt->pam_csock, - PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) + PAM_MSG_MEMBER(msg, i, msg_style), buffer) == -1) goto fail; break; default: goto fail; } - buffer_clear(&buffer); + sshbuf_reset(buffer); } - buffer_free(&buffer); + sshbuf_free(buffer); *resp = reply; return (PAM_SUCCESS); @@ -415,7 +459,7 @@ sshpam_thread_conv(int n, sshpam_const struct pam_message **msg, free(reply[i].resp); } free(reply); - buffer_free(&buffer); + sshbuf_free(buffer); return (PAM_CONV_ERR); } @@ -426,9 +470,9 @@ static void * sshpam_thread(void *ctxtp) { struct pam_ctxt *ctxt = ctxtp; - Buffer buffer; + struct sshbuf *buffer = NULL; struct pam_conv sshpam_conv; - int flags = (options.permit_empty_passwd == 0 ? + int r, flags = (options.permit_empty_passwd == 0 ? PAM_DISALLOW_NULL_AUTHTOK : 0); #ifndef UNSUPPORTED_POSIX_THREADS_HACK extern char **environ; @@ -461,66 +505,84 @@ sshpam_thread(void *ctxtp) if (sshpam_authctxt == NULL) fatal("%s: PAM authctxt not initialized", __func__); - buffer_init(&buffer); + if ((buffer = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + 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, flags); + if (sshpam_err == PAM_MAXTRIES) + sshpam_set_maxtries_reached(1); if (sshpam_err != PAM_SUCCESS) goto auth_fail; - if (compat20) { - if (!do_pam_account()) { - sshpam_err = PAM_ACCT_EXPIRED; + if (!do_pam_account()) { + sshpam_err = PAM_ACCT_EXPIRED; + goto auth_fail; + } + if (sshpam_authctxt->force_pwchange) { + sshpam_err = pam_chauthtok(sshpam_handle, + PAM_CHANGE_EXPIRED_AUTHTOK); + if (sshpam_err != PAM_SUCCESS) goto auth_fail; - } - if (sshpam_authctxt->force_pwchange) { - sshpam_err = pam_chauthtok(sshpam_handle, - PAM_CHANGE_EXPIRED_AUTHTOK); - if (sshpam_err != PAM_SUCCESS) - goto auth_fail; - sshpam_password_change_required(0); - } + sshpam_password_change_required(0); } - buffer_put_cstring(&buffer, "OK"); + if ((r = sshbuf_put_cstring(buffer, "OK")) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); #ifndef UNSUPPORTED_POSIX_THREADS_HACK /* Export variables set by do_pam_account */ - buffer_put_int(&buffer, sshpam_account_status); - buffer_put_int(&buffer, sshpam_authctxt->force_pwchange); + if ((r = sshbuf_put_u32(buffer, sshpam_account_status)) != 0 || + (r = sshbuf_put_u32(buffer, sshpam_authctxt->force_pwchange)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* Export any environment strings set in child */ - for(i = 0; environ[i] != NULL; i++) - ; /* Count */ - buffer_put_int(&buffer, i); - for(i = 0; environ[i] != NULL; i++) - buffer_put_cstring(&buffer, environ[i]); - + for (i = 0; environ[i] != NULL; i++) { + /* Count */ + if (i > INT_MAX) + fatal("%s: too many environment strings", __func__); + } + if ((r = sshbuf_put_u32(buffer, i)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + for (i = 0; environ[i] != NULL; i++) { + if ((r = sshbuf_put_cstring(buffer, environ[i])) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + } /* Export any environment strings set by PAM in child */ env_from_pam = pam_getenvlist(sshpam_handle); - for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) - ; /* Count */ - 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]); + for (i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) { + /* Count */ + if (i > INT_MAX) + fatal("%s: too many PAM environment strings", __func__); + } + if ((r = sshbuf_put_u32(buffer, i)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + for (i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) { + if ((r = sshbuf_put_cstring(buffer, env_from_pam[i])) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + } #endif /* UNSUPPORTED_POSIX_THREADS_HACK */ /* XXX - can't do much about an error here */ - ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer); - buffer_free(&buffer); + ssh_msg_send(ctxt->pam_csock, sshpam_err, buffer); + sshbuf_free(buffer); pthread_exit(NULL); auth_fail: - buffer_put_cstring(&buffer, - pam_strerror(sshpam_handle, sshpam_err)); + if ((r = sshbuf_put_cstring(buffer, + pam_strerror(sshpam_handle, sshpam_err))) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* XXX - can't do much about an error here */ if (sshpam_err == PAM_ACCT_EXPIRED) - ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer); + ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, buffer); + else if (sshpam_maxtries_reached) + ssh_msg_send(ctxt->pam_csock, PAM_MAXTRIES, buffer); else - ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer); - buffer_free(&buffer); + ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, buffer); + sshbuf_free(buffer); pthread_exit(NULL); return (NULL); /* Avoid warning for non-pthread case */ @@ -557,8 +619,7 @@ 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; + int r, i; debug3("PAM: %s called with %d messages", __func__, n); *resp = NULL; @@ -573,9 +634,10 @@ sshpam_store_conv(int n, sshpam_const struct pam_message **msg, 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 ); + if ((r = sshbuf_putf(loginmsg, "%s\n", + PAM_MSG_MEMBER(msg, i, msg))) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); reply[i].resp_retcode = PAM_SUCCESS; break; default: @@ -618,13 +680,17 @@ sshpam_cleanup(void) } static int -sshpam_init(Authctxt *authctxt) +sshpam_init(struct ssh *ssh, Authctxt *authctxt) { - extern char *__progname; - const char *pam_rhost, *pam_user, *user = authctxt->user; + const char *pam_user, *user = authctxt->user; const char **ptr_pam_user = &pam_user; - if (sshpam_handle != NULL) { + if (sshpam_handle == NULL) { + if (ssh == NULL) { + fatal("%s: called initially with no " + "packet context", __func__); + } + } if (sshpam_handle != NULL) { /* We already have a PAM context; check if the user matches */ sshpam_err = pam_get_item(sshpam_handle, PAM_USER, (sshpam_const void **)ptr_pam_user); @@ -643,14 +709,33 @@ sshpam_init(Authctxt *authctxt) sshpam_handle = NULL; return (-1); } - pam_rhost = get_remote_name_or_ip(utmp_len, options.use_dns); - debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost); - sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost); - if (sshpam_err != PAM_SUCCESS) { - pam_end(sshpam_handle, sshpam_err); - sshpam_handle = NULL; - return (-1); + + if (ssh != NULL && sshpam_rhost == NULL) { + /* + * We need to cache these as we don't have packet context + * during the kbdint flow. + */ + sshpam_rhost = xstrdup(auth_get_canonical_hostname(ssh, + options.use_dns)); + sshpam_laddr = get_local_ipaddr( + ssh_packet_get_connection_in(ssh)); + xasprintf(&sshpam_conninfo, "SSH_CONNECTION=%.50s %d %.50s %d", + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), + sshpam_laddr, ssh_local_port(ssh)); } + if (sshpam_rhost != NULL) { + debug("PAM: setting PAM_RHOST to \"%s\"", sshpam_rhost); + sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, + sshpam_rhost); + if (sshpam_err != PAM_SUCCESS) { + pam_end(sshpam_handle, sshpam_err); + sshpam_handle = NULL; + return (-1); + } + /* Put SSH_CONNECTION in the PAM environment too */ + pam_putenv(sshpam_handle, sshpam_conninfo); + } + #ifdef PAM_TTY_KLUDGE /* * Some silly PAM modules (e.g. pam_time) require a TTY to operate. @@ -668,11 +753,32 @@ sshpam_init(Authctxt *authctxt) return (0); } +static void +expose_authinfo(const char *caller) +{ + char *auth_info; + + /* + * Expose authentication information to PAM. + * The environment variable is versioned. Please increment the + * version suffix if the format of session_info changes. + */ + if (sshpam_authctxt->session_info == NULL) + auth_info = xstrdup(""); + else if ((auth_info = sshbuf_dup_string( + sshpam_authctxt->session_info)) == NULL) + fatal("%s: sshbuf_dup_string failed", __func__); + + debug2("%s: auth information in SSH_AUTH_INFO_0", caller); + do_pam_putenv("SSH_AUTH_INFO_0", auth_info); + free(auth_info); +} + static void * sshpam_init_ctx(Authctxt *authctxt) { struct pam_ctxt *ctxt; - int socks[2]; + int result, socks[2]; debug3("PAM: %s entering", __func__); /* @@ -683,11 +789,12 @@ sshpam_init_ctx(Authctxt *authctxt) return NULL; /* Initialize PAM */ - if (sshpam_init(authctxt) == -1) { + if (sshpam_init(NULL, authctxt) == -1) { error("PAM: initialization failed"); return (NULL); } + expose_authinfo(__func__); ctxt = xcalloc(1, sizeof *ctxt); /* Start the authentication thread */ @@ -698,9 +805,10 @@ sshpam_init_ctx(Authctxt *authctxt) } ctxt->pam_psock = socks[0]; ctxt->pam_csock = socks[1]; - if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) { + result = pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt); + if (result != 0) { error("PAM: failed to start authentication thread: %s", - strerror(errno)); + strerror(result)); close(socks[0]); close(socks[1]); free(ctxt); @@ -714,41 +822,44 @@ static int sshpam_query(void *ctx, char **name, char **info, u_int *num, char ***prompts, u_int **echo_on) { - Buffer buffer; + struct sshbuf *buffer; struct pam_ctxt *ctxt = ctx; size_t plen; u_char type; char *msg; size_t len, mlen; + int r; debug3("PAM: %s entering", __func__); - buffer_init(&buffer); + if ((buffer = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); *name = xstrdup(""); *info = xstrdup(""); *prompts = xmalloc(sizeof(char *)); **prompts = NULL; plen = 0; *echo_on = xmalloc(sizeof(u_int)); - while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) { - type = buffer_get_char(&buffer); - msg = buffer_get_string(&buffer, NULL); - mlen = strlen(msg); + while (ssh_msg_recv(ctxt->pam_psock, buffer) == 0) { + if ((r = sshbuf_get_u8(buffer, &type)) != 0 || + (r = sshbuf_get_cstring(buffer, &msg, &mlen)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); switch (type) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: *num = 1; len = plen + mlen + 1; - **prompts = xrealloc(**prompts, 1, len); + **prompts = xreallocarray(**prompts, 1, len); strlcpy(**prompts + plen, msg, len - plen); plen += mlen; **echo_on = (type == PAM_PROMPT_ECHO_ON); free(msg); + sshbuf_free(buffer); return (0); case PAM_ERROR_MSG: case PAM_TEXT_INFO: /* accumulate messages */ len = plen + mlen + 2; - **prompts = xrealloc(**prompts, 1, len); + **prompts = xreallocarray(**prompts, 1, len); strlcpy(**prompts + plen, msg, len - plen); plen += mlen; strlcat(**prompts + plen, "\n", len - plen); @@ -756,7 +867,11 @@ sshpam_query(void *ctx, char **name, char **info, free(msg); break; case PAM_ACCT_EXPIRED: - sshpam_account_status = 0; + case PAM_MAXTRIES: + if (type == PAM_ACCT_EXPIRED) + sshpam_account_status = 0; + if (type == PAM_MAXTRIES) + sshpam_set_maxtries_reached(1); /* FALLTHROUGH */ case PAM_AUTH_ERR: debug3("PAM: %s", pam_strerror(sshpam_handle, type)); @@ -767,6 +882,7 @@ sshpam_query(void *ctx, char **name, char **info, **echo_on = 0; ctxt->pam_done = -1; free(msg); + sshbuf_free(buffer); return 0; } /* FALLTHROUGH */ @@ -774,8 +890,10 @@ sshpam_query(void *ctx, char **name, char **info, if (**prompts != NULL) { /* drain any accumulated messages */ debug("PAM: %s", **prompts); - buffer_append(&loginmsg, **prompts, - strlen(**prompts)); + if ((r = sshbuf_put(loginmsg, **prompts, + strlen(**prompts))) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); free(**prompts); **prompts = NULL; } @@ -786,35 +904,63 @@ sshpam_query(void *ctx, char **name, char **info, fatal("Internal error: PAM auth " "succeeded when it should have " "failed"); - import_environments(&buffer); + import_environments(buffer); *num = 0; **echo_on = 0; ctxt->pam_done = 1; free(msg); + sshbuf_free(buffer); return (0); } 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)); + sshpam_authctxt->user, sshpam_rhost); /* FALLTHROUGH */ default: *num = 0; **echo_on = 0; free(msg); ctxt->pam_done = -1; + sshbuf_free(buffer); return (-1); } } + sshbuf_free(buffer); return (-1); } +/* + * Returns a junk password of identical length to that the user supplied. + * Used to mitigate timing attacks against crypt(3)/PAM stacks that + * vary processing time in proportion to password length. + */ +static char * +fake_password(const char *wire_password) +{ + const char junk[] = "\b\n\r\177INCORRECT"; + char *ret = NULL; + size_t i, l = wire_password != NULL ? strlen(wire_password) : 0; + + if (l >= INT_MAX) + fatal("%s: password length too long: %zu", __func__, l); + + ret = malloc(l + 1); + if (ret == NULL) + return NULL; + for (i = 0; i < l; i++) + ret[i] = junk[i % (sizeof(junk) - 1)]; + ret[i] = '\0'; + return ret; +} + /* XXX - see also comment in auth-chall.c:verify_response */ static int sshpam_respond(void *ctx, u_int num, char **resp) { - Buffer buffer; + struct sshbuf *buffer; struct pam_ctxt *ctxt = ctx; + char *fake; + int r; debug2("PAM: %s entering, %u responses", __func__, num); switch (ctxt->pam_done) { @@ -830,18 +976,24 @@ sshpam_respond(void *ctx, u_int num, char **resp) error("PAM: expected one response, got %u", num); return (-1); } - buffer_init(&buffer); + if ((buffer = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); 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); + options.permit_root_login == PERMIT_YES)) { + if ((r = sshbuf_put_cstring(buffer, *resp)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + } else { + fake = fake_password(*resp); + if ((r = sshbuf_put_cstring(buffer, fake)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + free(fake); + } + if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, buffer) == -1) { + sshbuf_free(buffer); return (-1); } - buffer_free(&buffer); + sshbuf_free(buffer); return (1); } @@ -881,12 +1033,14 @@ KbdintDevice mm_sshpam_device = { * This replaces auth-pam.c */ void -start_pam(Authctxt *authctxt) +start_pam(struct ssh *ssh) { + Authctxt *authctxt = (Authctxt *)ssh->authctxt; + if (!options.use_pam) fatal("PAM: initialisation requested when UsePAM=no"); - if (sshpam_init(authctxt) == -1) + if (sshpam_init(ssh, authctxt) == -1) fatal("PAM: initialisation failed"); } @@ -896,6 +1050,7 @@ finish_pam(void) sshpam_cleanup(); } + u_int do_pam_account(void) { @@ -903,6 +1058,8 @@ do_pam_account(void) if (sshpam_account_status != -1) return (sshpam_account_status); + expose_authinfo(__func__); + sshpam_err = pam_acct_mgmt(sshpam_handle, 0); debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, pam_strerror(sshpam_handle, sshpam_err)); @@ -920,18 +1077,6 @@ do_pam_account(void) } void -do_pam_set_tty(const char *tty) -{ - if (tty != NULL) { - debug("PAM: setting PAM_TTY to \"%s\"", tty); - sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, tty); - if (sshpam_err != PAM_SUCCESS) - fatal("PAM: failed to set PAM_TTY: %s", - pam_strerror(sshpam_handle, sshpam_err)); - } -} - -void do_pam_setcred(int init) { sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, @@ -1036,9 +1181,12 @@ do_pam_chauthtok(void) } void -do_pam_session(void) +do_pam_session(struct ssh *ssh) { debug3("PAM: opening session"); + + expose_authinfo(__func__); + sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&store_conv); if (sshpam_err != PAM_SUCCESS) @@ -1049,7 +1197,7 @@ do_pam_session(void) sshpam_session_open = 1; else { sshpam_session_open = 0; - disable_forwarding(); + auth_restrict_session(ssh); error("PAM: pam_open_session(): %s", pam_strerror(sshpam_handle, sshpam_err)); } @@ -1071,7 +1219,6 @@ int do_pam_putenv(char *name, char *value) { int ret = 1; -#ifdef HAVE_PAM_PUTENV char *compound; size_t len; @@ -1081,7 +1228,6 @@ do_pam_putenv(char *name, char *value) snprintf(compound, len, "%s=%s", name, value); ret = pam_putenv(sshpam_handle, compound); free(compound); -#endif return (ret); } @@ -1121,7 +1267,7 @@ sshpam_passwd_conv(int n, sshpam_const struct pam_message **msg, struct pam_response **resp, void *data) { struct pam_response *reply; - int i; + int r, i; size_t len; debug3("PAM: %s called with %d messages", __func__, n); @@ -1147,9 +1293,10 @@ sshpam_passwd_conv(int n, sshpam_const struct pam_message **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 ((r = sshbuf_putf(loginmsg, "%s\n", + PAM_MSG_MEMBER(msg, i, msg))) != 0) + fatal("%s: buffer error: %s", + __func__, ssh_err(r)); } if ((reply[i].resp = strdup("")) == NULL) goto fail; @@ -1180,6 +1327,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password) { int flags = (options.permit_empty_passwd == 0 ? PAM_DISALLOW_NULL_AUTHTOK : 0); + char *fake = NULL; if (!options.use_pam || sshpam_handle == NULL) fatal("PAM: %s called when PAM disabled or failed to " @@ -1195,7 +1343,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password) */ if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && options.permit_root_login != PERMIT_YES)) - sshpam_password = badpw; + sshpam_password = fake = fake_password(password); sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&passwd_conv); @@ -1205,6 +1353,9 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password) sshpam_err = pam_authenticate(sshpam_handle, flags); sshpam_password = NULL; + free(fake); + if (sshpam_err == PAM_MAXTRIES) + sshpam_set_maxtries_reached(1); if (sshpam_err == PAM_SUCCESS && authctxt->valid) { debug("PAM: password authentication accepted for %.100s", authctxt->user); @@ -1216,4 +1367,21 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password) return 0; } } + +int +sshpam_get_maxtries_reached(void) +{ + return sshpam_maxtries_reached; +} + +void +sshpam_set_maxtries_reached(int reached) +{ + if (reached == 0 || sshpam_maxtries_reached) + return; + sshpam_maxtries_reached = 1; + options.password_authentication = 0; + options.kbd_interactive_authentication = 0; + options.challenge_response_authentication = 0; +} #endif /* USE_PAM */ |