/** * @brief Connect to the ssh server. * * @param[in] session The ssh session to connect. * * @returns SSH_OK on success, SSH_ERROR on error. * @returns SSH_AGAIN, if the session is in nonblocking mode, * and call must be done again. * * @see ssh_new() * @see ssh_disconnect() */ int ssh_connect(ssh_session session) { int ret; if (session == NULL) { ssh_set_error(session, SSH_FATAL, "Invalid session pointer"); return SSH_ERROR; } enter_function(); switch(session->pending_call_state){ case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_CONNECT: goto pending; default: ssh_set_error(session,SSH_FATAL,"Bad call during pending SSH call in ssh_connect"); leave_function(); return SSH_ERROR; } session->alive = 0; session->client = 1; if (ssh_init() < 0) { leave_function(); return SSH_ERROR; } if (session->fd == SSH_INVALID_SOCKET && session->host == NULL && session->ProxyCommand == NULL) { ssh_set_error(session, SSH_FATAL, "Hostname required"); leave_function(); return SSH_ERROR; } ret = ssh_options_apply(session); if (ret < 0) { ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); leave_function(); return SSH_ERROR; } ssh_log(session,SSH_LOG_RARE,"libssh %s, using threading %s", ssh_copyright(), ssh_threads_get_type()); session->ssh_connection_callback = ssh_client_connection_callback; session->session_state=SSH_SESSION_STATE_CONNECTING; ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); session->socket_callbacks.connected=socket_callback_connected; session->socket_callbacks.data=callback_receive_banner; session->socket_callbacks.exception=ssh_socket_exception_callback; session->socket_callbacks.userdata=session; if (session->fd != SSH_INVALID_SOCKET) { ssh_socket_set_fd(session->socket, session->fd); ret=SSH_OK; #ifndef _WIN32 } else if (session->ProxyCommand != NULL){ ret=ssh_socket_connect_proxycommand(session->socket, session->ProxyCommand); #endif } else { ret=ssh_socket_connect(session->socket, session->host, session->port, session->bindaddr); /*, session->timeout * 1000 + session->timeout_usec); */ } if (ret == SSH_ERROR) { leave_function(); return SSH_ERROR; } set_status(session, 0.2f); session->alive = 1; ssh_log(session,SSH_LOG_PROTOCOL,"Socket connecting, now waiting for the callbacks to work"); pending: session->pending_call_state=SSH_PENDING_CALL_CONNECT; if(ssh_is_blocking(session)) ssh_handle_packets_termination(session,-1,ssh_connect_termination,session); else ssh_handle_packets_termination(session,0,ssh_connect_termination, session); ssh_log(session,SSH_LOG_PACKET,"ssh_connect: Actual state : %d",session->session_state); if(!ssh_is_blocking(session) && !ssh_connect_termination(session)){ leave_function(); return SSH_AGAIN; } leave_function(); session->pending_call_state=SSH_PENDING_CALL_NONE; if(session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) return SSH_ERROR; return SSH_OK; }
/** * @brief Connect to the ssh server. * * @param[in] session The ssh session to connect. * * @returns SSH_OK on success, SSH_ERROR on error. * @returns SSH_AGAIN, if the session is in nonblocking mode, * and call must be done again. * * @see ssh_new() * @see ssh_disconnect() */ int ssh_connect(ssh_session session) { int ret; if (session == NULL) { return SSH_ERROR; } switch(session->pending_call_state){ case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_CONNECT: goto pending; default: ssh_set_error(session,SSH_FATAL,"Bad call during pending SSH call in ssh_connect"); return SSH_ERROR; } session->alive = 0; session->client = 1; if (ssh_init() < 0) { return SSH_ERROR; } if (session->opts.fd == SSH_INVALID_SOCKET && session->opts.host == NULL && session->opts.ProxyCommand == NULL) { ssh_set_error(session, SSH_FATAL, "Hostname required"); return SSH_ERROR; } ret = ssh_options_apply(session); if (ret < 0) { ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); return SSH_ERROR; } SSH_LOG(SSH_LOG_RARE,"libssh %s, using threading %s", ssh_copyright(), ssh_threads_get_type()); session->ssh_connection_callback = ssh_client_connection_callback; session->session_state=SSH_SESSION_STATE_CONNECTING; ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); session->socket_callbacks.connected=socket_callback_connected; session->socket_callbacks.data=callback_receive_banner; session->socket_callbacks.exception=ssh_socket_exception_callback; session->socket_callbacks.userdata=session; if (session->opts.fd != SSH_INVALID_SOCKET) { session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; ssh_socket_set_fd(session->socket, session->opts.fd); ret=SSH_OK; #ifndef _WIN32 } else if (session->opts.ProxyCommand != NULL){ ret = ssh_socket_connect_proxycommand(session->socket, session->opts.ProxyCommand); #endif } else { ret=ssh_socket_connect(session->socket, session->opts.host, session->opts.port, session->opts.bindaddr); } if (ret == SSH_ERROR) { return SSH_ERROR; } set_status(session, 0.2f); session->alive = 1; SSH_LOG(SSH_LOG_PROTOCOL,"Socket connecting, now waiting for the callbacks to work"); pending: session->pending_call_state=SSH_PENDING_CALL_CONNECT; if(ssh_is_blocking(session)) { int timeout = (session->opts.timeout * 1000) + (session->opts.timeout_usec / 1000); if (timeout == 0) { timeout = 10 * 1000; } SSH_LOG(SSH_LOG_PACKET,"Actual timeout : %d", timeout); ret = ssh_handle_packets_termination(session, timeout, ssh_connect_termination, session); if (session->session_state != SSH_SESSION_STATE_ERROR && (ret == SSH_ERROR || !ssh_connect_termination(session))) { ssh_set_error(session, SSH_FATAL, "Timeout connecting to %s", session->opts.host); session->session_state = SSH_SESSION_STATE_ERROR; } } else { ret = ssh_handle_packets_termination(session, SSH_TIMEOUT_NONBLOCKING, ssh_connect_termination, session); if (ret == SSH_ERROR) { session->session_state = SSH_SESSION_STATE_ERROR; } } SSH_LOG(SSH_LOG_PACKET,"current state : %d",session->session_state); if(!ssh_is_blocking(session) && !ssh_connect_termination(session)){ return SSH_AGAIN; } session->pending_call_state=SSH_PENDING_CALL_NONE; if(session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) return SSH_ERROR; return SSH_OK; }
/** * @brief Write the current server as known in the known hosts file. * * This will create the known hosts file if it does not exist. You generaly use * it when ssh_is_server_known() answered SSH_SERVER_NOT_KNOWN. * * @param[in] session The ssh session to use. * * @return SSH_OK on success, SSH_ERROR on error. */ int ssh_write_knownhost(ssh_session session) { ssh_string pubkey; unsigned char *pubkey_64; char buffer[4096] = {0}; FILE *file; char *dir; char *host; char *hostport; size_t len = 0; if (session->host == NULL) { ssh_set_error(session, SSH_FATAL, "Can't write host in known hosts if the hostname isn't known"); return SSH_ERROR; } host = ssh_lowercase(session->host); /* If using a nonstandard port, save the host in the [host]:port format */ if(session->port != 22){ hostport = ssh_hostport(host,session->port); SAFE_FREE(host); host=hostport; hostport=NULL; } if (session->knownhosts == NULL) { if (ssh_options_apply(session) < 0) { ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); return SSH_ERROR; } } if(session->current_crypto==NULL) { ssh_set_error(session, SSH_FATAL, "No current crypto context"); return SSH_ERROR; } pubkey = session->current_crypto->server_pubkey; if(pubkey == NULL){ ssh_set_error(session, SSH_FATAL, "No public key present"); return SSH_ERROR; } /* Check if ~/.ssh exists and create it if not */ dir = ssh_dirname(session->knownhosts); if (dir == NULL) { ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); return -1; } if (! ssh_file_readaccess_ok(dir)) { if (ssh_mkdir(dir, 0700) < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory.", dir); SAFE_FREE(dir); return -1; } } SAFE_FREE(dir); file = fopen(session->knownhosts, "a"); if (file == NULL) { ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s for appending: %s", session->knownhosts, strerror(errno)); SAFE_FREE(host); return -1; } if (strcmp(session->current_crypto->server_pubkey_type, "ssh-rsa1") == 0) { /* openssh uses a different format for ssh-rsa1 keys. Be compatible --kv */ ssh_public_key key; char *e_string = NULL; char *n_string = NULL; bignum e = NULL; bignum n = NULL; int rsa_size; #ifdef HAVE_LIBGCRYPT gcry_sexp_t sexp; #endif key = publickey_from_string(session, pubkey); if (key == NULL) { fclose(file); SAFE_FREE(host); return -1; } #ifdef HAVE_LIBGCRYPT sexp = gcry_sexp_find_token(key->rsa_pub, "e", 0); if (sexp == NULL) { publickey_free(key); fclose(file); SAFE_FREE(host); return -1; } e = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); gcry_sexp_release(sexp); if (e == NULL) { publickey_free(key); fclose(file); SAFE_FREE(host); return -1; } sexp = gcry_sexp_find_token(key->rsa_pub, "n", 0); if (sexp == NULL) { publickey_free(key); bignum_free(e); fclose(file); SAFE_FREE(host); return -1; } n = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); gcry_sexp_release(sexp); if (n == NULL) { publickey_free(key); bignum_free(e); fclose(file); SAFE_FREE(host); return -1; } rsa_size = (gcry_pk_get_nbits(key->rsa_pub) + 7) / 8; #elif defined HAVE_LIBCRYPTO e = key->rsa_pub->e; n = key->rsa_pub->n; rsa_size = RSA_size(key->rsa_pub); #endif e_string = bignum_bn2dec(e); n_string = bignum_bn2dec(n); if (e_string == NULL || n_string == NULL) { #ifdef HAVE_LIBGCRYPT bignum_free(e); bignum_free(n); SAFE_FREE(e_string); SAFE_FREE(n_string); #elif defined HAVE_LIBCRYPTO OPENSSL_free(e_string); OPENSSL_free(n_string); #endif publickey_free(key); fclose(file); SAFE_FREE(host); return -1; } snprintf(buffer, sizeof(buffer), "%s %d %s %s\n", host, rsa_size << 3, e_string, n_string); #ifdef HAVE_LIBGCRYPT bignum_free(e); bignum_free(n); SAFE_FREE(e_string); SAFE_FREE(n_string); #elif defined HAVE_LIBCRYPTO OPENSSL_free(e_string); OPENSSL_free(n_string); #endif publickey_free(key); } else { pubkey_64 = bin_to_base64(pubkey->string, ssh_string_len(pubkey)); if (pubkey_64 == NULL) { fclose(file); SAFE_FREE(host); return -1; } snprintf(buffer, sizeof(buffer), "%s %s %s\n", host, session->current_crypto->server_pubkey_type, pubkey_64); SAFE_FREE(pubkey_64); } SAFE_FREE(host); len = strlen(buffer); if (fwrite(buffer, len, 1, file) != 1 || ferror(file)) { fclose(file); return -1; } fclose(file); return 0; }
/** * @brief Check if the server is known. * * Checks the user's known host file for a previous connection to the * current server. * * @param[in] session The SSH session to use. * * @returns SSH_SERVER_KNOWN_OK: The server is known and has not changed.\n * SSH_SERVER_KNOWN_CHANGED: The server key has changed. Either you * are under attack or the administrator * changed the key. You HAVE to warn the * user about a possible attack.\n * SSH_SERVER_FOUND_OTHER: The server gave use a key of a type while * we had an other type recorded. It is a * possible attack.\n * SSH_SERVER_NOT_KNOWN: The server is unknown. User should * confirm the MD5 is correct.\n * SSH_SERVER_FILE_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n * SSH_SERVER_ERROR: Some error happened. * * @see ssh_get_pubkey_hash() * * @bug There is no current way to remove or modify an entry into the known * host table. */ int ssh_is_server_known(ssh_session session) { FILE *file = NULL; char **tokens; char *host; char *hostport; const char *type; int match; int ret = SSH_SERVER_NOT_KNOWN; enter_function(); if (session->knownhosts == NULL) { if (ssh_options_apply(session) < 0) { ssh_set_error(session, SSH_REQUEST_DENIED, "Can't find a known_hosts file"); leave_function(); return SSH_SERVER_FILE_NOT_FOUND; } } if (session->host == NULL) { ssh_set_error(session, SSH_FATAL, "Can't verify host in known hosts if the hostname isn't known"); leave_function(); return SSH_SERVER_ERROR; } if (session->current_crypto == NULL){ ssh_set_error(session, SSH_FATAL, "ssh_is_host_known called without cryptographic context"); leave_function(); return SSH_SERVER_ERROR; } host = ssh_lowercase(session->host); hostport = ssh_hostport(host,session->port); if (host == NULL || hostport == NULL) { ssh_set_error_oom(session); SAFE_FREE(host); SAFE_FREE(hostport); leave_function(); return SSH_SERVER_ERROR; } do { tokens = ssh_get_knownhost_line(session, &file, session->knownhosts, &type); /* End of file, return the current state */ if (tokens == NULL) { break; } match = match_hashed_host(session, host, tokens[0]); if (match == 0){ match = match_hostname(hostport, tokens[0], strlen(tokens[0])); } if (match == 0) { match = match_hostname(host, tokens[0], strlen(tokens[0])); } if (match == 0) { match = match_hashed_host(session, hostport, tokens[0]); } if (match) { /* We got a match. Now check the key type */ if (strcmp(session->current_crypto->server_pubkey_type, type) != 0) { /* Different type. We don't override the known_changed error which is * more important */ if (ret != SSH_SERVER_KNOWN_CHANGED) ret = SSH_SERVER_FOUND_OTHER; tokens_free(tokens); continue; } /* so we know the key type is good. We may get a good key or a bad key. */ match = check_public_key(session, tokens); tokens_free(tokens); if (match < 0) { ret = SSH_SERVER_ERROR; break; } else if (match == 1) { ret = SSH_SERVER_KNOWN_OK; break; } else if(match == 0) { /* We override the status with the wrong key state */ ret = SSH_SERVER_KNOWN_CHANGED; } } else { tokens_free(tokens); } } while (1); if ( (ret == SSH_SERVER_NOT_KNOWN) && (session->StrictHostKeyChecking == 0) ) { ssh_write_knownhost(session); ret = SSH_SERVER_KNOWN_OK; } SAFE_FREE(host); SAFE_FREE(hostport); if (file != NULL) { fclose(file); } /* Return the current state at end of file */ leave_function(); return ret; }
/** * @brief Try to authenticate by password. * * @param session The ssh session to use. * * @param username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param password The password to use. Take care to clean it after * the authentication. * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: Authentication successful. * * @see ssh_userauth_kbdint() * @see BURN_STRING */ int ssh_userauth_password(ssh_session session, const char *username, const char *password) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_string pwd = NULL; int rc = SSH_AUTH_ERROR; enter_function(); #ifdef WITH_SSH1 if (session->version == 1) { rc = ssh_userauth1_password(session, username, password); leave_function(); return rc; } #endif if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = string_from_char(session->username); } else { user = string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { string_free(user); leave_function(); return rc; } service = string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = string_from_char("password"); if (method == NULL) { goto error; } pwd = string_from_char(password); if (pwd == NULL) { goto error; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u8(session->out_buffer, 0) < 0 || buffer_add_ssh_string(session->out_buffer, pwd) < 0) { goto error; } string_free(user); string_free(service); string_free(method); string_burn(pwd); string_free(pwd); if (packet_send(session) != SSH_OK) { leave_function(); return rc; } rc = wait_auth_status(session, 0); leave_function(); return rc; error: buffer_reinit(session->out_buffer); string_free(user); string_free(service); string_free(method); string_burn(pwd); string_free(pwd); leave_function(); return rc; }
/** * @brief Try to authenticate through public key with an ssh agent. * * @param session The ssh session to use. * * @param username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param publickey The public key provided by the agent. * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: Authentication successful. * * @see publickey_from_file() * @see privatekey_from_file() * @see privatekey_free() * @see ssh_userauth_offer_pubkey() */ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, ssh_public_key publickey) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_string algo = NULL; ssh_string key = NULL; ssh_string sign = NULL; int rc = SSH_AUTH_ERROR; enter_function(); if (! agent_is_running(session)) { return rc; } if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = string_from_char(session->username); } else { user = string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { string_free(user); leave_function(); return rc; } service = string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = string_from_char("publickey"); if (method == NULL) { goto error; } algo = string_from_char(ssh_type_to_char(publickey->type)); if (algo == NULL) { goto error; } key = publickey_to_string(publickey); if (key == NULL) { goto error; } /* we said previously the public key was accepted */ if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u8(session->out_buffer, 1) < 0 || buffer_add_ssh_string(session->out_buffer, algo) < 0 || buffer_add_ssh_string(session->out_buffer, key) < 0) { goto error; } sign = ssh_do_sign_with_agent(session, session->out_buffer, publickey); if (sign) { if (buffer_add_ssh_string(session->out_buffer, sign) < 0) { goto error; } string_free(sign); if (packet_send(session) != SSH_OK) { leave_function(); return rc; } rc = wait_auth_status(session,0); } string_free(user); string_free(service); string_free(method); string_free(algo); string_free(key); leave_function(); return rc; error: buffer_reinit(session->out_buffer); string_free(sign); string_free(user); string_free(service); string_free(method); string_free(algo); string_free(key); leave_function(); return rc; }
/** * @brief Try to authenticate through the "keyboard-interactive" method. * * @param session The ssh session to use. * * @param user The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param submethods Undocumented. Set it to NULL. * * @returns SSH_AUTH_ERROR: A serious error happened\n * SSH_AUTH_DENIED: Authentication failed : use another method\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method\n * SSH_AUTH_SUCCESS: Authentication success\n * SSH_AUTH_INFO: The server asked some questions. Use * ssh_userauth_kbdint_getnprompts() and such. * * @see ssh_userauth_kbdint_getnprompts() * @see ssh_userauth_kbdint_getname() * @see ssh_userauth_kbdint_getinstruction() * @see ssh_userauth_kbdint_getprompt() * @see ssh_userauth_kbdint_setanswer() */ int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods) { int rc = SSH_AUTH_ERROR; if (session->version == 1) { /* No keyb-interactive for ssh1 */ return SSH_AUTH_DENIED; } enter_function(); if (session->kbdint == NULL) { /* first time we call. we must ask for a challenge */ if (user == NULL) { if ((user = session->username) == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return SSH_AUTH_ERROR; } else { user = session->username; } } } if (ask_userauth(session)) { leave_function(); return SSH_AUTH_ERROR; } rc = kbdauth_init(session, user, submethods); if (rc != SSH_AUTH_INFO) { leave_function(); return rc; /* error or first try success */ } rc = kbdauth_info_get(session); if (rc == SSH_AUTH_ERROR) { kbdint_free(session->kbdint); session->kbdint = NULL; } leave_function(); return rc; } /* * If we are at this point, it ss because session->kbdint exists. * It means the user has set some information there we need to send * the server and then we need to ack the status (new questions or ok * pass in). */ rc = kbdauth_send(session); kbdint_free(session->kbdint); session->kbdint = NULL; if(rc != SSH_AUTH_INFO) { leave_function(); return rc; } rc = kbdauth_info_get(session); if (rc == SSH_AUTH_ERROR) { kbdint_free(session->kbdint); session->kbdint = NULL; } leave_function(); return rc; }