summaryrefslogtreecommitdiff
path: root/auth-pam.c
diff options
context:
space:
mode:
Diffstat (limited to 'auth-pam.c')
-rw-r--r--auth-pam.c522
1 files changed, 345 insertions, 177 deletions
diff --git a/auth-pam.c b/auth-pam.c
index d789bad7..5a3ba09b 100644
--- a/auth-pam.c
+++ b/auth-pam.c
@@ -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 */