aboutsummaryrefslogtreecommitdiff
path: root/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c1174
1 files changed, 1174 insertions, 0 deletions
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 00000000..e2367675
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,1174 @@
+/*
+ * server.c - functions for creating a SSH server
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2004-2005 by Aris Adamantiadis
+ *
+ * The SSH Library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * The SSH Library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the SSH Library; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "libssh/priv.h"
+#include "libssh/libssh.h"
+#include "libssh/server.h"
+#include "libssh/ssh2.h"
+#include "libssh/keyfiles.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.h"
+#include "libssh/socket.h"
+#include "libssh/channels.h"
+#include "libssh/session.h"
+#include "libssh/misc.h"
+#include "libssh/keys.h"
+#include "libssh/dh.h"
+#include "libssh/messages.h"
+
+#define set_status(session, status) do {\
+ if (session->callbacks && session->callbacks->connect_status_function) \
+ session->callbacks->connect_status_function(session->callbacks->userdata, status); \
+ } while (0)
+
+static int dh_handshake_server(ssh_session session);
+
+
+/**
+ * @addtogroup libssh_server
+ *
+ * @{
+ */
+
+#ifdef _WIN32
+
+#include <winsock2.h>
+#define SOCKOPT_TYPE_ARG4 char
+
+/* We need to provide hstrerror. Not we can't call the parameter h_errno because it's #defined */
+static char *hstrerror(int h_errno_val) {
+ static char text[50] = {0};
+
+ snprintf(text, sizeof(text), "gethostbyname error %d\n", h_errno_val);
+
+ return text;
+}
+#else /* _WIN32 */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#define SOCKOPT_TYPE_ARG4 int
+
+#endif /* _WIN32 */
+
+/* TODO FIXME: must use getaddrinfo */
+static socket_t bind_socket(ssh_bind sshbind, const char *hostname,
+ int port) {
+ struct sockaddr_in myaddr;
+ struct hostent *hp=NULL;
+ socket_t s;
+ int opt = 1;
+
+ s = socket(PF_INET, SOCK_STREAM, 0);
+ if (s < 0) {
+ ssh_set_error(sshbind, SSH_FATAL, "%s", strerror(errno));
+ return -1;
+ }
+
+#ifdef HAVE_GETHOSTBYNAME
+ hp = gethostbyname(hostname);
+#endif
+
+ if (hp == NULL) {
+ ssh_set_error(sshbind, SSH_FATAL,
+ "Resolving %s: %s", hostname, hstrerror(h_errno));
+ close(s);
+ return -1;
+ }
+
+ memset(&myaddr, 0, sizeof(myaddr));
+ memcpy(&myaddr.sin_addr, hp->h_addr, hp->h_length);
+ myaddr.sin_family = hp->h_addrtype;
+ myaddr.sin_port = htons(port);
+
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) {
+ ssh_set_error(sshbind, SSH_FATAL,
+ "Setting socket options failed: %s", hstrerror(h_errno));
+ close(s);
+ return -1;
+ }
+
+ if (bind(s, (struct sockaddr *) &myaddr, sizeof(myaddr)) < 0) {
+ ssh_set_error(sshbind, SSH_FATAL, "Binding to %s:%d: %s",
+ hostname,
+ port,
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+ssh_bind ssh_bind_new(void) {
+ ssh_bind ptr;
+
+ ptr = malloc(sizeof(struct ssh_bind_struct));
+ if (ptr == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(ptr);
+ ptr->bindfd = SSH_INVALID_SOCKET;
+ ptr->bindport= 22;
+ ptr->log_verbosity = 0;
+
+ return ptr;
+}
+
+int ssh_bind_listen(ssh_bind sshbind) {
+ const char *host;
+ socket_t fd;
+
+ if (ssh_init() < 0) {
+ return -1;
+ }
+
+ host = sshbind->bindaddr;
+ if (host == NULL) {
+ host = "0.0.0.0";
+ }
+
+ fd = bind_socket(sshbind, host, sshbind->bindport);
+ if (fd == SSH_INVALID_SOCKET) {
+ return -1;
+ }
+ sshbind->bindfd = fd;
+
+ if (listen(fd, 10) < 0) {
+ ssh_set_error(sshbind, SSH_FATAL,
+ "Listening to socket %d: %s",
+ fd, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) {
+ sshbind->blocking = blocking ? 1 : 0;
+}
+
+socket_t ssh_bind_get_fd(ssh_bind sshbind) {
+ return sshbind->bindfd;
+}
+
+void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) {
+ sshbind->bindfd = fd;
+}
+
+void ssh_bind_fd_toaccept(ssh_bind sshbind) {
+ sshbind->toaccept = 1;
+}
+
+void ssh_bind_free(ssh_bind sshbind){
+ int i;
+
+ if (sshbind == NULL) {
+ return;
+ }
+
+ if (sshbind->bindfd >= 0) {
+#ifdef _WIN32
+ closesocket(sshbind->bindfd);
+#else
+ close(sshbind->bindfd);
+#endif
+ }
+ sshbind->bindfd = SSH_INVALID_SOCKET;
+
+ /* options */
+ SAFE_FREE(sshbind->banner);
+ SAFE_FREE(sshbind->dsakey);
+ SAFE_FREE(sshbind->rsakey);
+ SAFE_FREE(sshbind->bindaddr);
+
+ for (i = 0; i < 10; i++) {
+ if (sshbind->wanted_methods[i]) {
+ SAFE_FREE(sshbind->wanted_methods[i]);
+ }
+ }
+
+ SAFE_FREE(sshbind);
+}
+
+extern char *supported_methods[];
+/** @internal
+ * This functions sets the Key Exchange protocols to be accepted
+ * by the server. They depend on
+ * -What the user asked (via options)
+ * -What is available (keys)
+ * It should then accept the intersection of what the user asked
+ * and what is available, and return an error if nothing matches
+ */
+
+static int server_set_kex(ssh_session session) {
+ KEX *server = &session->server_kex;
+ int i, j;
+ char *wanted;
+
+ ZERO_STRUCTP(server);
+ ssh_get_random(server->cookie, 16, 0);
+ if (session->dsa_key != NULL && session->rsa_key != NULL) {
+ if (ssh_options_set_algo(session, SSH_HOSTKEYS,
+ "ssh-dss,ssh-rsa") < 0) {
+ return -1;
+ }
+ } else if (session->dsa_key != NULL) {
+ if (ssh_options_set_algo(session, SSH_HOSTKEYS, "ssh-dss") < 0) {
+ return -1;
+ }
+ } else {
+ if (ssh_options_set_algo(session, SSH_HOSTKEYS, "ssh-rsa") < 0) {
+ return -1;
+ }
+ }
+
+ server->methods = malloc(10 * sizeof(char **));
+ if (server->methods == NULL) {
+ return -1;
+ }
+
+ for (i = 0; i < 10; i++) {
+ if ((wanted = session->wanted_methods[i]) == NULL) {
+ wanted = supported_methods[i];
+ }
+ server->methods[i] = strdup(wanted);
+ if (server->methods[i] == NULL) {
+ for (j = i - 1; j <= 0; j--) {
+ SAFE_FREE(server->methods[j]);
+ }
+ SAFE_FREE(server->methods);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){
+ ssh_string e;
+ (void)type;
+ (void)user;enter_function();
+ ssh_log(session,SSH_LOG_PACKET,"Received SSH_MSG_KEXDH_INIT");
+ if(session->dh_handshake_state != DH_STATE_INIT){
+ ssh_log(session,SSH_LOG_RARE,"Invalid state for SSH_MSG_KEXDH_INIT");
+ goto error;
+ }
+ e = buffer_get_ssh_string(packet);
+ if (e == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No e number in client request");
+ return -1;
+ }
+ if (dh_import_e(session, e) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Cannot import e number");
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ } else {
+ session->dh_handshake_state=DH_STATE_INIT_SENT;
+ dh_handshake_server(session);
+ }
+ ssh_string_free(e);
+
+ error:
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+static int dh_handshake_server(ssh_session session) {
+ ssh_string f;
+ ssh_string pubkey;
+ ssh_string sign;
+ ssh_public_key pub;
+ ssh_private_key prv;
+
+ if (dh_generate_y(session) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Could not create y number");
+ return -1;
+ }
+ if (dh_generate_f(session) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Could not create f number");
+ return -1;
+ }
+
+ f = dh_get_f(session);
+ if (f == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Could not get the f number");
+ return -1;
+ }
+
+ switch(session->hostkeys){
+ case SSH_KEYTYPE_DSS:
+ prv = session->dsa_key;
+ break;
+ case SSH_KEYTYPE_RSA:
+ prv = session->rsa_key;
+ break;
+ default:
+ prv = NULL;
+ }
+
+ pub = publickey_from_privatekey(prv);
+ if (pub == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Could not get the public key from the private key");
+ ssh_string_free(f);
+ return -1;
+ }
+ pubkey = publickey_to_string(pub);
+ publickey_free(pub);
+ if (pubkey == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ ssh_string_free(f);
+ return -1;
+ }
+
+ dh_import_pubkey(session, pubkey);
+ if (dh_build_k(session) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Could not import the public key");
+ ssh_string_free(f);
+ return -1;
+ }
+
+ if (make_sessionid(session) != SSH_OK) {
+ ssh_set_error(session, SSH_FATAL, "Could not create a session id");
+ ssh_string_free(f);
+ return -1;
+ }
+
+ sign = ssh_sign_session_id(session, prv);
+ if (sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Could not sign the session id");
+ ssh_string_free(f);
+ return -1;
+ }
+
+ /* Free private keys as they should not be readable after this point */
+ if (session->rsa_key) {
+ privatekey_free(session->rsa_key);
+ session->rsa_key = NULL;
+ }
+ if (session->dsa_key) {
+ privatekey_free(session->dsa_key);
+ session->dsa_key = NULL;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_REPLY) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, pubkey) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, f) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, sign) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(f);
+ ssh_string_free(sign);
+ return -1;
+ }
+ ssh_string_free(f);
+ ssh_string_free(sign);
+ if (packet_send(session) == SSH_ERROR) {
+ return -1;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) {
+ buffer_reinit(session->out_buffer);
+ return -1;
+ }
+
+ if (packet_send(session) == SSH_ERROR) {
+ return -1;
+ }
+ ssh_log(session, SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent");
+ session->dh_handshake_state=DH_STATE_NEWKEYS_SENT;
+
+ return 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;
+ }
+
+ /*
+ * Typical banners e.g. are:
+ * SSH-1.5-blah
+ * SSH-1.99-blah
+ * SSH-2.0-blah
+ */
+ 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;
+ }
+
+ 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;
+}
+
+/**
+ * @internal
+ *
+ * @brief A function to be called each time a step has been done in the
+ * connection.
+ */
+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();
+}
+
+/**
+ * @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;
+ }
+ }
+
+ leave_function();
+ return ret;
+}
+
+int ssh_bind_accept(ssh_bind sshbind, ssh_session session) {
+ ssh_private_key dsa = NULL;
+ ssh_private_key rsa = NULL;
+ socket_t fd = SSH_INVALID_SOCKET;
+ int i;
+
+ if (sshbind->bindfd == SSH_INVALID_SOCKET) {
+ ssh_set_error(sshbind, SSH_FATAL,
+ "Can't accept new clients on a not bound socket.");
+ return SSH_ERROR;
+ }
+ if(session == NULL){
+ ssh_set_error(sshbind, SSH_FATAL,"session is null");
+ return SSH_ERROR;
+ }
+ if (sshbind->dsakey == NULL && sshbind->rsakey == NULL) {
+ ssh_set_error(sshbind, SSH_FATAL,
+ "DSA or RSA host key file must be set before accept()");
+ return SSH_ERROR;
+ }
+
+ if (sshbind->dsakey) {
+ dsa = _privatekey_from_file(sshbind, sshbind->dsakey, SSH_KEYTYPE_DSS);
+ if (dsa == NULL) {
+ return SSH_ERROR;
+ }
+ }
+
+ if (sshbind->rsakey) {
+ rsa = _privatekey_from_file(sshbind, sshbind->rsakey, SSH_KEYTYPE_RSA);
+ if (rsa == NULL) {
+ privatekey_free(dsa);
+ return SSH_ERROR;
+ }
+ }
+
+ fd = accept(sshbind->bindfd, NULL, NULL);
+ if (fd == SSH_INVALID_SOCKET) {
+ ssh_set_error(sshbind, SSH_FATAL,
+ "Accepting a new connection: %s",
+ strerror(errno));
+ privatekey_free(dsa);
+ privatekey_free(rsa);
+ return SSH_ERROR;
+ }
+
+ session->server = 1;
+ session->version = 2;
+
+ /* copy options */
+ for (i = 0; i < 10; ++i) {
+ if (sshbind->wanted_methods[i]) {
+ session->wanted_methods[i] = strdup(sshbind->wanted_methods[i]);
+ if (session->wanted_methods[i] == NULL) {
+ privatekey_free(dsa);
+ privatekey_free(rsa);
+ return SSH_ERROR;
+ }
+ }
+ }
+
+ if (sshbind->bindaddr == NULL)
+ session->bindaddr = NULL;
+ else {
+ session->bindaddr = strdup(sshbind->bindaddr);
+ if (session->bindaddr == NULL) {
+ privatekey_free(dsa);
+ privatekey_free(rsa);
+ return SSH_ERROR;
+ }
+ }
+
+ session->log_verbosity = sshbind->log_verbosity;
+
+ ssh_socket_free(session->socket);
+ session->socket = ssh_socket_new(session);
+ if (session->socket == NULL) {
+ privatekey_free(dsa);
+ privatekey_free(rsa);
+ return SSH_ERROR;
+ }
+ ssh_socket_set_fd(session->socket, fd);
+ session->dsa_key = dsa;
+ session->rsa_key = rsa;
+
+return SSH_OK;
+}
+
+/* Do the banner and key exchange */
+int ssh_handle_key_exchange(ssh_session session) {
+ int rc;
+
+ rc = ssh_send_banner(session, 1);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+
+ session->alive = 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;
+ session->socket_callbacks.exception=ssh_socket_exception_callback;
+ session->socket_callbacks.userdata=session;
+
+ rc = server_set_kex(session);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+
+ 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);
+ }
+
+ if (session->session_state == SSH_SESSION_STATE_ERROR ||
+ session->session_state == SSH_SESSION_STATE_DISCONNECTED) {
+ return SSH_ERROR;
+ }
+
+ return SSH_OK;
+}
+
+/**
+ * @brief Blocking write on channel for stderr.
+ *
+ * @param channel The channel to write to.
+ *
+ * @param data A pointer to the data to write.
+ *
+ * @param len The length of the buffer to write to.
+ *
+ * @return The number of bytes written, SSH_ERROR on error.
+ *
+ * @see channel_read()
+ */
+int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) {
+ return channel_write_common(channel, data, len, 1);
+}
+
+/* messages */
+
+static int ssh_message_auth_reply_default(ssh_message msg,int partial) {
+ ssh_session session = msg->session;
+ char methods_c[128] = {0};
+ ssh_string methods = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_FAILURE) < 0) {
+ return rc;
+ }
+
+ if (session->auth_methods == 0) {
+ session->auth_methods = SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD;
+ }
+ if (session->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) {
+ strcat(methods_c, "publickey,");
+ }
+ if (session->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) {
+ strcat(methods_c, "keyboard-interactive,");
+ }
+ if (session->auth_methods & SSH_AUTH_METHOD_PASSWORD) {
+ strcat(methods_c, "password,");
+ }
+ if (session->auth_methods & SSH_AUTH_METHOD_HOSTBASED) {
+ strcat(methods_c, "hostbased,");
+ }
+
+ /* Strip the comma. */
+ methods_c[strlen(methods_c) - 1] = '\0'; // strip the comma. We are sure there is at
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sending a auth failure. methods that can continue: %s", methods_c);
+
+ methods = ssh_string_from_char(methods_c);
+ if (methods == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(msg->session->out_buffer, methods) < 0) {
+ goto error;
+ }
+
+ if (partial) {
+ if (buffer_add_u8(session->out_buffer, 1) < 0) {
+ goto error;
+ }
+ } else {
+ if (buffer_add_u8(session->out_buffer, 0) < 0) {
+ goto error;
+ }
+ }
+
+ rc = packet_send(msg->session);
+error:
+ ssh_string_free(methods);
+
+ leave_function();
+ return rc;
+}
+
+static int ssh_message_channel_request_open_reply_default(ssh_message msg) {
+ ssh_log(msg->session, SSH_LOG_FUNCTIONS, "Refusing a channel");
+
+ if (buffer_add_u8(msg->session->out_buffer
+ , SSH2_MSG_CHANNEL_OPEN_FAILURE) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(msg->session->out_buffer,
+ htonl(msg->channel_request_open.sender)) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(msg->session->out_buffer,
+ htonl(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED)) < 0) {
+ goto error;
+ }
+ /* reason is an empty string */
+ if (buffer_add_u32(msg->session->out_buffer, 0) < 0) {
+ goto error;
+ }
+ /* language too */
+ if (buffer_add_u32(msg->session->out_buffer, 0) < 0) {
+ goto error;
+ }
+
+ return packet_send(msg->session);
+error:
+ return SSH_ERROR;
+}
+
+static int ssh_message_channel_request_reply_default(ssh_message msg) {
+ uint32_t channel;
+
+ if (msg->channel_request.want_reply) {
+ channel = msg->channel_request.channel->remote_channel;
+
+ ssh_log(msg->session, SSH_LOG_PACKET,
+ "Sending a default channel_request denied to channel %d", channel);
+
+ if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_CHANNEL_FAILURE) < 0) {
+ return SSH_ERROR;
+ }
+ if (buffer_add_u32(msg->session->out_buffer, htonl(channel)) < 0) {
+ return SSH_ERROR;
+ }
+
+ return packet_send(msg->session);
+ }
+
+ ssh_log(msg->session, SSH_LOG_PACKET,
+ "The client doesn't want to know the request failed!");
+
+ return SSH_OK;
+}
+
+static int ssh_message_service_request_reply_default(ssh_message msg) {
+ /* The only return code accepted by specifications are success or disconnect */
+ return ssh_message_service_reply_success(msg);
+}
+
+int ssh_message_service_reply_success(ssh_message msg) {
+ struct ssh_string_struct *service;
+ ssh_session session=msg->session;
+ if (msg == NULL) {
+ return SSH_ERROR;
+ }
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sending a SERVICE_ACCEPT for service %s", msg->service_request.service);
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_SERVICE_ACCEPT) < 0) {
+ return -1;
+ }
+ service=ssh_string_from_char(msg->service_request.service);
+ if (buffer_add_ssh_string(session->out_buffer, service) < 0) {
+ ssh_string_free(service);
+ return -1;
+ }
+ ssh_string_free(service);
+ return packet_send(msg->session);
+}
+
+int ssh_message_reply_default(ssh_message msg) {
+ if (msg == NULL) {
+ return -1;
+ }
+
+ switch(msg->type) {
+ case SSH_REQUEST_AUTH:
+ return ssh_message_auth_reply_default(msg, 0);
+ case SSH_REQUEST_CHANNEL_OPEN:
+ return ssh_message_channel_request_open_reply_default(msg);
+ case SSH_REQUEST_CHANNEL:
+ return ssh_message_channel_request_reply_default(msg);
+ case SSH_REQUEST_SERVICE:
+ return ssh_message_service_request_reply_default(msg);
+ default:
+ ssh_log(msg->session, SSH_LOG_PACKET,
+ "Don't know what to default reply to %d type",
+ msg->type);
+ break;
+ }
+
+ return -1;
+}
+
+char *ssh_message_service_service(ssh_message msg){
+ if (msg == NULL) {
+ return NULL;
+ }
+ return msg->service_request.service;
+}
+
+char *ssh_message_auth_user(ssh_message msg) {
+ if (msg == NULL) {
+ return NULL;
+ }
+
+ return msg->auth_request.username;
+}
+
+char *ssh_message_auth_password(ssh_message msg){
+ if (msg == NULL) {
+ return NULL;
+ }
+
+ return msg->auth_request.password;
+}
+
+/* Get the publickey of an auth request */
+ssh_public_key ssh_message_auth_publickey(ssh_message msg){
+ if (msg == NULL) {
+ return NULL;
+ }
+
+ return msg->auth_request.public_key;
+}
+
+enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){
+ if (msg == NULL) {
+ return -1;
+ }
+ return msg->auth_request.signature_state;
+}
+
+int ssh_message_auth_set_methods(ssh_message msg, int methods) {
+ if (msg == NULL || msg->session == NULL) {
+ return -1;
+ }
+
+ msg->session->auth_methods = methods;
+
+ return 0;
+}
+
+int ssh_message_auth_reply_success(ssh_message msg, int partial) {
+ if (msg == NULL) {
+ return SSH_ERROR;
+ }
+
+ if (partial) {
+ return ssh_message_auth_reply_default(msg, partial);
+ }
+
+ if (buffer_add_u8(msg->session->out_buffer,SSH2_MSG_USERAUTH_SUCCESS) < 0) {
+ return SSH_ERROR;
+ }
+
+ return packet_send(msg->session);
+}
+
+/* Answer OK to a pubkey auth request */
+int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) {
+ if (msg == NULL) {
+ return SSH_ERROR;
+ }
+
+ if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_USERAUTH_PK_OK) < 0 ||
+ buffer_add_ssh_string(msg->session->out_buffer, algo) < 0 ||
+ buffer_add_ssh_string(msg->session->out_buffer, pubkey) < 0) {
+ return SSH_ERROR;
+ }
+
+ return packet_send(msg->session);
+}
+
+int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) {
+ ssh_string algo;
+ ssh_string pubkey;
+ int ret;
+ algo=ssh_string_from_char(msg->auth_request.public_key->type_c);
+ pubkey=publickey_to_string(msg->auth_request.public_key);
+ ret=ssh_message_auth_reply_pk_ok(msg,algo,pubkey);
+ ssh_string_free(algo);
+ ssh_string_free(pubkey);
+ return ret;
+}
+
+
+char *ssh_message_channel_request_open_originator(ssh_message msg){
+ return msg->channel_request_open.originator;
+}
+
+int ssh_message_channel_request_open_originator_port(ssh_message msg){
+ return msg->channel_request_open.originator_port;
+}
+
+char *ssh_message_channel_request_open_destination(ssh_message msg){
+ return msg->channel_request_open.destination;
+}
+
+int ssh_message_channel_request_open_destination_port(ssh_message msg){
+ return msg->channel_request_open.destination_port;
+}
+
+ssh_channel ssh_message_channel_request_channel(ssh_message msg){
+ return msg->channel_request.channel;
+}
+
+char *ssh_message_channel_request_pty_term(ssh_message msg){
+ return msg->channel_request.TERM;
+}
+
+int ssh_message_channel_request_pty_width(ssh_message msg){
+ return msg->channel_request.width;
+}
+
+int ssh_message_channel_request_pty_height(ssh_message msg){
+ return msg->channel_request.height;
+}
+
+int ssh_message_channel_request_pty_pxwidth(ssh_message msg){
+ return msg->channel_request.pxwidth;
+}
+
+int ssh_message_channel_request_pty_pxheight(ssh_message msg){
+ return msg->channel_request.pxheight;
+}
+
+char *ssh_message_channel_request_env_name(ssh_message msg){
+ return msg->channel_request.var_name;
+}
+
+char *ssh_message_channel_request_env_value(ssh_message msg){
+ return msg->channel_request.var_value;
+}
+
+char *ssh_message_channel_request_command(ssh_message msg){
+ return msg->channel_request.command;
+}
+
+char *ssh_message_channel_request_subsystem(ssh_message msg){
+ return msg->channel_request.subsystem;
+}
+
+/** @brief defines the SSH_MESSAGE callback
+ * @param session the current ssh session
+ * @param[in] ssh_message_callback_ a function pointer to a callback taking the
+ * current ssh session and received message as parameters. the function returns
+ * 0 if the message has been parsed and treated sucessfuly, 1 otherwise (libssh
+ * must take care of the response).
+ * @param[in] data void pointer to be passed to callback functions
+ */
+void ssh_set_message_callback(ssh_session session,
+ int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data),
+ void *data) {
+ session->ssh_message_callback = ssh_bind_message_callback;
+ session->ssh_message_callback_data = data;
+}
+
+int ssh_execute_message_callbacks(ssh_session session){
+ ssh_message msg=NULL;
+ int ret;
+ ssh_handle_packets(session, 0);
+ if(!session->ssh_message_list)
+ return SSH_OK;
+ if(session->ssh_message_callback){
+ while((msg=ssh_message_pop_head(session)) != NULL) {
+ ret=session->ssh_message_callback(session,msg,
+ session->ssh_message_callback_data);
+ if(ret==1){
+ ret = ssh_message_reply_default(msg);
+ ssh_message_free(msg);
+ if(ret != SSH_OK)
+ return ret;
+ } else {
+ ssh_message_free(msg);
+ }
+ }
+ } else {
+ while((msg=ssh_message_pop_head(session)) != NULL) {
+ ret = ssh_message_reply_default(msg);
+ ssh_message_free(msg);
+ if(ret != SSH_OK)
+ return ret;
+ }
+ }
+ return SSH_OK;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */