*/
/*
* Copyright (c) 2003,2004 Damien Miller <djm@mindrot.org>
- * Copyright (c) 2003,2004 Darren Tucker <dtucker@zip.com.au>
+ * Copyright (c) 2003,2004,2006 Darren Tucker <dtucker@zip.com.au>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
/* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */
#include "includes.h"
-RCSID("$Id$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
#ifdef USE_PAM
#if defined(HAVE_SECURITY_PAM_APPL_H)
# define sshpam_const const /* LinuxPAM, OpenPAM */
#endif
+/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
+#ifdef PAM_SUN_CODEBASE
+# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member)
+#else
+# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member)
+#endif
+
+#include "xmalloc.h"
+#include "buffer.h"
+#include "key.h"
+#include "hostfile.h"
#include "auth.h"
#include "auth-pam.h"
-#include "buffer.h"
-#include "bufaux.h"
#include "canohost.h"
#include "log.h"
-#include "monitor_wrap.h"
#include "msg.h"
#include "packet.h"
#include "misc.h"
#include "servconf.h"
#include "ssh2.h"
-#include "xmalloc.h"
#include "auth-options.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+#include "monitor_wrap.h"
extern ServerOptions options;
extern Buffer loginmsg;
WTERMSIG(sshpam_thread_status) == SIGTERM)
return; /* terminated by pthread_cancel */
if (!WIFEXITED(sshpam_thread_status))
- fatal("PAM: authentication thread exited unexpectedly");
+ sigdie("PAM: authentication thread exited unexpectedly");
if (WEXITSTATUS(sshpam_thread_status) != 0)
- fatal("PAM: authentication thread exited uncleanly");
+ sigdie("PAM: authentication thread exited uncleanly");
}
+/* ARGSUSED */
static void
-pthread_exit(void *value __unused)
+pthread_exit(void *value)
{
_exit(0);
}
+/* ARGSUSED */
static int
-pthread_create(sp_pthread_t *thread, const void *attr __unused,
+pthread_create(sp_pthread_t *thread, const void *attr,
void *(*thread_start)(void *), void *arg)
{
pid_t pid;
return (kill(thread, SIGTERM));
}
+/* ARGSUSED */
static int
-pthread_join(sp_pthread_t thread, void **value __unused)
+pthread_join(sp_pthread_t thread, void **value)
{
int status;
# define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b)))
#endif
+struct passwd *
+sshpam_getpw(const char *user)
+{
+ struct passwd *pw;
+
+ if ((pw = getpwnam(user)) != NULL)
+ return(pw);
+
+ debug("PAM: faking passwd struct for user '%.100s'", user);
+ if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL)
+ return NULL;
+ pw->pw_name = xstrdup(user); /* XXX leak */
+ pw->pw_shell = "/bin/true";
+ pw->pw_gecos = "sshd fake PAM user";
+ return (pw);
+}
+
+void
+sshpam_check_userchanged(void)
+{
+ int sshpam_err;
+ struct passwd *pw;
+ const char *user;
+
+ debug("sshpam_check_userchanged");
+ sshpam_err = pam_get_item(sshpam_handle, PAM_USER, &user);
+ if (sshpam_err != PAM_SUCCESS)
+ fatal("PAM: could not get PAM_USER: %s",
+ pam_strerror(sshpam_handle, sshpam_err));
+ if (strcmp(user, sshpam_authctxt->pw->pw_name) != 0) {
+ debug("PAM: user mapped from '%.100s' to '%.100s'",
+ sshpam_authctxt->pw->pw_name, user);
+ if ((pw = getpwnam(user)) == NULL)
+ fatal("PAM: could not get passwd entry for user "
+ "'%.100s' provided by PAM_USER", user);
+ pwfree(sshpam_authctxt->pw);
+ sshpam_authctxt->pw = pw;
+ sshpam_authctxt->valid = allowed_user(pw);
+ debug("PAM: user '%.100s' now %svalid", user,
+ sshpam_authctxt->valid ? "" : "in");
+ }
+}
+
void
sshpam_password_change_required(int reqd)
{
static void
import_environments(Buffer *b)
{
- char *env;
+ char *env, *user;
u_int i, num_env;
int err;
/* Import variables set by do_pam_account */
sshpam_account_status = buffer_get_int(b);
sshpam_password_change_required(buffer_get_int(b));
+ if (options.permit_pam_user_change) {
+ user = buffer_get_string(b, NULL);
+ debug("PAM: got username '%.100s' from thread", user);
+ if ((err = pam_set_item(sshpam_handle, PAM_USER, user)) != PAM_SUCCESS)
+ fatal("PAM: failed to set PAM_USER: %s",
+ pam_strerror(sshpam_handle, err));
+ pwfree(sshpam_authctxt->pw);
+ sshpam_authctxt->pw = pwcopy(sshpam_getpw(user));
+ }
/* Import environment from subprocess */
num_env = buffer_get_int(b);
- sshpam_env = xmalloc((num_env + 1) * sizeof(*sshpam_env));
+ 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);
if (n <= 0 || n > PAM_MAX_NUM_MSG)
return (PAM_CONV_ERR);
- if ((reply = malloc(n * sizeof(*reply))) == NULL)
+ if ((reply = calloc(n, sizeof(*reply))) == NULL)
return (PAM_CONV_ERR);
- memset(reply, 0, n * sizeof(*reply));
buffer_init(&buffer);
for (i = 0; i < n; ++i) {
u_int i;
const char *pam_user;
const char **ptr_pam_user = &pam_user;
+ char *tz = getenv("TZ");
pam_get_item(sshpam_handle, PAM_USER,
(sshpam_const void **)ptr_pam_user);
+
environ[0] = NULL;
+ if (tz != NULL)
+ if (setenv("TZ", tz, 1) == -1)
+ error("PAM: could not set TZ environment: %s",
+ strerror(errno));
if (sshpam_authctxt != NULL) {
setproctitle("%s [pam]",
if (sshpam_err != PAM_SUCCESS)
goto auth_fail;
+ if (options.permit_pam_user_change) {
+ sshpam_check_userchanged();
+ }
if (compat20) {
- if (!do_pam_account())
+ 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);
/* Export variables set by do_pam_account */
buffer_put_int(&buffer, sshpam_account_status);
buffer_put_int(&buffer, sshpam_authctxt->force_pwchange);
+ if (options.permit_pam_user_change) {
+ buffer_put_cstring(&buffer, sshpam_authctxt->pw->pw_name);
+ }
/* Export any environment strings set in child */
for(i = 0; environ[i] != NULL; i++)
buffer_put_cstring(&buffer,
pam_strerror(sshpam_handle, sshpam_err));
/* XXX - can't do much about an error here */
- ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
+ if (sshpam_err == PAM_ACCT_EXPIRED)
+ ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer);
+ else
+ ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
buffer_free(&buffer);
pthread_exit(NULL);
if (n <= 0 || n > PAM_MAX_NUM_MSG)
return (PAM_CONV_ERR);
- if ((reply = malloc(n * sizeof(*reply))) == NULL)
+ if ((reply = calloc(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)) {
void
sshpam_cleanup(void)
{
- debug("PAM: cleanup");
- if (sshpam_handle == NULL)
+ if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor()))
return;
+ debug("PAM: cleanup");
pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv);
if (sshpam_cred_established) {
+ debug("PAM: deleting credentials");
pam_setcred(sshpam_handle, PAM_DELETE_CRED);
sshpam_cred_established = 0;
}
if (sshpam_session_open) {
+ debug("PAM: closing session");
pam_close_session(sshpam_handle, PAM_SILENT);
sshpam_session_open = 0;
}
int socks[2];
debug3("PAM: %s entering", __func__);
- /* Refuse to start if we don't have PAM enabled */
- if (!options.use_pam)
+ /*
+ * Refuse to start if we don't have PAM enabled or do_pam_account
+ * has previously failed.
+ */
+ if (!options.use_pam || sshpam_account_status == 0)
return NULL;
/* Initialize PAM */
return (NULL);
}
- ctxt = xmalloc(sizeof *ctxt);
- memset(ctxt, 0, sizeof(*ctxt));
+ ctxt = xcalloc(1, sizeof *ctxt);
/* Start the authentication thread */
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) {
case PAM_PROMPT_ECHO_OFF:
*num = 1;
len = plen + mlen + 1;
- **prompts = xrealloc(**prompts, len);
+ **prompts = xrealloc(**prompts, 1, len);
strlcpy(**prompts + plen, msg, len - plen);
plen += mlen;
**echo_on = (type == PAM_PROMPT_ECHO_ON);
case PAM_TEXT_INFO:
/* accumulate messages */
len = plen + mlen + 2;
- **prompts = xrealloc(**prompts, len);
+ **prompts = xrealloc(**prompts, 1, len);
strlcpy(**prompts + plen, msg, len - plen);
plen += mlen;
strlcat(**prompts + plen, "\n", len - plen);
plen++;
xfree(msg);
break;
+ case PAM_ACCT_EXPIRED:
+ sshpam_account_status = 0;
+ /* FALLTHROUGH */
case PAM_AUTH_ERR:
- debug3("PAM: PAM_AUTH_ERR");
+ debug3("PAM: %s", pam_strerror(sshpam_handle, type));
if (**prompts != NULL && strlen(**prompts) != 0) {
*info = **prompts;
**prompts = NULL;
*num = 0;
**echo_on = 0;
ctxt->pam_done = -1;
+ xfree(msg);
return 0;
}
/* FALLTHROUGH */
debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
pam_strerror(sshpam_handle, sshpam_err));
+ if (options.permit_pam_user_change) {
+ sshpam_check_userchanged();
+ if (getpwnam(sshpam_authctxt->pw->pw_name) == NULL)
+ fatal("PAM: completed authentication but PAM account invalid");
+ }
+
if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
sshpam_account_status = 0;
return (sshpam_account_status);
if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO))
return (PAM_CONV_ERR);
- if ((reply = malloc(n * sizeof(*reply))) == NULL)
+ if ((reply = calloc(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)) {
break;
case PAM_PROMPT_ECHO_ON:
fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
- fgets(input, sizeof input, stdin);
+ if (fgets(input, sizeof input, stdin) == NULL)
+ input[0] = '\0';
if ((reply[i].resp = strdup(input)) == NULL)
goto fail;
reply[i].resp_retcode = PAM_SUCCESS;
if (n <= 0 || n > PAM_MAX_NUM_MSG)
return (PAM_CONV_ERR);
- if ((reply = malloc(n * sizeof(*reply))) == NULL)
+ if ((reply = calloc(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)) {
pam_strerror(sshpam_handle, sshpam_err));
sshpam_err = pam_authenticate(sshpam_handle, flags);
+ if (options.permit_pam_user_change) {
+ sshpam_check_userchanged();
+ }
sshpam_password = NULL;
if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
debug("PAM: password authentication accepted for %.100s",