From da653774aa57fc234d978cf03d3b38d674b00e12 Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Sat, 11 Jun 2005 19:18:06 +0000 Subject: [PATCH] Add keyboard interactive authentication. Implementation contributed by Mikhail Gusarov. --- README | 2 + include/libssh2.h | 27 ++++++ include/libssh2_priv.h | 3 + src/userauth.c | 191 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 223 insertions(+) diff --git a/README b/README index 731141f2..aada6b02 100644 --- a/README +++ b/README @@ -4,6 +4,8 @@ libssh2 - SSH2 library Version 0.11 ------------ + Added libssh2_userauth_keyboard_interactive_ex() -- Code contributed by Mikhail Gusarov + Added libssh2_channel_receive_window_adjust() to be able to increase the size of the receive window. Added queueing for small window_adjust packets to avoid unnecessary packet conversation. diff --git a/include/libssh2.h b/include/libssh2.h index c37b295a..0b610fa2 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -107,6 +107,22 @@ typedef long long libssh2_int64_t; #define LIBSSH2_REALLOC_FUNC(name) void *name(void *ptr, size_t count, void **abstract) #define LIBSSH2_FREE_FUNC(name) void name(void *ptr, void **abstract) +typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT +{ + char* text; + unsigned int length; + unsigned char echo; +} LIBSSH2_USERAUTH_KBDINT_PROMPT; + +typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE +{ + char* text; + unsigned int length; +} LIBSSH2_USERAUTH_KBDINT_RESPONSE; + +/* 'keyboard-interactive' authentication callback */ +#define LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC(name_) void name_(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses) + /* Callbacks for special SSH packets */ #define LIBSSH2_IGNORE_FUNC(name) void name(LIBSSH2_SESSION *session, const char *message, int message_len, void **abstract) #define LIBSSH2_DEBUG_FUNC(name) void name(LIBSSH2_SESSION *session, int always_display, const char *message, int message_len, const char *language, int language_len,void **abstract) @@ -274,6 +290,17 @@ LIBSSH2_API int libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, #define libssh2_userauth_hostbased_fromfile(session, username, publickey, privatekey, passphrase, hostname) \ libssh2_userauth_hostbased_fromfile_ex((session), (username), strlen(username), (publickey), (privatekey), (passphrase), (hostname), strlen(hostname), (username), strlen(username)) +/* + * response_callback is provided with filled by library prompts array, + * but client must allocate and fill individual responses. Responses + * array is already allocated. Responses data will be freed by libssh2 + * after callback return, but before subsequent callback invokation. + */ +LIBSSH2_API int libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION* session, const char* username, int username_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))); +#define libssh2_userauth_keyboard_interactive(session, username, response_callback) \ + libssh2_userauth_keyboard_interactive_ex((session), (username), strlen(username), (response_callback)) + LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, long timeout); /* Channel API */ diff --git a/include/libssh2_priv.h b/include/libssh2_priv.h index 29e1a6f8..cc414a4a 100644 --- a/include/libssh2_priv.h +++ b/include/libssh2_priv.h @@ -407,6 +407,9 @@ void _libssh2_debug(LIBSSH2_SESSION *session, int context, const char *format, . #define SSH_MSG_USERAUTH_PK_OK 60 /* "password" method */ #define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +/* "keyboard-interactive" method */ +#define SSH_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH_MSG_USERAUTH_INFO_RESPONSE 61 /* Channels */ #define SSH_MSG_GLOBAL_REQUEST 80 diff --git a/src/userauth.c b/src/userauth.c index b9a24ba7..a6e89255 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -652,3 +652,194 @@ LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, return -1; } /* }}} */ + +/* {{{ libssh2_userauth_keyboard_interactive + * Authenticate using a challenge-response authentication + */ +LIBSSH2_API int libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, const char *username, int username_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))) +{ + unsigned char *s, *data; /* packet */ + unsigned long packet_len; + + packet_len = 1 /* byte SSH_MSG_USERAUTH_REQUEST */ + + 4 + username_len /* string user name (ISO-10646 UTF-8, as defined in [RFC-3629]) */ + + 4 + 14 /* string service name (US-ASCII) */ + + 4 + 20 /* string "keyboard-interactive" (US-ASCII) */ + + 4 + 0 /* string language tag (as defined in [RFC-3066]) */ + + 4 + 0 /* string submethods (ISO-10646 UTF-8) */ + ; + + if (!(data = s = LIBSSH2_ALLOC(session, packet_len))) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive authentication", 0); + return -1; + } + + *s++ = SSH_MSG_USERAUTH_REQUEST; + + /* user name */ + libssh2_htonu32(s, username_len); s += 4; + memcpy(s, username, username_len); s += username_len; + + /* service name */ + libssh2_htonu32(s, sizeof("ssh-connection")); s += 4; + memcpy(s, "ssh-connection", sizeof("ssh-connection")); s += sizeof("ssh-connection"); + + /* "keyboard-interactive" */ + libssh2_htonu32(s, sizeof("keyboard-interactive")); s += 4; + memcpy(s, "keyboard-interactive", sizeof("keyboard-interactive")); s += sizeof("keyboard-interactive"); + + /* language tag */ + libssh2_htonu32(s, 0); s += 4; + + /* submethods */ + libssh2_htonu32(s, 0); s += 4; + + if (libssh2_packet_write(session, data, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send keyboard-interactive request", 0); + LIBSSH2_FREE(session, data); + return -1; + } + LIBSSH2_FREE(session, data); + + for (;;) { + unsigned char reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_INFO_REQUEST, 0 }; + unsigned int auth_name_len; + char* auth_name = NULL; + unsigned auth_instruction_len; + char* auth_instruction = NULL; + unsigned int language_tag_len; + unsigned long data_len; + unsigned int num_prompts = 0; + unsigned int i; + int auth_failure = 1; + LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts = NULL; + LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses = NULL; + + if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { + return -1; + } + + if (data[0] == SSH_MSG_USERAUTH_SUCCESS) { + LIBSSH2_FREE(session, data); + session->state |= LIBSSH2_STATE_AUTHENTICATED; + return 0; + } + + if (data[0] == SSH_MSG_USERAUTH_FAILURE) { + LIBSSH2_FREE(session, data); + return -1; + } + + /* server requested PAM-like conversation */ + + s = data + 1; + + /* string name (ISO-10646 UTF-8) */ + auth_name_len = libssh2_ntohu32(s); s += 4; + if (!(auth_name = LIBSSH2_ALLOC(session, auth_name_len))) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive 'name' request field", 0); + goto cleanup; + } + memcpy(auth_name, s, auth_name_len); s += auth_name_len; + + /* string instruction (ISO-10646 UTF-8) */ + auth_instruction_len = libssh2_ntohu32(s); s += 4; + if (!(auth_instruction = LIBSSH2_ALLOC(session, auth_instruction_len))) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive 'instruction' request field", 0); + goto cleanup; + } + memcpy(auth_instruction, s, auth_instruction_len); s += auth_instruction_len; + + /* string language tag (as defined in [RFC-3066]) */ + language_tag_len = libssh2_ntohu32(s); s += 4; + /* ignoring this field as deprecated */ s += language_tag_len; + + /* int num-prompts */ + num_prompts = libssh2_ntohu32(s); s += 4; + + prompts = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * num_prompts); + if (!prompts) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive prompts array", 0); + goto cleanup; + } + memset(prompts, 0, sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * num_prompts); + + responses = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * num_prompts); + if (!responses) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive responses array", 0); + goto cleanup; + } + memset(responses, 0, sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * num_prompts); + + for(i = 0; i != num_prompts; ++i) { + /* string prompt[1] (ISO-10646 UTF-8) */ + prompts[i].length = libssh2_ntohu32(s); s += 4; + if (!(prompts[i].text = LIBSSH2_ALLOC(session, prompts[i].length))) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive prompt message", 0); + goto cleanup; + } + memcpy(prompts[i].text, s, prompts[i].length); s += prompts[i].length; + + /* boolean echo[1] */ + prompts[i].echo = *s++; + } + + response_callback(auth_name, auth_name_len, auth_instruction, auth_instruction_len, num_prompts, prompts, responses); + + packet_len = 1 /* byte SSH_MSG_USERAUTH_INFO_RESPONSE */ + + 4 /* int num-responses */ + ; + + for (i = 0; i != num_prompts; ++i) { + packet_len += 4 + responses[i].length; /* string response[1] (ISO-10646 UTF-8) */ + } + + if (!(data = s = LIBSSH2_ALLOC(session, packet_len))) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive response packet", 0); + goto cleanup; + } + + *s = SSH_MSG_USERAUTH_INFO_RESPONSE; s++; + libssh2_htonu32(s, num_prompts); s += 4; + + for (i = 0; i != num_prompts; ++i) { + libssh2_htonu32(s, responses[i].length); s += 4; + memcpy(s, responses[i].text, responses[i].length); s += responses[i].length; + } + + if (libssh2_packet_write(session, data, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-keyboard-interactive request", 0); + goto cleanup; + } + + auth_failure = 0; + + cleanup: + /* It's safe to clean all the data here, because unallocated pointers + * are filled by zeroes + */ + + LIBSSH2_FREE(session, data); + + if (prompts) { + for (i = 0; i != num_prompts; ++i) { + LIBSSH2_FREE(session, prompts[i].text); + } + } + + if (responses) { + for (i = 0; i != num_prompts; ++i) { + LIBSSH2_FREE(session, responses[i].text); + } + } + + LIBSSH2_FREE(session, prompts); + LIBSSH2_FREE(session, responses); + + if (auth_failure) { + return -1; + } + } +} +/* }}} */