diff --git a/examples/ssh_server.c b/examples/ssh_server.c index 532972ac..fb1541d9 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -631,6 +631,58 @@ auth_publickey(ssh_session session, return SSH_AUTH_DENIED; } +static int kbdint_check_response(ssh_session session) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp("omnitrix", answer); + if (cmp != 0) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp("000", answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Most powerful weapon in the galaxy"; + const char *prompts[2] = {"Name of the weapon: ", "Destruct Code: "}; + char echo[] = {1, 0}; + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; + } +} + static ssh_channel channel_open(ssh_session session, void *userdata) { @@ -720,14 +772,15 @@ handle_session(ssh_event event, ssh_session session) struct ssh_server_callbacks_struct server_cb = { .userdata = &sdata, .auth_password_function = auth_password, + .auth_kbdint_function = auth_kbdint, .channel_open_request_session_function = channel_open, }; if (authorizedkeys[0]) { server_cb.auth_pubkey_function = auth_publickey; - ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY); + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_INTERACTIVE); } else - ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_INTERACTIVE); ssh_callbacks_init(&server_cb); ssh_callbacks_init(&channel_cb); diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h index 13646aff..e4ecf726 100644 --- a/include/libssh/callbacks.h +++ b/include/libssh/callbacks.h @@ -276,6 +276,18 @@ typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *us typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, char signature_state, void *userdata); +/** + * @brief SSH authentication callback. Tries to authenticates user with the "keyboard-interactive" method + * @param message Current message + * @param session Current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_INFO More info required for authentication. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. +*/ +typedef int (*ssh_auth_kbdint_callback) (ssh_message message, ssh_session session, void *userdata); + /** * @brief Handles an SSH service request * @param session current session handler @@ -418,6 +430,12 @@ struct ssh_server_callbacks_struct { */ ssh_channel_open_request_direct_tcpip_callback channel_open_request_direct_tcpip_function; + + /** This function gets called when a client tries to authenticate through + * keyboard interactive method. + */ + ssh_auth_kbdint_callback auth_kbdint_function; + }; typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; diff --git a/src/messages.c b/src/messages.c index bebd4434..824b2772 100644 --- a/src/messages.c +++ b/src/messages.c @@ -184,6 +184,19 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) ssh_message_reply_default(msg); } + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_INTERACTIVE && + ssh_callbacks_exists(session->server_callbacks, auth_kbdint_function)) { + rc = session->server_callbacks->auth_kbdint_function(msg, + session, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else if (rc == SSH_AUTH_INFO) { + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } return SSH_OK; } break; diff --git a/tests/server/test_server/default_cb.c b/tests/server/test_server/default_cb.c index a6df448c..10426f33 100644 --- a/tests/server/test_server/default_cb.c +++ b/tests/server/test_server/default_cb.c @@ -172,6 +172,65 @@ null_userdata: return SSH_AUTH_DENIED; } +static int kbdint_check_response(ssh_session session, struct session_data_st *sdata) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp(sdata->username, answer); + if (cmp != 0) { + return 0; + } + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp(sdata->password, answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint_cb(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Get yourself authenticated"; + const char *prompts[2] = {"Username: ", "Password: "}; + char echo[] = {1, 0}; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + return SSH_AUTH_DENIED; + } + + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session, sdata)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + + return SSH_AUTH_DENIED; +} + #if WITH_GSSAPI int auth_gssapi_mic_cb(ssh_session session, UNUSED_PARAM(const char *user), @@ -783,6 +842,7 @@ struct ssh_server_callbacks_struct *get_default_server_cb(void) cb->auth_password_function = auth_password_cb; cb->auth_pubkey_function = auth_pubkey_cb; cb->channel_open_request_session_function = channel_new_session_cb; + cb->auth_kbdint_function = auth_kbdint_cb; #if WITH_GSSAPI cb->auth_gssapi_mic_function = auth_gssapi_mic_cb; #endif @@ -912,7 +972,8 @@ void default_handle_session_cb(ssh_event event, } else { ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | - SSH_AUTH_METHOD_PUBLICKEY| + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | SSH_AUTH_METHOD_GSSAPI_MIC); } diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c index e47ae231..b0f9e2c2 100644 --- a/tests/server/test_server/main.c +++ b/tests/server/test_server/main.c @@ -276,6 +276,7 @@ static int init_server_state(struct server_state_st *state, } else { state->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | SSH_AUTH_METHOD_GSSAPI_MIC; } diff --git a/tests/server/torture_server_auth_kbdint.c b/tests/server/torture_server_auth_kbdint.c index 1d5eb733..9beb7289 100644 --- a/tests/server/torture_server_auth_kbdint.c +++ b/tests/server/torture_server_auth_kbdint.c @@ -375,6 +375,22 @@ static void handle_kbdint_session_cb(ssh_event event, goto end; } + /* + * This test was written prior to adding the kbdint callback + * for the server. Hence, here the server uses the + * ssh_message_callback for kbdint authentication, + * instead of the kbdint callback. + * + * Setting the kbdint callback as NULL ensures that the + * default kbdint callback for test_server doesn't get used + * for kbdint authentication. + * + * The test for kbdint callback based authentication has + * been added in torture_server.c, libssh keeps this test to + * test the old way of doing kbdint authentication using + * ssh_message_callback. + */ + server_cb->auth_kbdint_function = NULL; server_cb->userdata = &sdata; /* This is a macro, it does not return a value */ diff --git a/tests/server/torture_server_default.c b/tests/server/torture_server_default.c index c6e47d6a..a3c3d9da 100644 --- a/tests/server/torture_server_default.c +++ b/tests/server/torture_server_default.c @@ -287,6 +287,76 @@ static void torture_server_auth_pubkey(void **state) assert_int_equal(rc, SSH_AUTH_SUCCESS); } +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, "wrongpassword"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong username */ + rc = ssh_userauth_kbdint_setanswer(session, 0, "wrongusername"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing the right password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + static void torture_server_hostkey_mismatch(void **state) { struct test_server_st *tss = *state; @@ -551,6 +621,9 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_auth_pubkey, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_server_hostkey_mismatch, session_setup, session_teardown),