diff options
-rw-r--r-- | include/libssh/auth.h | 21 | ||||
-rw-r--r-- | include/libssh/libssh.h | 2 | ||||
-rw-r--r-- | include/libssh/messages.h | 1 | ||||
-rw-r--r-- | include/libssh/server.h | 5 | ||||
-rw-r--r-- | include/libssh/session.h | 1 | ||||
-rw-r--r-- | src/auth.c | 60 | ||||
-rw-r--r-- | src/messages.c | 155 | ||||
-rw-r--r-- | src/packet.c | 2 | ||||
-rw-r--r-- | src/server.c | 133 |
9 files changed, 363 insertions, 17 deletions
diff --git a/include/libssh/auth.h b/include/libssh/auth.h index 343b3091..c2fba2af 100644 --- a/include/libssh/auth.h +++ b/include/libssh/auth.h @@ -29,6 +29,27 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); SSH_PACKET_CALLBACK(ssh_packet_userauth_success); SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok); SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response); + +/** @internal + * kdbint structure must be shared with message.c + * and server.c + */ +struct ssh_kbdint_struct { + uint32_t nprompts; + uint32_t nanswers; + char *name; + char *instruction; + char **prompts; + unsigned char *echo; /* bool array */ + char **answers; +}; +typedef struct ssh_kbdint_struct* ssh_kbdint; + +ssh_kbdint kbdint_new(void); +void kbdint_clean(ssh_kbdint kbd); +void kbdint_free(ssh_kbdint kbd); + #ifdef WITH_SSH1 void ssh_auth1_handler(ssh_session session, uint8_t type); diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index a215c31e..5430786b 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -446,6 +446,8 @@ LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, const char *answer); LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); diff --git a/include/libssh/messages.h b/include/libssh/messages.h index 419c3cbb..5001fbe0 100644 --- a/include/libssh/messages.h +++ b/include/libssh/messages.h @@ -30,6 +30,7 @@ struct ssh_auth_request { char *password; struct ssh_public_key_struct *public_key; char signature_state; + char kbdint_response; }; struct ssh_channel_request_open { diff --git a/include/libssh/server.h b/include/libssh/server.h index cd334c1d..8f67d26d 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -173,6 +173,7 @@ LIBSSH_API int ssh_message_reply_default(ssh_message msg); LIBSSH_API char *ssh_message_auth_user(ssh_message msg); LIBSSH_API char *ssh_message_auth_password(ssh_message msg); LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); @@ -180,6 +181,10 @@ LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); LIBSSH_API char *ssh_message_service_service(ssh_message msg); diff --git a/include/libssh/session.h b/include/libssh/session.h index 58df0d58..f6ad8c67 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -27,7 +27,6 @@ #include "libssh/auth.h" #include "libssh/channels.h" #include "libssh/poll.h" -typedef struct ssh_kbdint_struct* ssh_kbdint; /* These are the different states a SSH session can be into its life */ enum ssh_session_state_e { @@ -1275,16 +1275,7 @@ int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) { return SSH_AUTH_DENIED; } -struct ssh_kbdint_struct { - uint32_t nprompts; - char *name; - char *instruction; - char **prompts; - unsigned char *echo; /* bool array */ - char **answers; -}; - -static ssh_kbdint kbdint_new(void) { +ssh_kbdint kbdint_new(void) { ssh_kbdint kbd; kbd = malloc(sizeof (struct ssh_kbdint_struct)); @@ -1297,19 +1288,19 @@ static ssh_kbdint kbdint_new(void) { } -static void kbdint_free(ssh_kbdint kbd) { +void kbdint_free(ssh_kbdint kbd) { int i, n; if (kbd == NULL) { return; } - n = kbd->nprompts; SAFE_FREE(kbd->name); SAFE_FREE(kbd->instruction); SAFE_FREE(kbd->echo); + n = kbd->nprompts; if (kbd->prompts) { for (i = 0; i < n; i++) { BURN_STRING(kbd->prompts[i]); @@ -1317,6 +1308,8 @@ static void kbdint_free(ssh_kbdint kbd) { } SAFE_FREE(kbd->prompts); } + + n = kbd->nanswers; if (kbd->answers) { for (i = 0; i < n; i++) { BURN_STRING(kbd->answers[i]); @@ -1328,19 +1321,18 @@ static void kbdint_free(ssh_kbdint kbd) { SAFE_FREE(kbd); } -static void kbdint_clean(ssh_kbdint kbd) { +void kbdint_clean(ssh_kbdint kbd) { int i, n; if (kbd == NULL) { return; } - n = kbd->nprompts; - SAFE_FREE(kbd->name); SAFE_FREE(kbd->instruction); SAFE_FREE(kbd->echo); + n = kbd->nprompts; if (kbd->prompts) { for (i = 0; i < n; i++) { BURN_STRING(kbd->prompts[i]); @@ -1349,6 +1341,8 @@ static void kbdint_clean(ssh_kbdint kbd) { SAFE_FREE(kbd->prompts); } + n = kbd->nanswers; + if (kbd->answers) { for (i = 0; i < n; i++) { BURN_STRING(kbd->answers[i]); @@ -1358,6 +1352,7 @@ static void kbdint_clean(ssh_kbdint kbd) { } kbd->nprompts = 0; + kbd->nanswers = 0; } /* this function sends the first packet as explained in section 3.1 @@ -1535,6 +1530,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { ssh_string_free(tmp); if (session->kbdint->prompts[i] == NULL) { ssh_set_error_oom(session); + session->kbdint->nprompts = i; kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); @@ -1757,6 +1753,40 @@ const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, return session->kbdint->prompts[i]; } +#ifdef WITH_SERVER +/** + * @brief Get the number of answers the client has given. + * + * @param[in] session The ssh session to use. + * + * @returns The number of answers. + */ +int ssh_userauth_kbdint_getnanswers(ssh_session session) { + if(session==NULL || session->kbdint == NULL) + return SSH_ERROR; + return session->kbdint->nanswers; +} + +/** + * @brief Get the answer for a question from a message block. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith answer. + * + * @return 0 on success, < 0 on error. + */ +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { + if(session==NULL || session->kbdint == NULL) + return NULL; + if (i > session->kbdint->nanswers) { + return NULL; + } + + return session->kbdint->answers[i]; +} +#endif + /** * @brief Set the answer for a question from a message block. * diff --git a/src/messages.c b/src/messages.c index 48e1acb5..64daf7f3 100644 --- a/src/messages.c +++ b/src/messages.c @@ -336,6 +336,38 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ goto end; } + if (strncmp(method_c, "keyboard-interactive", method_size) == 0) { + ssh_string lang = NULL; + ssh_string submethods = NULL; + + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + SAFE_FREE(service_c); + SAFE_FREE(method_c); + lang = buffer_get_ssh_string(packet); + if (lang == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "The language tag is deprecated and SHOULD be the empty string." + */ + ssh_string_free(lang); + + submethods = buffer_get_ssh_string(packet); + if (submethods == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "One possible implementation strategy of the submethods field on the + * server is that, unless the user may use multiple different + * submethods, the server ignores this field." + */ + ssh_string_free(submethods); + + goto end; + } + if (strncmp(method_c, "publickey", method_size) == 0) { ssh_string algo = NULL; ssh_string publickey = NULL; @@ -432,6 +464,129 @@ end: return SSH_PACKET_USED; } +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_INFO_RESPONSE packet and queue a + * SSH Message + */ +#ifndef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + (void)session; + (void)type; + (void)packet; + (void)user; + return SSH_PACKET_USED; +} +#else +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + uint32_t nanswers; + uint32_t i; + ssh_string tmp; + + ssh_message msg = NULL; + + enter_function(); + + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* HACK: we forge a message to be able to handle it in the + * same switch() as other auth methods */ + msg->type = SSH_REQUEST_AUTH; + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + msg->auth_request.kbdint_response = 1; +#if 0 // should we wipe the username ? + msg->auth_request.username = NULL; +#endif + + buffer_get_u32(packet, &nanswers); + + if (session->kbdint == NULL) { + ssh_log(session, SSH_LOG_PROTOCOL, "Warning: Got a keyboard-interactive " + "response but it seems we didn't send the request."); + + session->kbdint = kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + + leave_function(); + return SSH_PACKET_USED; + } + } + + nanswers = ntohl(nanswers); + ssh_log(session,SSH_LOG_PACKET,"kbdint: %d answers",nanswers); + if (nanswers > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much answers received from client: %u (0x%.4x)", + nanswers, nanswers); + kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + + if(nanswers != session->kbdint->nprompts) { + /* warn but let the application handle this case */ + ssh_log(session, SSH_LOG_PROTOCOL, "Warning: Number of prompts and answers" + " mismatch: p=%u a=%u", session->kbdint->nprompts, nanswers); + } + session->kbdint->nanswers = nanswers; + session->kbdint->answers = malloc(nanswers * sizeof(char *)); + if (session->kbdint->answers == NULL) { + session->kbdint->nanswers = 0; + ssh_set_error_oom(session); + kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + memset(session->kbdint->answers, 0, nanswers * sizeof(char *)); + + for (i = 0; i < nanswers; i++) { + tmp = buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_set_error(session, SSH_FATAL, "Short INFO_RESPONSE packet"); + session->kbdint->nanswers = i; + kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + session->kbdint->answers[i] = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + session->kbdint->nanswers = i; + kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + } + + goto end; + +error: + ssh_message_free(msg); + + leave_function(); + return SSH_PACKET_USED; + +end: + ssh_message_queue(session,msg); + leave_function(); + return SSH_PACKET_USED; +} +#endif + SSH_PACKET_CALLBACK(ssh_packet_channel_open){ ssh_message msg = NULL; ssh_string type_s = NULL, originator = NULL, destination = NULL; diff --git a/src/packet.c b/src/packet.c index 52e1ef01..99760dff 100644 --- a/src/packet.c +++ b/src/packet.c @@ -81,7 +81,7 @@ ssh_packet_callback default_packet_handlers[]= { ssh_packet_userauth_pk_ok, // SSH2_MSG_USERAUTH_PK_OK 60 // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 // SSH2_MSG_USERAUTH_INFO_REQUEST 60 - NULL, // SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + ssh_packet_userauth_info_response, // SSH2_MSG_USERAUTH_INFO_RESPONSE 61 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 62-79 diff --git a/src/server.c b/src/server.c index e4ecbe1e..86c4b84e 100644 --- a/src/server.c +++ b/src/server.c @@ -747,6 +747,14 @@ enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ return msg->auth_request.signature_state; } +int ssh_message_auth_kbdint_is_response(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->auth_request.kbdint_response != 0; +} + int ssh_message_auth_set_methods(ssh_message msg, int methods) { if (msg == NULL || msg->session == NULL) { return -1; @@ -757,6 +765,131 @@ int ssh_message_auth_set_methods(ssh_message msg, int methods) { return 0; } +int ssh_message_auth_interactive_request(ssh_message msg, const char *name, + const char *instruction, unsigned int num_prompts, + const char **prompts, char *echo) { + int r; + unsigned int i = 0; + ssh_string tmp = NULL; + + if(name == NULL || instruction == NULL) { + return SSH_ERROR; + } + if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { + return SSH_ERROR; + } + + if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_USERAUTH_INFO_REQUEST) < 0) { + return SSH_ERROR; + } + + /* name */ + tmp = ssh_string_from_char(name); + if (buffer_add_ssh_string(msg->session->out_buffer, tmp) < 0) { + return SSH_ERROR; + } + ssh_string_free(tmp); tmp = NULL; + + /* instruction */ + tmp = ssh_string_from_char(instruction); + if (buffer_add_ssh_string(msg->session->out_buffer, tmp) < 0) { + return SSH_ERROR; + } + ssh_string_free(tmp); tmp = NULL; + + /* language tag */ + tmp = ssh_string_from_char(""); + if (buffer_add_ssh_string(msg->session->out_buffer, tmp) < 0) { + return SSH_ERROR; + } + ssh_string_free(tmp); tmp = NULL; + + /* num prompts */ + if (buffer_add_u32(msg->session->out_buffer, ntohl(num_prompts)) < 0) { + return SSH_ERROR; + } + + for(i = 0; i < num_prompts; i++) { + /* prompt[i] */ + tmp = ssh_string_from_char(prompts[i]); + if (buffer_add_ssh_string(msg->session->out_buffer, tmp) < 0) { + goto error; + } + ssh_string_free(tmp); tmp = NULL; + + /* echo[i] */ + if (buffer_add_u8(msg->session->out_buffer, echo[i]) < 0) { + return SSH_ERROR; + } + } + + r = packet_send(msg->session); + + /* fill in the kbdint structure */ + if (msg->session->kbdint == NULL) { + ssh_log(msg->session, SSH_LOG_PROTOCOL, "Warning: Got a " + "keyboard-interactive response but it " + "seems we didn't send the request."); + + msg->session->kbdint = kbdint_new(); + if (msg->session->kbdint == NULL) { + ssh_set_error_oom(msg->session); + + return SSH_ERROR; + } + } else { + kbdint_clean(msg->session->kbdint); + } + + msg->session->kbdint->name = strdup(name); + if(msg->session->kbdint->name == NULL) { + ssh_set_error_oom(msg->session); + kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + msg->session->kbdint->instruction = strdup(instruction); + if(msg->session->kbdint->instruction == NULL) { + ssh_set_error_oom(msg->session); + kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + + msg->session->kbdint->nprompts = num_prompts; + msg->session->kbdint->prompts = malloc(num_prompts * sizeof(char *)); + if (msg->session->kbdint->prompts == NULL) { + msg->session->kbdint->nprompts = 0; + ssh_set_error_oom(msg->session); + kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + msg->session->kbdint->echo = malloc(num_prompts * sizeof(char)); + if (msg->session->kbdint->echo == NULL) { + ssh_set_error_oom(msg->session); + kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + for (i = 0; i < num_prompts; i++) { + msg->session->kbdint->echo[i] = echo[i]; + msg->session->kbdint->prompts[i] = strdup(prompts[i]); + if (msg->session->kbdint->prompts[i] == NULL) { + ssh_set_error_oom(msg->session); + msg->session->kbdint->nprompts = i; + kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + } + + return r; +error: + if(tmp) ssh_string_free(tmp); + return SSH_ERROR; +} + int ssh_message_auth_reply_success(ssh_message msg, int partial) { int r; |