aboutsummaryrefslogtreecommitdiff
path: root/libssh
diff options
context:
space:
mode:
authormilo <milo@r0ot.me>2010-08-09 18:43:15 +0200
committerAndreas Schneider <asn@cynapses.org>2010-08-09 20:54:32 +0200
commit8ef0fbc294d8193278385f45456f45d74e177418 (patch)
tree18fabb5dc29ca94480e367243e61d929a03f0bdc /libssh
parent51a21823f901b832cffd17ce53ac1d8c2a544e45 (diff)
downloadlibssh-8ef0fbc294d8193278385f45456f45d74e177418.tar.gz
libssh-8ef0fbc294d8193278385f45456f45d74e177418.tar.xz
libssh-8ef0fbc294d8193278385f45456f45d74e177418.zip
server: Fixed the server implementation.
Signed-off-by: Andreas Schneider <asn@cynapses.org>
Diffstat (limited to 'libssh')
-rw-r--r--libssh/server.c327
1 files changed, 278 insertions, 49 deletions
diff --git a/libssh/server.c b/libssh/server.c
index 0c8dd4ce..b1cd9b3f 100644
--- a/libssh/server.c
+++ b/libssh/server.c
@@ -49,6 +49,9 @@
session->callbacks->connect_status_function(session->callbacks->userdata, status); \
} while (0)
+static int dh_handshake_server(ssh_session session);
+
+
/**
* @addtogroup libssh_server
*
@@ -379,6 +382,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){
session->session_state=SSH_SESSION_STATE_ERROR;
} else {
session->dh_handshake_state=DH_STATE_INIT_SENT;
+ dh_handshake_server(session);
}
ssh_string_free(e);
@@ -393,11 +397,6 @@ static int dh_handshake_server(ssh_session session) {
ssh_string sign;
ssh_public_key pub;
ssh_private_key prv;
- /* waiting for SSH_MSG_KEXDH_INIT */
- while(session->dh_handshake_state != DH_STATE_INIT_SENT){
- ssh_handle_packets(session,-1);
- }
- /* received SSH_MSG_KEXDH_INIT */
if (dh_generate_y(session) < 0) {
ssh_set_error(session, SSH_FATAL, "Could not create y number");
@@ -497,74 +496,304 @@ static int dh_handshake_server(ssh_session session) {
ssh_log(session, SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent");
session->dh_handshake_state=DH_STATE_NEWKEYS_SENT;
- while(session->dh_handshake_state != DH_STATE_FINISHED)
- ssh_handle_packets(session,-1);
+ return 0;
+}
- if (generate_session_keys(session) < 0) {
+/**
+ * @internal
+ *
+ * @brief Analyze the SSH banner to find out if we have a SSHv1 or SSHv2
+ * server.
+ *
+ * @param session The session to analyze the banner from.
+ * @param ssh1 The variable which is set if it is a SSHv1 server.
+ * @param ssh2 The variable which is set if it is a SSHv2 server.
+ *
+ * @return 0 on success, < 0 on error.
+ *
+ * @see ssh_get_banner()
+ */
+static int ssh_analyze_banner(ssh_session session, int *ssh1, int *ssh2) {
+ const char *banner = session->clientbanner;
+ const char *openssh;
+
+ ssh_log(session, SSH_LOG_RARE, "Analyzing banner: %s", banner);
+
+ if (strncmp(banner, "SSH-", 4) != 0) {
+ ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner);
return -1;
}
/*
- * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and
- * current_crypto
+ * Typical banners e.g. are:
+ * SSH-1.5-blah
+ * SSH-1.99-blah
+ * SSH-2.0-blah
*/
- if (session->current_crypto) {
- crypto_free(session->current_crypto);
+ switch(banner[4]) {
+ case '1':
+ *ssh1 = 1;
+ if (banner[6] == '9') {
+ *ssh2 = 1;
+ } else {
+ *ssh2 = 0;
+ }
+ break;
+ case '2':
+ *ssh1 = 0;
+ *ssh2 = 1;
+ break;
+ default:
+ ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner);
+ return -1;
}
- /* FIXME TODO later, include a function to change keys */
- session->current_crypto = session->next_crypto;
- session->next_crypto = crypto_new();
- if (session->next_crypto == NULL) {
- return -1;
+ openssh = strstr(banner, "OpenSSH");
+ if (openssh != NULL) {
+ int major, minor;
+ major = strtol(openssh + 8, (char **) NULL, 10);
+ minor = strtol(openssh + 10, (char **) NULL, 10);
+ session->openssh = SSH_VERSION_INT(major, minor, 0);
+ ssh_log(session, SSH_LOG_RARE,
+ "We are talking to an OpenSSH client version: %d.%d (%x)",
+ major, minor, session->openssh);
}
return 0;
}
-/* FIXME TODO BUG
- * Makes linker happy until I work on server code
+/**
+ * @internal
+ *
+ * @brief A function to be called each time a step has been done in the
+ * connection.
*/
-static char *ssh_get_banner(ssh_session session){
- (void)session;
- return strdup("BUG");
+static void ssh_server_connection_callback(ssh_session session){
+ int ssh1,ssh2;
+ enter_function();
+ switch(session->session_state){
+ case SSH_SESSION_STATE_NONE:
+ case SSH_SESSION_STATE_CONNECTING:
+ case SSH_SESSION_STATE_SOCKET_CONNECTED:
+ break;
+ case SSH_SESSION_STATE_BANNER_RECEIVED:
+ if (session->clientbanner == NULL) {
+ goto error;
+ }
+ set_status(session, 0.4f);
+ ssh_log(session, SSH_LOG_RARE,
+ "SSH client banner: %s", session->clientbanner);
+
+ /* Here we analyze the different protocols the server allows. */
+ if (ssh_analyze_banner(session, &ssh1, &ssh2) < 0) {
+ goto error;
+ }
+ /* Here we decide which version of the protocol to use. */
+ if (ssh2 && session->ssh2) {
+ session->version = 2;
+ } else if(ssh1 && session->ssh1) {
+ session->version = 1;
+ } else if(ssh1 && !session->ssh1){
+#ifdef WITH_SSH1
+ ssh_set_error(session, SSH_FATAL,
+ "SSH-1 protocol not available (configure session to allow SSH-1)");
+ goto error;
+#else
+ ssh_set_error(session, SSH_FATAL,
+ "SSH-1 protocol not available (libssh compiled without SSH-1 support)");
+ goto error;
+#endif
+ } else {
+ ssh_set_error(session, SSH_FATAL,
+ "No version of SSH protocol usable (banner: %s)",
+ session->clientbanner);
+ goto error;
+ }
+ /* from now, the packet layer is handling incoming packets */
+ if(session->version==2)
+ session->socket_callbacks.data=ssh_packet_socket_callback;
+#ifdef WITH_SSH1
+ else
+ session->socket_callbacks.data=ssh_packet_socket_callback1;
+#endif
+ ssh_packet_set_default_callbacks(session);
+ set_status(session, 0.5f);
+ session->session_state=SSH_SESSION_STATE_INITIAL_KEX;
+ if (ssh_send_kex(session, 1) < 0) {
+ goto error;
+ }
+ break;
+ case SSH_SESSION_STATE_INITIAL_KEX:
+ /* TODO: This state should disappear in favor of get_key handle */
+#ifdef WITH_SSH1
+ if(session->version==1){
+ if (ssh_get_kex1(session) < 0)
+ goto error;
+ set_status(session,0.6f);
+ session->connected = 1;
+ break;
+ }
+#endif
+ break;
+ case SSH_SESSION_STATE_KEXINIT_RECEIVED:
+ set_status(session,0.6f);
+ ssh_list_kex(session, &session->client_kex); // log client kex
+ crypt_set_algorithms_server(session);
+ if (set_kex(session) < 0) {
+ goto error;
+ }
+ set_status(session,0.8f);
+ session->session_state=SSH_SESSION_STATE_DH;
+ break;
+ case SSH_SESSION_STATE_DH:
+ if(session->dh_handshake_state==DH_STATE_FINISHED){
+ if (generate_session_keys(session) < 0) {
+ goto error;
+ }
+
+ /*
+ * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and
+ * current_crypto
+ */
+ if (session->current_crypto) {
+ crypto_free(session->current_crypto);
+ }
+
+ /* FIXME TODO later, include a function to change keys */
+ session->current_crypto = session->next_crypto;
+ session->next_crypto = crypto_new();
+ if (session->next_crypto == NULL) {
+ goto error;
+ }
+ set_status(session,1.0f);
+ session->connected = 1;
+ session->session_state=SSH_SESSION_STATE_AUTHENTICATING;
+ }
+ break;
+ case SSH_SESSION_STATE_AUTHENTICATING:
+ break;
+ case SSH_SESSION_STATE_ERROR:
+ goto error;
+ default:
+ ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state);
+ }
+ leave_function();
+ return;
+ error:
+ ssh_socket_close(session->socket);
+ session->alive = 0;
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ leave_function();
}
-/* Do the banner and key exchange */
-int ssh_accept(ssh_session session) {
- if (ssh_send_banner(session, 1) < 0) {
- return -1;
- }
- session->alive = 1;
- session->clientbanner = ssh_get_banner(session);
- if (session->clientbanner == NULL) {
- return -1;
- }
+/**
+ * @internal
+ *
+ * @brief Gets the banner from socket and saves it in session.
+ * Updates the session state
+ *
+ * @param data pointer to the beginning of header
+ * @param len size of the banner
+ * @param user is a pointer to session
+ * @returns Number of bytes processed, or zero if the banner is not complete.
+ */
+static int callback_receive_banner(const void *data, size_t len, void *user) {
+ char *buffer = (char *) data;
+ ssh_session session = (ssh_session) user;
+ char *str = NULL;
+ size_t i;
+ int ret=0;
+
+ enter_function();
+
+ for (i = 0; i < len; i++) {
+#ifdef WITH_PCAP
+ if(session->pcap_ctx && buffer[i] == '\n') {
+ ssh_pcap_context_write(session->pcap_ctx,
+ SSH_PCAP_DIR_IN,
+ buffer,
+ i + 1,
+ i + 1);
+ }
+#endif
+ if (buffer[i] == '\r') {
+ buffer[i]='\0';
+ }
+
+ if (buffer[i] == '\n') {
+ buffer[i]='\0';
+
+ str = strdup(buffer);
+ /* number of bytes read */
+ ret = i + 1;
+ session->clientbanner = str;
+ session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED;
+ ssh_log(session, SSH_LOG_PACKET, "Received banner: %s", str);
+ session->ssh_connection_callback(session);
+
+ leave_function();
+ return ret;
+ }
+
+ if(i > 127) {
+ /* Too big banner */
+ session->session_state = SSH_SESSION_STATE_ERROR;
+ ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner");
+
+ leave_function();
+ return 0;
+ }
+ }
- if (server_set_kex(session) < 0) {
- return -1;
- }
+ leave_function();
+ return ret;
+}
- if (ssh_send_kex(session, 1) < 0) {
- return -1;
- }
+/* Do the banner and key exchange */
+int ssh_accept(ssh_session session) {
+ int rc;
- /* TODO here things won't work anymore */
-// if (ssh_get_kex(session,1) < 0) {
-// return -1;
-// }
+ rc = ssh_send_banner(session, 1);
+ if (rc < 0) {
+ return -1;
+ }
- ssh_list_kex(session, &session->client_kex);
- crypt_set_algorithms_server(session);
+ session->alive = 1;
- if (dh_handshake_server(session) < 0) {
- return -1;
- }
+ session->ssh_connection_callback = ssh_server_connection_callback;
+ session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED;
+ ssh_socket_set_callbacks(session->socket,&session->socket_callbacks);
+ session->socket_callbacks.data=callback_receive_banner;
+#if 0
+ session->socket_callbacks.exception=socket_callback_exception;
+#endif
+ session->socket_callbacks.userdata=session;
+
+ rc = server_set_kex(session);
+ if (rc < 0) {
+ return -1;
+ }
- session->connected = 1;
+ while (session->session_state != SSH_SESSION_STATE_ERROR &&
+ session->session_state != SSH_SESSION_STATE_AUTHENTICATING &&
+ session->session_state != SSH_SESSION_STATE_DISCONNECTED) {
+ /*
+ * loop until SSH_SESSION_STATE_BANNER_RECEIVED or
+ * SSH_SESSION_STATE_ERROR
+ */
+ ssh_handle_packets(session,-1);
+ ssh_log(session,SSH_LOG_PACKET, "ssh_accept: Actual state : %d",
+ session->session_state);
+ }
- return 0;
+ leave_function();
+ if (session->session_state == SSH_SESSION_STATE_ERROR ||
+ session->session_state == SSH_SESSION_STATE_DISCONNECTED) {
+ return SSH_ERROR;
+ }
+
+ return SSH_OK;
}
/**