diff options
author | cvs2svn <cvs2svn@FreeBSD.org> | 2000-12-05 02:55:13 +0000 |
---|---|---|
committer | cvs2svn <cvs2svn@FreeBSD.org> | 2000-12-05 02:55:13 +0000 |
commit | 5bcde1229c897d3a1ecba9ae48d888c773877ad9 (patch) | |
tree | 77a3cf9e728cff5023d829ebe45a5d6e6b2a0d75 /crypto | |
parent | 2aecee364f2b1fa8b38c4d29600f05f33075cddf (diff) | |
download | FreeBSD-src-5bcde1229c897d3a1ecba9ae48d888c773877ad9.zip FreeBSD-src-5bcde1229c897d3a1ecba9ae48d888c773877ad9.tar.gz |
This commit was manufactured by cvs2svn to create branch
'VENDOR-crypto-openssh'.
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/openssh/auth-krb5.c | 249 | ||||
-rw-r--r-- | crypto/openssh/auth-pam.c | 777 | ||||
-rw-r--r-- | crypto/openssh/auth-pam.h | 38 |
3 files changed, 1064 insertions, 0 deletions
diff --git a/crypto/openssh/auth-krb5.c b/crypto/openssh/auth-krb5.c new file mode 100644 index 0000000..6030d9a --- /dev/null +++ b/crypto/openssh/auth-krb5.c @@ -0,0 +1,249 @@ +/* + * Kerberos v5 authentication and ticket-passing routines. + * + * $FreeBSD$ + */ + +#include "includes.h" +#include "ssh.h" +#include "packet.h" +#include "xmalloc.h" + +#ifdef KRB5 + +extern krb5_context ssh_context; +krb5_auth_context auth_context; +krb5_ccache mem_ccache = NULL; /* Credential cache for acquired ticket */ + +/* Try krb5 authentication. server_user is passed for logging purposes only, + in auth is received ticket, in client is returned principal from the + ticket */ +int +auth_krb5(const char* server_user, krb5_data *auth, krb5_principal *client) +{ + krb5_error_code problem; + krb5_principal server = NULL; + krb5_principal tkt_client = NULL; + krb5_data reply; + krb5_ticket *ticket = NULL; + int fd; + int ret; + + reply.length = 0; + + problem = krb5_init(); + if (problem) + return 0; + + problem = krb5_auth_con_init(ssh_context, &auth_context); + if (problem) { + log("Kerberos v5 authentication failed: %.100s", + krb5_get_err_text(ssh_context, problem)); + + return 0; + } + + fd = packet_get_connection_in(); + problem = krb5_auth_con_setaddrs_from_fd(ssh_context, auth_context, &fd); + if (problem) { + ret = 0; + goto err; + } + + problem = krb5_sname_to_principal(ssh_context, NULL, NULL , + KRB5_NT_SRV_HST, &server); + if (problem) { + ret = 0; + goto err; + } + + problem = krb5_rd_req(ssh_context, &auth_context, auth, server, NULL, + NULL, &ticket); + if (problem) { + ret = 0; + goto err; + } + + problem = krb5_copy_principal(ssh_context, ticket->client, &tkt_client); + if (problem) { + ret = 0; + goto err; + } + + /* if client wants mutual auth */ + problem = krb5_mk_rep(ssh_context, &auth_context, &reply); + if (problem) { + ret = 0; + goto err; + } + + *client = tkt_client; + + packet_start(SSH_SMSG_AUTH_KRB5_RESPONSE); + packet_put_string((char *) reply.data, reply.length); + packet_send(); + packet_write_wait(); + ret = 1; + +err: + if (server) + krb5_free_principal(ssh_context, server); + if (ticket) + krb5_free_ticket(ssh_context, ticket); + if (reply.length) + xfree(reply.data); + return ret; +} + +int +auth_krb5_tgt(char *server_user, krb5_data *tgt, krb5_principal tkt_client) +{ + krb5_error_code problem; + krb5_ccache ccache = NULL; + + if (ssh_context == NULL) { + goto fail; + } + + problem = krb5_cc_gen_new(ssh_context, &krb5_mcc_ops, &ccache); + if (problem) { + goto fail; + } + + problem = krb5_cc_initialize(ssh_context, ccache, tkt_client); + if (problem) { + goto fail; + } + + problem = krb5_rd_cred(ssh_context, auth_context, ccache, tgt); + if (problem) { + goto fail; + } + + mem_ccache = ccache; + ccache = NULL; + + /* + problem = krb5_cc_copy_cache(ssh_context, ccache, mem_ccache); + if (problem) { + mem_ccache = NULL; + goto fail; + } + + + problem = krb5_cc_destroy(ssh_context, ccache); + if (problem) + goto fail; + */ + +#if 0 + packet_start(SSH_SMSG_SUCCESS); + packet_send(); + packet_write_wait(); +#endif + return 1; + +fail: + if (ccache) + krb5_cc_destroy(ssh_context, ccache); +#if 0 + packet_start(SSH_SMSG_FAILURE); + packet_send(); + packet_write_wait(); +#endif + return 0; +} + +int +auth_krb5_password(struct passwd *pw, const char *password) +{ + krb5_error_code problem; + krb5_ccache ccache = NULL; + krb5_principal client = NULL; + int ret; + + problem = krb5_init(); + if (problem) + return 0; + + problem = krb5_parse_name(ssh_context, pw->pw_name, &client); + if (problem) { + ret = 0; + goto out; + } + + problem = krb5_cc_gen_new(ssh_context, &krb5_mcc_ops, &ccache); + if (problem) { + ret = 0; + goto out; + } + + problem = krb5_cc_initialize(ssh_context, ccache, client); + if (problem) { + ret = 0; + goto out; + } + + problem = krb5_verify_user(ssh_context, client, ccache, password, 1, NULL); + if (problem) { + ret = 0; + goto out; + } + +/* + problem = krb5_cc_copy_cache(ssh_context, ccache, mem_ccache); + if (problem) { + ret = 0; + mem_ccache = NULL; + goto out; + } + */ + mem_ccache = ccache; + ccache = NULL; + + ret = 1; +out: + if (client != NULL) + krb5_free_principal(ssh_context, client); + if (ccache != NULL) + krb5_cc_destroy(ssh_context, ccache); + return ret; +} + +void +krb5_cleanup_proc(void *ignore) +{ + extern krb5_principal tkt_client; + + debug("krb5_cleanup_proc() called"); + if (mem_ccache) + krb5_cc_destroy(ssh_context, mem_ccache); + if (tkt_client) + krb5_free_principal(ssh_context, tkt_client); + if (auth_context) + krb5_auth_con_free(ssh_context, auth_context); + if (ssh_context) + krb5_free_context(ssh_context); +} + +int +krb5_init(void) +{ + krb5_error_code problem; + static cleanup_registered = 0; + + if (ssh_context == NULL) { + problem = krb5_init_context(&ssh_context); + if (problem) + return problem; + krb5_init_ets(ssh_context); + } + + if (!cleanup_registered) { + fatal_add_cleanup(krb4_cleanup_proc, NULL); + cleanup_registered = 1; + } + return 0; +} + +#endif /* KRB5 */ diff --git a/crypto/openssh/auth-pam.c b/crypto/openssh/auth-pam.c new file mode 100644 index 0000000..e7403cc --- /dev/null +++ b/crypto/openssh/auth-pam.c @@ -0,0 +1,777 @@ +/* + * Copyright (c) 2000 Damien Miller. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#ifdef USE_PAM +#include <security/pam_appl.h> +#include "ssh.h" +#include "xmalloc.h" +#include "servconf.h" + +RCSID("$FreeBSD$"); + +#define NEW_AUTHTOK_MSG \ + "Warning: Your password has expired, please change it now" + +#define SSHD_PAM_SERVICE "sshd" +#define PAM_STRERROR(a, b) pam_strerror((a), (b)) + +/* Callbacks */ +static int pamconv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr); +void pam_cleanup_proc(void *context); +void pam_msg_cat(const char *msg); + +/* module-local variables */ +static struct pam_conv conv = { + pamconv, + NULL +}; +static pam_handle_t *pamh = NULL; +static const char *pampasswd = NULL; +static char *pam_msg = NULL; + +/* states for pamconv() */ +typedef enum { INITIAL_LOGIN, OTHER } pamstates; +static pamstates pamstate = INITIAL_LOGIN; +/* remember whether pam_acct_mgmt() returned PAM_NEWAUTHTOK_REQD */ +static int password_change_required = 0; + +/* + * PAM conversation function. + * There are two states this can run in. + * + * INITIAL_LOGIN mode simply feeds the password from the client into + * PAM in response to PAM_PROMPT_ECHO_OFF, and collects output + * messages with pam_msg_cat(). This is used during initial + * authentication to bypass the normal PAM password prompt. + * + * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase(prompt, 1) + * and outputs messages to stderr. This mode is used if pam_chauthtok() + * is called to update expired passwords. + */ +static int pamconv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +{ + struct pam_response *reply; + int count; + char buf[1024]; + + /* PAM will free this later */ + reply = malloc(num_msg * sizeof(*reply)); + if (reply == NULL) + return PAM_CONV_ERR; + + for (count = 0; count < num_msg; count++) { + switch ((*msg)[count].msg_style) { + case PAM_PROMPT_ECHO_ON: + if (pamstate == INITIAL_LOGIN) { + free(reply); + return PAM_CONV_ERR; + } else { + fputs((*msg)[count].msg, stderr); + fgets(buf, sizeof(buf), stdin); + reply[count].resp = xstrdup(buf); + reply[count].resp_retcode = PAM_SUCCESS; + break; + } + case PAM_PROMPT_ECHO_OFF: + if (pamstate == INITIAL_LOGIN) { + if (pampasswd == NULL) { + free(reply); + return PAM_CONV_ERR; + } + reply[count].resp = xstrdup(pampasswd); + } else { + reply[count].resp = + xstrdup(read_passphrase((*msg)[count].msg, 1)); + } + reply[count].resp_retcode = PAM_SUCCESS; + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + if ((*msg)[count].msg != NULL) { + if (pamstate == INITIAL_LOGIN) + pam_msg_cat((*msg)[count].msg); + else { + fputs((*msg)[count].msg, stderr); + fputs("\n", stderr); + } + } + reply[count].resp = xstrdup(""); + reply[count].resp_retcode = PAM_SUCCESS; + break; + default: + free(reply); + return PAM_CONV_ERR; + } + } + + *resp = reply; + + return PAM_SUCCESS; +} + +/* Called at exit to cleanly shutdown PAM */ +void pam_cleanup_proc(void *context) +{ + int pam_retval; + + if (pamh != NULL) + { + pam_retval = pam_close_session(pamh, 0); + if (pam_retval != PAM_SUCCESS) { + log("Cannot close PAM session[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + + pam_retval = pam_setcred(pamh, PAM_DELETE_CRED); + if (pam_retval != PAM_SUCCESS) { + debug("Cannot delete credentials[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + + pam_retval = pam_end(pamh, pam_retval); + if (pam_retval != PAM_SUCCESS) { + log("Cannot release PAM authentication[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + } +} + +/* Attempt password authentation using PAM */ +int auth_pam_password(struct passwd *pw, const char *password) +{ + extern ServerOptions options; + int pam_retval; + + /* deny if no user. */ + if (pw == NULL) + return 0; + if (pw->pw_uid == 0 && options.permit_root_login == 2) + return 0; + if (*password == '\0' && options.permit_empty_passwd == 0) + return 0; + + pampasswd = password; + + pamstate = INITIAL_LOGIN; + pam_retval = pam_authenticate(pamh, 0); + if (pam_retval == PAM_SUCCESS) { + debug("PAM Password authentication accepted for user \"%.100s\"", + pw->pw_name); + return 1; + } else { + debug("PAM Password authentication for \"%.100s\" failed[%d]: %s", + pw->pw_name, pam_retval, PAM_STRERROR(pamh, pam_retval)); + return 0; + } +} + +/* Do account management using PAM */ +int do_pam_account(char *username, char *remote_user) +{ + int pam_retval; + + debug("PAM setting rhost to \"%.200s\"", get_canonical_hostname()); + pam_retval = pam_set_item(pamh, PAM_RHOST, + get_canonical_hostname()); + if (pam_retval != PAM_SUCCESS) { + fatal("PAM set rhost failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + + if (remote_user != NULL) { + debug("PAM setting ruser to \"%.200s\"", remote_user); + pam_retval = pam_set_item(pamh, PAM_RUSER, remote_user); + if (pam_retval != PAM_SUCCESS) { + fatal("PAM set ruser failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + } + + pam_retval = pam_acct_mgmt(pamh, 0); + switch (pam_retval) { + case PAM_SUCCESS: + /* This is what we want */ + break; + case PAM_NEW_AUTHTOK_REQD: + pam_msg_cat(NEW_AUTHTOK_MSG); + /* flag that password change is necessary */ + password_change_required = 1; + break; + default: + log("PAM rejected by account configuration[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + return(0); + } + + return(1); +} + +/* Do PAM-specific session initialisation */ +void do_pam_session(char *username, const char *ttyname) +{ + int pam_retval; + + if (ttyname != NULL) { + debug("PAM setting tty to \"%.200s\"", ttyname); + pam_retval = pam_set_item(pamh, PAM_TTY, ttyname); + if (pam_retval != PAM_SUCCESS) { + fatal("PAM set tty failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + } + + debug("do_pam_session: euid %u, uid %u", geteuid(), getuid()); + pam_retval = pam_open_session(pamh, 0); + if (pam_retval != PAM_SUCCESS) { + fatal("PAM session setup failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } +} + +/* Set PAM credentials */ +void do_pam_setcred(void) +{ + int pam_retval; + + debug("PAM establishing creds"); + pam_retval = pam_setcred(pamh, PAM_ESTABLISH_CRED); + if (pam_retval != PAM_SUCCESS) { + fatal("PAM setcred failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } +} + +/* accessor function for file scope static variable */ +int pam_password_change_required(void) +{ + return password_change_required; +} + +/* + * Have user change authentication token if pam_acct_mgmt() indicated + * it was expired. This needs to be called after an interactive + * session is established and the user's pty is connected to + * stdin/stout/stderr. + */ +void do_pam_chauthtok(void) +{ + int pam_retval; + + if (password_change_required) { + pamstate = OTHER; + /* + * XXX: should we really loop forever? + */ + do { + pam_retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (pam_retval != PAM_SUCCESS) { + log("PAM pam_chauthtok failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + } while (pam_retval != PAM_SUCCESS); + } +} + +/* Cleanly shutdown PAM */ +void finish_pam(void) +{ + pam_cleanup_proc(NULL); + fatal_remove_cleanup(&pam_cleanup_proc, NULL); +} + +/* Start PAM authentication for specified account */ +void start_pam(struct passwd *pw) +{ + int pam_retval; + + debug("Starting up PAM with username \"%.200s\"", pw->pw_name); + + pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, &pamh); + + if (pam_retval != PAM_SUCCESS) { + fatal("PAM initialisation failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } + +#ifdef PAM_TTY_KLUDGE + /* + * Some PAM modules (e.g. pam_time) require a TTY to operate, + * and will fail in various stupid ways if they don't get one. + * sshd doesn't set the tty until too late in the auth process and may + * not even need one (for tty-less connections) + * Kludge: Set a fake PAM_TTY + */ + pam_retval = pam_set_item(pamh, PAM_TTY, "ssh"); + if (pam_retval != PAM_SUCCESS) { + fatal("PAM set tty failed[%d]: %.200s", + pam_retval, PAM_STRERROR(pamh, pam_retval)); + } +#endif /* PAM_TTY_KLUDGE */ + + fatal_add_cleanup(&pam_cleanup_proc, NULL); +} + +/* Return list of PAM enviornment strings */ +char **fetch_pam_environment(void) +{ +#ifdef HAVE_PAM_GETENVLIST + return(pam_getenvlist(pamh)); +#else /* HAVE_PAM_GETENVLIST */ + return(NULL); +#endif /* HAVE_PAM_GETENVLIST */ +} + +/* Print any messages that have been generated during authentication */ +/* or account checking to stderr */ +void print_pam_messages(void) +{ + if (pam_msg != NULL) + fputs(pam_msg, stderr); +} + +/* Append a message to the PAM message buffer */ +void pam_msg_cat(const char *msg) +{ + char *p; + size_t new_msg_len; + size_t pam_msg_len; + + new_msg_len = strlen(msg); + + if (pam_msg) { + pam_msg_len = strlen(pam_msg); + pam_msg = xrealloc(pam_msg, new_msg_len + pam_msg_len + 2); + p = pam_msg + pam_msg_len; + } else { + pam_msg = p = xmalloc(new_msg_len + 2); + } + + memcpy(p, msg, new_msg_len); + p[new_msg_len] = '\n'; + p[new_msg_len + 1] = '\0'; +} + +struct inverted_pam_userdata { + /* + * Pipe for telling whether we are doing conversation or sending + * authentication results. + */ + int statefd[2]; + int challengefd[2]; + int responsefd[2]; + + /* Whether we have sent off our challenge */ + int state; +}; + +#define STATE_CONV 1 +#define STATE_AUTH_OK 2 +#define STATE_AUTH_FAIL 3 + +int +ssh_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, + void *userdata) { + int i; + FILE *reader; + char buf[1024]; + struct pam_response *reply = NULL; + char state_to_write = STATE_CONV; /* One char to write */ + struct inverted_pam_userdata *ud = userdata; + char *response = NULL; + + /* The stdio functions are more convenient for the read half */ + reader = fdopen(ud->responsefd[0], "rb"); + if (reader == NULL) + goto protocol_failure; + + reply = malloc(num_msg * sizeof(struct pam_response)); + if (reply == NULL) + return PAM_CONV_ERR; + + if (write(ud->statefd[1], &state_to_write, 1) != 1) + goto protocol_failure; + + /* + * Re-package our data and send it off to our better half (the actual SSH + * process) + */ + if (write(ud->challengefd[1], buf, + sprintf(buf, "%d\n", num_msg)) == -1) + goto protocol_failure; + for (i = 0; i < num_msg; i++) { + if (write(ud->challengefd[1], buf, + sprintf(buf, "%d\n", msg[i]->msg_style)) == -1) + goto protocol_failure; + if (write(ud->challengefd[1], buf, + sprintf(buf, "%d\n", strlen(msg[i]->msg))) == -1) + goto protocol_failure; + if (write(ud->challengefd[1], msg[i]->msg, + strlen(msg[i]->msg)) == -1) + goto protocol_failure; + } + /* + * Read back responses. These may not be as nice as we want, as the SSH + * protocol isn't exactly a perfect fit with PAM. + */ + + for (i = 0; i < num_msg; i++) { + char buf[1024]; + char *endptr; + size_t len; /* Length of the response */ + + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + if (fgets(buf, sizeof(buf), reader) == NULL) + goto protocol_failure; + len = (size_t)strtoul(buf, &endptr, 10); + /* The length is supposed to stand on a line by itself */ + if (endptr == NULL || *endptr != '\n') + goto protocol_failure; + response = malloc(len+1); + if (response == NULL) + goto protocol_failure; + if (fread(response, len, 1, reader) != 1) + goto protocol_failure; + response[len] = '\0'; + reply[i].resp = response; + response = NULL; + break; + default: + reply[i].resp = NULL; + break; + } + } + *resp = reply; + return PAM_SUCCESS; + protocol_failure: + free(reply); + return PAM_CONV_ERR; +} + +void +ipam_free_cookie(struct inverted_pam_cookie *cookie) { + struct inverted_pam_userdata *ud; + int i; + + if (cookie == NULL) + return; + ud = cookie->userdata; + cookie->userdata = NULL; + /* Free userdata if allocated */ + if (ud) { + /* Close any opened file descriptors */ + if (ud->statefd[0] != -1) + close(ud->statefd[0]); + if (ud->statefd[1] != -1) + close(ud->statefd[1]); + if (ud->challengefd[0] != -1) + close(ud->challengefd[0]); + if (ud->challengefd[1] != -1) + close(ud->challengefd[1]); + if (ud->responsefd[0] != -1) + close(ud->responsefd[0]); + if (ud->responsefd[1] != -1) + close(ud->responsefd[1]); + free(ud); + ud = NULL; + } + /* Now free the normal cookie */ + if (cookie->pid != 0 && cookie->pid != -1) { + int status; + + /* XXX Use different signal? */ + kill(cookie->pid, SIGKILL); + waitpid(cookie->pid, &status, 0); + } + for (i = 0; i < cookie->num_msg; i++) { + if (cookie->resp && cookie->resp[i]) { + free(cookie->resp[i]->resp); + free(cookie->resp[i]); + } + if (cookie->msg && cookie->msg[i]) { + free((void *)cookie->msg[i]->msg); + free(cookie->msg[i]); + } + } + free(cookie->msg); + free(cookie->resp); + free(cookie); +} + +/* + * Do first half of PAM authentication - this comes to the point where + * you get a message to send to the user. + */ +struct inverted_pam_cookie * +ipam_start_auth(const char *service, const char *username) { + struct inverted_pam_cookie *cookie; + struct inverted_pam_userdata *ud; + static struct pam_conv conv = { + ssh_conv, + NULL + }; + + cookie = malloc(sizeof(*cookie)); + if (cookie == NULL) + return NULL; + cookie->state = 0; + /* Set up the cookie so ipam_freecookie can be used on it */ + cookie->num_msg = 0; + cookie->msg = NULL; + cookie->resp = NULL; + cookie->pid = -1; + + ud = calloc(sizeof(*ud), 1); + if (ud == NULL) { + free(cookie); + return NULL; + } + cookie->userdata = ud; + ud->statefd[0] = ud->statefd[1] = -1; + ud->challengefd[0] = ud->challengefd[1] = -1; + ud->responsefd[0] = ud->responsefd[1] = -1; + + if (pipe(ud->statefd) != 0) { + ud->statefd[0] = ud->statefd[1] = -1; + ipam_free_cookie(cookie); + return NULL; + } + if (pipe(ud->challengefd) != 0) { + ud->challengefd[0] = ud->challengefd[1] = -1; + ipam_free_cookie(cookie); + return NULL; + } + if (pipe(ud->responsefd) != 0) { + ud->responsefd[0] = ud->responsefd[1] = -1; + ipam_free_cookie(cookie); + return NULL; + } + cookie->pid = fork(); + if (cookie->pid == -1) { + ipam_free_cookie(cookie); + return NULL; + } else if (cookie->pid != 0) { + int num_msgs; /* Number of messages from PAM */ + char *endptr; + char buf[1024]; + FILE *reader; + size_t num_msg; + int i; + char state; /* Which state did the connection just enter? */ + + /* We are the parent - wait for a call to the communications + function to turn up, or the challenge to be finished */ + if (read(ud->statefd[0], &state, 1) != 1) { + ipam_free_cookie(cookie); + return NULL; + } + cookie->state = state; + switch (state) { + case STATE_CONV: + /* We are running the conversation function */ + /* The stdio functions are more convenient for read */ + reader = fdopen(ud->challengefd[0], "r"); + if (reader == NULL) { + ipam_free_cookie(cookie); + return NULL; + } + if (fgets(buf, 4, reader) == NULL) { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + num_msg = (size_t)strtoul(buf, &endptr, 10); + /* The length is supposed to stand on a line by itself */ + if (endptr == NULL || *endptr != '\n') { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + cookie->msg = + malloc(sizeof(struct pam_message *) * num_msg); + cookie->resp = + malloc(sizeof(struct pam_response *) * num_msg); + if (cookie->msg == NULL || cookie->resp == NULL) { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + for (i = 0; i < num_msg; i++) { + cookie->msg[i] = + malloc(sizeof(struct pam_message)); + cookie->resp[i] = + malloc(sizeof(struct pam_response)); + if (cookie->msg[i] == NULL || + cookie->resp[i] == NULL) { + for (;;) { + free(cookie->msg[i]); + free(cookie->resp[i]); + if (i == 0) + break; + i--; + } + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + cookie->msg[i]->msg = NULL; + cookie->resp[i]->resp = NULL; + cookie->resp[i]->resp_retcode = 0; + } + /* Set up so the above will be freed on failure */ + cookie->num_msg = num_msg; + /* + * We have a an allocated response and message for + * each of the entries in the PAM structure - transfer + * the data sent to the conversation function over. + */ + for (i = 0; i < num_msg; i++) { + size_t len; + + if (fgets(buf, sizeof(buf), reader) == NULL) { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + cookie->msg[i]->msg_style = + (size_t)strtoul(buf, &endptr, 10); + if (endptr == NULL || *endptr != '\n') { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + if (fgets(buf, sizeof(buf), reader) == NULL) { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + len = (size_t)strtoul(buf, &endptr, 10); + if (endptr == NULL || *endptr != '\n') { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + cookie->msg[i]->msg = malloc(len + 1); + if (cookie->msg[i]->msg == NULL) { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + if (fread((char *)cookie->msg[i]->msg, len, 1, reader) != + 1) { + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + *(char *)&(cookie->msg[i]->msg[len]) = '\0'; + } + break; + case STATE_AUTH_OK: + case STATE_AUTH_FAIL: + break; + default: + /* Internal failure, somehow */ + fclose(reader); + ipam_free_cookie(cookie); + return NULL; + } + return cookie; + } else { + /* We are the child */ + pam_handle_t *pamh=NULL; + int retval; + char state; + + conv.appdata_ptr = ud; + retval = pam_start(service, username, &conv, &pamh); + /* Is user really user? */ + if (retval == PAM_SUCCESS) + retval = pam_authenticate(pamh, 0); + /* permitted access? */ + if (retval == PAM_SUCCESS) + retval = pam_acct_mgmt(pamh, 0); + /* This is where we have been authorized or not. */ + + /* Be conservative - flag as auth failure if we can't close */ + /* + * XXX This is based on example code from Linux-PAM - + * but can it really be correct to pam_end if + * pam_start failed? + */ + if (pam_end(pamh, retval) != PAM_SUCCESS) + retval = PAM_AUTH_ERR; + + /* Message to parent */ + state = retval == PAM_SUCCESS ? STATE_AUTH_OK : STATE_AUTH_FAIL; + if (write(ud->statefd[1], &state, 1) != 1) { + _exit(1); + } + /* FDs will be closed, so further communication will stop */ + _exit(0); + } +} + +/* + * Do second half of PAM authentication - cookie should now be filled + * in with the response to the challenge. + */ + +int +ipam_complete_auth(struct inverted_pam_cookie *cookie) { + int i; + char buf[1024]; + struct inverted_pam_userdata *ud = cookie->userdata; + char state; + + /* Send over our responses */ + for (i = 0; i < cookie->num_msg; i++) { + if (cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_ON && + cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) + continue; + if (write(ud->responsefd[1], buf, + sprintf(buf, "%d\n", strlen(cookie->resp[i]->resp))) == -1) { + ipam_free_cookie(cookie); + return 0; + } + if (write(ud->responsefd[1], cookie->resp[i]->resp, + strlen(cookie->resp[i]->resp)) == -1) { + ipam_free_cookie(cookie); + return 0; + } + } + /* Find out what state we are changing to */ + if (read(ud->statefd[0], &state, 1) != 1) { + ipam_free_cookie(cookie); + return 0; + } + + return state == STATE_AUTH_OK ? 1 : 0; +} + +#endif /* USE_PAM */ diff --git a/crypto/openssh/auth-pam.h b/crypto/openssh/auth-pam.h new file mode 100644 index 0000000..194f7e8 --- /dev/null +++ b/crypto/openssh/auth-pam.h @@ -0,0 +1,38 @@ +/* + * OpenSSH PAM authentication support. + * + * $FreeBSD$ + */ +#ifndef AUTH_PAM_H +#define AUTH_PAM_H +#include "includes.h" +#ifdef USE_PAM + +#include <pwd.h> /* For struct passwd */ + +void start_pam(struct passwd *pw); +void finish_pam(void); +int auth_pam_password(struct passwd *pw, const char *password); +char **fetch_pam_environment(void); +int do_pam_account(char *username, char *remote_user); +void do_pam_session(char *username, const char *ttyname); +void do_pam_setcred(void); +void print_pam_messages(void); +int pam_password_change_required(void); +void do_pam_chauthtok(void); + +struct inverted_pam_cookie { + int state; /* Which state have we reached? */ + pid_t pid; /* PID of child process */ + + /* Only valid in state STATE_CONV */ + int num_msg; /* Number of messages */ + struct pam_message **msg; /* Message structures */ + struct pam_response **resp; /* Response structures */ + struct inverted_pam_userdata *userdata; +}; +void ipam_free_cookie(struct inverted_pam_cookie *cookie); +struct inverted_pam_cookie *ipam_start_auth(const char *, const char *); + +#endif /* USE_PAM */ +#endif /* AUTH_PAM_H */ |