aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt204
-rw-r--r--src/agent.c502
-rw-r--r--src/auth.c1682
-rw-r--r--src/auth1.c206
-rw-r--r--src/base64.c287
-rw-r--r--src/buffer.c578
-rw-r--r--src/callbacks.c43
-rw-r--r--src/channels.c2495
-rw-r--r--src/channels1.c298
-rw-r--r--src/client.c814
-rw-r--r--src/config.c347
-rw-r--r--src/connect.c600
-rw-r--r--src/crc32.c92
-rw-r--r--src/crypt.c216
-rw-r--r--src/dh.c1049
-rw-r--r--src/error.c123
-rw-r--r--src/gcrypt_missing.c99
-rw-r--r--src/gzip.c222
-rw-r--r--src/init.c94
-rw-r--r--src/kex.c835
-rw-r--r--src/keyfiles.c1902
-rw-r--r--src/keys.c1497
-rw-r--r--src/legacy.c237
-rw-r--r--src/libcrypto.c443
-rw-r--r--src/libgcrypt.c423
-rw-r--r--src/log.c82
-rw-r--r--src/match.c185
-rw-r--r--src/messages.c848
-rw-r--r--src/misc.c685
-rw-r--r--src/options.c1138
-rw-r--r--src/packet.c529
-rw-r--r--src/packet1.c362
-rw-r--r--src/pcap.c434
-rw-r--r--src/pki.c113
-rw-r--r--src/poll.c692
-rw-r--r--src/scp.c752
-rw-r--r--src/server.c1174
-rw-r--r--src/session.c520
-rw-r--r--src/sftp.c3207
-rw-r--r--src/sftpserver.c490
-rw-r--r--src/socket.c719
-rw-r--r--src/string.c212
-rw-r--r--src/threads.c159
-rw-r--r--src/wrapper.c325
44 files changed, 27914 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 00000000..b158ae6c
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,204 @@
+project(libssh-library C)
+
+set(LIBSSH_PUBLIC_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}
+ CACHE INTERNAL "libssh public include directories"
+)
+
+set(LIBSSH_PRIVATE_INCLUDE_DIRS
+ ${CMAKE_BINARY_DIR}
+ ${ZLIB_INCLUDE_DIRS}
+)
+
+set(LIBSSH_SHARED_LIBRARY
+ ssh_shared
+ CACHE INTERNAL "libssh shared library"
+)
+
+if (WITH_STATIC_LIB)
+ set(LIBSSH_STATIC_LIBRARY
+ ssh_static
+ CACHE INTERNAL "libssh static library"
+ )
+endif (WITH_STATIC_LIB)
+
+set(LIBSSH_LINK_LIBRARIES
+ ${LIBSSH_REQUIRED_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+)
+
+if (WIN32)
+ set(LIBSSH_LINK_LIBRARIES
+ ${LIBSSH_LINK_LIBRARIES}
+ ws2_32
+ )
+endif (WIN32)
+
+if (HAVE_LIBSOCKET)
+ set(LIBSSH_LINK_LIBRARIES
+ ${LIBSSH_LINK_LIBRARIES}
+ socket
+ )
+endif (HAVE_LIBSOCKET)
+
+if (OPENSSL_LIBRARIES)
+ set(LIBSSH_PRIVATE_INCLUDE_DIRS
+ ${LIBSSH_PRIVATE_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ )
+
+ set(LIBSSH_LINK_LIBRARIES
+ ${LIBSSH_LINK_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ )
+endif (OPENSSL_LIBRARIES)
+
+if (GCRYPT_LIBRARY)
+ set(LIBSSH_PRIVATE_INCLUDE_DIRS
+ ${LIBSSH_PRIVATE_INCLUDE_DIRS}
+ ${GCRYPT_INCLUDE_DIRS}
+ )
+
+ set(LIBSSH_LINK_LIBRARIES
+ ${LIBSSH_LINK_LIBRARIES}
+ ${GCRYPT_LIBRARY}
+ )
+endif (GCRYPT_LIBRARY)
+
+set(LIBSSH_LINK_LIBRARIES
+ ${LIBSSH_LINK_LIBRARIES}
+ CACHE INTERNAL "libssh link libraries"
+)
+
+set(libssh_SRCS
+ agent.c
+ auth.c
+ base64.c
+ buffer.c
+ callbacks.c
+ channels.c
+ client.c
+ config.c
+ connect.c
+ crc32.c
+ crypt.c
+ dh.c
+ error.c
+ gcrypt_missing.c
+ gzip.c
+ init.c
+ kex.c
+ keyfiles.c
+ keys.c
+ legacy.c
+ libcrypto.c
+ libgcrypt.c
+ log.c
+ match.c
+ messages.c
+ misc.c
+ options.c
+ packet.c
+ pcap.c
+ pki.c
+ poll.c
+ session.c
+ scp.c
+ socket.c
+ string.c
+ threads.c
+ wrapper.c
+)
+
+if (WITH_SFTP)
+ set(libssh_SRCS
+ ${libssh_SRCS}
+ sftp.c
+ )
+
+ if (WITH_SERVER)
+ set(libssh_SRCS
+ ${libssh_SRCS}
+ sftpserver.c
+ )
+ endif (WITH_SERVER)
+endif (WITH_SFTP)
+
+if (WITH_SSH1)
+ set(libssh_SRCS
+ ${libssh_SRCS}
+ auth1.c
+ channels1.c
+ packet1.c
+ )
+endif (WITH_SSH1)
+
+if (WITH_SERVER)
+ set(libssh_SRCS
+ ${libssh_SRCS}
+ server.c
+ )
+endif (WITH_SERVER)
+
+include_directories(
+ ${LIBSSH_PUBLIC_INCLUDE_DIRS}
+ ${LIBSSH_PRIVATE_INCLUDE_DIRS}
+)
+
+add_library(${LIBSSH_SHARED_LIBRARY} SHARED ${libssh_SRCS})
+
+target_link_libraries(${LIBSSH_SHARED_LIBRARY} ${LIBSSH_LINK_LIBRARIES})
+
+set_target_properties(
+ ${LIBSSH_SHARED_LIBRARY}
+ PROPERTIES
+ VERSION
+ ${LIBRARY_VERSION}
+ SOVERSION
+ ${LIBRARY_SOVERSION}
+ OUTPUT_NAME
+ ssh
+ DEFINE_SYMBOL
+ LIBSSH_EXPORTS
+)
+
+if (WITH_VISIBILITY_HIDDEN)
+ set_target_properties(${LIBSSH_SHARED_LIBRARY} PROPERTIES COMPILE_FLAGS "-fvisibility=hidden")
+endif (WITH_VISIBILITY_HIDDEN)
+
+
+install(
+ TARGETS
+ ${LIBSSH_SHARED_LIBRARY}
+ RUNTIME DESTINATION ${BIN_INSTALL_DIR}
+ LIBRARY DESTINATION ${LIB_INSTALL_DIR}
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
+ COMPONENT libraries
+)
+
+if (WITH_STATIC_LIB)
+ add_library(${LIBSSH_STATIC_LIBRARY} STATIC ${libssh_SRCS})
+
+ set_target_properties(
+ ${LIBSSH_STATIC_LIBRARY}
+ PROPERTIES
+ VERSION
+ ${LIBRARY_VERSION}
+ SOVERSION
+ ${LIBRARY_SOVERSION}
+ COMPILE_FLAGS
+ "-DLIBSSH_STATIC"
+ )
+
+ install(
+ TARGETS
+ ${LIBSSH_STATIC_LIBRARY}
+ DESTINATION
+ ${LIB_INSTALL_DIR}
+ COMPONENT
+ libraries
+ )
+endif (WITH_STATIC_LIB)
+
diff --git a/src/agent.c b/src/agent.c
new file mode 100644
index 00000000..16b38b8c
--- /dev/null
+++ b/src/agent.c
@@ -0,0 +1,502 @@
+/*
+ * agent.c - ssh agent functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2008-2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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.
+ */
+
+/* This file is based on authfd.c from OpenSSH */
+
+/*
+ * How does the ssh-agent work?
+ *
+ * a) client sends a request to get a list of all keys
+ * the agent returns the count and all public keys
+ * b) iterate over them to check if the server likes one
+ * c) the client sends a sign request to the agent
+ * type, pubkey as blob, data to sign, flags
+ * the agent returns the signed data
+ */
+
+#ifndef _WIN32
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <unistd.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/agent.h"
+#include "libssh/priv.h"
+#include "libssh/socket.h"
+#include "libssh/buffer.h"
+#include "libssh/session.h"
+#include "libssh/keys.h"
+#include "libssh/poll.h"
+
+/* macro to check for "agent failure" message */
+#define agent_failed(x) \
+ (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \
+ ((x) == SSH2_AGENT_FAILURE))
+
+static uint32_t agent_get_u32(const void *vp) {
+ const uint8_t *p = (const uint8_t *)vp;
+ uint32_t v;
+
+ v = (uint32_t)p[0] << 24;
+ v |= (uint32_t)p[1] << 16;
+ v |= (uint32_t)p[2] << 8;
+ v |= (uint32_t)p[3];
+
+ return v;
+}
+
+static void agent_put_u32(void *vp, uint32_t v) {
+ uint8_t *p = (uint8_t *)vp;
+
+ p[0] = (uint8_t)(v >> 24) & 0xff;
+ p[1] = (uint8_t)(v >> 16) & 0xff;
+ p[2] = (uint8_t)(v >> 8) & 0xff;
+ p[3] = (uint8_t)v & 0xff;
+}
+
+static size_t atomicio(ssh_socket s, void *buf, size_t n, int do_read) {
+ char *b = buf;
+ size_t pos = 0;
+ ssize_t res;
+ ssh_pollfd_t pfd;
+ socket_t fd = ssh_socket_get_fd_in(s);
+
+ pfd.fd = fd;
+ pfd.events = do_read ? POLLIN : POLLOUT;
+
+ while (n > pos) {
+ if (do_read) {
+ res = read(fd, b + pos, n - pos);
+ } else {
+ res = write(fd, b + pos, n - pos);
+ }
+ switch (res) {
+ case -1:
+ if (errno == EINTR) {
+ continue;
+ }
+#ifdef EWOULDBLOCK
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+#else
+ if (errno == EAGAIN) {
+#endif
+ (void) ssh_poll(&pfd, 1, -1);
+ continue;
+ }
+ return 0;
+ case 0:
+ errno = EPIPE;
+ return pos;
+ default:
+ pos += (size_t) res;
+ }
+ }
+
+ return pos;
+}
+
+ssh_agent agent_new(struct ssh_session_struct *session) {
+ ssh_agent agent = NULL;
+
+ agent = malloc(sizeof(struct ssh_agent_struct));
+ if (agent == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(agent);
+
+ agent->count = 0;
+ agent->sock = ssh_socket_new(session);
+ if (agent->sock == NULL) {
+ SAFE_FREE(agent);
+ return NULL;
+ }
+
+ return agent;
+}
+
+void agent_close(struct ssh_agent_struct *agent) {
+ if (agent == NULL) {
+ return;
+ }
+
+ if (getenv("SSH_AUTH_SOCK")) {
+ ssh_socket_close(agent->sock);
+ }
+}
+
+void agent_free(ssh_agent agent) {
+ if (agent) {
+ if (agent->ident) {
+ ssh_buffer_free(agent->ident);
+ }
+ if (agent->sock) {
+ agent_close(agent);
+ ssh_socket_free(agent->sock);
+ }
+ SAFE_FREE(agent);
+ }
+}
+
+static int agent_connect(ssh_session session) {
+ const char *auth_sock = NULL;
+
+ if (session == NULL || session->agent == NULL) {
+ return -1;
+ }
+
+ auth_sock = getenv("SSH_AUTH_SOCK");
+
+ if (auth_sock && *auth_sock) {
+ if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ return -1;
+}
+
+#if 0
+static int agent_decode_reply(struct ssh_session_struct *session, int type) {
+ switch (type) {
+ case SSH_AGENT_FAILURE:
+ case SSH2_AGENT_FAILURE:
+ case SSH_COM_AGENT2_FAILURE:
+ ssh_log(session, SSH_LOG_RARE, "SSH_AGENT_FAILURE");
+ return 0;
+ case SSH_AGENT_SUCCESS:
+ return 1;
+ default:
+ ssh_set_error(session, SSH_FATAL,
+ "Bad response from authentication agent: %d", type);
+ break;
+ }
+
+ return -1;
+}
+#endif
+
+static int agent_talk(struct ssh_session_struct *session,
+ struct ssh_buffer_struct *request, struct ssh_buffer_struct *reply) {
+ uint32_t len = 0;
+ uint8_t payload[1024] = {0};
+
+ len = ssh_buffer_get_len(request);
+ ssh_log(session, SSH_LOG_PACKET, "agent_talk - len of request: %u", len);
+ agent_put_u32(payload, len);
+
+ /* send length and then the request packet */
+ if (atomicio(session->agent->sock, payload, 4, 0) == 4) {
+ if (atomicio(session->agent->sock, buffer_get_rest(request), len, 0)
+ != len) {
+ ssh_log(session, SSH_LOG_PACKET, "atomicio sending request failed: %s",
+ strerror(errno));
+ return -1;
+ }
+ } else {
+ ssh_log(session, SSH_LOG_PACKET,
+ "atomicio sending request length failed: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ /* wait for response, read the length of the response packet */
+ if (atomicio(session->agent->sock, payload, 4, 1) != 4) {
+ ssh_log(session, SSH_LOG_PACKET, "atomicio read response length failed: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ len = agent_get_u32(payload);
+ if (len > 256 * 1024) {
+ ssh_set_error(session, SSH_FATAL,
+ "Authentication response too long: %u", len);
+ return -1;
+ }
+ ssh_log(session, SSH_LOG_PACKET, "agent_talk - response length: %u", len);
+
+ while (len > 0) {
+ size_t n = len;
+ if (n > sizeof(payload)) {
+ n = sizeof(payload);
+ }
+ if (atomicio(session->agent->sock, payload, n, 1) != n) {
+ ssh_log(session, SSH_LOG_RARE,
+ "Error reading response from authentication socket.");
+ return -1;
+ }
+ if (buffer_add_data(reply, payload, n) < 0) {
+ ssh_log(session, SSH_LOG_FUNCTIONS,
+ "Not enough space");
+ return -1;
+ }
+ len -= n;
+ }
+
+ return 0;
+}
+
+int agent_get_ident_count(struct ssh_session_struct *session) {
+ ssh_buffer request = NULL;
+ ssh_buffer reply = NULL;
+ unsigned int type = 0;
+ unsigned int c1 = 0, c2 = 0;
+ uint8_t buf[4] = {0};
+
+ switch (session->version) {
+ case 1:
+ c1 = SSH_AGENTC_REQUEST_RSA_IDENTITIES;
+ c2 = SSH_AGENT_RSA_IDENTITIES_ANSWER;
+ break;
+ case 2:
+ c1 = SSH2_AGENTC_REQUEST_IDENTITIES;
+ c2 = SSH2_AGENT_IDENTITIES_ANSWER;
+ break;
+ default:
+ return 0;
+ }
+
+ /* send message to the agent requesting the list of identities */
+ request = ssh_buffer_new();
+ if (buffer_add_u8(request, c1) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ return -1;
+ }
+
+ reply = ssh_buffer_new();
+ if (reply == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ return -1;
+ }
+
+ if (agent_talk(session, request, reply) < 0) {
+ ssh_buffer_free(request);
+ return 0;
+ }
+ ssh_buffer_free(request);
+
+ /* get message type and verify the answer */
+ buffer_get_u8(reply, (uint8_t *) &type);
+ ssh_log(session, SSH_LOG_PACKET,
+ "agent_ident_count - answer type: %d, expected answer: %d",
+ type, c2);
+ if (agent_failed(type)) {
+ return 0;
+ } else if (type != c2) {
+ ssh_set_error(session, SSH_FATAL,
+ "Bad authentication reply message type: %d", type);
+ return -1;
+ }
+
+ buffer_get_u32(reply, (uint32_t *) buf);
+ session->agent->count = agent_get_u32(buf);
+ ssh_log(session, SSH_LOG_PACKET, "agent_ident_count - count: %d",
+ session->agent->count);
+ if (session->agent->count > 1024) {
+ ssh_set_error(session, SSH_FATAL,
+ "Too many identities in authentication reply: %d",
+ session->agent->count);
+ ssh_buffer_free(reply);
+ return -1;
+ }
+
+ if (session->agent->ident) {
+ buffer_reinit(session->agent->ident);
+ }
+ session->agent->ident = reply;
+
+ return session->agent->count;
+}
+
+/* caller has to free commment */
+struct ssh_public_key_struct *agent_get_first_ident(struct ssh_session_struct *session,
+ char **comment) {
+ if (agent_get_ident_count(session) > 0) {
+ return agent_get_next_ident(session, comment);
+ }
+
+ return NULL;
+}
+
+/* caller has to free commment */
+struct ssh_public_key_struct *agent_get_next_ident(struct ssh_session_struct *session,
+ char **comment) {
+ struct ssh_public_key_struct *pubkey = NULL;
+ struct ssh_string_struct *blob = NULL;
+ struct ssh_string_struct *tmp = NULL;
+
+ if (session->agent->count == 0) {
+ return NULL;
+ }
+
+ switch(session->version) {
+ case 1:
+ return NULL;
+ case 2:
+ /* get the blob */
+ blob = buffer_get_ssh_string(session->agent->ident);
+ if (blob == NULL) {
+ return NULL;
+ }
+
+ /* get the comment */
+ tmp = buffer_get_ssh_string(session->agent->ident);
+ if (tmp == NULL) {
+ ssh_string_free(blob);
+
+ return NULL;
+ }
+
+ if (comment) {
+ *comment = ssh_string_to_char(tmp);
+ } else {
+ ssh_string_free(blob);
+ ssh_string_free(tmp);
+
+ return NULL;
+ }
+ ssh_string_free(tmp);
+
+ /* get key from blob */
+ pubkey = publickey_from_string(session, blob);
+ ssh_string_free(blob);
+ break;
+ default:
+ return NULL;
+ }
+
+ return pubkey;
+}
+
+ssh_string agent_sign_data(struct ssh_session_struct *session,
+ struct ssh_buffer_struct *data,
+ struct ssh_public_key_struct *pubkey) {
+ struct ssh_string_struct *blob = NULL;
+ struct ssh_string_struct *sig = NULL;
+ struct ssh_buffer_struct *request = NULL;
+ struct ssh_buffer_struct *reply = NULL;
+ int type = SSH2_AGENT_FAILURE;
+ int flags = 0;
+ uint32_t dlen = 0;
+
+ /* create blob from the pubkey */
+ blob = publickey_to_string(pubkey);
+
+ request = ssh_buffer_new();
+ if (request == NULL) {
+ goto error;
+ }
+
+ /* create request */
+ if (buffer_add_u8(request, SSH2_AGENTC_SIGN_REQUEST) < 0) {
+ goto error;
+ }
+
+ /* adds len + blob */
+ if (buffer_add_ssh_string(request, blob) < 0) {
+ goto error;
+ }
+
+ /* Add data */
+ dlen = ssh_buffer_get_len(data);
+ if (buffer_add_u32(request, htonl(dlen)) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(request, ssh_buffer_get_begin(data), dlen) < 0) {
+ goto error;
+ }
+
+ if (buffer_add_u32(request, htonl(flags)) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(blob);
+
+ reply = ssh_buffer_new();
+ if (reply == NULL) {
+ goto error;
+ }
+
+ /* send the request */
+ if (agent_talk(session, request, reply) < 0) {
+ ssh_buffer_free(request);
+ return NULL;
+ }
+ ssh_buffer_free(request);
+
+ /* check if reply is valid */
+ if (buffer_get_u8(reply, (uint8_t *) &type) != sizeof(uint8_t)) {
+ goto error;
+ }
+ if (agent_failed(type)) {
+ ssh_log(session, SSH_LOG_RARE, "Agent reports failure in signing the key");
+ ssh_buffer_free(reply);
+ return NULL;
+ } else if (type != SSH2_AGENT_SIGN_RESPONSE) {
+ ssh_set_error(session, SSH_FATAL, "Bad authentication response: %d", type);
+ ssh_buffer_free(reply);
+ return NULL;
+ }
+
+ sig = buffer_get_ssh_string(reply);
+
+ ssh_buffer_free(reply);
+
+ return sig;
+error:
+ ssh_set_error(session, SSH_FATAL, "Not enough memory");
+ ssh_string_free(blob);
+ ssh_buffer_free(request);
+ ssh_buffer_free(reply);
+
+ return NULL;
+}
+
+int agent_is_running(ssh_session session) {
+ if (session == NULL || session->agent == NULL) {
+ return 0;
+ }
+
+ if (ssh_socket_is_open(session->agent->sock)) {
+ return 1;
+ } else {
+ if (agent_connect(session) < 0) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#endif /* _WIN32 */
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 00000000..f0443db0
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,1682 @@
+/*
+ * auth1.c - authentication with SSH protocols
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 by Aris Adamantiadis
+ * Copyright (c) 2008-2009 Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/buffer.h"
+#include "libssh/agent.h"
+#include "libssh/keyfiles.h"
+#include "libssh/misc.h"
+#include "libssh/packet.h"
+#include "libssh/session.h"
+#include "libssh/keys.h"
+#include "libssh/auth.h"
+
+/**
+ * @defgroup libssh_auth The SSH authentication functions.
+ * @ingroup libssh
+ *
+ * Functions to authenticate with a server.
+ *
+ * @{
+ */
+
+/**
+ * @internal
+ *
+ * @brief Ask access to the ssh-userauth service.
+ *
+ * @param[in] session The SSH session handle.
+ *
+ * @returns SSH_OK on success, SSH_ERROR on error.
+ *
+ * @bug current implementation is blocking
+ */
+static int ask_userauth(ssh_session session) {
+ int rc = 0;
+
+ enter_function();
+ do {
+ rc=ssh_service_request(session,"ssh-userauth");
+ if(rc==SSH_AGAIN)
+ ssh_handle_packets(session,-1);
+ } while(rc==SSH_AGAIN);
+ leave_function();
+ return rc;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handles a SSH_USERAUTH_BANNER packet.
+ *
+ * This banner should be shown to user prior to authentication
+ */
+SSH_PACKET_CALLBACK(ssh_packet_userauth_banner){
+ ssh_string banner;
+ (void)type;
+ (void)user;
+ enter_function();
+ banner = buffer_get_ssh_string(packet);
+ if (banner == NULL) {
+ ssh_log(session, SSH_LOG_RARE,
+ "Invalid SSH_USERAUTH_BANNER packet");
+ } else {
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received SSH_USERAUTH_BANNER packet");
+ if(session->banner != NULL)
+ ssh_string_free(session->banner);
+ session->banner = banner;
+ }
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handles a SSH_USERAUTH_FAILURE packet.
+ *
+ * This handles the complete or partial authentication failure.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_userauth_failure){
+ char *auth_methods = NULL;
+ ssh_string auth;
+ uint8_t partial = 0;
+ (void) type;
+ (void) user;
+ enter_function();
+
+ auth = buffer_get_ssh_string(packet);
+ if (auth == NULL || buffer_get_u8(packet, &partial) != 1) {
+ ssh_set_error(session, SSH_FATAL,
+ "Invalid SSH_MSG_USERAUTH_FAILURE message");
+ session->auth_state=SSH_AUTH_STATE_ERROR;
+ goto end;
+ }
+
+ auth_methods = ssh_string_to_char(auth);
+ if (auth_methods == NULL) {
+ ssh_set_error_oom(session);
+ goto end;
+ }
+
+ if (partial) {
+ session->auth_state=SSH_AUTH_STATE_PARTIAL;
+ ssh_log(session,SSH_LOG_PROTOCOL,
+ "Partial success. Authentication that can continue: %s",
+ auth_methods);
+ } else {
+ session->auth_state=SSH_AUTH_STATE_FAILED;
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Access denied. Authentication that can continue: %s",
+ auth_methods);
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Access denied. Authentication that can continue: %s",
+ auth_methods);
+
+ session->auth_methods = 0;
+ }
+ if (strstr(auth_methods, "password") != NULL) {
+ session->auth_methods |= SSH_AUTH_METHOD_PASSWORD;
+ }
+ if (strstr(auth_methods, "keyboard-interactive") != NULL) {
+ session->auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
+ }
+ if (strstr(auth_methods, "publickey") != NULL) {
+ session->auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
+ }
+ if (strstr(auth_methods, "hostbased") != NULL) {
+ session->auth_methods |= SSH_AUTH_METHOD_HOSTBASED;
+ }
+
+end:
+ ssh_string_free(auth);
+ SAFE_FREE(auth_methods);
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handles a SSH_USERAUTH_SUCCESS packet.
+ *
+ * It is also used to communicate the new to the upper levels.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_userauth_success){
+ enter_function();
+ (void)packet;
+ (void)type;
+ (void)user;
+ ssh_log(session,SSH_LOG_PACKET,"Received SSH_USERAUTH_SUCCESS");
+ ssh_log(session,SSH_LOG_PROTOCOL,"Authentication successful");
+ session->auth_state=SSH_AUTH_STATE_SUCCESS;
+ session->session_state=SSH_SESSION_STATE_AUTHENTICATED;
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handles a SSH_USERAUTH_PK_OK or SSH_USERAUTH_INFO_REQUEST packet.
+ *
+ * Since the two types of packets share the same code, additional work is done
+ * to understand if we are in a public key or keyboard-interactive context.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok){
+ int rc;
+ enter_function();
+ ssh_log(session,SSH_LOG_PACKET,"Received SSH_USERAUTH_PK_OK/INFO_REQUEST");
+ if(session->auth_state==SSH_AUTH_STATE_KBDINT_SENT){
+ /* Assuming we are in keyboard-interactive context */
+ ssh_log(session,SSH_LOG_PACKET,"keyboard-interactive context, assuming SSH_USERAUTH_INFO_REQUEST");
+ rc=ssh_packet_userauth_info_request(session,type,packet,user);
+ } else {
+ session->auth_state=SSH_AUTH_STATE_PK_OK;
+ ssh_log(session,SSH_LOG_PACKET,"assuming SSH_USERAUTH_PK_OK");
+ rc=SSH_PACKET_USED;
+ }
+ leave_function();
+ return rc;
+}
+
+static int wait_auth_status(ssh_session session) {
+ int rc = SSH_AUTH_ERROR;
+
+ enter_function();
+
+ while (session->auth_state == SSH_AUTH_STATE_NONE ||
+ session->auth_state == SSH_AUTH_STATE_KBDINT_SENT) {
+ if (ssh_handle_packets(session,-1) != SSH_OK)
+ break;
+ }
+ switch(session->auth_state){
+ case SSH_AUTH_STATE_ERROR:
+ rc=SSH_AUTH_ERROR;
+ break;
+ case SSH_AUTH_STATE_FAILED:
+ rc=SSH_AUTH_DENIED;
+ break;
+ case SSH_AUTH_STATE_INFO:
+ rc=SSH_AUTH_INFO;
+ break;
+ case SSH_AUTH_STATE_PARTIAL:
+ rc=SSH_AUTH_PARTIAL;
+ break;
+ case SSH_AUTH_STATE_PK_OK:
+ case SSH_AUTH_STATE_SUCCESS:
+ rc=SSH_AUTH_SUCCESS;
+ break;
+ case SSH_AUTH_STATE_KBDINT_SENT:
+ case SSH_AUTH_STATE_NONE:
+ /* not reached */
+ rc=SSH_AUTH_ERROR;
+ break;
+ }
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief retrieves available authentication methods for this session
+ * @deprecated
+ * @see ssh_userauth_list
+ */
+int ssh_auth_list(ssh_session session) {
+ return ssh_userauth_list(session, NULL);
+}
+
+/**
+ * @brief retrieves available authentication methods for this session
+ * @param[in] session the SSH session
+ * @param[in] username set to NULL
+ * @returns A bitfield of values SSH_AUTH_METHOD_NONE, SSH_AUTH_METHOD_PASSWORD,
+ SSH_AUTH_METHOD_PUBLICKEY, SSH_AUTH_METHOD_HOSTBASED,
+ SSH_AUTH_METHOD_INTERACTIVE.
+ @warning Other reserved flags may appear in future versions.
+ */
+int ssh_userauth_list(ssh_session session, const char *username) {
+ if (session == NULL) {
+ return SSH_AUTH_ERROR;
+ }
+
+#ifdef WITH_SSH1
+ if(session->version==1){
+ return SSH_AUTH_METHOD_PASSWORD;
+ }
+#endif
+ if (session->auth_methods == 0) {
+ ssh_userauth_none(session, username);
+ }
+ return session->auth_methods;
+}
+
+/* use the "none" authentication question */
+
+/**
+ * @brief Try to authenticate through the "none" method.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] username Deprecated, set 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
+ */
+int ssh_userauth_none(ssh_session session, const char *username) {
+ ssh_string user = NULL;
+ ssh_string service = NULL;
+ ssh_string method = NULL;
+ int rc = SSH_AUTH_ERROR;
+
+ enter_function();
+
+#ifdef WITH_SSH1
+ if (session->version == 1) {
+ rc = ssh_userauth1_none(session, username);
+ leave_function();
+ return rc;
+ }
+#endif
+ if(session->auth_methods != 0){
+ /* userauth_none or other method was already tried before */
+ ssh_set_error(session,SSH_REQUEST_DENIED,"None method rejected by server");
+ leave_function();
+ return SSH_AUTH_DENIED;
+ }
+ if (username == NULL) {
+ if (session->username == NULL) {
+ if (ssh_options_apply(session) < 0) {
+ leave_function();
+ return rc;
+ }
+ }
+ user = ssh_string_from_char(session->username);
+ } else {
+ user = ssh_string_from_char(username);
+ }
+
+ if (user == NULL) {
+ leave_function();
+ return rc;
+ }
+
+ if (ask_userauth(session) < 0) {
+ ssh_string_free(user);
+ leave_function();
+ return rc;
+ }
+
+ method = ssh_string_from_char("none");
+ if (method == NULL) {
+ goto error;
+ }
+ service = ssh_string_from_char("ssh-connection");
+ if (service == 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) {
+ goto error;
+ }
+
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(user);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(user);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Try to authenticate through public key.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] 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[in] type The type of the public key. This value is given by
+ * publickey_from_file() or ssh_privatekey_type().
+ *
+ * @param[in] publickey A public key returned by publickey_from_file().
+ *
+ * @returns SSH_AUTH_ERROR: A serious error happened.\n
+ * SSH_AUTH_DENIED: The server doesn't accept that public key as an
+ * authentication token. Try another key or another
+ * method.\n
+ * SSH_AUTH_PARTIAL: You've been partially authenticated, you still
+ * have to use another method.\n
+ * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use
+ * ssh_userauth_pubkey().
+ *
+ * @see publickey_from_file()
+ * @see privatekey_from_file()
+ * @see ssh_privatekey_type()
+ * @see ssh_userauth_pubkey()
+ */
+int ssh_userauth_offer_pubkey(ssh_session session, const char *username,
+ int type, ssh_string publickey) {
+ ssh_string user = NULL;
+ ssh_string service = NULL;
+ ssh_string method = NULL;
+ ssh_string algo = NULL;
+ int rc = SSH_AUTH_ERROR;
+
+ enter_function();
+
+#ifdef WITH_SSH1
+ if (session->version == 1) {
+ ssh_userauth1_offer_pubkey(session, username, type, publickey);
+ leave_function();
+ return rc;
+ }
+#endif
+
+ if (username == NULL) {
+ if (session->username == NULL) {
+ if (ssh_options_apply(session) < 0) {
+ leave_function();
+ return rc;
+ }
+ }
+ user = ssh_string_from_char(session->username);
+ } else {
+ user = ssh_string_from_char(username);
+ }
+
+ if (user == NULL) {
+ leave_function();
+ return rc;
+ }
+
+ if (ask_userauth(session) < 0) {
+ ssh_string_free(user);
+ leave_function();
+ return rc;
+ }
+
+ service = ssh_string_from_char("ssh-connection");
+ if (service == NULL) {
+ goto error;
+ }
+ method = ssh_string_from_char("publickey");
+ if (method == NULL) {
+ goto error;
+ }
+ algo = ssh_string_from_char(ssh_type_to_char(type));
+ if (algo == 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, algo) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, publickey) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(user);
+ ssh_string_free(method);
+ ssh_string_free(service);
+ ssh_string_free(algo);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(user);
+ ssh_string_free(method);
+ ssh_string_free(service);
+ ssh_string_free(algo);
+
+ leave_function();
+ return rc;
+}
+
+
+/**
+ * @brief Try to authenticate through public key.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] 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[in] publickey A public key returned by publickey_from_file(), or NULL
+ * to generate automatically from privatekey.
+ *
+ * @param[in] privatekey A private key returned by privatekey_from_file().
+ *
+ * @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_pubkey(ssh_session session, const char *username,
+ ssh_string publickey, ssh_private_key privatekey) {
+ ssh_string user = NULL;
+ ssh_string service = NULL;
+ ssh_string method = NULL;
+ ssh_string algo = NULL;
+ ssh_string sign = NULL;
+ ssh_public_key pk = NULL;
+ ssh_string pkstr = NULL;
+ int rc = SSH_AUTH_ERROR;
+
+ enter_function();
+
+#if 0
+ if (session->version == 1) {
+ return ssh_userauth1_pubkey(session, username, publickey, privatekey);
+ }
+#endif
+
+ if (username == NULL) {
+ if (session->username == NULL) {
+ if (ssh_options_apply(session) < 0) {
+ leave_function();
+ return rc;
+ }
+ }
+ user = ssh_string_from_char(session->username);
+ } else {
+ user = ssh_string_from_char(username);
+ }
+
+ if (user == NULL) {
+ leave_function();
+ return rc;
+ }
+
+ if (ask_userauth(session) < 0) {
+ ssh_string_free(user);
+ leave_function();
+ return rc;
+ }
+
+ service = ssh_string_from_char("ssh-connection");
+ if (service == NULL) {
+ goto error;
+ }
+ method = ssh_string_from_char("publickey");
+ if (method == NULL) {
+ goto error;
+ }
+ algo = ssh_string_from_char(ssh_type_to_char(privatekey->type));
+ if (algo == NULL) {
+ goto error;
+ }
+ if (publickey == NULL) {
+ pk = publickey_from_privatekey(privatekey);
+ if (pk == NULL) {
+ goto error;
+ }
+ pkstr = publickey_to_string(pk);
+ publickey_free(pk);
+ if (pkstr == 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, (publickey == NULL ? pkstr : publickey)) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(user);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(algo);
+ ssh_string_free(pkstr);
+
+ sign = ssh_do_sign(session,session->out_buffer, privatekey);
+ if (sign) {
+ if (buffer_add_ssh_string(session->out_buffer,sign) < 0) {
+ goto error;
+ }
+ ssh_string_free(sign);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+ }
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(user);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(algo);
+ ssh_string_free(pkstr);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Try to authenticate through a private key file.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] 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[in] filename Filename containing the private key.
+ *
+ * @param[in] passphrase Passphrase to decrypt the private key. Set to null if
+ * none is needed or it is unknown.
+ *
+ * @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_pubkey()
+ */
+int ssh_userauth_privatekey_file(ssh_session session, const char *username,
+ const char *filename, const char *passphrase) {
+ char *pubkeyfile = NULL;
+ ssh_string pubkey = NULL;
+ ssh_private_key privkey = NULL;
+ int type = 0;
+ int rc = SSH_AUTH_ERROR;
+
+ enter_function();
+
+ pubkeyfile = malloc(strlen(filename) + 1 + 4);
+ if (pubkeyfile == NULL) {
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ sprintf(pubkeyfile, "%s.pub", filename);
+
+ pubkey = publickey_from_file(session, pubkeyfile, &type);
+ if (pubkey == NULL) {
+ ssh_log(session, SSH_LOG_RARE, "Public key file %s not found. Trying to generate it.", pubkeyfile);
+ /* auto-detect the key type with type=0 */
+ privkey = privatekey_from_file(session, filename, 0, passphrase);
+ } else {
+ ssh_log(session, SSH_LOG_RARE, "Public key file %s loaded.", pubkeyfile);
+ privkey = privatekey_from_file(session, filename, type, passphrase);
+ }
+ if (privkey == NULL) {
+ goto error;
+ }
+ /* ssh_userauth_pubkey is responsible for taking care of null-pubkey */
+ rc = ssh_userauth_pubkey(session, username, pubkey, privkey);
+ privatekey_free(privkey);
+
+error:
+ SAFE_FREE(pubkeyfile);
+ ssh_string_free(pubkey);
+
+ leave_function();
+ return rc;
+}
+
+#ifndef _WIN32
+/**
+ * @brief Try to authenticate through public key with an ssh agent.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] 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[in] 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 = ssh_string_from_char(session->username);
+ } else {
+ user = ssh_string_from_char(username);
+ }
+
+ if (user == NULL) {
+ leave_function();
+ return rc;
+ }
+
+ if (ask_userauth(session) < 0) {
+ ssh_string_free(user);
+ leave_function();
+ return rc;
+ }
+
+ service = ssh_string_from_char("ssh-connection");
+ if (service == NULL) {
+ goto error;
+ }
+ method = ssh_string_from_char("publickey");
+ if (method == NULL) {
+ goto error;
+ }
+ algo = ssh_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;
+ }
+ ssh_string_free(sign);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+ }
+
+ ssh_string_free(user);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(algo);
+ ssh_string_free(key);
+
+ leave_function();
+
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(sign);
+ ssh_string_free(user);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(algo);
+ ssh_string_free(key);
+
+ leave_function();
+ return rc;
+}
+#endif /* _WIN32 */
+
+/**
+ * @brief Try to authenticate by password.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] 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[in] 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 = ssh_string_from_char(session->username);
+ } else {
+ user = ssh_string_from_char(username);
+ }
+
+ if (user == NULL) {
+ leave_function();
+ return rc;
+ }
+
+ if (ask_userauth(session) < 0) {
+ ssh_string_free(user);
+ leave_function();
+ return rc;
+ }
+
+ service = ssh_string_from_char("ssh-connection");
+ if (service == NULL) {
+ goto error;
+ }
+ method = ssh_string_from_char("password");
+ if (method == NULL) {
+ goto error;
+ }
+ pwd = ssh_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;
+ }
+
+ ssh_string_free(user);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_burn(pwd);
+ ssh_string_free(pwd);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(user);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_burn(pwd);
+ ssh_string_free(pwd);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Tries to automatically authenticate with public key and "none"
+ *
+ * It may fail, for instance it doesn't ask for a password and uses a default
+ * asker for passphrases (in case the private key is encrypted).
+ *
+ * @param[in] session The ssh session to authenticate with.
+ *
+ * @param[in] passphrase Use this passphrase to unlock the privatekey. Use NULL
+ * if you don't want to use a passphrase or the user
+ * should be asked.
+ *
+ * @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
+ *
+ * @see ssh_userauth_kbdint()
+ * @see ssh_userauth_password()
+ */
+int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) {
+ struct ssh_iterator *it;
+ ssh_private_key privkey;
+ ssh_public_key pubkey;
+ ssh_string pubkey_string;
+ int type = 0;
+ int rc;
+
+ enter_function();
+
+ /* Always test none authentication */
+ rc = ssh_userauth_none(session, NULL);
+ if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_SUCCESS) {
+ leave_function();
+ return rc;
+ }
+
+ /* Try authentication with ssh-agent first */
+#ifndef _WIN32
+ if (agent_is_running(session)) {
+ char *privkey_file = NULL;
+
+ ssh_log(session, SSH_LOG_RARE,
+ "Trying to authenticate with SSH agent keys as user: %s",
+ session->username);
+
+ for (pubkey = agent_get_first_ident(session, &privkey_file);
+ pubkey != NULL;
+ pubkey = agent_get_next_ident(session, &privkey_file)) {
+
+ ssh_log(session, SSH_LOG_RARE, "Trying identity %s", privkey_file);
+
+ pubkey_string = publickey_to_string(pubkey);
+ if (pubkey_string) {
+ rc = ssh_userauth_offer_pubkey(session, NULL, pubkey->type, pubkey_string);
+ ssh_string_free(pubkey_string);
+ if (rc == SSH_AUTH_ERROR) {
+ SAFE_FREE(privkey_file);
+ publickey_free(pubkey);
+ leave_function();
+
+ return rc;
+ } else if (rc != SSH_AUTH_SUCCESS) {
+ ssh_log(session, SSH_LOG_PROTOCOL, "Public key refused by server");
+ SAFE_FREE(privkey_file);
+ publickey_free(pubkey);
+ continue;
+ }
+ ssh_log(session, SSH_LOG_PROTOCOL, "Public key accepted");
+ /* pubkey accepted by server ! */
+ rc = ssh_userauth_agent_pubkey(session, NULL, pubkey);
+ if (rc == SSH_AUTH_ERROR) {
+ SAFE_FREE(privkey_file);
+ publickey_free(pubkey);
+ leave_function();
+
+ return rc;
+ } else if (rc != SSH_AUTH_SUCCESS) {
+ ssh_log(session, SSH_LOG_RARE,
+ "Server accepted public key but refused the signature ;"
+ " It might be a bug of libssh");
+ SAFE_FREE(privkey_file);
+ publickey_free(pubkey);
+ continue;
+ }
+ /* auth success */
+ ssh_log(session, SSH_LOG_PROTOCOL, "Authentication using %s success",
+ privkey_file);
+ SAFE_FREE(privkey_file);
+ publickey_free(pubkey);
+
+ leave_function();
+
+ return SSH_AUTH_SUCCESS;
+ } /* if pubkey */
+ SAFE_FREE(privkey_file);
+ publickey_free(pubkey);
+ } /* for each privkey */
+ } /* if agent is running */
+#endif
+
+
+ for (it = ssh_list_get_iterator(session->identity);
+ it != NULL;
+ it = it->next) {
+ const char *privkey_file = it->data;
+ int privkey_open = 0;
+
+ privkey = NULL;
+
+ ssh_log(session, SSH_LOG_PROTOCOL, "Trying to read privatekey %s", privkey_file);
+
+ rc = ssh_try_publickey_from_file(session, privkey_file, &pubkey_string, &type);
+ if (rc == 1) {
+ char *publickey_file;
+ size_t len;
+
+ privkey = privatekey_from_file(session, privkey_file, type, passphrase);
+ if (privkey == NULL) {
+ ssh_log(session, SSH_LOG_RARE,
+ "Reading private key %s failed (bad passphrase ?)",
+ privkey_file);
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ privkey_open = 1;
+
+ pubkey = publickey_from_privatekey(privkey);
+ if (pubkey == NULL) {
+ privatekey_free(privkey);
+ ssh_set_error_oom(session);
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+
+ pubkey_string = publickey_to_string(pubkey);
+ type = pubkey->type;
+ publickey_free(pubkey);
+ if (pubkey_string == NULL) {
+ ssh_set_error_oom(session);
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+
+ len = strlen(privkey_file) + 5;
+ publickey_file = malloc(len);
+ if (publickey_file == NULL) {
+ ssh_set_error_oom(session);
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ snprintf(publickey_file, len, "%s.pub", privkey_file);
+ rc = ssh_publickey_to_file(session, publickey_file, pubkey_string, type);
+ if (rc < 0) {
+ ssh_log(session, SSH_LOG_PACKET,
+ "Could not write public key to file: %s", publickey_file);
+ }
+ SAFE_FREE(publickey_file);
+ } else if (rc < 0) {
+ continue;
+ }
+
+ rc = ssh_userauth_offer_pubkey(session, NULL, type, pubkey_string);
+ if (rc == SSH_AUTH_ERROR){
+ ssh_string_free(pubkey_string);
+ ssh_log(session, SSH_LOG_RARE, "Publickey authentication error");
+ leave_function();
+ return rc;
+ } else {
+ if (rc != SSH_AUTH_SUCCESS){
+ ssh_log(session, SSH_LOG_PROTOCOL, "Publickey refused by server");
+ ssh_string_free(pubkey_string);
+ continue;
+ }
+ }
+
+ /* Public key accepted by server! */
+ if (!privkey_open) {
+ ssh_log(session, SSH_LOG_PROTOCOL, "Trying to read privatekey %s",
+ privkey_file);
+ privkey = privatekey_from_file(session, privkey_file, type, passphrase);
+ if (privkey == NULL) {
+ ssh_log(session, SSH_LOG_RARE,
+ "Reading private key %s failed (bad passphrase ?)",
+ privkey_file);
+ ssh_string_free(pubkey_string);
+ continue; /* continue the loop with other pubkey */
+ }
+ }
+
+ rc = ssh_userauth_pubkey(session, NULL, pubkey_string, privkey);
+ if (rc == SSH_AUTH_ERROR) {
+ ssh_string_free(pubkey_string);
+ privatekey_free(privkey);
+ leave_function();
+ return rc;
+ } else {
+ if (rc != SSH_AUTH_SUCCESS){
+ ssh_log(session, SSH_LOG_RARE,
+ "The server accepted the public key but refused the signature");
+ ssh_string_free(pubkey_string);
+ privatekey_free(privkey);
+ continue;
+ }
+ }
+
+ /* auth success */
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Successfully authenticated using %s", privkey_file);
+ ssh_string_free(pubkey_string);
+ privatekey_free(privkey);
+
+ leave_function();
+ return SSH_AUTH_SUCCESS;
+ }
+
+ /* at this point, pubkey is NULL and so is privkeyfile */
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Tried every public key, none matched");
+ ssh_set_error(session,SSH_NO_ERROR,"No public key matched");
+
+ leave_function();
+ 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 kbd;
+
+ kbd = malloc(sizeof (struct ssh_kbdint_struct));
+ if (kbd == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(kbd);
+
+ return kbd;
+}
+
+
+static 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);
+
+ if (kbd->prompts) {
+ for (i = 0; i < n; i++) {
+ BURN_STRING(kbd->prompts[i]);
+ SAFE_FREE(kbd->prompts[i]);
+ }
+ SAFE_FREE(kbd->prompts);
+ }
+ if (kbd->answers) {
+ for (i = 0; i < n; i++) {
+ BURN_STRING(kbd->answers[i]);
+ SAFE_FREE(kbd->answers[i]);
+ }
+ SAFE_FREE(kbd->answers);
+ }
+
+ SAFE_FREE(kbd);
+}
+
+static 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);
+
+ if (kbd->prompts) {
+ for (i = 0; i < n; i++) {
+ BURN_STRING(kbd->prompts[i]);
+ SAFE_FREE(kbd->prompts[i]);
+ }
+ SAFE_FREE(kbd->prompts);
+ }
+
+ if (kbd->answers) {
+ for (i = 0; i < n; i++) {
+ BURN_STRING(kbd->answers[i]);
+ SAFE_FREE(kbd->answers[i]);
+ }
+ SAFE_FREE(kbd->answers);
+ }
+
+ kbd->nprompts = 0;
+}
+
+/* this function sends the first packet as explained in section 3.1
+ * of the draft */
+static int kbdauth_init(ssh_session session, const char *user,
+ const char *submethods) {
+ ssh_string usr = NULL;
+ ssh_string sub = NULL;
+ ssh_string service = NULL;
+ ssh_string method = NULL;
+ int rc = SSH_AUTH_ERROR;
+
+ enter_function();
+
+ usr = ssh_string_from_char(user);
+ if (usr == NULL) {
+ goto error;
+ }
+ sub = (submethods ? ssh_string_from_char(submethods) : ssh_string_from_char(""));
+ if (sub == NULL) {
+ goto error;
+ }
+ service = ssh_string_from_char("ssh-connection");
+ if (service == NULL) {
+ goto error;
+ }
+ method = ssh_string_from_char("keyboard-interactive");
+ if (method == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, usr) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, service) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, method) < 0 ||
+ buffer_add_u32(session->out_buffer, 0) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, sub) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(usr);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(sub);
+ session->auth_state=SSH_AUTH_STATE_KBDINT_SENT;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(usr);
+ ssh_string_free(service);
+ ssh_string_free(method);
+ ssh_string_free(sub);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @internal
+ * @brief handles a SSH_USERAUTH_INFO_REQUEST packet, as used in
+ * keyboard-interactive authentication, and changes the
+ * authentication state.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) {
+ ssh_string name; /* name of the "asking" window showed to client */
+ ssh_string instruction;
+ ssh_string tmp;
+ uint32_t nprompts;
+ uint32_t i;
+ (void)user;
+ (void)type;
+ enter_function();
+
+ name = buffer_get_ssh_string(packet);
+ instruction = buffer_get_ssh_string(packet);
+ tmp = buffer_get_ssh_string(packet);
+ buffer_get_u32(packet, &nprompts);
+
+ if (name == NULL || instruction == NULL || tmp == NULL) {
+ ssh_string_free(name);
+ ssh_string_free(instruction);
+ /* tmp if empty if we got here */
+ ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg");
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ ssh_string_free(tmp);
+
+ if (session->kbdint == NULL) {
+ session->kbdint = kbdint_new();
+ if (session->kbdint == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(name);
+ ssh_string_free(instruction);
+
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ } else {
+ kbdint_clean(session->kbdint);
+ }
+
+ session->kbdint->name = ssh_string_to_char(name);
+ ssh_string_free(name);
+ if (session->kbdint->name == NULL) {
+ ssh_set_error_oom(session);
+ kbdint_free(session->kbdint);
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ session->kbdint->instruction = ssh_string_to_char(instruction);
+ ssh_string_free(instruction);
+ if (session->kbdint->instruction == NULL) {
+ ssh_set_error_oom(session);
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ nprompts = ntohl(nprompts);
+ ssh_log(session,SSH_LOG_PACKET,"kbdint: %d prompts",nprompts);
+ if (nprompts > KBDINT_MAX_PROMPT) {
+ ssh_set_error(session, SSH_FATAL,
+ "Too much prompt asked from server: %u (0x%.4x)",
+ nprompts, nprompts);
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ session->kbdint->nprompts = nprompts;
+ session->kbdint->prompts = malloc(nprompts * sizeof(char *));
+ if (session->kbdint->prompts == NULL) {
+ session->kbdint->nprompts = 0;
+ ssh_set_error_oom(session);
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ memset(session->kbdint->prompts, 0, nprompts * sizeof(char *));
+
+ session->kbdint->echo = malloc(nprompts);
+ if (session->kbdint->echo == NULL) {
+ session->kbdint->nprompts = 0;
+ ssh_set_error_oom(session);
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ memset(session->kbdint->echo, 0, nprompts);
+
+ for (i = 0; i < nprompts; i++) {
+ tmp = buffer_get_ssh_string(packet);
+ buffer_get_u8(packet, &session->kbdint->echo[i]);
+ if (tmp == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet");
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ session->kbdint->prompts[i] = ssh_string_to_char(tmp);
+ ssh_string_free(tmp);
+ if (session->kbdint->prompts[i] == NULL) {
+ ssh_set_error_oom(session);
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ }
+ session->auth_state=SSH_AUTH_STATE_INFO;
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ * @brief Sends the current challenge response and wait for a
+ * reply from the server
+ * @returns SSH_AUTH_INFO if more info is needed
+ * @returns SSH_AUTH_SUCCESS
+ * @returns SSH_AUTH_FAILURE
+ * @returns SSH_AUTH_PARTIAL
+ */
+static int kbdauth_send(ssh_session session) {
+ ssh_string answer = NULL;
+ int rc = SSH_AUTH_ERROR;
+ uint32_t i;
+
+ enter_function();
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_INFO_RESPONSE) < 0 ||
+ buffer_add_u32(session->out_buffer,
+ htonl(session->kbdint->nprompts)) < 0) {
+ goto error;
+ }
+
+ for (i = 0; i < session->kbdint->nprompts; i++) {
+ if (session->kbdint->answers[i]) {
+ answer = ssh_string_from_char(session->kbdint->answers[i]);
+ } else {
+ answer = ssh_string_from_char("");
+ }
+ if (answer == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(session->out_buffer, answer) < 0) {
+ goto error;
+ }
+
+ ssh_string_burn(answer);
+ ssh_string_free(answer);
+ }
+ session->auth_state=SSH_AUTH_STATE_KBDINT_SENT;
+ kbdint_free(session->kbdint);
+ session->kbdint = NULL;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+ rc = wait_auth_status(session);
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_burn(answer);
+ ssh_string_free(answer);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Try to authenticate through the "keyboard-interactive" method.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] 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[in] 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);
+
+ leave_function();
+ return rc;
+ }
+
+ /*
+ * If we are at this point, it is 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);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Get the number of prompts (questions) the server has given.
+ *
+ * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This
+ * function returns the questions from the server.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @returns The number of prompts.
+ */
+int ssh_userauth_kbdint_getnprompts(ssh_session session) {
+ return session->kbdint->nprompts;
+}
+
+/**
+ * @brief Get the "name" of the message block.
+ *
+ * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This
+ * function returns the questions from the server.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @returns The name of the message block. Do not free it.
+ */
+const char *ssh_userauth_kbdint_getname(ssh_session session) {
+ return session->kbdint->name;
+}
+
+/**
+ * @brief Get the "instruction" of the message block.
+ *
+ * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This
+ * function returns the questions from the server.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @returns The instruction of the message block.
+ */
+
+const char *ssh_userauth_kbdint_getinstruction(ssh_session session) {
+ return session->kbdint->instruction;
+}
+
+/**
+ * @brief Get a prompt from a message block.
+ *
+ * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This
+ * function returns the questions from the server.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] i The index number of the i'th prompt.
+ *
+ * @param[in] echo When different of NULL, it will obtain a boolean meaning
+ * that the resulting user input should be echoed or not
+ * (like passwords).
+ *
+ * @returns A pointer to the prompt. Do not free it.
+ */
+const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i,
+ char *echo) {
+ if (i > session->kbdint->nprompts) {
+ return NULL;
+ }
+
+ if (echo) {
+ *echo = session->kbdint->echo[i];
+ }
+
+ return session->kbdint->prompts[i];
+}
+
+/**
+ * @brief Set the answer for a question from a message block.
+ *
+ * If you have called ssh_userauth_kbdint() and got SSH_AUTH_INFO, this
+ * function returns the questions from the server.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] i index The number of the ith prompt.
+ *
+ * @param[in] answer The answer to give to the server.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i,
+ const char *answer) {
+ if (session == NULL || answer == NULL || i > session->kbdint->nprompts) {
+ return -1;
+ }
+
+ if (session->kbdint->answers == NULL) {
+ session->kbdint->answers = malloc(sizeof(char*) * session->kbdint->nprompts);
+ if (session->kbdint->answers == NULL) {
+ return -1;
+ }
+ memset(session->kbdint->answers, 0, sizeof(char *) * session->kbdint->nprompts);
+ }
+
+ if (session->kbdint->answers[i]) {
+ BURN_STRING(session->kbdint->answers[i]);
+ SAFE_FREE(session->kbdint->answers[i]);
+ }
+
+ session->kbdint->answers[i] = strdup(answer);
+ if (session->kbdint->answers[i] == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/auth1.c b/src/auth1.c
new file mode 100644
index 00000000..06f05497
--- /dev/null
+++ b/src/auth1.c
@@ -0,0 +1,206 @@
+/*
+ * auth1.c - authentication with SSH-1 protocol
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2005-2008 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 <string.h>
+#include <stdlib.h>
+
+#include "libssh/priv.h"
+#include "libssh/ssh1.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.h"
+#include "libssh/session.h"
+#include "libssh/string.h"
+
+#ifdef WITH_SSH1
+static int wait_auth1_status(ssh_session session) {
+ enter_function();
+ /* wait for a packet */
+ while(session->auth_state == SSH_AUTH_STATE_NONE)
+ if (ssh_handle_packets(session,-1) != SSH_OK)
+ break;
+ ssh_log(session,SSH_LOG_PROTOCOL,"Auth state : %d",session->auth_state);
+ leave_function();
+ switch(session->auth_state) {
+ case SSH_AUTH_STATE_SUCCESS:
+ return SSH_AUTH_SUCCESS;
+ case SSH_AUTH_STATE_FAILED:
+ return SSH_AUTH_DENIED;
+ default:
+ return SSH_AUTH_ERROR;
+ }
+ return SSH_AUTH_ERROR;
+}
+
+void ssh_auth1_handler(ssh_session session, uint8_t type){
+ if(session->session_state != SSH_SESSION_STATE_AUTHENTICATING){
+ ssh_set_error(session,SSH_FATAL,"SSH_SMSG_SUCCESS or FAILED received in wrong state");
+ return;
+ }
+ if(type==SSH_SMSG_SUCCESS){
+ session->auth_state=SSH_AUTH_STATE_SUCCESS;
+ session->session_state=SSH_SESSION_STATE_AUTHENTICATED;
+ } else if(type==SSH_SMSG_FAILURE)
+ session->auth_state=SSH_AUTH_STATE_FAILED;
+}
+
+static int send_username(ssh_session session, const char *username) {
+ ssh_string user = NULL;
+ /* returns SSH_AUTH_SUCCESS or SSH_AUTH_DENIED */
+ if(session->auth_service_state == SSH_AUTH_SERVICE_USER_SENT) {
+ if(session->auth_state == SSH_AUTH_STATE_FAILED)
+ return SSH_AUTH_DENIED;
+ if(session->auth_state == SSH_AUTH_STATE_SUCCESS)
+ return SSH_AUTH_SUCCESS;
+ return SSH_AUTH_ERROR;
+ }
+
+ if (!username) {
+ if(!(username = session->username)) {
+ if (ssh_options_set(session, SSH_OPTIONS_USER, NULL) < 0) {
+ session->auth_service_state = SSH_AUTH_SERVICE_DENIED;
+ return SSH_ERROR;
+ } else {
+ username = session->username;
+ }
+ }
+ }
+ user = ssh_string_from_char(username);
+ if (user == NULL) {
+ return SSH_AUTH_ERROR;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_USER) < 0) {
+ ssh_string_free(user);
+ return SSH_AUTH_ERROR;
+ }
+ if (buffer_add_ssh_string(session->out_buffer, user) < 0) {
+ ssh_string_free(user);
+ return SSH_AUTH_ERROR;
+ }
+ ssh_string_free(user);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ return SSH_AUTH_ERROR;
+ }
+
+ if(wait_auth1_status(session) == SSH_AUTH_SUCCESS){
+ session->auth_service_state=SSH_AUTH_SERVICE_USER_SENT;
+ session->auth_state=SSH_AUTH_STATE_SUCCESS;
+ return SSH_AUTH_SUCCESS;
+ } else {
+ session->auth_service_state=SSH_AUTH_SERVICE_USER_SENT;
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Password authentication necessary for user %s",username);
+ return SSH_AUTH_DENIED;
+ }
+
+}
+
+/* use the "none" authentication question */
+int ssh_userauth1_none(ssh_session session, const char *username){
+ return send_username(session, username);
+}
+
+/** \internal
+ * \todo implement ssh1 public key
+ */
+int ssh_userauth1_offer_pubkey(ssh_session session, const char *username,
+ int type, ssh_string pubkey) {
+ (void) session;
+ (void) username;
+ (void) type;
+ (void) pubkey;
+ enter_function();
+ leave_function();
+ return SSH_AUTH_DENIED;
+}
+
+int ssh_userauth1_password(ssh_session session, const char *username,
+ const char *password) {
+ ssh_string pwd = NULL;
+ int rc;
+ enter_function();
+ rc = send_username(session, username);
+ if (rc != SSH_AUTH_DENIED) {
+ leave_function();
+ return rc;
+ }
+
+ /* we trick a bit here. A known flaw in SSH1 protocol is that it's
+ * easy to guess password sizes.
+ * not that sure ...
+ */
+
+ /* XXX fix me here ! */
+ /* cisco IOS doesn't like when a password is followed by zeroes and random pad. */
+ if(1 || strlen(password) >= 128) {
+ /* not risky to disclose the size of such a big password .. */
+ pwd = ssh_string_from_char(password);
+ if (pwd == NULL) {
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ } else {
+ /* fill the password string from random things. the strcpy
+ * ensure there is at least a nul byte after the password.
+ * most implementation won't see the garbage at end.
+ * why garbage ? because nul bytes will be compressed by
+ * gzip and disclose password len.
+ */
+ pwd = ssh_string_new(128);
+ if (pwd == NULL) {
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ ssh_get_random( pwd->string, 128, 0);
+ strcpy((char *) pwd->string, password);
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_AUTH_PASSWORD) < 0) {
+ ssh_string_burn(pwd);
+ ssh_string_free(pwd);
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ if (buffer_add_ssh_string(session->out_buffer, pwd) < 0) {
+ ssh_string_burn(pwd);
+ ssh_string_free(pwd);
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+
+ ssh_string_burn(pwd);
+ ssh_string_free(pwd);
+ session->auth_state=SSH_AUTH_STATE_NONE;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return SSH_AUTH_ERROR;
+ }
+ rc = wait_auth1_status(session);
+ leave_function();
+ return rc;
+}
+
+#endif /* WITH_SSH1 */
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 00000000..262c97ca
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,287 @@
+/*
+ * base64.c - support for base64 alphabet system, described in RFC1521
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2005-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.
+ */
+
+/* just the dirtiest part of code i ever made */
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libssh/priv.h"
+#include "libssh/buffer.h"
+
+static char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+/* Transformations */
+#define SET_A(n, i) do { (n) |= ((i) & 63) <<18; } while (0)
+#define SET_B(n, i) do { (n) |= ((i) & 63) <<12; } while (0)
+#define SET_C(n, i) do { (n) |= ((i) & 63) << 6; } while (0)
+#define SET_D(n, i) do { (n) |= ((i) & 63); } while (0)
+
+#define GET_A(n) (((n) & 0xff0000) >> 16)
+#define GET_B(n) (((n) & 0xff00) >> 8)
+#define GET_C(n) ((n) & 0xff)
+
+static int _base64_to_bin(unsigned char dest[3], const char *source, int num);
+static int get_equals(char *string);
+
+/* First part: base64 to binary */
+
+/**
+ * @internal
+ *
+ * @brief Translates a base64 string into a binary one.
+ *
+ * @returns A buffer containing the decoded string, NULL if something went
+ * wrong (e.g. incorrect char).
+ */
+ssh_buffer base64_to_bin(const char *source) {
+ ssh_buffer buffer = NULL;
+ unsigned char block[3];
+ char *base64;
+ char *ptr;
+ size_t len;
+ int equals;
+
+ base64 = strdup(source);
+ if (base64 == NULL) {
+ return NULL;
+ }
+ ptr = base64;
+
+ /* Get the number of equals signs, which mirrors the padding */
+ equals = get_equals(ptr);
+ if (equals > 2) {
+ SAFE_FREE(base64);
+ return NULL;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ SAFE_FREE(base64);
+ return NULL;
+ }
+
+ len = strlen(ptr);
+ while (len > 4) {
+ if (_base64_to_bin(block, ptr, 3) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(buffer, block, 3) < 0) {
+ goto error;
+ }
+ len -= 4;
+ ptr += 4;
+ }
+
+ /*
+ * Depending on the number of bytes resting, there are 3 possibilities
+ * from the RFC.
+ */
+ switch (len) {
+ /*
+ * (1) The final quantum of encoding input is an integral multiple of
+ * 24 bits. Here, the final unit of encoded output will be an integral
+ * multiple of 4 characters with no "=" padding
+ */
+ case 4:
+ if (equals != 0) {
+ goto error;
+ }
+ if (_base64_to_bin(block, ptr, 3) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(buffer, block, 3) < 0) {
+ goto error;
+ }
+ SAFE_FREE(base64);
+
+ return buffer;
+ /*
+ * (2) The final quantum of encoding input is exactly 8 bits; here, the
+ * final unit of encoded output will be two characters followed by
+ * two "=" padding characters.
+ */
+ case 2:
+ if (equals != 2){
+ goto error;
+ }
+
+ if (_base64_to_bin(block, ptr, 1) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(buffer, block, 1) < 0) {
+ goto error;
+ }
+ SAFE_FREE(base64);
+
+ return buffer;
+ /*
+ * The final quantum of encoding input is exactly 16 bits. Here, the final
+ * unit of encoded output will be three characters followed by one "="
+ * padding character.
+ */
+ case 3:
+ if (equals != 1) {
+ goto error;
+ }
+ if (_base64_to_bin(block, ptr, 2) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(buffer,block,2) < 0) {
+ goto error;
+ }
+ SAFE_FREE(base64);
+
+ return buffer;
+ default:
+ /* 4,3,2 are the only padding size allowed */
+ goto error;
+ }
+
+error:
+ SAFE_FREE(base64);
+ ssh_buffer_free(buffer);
+ return NULL;
+}
+
+#define BLOCK(letter, n) do {ptr = strchr(alphabet, source[n]); \
+ if(!ptr) return -1; \
+ i = ptr - alphabet; \
+ SET_##letter(*block, i); \
+ } while(0)
+
+/* Returns 0 if ok, -1 if not (ie invalid char into the stuff) */
+static int to_block4(unsigned long *block, const char *source, int num) {
+ char *ptr;
+ unsigned int i;
+
+ *block = 0;
+ if (num < 1) {
+ return 0;
+ }
+
+ BLOCK(A, 0); /* 6 bit */
+ BLOCK(B,1); /* 12 bit */
+
+ if (num < 2) {
+ return 0;
+ }
+
+ BLOCK(C, 2); /* 18 bit */
+
+ if (num < 3) {
+ return 0;
+ }
+
+ BLOCK(D, 3); /* 24 bit */
+
+ return 0;
+}
+
+/* num = numbers of final bytes to be decoded */
+static int _base64_to_bin(unsigned char dest[3], const char *source, int num) {
+ unsigned long block;
+
+ if (to_block4(&block, source, num) < 0) {
+ return -1;
+ }
+ dest[0] = GET_A(block);
+ dest[1] = GET_B(block);
+ dest[2] = GET_C(block);
+
+ return 0;
+}
+
+/* Count the number of "=" signs and replace them by zeroes */
+static int get_equals(char *string) {
+ char *ptr = string;
+ int num = 0;
+
+ while ((ptr=strchr(ptr,'=')) != NULL) {
+ num++;
+ *ptr = '\0';
+ ptr++;
+ }
+
+ return num;
+}
+
+/* thanks sysk for debugging my mess :) */
+#define BITS(n) ((1 << (n)) - 1)
+static void _bin_to_base64(unsigned char *dest, const unsigned char source[3],
+ int len) {
+ switch (len) {
+ case 1:
+ dest[0] = alphabet[(source[0] >> 2)];
+ dest[1] = alphabet[((source[0] & BITS(2)) << 4)];
+ dest[2] = '=';
+ dest[3] = '=';
+ break;
+ case 2:
+ dest[0] = alphabet[source[0] >> 2];
+ dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)];
+ dest[2] = alphabet[(source[1] & BITS(4)) << 2];
+ dest[3] = '=';
+ break;
+ case 3:
+ dest[0] = alphabet[(source[0] >> 2)];
+ dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)];
+ dest[2] = alphabet[ (source[2] >> 6) | (source[1] & BITS(4)) << 2];
+ dest[3] = alphabet[source[2] & BITS(6)];
+ break;
+ }
+}
+
+/**
+ * @internal
+ *
+ * @brief Converts binary data to a base64 string.
+ *
+ * @returns the converted string
+ */
+unsigned char *bin_to_base64(const unsigned char *source, int len) {
+ unsigned char *base64;
+ unsigned char *ptr;
+ int flen = len + (3 - (len % 3)); /* round to upper 3 multiple */
+ flen = (4 * flen) / 3 + 1;
+
+ base64 = malloc(flen);
+ if (base64 == NULL) {
+ return NULL;
+ }
+ ptr = base64;
+
+ while(len > 0){
+ _bin_to_base64(ptr, source, len > 3 ? 3 : len);
+ ptr += 4;
+ source += 3;
+ len -= 3;
+ }
+ ptr[0] = '\0';
+
+ return base64;
+}
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 00000000..80c99560
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,578 @@
+/*
+ * buffer.c - buffer functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2009 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 <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/buffer.h"
+
+/**
+ * @defgroup libssh_buffer The SSH buffer functions.
+ * @ingroup libssh
+ *
+ * Functions to handle SSH buffers.
+ *
+ * @{
+ */
+
+
+#ifdef DEBUG_BUFFER
+/**
+ * @internal
+ *
+ * @brief Check that preconditions and postconditions are valid.
+ *
+ * @param[in] buf The buffer to check.
+ */
+static void buffer_verify(struct buffer_struct *buf){
+ int doabort=0;
+ if(buf->data == NULL)
+ return;
+ if(buf->used > buf->allocated){
+ fprintf(stderr,"Buffer error : allocated %u, used %u\n",buf->allocated, buf->used);
+ doabort=1;
+ }
+ if(buf->pos > buf->used){
+ fprintf(stderr,"Buffer error : position %u, used %u\n",buf->pos, buf->used);
+ doabort=1;
+ }
+ if(buf->pos > buf->allocated){
+ fprintf(stderr,"Buffer error : position %u, allocated %u\n",buf->pos, buf->allocated);
+ doabort=1;
+ }
+ if(doabort)
+ abort();
+}
+
+#else
+#define buffer_verify(x)
+#endif
+
+/**
+ * @brief Create a new SSH buffer.
+ *
+ * @return A newly initialized SSH buffer, NULL on error.
+ */
+struct ssh_buffer_struct *ssh_buffer_new(void) {
+ struct ssh_buffer_struct *buf = malloc(sizeof(struct ssh_buffer_struct));
+
+ if (buf == NULL) {
+ return NULL;
+ }
+ memset(buf, 0, sizeof(struct ssh_buffer_struct));
+ buffer_verify(buf);
+ return buf;
+}
+
+/**
+ * @brief Deallocate a SSH buffer.
+ *
+ * \param[in] buffer The buffer to free.
+ */
+void ssh_buffer_free(struct ssh_buffer_struct *buffer) {
+ if (buffer == NULL) {
+ return;
+ }
+ buffer_verify(buffer);
+
+ if (buffer->data) {
+ /* burn the data */
+ memset(buffer->data, 0, buffer->allocated);
+ SAFE_FREE(buffer->data);
+ }
+ memset(buffer, 'X', sizeof(*buffer));
+ SAFE_FREE(buffer);
+}
+
+static int realloc_buffer(struct ssh_buffer_struct *buffer, int needed) {
+ int smallest = 1;
+ char *new = NULL;
+ buffer_verify(buffer);
+ /* Find the smallest power of two which is greater or equal to needed */
+ while(smallest < needed) {
+ smallest <<= 1;
+ }
+ needed = smallest;
+ new = realloc(buffer->data, needed);
+ if (new == NULL) {
+ return -1;
+ }
+ buffer->data = new;
+ buffer->allocated = needed;
+ buffer_verify(buffer);
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Reinitialize a SSH buffer.
+ *
+ * @param[in] buffer The buffer to reinitialize.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int buffer_reinit(struct ssh_buffer_struct *buffer) {
+ buffer_verify(buffer);
+ memset(buffer->data, 0, buffer->used);
+ buffer->used = 0;
+ buffer->pos = 0;
+ if(buffer->allocated > 127) {
+ if (realloc_buffer(buffer, 127) < 0) {
+ return -1;
+ }
+ }
+ buffer_verify(buffer);
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add data at the tail of a buffer.
+ *
+ * @param[in] buffer The buffer to add the data.
+ *
+ * @param[in] data A pointer to the data to add.
+ *
+ * @param[in] len The length of the data to add.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint32_t len) {
+ buffer_verify(buffer);
+ if (buffer->allocated < (buffer->used + len)) {
+ if (realloc_buffer(buffer, buffer->used + len) < 0) {
+ return -1;
+ }
+ }
+
+ memcpy(buffer->data+buffer->used, data, len);
+ buffer->used+=len;
+ buffer_verify(buffer);
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add a SSH string to the tail of a buffer.
+ *
+ * @param[in] buffer The buffer to add the string.
+ *
+ * @param[in] string The SSH String to add.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int buffer_add_ssh_string(struct ssh_buffer_struct *buffer,
+ struct ssh_string_struct *string) {
+ uint32_t len = 0;
+
+ len = ssh_string_len(string);
+ if (buffer_add_data(buffer, string, len + sizeof(uint32_t)) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add a 32 bits unsigned integer to the tail of a buffer.
+ *
+ * @param[in] buffer The buffer to add the integer.
+ *
+ * @param[in] data The 32 bits integer to add.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int buffer_add_u32(struct ssh_buffer_struct *buffer,uint32_t data){
+ if (buffer_add_data(buffer, &data, sizeof(data)) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add a 16 bits unsigned integer to the tail of a buffer.
+ *
+ * @param[in] buffer The buffer to add the integer.
+ *
+ * @param[in] data The 16 bits integer to add.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int buffer_add_u16(struct ssh_buffer_struct *buffer,uint16_t data){
+ if (buffer_add_data(buffer, &data, sizeof(data)) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add a 64 bits unsigned integer to the tail of a buffer.
+ *
+ * @param[in] buffer The buffer to add the integer.
+ *
+ * @param[in] data The 64 bits integer to add.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int buffer_add_u64(struct ssh_buffer_struct *buffer, uint64_t data){
+ if (buffer_add_data(buffer, &data, sizeof(data)) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add a 8 bits unsigned integer to the tail of a buffer.
+ *
+ * @param[in] buffer The buffer to add the integer.
+ *
+ * @param[in] data The 8 bits integer to add.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int buffer_add_u8(struct ssh_buffer_struct *buffer,uint8_t data){
+ if (buffer_add_data(buffer, &data, sizeof(uint8_t)) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Add data at the head of a buffer.
+ *
+ * @param[in] buffer The buffer to add the data.
+ *
+ * @param[in] data The data to prepend.
+ *
+ * @param[in] len The length of data to prepend.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int buffer_prepend_data(struct ssh_buffer_struct *buffer, const void *data,
+ uint32_t len) {
+ buffer_verify(buffer);
+ if (buffer->allocated < (buffer->used + len)) {
+ if (realloc_buffer(buffer, buffer->used + len) < 0) {
+ return -1;
+ }
+ }
+ memmove(buffer->data + len, buffer->data, buffer->used);
+ memcpy(buffer->data, data, len);
+ buffer->used += len;
+ buffer_verify(buffer);
+ return 0;
+}
+
+/**
+ * @internal
+ *
+ * @brief Append data from a buffer to the tail of another buffer.
+ *
+ * @param[in] buffer The destination buffer.
+ *
+ * @param[in] source The source buffer to append. It doesn't take the
+ * position of the buffer into account.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int buffer_add_buffer(struct ssh_buffer_struct *buffer,
+ struct ssh_buffer_struct *source) {
+ if (buffer_add_data(buffer, ssh_buffer_get_begin(source), ssh_buffer_get_len(source)) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Get a pointer on the head of a buffer.
+ *
+ * @param[in] buffer The buffer to get the head pointer.
+ *
+ * @return A data pointer on the head. It doesn't take the position
+ * into account.
+ *
+ * @warning Don't expect data to be nul-terminated.
+ *
+ * @see buffer_get_rest()
+ * @see buffer_get_len()
+ */
+void *ssh_buffer_get_begin(struct ssh_buffer_struct *buffer){
+ return buffer->data;
+}
+
+/**
+ * @internal
+ *
+ * @brief Get a pointer to the head of a buffer at the current position.
+ *
+ * @param[in] buffer The buffer to get the head pointer.
+ *
+ * @return A pointer to the data from current position.
+ *
+ * @see buffer_get_rest_len()
+ * @see buffer_get()
+ */
+void *buffer_get_rest(struct ssh_buffer_struct *buffer){
+ return buffer->data + buffer->pos;
+}
+
+/**
+ * @brief Get the length of the buffer, not counting position.
+ *
+ * @param[in] buffer The buffer to get the length from.
+ *
+ * @return The length of the buffer.
+ *
+ * @see buffer_get()
+ */
+uint32_t ssh_buffer_get_len(struct ssh_buffer_struct *buffer){
+ return buffer->used;
+}
+
+/**
+ * @internal
+ *
+ * @brief Get the length of the buffer from the current position.
+ *
+ * @param[in] buffer The buffer to get the length from.
+ *
+ * @return The length of the buffer.
+ *
+ * @see buffer_get_rest()
+ */
+uint32_t buffer_get_rest_len(struct ssh_buffer_struct *buffer){
+ buffer_verify(buffer);
+ return buffer->used - buffer->pos;
+}
+
+/**
+ * @internal
+ *
+ * @brief Advance the position in the buffer.
+ *
+ * This has effect to "eat" bytes at head of the buffer.
+ *
+ * @param[in] buffer The buffer to advance the position.
+ *
+ * @param[in] len The number of bytes to eat.
+ *
+ * @return The new size of the buffer.
+ */
+uint32_t buffer_pass_bytes(struct ssh_buffer_struct *buffer, uint32_t len){
+ buffer_verify(buffer);
+ if(buffer->used < buffer->pos+len)
+ return 0;
+ buffer->pos+=len;
+ /* if the buffer is empty after having passed the whole bytes into it, we can clean it */
+ if(buffer->pos==buffer->used){
+ buffer->pos=0;
+ buffer->used=0;
+ }
+ buffer_verify(buffer);
+ return len;
+}
+
+/**
+ * @internal
+ *
+ * @brief Cut the end of the buffer.
+ *
+ * @param[in] buffer The buffer to cut.
+ *
+ * @param[in] len The number of bytes to remove from the tail.
+ *
+ * @return The new size of the buffer.
+ */
+uint32_t buffer_pass_bytes_end(struct ssh_buffer_struct *buffer, uint32_t len){
+ buffer_verify(buffer);
+ if(buffer->used < buffer->pos + len)
+ return 0;
+ buffer->used-=len;
+ buffer_verify(buffer);
+ return len;
+}
+
+/**
+ * @internal
+ *
+ * @brief Get the remaining data out of the buffer and adjust the read pointer.
+ *
+ * @param[in] buffer The buffer to read.
+ *
+ * @param[in] data The data buffer where to store the data.
+ *
+ * @param[in] len The length to read from the buffer.
+ *
+ * @returns 0 if there is not enough data in buffer, len otherwise.
+ */
+uint32_t buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint32_t len){
+ /*
+ * Check for a integer overflow first, then check if not enough data is in
+ * the buffer.
+ */
+ if (buffer->pos + len < len || buffer->pos + len > buffer->used) {
+ return 0;
+ }
+ memcpy(data,buffer->data+buffer->pos,len);
+ buffer->pos+=len;
+ return len; /* no yet support for partial reads (is it really needed ?? ) */
+}
+
+/**
+ * @internal
+ *
+ * @brief Get a 8 bits unsigned int out of the buffer and adjusts the read
+ * pointer.
+ *
+ * @param[in] buffer The buffer to read.
+ *
+ * @param[in] data A pointer to a uint8_t where to store the data.
+ *
+ * @returns 0 if there is not enough data in buffer, 1 otherwise.
+ */
+int buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){
+ return buffer_get_data(buffer,data,sizeof(uint8_t));
+}
+
+/** \internal
+ * \brief gets a 32 bits unsigned int out of the buffer. Adjusts the read pointer.
+ * \param buffer Buffer to read
+ * \param data pointer to a uint32_t where to store the data
+ * \returns 0 if there is not enough data in buffer
+ * \returns 4 otherwise.
+ */
+int buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){
+ return buffer_get_data(buffer,data,sizeof(uint32_t));
+}
+/**
+ * @internal
+ *
+ * @brief Get a 64 bits unsigned int out of the buffer and adjusts the read
+ * pointer.
+ *
+ * @param[in] buffer The buffer to read.
+ *
+ * @param[in] data A pointer to a uint64_t where to store the data.
+ *
+ * @returns 0 if there is not enough data in buffer, 8 otherwise.
+ */
+int buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){
+ return buffer_get_data(buffer,data,sizeof(uint64_t));
+}
+
+/**
+ * @internal
+ *
+ * @brief Get a SSH String out of the buffer and adjusts the read pointer.
+ *
+ * @param[in] buffer The buffer to read.
+ *
+ * @returns The SSH String, NULL on error.
+ */
+struct ssh_string_struct *buffer_get_ssh_string(struct ssh_buffer_struct *buffer) {
+ uint32_t stringlen;
+ uint32_t hostlen;
+ struct ssh_string_struct *str = NULL;
+
+ if (buffer_get_u32(buffer, &stringlen) == 0) {
+ return NULL;
+ }
+ hostlen = ntohl(stringlen);
+ /* verify if there is enough space in buffer to get it */
+ if ((buffer->pos + hostlen) > buffer->used) {
+ return NULL; /* it is indeed */
+ }
+ str = ssh_string_new(hostlen);
+ if (str == NULL) {
+ return NULL;
+ }
+ if (buffer_get_data(buffer, ssh_string_data(str), hostlen) != hostlen) {
+ /* should never happen */
+ SAFE_FREE(str);
+ return NULL;
+ }
+
+ return str;
+}
+
+/**
+ * @internal
+ *
+ * @brief Get a mpint out of the buffer and adjusts the read pointer.
+ *
+ * @note This function is SSH-1 only.
+ *
+ * @param[in] buffer The buffer to read.
+ *
+ * @returns The SSH String containing the mpint, NULL on error.
+ */
+struct ssh_string_struct *buffer_get_mpint(struct ssh_buffer_struct *buffer) {
+ uint16_t bits;
+ uint32_t len;
+ struct ssh_string_struct *str = NULL;
+
+ if (buffer_get_data(buffer, &bits, sizeof(uint16_t)) != sizeof(uint16_t)) {
+ return NULL;
+ }
+ bits = ntohs(bits);
+ len = (bits + 7) / 8;
+ if ((buffer->pos + len) > buffer->used) {
+ return NULL;
+ }
+ str = ssh_string_new(len);
+ if (str == NULL) {
+ return NULL;
+ }
+ if (buffer_get_data(buffer, ssh_string_data(str), len) != len) {
+ SAFE_FREE(str);
+ return NULL;
+ }
+ return str;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/callbacks.c b/src/callbacks.c
new file mode 100644
index 00000000..1568d516
--- /dev/null
+++ b/src/callbacks.c
@@ -0,0 +1,43 @@
+/*
+ * callbacks.c - callback functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 "libssh/callbacks.h"
+#include "libssh/session.h"
+
+int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) {
+ if (session == NULL || cb == NULL) {
+ return SSH_ERROR;
+ }
+ enter_function();
+ if(cb->size <= 0 || cb->size > 1024 * sizeof(void *)){
+ ssh_set_error(session,SSH_FATAL,
+ "Invalid callback passed in (badly initialized)");
+ leave_function();
+ return SSH_ERROR;
+ }
+ session->callbacks = cb;
+ leave_function();
+ return 0;
+}
diff --git a/src/channels.c b/src/channels.c
new file mode 100644
index 00000000..6900beea
--- /dev/null
+++ b/src/channels.c
@@ -0,0 +1,2495 @@
+/*
+ * channels.c - SSH channel functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 by Aris Adamantiadis
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.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/messages.h"
+
+#define WINDOWBASE 128000
+#define WINDOWLIMIT (WINDOWBASE/2)
+
+/**
+ * @defgroup libssh_channel The SSH channel functions
+ * @ingroup libssh
+ *
+ * Functions that manage a SSH channel.
+ *
+ * @{
+ */
+
+static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet);
+
+/**
+ * @brief Allocate a new channel.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @return A pointer to a newly allocated channel, NULL on error.
+ */
+ssh_channel ssh_channel_new(ssh_session session) {
+ ssh_channel channel = NULL;
+
+ channel = malloc(sizeof(struct ssh_channel_struct));
+ if (channel == NULL) {
+ return NULL;
+ }
+ memset(channel,0,sizeof(struct ssh_channel_struct));
+
+ channel->stdout_buffer = ssh_buffer_new();
+ if (channel->stdout_buffer == NULL) {
+ SAFE_FREE(channel);
+ return NULL;
+ }
+
+ channel->stderr_buffer = ssh_buffer_new();
+ if (channel->stderr_buffer == NULL) {
+ ssh_buffer_free(channel->stdout_buffer);
+ SAFE_FREE(channel);
+ return NULL;
+ }
+
+ channel->session = session;
+ channel->version = session->version;
+ channel->exit_status = -1;
+
+ if(session->channels == NULL) {
+ session->channels = channel;
+ channel->next = channel->prev = channel;
+ return channel;
+ }
+ channel->next = session->channels;
+ channel->prev = session->channels->prev;
+ channel->next->prev = channel;
+ channel->prev->next = channel;
+
+ return channel;
+}
+
+/**
+ * @internal
+ *
+ * @brief Create a new channel identifier.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @return The new channel identifier.
+ */
+uint32_t ssh_channel_new_id(ssh_session session) {
+ return ++(session->maxchannel);
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet.
+ *
+ * Constructs the channel object.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){
+ uint32_t channelid=0;
+ uint32_t tmp;
+ ssh_channel channel;
+ (void)type;
+ (void)user;
+ enter_function();
+ ssh_log(session,SSH_LOG_PACKET,"Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION");
+
+ buffer_get_u32(packet, &channelid);
+ channelid=ntohl(channelid);
+ channel=ssh_channel_from_local(session,channelid);
+ if(channel==NULL){
+ ssh_set_error(session, SSH_FATAL,
+ "Unknown channel id %lu",
+ (long unsigned int) channelid);
+ /* TODO: Set error marking in channel object */
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ buffer_get_u32(packet, &tmp);
+ channel->remote_channel = ntohl(tmp);
+
+ buffer_get_u32(packet, &tmp);
+ channel->remote_window = ntohl(tmp);
+
+ buffer_get_u32(packet,&tmp);
+ channel->remote_maxpacket=ntohl(tmp);
+
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d",
+ channel->local_channel,
+ channel->remote_channel);
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Remote window : %lu, maxpacket : %lu",
+ (long unsigned int) channel->remote_window,
+ (long unsigned int) channel->remote_maxpacket);
+
+ channel->state = SSH_CHANNEL_STATE_OPEN;
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){
+
+ ssh_channel channel;
+ ssh_string error_s;
+ char *error = NULL;
+ uint32_t code;
+ (void)user;
+ (void)type;
+ channel=channel_from_msg(session,packet);
+ if(channel==NULL){
+ ssh_log(session,SSH_LOG_RARE,"Invalid channel in packet");
+ return SSH_PACKET_USED;
+ }
+ buffer_get_u32(packet, &code);
+
+ error_s = buffer_get_ssh_string(packet);
+ if(error_s != NULL)
+ error = ssh_string_to_char(error_s);
+ ssh_string_free(error_s);
+ if (error == NULL) {
+ ssh_set_error_oom(session);
+ return SSH_PACKET_USED;
+ }
+
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Channel opening failure: channel %u error (%lu) %s",
+ channel->local_channel,
+ (long unsigned int) ntohl(code),
+ error);
+ SAFE_FREE(error);
+ channel->state=SSH_CHANNEL_STATE_OPEN_DENIED;
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and
+ * wait for the reply.
+ *
+ * @param[in] channel The current channel.
+ *
+ * @param[in] type_c A C string describing the kind of channel (e.g. "exec").
+ *
+ * @param[in] window The receiving window of the channel. The window is the
+ * maximum size of data that can stay in buffers and
+ * network.
+ *
+ * @param[in] maxpacket The maximum packet size allowed (like MTU).
+ *
+ * @param[in] payload The buffer containing additional payload for the query.
+ */
+static int channel_open(ssh_channel channel, const char *type_c, int window,
+ int maxpacket, ssh_buffer payload) {
+ ssh_session session = channel->session;
+ ssh_string type = NULL;
+ int err=SSH_ERROR;
+
+ enter_function();
+ channel->local_channel = ssh_channel_new_id(session);
+ channel->local_maxpacket = maxpacket;
+ channel->local_window = window;
+
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Creating a channel %d with %d window and %d max packet",
+ channel->local_channel, window, maxpacket);
+
+ type = ssh_string_from_char(type_c);
+ if (type == NULL) {
+ leave_function();
+ return err;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN) < 0 ||
+ buffer_add_ssh_string(session->out_buffer,type) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(channel->local_channel)) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(channel->local_window)) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(channel->local_maxpacket)) < 0) {
+ ssh_string_free(type);
+ leave_function();
+ return err;
+ }
+
+ ssh_string_free(type);
+
+ if (payload != NULL) {
+ if (buffer_add_buffer(session->out_buffer, payload) < 0) {
+ leave_function();
+ return err;
+ }
+ }
+
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return err;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d",
+ type_c, channel->local_channel);
+
+ /* Todo: fix this into a correct loop */
+ /* wait until channel is opened by server */
+ while(channel->state == SSH_CHANNEL_STATE_NOT_OPEN){
+ ssh_handle_packets(session,-1);
+ }
+ if(channel->state == SSH_CHANNEL_STATE_OPEN)
+ err=SSH_OK;
+ leave_function();
+ return err;
+}
+
+/* get ssh channel from local session? */
+ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) {
+ ssh_channel initchan = session->channels;
+ ssh_channel channel;
+
+ /* We assume we are always the local */
+ if (initchan == NULL) {
+ return NULL;
+ }
+
+ for (channel = initchan; channel->local_channel != id;
+ channel=channel->next) {
+ if (channel->next == initchan) {
+ return NULL;
+ }
+ }
+
+ return channel;
+}
+
+static int grow_window(ssh_session session, ssh_channel channel, int minimumsize) {
+ uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE;
+
+ enter_function();
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_WINDOW_ADJUST) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(new_window)) < 0) {
+ goto error;
+ }
+
+ if (packet_send(session) == SSH_ERROR) {
+ /* FIXME should we fail here or not? */
+ leave_function();
+ return 1;
+ }
+
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "growing window (channel %d:%d) to %d bytes",
+ channel->local_channel,
+ channel->remote_channel,
+ channel->local_window + new_window);
+
+ channel->local_window += new_window;
+
+ leave_function();
+ return 0;
+error:
+ buffer_reinit(session->out_buffer);
+
+ leave_function();
+ return -1;
+}
+
+/**
+ * @internal
+ *
+ * @brief Parse a channel-related packet to resolve it to a ssh_channel.
+ *
+ * This works on SSH1 sessions too.
+ *
+ * @param[in] session The current SSH session.
+ *
+ * @param[in] packet The buffer to parse packet from. The read pointer will
+ * be moved after the call.
+ *
+ * @returns The related ssh_channel, or NULL if the channel is
+ * unknown or the packet is invalid.
+ */
+static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) {
+ ssh_channel channel;
+ uint32_t chan;
+#ifdef WITH_SSH1
+ /* With SSH1, the channel is always the first one */
+ if(session->version==1)
+ return session->channels;
+#endif
+ if (buffer_get_u32(packet, &chan) != sizeof(uint32_t)) {
+ ssh_set_error(session, SSH_FATAL,
+ "Getting channel from message: short read");
+ return NULL;
+ }
+
+ channel = ssh_channel_from_local(session, ntohl(chan));
+ if (channel == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Server specified invalid channel %lu",
+ (long unsigned int) ntohl(chan));
+ }
+
+ return channel;
+}
+
+SSH_PACKET_CALLBACK(channel_rcv_change_window) {
+ ssh_channel channel;
+ uint32_t bytes;
+ int rc;
+ (void)user;
+ (void)type;
+ enter_function();
+
+ channel = channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
+ }
+
+ rc = buffer_get_u32(packet, &bytes);
+ if (channel == NULL || rc != sizeof(uint32_t)) {
+ ssh_log(session, SSH_LOG_PACKET,
+ "Error getting a window adjust message: invalid packet");
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ bytes = ntohl(bytes);
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Adding %d bytes to channel (%d:%d) (from %d bytes)",
+ bytes,
+ channel->local_channel,
+ channel->remote_channel,
+ channel->remote_window);
+
+ channel->remote_window += bytes;
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/* is_stderr is set to 1 if the data are extended, ie stderr */
+SSH_PACKET_CALLBACK(channel_rcv_data){
+ ssh_channel channel;
+ ssh_string str;
+ size_t len;
+ int is_stderr;
+ (void)user;
+ enter_function();
+ if(type==SSH2_MSG_CHANNEL_DATA)
+ is_stderr=0;
+ else
+ is_stderr=1;
+
+ channel = channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS,
+ "%s", ssh_get_error(session));
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ if (is_stderr) {
+ uint32_t ignore;
+ /* uint32 data type code. we can ignore it */
+ buffer_get_u32(packet, &ignore);
+ }
+
+ str = buffer_get_ssh_string(packet);
+ if (str == NULL) {
+ ssh_log(session, SSH_LOG_PACKET, "Invalid data packet!");
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ len = ssh_string_len(str);
+
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Channel receiving %zu bytes data in %d (local win=%d remote win=%d)",
+ len,
+ is_stderr,
+ channel->local_window,
+ channel->remote_window);
+
+ /* What shall we do in this case? Let's accept it anyway */
+ if (len > channel->local_window) {
+ ssh_log(session, SSH_LOG_RARE,
+ "Data packet too big for our window(%zu vs %d)",
+ len,
+ channel->local_window);
+ }
+
+ if (channel_default_bufferize(channel, ssh_string_data(str), len,
+ is_stderr) < 0) {
+ ssh_string_free(str);
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ if (len <= channel->local_window) {
+ channel->local_window -= len;
+ } else {
+ channel->local_window = 0; /* buggy remote */
+ }
+
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Channel windows are now (local win=%d remote win=%d)",
+ channel->local_window,
+ channel->remote_window);
+
+ ssh_string_free(str);
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(channel_rcv_eof) {
+ ssh_channel channel;
+ (void)user;
+ (void)type;
+ enter_function();
+
+ channel = channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received eof on channel (%d:%d)",
+ channel->local_channel,
+ channel->remote_channel);
+ /* channel->remote_window = 0; */
+ channel->remote_eof = 1;
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(channel_rcv_close) {
+ ssh_channel channel;
+ (void)user;
+ (void)type;
+ enter_function();
+
+ channel = channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received close on channel (%d:%d)",
+ channel->local_channel,
+ channel->remote_channel);
+
+ if ((channel->stdout_buffer &&
+ buffer_get_rest_len(channel->stdout_buffer) > 0) ||
+ (channel->stderr_buffer &&
+ buffer_get_rest_len(channel->stderr_buffer) > 0)) {
+ channel->delayed_close = 1;
+ } else {
+ channel->state = SSH_CHANNEL_STATE_CLOSED;
+ }
+
+ if (channel->remote_eof == 0) {
+ ssh_log(session, SSH_LOG_PACKET,
+ "Remote host not polite enough to send an eof before close");
+ }
+ channel->remote_eof = 1;
+ /*
+ * The remote eof doesn't break things if there was still data into read
+ * buffer because the eof is ignored until the buffer is empty.
+ */
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(channel_rcv_request) {
+ ssh_channel channel;
+ ssh_string request_s;
+ char *request;
+ uint32_t status;
+ (void)user;
+ (void)type;
+
+ enter_function();
+
+ channel = channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session));
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ request_s = buffer_get_ssh_string(packet);
+ if (request_s == NULL) {
+ ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ request = ssh_string_to_char(request_s);
+ ssh_string_free(request_s);
+ if (request == NULL) {
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ buffer_get_u8(packet, (uint8_t *) &status);
+
+ if (strcmp(request,"exit-status") == 0) {
+ SAFE_FREE(request);
+ ssh_log(session, SSH_LOG_PACKET, "received exit-status");
+ buffer_get_u32(packet, &status);
+ channel->exit_status = ntohl(status);
+
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ if (strcmp(request, "exit-signal") == 0) {
+ const char *core = "(core dumped)";
+ ssh_string signal_s;
+ char *sig;
+ uint8_t i;
+
+ SAFE_FREE(request);
+
+ signal_s = buffer_get_ssh_string(packet);
+ if (signal_s == NULL) {
+ ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ sig = ssh_string_to_char(signal_s);
+ ssh_string_free(signal_s);
+ if (sig == NULL) {
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ buffer_get_u8(packet, &i);
+ if (i == 0) {
+ core = "";
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Remote connection closed by signal SIG %s %s", sig, core);
+ SAFE_FREE(sig);
+
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+ if(strcmp(request,"keepalive@openssh.com")==0){
+ SAFE_FREE(request);
+ ssh_log(session, SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive");
+ buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_FAILURE);
+ buffer_add_u32(session->out_buffer, htonl(channel->remote_channel));
+ packet_send(session);
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ /* If we are here, that means we have a request that is not in the understood
+ * client requests. That means we need to create a ssh message to be passed
+ * to the user code handling ssh messages
+ */
+ ssh_message_handle_channel_request(session,channel,packet,request,status);
+
+ SAFE_FREE(request);
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/*
+ * When data has been received from the ssh server, it can be applied to the
+ * known user function, with help of the callback, or inserted here
+ *
+ * FIXME is the window changed?
+ */
+int channel_default_bufferize(ssh_channel channel, void *data, int len,
+ int is_stderr) {
+ ssh_session session = channel->session;
+
+ ssh_log(session, SSH_LOG_RARE,
+ "placing %d bytes into channel buffer (stderr=%d)", len, is_stderr);
+ if (is_stderr == 0) {
+ /* stdout */
+ if (channel->stdout_buffer == NULL) {
+ channel->stdout_buffer = ssh_buffer_new();
+ if (channel->stdout_buffer == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ }
+
+ if (buffer_add_data(channel->stdout_buffer, data, len) < 0) {
+ ssh_buffer_free(channel->stdout_buffer);
+ channel->stdout_buffer = NULL;
+ return -1;
+ }
+ } else {
+ /* stderr */
+ if (channel->stderr_buffer == NULL) {
+ channel->stderr_buffer = ssh_buffer_new();
+ if (channel->stderr_buffer == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ }
+
+ if (buffer_add_data(channel->stderr_buffer, data, len) < 0) {
+ ssh_buffer_free(channel->stderr_buffer);
+ channel->stderr_buffer = NULL;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Open a session channel (suited for a shell, not TCP forwarding).
+ *
+ * @param[in] channel An allocated channel.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @see channel_open_forward()
+ * @see channel_request_env()
+ * @see channel_request_shell()
+ * @see channel_request_exec()
+ */
+int ssh_channel_open_session(ssh_channel channel) {
+#ifdef WITH_SSH1
+ if (channel->session->version == 1) {
+ return channel_open_session1(channel);
+ }
+#endif
+
+ return channel_open(channel,"session",64000,32000,NULL);
+}
+
+/**
+ * @brief Open a TCP/IP forwarding channel.
+ *
+ * @param[in] channel An allocated channel.
+ *
+ * @param[in] remotehost The remote host to connected (host name or IP).
+ *
+ * @param[in] remoteport The remote port.
+ *
+ * @param[in] sourcehost The source host (your local computer). It's optional
+ * and for logging purpose.
+ *
+ * @param[in] localport The source port (your local computer). It's optional
+ * and for logging purpose.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @warning This function does not bind the local port and does not automatically
+ * forward the content of a socket to the channel. You still have to
+ * use channel_read and channel_write for this.
+ */
+int ssh_channel_open_forward(ssh_channel channel, const char *remotehost,
+ int remoteport, const char *sourcehost, int localport) {
+ ssh_session session = channel->session;
+ ssh_buffer payload = NULL;
+ ssh_string str = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+ payload = ssh_buffer_new();
+ if (payload == NULL) {
+ goto error;
+ }
+ str = ssh_string_from_char(remotehost);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(payload, str) < 0 ||
+ buffer_add_u32(payload,htonl(remoteport)) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(str);
+ str = ssh_string_from_char(sourcehost);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(payload, str) < 0 ||
+ buffer_add_u32(payload,htonl(localport)) < 0) {
+ goto error;
+ }
+
+ rc = channel_open(channel, "direct-tcpip", 64000, 32000, payload);
+
+error:
+ ssh_buffer_free(payload);
+ ssh_string_free(str);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Close and free a channel.
+ *
+ * @param[in] channel The channel to free.
+ *
+ * @warning Any data unread on this channel will be lost.
+ */
+void ssh_channel_free(ssh_channel channel) {
+ ssh_session session = channel->session;
+ enter_function();
+
+ if (channel == NULL) {
+ leave_function();
+ return;
+ }
+
+ if (session->alive && channel->state == SSH_CHANNEL_STATE_OPEN) {
+ ssh_channel_close(channel);
+ }
+
+ /* handle the "my channel is first on session list" case */
+ if (session->channels == channel) {
+ session->channels = channel->next;
+ }
+
+ /* handle the "my channel is the only on session list" case */
+ if (channel->next == channel) {
+ session->channels = NULL;
+ } else {
+ channel->prev->next = channel->next;
+ channel->next->prev = channel->prev;
+ }
+
+ ssh_buffer_free(channel->stdout_buffer);
+ ssh_buffer_free(channel->stderr_buffer);
+
+ /* debug trick to catch use after frees */
+ memset(channel, 'X', sizeof(struct ssh_channel_struct));
+ SAFE_FREE(channel);
+
+ leave_function();
+}
+
+/**
+ * @brief Send an end of file on the channel.
+ *
+ * This doesn't close the channel. You may still read from it but not write.
+ *
+ * @param[in] channel The channel to send the eof to.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @see channel_close()
+ * @see channel_free()
+ */
+int ssh_channel_send_eof(ssh_channel channel){
+ ssh_session session = channel->session;
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_EOF) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)) < 0) {
+ goto error;
+ }
+ rc = packet_send(session);
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sent a EOF on client channel (%d:%d)",
+ channel->local_channel,
+ channel->remote_channel);
+
+ channel->local_eof = 1;
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Close a channel.
+ *
+ * This sends an end of file and then closes the channel. You won't be able
+ * to recover any data the server was going to send or was in buffers.
+ *
+ * @param[in] channel The channel to close.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @see channel_free()
+ * @see channel_eof()
+ */
+int ssh_channel_close(ssh_channel channel){
+ ssh_session session = channel->session;
+ int rc = 0;
+
+ enter_function();
+
+ if (channel->local_eof == 0) {
+ rc = ssh_channel_send_eof(channel);
+ }
+
+ if (rc != SSH_OK) {
+ leave_function();
+ return rc;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_CLOSE) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0) {
+ goto error;
+ }
+
+ rc = packet_send(session);
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sent a close on client channel (%d:%d)",
+ channel->local_channel,
+ channel->remote_channel);
+
+ if(rc == SSH_OK) {
+ channel->state=SSH_CHANNEL_STATE_CLOSED;
+ }
+
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+
+ leave_function();
+ return rc;
+}
+
+int channel_write_common(ssh_channel channel, const void *data,
+ uint32_t len, int is_stderr) {
+ ssh_session session = channel->session;
+ int origlen = len;
+ size_t effectivelen;
+ /* handle the max packet len from remote side, be nice */
+ /* 10 bytes for the headers */
+ size_t maxpacketlen = channel->remote_maxpacket - 10;
+
+ enter_function();
+
+ if (channel->local_eof) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Can't write to channel %d:%d after EOF was sent",
+ channel->local_channel,
+ channel->remote_channel);
+ leave_function();
+ return -1;
+ }
+
+ if (channel->state != SSH_CHANNEL_STATE_OPEN || channel->delayed_close != 0) {
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed");
+ leave_function();
+ return -1;
+ }
+
+#ifdef WITH_SSH1
+ if (channel->version == 1) {
+ int rc = channel_write1(channel, data, len);
+ leave_function();
+ return rc;
+ }
+#endif
+
+ while (len > 0) {
+ if (channel->remote_window < len) {
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Remote window is %d bytes. going to write %d bytes",
+ channel->remote_window,
+ len);
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Waiting for a growing window message...");
+ /* What happens when the channel window is zero? */
+ while(channel->remote_window == 0) {
+ /* parse every incoming packet */
+ if (ssh_handle_packets(session,-1) == SSH_ERROR) {
+ leave_function();
+ return SSH_ERROR;
+ }
+ }
+ effectivelen = len > channel->remote_window ? channel->remote_window : len;
+ } else {
+ effectivelen = len;
+ }
+ effectivelen = effectivelen > maxpacketlen ? maxpacketlen : effectivelen;
+ if (buffer_add_u8(session->out_buffer, is_stderr ?
+ SSH2_MSG_CHANNEL_EXTENDED_DATA : SSH2_MSG_CHANNEL_DATA) < 0 ||
+ buffer_add_u32(session->out_buffer,
+ htonl(channel->remote_channel)) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(effectivelen)) < 0 ||
+ buffer_add_data(session->out_buffer, data, effectivelen) < 0) {
+ goto error;
+ }
+
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ ssh_log(session, SSH_LOG_RARE,
+ "channel_write wrote %ld bytes", effectivelen);
+
+ channel->remote_window -= effectivelen;
+ len -= effectivelen;
+ data = ((uint8_t*)data + effectivelen);
+ }
+
+ leave_function();
+ return origlen;
+error:
+ buffer_reinit(session->out_buffer);
+
+ leave_function();
+ return SSH_ERROR;
+}
+
+/**
+ * @brief Blocking write on a channel.
+ *
+ * @param[in] channel The channel to write to.
+ *
+ * @param[in] data A pointer to the data to write.
+ *
+ * @param[in] len The length of the buffer to write to.
+ *
+ * @return The number of bytes written, SSH_ERROR on error.
+ *
+ * @see channel_read()
+ */
+int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) {
+ return channel_write_common(channel, data, len, 0);
+}
+
+/**
+ * @brief Check if the channel is open or not.
+ *
+ * @param[in] channel The channel to check.
+ *
+ * @return 0 if channel is closed, nonzero otherwise.
+ *
+ * @see channel_is_closed()
+ */
+int ssh_channel_is_open(ssh_channel channel) {
+ return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0);
+}
+
+/**
+ * @brief Check if the channel is closed or not.
+ *
+ * @param[in] channel The channel to check.
+ *
+ * @return 0 if channel is opened, nonzero otherwise.
+ *
+ * @see channel_is_open()
+ */
+int ssh_channel_is_closed(ssh_channel channel) {
+ return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0);
+}
+
+/**
+ * @brief Check if remote has sent an EOF.
+ *
+ * @param[in] channel The channel to check.
+ *
+ * @return 0 if there is no EOF, nonzero otherwise.
+ */
+int ssh_channel_is_eof(ssh_channel channel) {
+ if ((channel->stdout_buffer &&
+ buffer_get_rest_len(channel->stdout_buffer) > 0) ||
+ (channel->stderr_buffer &&
+ buffer_get_rest_len(channel->stderr_buffer) > 0)) {
+ return 0;
+ }
+
+ return (channel->remote_eof != 0);
+}
+
+/**
+ * @brief Put the channel into blocking or nonblocking mode.
+ *
+ * @param[in] channel The channel to use.
+ *
+ * @param[in] blocking A boolean for blocking or nonblocking.
+ *
+ * @bug This functionality is still under development and
+ * doesn't work correctly.
+ */
+void ssh_channel_set_blocking(ssh_channel channel, int blocking) {
+ channel->blocking = (blocking == 0 ? 0 : 1);
+}
+
+/**
+ * @internal
+ *
+ * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state.
+ *
+ * This works on SSH1 sessions too.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_channel_success){
+ ssh_channel channel;
+ (void)type;
+ (void)user;
+ enter_function();
+ channel=channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received SSH_CHANNEL_SUCCESS on channel (%d:%d)",
+ channel->local_channel,
+ channel->remote_channel);
+ if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){
+ ssh_log(session, SSH_LOG_RARE, "SSH_CHANNEL_SUCCESS received in incorrect state %d",
+ channel->request_state);
+ } else {
+ channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED;
+ }
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state.
+ *
+ * This works on SSH1 sessions too.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_channel_failure){
+ ssh_channel channel;
+ (void)type;
+ (void)user;
+ enter_function();
+ channel=channel_from_msg(session,packet);
+ if (channel == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
+ leave_function();
+ return SSH_PACKET_USED;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received SSH_CHANNEL_FAILURE on channel (%d:%d)",
+ channel->local_channel,
+ channel->remote_channel);
+ if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){
+ ssh_log(session, SSH_LOG_RARE, "SSH_CHANNEL_FAILURE received in incorrect state %d",
+ channel->request_state);
+ } else {
+ channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED;
+ }
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+static int channel_request(ssh_channel channel, const char *request,
+ ssh_buffer buffer, int reply) {
+ ssh_session session = channel->session;
+ ssh_string req = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+ if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE){
+ ssh_set_error(session,SSH_REQUEST_DENIED,"channel_request_* used in incorrect state");
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ req = ssh_string_from_char(request);
+ if (req == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_REQUEST) < 0 ||
+ buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, req) < 0 ||
+ buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) {
+ goto error;
+ }
+ ssh_string_free(req);
+
+ if (buffer != NULL) {
+ if (buffer_add_data(session->out_buffer, ssh_buffer_get_begin(buffer),
+ ssh_buffer_get_len(buffer)) < 0) {
+ goto error;
+ }
+ }
+ channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sent a SSH_MSG_CHANNEL_REQUEST %s", request);
+ if (reply == 0) {
+ channel->request_state = SSH_CHANNEL_REQ_STATE_NONE;
+ leave_function();
+ return SSH_OK;
+ }
+ while(channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING){
+ ssh_handle_packets(session,-1);
+ }
+ /* we received something */
+ switch (channel->request_state){
+ case SSH_CHANNEL_REQ_STATE_ERROR:
+ rc=SSH_ERROR;
+ break;
+ case SSH_CHANNEL_REQ_STATE_DENIED:
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Channel request %s failed", request);
+ rc=SSH_ERROR;
+ break;
+ case SSH_CHANNEL_REQ_STATE_ACCEPTED:
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Channel request %s success",request);
+ rc=SSH_OK;
+ break;
+ case SSH_CHANNEL_REQ_STATE_NONE:
+ case SSH_CHANNEL_REQ_STATE_PENDING:
+ /* Never reached */
+ ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()");
+ rc=SSH_ERROR;
+ break;
+ }
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ leave_function();
+ return rc;
+error:
+ buffer_reinit(session->out_buffer);
+ ssh_string_free(req);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Request a pty with a specific type and size.
+ *
+ * @param[in] channel The channel to sent the request.
+ *
+ * @param[in] terminal The terminal type ("vt100, xterm,...").
+ *
+ * @param[in] col The number of columns.
+ *
+ * @param[in] row The number of rows.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ */
+int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
+ int col, int row) {
+ ssh_session session = channel->session;
+ ssh_string term = NULL;
+ ssh_buffer buffer = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+#ifdef WITH_SSH1
+ if (channel->version==1) {
+ channel_request_pty_size1(channel,terminal, col, row);
+ leave_function();
+ return rc;
+ }
+#endif
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ term = ssh_string_from_char(terminal);
+ if (term == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, term) < 0 ||
+ buffer_add_u32(buffer, htonl(col)) < 0 ||
+ buffer_add_u32(buffer, htonl(row)) < 0 ||
+ buffer_add_u32(buffer, 0) < 0 ||
+ buffer_add_u32(buffer, 0) < 0 ||
+ buffer_add_u32(buffer, htonl(1)) < 0 || /* Add a 0byte string */
+ buffer_add_u8(buffer, 0) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "pty-req", buffer, 1);
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(term);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Request a PTY.
+ *
+ * @param[in] channel The channel to send the request.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @see channel_request_pty_size()
+ */
+int ssh_channel_request_pty(ssh_channel channel) {
+ return ssh_channel_request_pty_size(channel, "xterm", 80, 24);
+}
+
+/**
+ * @brief Change the size of the terminal associated to a channel.
+ *
+ * @param[in] channel The channel to change the size.
+ *
+ * @param[in] cols The new number of columns.
+ *
+ * @param[in] rows The new number of rows.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @warning Do not call it from a signal handler if you are not sure any other
+ * libssh function using the same channel/session is running at same
+ * time (not 100% threadsafe).
+ */
+int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) {
+ ssh_session session = channel->session;
+ ssh_buffer buffer = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+#ifdef WITH_SSH1
+ if (channel->version == 1) {
+ rc = channel_change_pty_size1(channel,cols,rows);
+ leave_function();
+ return rc;
+ }
+#endif
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_u32(buffer, htonl(cols)) < 0 ||
+ buffer_add_u32(buffer, htonl(rows)) < 0 ||
+ buffer_add_u32(buffer, 0) < 0 ||
+ buffer_add_u32(buffer, 0) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "window-change", buffer, 0);
+error:
+ ssh_buffer_free(buffer);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Request a shell.
+ *
+ * @param[in] channel The channel to send the request.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ */
+int ssh_channel_request_shell(ssh_channel channel) {
+#ifdef WITH_SSH1
+ if (channel->version == 1) {
+ return channel_request_shell1(channel);
+ }
+#endif
+ return channel_request(channel, "shell", NULL, 1);
+}
+
+/**
+ * @brief Request a subsystem (for example "sftp").
+ *
+ * @param[in] channel The channel to send the request.
+ *
+ * @param[in] subsys The subsystem to request (for example "sftp").
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @warning You normally don't have to call it for sftp, see sftp_new().
+ */
+int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) {
+ ssh_buffer buffer = NULL;
+ ssh_string subsystem = NULL;
+ int rc = SSH_ERROR;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ subsystem = ssh_string_from_char(subsys);
+ if (subsystem == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, subsystem) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "subsystem", buffer, 1);
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(subsystem);
+
+ return rc;
+}
+
+int ssh_channel_request_sftp( ssh_channel channel){
+ return ssh_channel_request_subsystem(channel, "sftp");
+}
+
+static ssh_string generate_cookie(void) {
+ static const char *hex = "0123456789abcdef";
+ char s[36];
+ int i;
+
+ srand ((unsigned int)time(NULL));
+ for (i = 0; i < 32; i++) {
+ s[i] = hex[rand() % 16];
+ }
+ s[32] = '\0';
+ return ssh_string_from_char(s);
+}
+
+/**
+ * @brief Sends the "x11-req" channel request over an existing session channel.
+ *
+ * This will enable redirecting the display of the remote X11 applications to
+ * local X server over an secure tunnel.
+ *
+ * @param[in] channel An existing session channel where the remote X11
+ * applications are going to be executed.
+ *
+ * @param[in] single_connection A boolean to mark only one X11 app will be
+ * redirected.
+ *
+ * @param[in] protocol A x11 authentication protocol. Pass NULL to use the
+ * default value MIT-MAGIC-COOKIE-1.
+ *
+ * @param[in] cookie A x11 authentication cookie. Pass NULL to generate
+ * a random cookie.
+ *
+ * @param[in] screen_number The screen number.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ */
+int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol,
+ const char *cookie, int screen_number) {
+ ssh_buffer buffer = NULL;
+ ssh_string p = NULL;
+ ssh_string c = NULL;
+ int rc = SSH_ERROR;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ p = ssh_string_from_char(protocol ? protocol : "MIT-MAGIC-COOKIE-1");
+ if (p == NULL) {
+ goto error;
+ }
+
+ if (cookie) {
+ c = ssh_string_from_char(cookie);
+ } else {
+ c = generate_cookie();
+ }
+ if (c == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_u8(buffer, single_connection == 0 ? 0 : 1) < 0 ||
+ buffer_add_ssh_string(buffer, p) < 0 ||
+ buffer_add_ssh_string(buffer, c) < 0 ||
+ buffer_add_u32(buffer, htonl(screen_number)) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "x11-req", buffer, 1);
+
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(p);
+ ssh_string_free(c);
+ return rc;
+}
+
+static ssh_channel ssh_channel_accept(ssh_session session, int channeltype,
+ int timeout_ms) {
+#ifndef _WIN32
+ static const struct timespec ts = {
+ .tv_sec = 0,
+ .tv_nsec = 50000000 /* 50ms */
+ };
+#endif
+ ssh_message msg = NULL;
+ ssh_channel channel = NULL;
+ struct ssh_iterator *iterator;
+ int t;
+
+ for (t = timeout_ms; t >= 0; t -= 50)
+ {
+ ssh_handle_packets(session,50);
+
+ if (session->ssh_message_list) {
+ iterator = ssh_list_get_iterator(session->ssh_message_list);
+ while (iterator) {
+ msg = (ssh_message)iterator->data;
+ if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN &&
+ ssh_message_subtype(msg) == channeltype) {
+ ssh_list_remove(session->ssh_message_list, iterator);
+ channel = ssh_message_channel_request_open_reply_accept(msg);
+ ssh_message_free(msg);
+ return channel;
+ }
+ iterator = iterator->next;
+ }
+ }
+#ifdef _WIN32
+ Sleep(50); /* 50ms */
+#else
+ nanosleep(&ts, NULL);
+#endif
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Accept an X11 forwarding channel.
+ *
+ * @param[in] channel An x11-enabled session channel.
+ *
+ * @param[in] timeout_ms Timeout in milliseconds.
+ *
+ * @return A newly created channel, or NULL if no X11 request from
+ * the server.
+ */
+ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) {
+ return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms);
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global
+ * request.
+ */
+SSH_PACKET_CALLBACK(ssh_request_success){
+ (void)type;
+ (void)user;
+ (void)packet;
+ enter_function();
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received SSH_REQUEST_SUCCESS");
+ if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){
+ ssh_log(session, SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d",
+ session->global_req_state);
+ } else {
+ session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED;
+ }
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global
+ * request.
+ */
+SSH_PACKET_CALLBACK(ssh_request_denied){
+ (void)type;
+ (void)user;
+ (void)packet;
+ enter_function();
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received SSH_REQUEST_FAILURE");
+ if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){
+ ssh_log(session, SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d",
+ session->global_req_state);
+ } else {
+ session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED;
+ }
+
+ leave_function();
+ return SSH_PACKET_USED;
+
+}
+
+/**
+ * @internal
+ *
+ * @brief Send a global request (needed for forward listening) and wait for the
+ * result.
+ *
+ * @param[in] session The SSH session handle.
+ *
+ * @param[in] request The type of request (defined in RFC).
+ *
+ * @param[in] buffer Additional data to put in packet.
+ *
+ * @param[in] reply Set if you expect a reply from server.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ */
+static int global_request(ssh_session session, const char *request,
+ ssh_buffer buffer, int reply) {
+ ssh_string req = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+ if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE){
+ ssh_set_error(session,SSH_FATAL,"Invalid state in start of global_request()");
+ leave_function();
+ return rc;
+ }
+ req = ssh_string_from_char(request);
+ if (req == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_GLOBAL_REQUEST) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, req) < 0 ||
+ buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) {
+ goto error;
+ }
+ ssh_string_free(req);
+ req=NULL;
+
+ if (buffer != NULL) {
+ if (buffer_add_data(session->out_buffer, ssh_buffer_get_begin(buffer),
+ ssh_buffer_get_len(buffer)) < 0) {
+ goto error;
+ }
+ }
+ session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING;
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return rc;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sent a SSH_MSG_GLOBAL_REQUEST %s", request);
+ if (reply == 0) {
+ session->global_req_state=SSH_CHANNEL_REQ_STATE_NONE;
+ leave_function();
+ return SSH_OK;
+ }
+ while(session->global_req_state == SSH_CHANNEL_REQ_STATE_PENDING){
+ rc=ssh_handle_packets(session,-1);
+ if(rc==SSH_ERROR){
+ session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR;
+ break;
+ }
+ }
+ switch(session->global_req_state){
+ case SSH_CHANNEL_REQ_STATE_ACCEPTED:
+ ssh_log(session, SSH_LOG_PROTOCOL, "Global request %s success",request);
+ rc=SSH_OK;
+ break;
+ case SSH_CHANNEL_REQ_STATE_DENIED:
+ ssh_log(session, SSH_LOG_PACKET,
+ "Global request %s failed", request);
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Global request %s failed", request);
+ rc=SSH_ERROR;
+ break;
+ case SSH_CHANNEL_REQ_STATE_ERROR:
+ case SSH_CHANNEL_REQ_STATE_NONE:
+ case SSH_CHANNEL_REQ_STATE_PENDING:
+ rc=SSH_ERROR;
+ break;
+
+ }
+
+ leave_function();
+ return rc;
+error:
+ ssh_string_free(req);
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Sends the "tcpip-forward" global request to ask the server to begin
+ * listening for inbound connections.
+ *
+ * @param[in] session The ssh session to send the request.
+ *
+ * @param[in] address The address to bind to on the server. Pass NULL to bind
+ * to all available addresses on all protocol families
+ * supported by the server.
+ *
+ * @param[in] port The port to bind to on the server. Pass 0 to ask the
+ * server to allocate the next available unprivileged port
+ * number
+ *
+ * @param[in] bound_port The pointer to get actual bound port. Pass NULL to
+ * ignore.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ */
+int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) {
+ ssh_buffer buffer = NULL;
+ ssh_string addr = NULL;
+ int rc = SSH_ERROR;
+ uint32_t tmp;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ addr = ssh_string_from_char(address ? address : "");
+ if (addr == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, addr) < 0 ||
+ buffer_add_u32(buffer, htonl(port)) < 0) {
+ goto error;
+ }
+
+ rc = global_request(session, "tcpip-forward", buffer, 1);
+
+ if (rc == SSH_OK && port == 0 && bound_port) {
+ buffer_get_u32(session->in_buffer, &tmp);
+ *bound_port = ntohl(tmp);
+ }
+
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(addr);
+ return rc;
+}
+
+/**
+ * @brief Accept an incoming TCP/IP forwarding channel.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] timeout_ms A timeout in milliseconds.
+ *
+ * @return Newly created channel, or NULL if no incoming channel request from
+ * the server
+ */
+ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) {
+ return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms);
+}
+
+/**
+ * @brief Sends the "cancel-tcpip-forward" global request to ask the server to
+ * cancel the tcpip-forward request.
+ *
+ * @param[in] session The ssh session to send the request.
+ *
+ * @param[in] address The bound address on the server.
+ *
+ * @param[in] port The bound port on the server.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ */
+int ssh_forward_cancel(ssh_session session, const char *address, int port) {
+ ssh_buffer buffer = NULL;
+ ssh_string addr = NULL;
+ int rc = SSH_ERROR;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ addr = ssh_string_from_char(address ? address : "");
+ if (addr == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, addr) < 0 ||
+ buffer_add_u32(buffer, htonl(port)) < 0) {
+ goto error;
+ }
+
+ rc = global_request(session, "cancel-tcpip-forward", buffer, 1);
+
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(addr);
+ return rc;
+}
+
+/**
+ * @brief Set environment variables.
+ *
+ * @param[in] channel The channel to set the environment variables.
+ *
+ * @param[in] name The name of the variable.
+ *
+ * @param[in] value The value to set.
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @warning Some environment variables may be refused by security reasons.
+ */
+int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) {
+ ssh_buffer buffer = NULL;
+ ssh_string str = NULL;
+ int rc = SSH_ERROR;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ str = ssh_string_from_char(name);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, str) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(str);
+ str = ssh_string_from_char(value);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, str) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "env", buffer,1);
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(str);
+
+ return rc;
+}
+
+/**
+ * @brief Run a shell command without an interactive shell.
+ *
+ * This is similar to 'sh -c command'.
+ *
+ * @param[in] channel The channel to execute the command.
+ *
+ * @param[in] cmd The command to execute
+ * (e.g. "ls ~/ -al | grep -i reports").
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured.
+ *
+ * @code
+ * rc = channel_request_exec(channel, "ps aux");
+ * if (rc > 0) {
+ * return -1;
+ * }
+ *
+ * while ((rc = channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
+ * if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) {
+ * return -1;
+ * }
+ * }
+ * @endcode
+ *
+ * @see channel_request_shell()
+ */
+int ssh_channel_request_exec(ssh_channel channel, const char *cmd) {
+ ssh_buffer buffer = NULL;
+ ssh_string command = NULL;
+ int rc = SSH_ERROR;
+
+#ifdef WITH_SSH1
+ if (channel->version == 1) {
+ return channel_request_exec1(channel, cmd);
+ }
+#endif
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ command = ssh_string_from_char(cmd);
+ if (command == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, command) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "exec", buffer, 1);
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(command);
+ return rc;
+}
+
+
+/**
+ * @brief Send a signal to remote process (as described in RFC 4254, section 6.9).
+ *
+ * Sends a signal 'sig' to the remote process.
+ * Note, that remote system may not support signals concept.
+ * In such a case this request will be silently ignored.
+ * Only SSH-v2 is supported (I'm not sure about SSH-v1).
+ *
+ * @param[in] channel The channel to send signal.
+ *
+ * @param[in] sig The signal to send (without SIG prefix)
+ * (e.g. "TERM" or "KILL").
+ *
+ * @return SSH_OK on success, SSH_ERROR if an error occured
+ * (including attempts to send signal via SSH-v1 session).
+ */
+int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) {
+ ssh_buffer buffer = NULL;
+ ssh_string encoded_signal = NULL;
+ int rc = SSH_ERROR;
+
+#ifdef WITH_SSH1
+ if (channel->version == 1) {
+ return SSH_ERROR; // TODO: Add support for SSH-v1 if possible.
+ }
+#endif
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+
+ encoded_signal = ssh_string_from_char(sig);
+ if (encoded_signal == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buffer, encoded_signal) < 0) {
+ goto error;
+ }
+
+ rc = channel_request(channel, "signal", buffer, 0);
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(encoded_signal);
+ return rc;
+}
+
+
+/**
+ * @brief Read data from a channel into a buffer.
+ *
+ * @param[in] channel The channel to read from.
+ *
+ * @param[in] buffer The buffer which will get the data.
+ *
+ * @param[in] count The count of bytes to be read. If it is bigger than 0,
+ * the exact size will be read, else (bytes=0) it will
+ * return once anything is available.
+ *
+ * @param is_stderr A boolean value to mark reading from the stderr stream.
+ *
+ * @return The number of bytes read, 0 on end of file or SSH_ERROR
+ * on error.
+ * @deprecated Please use ssh_channel_read instead
+ * @see ssh_channel_read
+ */
+int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count,
+ int is_stderr) {
+ ssh_session session=channel->session;
+ ssh_buffer stdbuf = channel->stdout_buffer;
+ uint32_t maxread = count;
+ uint32_t len;
+
+ buffer_reinit(buffer);
+
+ enter_function();
+
+ if (count == 0) {
+ maxread = MAX_PACKET_LEN;
+ }
+
+ if (is_stderr) {
+ stdbuf = channel->stderr_buffer;
+ }
+
+ /*
+ * We may have problem if the window is too small to accept as much data
+ * as asked
+ */
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Read (%d) buffered: %d bytes. Window: %d",
+ count,
+ buffer_get_rest_len(stdbuf),
+ channel->local_window);
+
+ if (count > buffer_get_rest_len(stdbuf) + channel->local_window) {
+ if (grow_window(session, channel,
+ count - buffer_get_rest_len(stdbuf)) < 0) {
+ leave_function();
+ return -1;
+ }
+ }
+ /* block reading if asked bytes=0 */
+ while (buffer_get_rest_len(stdbuf) == 0 ||
+ buffer_get_rest_len(stdbuf) < count) {
+ if (channel->remote_eof && buffer_get_rest_len(stdbuf) == 0) {
+ leave_function();
+ return 0;
+ }
+ if (channel->remote_eof) {
+ /* Return the resting bytes in buffer */
+ break;
+ }
+ if (buffer_get_rest_len(stdbuf) >= maxread) {
+ /* Stop reading when buffer is full enough */
+ break;
+ }
+ ssh_handle_packets(session,-1);
+ }
+
+ if(channel->local_window < WINDOWLIMIT) {
+ if (grow_window(session, channel, 0) < 0) {
+ leave_function();
+ return -1;
+ }
+ }
+
+ if (count == 0) {
+ /* write the ful buffer information */
+ if (buffer_add_data(buffer, buffer_get_rest(stdbuf),
+ buffer_get_rest_len(stdbuf)) < 0) {
+ leave_function();
+ return -1;
+ }
+ buffer_reinit(stdbuf);
+ } else {
+ /* Read bytes bytes if len is greater, everything otherwise */
+ len = buffer_get_rest_len(stdbuf);
+ len = (len > count ? count : len);
+ if (buffer_add_data(buffer, buffer_get_rest(stdbuf), len) < 0) {
+ leave_function();
+ return -1;
+ }
+ buffer_pass_bytes(stdbuf,len);
+ }
+
+ leave_function();
+ return ssh_buffer_get_len(buffer);
+}
+
+/* TODO FIXME Fix the delayed close thing */
+/* TODO FIXME Fix the blocking behaviours */
+
+/**
+ * @brief Reads data from a channel.
+ *
+ * @param[in] channel The channel to read from.
+ *
+ * @param[in] dest The destination buffer which will get the data.
+ *
+ * @param[in] count The count of bytes to be read.
+ *
+ * @param[in] is_stderr A boolean value to mark reading from the stderr flow.
+ *
+ * @return The number of bytes read, 0 on end of file or SSH_ERROR
+ * on error.
+ *
+ * @warning This function may return less than count bytes of data, and won't
+ * block until count bytes have been read.
+ * @warning The read function using a buffer has been renamed to
+ * channel_read_buffer().
+ */
+int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) {
+ ssh_session session = channel->session;
+ ssh_buffer stdbuf = channel->stdout_buffer;
+ uint32_t len;
+
+ enter_function();
+
+ if (count == 0) {
+ leave_function();
+ return 0;
+ }
+
+ if (is_stderr) {
+ stdbuf=channel->stderr_buffer;
+ }
+
+ /*
+ * We may have problem if the window is too small to accept as much data
+ * as asked
+ */
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Read (%d) buffered : %d bytes. Window: %d",
+ count,
+ buffer_get_rest_len(stdbuf),
+ channel->local_window);
+
+ if (count > buffer_get_rest_len(stdbuf) + channel->local_window) {
+ if (grow_window(session, channel,
+ count - buffer_get_rest_len(stdbuf)) < 0) {
+ leave_function();
+ return -1;
+ }
+ }
+
+ /* block reading until at least one byte is read
+ * and ignore the trivial case count=0
+ */
+ while (buffer_get_rest_len(stdbuf) == 0 && count > 0) {
+ if (channel->remote_eof && buffer_get_rest_len(stdbuf) == 0) {
+ leave_function();
+ return 0;
+ }
+
+ if (channel->remote_eof) {
+ /* Return the resting bytes in buffer */
+ break;
+ }
+
+ if (buffer_get_rest_len(stdbuf) >= count) {
+ /* Stop reading when buffer is full enough */
+ break;
+ }
+
+ ssh_handle_packets(session,-1);
+ }
+
+ if (channel->local_window < WINDOWLIMIT) {
+ if (grow_window(session, channel, 0) < 0) {
+ leave_function();
+ return -1;
+ }
+ }
+
+ len = buffer_get_rest_len(stdbuf);
+ /* Read count bytes if len is greater, everything otherwise */
+ len = (len > count ? count : len);
+ memcpy(dest, buffer_get_rest(stdbuf), len);
+ buffer_pass_bytes(stdbuf,len);
+
+ leave_function();
+ return len;
+}
+
+/**
+ * @brief Do a nonblocking read on the channel.
+ *
+ * A nonblocking read on the specified channel. it will return <= count bytes of
+ * data read atomically.
+ *
+ * @param[in] channel The channel to read from.
+ *
+ * @param[in] dest A pointer to a destination buffer.
+ *
+ * @param[in] count The count of bytes of data to be read.
+ *
+ * @param[in] is_stderr A boolean to select the stderr stream.
+ *
+ * @return The number of bytes read, 0 if nothing is available or
+ * SSH_ERROR on error.
+ *
+ * @warning Don't forget to check for EOF as it would return 0 here.
+ *
+ * @see channel_is_eof()
+ */
+int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count,
+ int is_stderr) {
+ ssh_session session = channel->session;
+ uint32_t to_read;
+ int rc;
+
+ enter_function();
+
+ to_read = ssh_channel_poll(channel, is_stderr);
+
+ if (to_read <= 0) {
+ leave_function();
+ return to_read; /* may be an error code */
+ }
+
+ if (to_read > count) {
+ to_read = count;
+ }
+ rc = ssh_channel_read(channel, dest, to_read, is_stderr);
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @brief Polls a channel for data to read.
+ *
+ * @param[in] channel The channel to poll.
+ *
+ * @param[in] is_stderr A boolean to select the stderr stream.
+ *
+ * @return The number of bytes available for reading, 0 if nothing
+ * is available or SSH_ERROR on error.
+ *
+ * @warning When the channel is in EOF state, the function returns SSH_EOF.
+ *
+ * @see channel_is_eof()
+ */
+int ssh_channel_poll(ssh_channel channel, int is_stderr){
+ ssh_session session = channel->session;
+ ssh_buffer stdbuf = channel->stdout_buffer;
+
+ enter_function();
+
+ if (is_stderr) {
+ stdbuf = channel->stderr_buffer;
+ }
+
+ if (buffer_get_rest_len(stdbuf) == 0 && channel->remote_eof == 0) {
+ if (ssh_handle_packets(channel->session,0)==SSH_ERROR) {
+ leave_function();
+ return SSH_ERROR;
+ }
+ }
+
+ if (buffer_get_rest_len(stdbuf) > 0){
+ leave_function();
+ return buffer_get_rest_len(stdbuf);
+ }
+
+ if (channel->remote_eof) {
+ leave_function();
+ return SSH_EOF;
+ }
+
+ leave_function();
+ return buffer_get_rest_len(stdbuf);
+}
+
+/**
+ * @brief Recover the session in which belongs a channel.
+ *
+ * @param[in] channel The channel to recover the session from.
+ *
+ * @return The session pointer.
+ */
+ssh_session ssh_channel_get_session(ssh_channel channel) {
+ return channel->session;
+}
+
+/**
+ * @brief Get the exit status of the channel (error code from the executed
+ * instruction).
+ *
+ * @param[in] channel The channel to get the status from.
+ *
+ * @returns The exit status, -1 if no exit status has been returned
+ * or eof not sent.
+ */
+int ssh_channel_get_exit_status(ssh_channel channel) {
+ if (channel->local_eof == 0) {
+ return -1;
+ }
+
+ while (channel->remote_eof == 0 || channel->exit_status == -1) {
+ /* Parse every incoming packet */
+ if (ssh_handle_packets(channel->session,-1) != SSH_OK) {
+ return -1;
+ }
+ /* XXX We should actually wait for a close packet and not a close
+ * we issued ourselves
+ */
+ if (channel->state != SSH_CHANNEL_STATE_OPEN) {
+ /* When a channel is closed, no exit status message can
+ * come anymore */
+ break;
+ }
+ }
+
+ return channel->exit_status;
+}
+
+/*
+ * This function acts as a meta select.
+ *
+ * First, channels are analyzed to seek potential can-write or can-read ones,
+ * then if no channel has been elected, it goes in a loop with the posix
+ * select(2).
+ * This is made in two parts: protocol select and network select. The protocol
+ * select does not use the network functions at all
+ */
+static int channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans,
+ ssh_channel *echans, ssh_channel *rout, ssh_channel *wout, ssh_channel *eout) {
+ ssh_channel chan;
+ int i;
+ int j = 0;
+
+ for (i = 0; rchans[i] != NULL; i++) {
+ chan = rchans[i];
+
+ while (ssh_channel_is_open(chan) && ssh_socket_data_available(chan->session->socket)) {
+ ssh_handle_packets(chan->session,-1);
+ }
+
+ if ((chan->stdout_buffer && ssh_buffer_get_len(chan->stdout_buffer) > 0) ||
+ (chan->stderr_buffer && ssh_buffer_get_len(chan->stderr_buffer) > 0) ||
+ chan->remote_eof) {
+ rout[j] = chan;
+ j++;
+ }
+ }
+ rout[j] = NULL;
+
+ j = 0;
+ for(i = 0; wchans[i] != NULL; i++) {
+ chan = wchans[i];
+ /* It's not our business to seek if the file descriptor is writable */
+ if (ssh_socket_data_writable(chan->session->socket) &&
+ ssh_channel_is_open(chan) && (chan->remote_window > 0)) {
+ wout[j] = chan;
+ j++;
+ }
+ }
+ wout[j] = NULL;
+
+ j = 0;
+ for (i = 0; echans[i] != NULL; i++) {
+ chan = echans[i];
+
+ if (!ssh_socket_is_open(chan->session->socket) || ssh_channel_is_closed(chan)) {
+ eout[j] = chan;
+ j++;
+ }
+ }
+ eout[j] = NULL;
+
+ return 0;
+}
+
+/* Just count number of pointers in the array */
+static int count_ptrs(ssh_channel *ptrs) {
+ int c;
+ for (c = 0; ptrs[c] != NULL; c++)
+ ;
+
+ return c;
+}
+
+/**
+ * @brief Act like the standard select(2) on channels.
+ *
+ * The list of pointers are then actualized and will only contain pointers to
+ * channels that are respectively readable, writable or have an exception to
+ * trap.
+ *
+ * @param[in] readchans A NULL pointer or an array of channel pointers,
+ * terminated by a NULL.
+ *
+ * @param[in] writechans A NULL pointer or an array of channel pointers,
+ * terminated by a NULL.
+ *
+ * @param[in] exceptchans A NULL pointer or an array of channel pointers,
+ * terminated by a NULL.
+ *
+ * @param[in] timeout Timeout as defined by select(2).
+ *
+ * @return SSH_OK on a successful operation, SSH_EINTR if the
+ * select(2) syscall was interrupted, then relaunch the
+ * function.
+ */
+int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans,
+ ssh_channel *exceptchans, struct timeval * timeout) {
+ ssh_channel *rchans, *wchans, *echans;
+ ssh_channel dummy = NULL;
+ fd_set rset;
+ fd_set wset;
+ fd_set eset;
+ socket_t max_fd = SSH_INVALID_SOCKET;
+ int rc;
+ int i;
+
+ /* don't allow NULL pointers */
+ if (readchans == NULL) {
+ readchans = &dummy;
+ }
+
+ if (writechans == NULL) {
+ writechans = &dummy;
+ }
+
+ if (exceptchans == NULL) {
+ exceptchans = &dummy;
+ }
+
+ if (readchans[0] == NULL && writechans[0] == NULL && exceptchans[0] == NULL) {
+ /* No channel to poll?? Go away! */
+ return 0;
+ }
+
+ /* Prepare the outgoing temporary arrays */
+ rchans = malloc(sizeof(ssh_channel ) * (count_ptrs(readchans) + 1));
+ if (rchans == NULL) {
+ return SSH_ERROR;
+ }
+
+ wchans = malloc(sizeof(ssh_channel ) * (count_ptrs(writechans) + 1));
+ if (wchans == NULL) {
+ SAFE_FREE(rchans);
+ return SSH_ERROR;
+ }
+
+ echans = malloc(sizeof(ssh_channel ) * (count_ptrs(exceptchans) + 1));
+ if (echans == NULL) {
+ SAFE_FREE(rchans);
+ SAFE_FREE(wchans);
+ return SSH_ERROR;
+ }
+
+ /*
+ * First, try without doing network stuff then, select and redo the
+ * networkless stuff
+ */
+ do {
+ channel_protocol_select(readchans, writechans, exceptchans,
+ rchans, wchans, echans);
+ if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) {
+ /* We've got one without doing any select overwrite the beginning arrays */
+ memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel ));
+ memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel ));
+ memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel ));
+ SAFE_FREE(rchans);
+ SAFE_FREE(wchans);
+ SAFE_FREE(echans);
+ return 0;
+ }
+ /*
+ * Since we verified the invalid fd cases into the networkless select,
+ * we can be sure all fd are valid ones
+ */
+ FD_ZERO(&rset);
+ FD_ZERO(&wset);
+ FD_ZERO(&eset);
+
+ for (i = 0; readchans[i] != NULL; i++) {
+ if (!ssh_socket_fd_isset(readchans[i]->session->socket, &rset)) {
+ ssh_socket_fd_set(readchans[i]->session->socket, &rset, &max_fd);
+ }
+ }
+
+ for (i = 0; writechans[i] != NULL; i++) {
+ if (!ssh_socket_fd_isset(writechans[i]->session->socket, &wset)) {
+ ssh_socket_fd_set(writechans[i]->session->socket, &wset, &max_fd);
+ }
+ }
+
+ for (i = 0; exceptchans[i] != NULL; i++) {
+ if (!ssh_socket_fd_isset(exceptchans[i]->session->socket, &eset)) {
+ ssh_socket_fd_set(exceptchans[i]->session->socket, &eset, &max_fd);
+ }
+ }
+
+ /* Here we go */
+ rc = select(max_fd, &rset, &wset, &eset, timeout);
+ /* Leave if select was interrupted */
+ if (rc == EINTR) {
+ SAFE_FREE(rchans);
+ SAFE_FREE(wchans);
+ SAFE_FREE(echans);
+ return SSH_EINTR;
+ }
+
+ for (i = 0; readchans[i] != NULL; i++) {
+ if (ssh_socket_fd_isset(readchans[i]->session->socket, &rset)) {
+ ssh_socket_set_toread(readchans[i]->session->socket);
+ }
+ }
+
+ for (i = 0; writechans[i] != NULL; i++) {
+ if (ssh_socket_fd_isset(writechans[i]->session->socket, &wset)) {
+ ssh_socket_set_towrite(writechans[i]->session->socket);
+ }
+ }
+
+ for (i = 0; exceptchans[i] != NULL; i++) {
+ if (ssh_socket_fd_isset(exceptchans[i]->session->socket, &eset)) {
+ ssh_socket_set_except(exceptchans[i]->session->socket);
+ }
+ }
+ } while(1); /* Return to do loop */
+
+ /* not reached */
+ return 0;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/channels1.c b/src/channels1.c
new file mode 100644
index 00000000..4cf7e778
--- /dev/null
+++ b/src/channels1.c
@@ -0,0 +1,298 @@
+/*
+ * channels1.c - Support for SSH-1 type channels
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 by Aris Adamantiadis
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+#include "libssh/priv.h"
+#include "libssh/ssh1.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.h"
+#include "libssh/channels.h"
+#include "libssh/session.h"
+
+#ifdef WITH_SSH1
+
+/*
+ * This is a big hack. In fact, SSH1 doesn't make a clever use of channels.
+ * The whole packets concerning shells are sent outside of a channel.
+ * Thus, an inside limitation of this behavior is that you can't only
+ * request one shell.
+ * The question is still how they managed to embed two "channel" into one
+ * protocol.
+ */
+
+int channel_open_session1(ssh_channel chan) {
+ /*
+ * We guess we are requesting an *exec* channel. It can only have one exec
+ * channel. So we abort with an error if we need more than one.
+ */
+ ssh_session session = chan->session;
+ if (session->exec_channel_opened) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "SSH1 supports only one execution channel. "
+ "One has already been opened");
+ return -1;
+ }
+ session->exec_channel_opened = 1;
+ chan->state = SSH_CHANNEL_STATE_OPEN;
+ chan->local_maxpacket = 32000;
+ chan->local_window = 64000;
+ ssh_log(session, SSH_LOG_PACKET, "Opened a SSH1 channel session");
+
+ return 0;
+}
+
+/* 10 SSH_CMSG_REQUEST_PTY
+ *
+ * string TERM environment variable value (e.g. vt100)
+ * 32-bit int terminal height, rows (e.g., 24)
+ * 32-bit int terminal width, columns (e.g., 80)
+ * 32-bit int terminal width, pixels (0 if no graphics) (e.g., 480)
+ * 32-bit int terminal height, pixels (0 if no graphics) (e.g., 640)
+ * n bytes tty modes encoded in binary
+ * Some day, someone should have a look at that nasty tty encoded. It's
+ * much simplier under ssh2. I just hope the defaults values are ok ...
+ */
+
+int channel_request_pty_size1(ssh_channel channel, const char *terminal, int col,
+ int row) {
+ ssh_session session = channel->session;
+ ssh_string str = NULL;
+ if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE){
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Wrong request state");
+ return SSH_ERROR;
+ }
+ str = ssh_string_from_char(terminal);
+ if (str == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_REQUEST_PTY) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, str) < 0) {
+ ssh_string_free(str);
+ return -1;
+ }
+ ssh_string_free(str);
+
+ if (buffer_add_u32(session->out_buffer, ntohl(row)) < 0 ||
+ buffer_add_u32(session->out_buffer, ntohl(col)) < 0 ||
+ buffer_add_u32(session->out_buffer, 0) < 0 || /* x */
+ buffer_add_u32(session->out_buffer, 0) < 0 || /* y */
+ buffer_add_u8(session->out_buffer, 0) < 0) { /* tty things */
+ return -1;
+ }
+
+ ssh_log(session, SSH_LOG_FUNCTIONS, "Opening a ssh1 pty");
+
+ if (packet_send(session) == SSH_ERROR) {
+ return -1;
+ }
+ switch(channel->request_state){
+ case SSH_CHANNEL_REQ_STATE_ERROR:
+ case SSH_CHANNEL_REQ_STATE_PENDING:
+ case SSH_CHANNEL_REQ_STATE_NONE:
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ return SSH_ERROR;
+ case SSH_CHANNEL_REQ_STATE_ACCEPTED:
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ ssh_log(session, SSH_LOG_RARE, "PTY: Success");
+ return SSH_OK;
+ case SSH_CHANNEL_REQ_STATE_DENIED:
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Server denied PTY allocation");
+ ssh_log(session, SSH_LOG_RARE, "PTY: denied\n");
+ return SSH_ERROR;
+ }
+ // Not reached
+ return SSH_ERROR;
+}
+
+int channel_change_pty_size1(ssh_channel channel, int cols, int rows) {
+ ssh_session session = channel->session;
+ if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE){
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Wrong request state");
+ return SSH_ERROR;
+ }
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_WINDOW_SIZE) < 0 ||
+ buffer_add_u32(session->out_buffer, ntohl(rows)) < 0 ||
+ buffer_add_u32(session->out_buffer, ntohl(cols)) < 0 ||
+ buffer_add_u32(session->out_buffer, 0) < 0 ||
+ buffer_add_u32(session->out_buffer, 0) < 0) {
+ return SSH_ERROR;
+ }
+ channel->request_state=SSH_CHANNEL_REQ_STATE_PENDING;
+ if (packet_send(session) == SSH_ERROR) {
+ return SSH_ERROR;
+ }
+
+ ssh_log(session, SSH_LOG_PROTOCOL, "Change pty size send");
+ while(channel->request_state==SSH_CHANNEL_REQ_STATE_PENDING){
+ ssh_handle_packets(session,-1);
+ }
+ switch(channel->request_state){
+ case SSH_CHANNEL_REQ_STATE_ERROR:
+ case SSH_CHANNEL_REQ_STATE_PENDING:
+ case SSH_CHANNEL_REQ_STATE_NONE:
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ return SSH_ERROR;
+ case SSH_CHANNEL_REQ_STATE_ACCEPTED:
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ ssh_log(session, SSH_LOG_PROTOCOL, "pty size changed");
+ return SSH_OK;
+ case SSH_CHANNEL_REQ_STATE_DENIED:
+ channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;
+ ssh_log(session, SSH_LOG_RARE, "pty size change denied");
+ ssh_set_error(session, SSH_REQUEST_DENIED, "pty size change denied");
+ return SSH_ERROR;
+ }
+ // Not reached
+ return SSH_ERROR;
+
+}
+
+int channel_request_shell1(ssh_channel channel) {
+ ssh_session session = channel->session;
+
+ if (buffer_add_u8(session->out_buffer,SSH_CMSG_EXEC_SHELL) < 0) {
+ return -1;
+ }
+
+ if (packet_send(session) == SSH_ERROR) {
+ return -1;
+ }
+
+ ssh_log(session, SSH_LOG_RARE, "Launched a shell");
+
+ return 0;
+}
+
+int channel_request_exec1(ssh_channel channel, const char *cmd) {
+ ssh_session session = channel->session;
+ ssh_string command = NULL;
+
+ command = ssh_string_from_char(cmd);
+ if (command == NULL) {
+ return -1;
+ }
+
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_EXEC_CMD) < 0 ||
+ buffer_add_ssh_string(session->out_buffer, command) < 0) {
+ ssh_string_free(command);
+ return -1;
+ }
+ ssh_string_free(command);
+
+ if(packet_send(session) == SSH_ERROR) {
+ return -1;
+ }
+
+ ssh_log(session, SSH_LOG_RARE, "Executing %s ...", cmd);
+
+ return 0;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_data1){
+ ssh_channel channel = session->channels;
+ ssh_string str = NULL;
+ int is_stderr=(type==SSH_SMSG_STDOUT_DATA ? 0 : 1);
+ (void)user;
+ str = buffer_get_ssh_string(packet);
+ if (str == NULL) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "Invalid data packet !\n");
+ return SSH_PACKET_USED;
+ }
+
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Adding %zu bytes data in %d",
+ ssh_string_len(str), is_stderr);
+
+ if (channel_default_bufferize(channel, ssh_string_data(str), ssh_string_len(str),
+ is_stderr) < 0) {
+ ssh_string_free(str);
+ return SSH_PACKET_USED;
+ }
+ ssh_string_free(str);
+
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_close1){
+ ssh_channel channel = session->channels;
+ uint32_t status;
+ (void)type;
+ (void)user;
+ buffer_get_u32(packet, &status);
+ /*
+ * It's much more than a channel closing. spec says it's the last
+ * message sent by server (strange)
+ */
+
+ /* actually status is lost somewhere */
+ channel->state = SSH_CHANNEL_STATE_CLOSED;
+ channel->remote_eof = 1;
+
+ buffer_add_u8(session->out_buffer, SSH_CMSG_EXIT_CONFIRMATION);
+ packet_send(session);
+
+ return SSH_PACKET_USED;
+}
+
+
+int channel_write1(ssh_channel channel, const void *data, int len) {
+ ssh_session session = channel->session;
+ int origlen = len;
+ int effectivelen;
+ const unsigned char *ptr=data;
+ while (len > 0) {
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_STDIN_DATA) < 0) {
+ return -1;
+ }
+
+ effectivelen = len > 32000 ? 32000 : len;
+
+ if (buffer_add_u32(session->out_buffer, htonl(effectivelen)) < 0 ||
+ buffer_add_data(session->out_buffer, ptr, effectivelen) < 0) {
+ return -1;
+ }
+
+ ptr += effectivelen;
+ len -= effectivelen;
+
+ if (packet_send(session) == SSH_ERROR) {
+ return -1;
+ }
+ }
+
+ return origlen;
+}
+
+#endif /* WITH_SSH1 */
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/client.c b/src/client.c
new file mode 100644
index 00000000..d12aa117
--- /dev/null
+++ b/src/client.c
@@ -0,0 +1,814 @@
+/*
+ * client.c - SSH client functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.h"
+#include "libssh/socket.h"
+#include "libssh/session.h"
+#include "libssh/dh.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)
+
+/**
+ * @internal
+ * @brief Callback to be called when the socket is connected or had a
+ * connection error. Changes the state of the session and updates the error
+ * message.
+ * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR
+ * @param user is a pointer to session
+ */
+static void socket_callback_connected(int code, int errno_code, void *user){
+ ssh_session session=(ssh_session)user;
+ enter_function();
+ ssh_log(session,SSH_LOG_RARE,"Socket connection callback: %d (%d)",code, errno_code);
+ if(code == SSH_SOCKET_CONNECTED_OK)
+ session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED;
+ else {
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ ssh_set_error(session,SSH_FATAL,"%s",strerror(errno_code));
+ }
+ session->ssh_connection_callback(session);
+ 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->serverbanner=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;
+}
+
+/**
+ * @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->serverbanner;
+ 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 server version: %d.%d (%x)",
+ major, minor, session->openssh);
+ }
+
+ return 0;
+}
+
+/** @internal
+ * @brief Sends a SSH banner to the server.
+ *
+ * @param session The SSH session to use.
+ *
+ * @param server Send client or server banner.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int ssh_send_banner(ssh_session session, int server) {
+ const char *banner = NULL;
+ char buffer[128] = {0};
+ int err=SSH_ERROR;
+
+ enter_function();
+
+ banner = session->version == 1 ? CLIENTBANNER1 : CLIENTBANNER2;
+
+ if (session->xbanner) {
+ banner = session->xbanner;
+ }
+
+ if (server) {
+ session->serverbanner = strdup(banner);
+ if (session->serverbanner == NULL) {
+ goto end;
+ }
+ } else {
+ session->clientbanner = strdup(banner);
+ if (session->clientbanner == NULL) {
+ goto end;
+ }
+ }
+
+ snprintf(buffer, 128, "%s\n", banner);
+
+ if (ssh_socket_write(session->socket, buffer, strlen(buffer)) == SSH_ERROR) {
+ goto end;
+ }
+ if (ssh_socket_nonblocking_flush(session->socket) == SSH_ERROR){
+ goto end;
+ }
+#ifdef WITH_PCAP
+ if(session->pcap_ctx)
+ ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT,buffer,strlen(buffer),strlen(buffer));
+#endif
+ err=SSH_OK;
+end:
+ leave_function();
+ return err;
+}
+
+
+
+SSH_PACKET_CALLBACK(ssh_packet_dh_reply){
+ ssh_string f = NULL;
+ ssh_string pubkey = NULL;
+ ssh_string signature = NULL;
+ (void)type;
+ (void)user;
+ ssh_log(session,SSH_LOG_PROTOCOL,"Received SSH_KEXDH_REPLY");
+ if(session->session_state!= SSH_SESSION_STATE_DH &&
+ session->dh_handshake_state != DH_STATE_INIT_SENT){
+ ssh_set_error(session,SSH_FATAL,"ssh_packet_dh_reply called in wrong state : %d:%d",
+ session->session_state,session->dh_handshake_state);
+ goto error;
+ }
+
+ pubkey = buffer_get_ssh_string(packet);
+ if (pubkey == NULL){
+ ssh_set_error(session,SSH_FATAL, "No public key in packet");
+ goto error;
+ }
+ dh_import_pubkey(session, pubkey);
+
+ f = buffer_get_ssh_string(packet);
+ if (f == NULL) {
+ ssh_set_error(session,SSH_FATAL, "No F number in packet");
+ goto error;
+ }
+ if (dh_import_f(session, f) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Cannot import f number");
+ goto error;
+ }
+ ssh_string_burn(f);
+ ssh_string_free(f);
+ f=NULL;
+ signature = buffer_get_ssh_string(packet);
+ if (signature == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No signature in packet");
+ goto error;
+ }
+ session->dh_server_signature = signature;
+ signature=NULL; /* ownership changed */
+ if (dh_build_k(session) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Cannot build k number");
+ goto error;
+ }
+
+ /* Send the MSG_NEWKEYS */
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) {
+ goto error;
+ }
+
+ packet_send(session);
+ ssh_log(session, SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent");
+
+ session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
+ return SSH_PACKET_USED;
+error:
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_newkeys){
+ ssh_string signature = NULL;
+ int rc;
+ (void)packet;
+ (void)user;
+ (void)type;
+ ssh_log(session, SSH_LOG_PROTOCOL, "Received SSH_MSG_NEWKEYS");
+ if(session->session_state!= SSH_SESSION_STATE_DH &&
+ session->dh_handshake_state != DH_STATE_NEWKEYS_SENT){
+ ssh_set_error(session,SSH_FATAL,"ssh_packet_newkeys called in wrong state : %d:%d",
+ session->session_state,session->dh_handshake_state);
+ goto error;
+ }
+ if(session->server){
+ /* server things are done in server.c */
+ session->dh_handshake_state=DH_STATE_FINISHED;
+ } else {
+ /* client */
+ rc = make_sessionid(session);
+ if (rc != SSH_OK) {
+ goto error;
+ }
+
+ /*
+ * Set the cryptographic functions for the next crypto
+ * (it is needed for generate_session_keys for key lengths)
+ */
+ if (crypt_set_algorithms(session)) {
+ goto error;
+ }
+
+ if (generate_session_keys(session) < 0) {
+ goto error;
+ }
+
+ /* Verify the host's signature. FIXME do it sooner */
+ signature = session->dh_server_signature;
+ session->dh_server_signature = NULL;
+ if (signature_verify(session, signature)) {
+ goto error;
+ }
+
+ /* forget it for now ... */
+ ssh_string_burn(signature);
+ ssh_string_free(signature);
+ signature=NULL;
+ /*
+ * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and
+ * current_crypto
+ */
+ if (session->current_crypto) {
+ crypto_free(session->current_crypto);
+ session->current_crypto=NULL;
+ }
+
+ /* FIXME later, include a function to change keys */
+ session->current_crypto = session->next_crypto;
+
+ session->next_crypto = crypto_new();
+ if (session->next_crypto == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ }
+ session->dh_handshake_state = DH_STATE_FINISHED;
+ session->ssh_connection_callback(session);
+ return SSH_PACKET_USED;
+error:
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ return SSH_PACKET_USED;
+}
+
+/** @internal
+ * @brief launches the DH handshake state machine
+ * @param session session handle
+ * @returns SSH_OK or SSH_ERROR
+ * @warning this function returning is no proof that DH handshake is
+ * completed
+ */
+static int dh_handshake(ssh_session session) {
+ ssh_string e = NULL;
+ ssh_string f = NULL;
+ ssh_string signature = NULL;
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+ switch (session->dh_handshake_state) {
+ case DH_STATE_INIT:
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_INIT) < 0) {
+ goto error;
+ }
+
+ if (dh_generate_x(session) < 0) {
+ goto error;
+ }
+ if (dh_generate_e(session) < 0) {
+ goto error;
+ }
+
+ e = dh_get_e(session);
+ if (e == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(session->out_buffer, e) < 0) {
+ goto error;
+ }
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ e=NULL;
+
+ rc = packet_send(session);
+ if (rc == SSH_ERROR) {
+ goto error;
+ }
+
+ session->dh_handshake_state = DH_STATE_INIT_SENT;
+ case DH_STATE_INIT_SENT:
+ /* wait until ssh_packet_dh_reply is called */
+ break;
+ case DH_STATE_NEWKEYS_SENT:
+ /* wait until ssh_packet_newkeys is called */
+ break;
+ case DH_STATE_FINISHED:
+ leave_function();
+ return SSH_OK;
+ default:
+ ssh_set_error(session, SSH_FATAL, "Invalid state in dh_handshake(): %d",
+ session->dh_handshake_state);
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ leave_function();
+ return SSH_AGAIN;
+error:
+ if(e != NULL){
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ }
+ if(f != NULL){
+ ssh_string_burn(f);
+ ssh_string_free(f);
+ }
+ if(signature != NULL){
+ ssh_string_burn(signature);
+ ssh_string_free(signature);
+ }
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @internal
+ * @brief handles a SSH_SERVICE_ACCEPT packet
+ *
+ */
+SSH_PACKET_CALLBACK(ssh_packet_service_accept){
+ (void)packet;
+ (void)type;
+ (void)user;
+ enter_function();
+ session->auth_service_state=SSH_AUTH_SERVICE_ACCEPTED;
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received SSH_MSG_SERVICE_ACCEPT");
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Request a service from the SSH server.
+ *
+ * Service requests are for example: ssh-userauth, ssh-connection, etc.
+ *
+ * @param session The session to use to ask for a service request.
+ * @param service The service request.
+ *
+ * @return SSH_OK on success
+ * @return SSH_ERROR on error
+ * @return SSH_AGAIN No response received yet
+ * @bug actually only works with ssh-userauth
+ */
+int ssh_service_request(ssh_session session, const char *service) {
+ ssh_string service_s = NULL;
+ int rc=SSH_ERROR;
+ enter_function();
+ switch(session->auth_service_state){
+ case SSH_AUTH_SERVICE_NONE:
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_SERVICE_REQUEST) < 0) {
+ break;
+ }
+ service_s = ssh_string_from_char(service);
+ if (service_s == NULL) {
+ break;
+ }
+
+ if (buffer_add_ssh_string(session->out_buffer,service_s) < 0) {
+ ssh_string_free(service_s);
+ break;
+ }
+ ssh_string_free(service_s);
+
+ if (packet_send(session) == SSH_ERROR) {
+ ssh_set_error(session, SSH_FATAL,
+ "Sending SSH2_MSG_SERVICE_REQUEST failed.");
+ break;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Sent SSH_MSG_SERVICE_REQUEST (service %s)", service);
+ session->auth_service_state=SSH_AUTH_SERVICE_SENT;
+ rc=SSH_AGAIN;
+ break;
+ case SSH_AUTH_SERVICE_DENIED:
+ ssh_set_error(session,SSH_FATAL,"ssh_auth_service request denied");
+ break;
+ case SSH_AUTH_SERVICE_ACCEPTED:
+ rc=SSH_OK;
+ break;
+ case SSH_AUTH_SERVICE_SENT:
+ rc=SSH_AGAIN;
+ break;
+ case SSH_AUTH_SERVICE_USER_SENT:
+ /* Invalid state, SSH1 specific */
+ rc=SSH_ERROR;
+ break;
+ }
+
+ leave_function();
+ return rc;
+}
+
+/**
+ * @addtogroup libssh_session
+ *
+ * @{
+ */
+
+/**
+ * @internal
+ *
+ * @brief A function to be called each time a step has been done in the
+ * connection.
+ */
+static void ssh_client_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->serverbanner == NULL) {
+ goto error;
+ }
+ set_status(session, 0.4f);
+ ssh_log(session, SSH_LOG_RARE,
+ "SSH server banner: %s", session->serverbanner);
+
+ /* 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->serverbanner);
+ 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);
+ ssh_send_banner(session, 0);
+ set_status(session, 0.5f);
+ session->session_state=SSH_SESSION_STATE_INITIAL_KEX;
+ 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->server_kex);
+ if (set_kex(session) < 0) {
+ goto error;
+ }
+ if (ssh_send_kex(session, 0) < 0) {
+ goto error;
+ }
+ set_status(session,0.8f);
+ session->session_state=SSH_SESSION_STATE_DH;
+ if (dh_handshake(session) == SSH_ERROR) {
+ goto error;
+ }
+ case SSH_SESSION_STATE_DH:
+ if(session->dh_handshake_state==DH_STATE_FINISHED){
+ 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();
+}
+
+/**
+ * @brief Connect to the ssh server.
+ *
+ * @param[in] session The ssh session to connect.
+ *
+ * @returns SSH_OK on success, SSH_ERROR on error.
+ *
+ * @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();
+
+ 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;
+ }
+
+ 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");
+ 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_connect: Actual state : %d",session->session_state);
+ }
+ leave_function();
+ if(session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED)
+ return SSH_ERROR;
+ return SSH_OK;
+}
+
+/**
+ * @brief Get the issue banner from the server.
+ *
+ * This is the banner showing a disclaimer to users who log in,
+ * typically their right or the fact that they will be monitored.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @return A newly allocated string with the banner, NULL on error.
+ */
+char *ssh_get_issue_banner(ssh_session session) {
+ if (session == NULL || session->banner == NULL) {
+ return NULL;
+ }
+
+ return ssh_string_to_char(session->banner);
+}
+
+/**
+ * @brief Get the version of the OpenSSH server, if it is not an OpenSSH server
+ * then 0 will be returned.
+ *
+ * You can use the SSH_VERSION_INT macro to compare version numbers.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @return The version number if available, 0 otherwise.
+ */
+int ssh_get_openssh_version(ssh_session session) {
+ if (session == NULL) {
+ return 0;
+ }
+
+ return session->openssh;
+}
+
+/**
+ * @brief Disconnect from a session (client or server).
+ * The session can then be reused to open a new session.
+ *
+ * @param[in] session The SSH session to use.
+ */
+void ssh_disconnect(ssh_session session) {
+ ssh_string str = NULL;
+
+ if (session == NULL) {
+ return;
+ }
+
+ enter_function();
+
+ if (ssh_socket_is_open(session->socket)) {
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_DISCONNECT) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer,
+ htonl(SSH2_DISCONNECT_BY_APPLICATION)) < 0) {
+ goto error;
+ }
+
+ str = ssh_string_from_char("Bye Bye");
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(session->out_buffer,str) < 0) {
+ ssh_string_free(str);
+ goto error;
+ }
+ ssh_string_free(str);
+
+ packet_send(session);
+ ssh_socket_close(session->socket);
+ }
+ session->alive = 0;
+
+error:
+ leave_function();
+}
+
+const char *ssh_copyright(void) {
+ return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2010 Aris Adamantiadis "
+ "(aris@0xbadc0de.be) Distributed under the LGPL, please refer to COPYING"
+ "file for information about your rights";
+}
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 00000000..cfe2cc5f
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,347 @@
+/*
+ * config.c - parse the ssh config file
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/misc.h"
+
+enum ssh_config_opcode_e {
+ SOC_UNSUPPORTED = -1,
+ SOC_HOST,
+ SOC_HOSTNAME,
+ SOC_PORT,
+ SOC_USERNAME,
+ SOC_IDENTITY,
+ SOC_CIPHERS,
+ SOC_COMPRESSION,
+ SOC_TIMEOUT,
+ SOC_PROTOCOL,
+ SOC_HOSTKEYCHECK,
+ SOC_KNOWNHOSTS,
+ SOC_PROXYCOMMAND
+};
+
+struct ssh_config_keyword_table_s {
+ const char *name;
+ enum ssh_config_opcode_e opcode;
+};
+
+static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
+ { "host", SOC_HOST },
+ { "hostname", SOC_HOSTNAME },
+ { "port", SOC_PORT },
+ { "user", SOC_USERNAME },
+ { "identityfile", SOC_IDENTITY },
+ { "ciphers", SOC_CIPHERS },
+ { "compression", SOC_COMPRESSION },
+ { "connecttimeout", SOC_TIMEOUT },
+ { "protocol", SOC_PROTOCOL },
+ { "stricthostkeychecking", SOC_HOSTKEYCHECK },
+ { "userknownhostsfile", SOC_KNOWNHOSTS },
+ { "proxycommand", SOC_PROXYCOMMAND },
+ { NULL, SOC_UNSUPPORTED }
+};
+
+static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
+ int i;
+
+ for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
+ if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
+ return ssh_config_keyword_table[i].opcode;
+ }
+ }
+
+ return SOC_UNSUPPORTED;
+}
+
+static char *ssh_config_get_token(char **str) {
+ register char *c;
+ char *r;
+
+ /* Ignore leading spaces */
+ for (c = *str; *c; c++) {
+ if (! isblank(*c)) {
+ break;
+ }
+ }
+
+ if (*c == '\"') {
+ for (r = ++c; *c; c++) {
+ if (*c == '\"') {
+ *c = '\0';
+ goto out;
+ }
+ }
+ }
+
+ for (r = c; *c; c++) {
+ if (isblank(*c)) {
+ *c = '\0';
+ goto out;
+ }
+ }
+
+out:
+ *str = c + 1;
+
+ return r;
+}
+
+static int ssh_config_get_int(char **str, int notfound) {
+ char *p, *endp;
+ int i;
+
+ p = ssh_config_get_token(str);
+ if (p && *p) {
+ i = strtol(p, &endp, 10);
+ if (p == endp) {
+ return notfound;
+ }
+ return i;
+ }
+
+ return notfound;
+}
+
+static const char *ssh_config_get_str(char **str, const char *def) {
+ char *p;
+
+ p = ssh_config_get_token(str);
+ if (p && *p) {
+ return p;
+ }
+
+ return def;
+}
+
+static int ssh_config_get_yesno(char **str, int notfound) {
+ const char *p;
+
+ p = ssh_config_get_str(str, NULL);
+ if (p == NULL) {
+ return notfound;
+ }
+
+ if (strncasecmp(p, "yes", 3) == 0) {
+ return 1;
+ } else if (strncasecmp(p, "no", 2) == 0) {
+ return 0;
+ }
+
+ return notfound;
+}
+
+static int ssh_config_parse_line(ssh_session session, const char *line,
+ unsigned int count, int *parsing) {
+ enum ssh_config_opcode_e opcode;
+ const char *p;
+ char *s, *x;
+ char *keyword;
+ char *lowerhost;
+ size_t len;
+ int i;
+
+ x = s = strdup(line);
+ if (s == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+
+ /* Remove trailing spaces */
+ for (len = strlen(s) - 1; len > 0; len--) {
+ if (! isspace(s[len])) {
+ break;
+ }
+ s[len] = '\0';
+ }
+
+ keyword = ssh_config_get_token(&s);
+ if (keyword == NULL || *keyword == '#' ||
+ *keyword == '\0' || *keyword == '\n') {
+ SAFE_FREE(x);
+ return 0;
+ }
+
+ opcode = ssh_config_get_opcode(keyword);
+
+ switch (opcode) {
+ case SOC_HOST:
+ *parsing = 0;
+ lowerhost = (session->host) ? ssh_lowercase(session->host) : NULL;
+ for (p = ssh_config_get_str(&s, NULL); p && *p;
+ p = ssh_config_get_str(&s, NULL)) {
+ if (match_hostname(lowerhost, p, strlen(p))) {
+ *parsing = 1;
+ }
+ }
+ SAFE_FREE(lowerhost);
+ break;
+ case SOC_HOSTNAME:
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_HOST, p);
+ }
+ break;
+ case SOC_PORT:
+ if (session->port == 22) {
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_PORT_STR, p);
+ }
+ }
+ break;
+ case SOC_USERNAME:
+ if (session->username == NULL) {
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_USER, p);
+ }
+ }
+ break;
+ case SOC_IDENTITY:
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
+ }
+ break;
+ case SOC_CIPHERS:
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
+ ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
+ }
+ break;
+ case SOC_COMPRESSION:
+ i = ssh_config_get_yesno(&s, -1);
+ if (i >= 0 && *parsing) {
+ if (i) {
+ ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib,none");
+ ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib,none");
+ } else {
+ ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "none");
+ ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "none");
+ }
+ }
+ break;
+ case SOC_PROTOCOL:
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ char *a, *b;
+ b = strdup(p);
+ if (b == NULL) {
+ SAFE_FREE(x);
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ i = 0;
+ ssh_options_set(session, SSH_OPTIONS_SSH1, &i);
+ ssh_options_set(session, SSH_OPTIONS_SSH2, &i);
+
+ for (a = strtok(b, ","); a; a = strtok(NULL, ",")) {
+ switch (atoi(a)) {
+ case 1:
+ i = 1;
+ ssh_options_set(session, SSH_OPTIONS_SSH1, &i);
+ break;
+ case 2:
+ i = 1;
+ ssh_options_set(session, SSH_OPTIONS_SSH2, &i);
+ break;
+ default:
+ break;
+ }
+ }
+ SAFE_FREE(b);
+ }
+ break;
+ case SOC_TIMEOUT:
+ i = ssh_config_get_int(&s, -1);
+ if (i >= 0 && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &i);
+ }
+ break;
+ case SOC_HOSTKEYCHECK:
+ i = ssh_config_get_yesno(&s, -1);
+ if (i >= 0 && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_HOSTKEYCHECK, &i);
+ }
+ break;
+ case SOC_KNOWNHOSTS:
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
+ }
+ break;
+ case SOC_PROXYCOMMAND:
+ p = ssh_config_get_str(&s, NULL);
+ if (p && *parsing) {
+ ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
+ }
+ break;
+ case SOC_UNSUPPORTED:
+ ssh_log(session, SSH_LOG_RARE, "Unsupported option: %s, line: %d\n",
+ keyword, count);
+ break;
+ default:
+ ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d\n",
+ opcode);
+ SAFE_FREE(x);
+ return -1;
+ break;
+ }
+
+ SAFE_FREE(x);
+ return 0;
+}
+
+/* ssh_config_parse_file */
+int ssh_config_parse_file(ssh_session session, const char *filename) {
+ char line[1024] = {0};
+ unsigned int count = 0;
+ FILE *f;
+ int parsing;
+
+ if ((f = fopen(filename, "r")) == NULL) {
+ return 0;
+ }
+
+ if (session->log_verbosity) {
+ fprintf(stderr, "Reading configuration data from %s\n", filename);
+ }
+
+ parsing = 1;
+ while (fgets(line, sizeof(line), f)) {
+ count++;
+ if (ssh_config_parse_line(session, line, count, &parsing) < 0) {
+ fclose(f);
+ return -1;
+ }
+ }
+
+ fclose(f);
+ return 0;
+}
diff --git a/src/connect.c b/src/connect.c
new file mode 100644
index 00000000..d877982c
--- /dev/null
+++ b/src/connect.c
@@ -0,0 +1,600 @@
+/*
+ * connect.c - handles connections to ssh servers
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2009 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef _WIN32
+/*
+ * Only use Windows API functions available on Windows 2000 SP4 or later.
+ * The available constants are in <sdkddkver.h>.
+ * http://msdn.microsoft.com/en-us/library/aa383745.aspx
+ * http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx
+ */
+#undef _WIN32_WINNT
+#ifdef HAVE_WSPIAPI_H
+#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */
+#undef NTDDI_VERSION
+#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */
+#else
+#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */
+#undef NTDDI_VERSION
+#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */
+#endif
+
+#if _MSC_VER >= 1400
+#include <io.h>
+#undef close
+#define close _close
+#endif /* _MSC_VER */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+/* <wspiapi.h> is necessary for getaddrinfo before Windows XP, but it isn't
+ * available on some platforms like MinGW. */
+#ifdef HAVE_WSPIAPI_H
+#include <wspiapi.h>
+#endif
+
+#else /* _WIN32 */
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+
+#endif /* _WIN32 */
+
+#include "libssh/priv.h"
+#include "libssh/socket.h"
+#include "libssh/channels.h"
+#include "libssh/session.h"
+
+#ifndef HAVE_SELECT
+#error "Your system must have select()"
+#endif
+
+#ifndef HAVE_GETADDRINFO
+#error "Your system must have getaddrinfo()"
+#endif
+
+#ifdef HAVE_REGCOMP
+/* don't declare gnu extended regexp's */
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE
+#endif
+#include <regex.h>
+#endif /* HAVE_REGCOMP */
+
+#ifdef _WIN32
+void ssh_sock_set_nonblocking(socket_t sock) {
+ u_long nonblocking = 1;
+ ioctlsocket(sock, FIONBIO, &nonblocking);
+}
+
+void ssh_sock_set_blocking(socket_t sock) {
+ u_long nonblocking = 0;
+ ioctlsocket(sock, FIONBIO, &nonblocking);
+}
+
+#ifndef gai_strerror
+char WSAAPI *gai_strerrorA(int code) {
+ static char buf[256];
+
+ snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code);
+
+ return buf;
+}
+#endif /* gai_strerror */
+
+#else /* _WIN32 */
+void ssh_sock_set_nonblocking(socket_t sock) {
+ fcntl(sock, F_SETFL, O_NONBLOCK);
+}
+
+void ssh_sock_set_blocking(socket_t sock) {
+ fcntl(sock, F_SETFL, 0);
+}
+
+#endif /* _WIN32 */
+
+#ifdef HAVE_REGCOMP
+static regex_t *ip_regex = NULL;
+
+/** @internal
+ * @brief initializes and compile the regexp to be used for IP matching
+ * @returns -1 on error (and error message is set)
+ * @returns 0 on success
+ */
+int ssh_regex_init(){
+ if(ip_regex==NULL){
+ int err;
+ regex_t *regex=malloc(sizeof (regex_t));
+ ZERO_STRUCTP(regex);
+ err=regcomp(regex,"^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$",REG_EXTENDED | REG_NOSUB);
+ if(err != 0){
+ char buffer[128];
+ regerror(err,regex,buffer,sizeof(buffer));
+ fprintf(stderr,"Error while compiling regular expression : %s\n",buffer);
+ SAFE_FREE(regex);
+ return -1;
+ }
+ ip_regex=regex;
+ }
+ return 0;
+}
+
+/** @internal
+ * @brief clean up the IP regexp
+ */
+void ssh_regex_finalize(){
+ if(ip_regex){
+ regfree(ip_regex);
+ SAFE_FREE(ip_regex);
+ }
+}
+
+#else /* HAVE_REGCOMP */
+int ssh_regex_init(){
+ return 0;
+}
+void ssh_regex_finalize(){
+}
+#endif
+
+
+static int ssh_connect_socket_close(socket_t s){
+#ifdef _WIN32
+ return closesocket(s);
+#else
+ return close(s);
+#endif
+}
+
+
+static int getai(ssh_session session, const char *host, int port, struct addrinfo **ai) {
+ const char *service = NULL;
+ struct addrinfo hints;
+ char s_port[10];
+
+ ZERO_STRUCT(hints);
+
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (port == 0) {
+ hints.ai_flags = AI_PASSIVE;
+ } else {
+ snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port);
+ service = s_port;
+#ifdef AI_NUMERICSERV
+ hints.ai_flags=AI_NUMERICSERV;
+#endif
+ }
+#ifdef HAVE_REGCOMP
+ if(regexec(ip_regex,host,0,NULL,0) == 0){
+ /* this is an IP address */
+ ssh_log(session,SSH_LOG_PACKET,"host %s matches an IP address",host);
+ hints.ai_flags |= AI_NUMERICHOST;
+ }
+#endif
+ return getaddrinfo(host, service, &hints, ai);
+}
+
+static int ssh_connect_ai_timeout(ssh_session session, const char *host,
+ int port, struct addrinfo *ai, long timeout, long usec, socket_t s) {
+ struct timeval to;
+ fd_set set;
+ int rc = 0;
+ unsigned int len = sizeof(rc);
+
+ enter_function();
+
+ to.tv_sec = timeout;
+ to.tv_usec = usec;
+
+ ssh_sock_set_nonblocking(s);
+
+ ssh_log(session, SSH_LOG_RARE, "Trying to connect to host: %s:%d with "
+ "timeout %ld.%ld", host, port, timeout, usec);
+
+ /* The return value is checked later */
+ connect(s, ai->ai_addr, ai->ai_addrlen);
+ freeaddrinfo(ai);
+
+ FD_ZERO(&set);
+ FD_SET(s, &set);
+
+ rc = select(s + 1, NULL, &set, NULL, &to);
+ if (rc == 0) {
+ /* timeout */
+ ssh_set_error(session, SSH_FATAL,
+ "Timeout while connecting to %s:%d", host, port);
+ ssh_connect_socket_close(s);
+ leave_function();
+ return -1;
+ }
+
+ if (rc < 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Select error: %s", strerror(errno));
+ ssh_connect_socket_close(s);
+ leave_function();
+ return -1;
+ }
+ rc = 0;
+
+ /* Get connect(2) return code. Zero means no error */
+ getsockopt(s, SOL_SOCKET, SO_ERROR,(char *) &rc, &len);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Connect to %s:%d failed: %s", host, port, strerror(rc));
+ ssh_connect_socket_close(s);
+ leave_function();
+ return -1;
+ }
+
+ /* s is connected ? */
+ ssh_log(session, SSH_LOG_PACKET, "Socket connected with timeout\n");
+ ssh_sock_set_blocking(s);
+
+ leave_function();
+ return s;
+}
+
+/**
+ * @internal
+ *
+ * @brief Connect to an IPv4 or IPv6 host specified by its IP address or
+ * hostname.
+ *
+ * @returns A file descriptor, < 0 on error.
+ */
+socket_t ssh_connect_host(ssh_session session, const char *host,
+ const char *bind_addr, int port, long timeout, long usec) {
+ socket_t s = -1;
+ int rc;
+ struct addrinfo *ai;
+ struct addrinfo *itr;
+
+ enter_function();
+
+ rc = getai(session,host, port, &ai);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Failed to resolve hostname %s (%s)", host, gai_strerror(rc));
+ leave_function();
+ return -1;
+ }
+
+ for (itr = ai; itr != NULL; itr = itr->ai_next){
+ /* create socket */
+ s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol);
+ if (s < 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Socket create failed: %s", strerror(errno));
+ continue;
+ }
+
+ if (bind_addr) {
+ struct addrinfo *bind_ai;
+ struct addrinfo *bind_itr;
+
+ ssh_log(session, SSH_LOG_PACKET, "Resolving %s\n", bind_addr);
+
+ rc = getai(session,bind_addr, 0, &bind_ai);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Failed to resolve bind address %s (%s)",
+ bind_addr,
+ gai_strerror(rc));
+ leave_function();
+ return -1;
+ }
+
+ for (bind_itr = bind_ai; bind_itr != NULL; bind_itr = bind_itr->ai_next) {
+ if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Binding local address: %s", strerror(errno));
+ continue;
+ } else {
+ break;
+ }
+ }
+ freeaddrinfo(bind_ai);
+
+ /* Cannot bind to any local addresses */
+ if (bind_itr == NULL) {
+ ssh_connect_socket_close(s);
+ s = -1;
+ continue;
+ }
+ }
+
+ if (timeout || usec) {
+ socket_t ret = ssh_connect_ai_timeout(session, host, port, itr,
+ timeout, usec, s);
+ leave_function();
+ return ret;
+ }
+
+ if (connect(s, itr->ai_addr, itr->ai_addrlen) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Connect failed: %s", strerror(errno));
+ ssh_connect_socket_close(s);
+ s = -1;
+ leave_function();
+ continue;
+ } else {
+ /* We are connected */
+ break;
+ }
+ }
+
+ freeaddrinfo(ai);
+ leave_function();
+
+ return s;
+}
+
+/**
+ * @internal
+ *
+ * @brief Launches a nonblocking connect to an IPv4 or IPv6 host
+ * specified by its IP address or hostname.
+ *
+ * @returns A file descriptor, < 0 on error.
+ * @warning very ugly !!!
+ */
+socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
+ const char *bind_addr, int port) {
+ socket_t s = -1;
+ int rc;
+ struct addrinfo *ai;
+ struct addrinfo *itr;
+
+ enter_function();
+
+ rc = getai(session,host, port, &ai);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Failed to resolve hostname %s (%s)", host, gai_strerror(rc));
+ leave_function();
+ return -1;
+ }
+
+ for (itr = ai; itr != NULL; itr = itr->ai_next){
+ /* create socket */
+ s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol);
+ if (s < 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Socket create failed: %s", strerror(errno));
+ continue;
+ }
+
+ if (bind_addr) {
+ struct addrinfo *bind_ai;
+ struct addrinfo *bind_itr;
+
+ ssh_log(session, SSH_LOG_PACKET, "Resolving %s\n", bind_addr);
+
+ rc = getai(session,bind_addr, 0, &bind_ai);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Failed to resolve bind address %s (%s)",
+ bind_addr,
+ gai_strerror(rc));
+ close(s);
+ s=-1;
+ break;
+ }
+
+ for (bind_itr = bind_ai; bind_itr != NULL; bind_itr = bind_itr->ai_next) {
+ if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Binding local address: %s", strerror(errno));
+ continue;
+ } else {
+ break;
+ }
+ }
+ freeaddrinfo(bind_ai);
+
+ /* Cannot bind to any local addresses */
+ if (bind_itr == NULL) {
+ ssh_connect_socket_close(s);
+ s = -1;
+ continue;
+ }
+ }
+ ssh_sock_set_nonblocking(s);
+
+ connect(s, itr->ai_addr, itr->ai_addrlen);
+ break;
+ }
+
+ freeaddrinfo(ai);
+ leave_function();
+
+ return s;
+}
+
+/**
+ * @addtogroup libssh_session
+ *
+ * @{
+ */
+
+/**
+ * @brief A wrapper for the select syscall
+ *
+ * This functions acts more or less like the select(2) syscall.\n
+ * There is no support for writing or exceptions.\n
+ *
+ * @param[in] channels Arrays of channels pointers terminated by a NULL.
+ * It is never rewritten.
+ *
+ * @param[out] outchannels Arrays of same size that "channels", there is no need
+ * to initialize it.
+ *
+ * @param[in] maxfd Maximum +1 file descriptor from readfds.
+ *
+ * @param[in] readfds A fd_set of file descriptors to be select'ed for
+ * reading.
+ *
+ * @param[in] timeout A timeout for the select.
+ *
+ * @return -1 if an error occured. E_INTR if it was interrupted, in
+ * that case, just restart it.
+ *
+ * @warning libssh is not threadsafe here. That means that if a signal is caught
+ * during the processing of this function, you cannot call ssh
+ * functions on sessions that are busy with ssh_select().
+ *
+ * @see select(2)
+ */
+int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd,
+ fd_set *readfds, struct timeval *timeout) {
+ struct timeval zerotime;
+ fd_set localset, localset2;
+ socket_t f;
+ int rep;
+ int set;
+ int i;
+ int j;
+
+ zerotime.tv_sec = 0;
+ zerotime.tv_usec = 0;
+
+ /*
+ * First, poll the maxfd file descriptors from the user with a zero-second
+ * timeout. They have the bigger priority.
+ */
+ if (maxfd > 0) {
+ memcpy(&localset, readfds, sizeof(fd_set));
+ rep = select(maxfd, &localset, NULL, NULL, &zerotime);
+ /* catch the eventual errors */
+ if (rep==-1) {
+ return -1;
+ }
+ }
+
+ /* Poll every channel */
+ j = 0;
+ for (i = 0; channels[i]; i++) {
+ if (channels[i]->session->alive) {
+ if(ssh_channel_poll(channels[i], 0) > 0) {
+ outchannels[j] = channels[i];
+ j++;
+ } else {
+ if(ssh_channel_poll(channels[i], 1) > 0) {
+ outchannels[j] = channels[i];
+ j++;
+ }
+ }
+ }
+ }
+ outchannels[j] = NULL;
+
+ /* Look into the localset for active fd */
+ set = 0;
+ for (f = 0; (f < maxfd) && !set; f++) {
+ if (FD_ISSET(f, &localset)) {
+ set = 1;
+ }
+ }
+
+ /* j != 0 means a channel has data */
+ if( (j != 0) || (set != 0)) {
+ if(maxfd > 0) {
+ memcpy(readfds, &localset, sizeof(fd_set));
+ }
+ return 0;
+ }
+
+ /*
+ * At this point, not any channel had any data ready for reading, nor any fd
+ * had data for reading.
+ */
+ memcpy(&localset, readfds, sizeof(fd_set));
+ for (i = 0; channels[i]; i++) {
+ if (channels[i]->session->alive) {
+ ssh_socket_fd_set(channels[i]->session->socket, &localset, &maxfd);
+ }
+ }
+
+ rep = select(maxfd, &localset, NULL, NULL, timeout);
+ if (rep == -1 && errno == EINTR) {
+ /* Interrupted by a signal */
+ return SSH_EINTR;
+ }
+
+ if (rep == -1) {
+ /*
+ * Was the error due to a libssh's channel or from a closed descriptor from
+ * the user? User closed descriptors have been caught in the first select
+ * and not closed since that moment. That case shouldn't occur at all
+ */
+ return -1;
+ }
+
+ /* Set the data_to_read flag on each session */
+ for (i = 0; channels[i]; i++) {
+ if (channels[i]->session->alive &&
+ ssh_socket_fd_isset(channels[i]->session->socket,&localset)) {
+ ssh_socket_set_toread(channels[i]->session->socket);
+ }
+ }
+
+ /* Now, test each channel */
+ j = 0;
+ for (i = 0; channels[i]; i++) {
+ if (channels[i]->session->alive &&
+ ssh_socket_fd_isset(channels[i]->session->socket,&localset)) {
+ if ((ssh_channel_poll(channels[i],0) > 0) ||
+ (ssh_channel_poll(channels[i], 1) > 0)) {
+ outchannels[j] = channels[i];
+ j++;
+ }
+ }
+ }
+ outchannels[j] = NULL;
+
+ FD_ZERO(&localset2);
+ for (f = 0; f < maxfd; f++) {
+ if (FD_ISSET(f, readfds) && FD_ISSET(f, &localset)) {
+ FD_SET(f, &localset2);
+ }
+ }
+
+ memcpy(readfds, &localset2, sizeof(fd_set));
+
+ return 0;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/crc32.c b/src/crc32.c
new file mode 100644
index 00000000..94d3020c
--- /dev/null
+++ b/src/crc32.c
@@ -0,0 +1,92 @@
+/*
+ * crc32.c - simple CRC32 code
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 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 "libssh/priv.h"
+
+static uint32_t crc_table[] = {
+ 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL,
+ 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL,
+ 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL,
+ 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL,
+ 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL,
+ 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL,
+ 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL,
+ 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL,
+ 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL,
+ 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL,
+ 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL,
+ 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL,
+ 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL,
+ 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL,
+ 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL,
+ 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL,
+ 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL,
+ 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL,
+ 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL,
+ 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL,
+ 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL,
+ 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL,
+ 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL,
+ 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL,
+ 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL,
+ 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL,
+ 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL,
+ 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL,
+ 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL,
+ 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL,
+ 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL,
+ 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL,
+ 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL,
+ 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL,
+ 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL,
+ 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL,
+ 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL,
+ 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL,
+ 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL,
+ 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL,
+ 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL,
+ 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL,
+ 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL,
+ 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL,
+ 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL,
+ 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL,
+ 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL,
+ 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL,
+ 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL,
+ 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL,
+ 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL,
+ 0x2d02ef8dUL
+};
+
+uint32_t ssh_crc32(const char *buf, uint32_t len) {
+ uint32_t ret = 0;
+ while(len > 0) {
+ ret = crc_table[(ret ^ *buf) & 0xff] ^ (ret >> 8);
+ --len;
+ ++buf;
+ }
+
+ return ret;
+}
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/crypt.c b/src/crypt.c
new file mode 100644
index 00000000..1085c4aa
--- /dev/null
+++ b/src/crypt.c
@@ -0,0 +1,216 @@
+/*
+ * crypt.c - blowfish-cbc code
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#ifdef OPENSSL_CRYPTO
+#include <openssl/blowfish.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/wrapper.h"
+#include "libssh/crypto.h"
+#include "libssh/buffer.h"
+
+uint32_t packet_decrypt_len(ssh_session session, char *crypted){
+ uint32_t decrypted;
+
+ if (session->current_crypto) {
+ if (packet_decrypt(session, crypted,
+ session->current_crypto->in_cipher->blocksize) < 0) {
+ return 0;
+ }
+ }
+
+ memcpy(&decrypted,crypted,sizeof(decrypted));
+ ssh_log(session, SSH_LOG_PACKET,
+ "Packet size decrypted: %lu (0x%lx)",
+ (long unsigned int) ntohl(decrypted),
+ (long unsigned int) ntohl(decrypted));
+ return ntohl(decrypted);
+}
+
+int packet_decrypt(ssh_session session, void *data,uint32_t len) {
+ struct crypto_struct *crypto = session->current_crypto->in_cipher;
+ char *out = NULL;
+ if(len % session->current_crypto->in_cipher->blocksize != 0){
+ ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set on at least one blocksize (received %d)",len);
+ return SSH_ERROR;
+ }
+ out = malloc(len);
+ if (out == NULL) {
+ return -1;
+ }
+
+ ssh_log(session,SSH_LOG_PACKET, "Decrypting %d bytes", len);
+
+#ifdef HAVE_LIBGCRYPT
+ if (crypto->set_decrypt_key(crypto, session->current_crypto->decryptkey,
+ session->current_crypto->decryptIV) < 0) {
+ SAFE_FREE(out);
+ return -1;
+ }
+ crypto->cbc_decrypt(crypto,data,out,len);
+#elif defined HAVE_LIBCRYPTO
+ if (crypto->set_decrypt_key(crypto, session->current_crypto->decryptkey) < 0) {
+ SAFE_FREE(out);
+ return -1;
+ }
+ crypto->cbc_decrypt(crypto,data,out,len,session->current_crypto->decryptIV);
+#endif
+
+ memcpy(data,out,len);
+ memset(out,0,len);
+
+ SAFE_FREE(out);
+ return 0;
+}
+
+unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len) {
+ struct crypto_struct *crypto = NULL;
+ HMACCTX ctx = NULL;
+ char *out = NULL;
+ unsigned int finallen;
+ uint32_t seq;
+
+ if (!session->current_crypto) {
+ return NULL; /* nothing to do here */
+ }
+ if(len % session->current_crypto->in_cipher->blocksize != 0){
+ ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set on at least one blocksize (received %d)",len);
+ return NULL;
+ }
+ out = malloc(len);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ seq = ntohl(session->send_seq);
+ crypto = session->current_crypto->out_cipher;
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Encrypting packet with seq num: %d, len: %d",
+ session->send_seq,len);
+
+#ifdef HAVE_LIBGCRYPT
+ if (crypto->set_encrypt_key(crypto, session->current_crypto->encryptkey,
+ session->current_crypto->encryptIV) < 0) {
+ SAFE_FREE(out);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ if (crypto->set_encrypt_key(crypto, session->current_crypto->encryptkey) < 0) {
+ SAFE_FREE(out);
+ return NULL;
+ }
+#endif
+
+ if (session->version == 2) {
+ ctx = hmac_init(session->current_crypto->encryptMAC,20,HMAC_SHA1);
+ if (ctx == NULL) {
+ SAFE_FREE(out);
+ return NULL;
+ }
+ hmac_update(ctx,(unsigned char *)&seq,sizeof(uint32_t));
+ hmac_update(ctx,data,len);
+ hmac_final(ctx,session->current_crypto->hmacbuf,&finallen);
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("mac: ",data,len);
+ if (finallen != 20) {
+ printf("Final len is %d\n",finallen);
+ }
+ ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, 20);
+#endif
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ crypto->cbc_encrypt(crypto, data, out, len);
+#elif defined HAVE_LIBCRYPTO
+ crypto->cbc_encrypt(crypto, data, out, len,
+ session->current_crypto->encryptIV);
+#endif
+
+ memcpy(data, out, len);
+ memset(out, 0, len);
+ SAFE_FREE(out);
+
+ if (session->version == 2) {
+ return session->current_crypto->hmacbuf;
+ }
+
+ return NULL;
+}
+
+/**
+ * @internal
+ *
+ * @brief Verify the hmac of a packet
+ *
+ * @param session The session to use.
+ * @param buffer The buffer to verify the hmac from.
+ * @param mac The mac to compare with the hmac.
+ *
+ * @return 0 if hmac and mac are equal, < 0 if not or an error
+ * occurred.
+ */
+int packet_hmac_verify(ssh_session session, ssh_buffer buffer,
+ unsigned char *mac) {
+ unsigned char hmacbuf[EVP_MAX_MD_SIZE] = {0};
+ HMACCTX ctx;
+ unsigned int len;
+ uint32_t seq;
+
+ ctx = hmac_init(session->current_crypto->decryptMAC, 20, HMAC_SHA1);
+ if (ctx == NULL) {
+ return -1;
+ }
+
+ seq = htonl(session->recv_seq);
+
+ hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t));
+ hmac_update(ctx, ssh_buffer_get_begin(buffer), ssh_buffer_get_len(buffer));
+ hmac_final(ctx, hmacbuf, &len);
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("received mac",mac,len);
+ ssh_print_hexa("Computed mac",hmacbuf,len);
+ ssh_print_hexa("seq",(unsigned char *)&seq,sizeof(uint32_t));
+#endif
+ if (memcmp(mac, hmacbuf, len) == 0) {
+ return 0;
+ }
+
+ return -1;
+}
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/dh.c b/src/dh.c
new file mode 100644
index 00000000..74d799e6
--- /dev/null
+++ b/src/dh.c
@@ -0,0 +1,1049 @@
+/*
+ * dh.c - Diffie-Helman algorithm code against SSH 2
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 by Aris Adamantiadis
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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.
+ */
+
+/*
+ * Let us resume the dh protocol.
+ * Each side computes a private prime number, x at client side, y at server
+ * side.
+ * g and n are two numbers common to every ssh software.
+ * client's public key (e) is calculated by doing:
+ * e = g^x mod p
+ * client sends e to the server.
+ * the server computes his own public key, f
+ * f = g^y mod p
+ * it sends it to the client
+ * the common key K is calculated by the client by doing
+ * k = f^x mod p
+ * the server does the same with the client public key e
+ * k' = e^y mod p
+ * if everything went correctly, k and k' are equal
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/crypto.h"
+#include "libssh/buffer.h"
+#include "libssh/session.h"
+#include "libssh/keys.h"
+#include "libssh/dh.h"
+
+/* todo: remove it */
+#include "libssh/string.h"
+#ifdef HAVE_LIBCRYPTO
+#include <openssl/rand.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#endif
+
+static unsigned char p_value[] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
+ 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
+ 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
+ 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
+ 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
+ 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
+ 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
+ 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
+ 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
+ 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+#define P_LEN 128 /* Size in bytes of the p number */
+
+static unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */
+static bignum g;
+static bignum p;
+static int ssh_crypto_initialized;
+
+int ssh_get_random(void *where, int len, int strong){
+
+#ifdef HAVE_LIBGCRYPT
+ /* variable not used in gcrypt */
+ (void) strong;
+ /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */
+ gcry_randomize(where,len,GCRY_STRONG_RANDOM);
+
+ return 1;
+#elif defined HAVE_LIBCRYPTO
+ if (strong) {
+ return RAND_bytes(where,len);
+ } else {
+ return RAND_pseudo_bytes(where,len);
+ }
+#endif
+
+ /* never reached */
+ return 1;
+}
+
+/*
+ * This inits the values g and p which are used for DH key agreement
+ * FIXME: Make the function thread safe by adding a semaphore or mutex.
+ */
+int ssh_crypto_init(void) {
+ if (ssh_crypto_initialized == 0) {
+#ifdef HAVE_LIBGCRYPT
+ gcry_check_version(NULL);
+ if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P,0)) {
+ gcry_control(GCRYCTL_INIT_SECMEM, 4096);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED,0);
+ }
+#endif
+
+ g = bignum_new();
+ if (g == NULL) {
+ return -1;
+ }
+ bignum_set_word(g,g_int);
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_bin2bn(p_value, P_LEN, &p);
+ if (p == NULL) {
+ bignum_free(g);
+ g = NULL;
+ return -1;
+ }
+#elif defined HAVE_LIBCRYPTO
+ p = bignum_new();
+ if (p == NULL) {
+ bignum_free(g);
+ g = NULL;
+ return -1;
+ }
+ bignum_bin2bn(p_value, P_LEN, p);
+ OpenSSL_add_all_algorithms();
+#endif
+
+ ssh_crypto_initialized = 1;
+ }
+
+ return 0;
+}
+
+void ssh_crypto_finalize(void) {
+ if (ssh_crypto_initialized) {
+ bignum_free(g);
+ g = NULL;
+ bignum_free(p);
+ p = NULL;
+ ssh_crypto_initialized=0;
+
+ }
+}
+
+/* prints the bignum on stderr */
+void ssh_print_bignum(const char *which, bignum num) {
+#ifdef HAVE_LIBGCRYPT
+ unsigned char *hex = NULL;
+ bignum_bn2hex(num, &hex);
+#elif defined HAVE_LIBCRYPTO
+ char *hex = NULL;
+ hex = bignum_bn2hex(num);
+#endif
+ fprintf(stderr, "%s value: ", which);
+ fprintf(stderr, "%s\n", (hex == NULL) ? "(null)" : (char *) hex);
+ SAFE_FREE(hex);
+}
+
+/**
+ * @brief Convert a buffer into a colon separated hex string.
+ * The caller has to free the memory.
+ *
+ * @param what What should be converted to a hex string.
+ *
+ * @param len Length of the buffer to convert.
+ *
+ * @return The hex string or NULL on error.
+ */
+char *ssh_get_hexa(const unsigned char *what, size_t len) {
+ char *hexa = NULL;
+ size_t i;
+
+ hexa = malloc(len * 3 + 1);
+ if (hexa == NULL) {
+ return NULL;
+ }
+
+ ZERO_STRUCTP(hexa);
+
+ for (i = 0; i < len; i++) {
+ char hex[4];
+ snprintf(hex, sizeof(hex), "%02x:", what[i]);
+ strcat(hexa, hex);
+ }
+
+ hexa[(len * 3) - 1] = '\0';
+
+ return hexa;
+}
+
+/**
+ * @brief Print a buffer as colon separated hex string.
+ *
+ * @param descr Description printed in front of the hex string.
+ *
+ * @param what What should be converted to a hex string.
+ *
+ * @param len Length of the buffer to convert.
+ */
+void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) {
+ char *hexa = ssh_get_hexa(what, len);
+
+ if (hexa == NULL) {
+ return;
+ }
+ printf("%s: %s\n", descr, hexa);
+}
+
+int dh_generate_x(ssh_session session) {
+ session->next_crypto->x = bignum_new();
+ if (session->next_crypto->x == NULL) {
+ return -1;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_rand(session->next_crypto->x, 128);
+#elif defined HAVE_LIBCRYPTO
+ bignum_rand(session->next_crypto->x, 128, 0, -1);
+#endif
+
+ /* not harder than this */
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("x", session->next_crypto->x);
+#endif
+
+ return 0;
+}
+
+/* used by server */
+int dh_generate_y(ssh_session session) {
+ session->next_crypto->y = bignum_new();
+ if (session->next_crypto->y == NULL) {
+ return -1;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_rand(session->next_crypto->y, 128);
+#elif defined HAVE_LIBCRYPTO
+ bignum_rand(session->next_crypto->y, 128, 0, -1);
+#endif
+
+ /* not harder than this */
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("y", session->next_crypto->y);
+#endif
+
+ return 0;
+}
+
+/* used by server */
+int dh_generate_e(ssh_session session) {
+#ifdef HAVE_LIBCRYPTO
+ bignum_CTX ctx = bignum_ctx_new();
+ if (ctx == NULL) {
+ return -1;
+ }
+#endif
+
+ session->next_crypto->e = bignum_new();
+ if (session->next_crypto->e == NULL) {
+#ifdef HAVE_LIBCRYPTO
+ bignum_ctx_free(ctx);
+#endif
+ return -1;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_mod_exp(session->next_crypto->e, g, session->next_crypto->x, p);
+#elif defined HAVE_LIBCRYPTO
+ bignum_mod_exp(session->next_crypto->e, g, session->next_crypto->x, p, ctx);
+#endif
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("e", session->next_crypto->e);
+#endif
+
+#ifdef HAVE_LIBCRYPTO
+ bignum_ctx_free(ctx);
+#endif
+
+ return 0;
+}
+
+int dh_generate_f(ssh_session session) {
+#ifdef HAVE_LIBCRYPTO
+ bignum_CTX ctx = bignum_ctx_new();
+ if (ctx == NULL) {
+ return -1;
+ }
+#endif
+
+ session->next_crypto->f = bignum_new();
+ if (session->next_crypto->f == NULL) {
+#ifdef HAVE_LIBCRYPTO
+ bignum_ctx_free(ctx);
+#endif
+ return -1;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_mod_exp(session->next_crypto->f, g, session->next_crypto->y, p);
+#elif defined HAVE_LIBCRYPTO
+ bignum_mod_exp(session->next_crypto->f, g, session->next_crypto->y, p, ctx);
+#endif
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("f", session->next_crypto->f);
+#endif
+
+#ifdef HAVE_LIBCRYPTO
+ bignum_ctx_free(ctx);
+#endif
+
+ return 0;
+}
+
+ssh_string make_bignum_string(bignum num) {
+ ssh_string ptr = NULL;
+ int pad = 0;
+ unsigned int len = bignum_num_bytes(num);
+ unsigned int bits = bignum_num_bits(num);
+
+ /* Remember if the fist bit is set, it is considered as a
+ * negative number. So 0's must be appended */
+ if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) {
+ pad++;
+ }
+
+#ifdef DEBUG_CRYPTO
+ fprintf(stderr, "%d bits, %d bytes, %d padding\n", bits, len, pad);
+#endif /* DEBUG_CRYPTO */
+/* TODO: fix that crap !! */
+ ptr = malloc(4 + len + pad);
+ if (ptr == NULL) {
+ return NULL;
+ }
+ ptr->size = htonl(len + pad);
+ if (pad) {
+ ptr->string[0] = 0;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_bn2bin(num, len, ptr->string + pad);
+#elif HAVE_LIBCRYPTO
+ bignum_bn2bin(num, ptr->string + pad);
+#endif
+
+ return ptr;
+}
+
+bignum make_string_bn(ssh_string string){
+ bignum bn = NULL;
+ unsigned int len = ssh_string_len(string);
+
+#ifdef DEBUG_CRYPTO
+ fprintf(stderr, "Importing a %d bits, %d bytes object ...\n",
+ len * 8, len);
+#endif /* DEBUG_CRYPTO */
+
+#ifdef HAVE_LIBGCRYPT
+ bignum_bin2bn(string->string, len, &bn);
+#elif defined HAVE_LIBCRYPTO
+ bn = bignum_bin2bn(string->string, len, NULL);
+#endif
+
+ return bn;
+}
+
+ssh_string dh_get_e(ssh_session session) {
+ return make_bignum_string(session->next_crypto->e);
+}
+
+/* used by server */
+ssh_string dh_get_f(ssh_session session) {
+ return make_bignum_string(session->next_crypto->f);
+}
+
+void dh_import_pubkey(ssh_session session, ssh_string pubkey_string) {
+ session->next_crypto->server_pubkey = pubkey_string;
+}
+
+int dh_import_f(ssh_session session, ssh_string f_string) {
+ session->next_crypto->f = make_string_bn(f_string);
+ if (session->next_crypto->f == NULL) {
+ return -1;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("f",session->next_crypto->f);
+#endif
+
+ return 0;
+}
+
+/* used by the server implementation */
+int dh_import_e(ssh_session session, ssh_string e_string) {
+ session->next_crypto->e = make_string_bn(e_string);
+ if (session->next_crypto->e == NULL) {
+ return -1;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("e",session->next_crypto->e);
+#endif
+
+ return 0;
+}
+
+int dh_build_k(ssh_session session) {
+#ifdef HAVE_LIBCRYPTO
+ bignum_CTX ctx = bignum_ctx_new();
+ if (ctx == NULL) {
+ return -1;
+ }
+#endif
+
+ session->next_crypto->k = bignum_new();
+ if (session->next_crypto->k == NULL) {
+#ifdef HAVE_LIBCRYPTO
+ bignum_ctx_free(ctx);
+#endif
+ return -1;
+ }
+
+ /* the server and clients don't use the same numbers */
+#ifdef HAVE_LIBGCRYPT
+ if(session->client) {
+ bignum_mod_exp(session->next_crypto->k, session->next_crypto->f,
+ session->next_crypto->x, p);
+ } else {
+ bignum_mod_exp(session->next_crypto->k, session->next_crypto->e,
+ session->next_crypto->y, p);
+ }
+#elif defined HAVE_LIBCRYPTO
+ if (session->client) {
+ bignum_mod_exp(session->next_crypto->k, session->next_crypto->f,
+ session->next_crypto->x, p, ctx);
+ } else {
+ bignum_mod_exp(session->next_crypto->k, session->next_crypto->e,
+ session->next_crypto->y, p, ctx);
+ }
+#endif
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Session server cookie", session->server_kex.cookie, 16);
+ ssh_print_hexa("Session client cookie", session->client_kex.cookie, 16);
+ ssh_print_bignum("Shared secret key", session->next_crypto->k);
+#endif
+
+#ifdef HAVE_LIBCRYPTO
+ bignum_ctx_free(ctx);
+#endif
+
+ return 0;
+}
+
+/*
+static void sha_add(ssh_string str,SHACTX ctx){
+ sha1_update(ctx,str,string_len(str)+4);
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("partial hashed sessionid",str,string_len(str)+4);
+#endif
+}
+*/
+
+int make_sessionid(ssh_session session) {
+ SHACTX ctx;
+ ssh_string num = NULL;
+ ssh_string str = NULL;
+ ssh_buffer server_hash = NULL;
+ ssh_buffer client_hash = NULL;
+ ssh_buffer buf = NULL;
+ uint32_t len;
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+ ctx = sha1_init();
+ if (ctx == NULL) {
+ return rc;
+ }
+
+ buf = ssh_buffer_new();
+ if (buf == NULL) {
+ return rc;
+ }
+
+ str = ssh_string_from_char(session->clientbanner);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buf, str) < 0) {
+ goto error;
+ }
+ ssh_string_free(str);
+
+ str = ssh_string_from_char(session->serverbanner);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buf, str) < 0) {
+ goto error;
+ }
+
+ if (session->client) {
+ server_hash = session->in_hashbuf;
+ client_hash = session->out_hashbuf;
+ } else {
+ server_hash = session->out_hashbuf;
+ client_hash = session->in_hashbuf;
+ }
+
+ if (buffer_add_u32(server_hash, 0) < 0) {
+ goto error;
+ }
+ if (buffer_add_u8(server_hash, 0) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(client_hash, 0) < 0) {
+ goto error;
+ }
+ if (buffer_add_u8(client_hash, 0) < 0) {
+ goto error;
+ }
+
+ len = ntohl(ssh_buffer_get_len(client_hash));
+ if (buffer_add_u32(buf,len) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(buf, ssh_buffer_get_begin(client_hash),
+ ssh_buffer_get_len(client_hash)) < 0) {
+ goto error;
+ }
+
+ len = ntohl(ssh_buffer_get_len(server_hash));
+ if (buffer_add_u32(buf, len) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(buf, ssh_buffer_get_begin(server_hash),
+ ssh_buffer_get_len(server_hash)) < 0) {
+ goto error;
+ }
+
+ len = ssh_string_len(session->next_crypto->server_pubkey) + 4;
+ if (buffer_add_data(buf, session->next_crypto->server_pubkey, len) < 0) {
+ goto error;
+ }
+
+ num = make_bignum_string(session->next_crypto->e);
+ if (num == NULL) {
+ goto error;
+ }
+
+ len = ssh_string_len(num) + 4;
+ if (buffer_add_data(buf, num, len) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(num);
+ num = make_bignum_string(session->next_crypto->f);
+ if (num == NULL) {
+ goto error;
+ }
+
+ len = ssh_string_len(num) + 4;
+ if (buffer_add_data(buf, num, len) < 0) {
+ goto error;
+ }
+
+ ssh_string_free(num);
+ num = make_bignum_string(session->next_crypto->k);
+ if (num == NULL) {
+ goto error;
+ }
+
+ len = ssh_string_len(num) + 4;
+ if (buffer_add_data(buf, num, len) < 0) {
+ goto error;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("hash buffer", ssh_buffer_get_begin(buf), ssh_buffer_get_len(buf));
+#endif
+
+ sha1_update(ctx, ssh_buffer_get_begin(buf), ssh_buffer_get_len(buf));
+ sha1_final(session->next_crypto->session_id, ctx);
+
+#ifdef DEBUG_CRYPTO
+ printf("Session hash: ");
+ ssh_print_hexa("session id", session->next_crypto->session_id, SHA_DIGEST_LEN);
+#endif
+
+ rc = SSH_OK;
+error:
+ ssh_buffer_free(buf);
+ ssh_buffer_free(client_hash);
+ ssh_buffer_free(server_hash);
+
+ session->in_hashbuf = NULL;
+ session->out_hashbuf = NULL;
+
+ ssh_string_free(str);
+ ssh_string_free(num);
+
+ leave_function();
+
+ return rc;
+}
+
+int hashbufout_add_cookie(ssh_session session) {
+ session->out_hashbuf = ssh_buffer_new();
+ if (session->out_hashbuf == NULL) {
+ return -1;
+ }
+
+ if (buffer_add_u8(session->out_hashbuf, 20) < 0) {
+ buffer_reinit(session->out_hashbuf);
+ return -1;
+ }
+
+ if (session->server) {
+ if (buffer_add_data(session->out_hashbuf,
+ session->server_kex.cookie, 16) < 0) {
+ buffer_reinit(session->out_hashbuf);
+ return -1;
+ }
+ } else {
+ if (buffer_add_data(session->out_hashbuf,
+ session->client_kex.cookie, 16) < 0) {
+ buffer_reinit(session->out_hashbuf);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int hashbufin_add_cookie(ssh_session session, unsigned char *cookie) {
+ session->in_hashbuf = ssh_buffer_new();
+ if (session->in_hashbuf == NULL) {
+ return -1;
+ }
+
+ if (buffer_add_u8(session->in_hashbuf, 20) < 0) {
+ buffer_reinit(session->in_hashbuf);
+ return -1;
+ }
+ if (buffer_add_data(session->in_hashbuf,cookie, 16) < 0) {
+ buffer_reinit(session->in_hashbuf);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int generate_one_key(ssh_string k,
+ unsigned char session_id[SHA_DIGEST_LEN],
+ unsigned char output[SHA_DIGEST_LEN],
+ char letter) {
+ SHACTX ctx = NULL;
+
+ ctx = sha1_init();
+ if (ctx == NULL) {
+ return -1;
+ }
+
+ sha1_update(ctx, k, ssh_string_len(k) + 4);
+ sha1_update(ctx, session_id, SHA_DIGEST_LEN);
+ sha1_update(ctx, &letter, 1);
+ sha1_update(ctx, session_id, SHA_DIGEST_LEN);
+ sha1_final(output, ctx);
+
+ return 0;
+}
+
+int generate_session_keys(ssh_session session) {
+ ssh_string k_string = NULL;
+ SHACTX ctx = NULL;
+ int rc = -1;
+
+ enter_function();
+
+ k_string = make_bignum_string(session->next_crypto->k);
+ if (k_string == NULL) {
+ goto error;
+ }
+
+ /* IV */
+ if (session->client) {
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->encryptIV, 'A') < 0) {
+ goto error;
+ }
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->decryptIV, 'B') < 0) {
+ goto error;
+ }
+ } else {
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->decryptIV, 'A') < 0) {
+ goto error;
+ }
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->encryptIV, 'B') < 0) {
+ goto error;
+ }
+ }
+ if (session->client) {
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->encryptkey, 'C') < 0) {
+ goto error;
+ }
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->decryptkey, 'D') < 0) {
+ goto error;
+ }
+ } else {
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->decryptkey, 'C') < 0) {
+ goto error;
+ }
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->encryptkey, 'D') < 0) {
+ goto error;
+ }
+ }
+
+ /* some ciphers need more than 20 bytes of input key */
+ /* XXX verify it's ok for server implementation */
+ if (session->next_crypto->out_cipher->keysize > SHA_DIGEST_LEN * 8) {
+ ctx = sha1_init();
+ if (ctx == NULL) {
+ goto error;
+ }
+ sha1_update(ctx, k_string, ssh_string_len(k_string) + 4);
+ sha1_update(ctx, session->next_crypto->session_id, SHA_DIGEST_LEN);
+ sha1_update(ctx, session->next_crypto->encryptkey, SHA_DIGEST_LEN);
+ sha1_final(session->next_crypto->encryptkey + SHA_DIGEST_LEN, ctx);
+ }
+
+ if (session->next_crypto->in_cipher->keysize > SHA_DIGEST_LEN * 8) {
+ ctx = sha1_init();
+ sha1_update(ctx, k_string, ssh_string_len(k_string) + 4);
+ sha1_update(ctx, session->next_crypto->session_id, SHA_DIGEST_LEN);
+ sha1_update(ctx, session->next_crypto->decryptkey, SHA_DIGEST_LEN);
+ sha1_final(session->next_crypto->decryptkey + SHA_DIGEST_LEN, ctx);
+ }
+ if(session->client) {
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->encryptMAC, 'E') < 0) {
+ goto error;
+ }
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->decryptMAC, 'F') < 0) {
+ goto error;
+ }
+ } else {
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->decryptMAC, 'E') < 0) {
+ goto error;
+ }
+ if (generate_one_key(k_string, session->next_crypto->session_id,
+ session->next_crypto->encryptMAC, 'F') < 0) {
+ goto error;
+ }
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Encrypt IV", session->next_crypto->encryptIV, SHA_DIGEST_LEN);
+ ssh_print_hexa("Decrypt IV", session->next_crypto->decryptIV, SHA_DIGEST_LEN);
+ ssh_print_hexa("Encryption key", session->next_crypto->encryptkey,
+ session->next_crypto->out_cipher->keysize);
+ ssh_print_hexa("Decryption key", session->next_crypto->decryptkey,
+ session->next_crypto->in_cipher->keysize);
+ ssh_print_hexa("Encryption MAC", session->next_crypto->encryptMAC, SHA_DIGEST_LEN);
+ ssh_print_hexa("Decryption MAC", session->next_crypto->decryptMAC, 20);
+#endif
+
+ rc = 0;
+error:
+ ssh_string_free(k_string);
+ leave_function();
+
+ return rc;
+}
+
+/**
+ * @addtogroup libssh_session
+ *
+ * @{
+ */
+
+/**
+ * @brief Allocates a buffer with the MD5 hash of the server public key.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @param[in] hash The buffer to allocate.
+ *
+ * @return The bytes allocated or < 0 on error.
+ *
+ * @warning It is very important that you verify at some moment that the hash
+ * matches a known server. If you don't do it, cryptography wont help
+ * you at making things secure
+ *
+ * @see ssh_is_server_known()
+ * @see ssh_get_hexa()
+ * @see ssh_print_hexa()
+ */
+int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) {
+ ssh_string pubkey;
+ MD5CTX ctx;
+ unsigned char *h;
+
+ if (session == NULL || hash == NULL) {
+ return -1;
+ }
+ *hash = NULL;
+ if (session->current_crypto == NULL ||
+ session->current_crypto->server_pubkey == NULL){
+ ssh_set_error(session,SSH_FATAL,"No current cryptographic context");
+ }
+
+ h = malloc(sizeof(unsigned char *) * MD5_DIGEST_LEN);
+ if (h == NULL) {
+ return -1;
+ }
+
+ ctx = md5_init();
+ if (ctx == NULL) {
+ SAFE_FREE(h);
+ return -1;
+ }
+
+ pubkey = session->current_crypto->server_pubkey;
+
+ md5_update(ctx, pubkey->string, ssh_string_len(pubkey));
+ md5_final(h, ctx);
+
+ *hash = h;
+
+ return MD5_DIGEST_LEN;
+}
+
+/**
+ * @brief Deallocate the hash obtained by ssh_get_pubkey_hash.
+ *
+ * This is required under Microsoft platform as this library might use a
+ * different C library than your software, hence a different heap.
+ *
+ * @param[in] hash The buffer to deallocate.
+ *
+ * @see ssh_get_pubkey_hash()
+ */
+void ssh_clean_pubkey_hash(unsigned char **hash) {
+ SAFE_FREE(*hash);
+ *hash = NULL;
+}
+
+ssh_string ssh_get_pubkey(ssh_session session){
+ return ssh_string_copy(session->current_crypto->server_pubkey);
+}
+
+static int match(const char *group, const char *object){
+ const char *a;
+ const char *z;
+
+ z = group;
+ do {
+ a = strchr(z, ',');
+ if (a == NULL) {
+ if (strcmp(z, object) == 0) {
+ return 1;
+ }
+ return 0;
+ } else {
+ if (strncmp(z, object, a - z) == 0) {
+ return 1;
+ }
+ }
+ z = a + 1;
+ } while(1);
+
+ /* not reached */
+ return 0;
+}
+
+int sig_verify(ssh_session session, ssh_public_key pubkey,
+ SIGNATURE *signature, unsigned char *digest, int size) {
+#ifdef HAVE_LIBGCRYPT
+ gcry_error_t valid = 0;
+ gcry_sexp_t gcryhash;
+#elif defined HAVE_LIBCRYPTO
+ int valid = 0;
+#endif
+ unsigned char hash[SHA_DIGEST_LEN + 1] = {0};
+
+ sha1(digest, size, hash + 1);
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Hash to be verified with dsa", hash + 1, SHA_DIGEST_LEN);
+#endif
+
+ switch(pubkey->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ valid = gcry_sexp_build(&gcryhash, NULL, "%b", SHA_DIGEST_LEN + 1, hash);
+ if (valid != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "RSA error: %s", gcry_strerror(valid));
+ return -1;
+ }
+ valid = gcry_pk_verify(signature->dsa_sign, gcryhash, pubkey->dsa_pub);
+ gcry_sexp_release(gcryhash);
+ if (valid == 0) {
+ return 0;
+ }
+
+ if (gcry_err_code(valid) != GPG_ERR_BAD_SIGNATURE) {
+ ssh_set_error(session, SSH_FATAL,
+ "DSA error: %s", gcry_strerror(valid));
+ return -1;
+ }
+#elif defined HAVE_LIBCRYPTO
+ valid = DSA_do_verify(hash + 1, SHA_DIGEST_LEN, signature->dsa_sign,
+ pubkey->dsa_pub);
+ if (valid == 1) {
+ return 0;
+ }
+
+ if (valid == -1) {
+ ssh_set_error(session, SSH_FATAL,
+ "DSA error: %s", ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+#endif
+ ssh_set_error(session, SSH_FATAL, "Invalid DSA signature");
+ return -1;
+
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+#ifdef HAVE_LIBGCRYPT
+ valid = gcry_sexp_build(&gcryhash, NULL,
+ "(data(flags pkcs1)(hash sha1 %b))", SHA_DIGEST_LEN, hash + 1);
+ if (valid != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "RSA error: %s", gcry_strerror(valid));
+ return -1;
+ }
+ valid = gcry_pk_verify(signature->rsa_sign,gcryhash,pubkey->rsa_pub);
+ gcry_sexp_release(gcryhash);
+ if (valid == 0) {
+ return 0;
+ }
+ if (gcry_err_code(valid) != GPG_ERR_BAD_SIGNATURE) {
+ ssh_set_error(session, SSH_FATAL,
+ "RSA error: %s", gcry_strerror(valid));
+ return -1;
+ }
+#elif defined HAVE_LIBCRYPTO
+ valid = RSA_verify(NID_sha1, hash + 1, SHA_DIGEST_LEN,
+ signature->rsa_sign->string, ssh_string_len(signature->rsa_sign),
+ pubkey->rsa_pub);
+ if (valid == 1) {
+ return 0;
+ }
+ if (valid == -1) {
+ ssh_set_error(session, SSH_FATAL,
+ "RSA error: %s", ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+#endif
+ ssh_set_error(session, SSH_FATAL, "Invalid RSA signature");
+ return -1;
+ default:
+ ssh_set_error(session, SSH_FATAL, "Unknown public key type");
+ return -1;
+ }
+
+ return -1;
+}
+
+int signature_verify(ssh_session session, ssh_string signature) {
+ ssh_public_key pubkey = NULL;
+ SIGNATURE *sign = NULL;
+ int err;
+
+ enter_function();
+
+ pubkey = publickey_from_string(session,session->next_crypto->server_pubkey);
+ if(pubkey == NULL) {
+ leave_function();
+ return -1;
+ }
+
+ if (session->wanted_methods[SSH_HOSTKEYS]) {
+ if(!match(session->wanted_methods[SSH_HOSTKEYS],pubkey->type_c)) {
+ ssh_set_error(session, SSH_FATAL,
+ "Public key from server (%s) doesn't match user preference (%s)",
+ pubkey->type_c, session->wanted_methods[SSH_HOSTKEYS]);
+ publickey_free(pubkey);
+ leave_function();
+ return -1;
+ }
+ }
+
+ sign = signature_from_string(session, signature, pubkey, pubkey->type);
+ if (sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Invalid signature blob");
+ publickey_free(pubkey);
+ leave_function();
+ return -1;
+ }
+
+ ssh_log(session, SSH_LOG_FUNCTIONS,
+ "Going to verify a %s type signature", pubkey->type_c);
+
+ err = sig_verify(session,pubkey,sign,
+ session->next_crypto->session_id,SHA_DIGEST_LEN);
+ signature_free(sign);
+ session->next_crypto->server_pubkey_type = pubkey->type_c;
+ publickey_free(pubkey);
+
+ leave_function();
+ return err;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/error.c b/src/error.c
new file mode 100644
index 00000000..f5bb1190
--- /dev/null
+++ b/src/error.c
@@ -0,0 +1,123 @@
+/*
+ * error.c - functions for ssh error handling
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 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 <stdio.h>
+#include <stdarg.h>
+#include "libssh/priv.h"
+
+/**
+ * @defgroup libssh_error The SSH error functions.
+ * @ingroup libssh
+ *
+ * Functions for error handling.
+ *
+ * @{
+ */
+
+/**
+ * @internal
+ *
+ * @brief Registers an error with a description.
+ *
+ * @param error The place to store the error.
+ *
+ * @param code The class of error.
+ *
+ * @param descr The description, which can be a format string.
+ *
+ * @param ... The arguments for the format string.
+ */
+void ssh_set_error(void *error, int code, const char *descr, ...) {
+ struct error_struct *err = error;
+ va_list va;
+ va_start(va, descr);
+ vsnprintf(err->error_buffer, ERROR_BUFFERLEN, descr, va);
+ va_end(va);
+ err->error_code = code;
+}
+
+/**
+ * @internal
+ *
+ * @brief Registers an out of memory error
+ *
+ * @param error The place to store the error.
+ *
+ */
+void ssh_set_error_oom(void *error) {
+ struct error_struct *err = error;
+
+ strcpy(err->error_buffer, "Out of memory");
+ err->error_code = SSH_FATAL;
+}
+
+/**
+ * @internal
+ *
+ * @brief Registers an invalid argument error
+ *
+ * @param error The place to store the error.
+ *
+ * @param function The function the error happened in.
+ *
+ */
+void ssh_set_error_invalid(void *error, const char *function) {
+ ssh_set_error(error, SSH_FATAL, "Invalid argument in %s", function);
+}
+
+/**
+ * @brief Retrieve the error text message from the last error.
+ *
+ * @param error The SSH session pointer.
+ *
+ * @return A static string describing the error.
+ */
+const char *ssh_get_error(void *error) {
+ struct error_struct *err = error;
+
+ return err->error_buffer;
+}
+
+/**
+ * @brief Retrieve the error code from the last error.
+ *
+ * @param error The SSH session pointer.
+ *
+ * \return SSH_NO_ERROR No error occurred\n
+ * SSH_REQUEST_DENIED The last request was denied but situation is
+ * recoverable\n
+ * SSH_FATAL A fatal error occurred. This could be an unexpected
+ * disconnection\n
+ *
+ * Other error codes are internal but can be considered same than
+ * SSH_FATAL.
+ */
+int ssh_get_error_code(void *error) {
+ struct error_struct *err = error;
+
+ return err->error_code;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/gcrypt_missing.c b/src/gcrypt_missing.c
new file mode 100644
index 00000000..7a456a6a
--- /dev/null
+++ b/src/gcrypt_missing.c
@@ -0,0 +1,99 @@
+/*
+ * gcrypt_missing.c - routines that are in OpenSSL but not in libgcrypt.
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2006 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 <stdlib.h>
+#include "libssh/priv.h"
+
+#ifdef HAVE_LIBGCRYPT
+int my_gcry_dec2bn(bignum *bn, const char *data) {
+ int count;
+
+ *bn = bignum_new();
+ if (*bn == NULL) {
+ return 0;
+ }
+ gcry_mpi_set_ui(*bn, 0);
+ for (count = 0; data[count]; count++) {
+ gcry_mpi_mul_ui(*bn, *bn, 10);
+ gcry_mpi_add_ui(*bn, *bn, data[count] - '0');
+ }
+
+ return count;
+}
+
+char *my_gcry_bn2dec(bignum bn) {
+ bignum bndup, num, ten;
+ char *ret;
+ int count, count2;
+ int size, rsize;
+ char decnum;
+
+ size = gcry_mpi_get_nbits(bn) * 3;
+ rsize = size / 10 + size / 1000 + 2;
+
+ ret = malloc(rsize + 1);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (!gcry_mpi_cmp_ui(bn, 0)) {
+ strcpy(ret, "0");
+ } else {
+ ten = bignum_new();
+ if (ten == NULL) {
+ SAFE_FREE(ret);
+ return NULL;
+ }
+
+ num = bignum_new();
+ if (num == NULL) {
+ SAFE_FREE(ret);
+ bignum_free(ten);
+ return NULL;
+ }
+
+ for (bndup = gcry_mpi_copy(bn), bignum_set_word(ten, 10), count = rsize;
+ count; count--) {
+ gcry_mpi_div(bndup, num, bndup, ten, 0);
+ for (decnum = 0, count2 = gcry_mpi_get_nbits(num); count2;
+ decnum *= 2, decnum += (gcry_mpi_test_bit(num, count2 - 1) ? 1 : 0),
+ count2--)
+ ;
+ ret[count - 1] = decnum + '0';
+ }
+ for (count = 0; count < rsize && ret[count] == '0'; count++)
+ ;
+ for (count2 = 0; count2 < rsize - count; ++count2) {
+ ret[count2] = ret[count2 + count];
+ }
+ ret[count2] = 0;
+ bignum_free(num);
+ bignum_free(bndup);
+ bignum_free(ten);
+ }
+
+ return ret;
+}
+
+#endif
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/gzip.c b/src/gzip.c
new file mode 100644
index 00000000..1e892dc7
--- /dev/null
+++ b/src/gzip.c
@@ -0,0 +1,222 @@
+/*
+ * gzip.c - hooks for compression of packets
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003 by Aris Adamantiadis
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 "libssh/priv.h"
+#include "libssh/buffer.h"
+#include "libssh/session.h"
+
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+
+#include <zlib.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define BLOCKSIZE 4092
+
+static z_stream *initcompress(ssh_session session, int level) {
+ z_stream *stream = NULL;
+ int status;
+
+ stream = malloc(sizeof(z_stream));
+ if (stream == NULL) {
+ return NULL;
+ }
+ memset(stream, 0, sizeof(z_stream));
+
+ status = deflateInit(stream, level);
+ if (status != Z_OK) {
+ SAFE_FREE(stream);
+ ssh_set_error(session, SSH_FATAL,
+ "status %d inititalising zlib deflate", status);
+ return NULL;
+ }
+
+ return stream;
+}
+
+static ssh_buffer gzip_compress(ssh_session session,ssh_buffer source,int level){
+ z_stream *zout = session->current_crypto->compress_out_ctx;
+ void *in_ptr = ssh_buffer_get_begin(source);
+ unsigned long in_size = ssh_buffer_get_len(source);
+ ssh_buffer dest = NULL;
+ unsigned char out_buf[BLOCKSIZE] = {0};
+ unsigned long len;
+ int status;
+
+ if(zout == NULL) {
+ zout = session->current_crypto->compress_out_ctx = initcompress(session, level);
+ if (zout == NULL) {
+ return NULL;
+ }
+ }
+
+ dest = ssh_buffer_new();
+ if (dest == NULL) {
+ return NULL;
+ }
+
+ zout->next_out = out_buf;
+ zout->next_in = in_ptr;
+ zout->avail_in = in_size;
+ do {
+ zout->avail_out = BLOCKSIZE;
+ status = deflate(zout, Z_PARTIAL_FLUSH);
+ if (status != Z_OK) {
+ ssh_buffer_free(dest);
+ ssh_set_error(session, SSH_FATAL,
+ "status %d deflating zlib packet", status);
+ return NULL;
+ }
+ len = BLOCKSIZE - zout->avail_out;
+ if (buffer_add_data(dest, out_buf, len) < 0) {
+ ssh_buffer_free(dest);
+ return NULL;
+ }
+ zout->next_out = out_buf;
+ } while (zout->avail_out == 0);
+
+ return dest;
+}
+
+int compress_buffer(ssh_session session, ssh_buffer buf) {
+ ssh_buffer dest = NULL;
+
+ dest = gzip_compress(session, buf, 9);
+ if (dest == NULL) {
+ return -1;
+ }
+
+ if (buffer_reinit(buf) < 0) {
+ ssh_buffer_free(dest);
+ return -1;
+ }
+
+ if (buffer_add_data(buf, ssh_buffer_get_begin(dest), ssh_buffer_get_len(dest)) < 0) {
+ ssh_buffer_free(dest);
+ return -1;
+ }
+
+ ssh_buffer_free(dest);
+ return 0;
+}
+
+/* decompression */
+
+static z_stream *initdecompress(ssh_session session) {
+ z_stream *stream = NULL;
+ int status;
+
+ stream = malloc(sizeof(z_stream));
+ if (stream == NULL) {
+ return NULL;
+ }
+ memset(stream,0,sizeof(z_stream));
+
+ status = inflateInit(stream);
+ if (status != Z_OK) {
+ SAFE_FREE(stream);
+ ssh_set_error(session, SSH_FATAL,
+ "Status = %d initiating inflate context!", status);
+ return NULL;
+ }
+
+ return stream;
+}
+
+static ssh_buffer gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) {
+ z_stream *zin = session->current_crypto->compress_in_ctx;
+ void *in_ptr = buffer_get_rest(source);
+ unsigned long in_size = buffer_get_rest_len(source);
+ unsigned char out_buf[BLOCKSIZE] = {0};
+ ssh_buffer dest = NULL;
+ unsigned long len;
+ int status;
+
+ if (zin == NULL) {
+ zin = session->current_crypto->compress_in_ctx = initdecompress(session);
+ if (zin == NULL) {
+ return NULL;
+ }
+ }
+
+ dest = ssh_buffer_new();
+ if (dest == NULL) {
+ return NULL;
+ }
+
+ zin->next_out = out_buf;
+ zin->next_in = in_ptr;
+ zin->avail_in = in_size;
+
+ do {
+ zin->avail_out = BLOCKSIZE;
+ status = inflate(zin, Z_PARTIAL_FLUSH);
+ if (status != Z_OK) {
+ ssh_set_error(session, SSH_FATAL,
+ "status %d inflating zlib packet", status);
+ ssh_buffer_free(dest);
+ return NULL;
+ }
+
+ len = BLOCKSIZE - zin->avail_out;
+ if (buffer_add_data(dest,out_buf,len) < 0) {
+ ssh_buffer_free(dest);
+ return NULL;
+ }
+ if (ssh_buffer_get_len(dest) > maxlen){
+ /* Size of packet exceded, avoid a denial of service attack */
+ ssh_buffer_free(dest);
+ return NULL;
+ }
+ zin->next_out = out_buf;
+ } while (zin->avail_out == 0);
+
+ return dest;
+}
+
+int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen){
+ ssh_buffer dest = NULL;
+
+ dest = gzip_decompress(session,buf, maxlen);
+ if (dest == NULL) {
+ return -1;
+ }
+
+ if (buffer_reinit(buf) < 0) {
+ ssh_buffer_free(dest);
+ return -1;
+ }
+
+ if (buffer_add_data(buf, ssh_buffer_get_begin(dest), ssh_buffer_get_len(dest)) < 0) {
+ ssh_buffer_free(dest);
+ return -1;
+ }
+
+ ssh_buffer_free(dest);
+ return 0;
+}
+
+#endif /* HAVE_LIBZ && WITH_LIBZ */
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 00000000..5952e272
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,94 @@
+/*
+ * init.c - initialization and finalization of the library
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2009 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 "libssh/priv.h"
+#include "libssh/socket.h"
+#include "libssh/dh.h"
+#include "libssh/poll.h"
+#include "libssh/threads.h"
+
+#ifdef _WIN32
+#include <winsock2.h>
+#endif
+
+/**
+ * @defgroup libssh The libssh API
+ *
+ * The libssh library is implementing the SSH protocols and some of its
+ * extensions. This group of functions is mostly used to implment a SSH client.
+ * Some function are needed to implement a SSH server too.
+ *
+ * @{
+ */
+
+/**
+ * @brief Initialize global cryptographic data structures.
+ *
+ * This function should only be called once, at the beginning of the program, in
+ * the main thread. It may be omitted if your program is not multithreaded.
+ *
+ * @returns 0 on success, -1 if an error occured.
+ */
+int ssh_init(void) {
+ if(ssh_threads_init())
+ return -1;
+ if(ssh_crypto_init())
+ return -1;
+ if(ssh_socket_init())
+ return -1;
+ if(ssh_regex_init())
+ return -1;
+ return 0;
+}
+
+
+/**
+ * @brief Finalize and cleanup all libssh and cryptographic data structures.
+ *
+ * This function should only be called once, at the end of the program!
+ *
+ * @returns 0 on succes, -1 if an error occured.
+ *
+ @returns 0 otherwise
+ */
+int ssh_finalize(void) {
+ ssh_threads_finalize();
+ ssh_free_global_poll_ctx();
+ ssh_regex_finalize();
+ ssh_crypto_finalize();
+ ssh_socket_cleanup();
+#ifdef HAVE_LIBGCRYPT
+ gcry_control(GCRYCTL_TERM_SECMEM);
+#elif defined HAVE_LIBCRYPTO
+ EVP_cleanup();
+#endif
+#ifdef _WIN32
+ WSACleanup();
+#endif
+ return 0;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/kex.c b/src/kex.c
new file mode 100644
index 00000000..d57273ec
--- /dev/null
+++ b/src/kex.c
@@ -0,0 +1,835 @@
+/*
+ * kex.c - key exchange
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 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 <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/ssh1.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.h"
+#include "libssh/session.h"
+#include "libssh/wrapper.h"
+#include "libssh/keys.h"
+#include "libssh/dh.h"
+#include "libssh/kex.h"
+#include "libssh/string.h"
+
+#ifdef HAVE_LIBGCRYPT
+#define BLOWFISH "blowfish-cbc,"
+#define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,"
+#define DES "3des-cbc"
+#elif defined HAVE_LIBCRYPTO
+#ifdef HAVE_OPENSSL_BLOWFISH_H
+#define BLOWFISH "blowfish-cbc,"
+#else
+#define BLOWFISH ""
+#endif
+#ifdef HAVE_OPENSSL_AES_H
+#ifdef BROKEN_AES_CTR
+#define AES "aes256-cbc,aes192-cbc,aes128-cbc,"
+#else
+#define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,"
+#endif /* BROKEN_AES_CTR */
+#else
+#define AES ""
+#endif
+
+#define DES "3des-cbc"
+#endif
+
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+#define ZLIB "none,zlib,zlib@openssh.org"
+#else
+#define ZLIB "none"
+#endif
+
+const char *default_methods[] = {
+ "diffie-hellman-group1-sha1",
+ "ssh-rsa,ssh-dss",
+ AES BLOWFISH DES,
+ AES BLOWFISH DES,
+ "hmac-sha1",
+ "hmac-sha1",
+ "none",
+ "none",
+ "",
+ "",
+ NULL
+};
+
+const char *supported_methods[] = {
+ "diffie-hellman-group1-sha1",
+ "ssh-rsa,ssh-dss",
+ AES BLOWFISH DES,
+ AES BLOWFISH DES,
+ "hmac-sha1",
+ "hmac-sha1",
+ ZLIB,
+ ZLIB,
+ "",
+ "",
+ NULL
+};
+
+/* descriptions of the key exchange packet */
+const char *ssh_kex_nums[] = {
+ "kex algos",
+ "server host key algo",
+ "encryption client->server",
+ "encryption server->client",
+ "mac algo client->server",
+ "mac algo server->client",
+ "compression algo client->server",
+ "compression algo server->client",
+ "languages client->server",
+ "languages server->client",
+ NULL
+};
+
+/* tokenize will return a token of strings delimited by ",". the first element has to be freed */
+static char **tokenize(const char *chain){
+ char **tokens;
+ int n=1;
+ int i=0;
+ char *tmp;
+ char *ptr;
+
+ tmp = strdup(chain);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ ptr = tmp;
+ while(*ptr){
+ if(*ptr==','){
+ n++;
+ *ptr=0;
+ }
+ ptr++;
+ }
+ /* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */
+ tokens=malloc(sizeof(char *) * (n+1) ); /* +1 for the null */
+ if (tokens == NULL) {
+ SAFE_FREE(tmp);
+ return NULL;
+ }
+ ptr=tmp;
+ for(i=0;i<n;i++){
+ tokens[i]=ptr;
+ while(*ptr)
+ ptr++; // find a zero
+ ptr++; // then go one step further
+ }
+ tokens[i]=NULL;
+ return tokens;
+}
+
+/* same as tokenize(), but with spaces instead of ',' */
+/* TODO FIXME rewrite me! */
+char **space_tokenize(const char *chain){
+ char **tokens;
+ int n=1;
+ int i=0;
+ char *tmp;
+ char *ptr;
+
+ tmp = strdup(chain);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ ptr = tmp;
+
+ while(*ptr==' ')
+ ++ptr; /* skip initial spaces */
+ while(*ptr){
+ if(*ptr==' '){
+ n++; /* count one token per word */
+ *ptr=0;
+ while(*(ptr+1)==' '){ /* don't count if the tokens have more than 2 spaces */
+ *(ptr++)=0;
+ }
+ }
+ ptr++;
+ }
+ /* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */
+ tokens = malloc(sizeof(char *) * (n + 1)); /* +1 for the null */
+ if (tokens == NULL) {
+ SAFE_FREE(tmp);
+ return NULL;
+ }
+ ptr=tmp; /* we don't pass the initial spaces because the "tmp" pointer is needed by the caller */
+ /* function to free the tokens. */
+ for(i=0;i<n;i++){
+ tokens[i]=ptr;
+ if(i!=n-1){
+ while(*ptr)
+ ptr++; // find a zero
+ while(!*(ptr+1))
+ ++ptr; /* if the zero is followed by other zeros, go through them */
+ ptr++; // then go one step further
+ }
+ }
+ tokens[i]=NULL;
+ return tokens;
+}
+
+/* find_matching gets 2 parameters : a list of available objects (available_d), separated by colons,*/
+/* and a list of preferred objects (preferred_d) */
+/* it will return a strduped pointer on the first preferred object found in the available objects list */
+
+char *ssh_find_matching(const char *available_d, const char *preferred_d){
+ char ** tok_available, **tok_preferred;
+ int i_avail, i_pref;
+ char *ret;
+
+ if ((available_d == NULL) || (preferred_d == NULL)) {
+ return NULL; /* don't deal with null args */
+ }
+
+ tok_available = tokenize(available_d);
+ if (tok_available == NULL) {
+ return NULL;
+ }
+
+ tok_preferred = tokenize(preferred_d);
+ if (tok_preferred == NULL) {
+ SAFE_FREE(tok_available[0]);
+ SAFE_FREE(tok_available);
+ return NULL;
+ }
+
+ for(i_pref=0; tok_preferred[i_pref] ; ++i_pref){
+ for(i_avail=0; tok_available[i_avail]; ++i_avail){
+ if(!strcmp(tok_available[i_avail],tok_preferred[i_pref])){
+ /* match */
+ ret=strdup(tok_available[i_avail]);
+ /* free the tokens */
+ SAFE_FREE(tok_available[0]);
+ SAFE_FREE(tok_preferred[0]);
+ SAFE_FREE(tok_available);
+ SAFE_FREE(tok_preferred);
+ return ret;
+ }
+ }
+ }
+ SAFE_FREE(tok_available[0]);
+ SAFE_FREE(tok_preferred[0]);
+ SAFE_FREE(tok_available);
+ SAFE_FREE(tok_preferred);
+ return NULL;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_kexinit){
+ int server_kex=session->server;
+ ssh_string str = NULL;
+ char *strings[10];
+ int i;
+
+ enter_function();
+ (void)type;
+ (void)user;
+ if(session->session_state != SSH_SESSION_STATE_INITIAL_KEX){
+ ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state");
+ goto error;
+ }
+ if (server_kex) {
+ if (buffer_get_data(packet,session->client_kex.cookie,16) != 16) {
+ ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet");
+ goto error;
+ }
+
+ if (hashbufin_add_cookie(session, session->client_kex.cookie) < 0) {
+ ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed");
+ goto error;
+ }
+ } else {
+ if (buffer_get_data(packet,session->server_kex.cookie,16) != 16) {
+ ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet");
+ goto error;
+ }
+
+ if (hashbufin_add_cookie(session, session->server_kex.cookie) < 0) {
+ ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed");
+ goto error;
+ }
+ }
+
+ memset(strings, 0, sizeof(char *) * 10);
+
+ for (i = 0; i < 10; i++) {
+ str = buffer_get_ssh_string(packet);
+ if (str == NULL) {
+ break;
+ }
+
+ if (buffer_add_ssh_string(session->in_hashbuf, str) < 0) {
+ goto error;
+ }
+
+ strings[i] = ssh_string_to_char(str);
+ if (strings[i] == NULL) {
+ goto error;
+ }
+ ssh_string_free(str);
+ str = NULL;
+ }
+
+ /* copy the server kex info into an array of strings */
+ if (server_kex) {
+ session->client_kex.methods = malloc(10 * sizeof(char **));
+ if (session->client_kex.methods == NULL) {
+ leave_function();
+ return -1;
+ }
+
+ for (i = 0; i < 10; i++) {
+ session->client_kex.methods[i] = strings[i];
+ }
+ } else { /* client */
+ session->server_kex.methods = malloc(10 * sizeof(char **));
+ if (session->server_kex.methods == NULL) {
+ leave_function();
+ return -1;
+ }
+
+ for (i = 0; i < 10; i++) {
+ session->server_kex.methods[i] = strings[i];
+ }
+ }
+
+ leave_function();
+ session->session_state=SSH_SESSION_STATE_KEXINIT_RECEIVED;
+ session->ssh_connection_callback(session);
+ return SSH_PACKET_USED;
+error:
+ ssh_string_free(str);
+ for (i = 0; i < 10; i++) {
+ SAFE_FREE(strings[i]);
+ }
+
+ session->session_state = SSH_SESSION_STATE_ERROR;
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+void ssh_list_kex(ssh_session session, KEX *kex) {
+ int i = 0;
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("session cookie", kex->cookie, 16);
+#endif
+ if(kex->methods==NULL){
+ ssh_log(session, SSH_LOG_RARE,"kex->methods is NULL");
+ return;
+ }
+ for(i = 0; i < 10; i++) {
+ ssh_log(session, SSH_LOG_FUNCTIONS, "%s: %s",
+ ssh_kex_nums[i], kex->methods[i]);
+ }
+}
+
+/* set_kex basicaly look at the option structure of the session and set the output kex message */
+/* it must be aware of the server kex message */
+/* it can fail if option is null, not any user specified kex method matches the server one, if not any default kex matches */
+
+int set_kex(ssh_session session){
+ KEX *server = &session->server_kex;
+ KEX *client=&session->client_kex;
+ int i;
+ const char *wanted;
+ enter_function();
+ ssh_get_random(client->cookie,16,0);
+ client->methods=malloc(10 * sizeof(char **));
+ if (client->methods == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No space left");
+ leave_function();
+ return -1;
+ }
+ memset(client->methods,0,10*sizeof(char **));
+ for (i=0;i<10;i++){
+ if(!(wanted=session->wanted_methods[i]))
+ wanted=default_methods[i];
+ client->methods[i]=ssh_find_matching(server->methods[i],wanted);
+ if(!client->methods[i] && i < SSH_LANG_C_S){
+ ssh_set_error(session,SSH_FATAL,"kex error : did not find one of algos %s in list %s for %s",
+ wanted,server->methods[i],ssh_kex_nums[i]);
+ leave_function();
+ return -1;
+ } else {
+ if ((i >= SSH_LANG_C_S) && (client->methods[i] == NULL)) {
+ /* we can safely do that for languages */
+ client->methods[i] = strdup("");
+ if (client->methods[i] == NULL) {
+ return -1;
+ }
+ }
+ }
+ }
+ leave_function();
+ return 0;
+}
+
+/* this function only sends the predefined set of kex methods */
+int ssh_send_kex(ssh_session session, int server_kex) {
+ KEX *kex = (server_kex ? &session->server_kex : &session->client_kex);
+ ssh_string str = NULL;
+ int i;
+
+ enter_function();
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXINIT) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(session->out_buffer, kex->cookie, 16) < 0) {
+ goto error;
+ }
+
+ if (hashbufout_add_cookie(session) < 0) {
+ goto error;
+ }
+
+ ssh_list_kex(session, kex);
+
+ for (i = 0; i < 10; i++) {
+ str = ssh_string_from_char(kex->methods[i]);
+ if (str == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(session->out_hashbuf, str) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(session->out_buffer, str) < 0) {
+ goto error;
+ }
+ ssh_string_free(str);
+ }
+
+ if (buffer_add_u8(session->out_buffer, 0) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer, 0) < 0) {
+ goto error;
+ }
+
+ if (packet_send(session) == SSH_ERROR) {
+ leave_function();
+ return -1;
+ }
+
+ leave_function();
+ return 0;
+error:
+ buffer_reinit(session->out_buffer);
+ buffer_reinit(session->out_hashbuf);
+ ssh_string_free(str);
+
+ leave_function();
+ return -1;
+}
+
+/* returns 1 if at least one of the name algos is in the default algorithms table */
+int verify_existing_algo(int algo, const char *name){
+ char *ptr;
+ if(algo>9 || algo <0)
+ return -1;
+ ptr=ssh_find_matching(supported_methods[algo],name);
+ if(ptr){
+ free(ptr);
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef WITH_SSH1
+
+/* makes a STRING contating 3 strings : ssh-rsa1,e and n */
+/* this is a public key in openssh's format */
+static ssh_string make_rsa1_string(ssh_string e, ssh_string n){
+ ssh_buffer buffer = NULL;
+ ssh_string rsa = NULL;
+ ssh_string ret = NULL;
+
+ buffer = ssh_buffer_new();
+ rsa = ssh_string_from_char("ssh-rsa1");
+
+ if (buffer_add_ssh_string(buffer, rsa) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(buffer, e) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(buffer, n) < 0) {
+ goto error;
+ }
+
+ ret = ssh_string_new(ssh_buffer_get_len(buffer));
+ if (ret == NULL) {
+ goto error;
+ }
+
+ ssh_string_fill(ret, ssh_buffer_get_begin(buffer), ssh_buffer_get_len(buffer));
+error:
+ ssh_buffer_free(buffer);
+ ssh_string_free(rsa);
+
+ return ret;
+}
+
+static int build_session_id1(ssh_session session, ssh_string servern,
+ ssh_string hostn) {
+ MD5CTX md5 = NULL;
+
+ md5 = md5_init();
+ if (md5 == NULL) {
+ return -1;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("host modulus",ssh_string_data(hostn),ssh_string_len(hostn));
+ ssh_print_hexa("server modulus",ssh_string_data(servern),ssh_string_len(servern));
+#endif
+ md5_update(md5,ssh_string_data(hostn),ssh_string_len(hostn));
+ md5_update(md5,ssh_string_data(servern),ssh_string_len(servern));
+ md5_update(md5,session->server_kex.cookie,8);
+ md5_final(session->next_crypto->session_id,md5);
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("session_id",session->next_crypto->session_id,MD5_DIGEST_LEN);
+#endif
+
+ return 0;
+}
+
+/* returns 1 if the modulus of k1 is < than the one of k2 */
+static int modulus_smaller(ssh_public_key k1, ssh_public_key k2){
+ bignum n1;
+ bignum n2;
+ int res;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_t sexp;
+ sexp=gcry_sexp_find_token(k1->rsa_pub,"n",0);
+ n1=gcry_sexp_nth_mpi(sexp,1,GCRYMPI_FMT_USG);
+ gcry_sexp_release(sexp);
+ sexp=gcry_sexp_find_token(k2->rsa_pub,"n",0);
+ n2=gcry_sexp_nth_mpi(sexp,1,GCRYMPI_FMT_USG);
+ gcry_sexp_release(sexp);
+#elif defined HAVE_LIBCRYPTO
+ n1=k1->rsa_pub->n;
+ n2=k2->rsa_pub->n;
+#endif
+ if(bignum_cmp(n1,n2)<0)
+ res=1;
+ else
+ res=0;
+#ifdef HAVE_LIBGCRYPT
+ bignum_free(n1);
+ bignum_free(n2);
+#endif
+ return res;
+
+}
+
+#define ABS(A) ( (A)<0 ? -(A):(A) )
+static ssh_string encrypt_session_key(ssh_session session, ssh_public_key srvkey,
+ ssh_public_key hostkey, int slen, int hlen) {
+ unsigned char buffer[32] = {0};
+ int i;
+ ssh_string data1 = NULL;
+ ssh_string data2 = NULL;
+
+ /* first, generate a session key */
+ ssh_get_random(session->next_crypto->encryptkey, 32, 1);
+ memcpy(buffer, session->next_crypto->encryptkey, 32);
+ memcpy(session->next_crypto->decryptkey, session->next_crypto->encryptkey, 32);
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("session key",buffer,32);
+#endif
+
+ /* xor session key with session_id */
+ for (i = 0; i < 16; i++) {
+ buffer[i] ^= session->next_crypto->session_id[i];
+ }
+ data1 = ssh_string_new(32);
+ if (data1 == NULL) {
+ return NULL;
+ }
+ ssh_string_fill(data1, buffer, 32);
+ if (ABS(hlen - slen) < 128){
+ ssh_log(session, SSH_LOG_FUNCTIONS,
+ "Difference between server modulus and host modulus is only %d. "
+ "It's illegal and may not work",
+ ABS(hlen - slen));
+ }
+
+ if (modulus_smaller(srvkey, hostkey)) {
+ data2 = ssh_encrypt_rsa1(session, data1, srvkey);
+ ssh_string_free(data1);
+ data1 = NULL;
+ if (data2 == NULL) {
+ return NULL;
+ }
+ data1 = ssh_encrypt_rsa1(session, data2, hostkey);
+ ssh_string_free(data2);
+ if (data1 == NULL) {
+ return NULL;
+ }
+ } else {
+ data2 = ssh_encrypt_rsa1(session, data1, hostkey);
+ ssh_string_free(data1);
+ data1 = NULL;
+ if (data2 == NULL) {
+ return NULL;
+ }
+ data1 = ssh_encrypt_rsa1(session, data2, srvkey);
+ ssh_string_free(data2);
+ if (data1 == NULL) {
+ return NULL;
+ }
+ }
+
+ return data1;
+}
+
+/* SSH-1 functions */
+/* 2 SSH_SMSG_PUBLIC_KEY
+ *
+ * 8 bytes anti_spoofing_cookie
+ * 32-bit int server_key_bits
+ * mp-int server_key_public_exponent
+ * mp-int server_key_public_modulus
+ * 32-bit int host_key_bits
+ * mp-int host_key_public_exponent
+ * mp-int host_key_public_modulus
+ * 32-bit int protocol_flags
+ * 32-bit int supported_ciphers_mask
+ * 32-bit int supported_authentications_mask
+ */
+/**
+ * @brief Wait for a SSH_SMSG_PUBLIC_KEY and does the key exchange
+ */
+SSH_PACKET_CALLBACK(ssh_packet_publickey1){
+ ssh_string server_exp = NULL;
+ ssh_string server_mod = NULL;
+ ssh_string host_exp = NULL;
+ ssh_string host_mod = NULL;
+ ssh_string serverkey = NULL;
+ ssh_string hostkey = NULL;
+ ssh_public_key srv = NULL;
+ ssh_public_key host = NULL;
+ uint32_t server_bits;
+ uint32_t host_bits;
+ uint32_t protocol_flags;
+ uint32_t supported_ciphers_mask;
+ uint32_t supported_authentications_mask;
+ ssh_string enc_session = NULL;
+ uint16_t bits;
+ int ko;
+ enter_function();
+ (void)type;
+ (void)user;
+ ssh_log(session, SSH_LOG_PROTOCOL, "Got a SSH_SMSG_PUBLIC_KEY");
+ if(session->session_state != SSH_SESSION_STATE_INITIAL_KEX){
+ ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state");
+ goto error;
+ }
+ if (buffer_get_data(packet, session->server_kex.cookie, 8) != 8) {
+ ssh_set_error(session, SSH_FATAL, "Can't get cookie in buffer");
+ goto error;
+ }
+
+ buffer_get_u32(packet, &server_bits);
+ server_exp = buffer_get_mpint(packet);
+ if (server_exp == NULL) {
+ goto error;
+ }
+ server_mod = buffer_get_mpint(packet);
+ if (server_mod == NULL) {
+ goto error;
+ }
+ buffer_get_u32(packet, &host_bits);
+ host_exp = buffer_get_mpint(packet);
+ if (host_exp == NULL) {
+ goto error;
+ }
+ host_mod = buffer_get_mpint(packet);
+ if (host_mod == NULL) {
+ goto error;
+ }
+ buffer_get_u32(packet, &protocol_flags);
+ buffer_get_u32(packet, &supported_ciphers_mask);
+ ko = buffer_get_u32(packet, &supported_authentications_mask);
+
+ if ((ko != sizeof(uint32_t)) || !host_mod || !host_exp
+ || !server_mod || !server_exp) {
+ ssh_log(session, SSH_LOG_RARE, "Invalid SSH_SMSG_PUBLIC_KEY packet");
+ ssh_set_error(session, SSH_FATAL, "Invalid SSH_SMSG_PUBLIC_KEY packet");
+ goto error;
+ }
+
+ server_bits = ntohl(server_bits);
+ host_bits = ntohl(host_bits);
+ protocol_flags = ntohl(protocol_flags);
+ supported_ciphers_mask = ntohl(supported_ciphers_mask);
+ supported_authentications_mask = ntohl(supported_authentications_mask);
+ ssh_log(session, SSH_LOG_PROTOCOL,
+ "Server bits: %d; Host bits: %d; Protocol flags: %.8lx; "
+ "Cipher mask: %.8lx; Auth mask: %.8lx",
+ server_bits,
+ host_bits,
+ (unsigned long int) protocol_flags,
+ (unsigned long int) supported_ciphers_mask,
+ (unsigned long int) supported_authentications_mask);
+
+ serverkey = make_rsa1_string(server_exp, server_mod);
+ if (serverkey == NULL) {
+ goto error;
+ }
+ hostkey = make_rsa1_string(host_exp,host_mod);
+ if (serverkey == NULL) {
+ goto error;
+ }
+ if (build_session_id1(session, server_mod, host_mod) < 0) {
+ goto error;
+ }
+
+ srv = publickey_from_string(session, serverkey);
+ if (srv == NULL) {
+ goto error;
+ }
+ host = publickey_from_string(session, hostkey);
+ if (host == NULL) {
+ goto error;
+ }
+
+ session->next_crypto->server_pubkey = ssh_string_copy(hostkey);
+ if (session->next_crypto->server_pubkey == NULL) {
+ goto error;
+ }
+ session->next_crypto->server_pubkey_type = "ssh-rsa1";
+
+ /* now, we must choose an encryption algo */
+ /* hardcode 3des */
+ if (!(supported_ciphers_mask & (1 << SSH_CIPHER_3DES))) {
+ ssh_set_error(session, SSH_FATAL, "Remote server doesn't accept 3DES");
+ goto error;
+ }
+ ssh_log(session, SSH_LOG_PROTOCOL, "Sending SSH_CMSG_SESSION_KEY");
+
+ if (buffer_add_u8(session->out_buffer, SSH_CMSG_SESSION_KEY) < 0) {
+ goto error;
+ }
+ if (buffer_add_u8(session->out_buffer, SSH_CIPHER_3DES) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(session->out_buffer, session->server_kex.cookie, 8) < 0) {
+ goto error;
+ }
+
+ enc_session = encrypt_session_key(session, srv, host, server_bits, host_bits);
+ if (enc_session == NULL) {
+ goto error;
+ }
+
+ bits = ssh_string_len(enc_session) * 8 - 7;
+ ssh_log(session, SSH_LOG_PROTOCOL, "%d bits, %zu bytes encrypted session",
+ bits, ssh_string_len(enc_session));
+ bits = htons(bits);
+ /* the encrypted mpint */
+ if (buffer_add_data(session->out_buffer, &bits, sizeof(uint16_t)) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(session->out_buffer, ssh_string_data(enc_session),
+ ssh_string_len(enc_session)) < 0) {
+ goto error;
+ }
+ /* the protocol flags */
+ if (buffer_add_u32(session->out_buffer, 0) < 0) {
+ goto error;
+ }
+ session->session_state=SSH_SESSION_STATE_KEXINIT_RECEIVED;
+ if (packet_send(session) == SSH_ERROR) {
+ goto error;
+ }
+
+ /* we can set encryption */
+ if (crypt_set_algorithms(session)) {
+ goto error;
+ }
+
+ session->current_crypto = session->next_crypto;
+ session->next_crypto = NULL;
+ goto end;
+error:
+ session->session_state=SSH_SESSION_STATE_ERROR;
+end:
+
+ ssh_string_free(host_mod);
+ ssh_string_free(host_exp);
+ ssh_string_free(server_mod);
+ ssh_string_free(server_exp);
+ ssh_string_free(serverkey);
+ ssh_string_free(hostkey);
+ ssh_string_free(enc_session);
+
+ publickey_free(srv);
+ publickey_free(host);
+
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+int ssh_get_kex1(ssh_session session) {
+ int ret=SSH_ERROR;
+ enter_function();
+ ssh_log(session, SSH_LOG_PROTOCOL, "Waiting for a SSH_SMSG_PUBLIC_KEY");
+ /* Here the callback is called */
+ while(session->session_state==SSH_SESSION_STATE_INITIAL_KEX){
+ ssh_handle_packets(session,-1);
+ }
+ if(session->session_state==SSH_SESSION_STATE_ERROR)
+ goto error;
+ ssh_log(session, SSH_LOG_PROTOCOL, "Waiting for a SSH_SMSG_SUCCESS");
+ /* Waiting for SSH_SMSG_SUCCESS */
+ while(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){
+ ssh_handle_packets(session,-1);
+ }
+ if(session->session_state==SSH_SESSION_STATE_ERROR)
+ goto error;
+ ssh_log(session, SSH_LOG_PROTOCOL, "received SSH_SMSG_SUCCESS\n");
+ ret=SSH_OK;
+error:
+ leave_function();
+ return ret;
+}
+
+#endif /* WITH_SSH1 */
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/keyfiles.c b/src/keyfiles.c
new file mode 100644
index 00000000..9512de0a
--- /dev/null
+++ b/src/keyfiles.c
@@ -0,0 +1,1902 @@
+/*
+ * keyfiles.c - private and public key handling for authentication.
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2009 by Aris Adamantiadis
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifndef _WIN32
+#if _MSC_VER >= 1400
+#include <io.h>
+#undef open
+#define open _open
+#undef close
+#define close _close
+#undef read
+#define read _read
+#endif /* _MSC_VER */
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/buffer.h"
+#include "libssh/keyfiles.h"
+#include "libssh/session.h"
+#include "libssh/wrapper.h"
+#include "libssh/misc.h"
+#include "libssh/keys.h"
+
+/*todo: remove this include */
+#include "libssh/string.h"
+
+
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+#elif defined HAVE_LIBCRYPTO
+#include <openssl/pem.h>
+#include <openssl/dsa.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#endif /* HAVE_LIBCRYPTO */
+
+#define MAXLINESIZE 80
+#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----"
+#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----"
+#define DSA_HEADER_BEGIN "-----BEGIN DSA PRIVATE KEY-----"
+#define DSA_HEADER_END "-----END DSA PRIVATE KEY-----"
+
+#ifdef HAVE_LIBGCRYPT
+
+#define MAX_KEY_SIZE 32
+#define MAX_PASSPHRASE_SIZE 1024
+#define ASN1_INTEGER 2
+#define ASN1_SEQUENCE 48
+#define PKCS5_SALT_LEN 8
+
+static int load_iv(char *header, unsigned char *iv, int iv_len) {
+ int i;
+ int j;
+ int k;
+
+ memset(iv, 0, iv_len);
+ for (i = 0; i < iv_len; i++) {
+ if ((header[2*i] >= '0') && (header[2*i] <= '9'))
+ j = header[2*i] - '0';
+ else if ((header[2*i] >= 'A') && (header[2*i] <= 'F'))
+ j = header[2*i] - 'A' + 10;
+ else if ((header[2*i] >= 'a') && (header[2*i] <= 'f'))
+ j = header[2*i] - 'a' + 10;
+ else
+ return -1;
+ if ((header[2*i+1] >= '0') && (header[2*i+1] <= '9'))
+ k = header[2*i+1] - '0';
+ else if ((header[2*i+1] >= 'A') && (header[2*i+1] <= 'F'))
+ k = header[2*i+1] - 'A' + 10;
+ else if ((header[2*i+1] >= 'a') && (header[2*i+1] <= 'f'))
+ k = header[2*i+1] - 'a' + 10;
+ else
+ return -1;
+ iv[i] = (j << 4) + k;
+ }
+ return 0;
+}
+
+static uint32_t char_to_u32(unsigned char *data, uint32_t size) {
+ uint32_t ret;
+ uint32_t i;
+
+ for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++])
+ ;
+ return ret;
+}
+
+static uint32_t asn1_get_len(ssh_buffer buffer) {
+ uint32_t len;
+ unsigned char tmp[4];
+
+ if (buffer_get_data(buffer,tmp,1) == 0) {
+ return 0;
+ }
+
+ if (tmp[0] > 127) {
+ len = tmp[0] & 127;
+ if (len > 4) {
+ return 0; /* Length doesn't fit in u32. Can this really happen? */
+ }
+ if (buffer_get_data(buffer,tmp,len) == 0) {
+ return 0;
+ }
+ len = char_to_u32(tmp, len);
+ } else {
+ len = char_to_u32(tmp, 1);
+ }
+
+ return len;
+}
+
+static ssh_string asn1_get_int(ssh_buffer buffer) {
+ ssh_string str;
+ unsigned char type;
+ uint32_t size;
+
+ if (buffer_get_data(buffer, &type, 1) == 0 || type != ASN1_INTEGER) {
+ return NULL;
+ }
+ size = asn1_get_len(buffer);
+ if (size == 0) {
+ return NULL;
+ }
+
+ str = ssh_string_new(size);
+ if (str == NULL) {
+ return NULL;
+ }
+
+ if (buffer_get_data(buffer, str->string, size) == 0) {
+ ssh_string_free(str);
+ return NULL;
+ }
+
+ return str;
+}
+
+static int asn1_check_sequence(ssh_buffer buffer) {
+ unsigned char *j = NULL;
+ unsigned char tmp;
+ int i;
+ uint32_t size;
+ uint32_t padding;
+
+ if (buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) {
+ return 0;
+ }
+
+ size = asn1_get_len(buffer);
+ if ((padding = ssh_buffer_get_len(buffer) - buffer->pos - size) > 0) {
+ for (i = ssh_buffer_get_len(buffer) - buffer->pos - size,
+ j = (unsigned char*)ssh_buffer_get_begin(buffer) + size + buffer->pos;
+ i;
+ i--, j++)
+ {
+ if (*j != padding) { /* padding is allowed */
+ return 0; /* but nothing else */
+ }
+ }
+ }
+
+ return 1;
+}
+
+static int read_line(char *data, unsigned int len, FILE *fp) {
+ char tmp;
+ unsigned int i;
+
+ for (i = 0; fread(&tmp, 1, 1, fp) && tmp != '\n' && i < len; data[i++] = tmp)
+ ;
+ if (tmp == '\n') {
+ return i;
+ }
+
+ if (i >= len) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int passphrase_to_key(char *data, unsigned int datalen,
+ unsigned char *salt, unsigned char *key, unsigned int keylen) {
+ MD5CTX md;
+ unsigned char digest[MD5_DIGEST_LEN] = {0};
+ unsigned int i;
+ unsigned int j;
+ unsigned int md_not_empty;
+
+ for (j = 0, md_not_empty = 0; j < keylen; ) {
+ md = md5_init();
+ if (md == NULL) {
+ return -1;
+ }
+
+ if (md_not_empty) {
+ md5_update(md, digest, MD5_DIGEST_LEN);
+ } else {
+ md_not_empty = 1;
+ }
+
+ md5_update(md, data, datalen);
+ if (salt) {
+ md5_update(md, salt, PKCS5_SALT_LEN);
+ }
+ md5_final(digest, md);
+
+ for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) {
+ if (key) {
+ key[j] = digest[i];
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int privatekey_decrypt(int algo, int mode, unsigned int key_len,
+ unsigned char *iv, unsigned int iv_len,
+ ssh_buffer data, ssh_auth_callback cb,
+ void *userdata,
+ const char *desc)
+{
+ char passphrase[MAX_PASSPHRASE_SIZE] = {0};
+ unsigned char key[MAX_KEY_SIZE] = {0};
+ unsigned char *tmp = NULL;
+ gcry_cipher_hd_t cipher;
+ int rc = -1;
+
+ if (!algo) {
+ return -1;
+ }
+
+ if (cb) {
+ rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata);
+ if (rc < 0) {
+ return -1;
+ }
+ } else if (cb == NULL && userdata != NULL) {
+ snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *) userdata);
+ }
+
+ if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < 0) {
+ return -1;
+ }
+
+ if (gcry_cipher_open(&cipher, algo, mode, 0)
+ || gcry_cipher_setkey(cipher, key, key_len)
+ || gcry_cipher_setiv(cipher, iv, iv_len)
+ || (tmp = malloc(ssh_buffer_get_len(data) * sizeof (char))) == NULL
+ || gcry_cipher_decrypt(cipher, tmp, ssh_buffer_get_len(data),
+ ssh_buffer_get_begin(data), ssh_buffer_get_len(data))) {
+ gcry_cipher_close(cipher);
+ return -1;
+ }
+
+ memcpy(ssh_buffer_get_begin(data), tmp, ssh_buffer_get_len(data));
+
+ SAFE_FREE(tmp);
+ gcry_cipher_close(cipher);
+
+ return 0;
+}
+
+static int privatekey_dek_header(char *header, unsigned int header_len,
+ int *algo, int *mode, unsigned int *key_len, unsigned char **iv,
+ unsigned int *iv_len) {
+ unsigned int iv_pos;
+
+ if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12))
+ {
+ *algo = GCRY_CIPHER_3DES;
+ iv_pos = 13;
+ *mode = GCRY_CIPHER_MODE_CBC;
+ *key_len = 24;
+ *iv_len = 8;
+ }
+ else if (header_len > 8 && !strncmp("DES-CBC", header, 7))
+ {
+ *algo = GCRY_CIPHER_DES;
+ iv_pos = 8;
+ *mode = GCRY_CIPHER_MODE_CBC;
+ *key_len = 8;
+ *iv_len = 8;
+ }
+ else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11))
+ {
+ *algo = GCRY_CIPHER_AES128;
+ iv_pos = 12;
+ *mode = GCRY_CIPHER_MODE_CBC;
+ *key_len = 16;
+ *iv_len = 16;
+ }
+ else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11))
+ {
+ *algo = GCRY_CIPHER_AES192;
+ iv_pos = 12;
+ *mode = GCRY_CIPHER_MODE_CBC;
+ *key_len = 24;
+ *iv_len = 16;
+ }
+ else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11))
+ {
+ *algo = GCRY_CIPHER_AES256;
+ iv_pos = 12;
+ *mode = GCRY_CIPHER_MODE_CBC;
+ *key_len = 32;
+ *iv_len = 16;
+ } else {
+ return -1;
+ }
+
+ *iv = malloc(*iv_len);
+ if (*iv == NULL) {
+ return -1;
+ }
+
+ return load_iv(header + iv_pos, *iv, *iv_len);
+}
+
+static ssh_buffer privatekey_file_to_buffer(FILE *fp, int type,
+ ssh_auth_callback cb, void *userdata, const char *desc) {
+ ssh_buffer buffer = NULL;
+ ssh_buffer out = NULL;
+ char buf[MAXLINESIZE] = {0};
+ unsigned char *iv = NULL;
+ const char *header_begin;
+ const char *header_end;
+ unsigned int header_begin_size;
+ unsigned int header_end_size;
+ unsigned int key_len = 0;
+ unsigned int iv_len = 0;
+ int algo = 0;
+ int mode = 0;
+ int len;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ switch(type) {
+ case SSH_KEYTYPE_DSS:
+ header_begin = DSA_HEADER_BEGIN;
+ header_end = DSA_HEADER_END;
+ break;
+ case SSH_KEYTYPE_RSA:
+ header_begin = RSA_HEADER_BEGIN;
+ header_end = RSA_HEADER_END;
+ break;
+ default:
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ header_begin_size = strlen(header_begin);
+ header_end_size = strlen(header_end);
+
+ while (read_line(buf, MAXLINESIZE, fp) &&
+ strncmp(buf, header_begin, header_begin_size))
+ ;
+
+ len = read_line(buf, MAXLINESIZE, fp);
+ if (len > 11 && strncmp("Proc-Type: 4,ENCRYPTED", buf, 11) == 0) {
+ len = read_line(buf, MAXLINESIZE, fp);
+ if (len > 10 && strncmp("DEK-Info: ", buf, 10) == 0) {
+ if ((privatekey_dek_header(buf + 10, len - 10, &algo, &mode, &key_len,
+ &iv, &iv_len) < 0)
+ || read_line(buf, MAXLINESIZE, fp)) {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+ } else {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+ } else {
+ if (buffer_add_data(buffer, buf, len) < 0) {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+ }
+
+ while ((len = read_line(buf,MAXLINESIZE,fp)) &&
+ strncmp(buf, header_end, header_end_size) != 0) {
+ if (len == -1) {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+ if (buffer_add_data(buffer, buf, len) < 0) {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+ }
+
+ if (strncmp(buf,header_end,header_end_size) != 0) {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+
+ if (buffer_add_data(buffer, "\0", 1) < 0) {
+ ssh_buffer_free(buffer);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+
+ out = base64_to_bin(ssh_buffer_get_begin(buffer));
+ ssh_buffer_free(buffer);
+ if (out == NULL) {
+ SAFE_FREE(iv);
+ return NULL;
+ }
+
+ if (algo) {
+ if (privatekey_decrypt(algo, mode, key_len, iv, iv_len, out,
+ cb, userdata, desc) < 0) {
+ ssh_buffer_free(out);
+ SAFE_FREE(iv);
+ return NULL;
+ }
+ }
+ SAFE_FREE(iv);
+
+ return out;
+}
+
+static int read_rsa_privatekey(FILE *fp, gcry_sexp_t *r,
+ ssh_auth_callback cb, void *userdata, const char *desc) {
+ ssh_string n = NULL;
+ ssh_string e = NULL;
+ ssh_string d = NULL;
+ ssh_string p = NULL;
+ ssh_string q = NULL;
+ ssh_string unused1 = NULL;
+ ssh_string unused2 = NULL;
+ ssh_string u = NULL;
+ ssh_string v = NULL;
+ ssh_buffer buffer = NULL;
+ int rc = 1;
+
+ buffer = privatekey_file_to_buffer(fp, SSH_KEYTYPE_RSA, cb, userdata, desc);
+ if (buffer == NULL) {
+ return 0;
+ }
+
+ if (!asn1_check_sequence(buffer)) {
+ ssh_buffer_free(buffer);
+ return 0;
+ }
+
+ v = asn1_get_int(buffer);
+ if (ntohl(v->size) != 1 || v->string[0] != 0) {
+ ssh_buffer_free(buffer);
+ return 0;
+ }
+
+ n = asn1_get_int(buffer);
+ e = asn1_get_int(buffer);
+ d = asn1_get_int(buffer);
+ q = asn1_get_int(buffer);
+ p = asn1_get_int(buffer);
+ unused1 = asn1_get_int(buffer);
+ unused2 = asn1_get_int(buffer);
+ u = asn1_get_int(buffer);
+
+ ssh_buffer_free(buffer);
+
+ if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL ||
+ unused1 == NULL || unused2 == NULL|| u == NULL) {
+ rc = 0;
+ goto error;
+ }
+
+ if (gcry_sexp_build(r, NULL,
+ "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))",
+ ntohl(n->size), n->string,
+ ntohl(e->size), e->string,
+ ntohl(d->size), d->string,
+ ntohl(p->size), p->string,
+ ntohl(q->size), q->string,
+ ntohl(u->size), u->string)) {
+ rc = 0;
+ }
+
+error:
+ ssh_string_free(n);
+ ssh_string_free(e);
+ ssh_string_free(d);
+ ssh_string_free(p);
+ ssh_string_free(q);
+ ssh_string_free(unused1);
+ ssh_string_free(unused2);
+ ssh_string_free(u);
+ ssh_string_free(v);
+
+ return rc;
+}
+
+static int read_dsa_privatekey(FILE *fp, gcry_sexp_t *r, ssh_auth_callback cb,
+ void *userdata, const char *desc) {
+ ssh_buffer buffer = NULL;
+ ssh_string p = NULL;
+ ssh_string q = NULL;
+ ssh_string g = NULL;
+ ssh_string y = NULL;
+ ssh_string x = NULL;
+ ssh_string v = NULL;
+ int rc = 1;
+
+ buffer = privatekey_file_to_buffer(fp, SSH_KEYTYPE_DSS, cb, userdata, desc);
+ if (buffer == NULL) {
+ return 0;
+ }
+
+ if (!asn1_check_sequence(buffer)) {
+ ssh_buffer_free(buffer);
+ return 0;
+ }
+
+ v = asn1_get_int(buffer);
+ if (ntohl(v->size) != 1 || v->string[0] != 0) {
+ ssh_buffer_free(buffer);
+ return 0;
+ }
+
+ p = asn1_get_int(buffer);
+ q = asn1_get_int(buffer);
+ g = asn1_get_int(buffer);
+ y = asn1_get_int(buffer);
+ x = asn1_get_int(buffer);
+ ssh_buffer_free(buffer);
+
+ if (p == NULL || q == NULL || g == NULL || y == NULL || x == NULL) {
+ rc = 0;
+ goto error;
+ }
+
+ if (gcry_sexp_build(r, NULL,
+ "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))",
+ ntohl(p->size), p->string,
+ ntohl(q->size), q->string,
+ ntohl(g->size), g->string,
+ ntohl(y->size), y->string,
+ ntohl(x->size), x->string)) {
+ rc = 0;
+ }
+
+error:
+ ssh_string_free(p);
+ ssh_string_free(q);
+ ssh_string_free(g);
+ ssh_string_free(y);
+ ssh_string_free(x);
+ ssh_string_free(v);
+
+ return rc;
+}
+#endif /* HAVE_LIBGCRYPT */
+
+#ifdef HAVE_LIBCRYPTO
+static int pem_get_password(char *buf, int size, int rwflag, void *userdata) {
+ ssh_session session = userdata;
+
+ /* unused flag */
+ (void) rwflag;
+ if(buf==NULL)
+ return 0;
+ memset(buf,'\0',size);
+ ssh_log(session, SSH_LOG_RARE,
+ "Trying to call external authentication function");
+
+ if (session && session->callbacks && session->callbacks->auth_function) {
+ if (session->callbacks->auth_function("Passphrase for private key:", buf, size, 0, 0,
+ session->callbacks->userdata) < 0) {
+ return 0;
+ }
+
+ return strlen(buf);
+ }
+
+ return 0;
+}
+#endif /* HAVE_LIBCRYPTO */
+
+static int privatekey_type_from_file(FILE *fp) {
+ char buffer[MAXLINESIZE] = {0};
+
+ if (!fgets(buffer, MAXLINESIZE, fp)) {
+ return 0;
+ }
+ fseek(fp, 0, SEEK_SET);
+ if (strncmp(buffer, DSA_HEADER_BEGIN, strlen(DSA_HEADER_BEGIN)) == 0) {
+ return SSH_KEYTYPE_DSS;
+ }
+ if (strncmp(buffer, RSA_HEADER_BEGIN, strlen(RSA_HEADER_BEGIN)) == 0) {
+ return SSH_KEYTYPE_RSA;
+ }
+ return 0;
+}
+
+/**
+ * @addtogroup libssh_auth
+ *
+ * @{
+ */
+
+/**
+ * @brief Reads a SSH private key from a file.
+ *
+ * @param[in] session The SSH Session to use.
+ *
+ * @param[in] filename The filename of the the private key.
+ *
+ * @param[in] type The type of the private key. This could be SSH_KEYTYPE_DSS or
+ * SSH_KEYTYPE_RSA. Pass 0 to automatically detect the type.
+ *
+ * @param[in] passphrase The passphrase to decrypt the private key. Set to null
+ * if none is needed or it is unknown.
+ *
+ * @return A private_key object containing the private key, or
+ * NULL on error.
+ * @see privatekey_free()
+ * @see publickey_from_privatekey()
+ */
+ssh_private_key privatekey_from_file(ssh_session session, const char *filename,
+ int type, const char *passphrase) {
+ ssh_private_key privkey = NULL;
+ FILE *file = NULL;
+#ifdef HAVE_LIBGCRYPT
+ ssh_auth_callback auth_cb = NULL;
+ void *auth_ud = NULL;
+
+ gcry_sexp_t dsa = NULL;
+ gcry_sexp_t rsa = NULL;
+ int valid;
+#elif defined HAVE_LIBCRYPTO
+ DSA *dsa = NULL;
+ RSA *rsa = NULL;
+#endif
+ /* TODO Implement to read both DSA and RSA at once. */
+
+ /* needed for openssl initialization */
+ ssh_init();
+ ssh_log(session, SSH_LOG_RARE, "Trying to open %s", filename);
+ file = fopen(filename,"r");
+ if (file == NULL) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Error opening %s: %s", filename, strerror(errno));
+ return NULL;
+ }
+
+ ssh_log(session, SSH_LOG_RARE, "Trying to read %s, passphase=%s, authcb=%s",
+ filename, passphrase ? "true" : "false",
+ session->callbacks && session->callbacks->auth_function ? "true" : "false");
+
+ if (type == 0) {
+ type = privatekey_type_from_file(file);
+ if (type == 0) {
+ fclose(file);
+ ssh_set_error(session, SSH_FATAL, "Invalid private key file.");
+ return NULL;
+ }
+ }
+ switch (type) {
+ case SSH_KEYTYPE_DSS:
+ if (passphrase == NULL) {
+ if (session->callbacks && session->callbacks->auth_function) {
+#ifdef HAVE_LIBGCRYPT
+ auth_cb = session->callbacks->auth_function;
+ auth_ud = session->callbacks->userdata;
+
+ valid = read_dsa_privatekey(file, &dsa, auth_cb, auth_ud,
+ "Passphrase for private key:");
+ } else { /* authcb */
+ valid = read_dsa_privatekey(file, &dsa, NULL, NULL, NULL);
+ } /* authcb */
+ } else { /* passphrase */
+ valid = read_dsa_privatekey(file, &dsa, NULL,
+ (void *) passphrase, NULL);
+ }
+
+ fclose(file);
+
+ if (!valid) {
+ ssh_set_error(session, SSH_FATAL, "Parsing private key %s", filename);
+#elif defined HAVE_LIBCRYPTO
+ dsa = PEM_read_DSAPrivateKey(file, NULL, pem_get_password, session);
+ } else { /* authcb */
+ /* openssl uses its own callback to get the passphrase here */
+ dsa = PEM_read_DSAPrivateKey(file, NULL, NULL, NULL);
+ } /* authcb */
+ } else { /* passphrase */
+ dsa = PEM_read_DSAPrivateKey(file, NULL, NULL, (void *) passphrase);
+ }
+
+ fclose(file);
+ if (dsa == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Parsing private key %s: %s",
+ filename, ERR_error_string(ERR_get_error(), NULL));
+#endif
+ return NULL;
+ }
+ break;
+ case SSH_KEYTYPE_RSA:
+ if (passphrase == NULL) {
+ if (session->callbacks && session->callbacks->auth_function) {
+#ifdef HAVE_LIBGCRYPT
+ auth_cb = session->callbacks->auth_function;
+ auth_ud = session->callbacks->userdata;
+ valid = read_rsa_privatekey(file, &rsa, auth_cb, auth_ud,
+ "Passphrase for private key:");
+ } else { /* authcb */
+ valid = read_rsa_privatekey(file, &rsa, NULL, NULL, NULL);
+ } /* authcb */
+ } else { /* passphrase */
+ valid = read_rsa_privatekey(file, &rsa, NULL,
+ (void *) passphrase, NULL);
+ }
+
+ fclose(file);
+
+ if (!valid) {
+ ssh_set_error(session,SSH_FATAL, "Parsing private key %s", filename);
+#elif defined HAVE_LIBCRYPTO
+ rsa = PEM_read_RSAPrivateKey(file, NULL, pem_get_password, session);
+ } else { /* authcb */
+ /* openssl uses its own callback to get the passphrase here */
+ rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
+ } /* authcb */
+ } else { /* passphrase */
+ rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, (void *) passphrase);
+ }
+
+ fclose(file);
+
+ if (rsa == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Parsing private key %s: %s",
+ filename, ERR_error_string(ERR_get_error(),NULL));
+#endif
+ return NULL;
+ }
+ break;
+ default:
+ fclose(file);
+ ssh_set_error(session, SSH_FATAL, "Invalid private key type %d", type);
+ return NULL;
+ } /* switch */
+
+ privkey = malloc(sizeof(struct ssh_private_key_struct));
+ if (privkey == NULL) {
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(dsa);
+ gcry_sexp_release(rsa);
+#elif defined HAVE_LIBCRYPTO
+ DSA_free(dsa);
+ RSA_free(rsa);
+#endif
+ return NULL;
+ }
+
+ privkey->type = type;
+ privkey->dsa_priv = dsa;
+ privkey->rsa_priv = rsa;
+
+ return privkey;
+}
+
+/**
+ * @brief returns the type of a private key
+ * @param[in] privatekey the private key handle
+ * @returns one of SSH_KEYTYPE_RSA,SSH_KEYTYPE_DSS,SSH_KEYTYPE_RSA1
+ * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown
+ * @see privatekey_from_file
+ * @see ssh_userauth_offer_pubkey
+ */
+enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey){
+ if (privatekey==NULL)
+ return SSH_KEYTYPE_UNKNOWN;
+ return privatekey->type;
+}
+
+/* same that privatekey_from_file() but without any passphrase things. */
+ssh_private_key _privatekey_from_file(void *session, const char *filename,
+ int type) {
+ ssh_private_key privkey = NULL;
+ FILE *file = NULL;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_t dsa = NULL;
+ gcry_sexp_t rsa = NULL;
+ int valid;
+#elif defined HAVE_LIBCRYPTO
+ DSA *dsa = NULL;
+ RSA *rsa = NULL;
+#endif
+
+ file = fopen(filename,"r");
+ if (file == NULL) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Error opening %s: %s", filename, strerror(errno));
+ return NULL;
+ }
+
+ switch (type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ valid = read_dsa_privatekey(file, &dsa, NULL, NULL, NULL);
+
+ fclose(file);
+
+ if (!valid) {
+ ssh_set_error(session, SSH_FATAL, "Parsing private key %s", filename);
+#elif defined HAVE_LIBCRYPTO
+ dsa = PEM_read_DSAPrivateKey(file, NULL, NULL, NULL);
+
+ fclose(file);
+
+ if (dsa == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Parsing private key %s: %s",
+ filename, ERR_error_string(ERR_get_error(), NULL));
+#endif
+ return NULL;
+ }
+ break;
+ case SSH_KEYTYPE_RSA:
+#ifdef HAVE_LIBGCRYPT
+ valid = read_rsa_privatekey(file, &rsa, NULL, NULL, NULL);
+
+ fclose(file);
+
+ if (!valid) {
+ ssh_set_error(session, SSH_FATAL, "Parsing private key %s", filename);
+#elif defined HAVE_LIBCRYPTO
+ rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
+
+ fclose(file);
+
+ if (rsa == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Parsing private key %s: %s",
+ filename, ERR_error_string(ERR_get_error(), NULL));
+#endif
+ return NULL;
+ }
+ break;
+ default:
+ ssh_set_error(session, SSH_FATAL, "Invalid private key type %d", type);
+ return NULL;
+ }
+
+ privkey = malloc(sizeof(struct ssh_private_key_struct));
+ if (privkey == NULL) {
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(dsa);
+ gcry_sexp_release(rsa);
+#elif defined HAVE_LIBCRYPTO
+ DSA_free(dsa);
+ RSA_free(rsa);
+#endif
+ return NULL;
+ }
+
+ privkey->type = type;
+ privkey->dsa_priv = dsa;
+ privkey->rsa_priv = rsa;
+
+ return privkey;
+}
+
+/**
+ * @brief Deallocate a private key object.
+ *
+ * @param[in] prv The private_key object to free.
+ */
+void privatekey_free(ssh_private_key prv) {
+ if (prv == NULL) {
+ return;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(prv->dsa_priv);
+ gcry_sexp_release(prv->rsa_priv);
+#elif defined HAVE_LIBCRYPTO
+ DSA_free(prv->dsa_priv);
+ RSA_free(prv->rsa_priv);
+#endif
+ memset(prv, 0, sizeof(struct ssh_private_key_struct));
+ SAFE_FREE(prv);
+}
+
+/**
+ * @brief Write a public key to a file.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] file The filename to write the key into.
+ *
+ * @param[in] pubkey The public key to write.
+ *
+ * @param[in] type The type of the public key.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int ssh_publickey_to_file(ssh_session session, const char *file,
+ ssh_string pubkey, int type) {
+ FILE *fp;
+ char *user;
+ char buffer[1024];
+ char host[256];
+ unsigned char *pubkey_64;
+ size_t len;
+ int rc;
+
+ pubkey_64 = bin_to_base64(pubkey->string, ssh_string_len(pubkey));
+ if (pubkey_64 == NULL) {
+ return -1;
+ }
+
+ user = ssh_get_local_username(session);
+ if (user == NULL) {
+ SAFE_FREE(pubkey_64);
+ return -1;
+ }
+
+ rc = gethostname(host, sizeof(host));
+ if (rc < 0) {
+ SAFE_FREE(user);
+ SAFE_FREE(pubkey_64);
+ return -1;
+ }
+
+ snprintf(buffer, sizeof(buffer), "%s %s %s@%s\n",
+ ssh_type_to_char(type),
+ pubkey_64,
+ user,
+ host);
+
+ SAFE_FREE(pubkey_64);
+ SAFE_FREE(user);
+
+ ssh_log(session, SSH_LOG_RARE, "Trying to write public key file: %s", file);
+ ssh_log(session, SSH_LOG_PACKET, "public key file content: %s", buffer);
+
+ fp = fopen(file, "w+");
+ if (fp == NULL) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Error opening %s: %s", file, strerror(errno));
+ return -1;
+ }
+
+ len = strlen(buffer);
+ if (fwrite(buffer, len, 1, fp) != 1 || ferror(fp)) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Unable to write to %s", file);
+ fclose(fp);
+ unlink(file);
+ return -1;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * @brief Retrieve a public key from a file.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @param[in] filename The filename of the public key.
+ *
+ * @param[out] type The Pointer to a integer. If it is not NULL, it will
+ * contain the type of the key after execution.
+ *
+ * @return A SSH String containing the public key, or NULL if it
+ * failed.
+ *
+ * @see string_free()
+ * @see publickey_from_privatekey()
+ */
+ssh_string publickey_from_file(ssh_session session, const char *filename,
+ int *type) {
+ ssh_buffer buffer = NULL;
+ char buf[4096] = {0};
+ ssh_string str = NULL;
+ char *ptr = NULL;
+ int key_type;
+ int fd = -1;
+ int r;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Public key file doesn't exist");
+ return NULL;
+ }
+
+ if (read(fd, buf, 8) != 8) {
+ close(fd);
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Invalid public key file");
+ return NULL;
+ }
+
+ buf[7] = '\0';
+
+ key_type = ssh_type_from_name(buf);
+ if (key_type == -1) {
+ close(fd);
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Invalid public key file");
+ return NULL;
+ }
+
+ r = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (r <= 0) {
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Invalid public key file");
+ return NULL;
+ }
+
+ buf[r] = 0;
+ ptr = strchr(buf, ' ');
+
+ /* eliminate the garbage at end of file */
+ if (ptr) {
+ *ptr = '\0';
+ }
+
+ buffer = base64_to_bin(buf);
+ if (buffer == NULL) {
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Invalid public key file");
+ return NULL;
+ }
+
+ str = ssh_string_new(ssh_buffer_get_len(buffer));
+ if (str == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ ssh_string_fill(str, ssh_buffer_get_begin(buffer), ssh_buffer_get_len(buffer));
+ ssh_buffer_free(buffer);
+
+ if (type) {
+ *type = key_type;
+ }
+
+ return str;
+}
+
+/**
+ * @brief Try to read the public key from a given file.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @param[in] keyfile The name of the private keyfile.
+ *
+ * @param[out] publickey A ssh_string to store the public key.
+ *
+ * @param[out] type A pointer to an integer to store the type.
+ *
+ * @return 0 on success, -1 on error or the private key doesn't
+ * exist, 1 if the public key doesn't exist.
+ */
+int ssh_try_publickey_from_file(ssh_session session, const char *keyfile,
+ ssh_string *publickey, int *type) {
+ char *pubkey_file;
+ size_t len;
+ ssh_string pubkey_string;
+ int pubkey_type;
+
+ if (session == NULL || keyfile == NULL || publickey == NULL || type == NULL) {
+ return -1;
+ }
+
+ if (session->sshdir == NULL) {
+ if (ssh_options_apply(session) < 0) {
+ return -1;
+ }
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Trying to open privatekey %s", keyfile);
+ if (!ssh_file_readaccess_ok(keyfile)) {
+ ssh_log(session, SSH_LOG_PACKET, "Failed to open privatekey %s", keyfile);
+ return -1;
+ }
+
+ len = strlen(keyfile) + 5;
+ pubkey_file = malloc(len);
+ if (pubkey_file == NULL) {
+ return -1;
+ }
+ snprintf(pubkey_file, len, "%s.pub", keyfile);
+
+ ssh_log(session, SSH_LOG_PACKET, "Trying to open publickey %s",
+ pubkey_file);
+ if (!ssh_file_readaccess_ok(pubkey_file)) {
+ ssh_log(session, SSH_LOG_PACKET, "Failed to open publickey %s",
+ pubkey_file);
+ SAFE_FREE(pubkey_file);
+ return 1;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Success opening public and private key");
+
+ /*
+ * We are sure both the private and public key file is readable. We return
+ * the public as a string, and the private filename as an argument
+ */
+ pubkey_string = publickey_from_file(session, pubkey_file, &pubkey_type);
+ if (pubkey_string == NULL) {
+ ssh_log(session, SSH_LOG_PACKET,
+ "Wasn't able to open public key file %s: %s",
+ pubkey_file,
+ ssh_get_error(session));
+ SAFE_FREE(pubkey_file);
+ return -1;
+ }
+
+ SAFE_FREE(pubkey_file);
+
+ *publickey = pubkey_string;
+ *type = pubkey_type;
+
+ return 0;
+}
+
+ssh_string try_publickey_from_file(ssh_session session, struct ssh_keys_struct keytab,
+ char **privkeyfile, int *type) {
+ const char *priv;
+ const char *pub;
+ char *new;
+ ssh_string pubkey=NULL;
+
+ pub = keytab.publickey;
+ if (pub == NULL) {
+ return NULL;
+ }
+ priv = keytab.privatekey;
+ if (priv == NULL) {
+ return NULL;
+ }
+
+ if (session->sshdir == NULL) {
+ if (ssh_options_apply(session) < 0) {
+ return NULL;
+ }
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Trying to open publickey %s", pub);
+ if (!ssh_file_readaccess_ok(pub)) {
+ ssh_log(session, SSH_LOG_PACKET, "Failed to open publickey %s", pub);
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Trying to open privatekey %s", priv);
+ if (!ssh_file_readaccess_ok(priv)) {
+ ssh_log(session, SSH_LOG_PACKET, "Failed to open privatekey %s", priv);
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Success opening public and private key");
+
+ /*
+ * We are sure both the private and public key file is readable. We return
+ * the public as a string, and the private filename as an argument
+ */
+ pubkey = publickey_from_file(session, pub, type);
+ if (pubkey == NULL) {
+ ssh_log(session, SSH_LOG_PACKET,
+ "Wasn't able to open public key file %s: %s",
+ pub,
+ ssh_get_error(session));
+ goto error;
+ }
+
+ new = realloc(*privkeyfile, strlen(priv) + 1);
+ if (new == NULL) {
+ ssh_string_free(pubkey);
+ goto error;
+ }
+
+ strcpy(new, priv);
+ *privkeyfile = new;
+error:
+ return pubkey;
+}
+
+static int alldigits(const char *s) {
+ while (*s) {
+ if (isdigit(*s)) {
+ s++;
+ } else {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/** @} */
+
+
+/**
+ * @addtogroup libssh_session
+ *
+ * @{
+ */
+
+/**
+ * @internal
+ *
+ * @brief Free a token array.
+ */
+static void tokens_free(char **tokens) {
+ if (tokens == NULL) {
+ return;
+ }
+
+ SAFE_FREE(tokens[0]);
+ /* It's not needed to free other pointers because tokens generated by
+ * space_tokenize fit all in one malloc
+ */
+ SAFE_FREE(tokens);
+}
+
+/**
+ * @internal
+ *
+ * @brief Return one line of known host file.
+ *
+ * This will return a token array containing (host|ip), keytype and key.
+ *
+ * @param[out] file A pointer to the known host file. Could be pointing to
+ * NULL at start.
+ *
+ * @param[in] filename The file name of the known host file.
+ *
+ * @param[out] found_type A pointer to a string to be set with the found key
+ * type.
+ *
+ * @returns The found_type type of key (ie "dsa","ssh-rsa1"). Don't
+ * free that value. NULL if no match was found or the file
+ * was not found.
+ */
+static char **ssh_get_knownhost_line(ssh_session session, FILE **file,
+ const char *filename, const char **found_type) {
+ char buffer[4096] = {0};
+ char *ptr;
+ char **tokens;
+
+ enter_function();
+
+ if(*file == NULL){
+ *file = fopen(filename,"r");
+ if (*file == NULL) {
+ leave_function();
+ return NULL;
+ }
+ }
+
+ while (fgets(buffer, sizeof(buffer), *file)) {
+ ptr = strchr(buffer, '\n');
+ if (ptr) {
+ *ptr = '\0';
+ }
+
+ ptr = strchr(buffer,'\r');
+ if (ptr) {
+ *ptr = '\0';
+ }
+
+ if (!buffer[0] || buffer[0] == '#') {
+ continue; /* skip empty lines */
+ }
+
+ tokens = space_tokenize(buffer);
+ if (tokens == NULL) {
+ fclose(*file);
+ *file = NULL;
+ leave_function();
+ return NULL;
+ }
+
+ if(!tokens[0] || !tokens[1] || !tokens[2]) {
+ /* it should have at least 3 tokens */
+ tokens_free(tokens);
+ continue;
+ }
+
+ *found_type = tokens[1];
+ if (tokens[3]) {
+ /* openssh rsa1 format has 4 tokens on the line. Recognize it
+ by the fact that everything is all digits */
+ if (tokens[4]) {
+ /* that's never valid */
+ tokens_free(tokens);
+ continue;
+ }
+ if (alldigits(tokens[1]) && alldigits(tokens[2]) && alldigits(tokens[3])) {
+ *found_type = "ssh-rsa1";
+ } else {
+ /* 3 tokens only, not four */
+ tokens_free(tokens);
+ continue;
+ }
+ }
+ leave_function();
+ return tokens;
+ }
+
+ fclose(*file);
+ *file = NULL;
+
+ /* we did not find anything, end of file*/
+ leave_function();
+ return NULL;
+}
+
+/**
+ * @brief Check the public key in the known host line matches the public key of
+ * the currently connected server.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @param[in] tokens A list of tokens in the known_hosts line.
+ *
+ * @returns 1 if the key matches, 0 if the key doesn't match and -1
+ * on error.
+ */
+static int check_public_key(ssh_session session, char **tokens) {
+ ssh_string pubkey = session->current_crypto->server_pubkey;
+ ssh_buffer pubkey_buffer;
+ char *pubkey_64;
+
+ /* ok we found some public key in known hosts file. now un-base64it */
+ if (alldigits(tokens[1])) {
+ /* openssh rsa1 format */
+ bignum tmpbn;
+ ssh_string tmpstring;
+ unsigned int len;
+ int i;
+
+ pubkey_buffer = ssh_buffer_new();
+ if (pubkey_buffer == NULL) {
+ return -1;
+ }
+
+ tmpstring = ssh_string_from_char("ssh-rsa1");
+ if (tmpstring == NULL) {
+ ssh_buffer_free(pubkey_buffer);
+ return -1;
+ }
+
+ if (buffer_add_ssh_string(pubkey_buffer, tmpstring) < 0) {
+ ssh_buffer_free(pubkey_buffer);
+ ssh_string_free(tmpstring);
+ return -1;
+ }
+ ssh_string_free(tmpstring);
+
+ for (i = 2; i < 4; i++) { /* e, then n */
+ tmpbn = NULL;
+ bignum_dec2bn(tokens[i], &tmpbn);
+ if (tmpbn == NULL) {
+ ssh_buffer_free(pubkey_buffer);
+ return -1;
+ }
+ /* for some reason, make_bignum_string does not work
+ because of the padding which it does --kv */
+ /* tmpstring = make_bignum_string(tmpbn); */
+ /* do it manually instead */
+ len = bignum_num_bytes(tmpbn);
+ tmpstring = malloc(4 + len);
+ if (tmpstring == NULL) {
+ ssh_buffer_free(pubkey_buffer);
+ bignum_free(tmpbn);
+ return -1;
+ }
+ /* TODO: fix the hardcoding */
+ tmpstring->size = htonl(len);
+#ifdef HAVE_LIBGCRYPT
+ bignum_bn2bin(tmpbn, len, tmpstring->string);
+#elif defined HAVE_LIBCRYPTO
+ bignum_bn2bin(tmpbn, tmpstring->string);
+#endif
+ bignum_free(tmpbn);
+ if (buffer_add_ssh_string(pubkey_buffer, tmpstring) < 0) {
+ ssh_buffer_free(pubkey_buffer);
+ ssh_string_free(tmpstring);
+ bignum_free(tmpbn);
+ return -1;
+ }
+ ssh_string_free(tmpstring);
+ }
+ } else {
+ /* ssh-dss or ssh-rsa */
+ pubkey_64 = tokens[2];
+ pubkey_buffer = base64_to_bin(pubkey_64);
+ }
+
+ if (pubkey_buffer == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Verifying that server is a known host: base64 error");
+ return -1;
+ }
+
+ if (ssh_buffer_get_len(pubkey_buffer) != ssh_string_len(pubkey)) {
+ ssh_buffer_free(pubkey_buffer);
+ return 0;
+ }
+
+ /* now test that they are identical */
+ if (memcmp(ssh_buffer_get_begin(pubkey_buffer), pubkey->string,
+ ssh_buffer_get_len(pubkey_buffer)) != 0) {
+ ssh_buffer_free(pubkey_buffer);
+ return 0;
+ }
+
+ ssh_buffer_free(pubkey_buffer);
+ return 1;
+}
+
+/**
+ * @brief Check if a hostname matches a openssh-style hashed known host.
+ *
+ * @param[in] host The host to check.
+ *
+ * @param[in] hashed The hashed value.
+ *
+ * @returns 1 if it matches, 0 otherwise.
+ */
+static int match_hashed_host(ssh_session session, const char *host,
+ const char *sourcehash) {
+ /* Openssh hash structure :
+ * |1|base64 encoded salt|base64 encoded hash
+ * hash is produced that way :
+ * hash := HMAC_SHA1(key=salt,data=host)
+ */
+ unsigned char buffer[256] = {0};
+ ssh_buffer salt;
+ ssh_buffer hash;
+ HMACCTX mac;
+ char *source;
+ char *b64hash;
+ int match;
+ unsigned int size;
+
+ enter_function();
+
+ if (strncmp(sourcehash, "|1|", 3) != 0) {
+ leave_function();
+ return 0;
+ }
+
+ source = strdup(sourcehash + 3);
+ if (source == NULL) {
+ leave_function();
+ return 0;
+ }
+
+ b64hash = strchr(source, '|');
+ if (b64hash == NULL) {
+ /* Invalid hash */
+ SAFE_FREE(source);
+ leave_function();
+ return 0;
+ }
+
+ *b64hash = '\0';
+ b64hash++;
+
+ salt = base64_to_bin(source);
+ if (salt == NULL) {
+ SAFE_FREE(source);
+ leave_function();
+ return 0;
+ }
+
+ hash = base64_to_bin(b64hash);
+ SAFE_FREE(source);
+ if (hash == NULL) {
+ ssh_buffer_free(salt);
+ leave_function();
+ return 0;
+ }
+
+ mac = hmac_init(ssh_buffer_get_begin(salt), ssh_buffer_get_len(salt), HMAC_SHA1);
+ if (mac == NULL) {
+ ssh_buffer_free(salt);
+ ssh_buffer_free(hash);
+ leave_function();
+ return 0;
+ }
+ size = sizeof(buffer);
+ hmac_update(mac, host, strlen(host));
+ hmac_final(mac, buffer, &size);
+
+ if (size == ssh_buffer_get_len(hash) &&
+ memcmp(buffer, ssh_buffer_get_begin(hash), size) == 0) {
+ match = 1;
+ } else {
+ match = 0;
+ }
+
+ ssh_buffer_free(salt);
+ ssh_buffer_free(hash);
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Matching a hashed host: %s match=%d", host, match);
+
+ leave_function();
+ return match;
+}
+
+/* How it's working :
+ * 1- we open the known host file and bitch if it doesn't exist
+ * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK:
+ * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK,
+ * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER)
+ * - there's no match : no change
+ */
+
+/**
+ * @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 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;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/keys.c b/src/keys.c
new file mode 100644
index 00000000..f0ebc155
--- /dev/null
+++ b/src/keys.c
@@ -0,0 +1,1497 @@
+/*
+ * keys.c - decoding a public key or signature and verifying them
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-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 <stdlib.h>
+#include <string.h>
+#ifdef HAVE_LIBCRYPTO
+#include <openssl/dsa.h>
+#include <openssl/rsa.h>
+#endif
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/server.h"
+#include "libssh/buffer.h"
+#include "libssh/agent.h"
+#include "libssh/session.h"
+#include "libssh/keys.h"
+#include "libssh/dh.h"
+#include "libssh/messages.h"
+#include "libssh/string.h"
+
+/**
+ * @addtogroup libssh_auth
+ *
+ * @{
+ */
+
+/* Public key decoding functions */
+const char *ssh_type_to_char(int type) {
+ switch (type) {
+ case SSH_KEYTYPE_DSS:
+ return "ssh-dss";
+ case SSH_KEYTYPE_RSA:
+ return "ssh-rsa";
+ case SSH_KEYTYPE_RSA1:
+ return "ssh-rsa1";
+ default:
+ return NULL;
+ }
+}
+
+int ssh_type_from_name(const char *name) {
+ if (strcmp(name, "rsa1") == 0) {
+ return SSH_KEYTYPE_RSA1;
+ } else if (strcmp(name, "rsa") == 0) {
+ return SSH_KEYTYPE_RSA;
+ } else if (strcmp(name, "dsa") == 0) {
+ return SSH_KEYTYPE_DSS;
+ } else if (strcmp(name, "ssh-rsa1") == 0) {
+ return SSH_KEYTYPE_RSA1;
+ } else if (strcmp(name, "ssh-rsa") == 0) {
+ return SSH_KEYTYPE_RSA;
+ } else if (strcmp(name, "ssh-dss") == 0) {
+ return SSH_KEYTYPE_DSS;
+ }
+
+ return -1;
+}
+
+ssh_public_key publickey_make_dss(ssh_session session, ssh_buffer buffer) {
+ ssh_string p = NULL;
+ ssh_string q = NULL;
+ ssh_string g = NULL;
+ ssh_string pubkey = NULL;
+ ssh_public_key key = NULL;
+
+ key = malloc(sizeof(struct ssh_public_key_struct));
+ if (key == NULL) {
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ key->type = SSH_KEYTYPE_DSS;
+ key->type_c = ssh_type_to_char(key->type);
+
+ p = buffer_get_ssh_string(buffer);
+ q = buffer_get_ssh_string(buffer);
+ g = buffer_get_ssh_string(buffer);
+ pubkey = buffer_get_ssh_string(buffer);
+
+ ssh_buffer_free(buffer); /* we don't need it anymore */
+
+ if (p == NULL || q == NULL || g == NULL || pubkey == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Invalid DSA public key");
+ goto error;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_build(&key->dsa_pub, NULL,
+ "(public-key(dsa(p %b)(q %b)(g %b)(y %b)))",
+ ssh_string_len(p), ssh_string_data(p),
+ ssh_string_len(q), ssh_string_data(q),
+ ssh_string_len(g), ssh_string_data(g),
+ ssh_string_len(pubkey), ssh_string_data(pubkey));
+ if (key->dsa_pub == NULL) {
+ goto error;
+ }
+#elif defined HAVE_LIBCRYPTO
+
+ key->dsa_pub = DSA_new();
+ if (key->dsa_pub == NULL) {
+ goto error;
+ }
+ key->dsa_pub->p = make_string_bn(p);
+ key->dsa_pub->q = make_string_bn(q);
+ key->dsa_pub->g = make_string_bn(g);
+ key->dsa_pub->pub_key = make_string_bn(pubkey);
+ if (key->dsa_pub->p == NULL ||
+ key->dsa_pub->q == NULL ||
+ key->dsa_pub->g == NULL ||
+ key->dsa_pub->pub_key == NULL) {
+ goto error;
+ }
+#endif /* HAVE_LIBCRYPTO */
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("p", ssh_string_data(p), ssh_string_len(p));
+ ssh_print_hexa("q", ssh_string_data(q), ssh_string_len(q));
+ ssh_print_hexa("g", ssh_string_data(g), ssh_string_len(g));
+#endif
+
+ ssh_string_burn(p);
+ ssh_string_free(p);
+ ssh_string_burn(q);
+ ssh_string_free(q);
+ ssh_string_burn(g);
+ ssh_string_free(g);
+ ssh_string_burn(pubkey);
+ ssh_string_free(pubkey);
+
+ return key;
+error:
+ ssh_string_burn(p);
+ ssh_string_free(p);
+ ssh_string_burn(q);
+ ssh_string_free(q);
+ ssh_string_burn(g);
+ ssh_string_free(g);
+ ssh_string_burn(pubkey);
+ ssh_string_free(pubkey);
+ publickey_free(key);
+
+ return NULL;
+}
+
+ssh_public_key publickey_make_rsa(ssh_session session, ssh_buffer buffer,
+ int type) {
+ ssh_string e = NULL;
+ ssh_string n = NULL;
+ ssh_public_key key = NULL;
+
+ key = malloc(sizeof(struct ssh_public_key_struct));
+ if (key == NULL) {
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ key->type = type;
+ key->type_c = ssh_type_to_char(key->type);
+
+ e = buffer_get_ssh_string(buffer);
+ n = buffer_get_ssh_string(buffer);
+
+ ssh_buffer_free(buffer); /* we don't need it anymore */
+
+ if(e == NULL || n == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Invalid RSA public key");
+ goto error;
+ }
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_build(&key->rsa_pub, NULL,
+ "(public-key(rsa(n %b)(e %b)))",
+ ssh_string_len(n), ssh_string_data(n),
+ ssh_string_len(e),ssh_string_data(e));
+ if (key->rsa_pub == NULL) {
+ goto error;
+ }
+#elif HAVE_LIBCRYPTO
+ key->rsa_pub = RSA_new();
+ if (key->rsa_pub == NULL) {
+ goto error;
+ }
+
+ key->rsa_pub->e = make_string_bn(e);
+ key->rsa_pub->n = make_string_bn(n);
+ if (key->rsa_pub->e == NULL ||
+ key->rsa_pub->n == NULL) {
+ goto error;
+ }
+#endif
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("e", ssh_string_data(e), ssh_string_len(e));
+ ssh_print_hexa("n", ssh_string_data(n), ssh_string_len(n));
+#endif
+
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ ssh_string_burn(n);
+ ssh_string_free(n);
+
+ return key;
+error:
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ ssh_string_burn(n);
+ ssh_string_free(n);
+ publickey_free(key);
+
+ return NULL;
+}
+
+void publickey_free(ssh_public_key key) {
+ if (key == NULL) {
+ return;
+ }
+
+ switch(key->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(key->dsa_pub);
+#elif HAVE_LIBCRYPTO
+ DSA_free(key->dsa_pub);
+#endif
+ break;
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(key->rsa_pub);
+#elif defined HAVE_LIBCRYPTO
+ RSA_free(key->rsa_pub);
+#endif
+ break;
+ default:
+ break;
+ }
+ SAFE_FREE(key);
+}
+
+ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) {
+ ssh_buffer tmpbuf = NULL;
+ ssh_string type_s = NULL;
+ char *type_c = NULL;
+ int type;
+
+ tmpbuf = ssh_buffer_new();
+ if (tmpbuf == NULL) {
+ return NULL;
+ }
+
+ if (buffer_add_data(tmpbuf, ssh_string_data(pubkey_s), ssh_string_len(pubkey_s)) < 0) {
+ goto error;
+ }
+
+ type_s = buffer_get_ssh_string(tmpbuf);
+ if (type_s == NULL) {
+ ssh_set_error(session,SSH_FATAL,"Invalid public key format");
+ goto error;
+ }
+
+ type_c = ssh_string_to_char(type_s);
+ ssh_string_free(type_s);
+ if (type_c == NULL) {
+ goto error;
+ }
+
+ type = ssh_type_from_name(type_c);
+ SAFE_FREE(type_c);
+
+ switch (type) {
+ case SSH_KEYTYPE_DSS:
+ return publickey_make_dss(session, tmpbuf);
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+ return publickey_make_rsa(session, tmpbuf, type);
+ }
+
+ ssh_set_error(session, SSH_FATAL, "Unknown public key protocol %s",
+ ssh_type_to_char(type));
+
+error:
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+}
+
+/**
+ * @brief Make a public_key object out of a private_key object.
+ *
+ * @param[in] prv The private key to generate the public key.
+ *
+ * @returns The generated public key, NULL on error.
+ *
+ * @see publickey_to_string()
+ */
+ssh_public_key publickey_from_privatekey(ssh_private_key prv) {
+ ssh_public_key key = NULL;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_t sexp;
+ const char *tmp = NULL;
+ size_t size;
+ ssh_string p = NULL;
+ ssh_string q = NULL;
+ ssh_string g = NULL;
+ ssh_string y = NULL;
+ ssh_string e = NULL;
+ ssh_string n = NULL;
+#endif /* HAVE_LIBGCRYPT */
+
+ key = malloc(sizeof(struct ssh_public_key_struct));
+ if (key == NULL) {
+ return NULL;
+ }
+
+ key->type = prv->type;
+ switch(key->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ sexp = gcry_sexp_find_token(prv->dsa_priv, "p", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ p = ssh_string_new(size);
+ if (p == NULL) {
+ goto error;
+ }
+ ssh_string_fill(p,(char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(prv->dsa_priv,"q",0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp,1,&size);
+ q = ssh_string_new(size);
+ if (q == NULL) {
+ goto error;
+ }
+ ssh_string_fill(q,(char *) tmp,size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(prv->dsa_priv, "g", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp,1,&size);
+ g = ssh_string_new(size);
+ if (g == NULL) {
+ goto error;
+ }
+ ssh_string_fill(g,(char *) tmp,size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(prv->dsa_priv,"y",0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp,1,&size);
+ y = ssh_string_new(size);
+ if (y == NULL) {
+ goto error;
+ }
+ ssh_string_fill(y,(char *) tmp,size);
+ gcry_sexp_release(sexp);
+
+ gcry_sexp_build(&key->dsa_pub, NULL,
+ "(public-key(dsa(p %b)(q %b)(g %b)(y %b)))",
+ ssh_string_len(p), ssh_string_data(p),
+ ssh_string_len(q), ssh_string_data(q),
+ ssh_string_len(g), ssh_string_data(g),
+ ssh_string_len(y), ssh_string_data(y));
+
+ ssh_string_burn(p);
+ ssh_string_free(p);
+ ssh_string_burn(q);
+ ssh_string_free(q);
+ ssh_string_burn(g);
+ ssh_string_free(g);
+ ssh_string_burn(y);
+ ssh_string_free(y);
+#elif defined HAVE_LIBCRYPTO
+ key->dsa_pub = DSA_new();
+ if (key->dsa_pub == NULL) {
+ goto error;
+ }
+ key->dsa_pub->p = BN_dup(prv->dsa_priv->p);
+ key->dsa_pub->q = BN_dup(prv->dsa_priv->q);
+ key->dsa_pub->g = BN_dup(prv->dsa_priv->g);
+ key->dsa_pub->pub_key = BN_dup(prv->dsa_priv->pub_key);
+ if (key->dsa_pub->p == NULL ||
+ key->dsa_pub->q == NULL ||
+ key->dsa_pub->g == NULL ||
+ key->dsa_pub->pub_key == NULL) {
+ goto error;
+ }
+#endif /* HAVE_LIBCRYPTO */
+ break;
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+#ifdef HAVE_LIBGCRYPT
+ sexp = gcry_sexp_find_token(prv->rsa_priv, "n", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ n = ssh_string_new(size);
+ if (n == NULL) {
+ goto error;
+ }
+ ssh_string_fill(n, (char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(prv->rsa_priv, "e", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ e = ssh_string_new(size);
+ if (e == NULL) {
+ goto error;
+ }
+ ssh_string_fill(e, (char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ gcry_sexp_build(&key->rsa_pub, NULL,
+ "(public-key(rsa(n %b)(e %b)))",
+ ssh_string_len(n), ssh_string_data(n),
+ ssh_string_len(e), ssh_string_data(e));
+ if (key->rsa_pub == NULL) {
+ goto error;
+ }
+
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ ssh_string_burn(n);
+ ssh_string_free(n);
+#elif defined HAVE_LIBCRYPTO
+ key->rsa_pub = RSA_new();
+ if (key->rsa_pub == NULL) {
+ goto error;
+ }
+ key->rsa_pub->e = BN_dup(prv->rsa_priv->e);
+ key->rsa_pub->n = BN_dup(prv->rsa_priv->n);
+ if (key->rsa_pub->e == NULL ||
+ key->rsa_pub->n == NULL) {
+ goto error;
+ }
+#endif
+ break;
+ }
+ key->type_c = ssh_type_to_char(prv->type);
+
+ return key;
+error:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(sexp);
+ ssh_string_burn(p);
+ ssh_string_free(p);
+ ssh_string_burn(q);
+ ssh_string_free(q);
+ ssh_string_burn(g);
+ ssh_string_free(g);
+ ssh_string_burn(y);
+ ssh_string_free(y);
+
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ ssh_string_burn(n);
+ ssh_string_free(n);
+#endif
+ publickey_free(key);
+
+ return NULL;
+}
+
+#ifdef HAVE_LIBGCRYPT
+static int dsa_public_to_string(gcry_sexp_t key, ssh_buffer buffer) {
+#elif defined HAVE_LIBCRYPTO
+static int dsa_public_to_string(DSA *key, ssh_buffer buffer) {
+#endif
+ ssh_string p = NULL;
+ ssh_string q = NULL;
+ ssh_string g = NULL;
+ ssh_string n = NULL;
+
+ int rc = -1;
+
+#ifdef HAVE_LIBGCRYPT
+ const char *tmp = NULL;
+ size_t size;
+ gcry_sexp_t sexp;
+
+ sexp = gcry_sexp_find_token(key, "p", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ p = ssh_string_new(size);
+ if (p == NULL) {
+ goto error;
+ }
+ ssh_string_fill(p, (char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(key, "q", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ q = ssh_string_new(size);
+ if (q == NULL) {
+ goto error;
+ }
+ ssh_string_fill(q, (char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(key, "g", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ g = ssh_string_new(size);
+ if (g == NULL) {
+ goto error;
+ }
+ ssh_string_fill(g, (char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(key, "y", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ n = ssh_string_new(size);
+ if (n == NULL) {
+ goto error;
+ }
+ ssh_string_fill(n, (char *) tmp, size);
+
+#elif defined HAVE_LIBCRYPTO
+ p = make_bignum_string(key->p);
+ q = make_bignum_string(key->q);
+ g = make_bignum_string(key->g);
+ n = make_bignum_string(key->pub_key);
+ if (p == NULL || q == NULL || g == NULL || n == NULL) {
+ goto error;
+ }
+#endif /* HAVE_LIBCRYPTO */
+ if (buffer_add_ssh_string(buffer, p) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(buffer, q) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(buffer, g) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(buffer, n) < 0) {
+ goto error;
+ }
+
+ rc = 0;
+error:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(sexp);
+#endif
+
+ ssh_string_burn(p);
+ ssh_string_free(p);
+ ssh_string_burn(q);
+ ssh_string_free(q);
+ ssh_string_burn(g);
+ ssh_string_free(g);
+ ssh_string_burn(n);
+ ssh_string_free(n);
+
+ return rc;
+}
+
+#ifdef HAVE_LIBGCRYPT
+static int rsa_public_to_string(gcry_sexp_t key, ssh_buffer buffer) {
+#elif defined HAVE_LIBCRYPTO
+static int rsa_public_to_string(RSA *key, ssh_buffer buffer) {
+#endif
+
+ ssh_string e = NULL;
+ ssh_string n = NULL;
+
+ int rc = -1;
+
+#ifdef HAVE_LIBGCRYPT
+ const char *tmp;
+ size_t size;
+ gcry_sexp_t sexp;
+
+ sexp = gcry_sexp_find_token(key, "n", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ n = ssh_string_new(size);
+ if (n == NULL) {
+ goto error;
+ }
+ ssh_string_fill(n, (char *) tmp, size);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(key, "e", 0);
+ if (sexp == NULL) {
+ goto error;
+ }
+ tmp = gcry_sexp_nth_data(sexp, 1, &size);
+ e = ssh_string_new(size);
+ if (e == NULL) {
+ goto error;
+ }
+ ssh_string_fill(e, (char *) tmp, size);
+
+#elif defined HAVE_LIBCRYPTO
+ e = make_bignum_string(key->e);
+ n = make_bignum_string(key->n);
+ if (e == NULL || n == NULL) {
+ goto error;
+ }
+#endif
+
+ if (buffer_add_ssh_string(buffer, e) < 0) {
+ goto error;
+ }
+ if (buffer_add_ssh_string(buffer, n) < 0) {
+ goto error;
+ }
+
+ rc = 0;
+error:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(sexp);
+#endif
+
+ ssh_string_burn(e);
+ ssh_string_free(e);
+ ssh_string_burn(n);
+ ssh_string_free(n);
+
+ return rc;
+}
+
+/**
+ * @brief Convert a public_key object into a a SSH string.
+ *
+ * @param[in] key The public key to convert.
+ *
+ * @returns An allocated SSH String containing the public key, NULL
+ * on error.
+ *
+ * @see string_free()
+ */
+ssh_string publickey_to_string(ssh_public_key key) {
+ ssh_string type = NULL;
+ ssh_string ret = NULL;
+ ssh_buffer buf = NULL;
+
+ buf = ssh_buffer_new();
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ type = ssh_string_from_char(key->type_c);
+ if (type == NULL) {
+ goto error;
+ }
+
+ if (buffer_add_ssh_string(buf, type) < 0) {
+ goto error;
+ }
+
+ switch (key->type) {
+ case SSH_KEYTYPE_DSS:
+ if (dsa_public_to_string(key->dsa_pub, buf) < 0) {
+ goto error;
+ }
+ break;
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+ if (rsa_public_to_string(key->rsa_pub, buf) < 0) {
+ goto error;
+ }
+ break;
+ }
+
+ ret = ssh_string_new(ssh_buffer_get_len(buf));
+ if (ret == NULL) {
+ goto error;
+ }
+
+ ssh_string_fill(ret, ssh_buffer_get_begin(buf), ssh_buffer_get_len(buf));
+error:
+ ssh_buffer_free(buf);
+ ssh_string_free(type);
+
+ return ret;
+}
+
+/* Signature decoding functions */
+static ssh_string signature_to_string(SIGNATURE *sign) {
+ unsigned char buffer[40] = {0};
+ ssh_buffer tmpbuf = NULL;
+ ssh_string str = NULL;
+ ssh_string tmp = NULL;
+ ssh_string rs = NULL;
+ int rc = -1;
+#ifdef HAVE_LIBGCRYPT
+ const char *r = NULL;
+ const char *s = NULL;
+ gcry_sexp_t sexp;
+ size_t size = 0;
+#elif defined HAVE_LIBCRYPTO
+ ssh_string r = NULL;
+ ssh_string s = NULL;
+#endif
+
+ tmpbuf = ssh_buffer_new();
+ if (tmpbuf == NULL) {
+ return NULL;
+ }
+
+ tmp = ssh_string_from_char(ssh_type_to_char(sign->type));
+ if (tmp == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ if (buffer_add_ssh_string(tmpbuf, tmp) < 0) {
+ ssh_buffer_free(tmpbuf);
+ ssh_string_free(tmp);
+ return NULL;
+ }
+ ssh_string_free(tmp);
+
+ switch(sign->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ sexp = gcry_sexp_find_token(sign->dsa_sign, "r", 0);
+ if (sexp == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ r = gcry_sexp_nth_data(sexp, 1, &size);
+ if (*r == 0) { /* libgcrypt put 0 when first bit is set */
+ size--;
+ r++;
+ }
+ memcpy(buffer, r + size - 20, 20);
+ gcry_sexp_release(sexp);
+
+ sexp = gcry_sexp_find_token(sign->dsa_sign, "s", 0);
+ if (sexp == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ s = gcry_sexp_nth_data(sexp,1,&size);
+ if (*s == 0) {
+ size--;
+ s++;
+ }
+ memcpy(buffer+ 20, s + size - 20, 20);
+ gcry_sexp_release(sexp);
+#elif defined HAVE_LIBCRYPTO
+ r = make_bignum_string(sign->dsa_sign->r);
+ if (r == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ s = make_bignum_string(sign->dsa_sign->s);
+ if (s == NULL) {
+ ssh_buffer_free(tmpbuf);
+ ssh_string_free(r);
+ return NULL;
+ }
+
+ memcpy(buffer, (char *)ssh_string_data(r) + ssh_string_len(r) - 20, 20);
+ memcpy(buffer + 20, (char *)ssh_string_data(s) + ssh_string_len(s) - 20, 20);
+
+ ssh_string_free(r);
+ ssh_string_free(s);
+#endif /* HAVE_LIBCRYPTO */
+ rs = ssh_string_new(40);
+ if (rs == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+
+ ssh_string_fill(rs, buffer, 40);
+ rc = buffer_add_ssh_string(tmpbuf, rs);
+ ssh_string_free(rs);
+ if (rc < 0) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+
+ break;
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+#ifdef HAVE_LIBGCRYPT
+ sexp = gcry_sexp_find_token(sign->rsa_sign, "s", 0);
+ if (sexp == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ s = gcry_sexp_nth_data(sexp,1,&size);
+ if (*s == 0) {
+ size--;
+ s++;
+ }
+ rs = ssh_string_new(size);
+ if (rs == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+
+ ssh_string_fill(rs, (char *) s, size);
+ rc = buffer_add_ssh_string(tmpbuf, rs);
+ gcry_sexp_release(sexp);
+ ssh_string_free(rs);
+ if (rc < 0) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ if (buffer_add_ssh_string(tmpbuf,sign->rsa_sign) < 0) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+#endif
+ break;
+ }
+
+ str = ssh_string_new(ssh_buffer_get_len(tmpbuf));
+ if (str == NULL) {
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ ssh_string_fill(str, ssh_buffer_get_begin(tmpbuf), ssh_buffer_get_len(tmpbuf));
+ ssh_buffer_free(tmpbuf);
+
+ return str;
+}
+
+/* TODO : split this function in two so it becomes smaller */
+SIGNATURE *signature_from_string(ssh_session session, ssh_string signature,
+ ssh_public_key pubkey, int needed_type) {
+ SIGNATURE *sign = NULL;
+ ssh_buffer tmpbuf = NULL;
+ ssh_string rs = NULL;
+ ssh_string type_s = NULL;
+ ssh_string e = NULL;
+ char *type_c = NULL;
+ int type;
+ int len;
+ int rsalen;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_t sig;
+#elif defined HAVE_LIBCRYPTO
+ DSA_SIG *sig = NULL;
+ ssh_string r = NULL;
+ ssh_string s = NULL;
+#endif
+
+ sign = malloc(sizeof(SIGNATURE));
+ if (sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ return NULL;
+ }
+
+ tmpbuf = ssh_buffer_new();
+ if (tmpbuf == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ signature_free(sign);
+ return NULL;
+ }
+
+ if (buffer_add_data(tmpbuf, ssh_string_data(signature), ssh_string_len(signature)) < 0) {
+ signature_free(sign);
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+
+ type_s = buffer_get_ssh_string(tmpbuf);
+ if (type_s == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Invalid signature packet");
+ signature_free(sign);
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+
+ type_c = ssh_string_to_char(type_s);
+ ssh_string_free(type_s);
+ if (type_c == NULL) {
+ signature_free(sign);
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+ type = ssh_type_from_name(type_c);
+ SAFE_FREE(type_c);
+
+ if (needed_type != type) {
+ ssh_set_error(session, SSH_FATAL, "Invalid signature type: %s",
+ ssh_type_to_char(type));
+ signature_free(sign);
+ ssh_buffer_free(tmpbuf);
+ return NULL;
+ }
+
+ switch(needed_type) {
+ case SSH_KEYTYPE_DSS:
+ rs = buffer_get_ssh_string(tmpbuf);
+ ssh_buffer_free(tmpbuf);
+
+ /* 40 is the dual signature blob len. */
+ if (rs == NULL || ssh_string_len(rs) != 40) {
+ ssh_string_free(rs);
+ signature_free(sign);
+ return NULL;
+ }
+
+ /* we make use of strings (because we have all-made functions to convert
+ * them to bignums (ou pas ;) */
+#ifdef HAVE_LIBGCRYPT
+ if (gcry_sexp_build(&sig, NULL, "(sig-val(dsa(r %b)(s %b)))",
+ 20 ,ssh_string_data(rs), 20,(unsigned char *)ssh_string_data(rs) + 20)) {
+ ssh_string_free(rs);
+ signature_free(sign);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ r = ssh_string_new(20);
+ s = ssh_string_new(20);
+ if (r == NULL || s == NULL) {
+ ssh_string_free(r);
+ ssh_string_free(s);
+ ssh_string_free(rs);
+ signature_free(sign);
+ return NULL;
+ }
+
+ ssh_string_fill(r, ssh_string_data(rs), 20);
+ ssh_string_fill(s, (char *)ssh_string_data(rs) + 20, 20);
+
+ sig = DSA_SIG_new();
+ if (sig == NULL) {
+ ssh_string_free(r);
+ ssh_string_free(s);
+ ssh_string_free(rs);
+ signature_free(sign);
+ return NULL;
+ }
+ sig->r = make_string_bn(r); /* is that really portable ? Openssh's hack isn't better */
+ sig->s = make_string_bn(s);
+ ssh_string_free(r);
+ ssh_string_free(s);
+
+ if (sig->r == NULL || sig->s == NULL) {
+ ssh_string_free(rs);
+ DSA_SIG_free(sig);
+ signature_free(sign);
+ return NULL;
+ }
+#endif
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("r", ssh_string_data(rs), 20);
+ ssh_print_hexa("s", (const unsigned char *)ssh_string_data(rs) + 20, 20);
+#endif
+ ssh_string_free(rs);
+
+ sign->type = SSH_KEYTYPE_DSS;
+ sign->dsa_sign = sig;
+
+ return sign;
+ case SSH_KEYTYPE_RSA:
+ e = buffer_get_ssh_string(tmpbuf);
+ ssh_buffer_free(tmpbuf);
+ if (e == NULL) {
+ signature_free(sign);
+ return NULL;
+ }
+ len = ssh_string_len(e);
+#ifdef HAVE_LIBGCRYPT
+ rsalen = (gcry_pk_get_nbits(pubkey->rsa_pub) + 7) / 8;
+#elif defined HAVE_LIBCRYPTO
+ rsalen = RSA_size(pubkey->rsa_pub);
+#endif
+ if (len > rsalen) {
+ ssh_string_free(e);
+ signature_free(sign);
+ ssh_set_error(session, SSH_FATAL, "Signature too big! %d instead of %d",
+ len, rsalen);
+ return NULL;
+ }
+
+ if (len < rsalen) {
+ ssh_log(session, SSH_LOG_RARE, "RSA signature len %d < %d",
+ len, rsalen);
+ }
+ sign->type = SSH_KEYTYPE_RSA;
+#ifdef HAVE_LIBGCRYPT
+ if (gcry_sexp_build(&sig, NULL, "(sig-val(rsa(s %b)))",
+ ssh_string_len(e), ssh_string_data(e))) {
+ signature_free(sign);
+ ssh_string_free(e);
+ return NULL;
+ }
+
+ sign->rsa_sign = sig;
+#elif defined HAVE_LIBCRYPTO
+ sign->rsa_sign = e;
+#endif
+
+#ifdef DEBUG_CRYPTO
+ ssh_log(session, SSH_LOG_FUNCTIONS, "len e: %d", len);
+ ssh_print_hexa("RSA signature", ssh_string_data(e), len);
+#endif
+
+#ifdef HAVE_LIBGCRYPT
+ ssh_string_free(e);
+#endif
+
+ return sign;
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+void signature_free(SIGNATURE *sign) {
+ if (sign == NULL) {
+ return;
+ }
+
+ switch(sign->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(sign->dsa_sign);
+#elif defined HAVE_LIBCRYPTO
+ DSA_SIG_free(sign->dsa_sign);
+#endif
+ break;
+ case SSH_KEYTYPE_RSA:
+ case SSH_KEYTYPE_RSA1:
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(sign->rsa_sign);
+#elif defined HAVE_LIBCRYPTO
+ SAFE_FREE(sign->rsa_sign);
+#endif
+ break;
+ default:
+ /* FIXME Passing NULL segfaults */
+#if 0
+ ssh_log(NULL, SSH_LOG_RARE, "Freeing a signature with no type!\n"); */
+#endif
+ break;
+ }
+ SAFE_FREE(sign);
+}
+
+#ifdef HAVE_LIBCRYPTO
+/*
+ * Maybe the missing function from libcrypto
+ *
+ * I think now, maybe it's a bad idea to name it has it should have be
+ * named in libcrypto
+ */
+static ssh_string RSA_do_sign(const unsigned char *payload, int len, RSA *privkey) {
+ ssh_string sign = NULL;
+ unsigned char *buffer = NULL;
+ unsigned int size;
+
+ buffer = malloc(RSA_size(privkey));
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ if (RSA_sign(NID_sha1, payload, len, buffer, &size, privkey) == 0) {
+ SAFE_FREE(buffer);
+ return NULL;
+ }
+
+ sign = ssh_string_new(size);
+ if (sign == NULL) {
+ SAFE_FREE(buffer);
+ return NULL;
+ }
+
+ ssh_string_fill(sign, buffer, size);
+ SAFE_FREE(buffer);
+
+ return sign;
+}
+#endif
+
+#ifndef _WIN32
+ssh_string ssh_do_sign_with_agent(ssh_session session,
+ struct ssh_buffer_struct *buf, struct ssh_public_key_struct *publickey) {
+ struct ssh_buffer_struct *sigbuf = NULL;
+ struct ssh_string_struct *signature = NULL;
+ struct ssh_string_struct *session_id = NULL;
+ struct ssh_crypto_struct *crypto = NULL;
+
+ if (session->current_crypto) {
+ crypto = session->current_crypto;
+ } else {
+ crypto = session->next_crypto;
+ }
+
+ /* prepend session identifier */
+ session_id = ssh_string_new(SHA_DIGEST_LEN);
+ if (session_id == NULL) {
+ return NULL;
+ }
+ ssh_string_fill(session_id, crypto->session_id, SHA_DIGEST_LEN);
+
+ sigbuf = ssh_buffer_new();
+ if (sigbuf == NULL) {
+ ssh_string_free(session_id);
+ return NULL;
+ }
+
+ if (buffer_add_ssh_string(sigbuf, session_id) < 0) {
+ ssh_buffer_free(sigbuf);
+ ssh_string_free(session_id);
+ return NULL;
+ }
+ ssh_string_free(session_id);
+
+ /* append out buffer */
+ if (buffer_add_buffer(sigbuf, buf) < 0) {
+ ssh_buffer_free(sigbuf);
+ return NULL;
+ }
+
+ /* create signature */
+ signature = agent_sign_data(session, sigbuf, publickey);
+
+ ssh_buffer_free(sigbuf);
+
+ return signature;
+}
+#endif /* _WIN32 */
+
+/*
+ * This function concats in a buffer the values needed to do a signature
+ * verification. */
+ssh_buffer ssh_userauth_build_digest(ssh_session session, ssh_message msg, char *service) {
+/*
+ The value of 'signature' is a signature by the corresponding private
+ key over the following data, in the following order:
+
+ string session identifier
+ byte SSH_MSG_USERAUTH_REQUEST
+ string user name
+ string service name
+ string "publickey"
+ boolean TRUE
+ string public key algorithm name
+ string public key to be used for authentication
+*/
+ struct ssh_crypto_struct *crypto = session->current_crypto ? session->current_crypto :
+ session->next_crypto;
+ ssh_buffer buffer = NULL;
+ ssh_string session_id = NULL;
+ uint8_t type = SSH2_MSG_USERAUTH_REQUEST;
+ ssh_string username = ssh_string_from_char(msg->auth_request.username);
+ ssh_string servicename = ssh_string_from_char(service);
+ ssh_string method = ssh_string_from_char("publickey");
+ uint8_t has_sign = 1;
+ ssh_string algo = ssh_string_from_char(msg->auth_request.public_key->type_c);
+ ssh_string publickey = publickey_to_string(msg->auth_request.public_key);
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ goto error;
+ }
+ session_id = ssh_string_new(SHA_DIGEST_LEN);
+ if (session_id == NULL) {
+ ssh_buffer_free(buffer);
+ buffer = NULL;
+ goto error;
+ }
+ ssh_string_fill(session_id, crypto->session_id, SHA_DIGEST_LEN);
+
+ if(buffer_add_ssh_string(buffer, session_id) < 0 ||
+ buffer_add_u8(buffer, type) < 0 ||
+ buffer_add_ssh_string(buffer, username) < 0 ||
+ buffer_add_ssh_string(buffer, servicename) < 0 ||
+ buffer_add_ssh_string(buffer, method) < 0 ||
+ buffer_add_u8(buffer, has_sign) < 0 ||
+ buffer_add_ssh_string(buffer, algo) < 0 ||
+ buffer_add_ssh_string(buffer, publickey) < 0) {
+ ssh_buffer_free(buffer);
+ buffer = NULL;
+ goto error;
+ }
+
+error:
+ if(session_id) ssh_string_free(session_id);
+ if(username) ssh_string_free(username);
+ if(servicename) ssh_string_free(servicename);
+ if(method) ssh_string_free(method);
+ if(algo) ssh_string_free(algo);
+ if(publickey) ssh_string_free(publickey);
+ return buffer;
+}
+
+/*
+ * This function signs the session id (known as H) as a string then
+ * the content of sigbuf */
+ssh_string ssh_do_sign(ssh_session session, ssh_buffer sigbuf,
+ ssh_private_key privatekey) {
+ struct ssh_crypto_struct *crypto = session->current_crypto ? session->current_crypto :
+ session->next_crypto;
+ unsigned char hash[SHA_DIGEST_LEN + 1] = {0};
+ ssh_string session_str = NULL;
+ ssh_string signature = NULL;
+ SIGNATURE *sign = NULL;
+ SHACTX ctx = NULL;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_t gcryhash;
+#endif
+
+ session_str = ssh_string_new(SHA_DIGEST_LEN);
+ if (session_str == NULL) {
+ return NULL;
+ }
+ ssh_string_fill(session_str, crypto->session_id, SHA_DIGEST_LEN);
+
+ ctx = sha1_init();
+ if (ctx == NULL) {
+ ssh_string_free(session_str);
+ return NULL;
+ }
+
+ sha1_update(ctx, session_str, ssh_string_len(session_str) + 4);
+ ssh_string_free(session_str);
+ sha1_update(ctx, ssh_buffer_get_begin(sigbuf), ssh_buffer_get_len(sigbuf));
+ sha1_final(hash + 1,ctx);
+ hash[0] = 0;
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Hash being signed with dsa", hash + 1, SHA_DIGEST_LEN);
+#endif
+
+ sign = malloc(sizeof(SIGNATURE));
+ if (sign == NULL) {
+ return NULL;
+ }
+
+ switch(privatekey->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ if (gcry_sexp_build(&gcryhash, NULL, "%b", SHA_DIGEST_LEN + 1, hash) ||
+ gcry_pk_sign(&sign->dsa_sign, gcryhash, privatekey->dsa_priv)) {
+ ssh_set_error(session, SSH_FATAL, "Signing: libcrypt error");
+ gcry_sexp_release(gcryhash);
+ signature_free(sign);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ sign->dsa_sign = DSA_do_sign(hash + 1, SHA_DIGEST_LEN,
+ privatekey->dsa_priv);
+ if (sign->dsa_sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Signing: openssl error");
+ signature_free(sign);
+ return NULL;
+ }
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("r", sign->dsa_sign->r);
+ ssh_print_bignum("s", sign->dsa_sign->s);
+#endif
+#endif /* HAVE_LIBCRYPTO */
+ sign->rsa_sign = NULL;
+ break;
+ case SSH_KEYTYPE_RSA:
+#ifdef HAVE_LIBGCRYPT
+ if (gcry_sexp_build(&gcryhash, NULL, "(data(flags pkcs1)(hash sha1 %b))",
+ SHA_DIGEST_LEN, hash + 1) ||
+ gcry_pk_sign(&sign->rsa_sign, gcryhash, privatekey->rsa_priv)) {
+ ssh_set_error(session, SSH_FATAL, "Signing: libcrypt error");
+ gcry_sexp_release(gcryhash);
+ signature_free(sign);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ sign->rsa_sign = RSA_do_sign(hash + 1, SHA_DIGEST_LEN,
+ privatekey->rsa_priv);
+ if (sign->rsa_sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Signing: openssl error");
+ signature_free(sign);
+ return NULL;
+ }
+#endif
+ sign->dsa_sign = NULL;
+ break;
+ default:
+ return NULL;
+ }
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(gcryhash);
+#endif
+
+ sign->type = privatekey->type;
+
+ signature = signature_to_string(sign);
+ signature_free(sign);
+
+ return signature;
+}
+
+ssh_string ssh_encrypt_rsa1(ssh_session session, ssh_string data, ssh_public_key key) {
+ ssh_string str = NULL;
+ size_t len = ssh_string_len(data);
+ size_t size = 0;
+#ifdef HAVE_LIBGCRYPT
+ const char *tmp = NULL;
+ gcry_sexp_t ret_sexp;
+ gcry_sexp_t data_sexp;
+
+ if (gcry_sexp_build(&data_sexp, NULL, "(data(flags pkcs1)(value %b))",
+ len, ssh_string_data(data))) {
+ ssh_set_error(session, SSH_FATAL, "RSA1 encrypt: libgcrypt error");
+ return NULL;
+ }
+ if (gcry_pk_encrypt(&ret_sexp, data_sexp, key->rsa_pub)) {
+ gcry_sexp_release(data_sexp);
+ ssh_set_error(session, SSH_FATAL, "RSA1 encrypt: libgcrypt error");
+ return NULL;
+ }
+
+ gcry_sexp_release(data_sexp);
+
+ data_sexp = gcry_sexp_find_token(ret_sexp, "a", 0);
+ if (data_sexp == NULL) {
+ ssh_set_error(session, SSH_FATAL, "RSA1 encrypt: libgcrypt error");
+ gcry_sexp_release(ret_sexp);
+ return NULL;
+ }
+ tmp = gcry_sexp_nth_data(data_sexp, 1, &size);
+ if (*tmp == 0) {
+ size--;
+ tmp++;
+ }
+
+ str = ssh_string_new(size);
+ if (str == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ gcry_sexp_release(data_sexp);
+ gcry_sexp_release(ret_sexp);
+ return NULL;
+ }
+ ssh_string_fill(str, tmp, size);
+
+ gcry_sexp_release(data_sexp);
+ gcry_sexp_release(ret_sexp);
+#elif defined HAVE_LIBCRYPTO
+ size = RSA_size(key->rsa_pub);
+
+ str = ssh_string_new(size);
+ if (str == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ return NULL;
+ }
+
+ if (RSA_public_encrypt(len, ssh_string_data(data), ssh_string_data(str), key->rsa_pub,
+ RSA_PKCS1_PADDING) < 0) {
+ ssh_string_free(str);
+ return NULL;
+ }
+#endif
+
+ return str;
+}
+
+
+/* this function signs the session id */
+ssh_string ssh_sign_session_id(ssh_session session, ssh_private_key privatekey) {
+ struct ssh_crypto_struct *crypto=session->current_crypto ? session->current_crypto :
+ session->next_crypto;
+ unsigned char hash[SHA_DIGEST_LEN + 1] = {0};
+ ssh_string signature = NULL;
+ SIGNATURE *sign = NULL;
+ SHACTX ctx = NULL;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_t data_sexp;
+#endif
+
+ ctx = sha1_init();
+ if (ctx == NULL) {
+ return NULL;
+ }
+ sha1_update(ctx,crypto->session_id,SHA_DIGEST_LEN);
+ sha1_final(hash + 1,ctx);
+ hash[0] = 0;
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Hash being signed with dsa",hash+1,SHA_DIGEST_LEN);
+#endif
+
+ sign = malloc(sizeof(SIGNATURE));
+ if (sign == NULL) {
+ return NULL;
+ }
+
+ switch(privatekey->type) {
+ case SSH_KEYTYPE_DSS:
+#ifdef HAVE_LIBGCRYPT
+ if (gcry_sexp_build(&data_sexp, NULL, "%b", SHA_DIGEST_LEN + 1, hash) ||
+ gcry_pk_sign(&sign->dsa_sign, data_sexp, privatekey->dsa_priv)) {
+ ssh_set_error(session, SSH_FATAL, "Signing: libgcrypt error");
+ gcry_sexp_release(data_sexp);
+ signature_free(sign);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ sign->dsa_sign = DSA_do_sign(hash + 1, SHA_DIGEST_LEN,
+ privatekey->dsa_priv);
+ if (sign->dsa_sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Signing: openssl error");
+ signature_free(sign);
+ return NULL;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_bignum("r",sign->dsa_sign->r);
+ ssh_print_bignum("s",sign->dsa_sign->s);
+#endif
+
+#endif /* HAVE_LIBCRYPTO */
+ sign->rsa_sign = NULL;
+ break;
+ case SSH_KEYTYPE_RSA:
+#ifdef HAVE_LIBGCRYPT
+ if (gcry_sexp_build(&data_sexp, NULL, "(data(flags pkcs1)(hash sha1 %b))",
+ SHA_DIGEST_LEN, hash + 1) ||
+ gcry_pk_sign(&sign->rsa_sign, data_sexp, privatekey->rsa_priv)) {
+ ssh_set_error(session, SSH_FATAL, "Signing: libgcrypt error");
+ gcry_sexp_release(data_sexp);
+ signature_free(sign);
+ return NULL;
+ }
+#elif defined HAVE_LIBCRYPTO
+ sign->rsa_sign = RSA_do_sign(hash + 1, SHA_DIGEST_LEN,
+ privatekey->rsa_priv);
+ if (sign->rsa_sign == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Signing: openssl error");
+ signature_free(sign);
+ return NULL;
+ }
+#endif
+ sign->dsa_sign = NULL;
+ break;
+ default:
+ return NULL;
+ }
+
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(data_sexp);
+#endif
+
+ sign->type = privatekey->type;
+
+ signature = signature_to_string(sign);
+ signature_free(sign);
+
+ return signature;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/legacy.c b/src/legacy.c
new file mode 100644
index 00000000..ea97a31f
--- /dev/null
+++ b/src/legacy.c
@@ -0,0 +1,237 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2010 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.
+ */
+
+/** functions in that file are wrappers to the newly named functions. All
+ * of them are depreciated, but these wrapper will avoid breaking backward
+ * compatibility
+ */
+
+#include <libssh/libssh.h>
+#include <libssh/server.h>
+#include <libssh/buffer.h>
+
+void buffer_free(ssh_buffer buffer){
+ ssh_buffer_free(buffer);
+}
+void *buffer_get(ssh_buffer buffer){
+ return ssh_buffer_get_begin(buffer);
+}
+uint32_t buffer_get_len(ssh_buffer buffer){
+ return ssh_buffer_get_len(buffer);
+}
+ssh_buffer buffer_new(void){
+ return ssh_buffer_new();
+}
+
+ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms){
+ return ssh_channel_accept_x11(channel, timeout_ms);
+}
+
+int channel_change_pty_size(ssh_channel channel,int cols,int rows){
+ return ssh_channel_change_pty_size(channel,cols,rows);
+}
+
+ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){
+ return ssh_forward_accept(session,timeout_ms);
+}
+
+int channel_close(ssh_channel channel){
+ return ssh_channel_close(channel);
+}
+
+int channel_forward_cancel(ssh_session session, const char *address, int port){
+ return ssh_forward_cancel(session, address, port);
+}
+
+int channel_forward_listen(ssh_session session, const char *address,
+ int port, int *bound_port){
+ return ssh_forward_listen(session, address, port, bound_port);
+}
+
+void channel_free(ssh_channel channel){
+ ssh_channel_free(channel);
+}
+
+int channel_get_exit_status(ssh_channel channel){
+ return ssh_channel_get_exit_status(channel);
+}
+
+ssh_session channel_get_session(ssh_channel channel){
+ return ssh_channel_get_session(channel);
+}
+
+int channel_is_closed(ssh_channel channel){
+ return ssh_channel_is_closed(channel);
+}
+
+int channel_is_eof(ssh_channel channel){
+ return ssh_channel_is_eof(channel);
+}
+
+int channel_is_open(ssh_channel channel){
+ return ssh_channel_is_open(channel);
+}
+
+ssh_channel channel_new(ssh_session session){
+ return ssh_channel_new(session);
+}
+
+int channel_open_forward(ssh_channel channel, const char *remotehost,
+ int remoteport, const char *sourcehost, int localport){
+ return ssh_channel_open_forward(channel, remotehost, remoteport,
+ sourcehost,localport);
+}
+
+int channel_open_session(ssh_channel channel){
+ return ssh_channel_open_session(channel);
+}
+
+int channel_poll(ssh_channel channel, int is_stderr){
+ return ssh_channel_poll(channel, is_stderr);
+}
+
+int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr){
+ return ssh_channel_read(channel, dest, count, is_stderr);
+}
+
+/*
+ * This function will completely be depreciated. The old implementation was not
+ * renamed.
+ * int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count,
+ * int is_stderr);
+ */
+
+int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count,
+ int is_stderr){
+ return ssh_channel_read_nonblocking(channel, dest, count, is_stderr);
+}
+
+int channel_request_env(ssh_channel channel, const char *name, const char *value){
+ return ssh_channel_request_env(channel, name, value);
+}
+
+int channel_request_exec(ssh_channel channel, const char *cmd){
+ return ssh_channel_request_exec(channel, cmd);
+}
+
+int channel_request_pty(ssh_channel channel){
+ return ssh_channel_request_pty(channel);
+}
+
+int channel_request_pty_size(ssh_channel channel, const char *term,
+ int cols, int rows){
+ return ssh_channel_request_pty_size(channel, term, cols, rows);
+}
+
+int channel_request_shell(ssh_channel channel){
+ return ssh_channel_request_shell(channel);
+}
+
+int channel_request_send_signal(ssh_channel channel, const char *signum){
+ return ssh_channel_request_send_signal(channel, signum);
+}
+
+int channel_request_sftp(ssh_channel channel){
+ return ssh_channel_request_sftp(channel);
+}
+
+int channel_request_subsystem(ssh_channel channel, const char *subsystem){
+ return ssh_channel_request_subsystem(channel, subsystem);
+}
+
+int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol,
+ const char *cookie, int screen_number){
+ return ssh_channel_request_x11(channel, single_connection, protocol, cookie,
+ screen_number);
+}
+
+int channel_send_eof(ssh_channel channel){
+ return ssh_channel_send_eof(channel);
+}
+
+int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct
+ timeval * timeout){
+ return ssh_channel_select(readchans, writechans, exceptchans, timeout);
+}
+
+void channel_set_blocking(ssh_channel channel, int blocking){
+ ssh_channel_set_blocking(channel, blocking);
+}
+
+int channel_write(ssh_channel channel, const void *data, uint32_t len){
+ return ssh_channel_write(channel, data, len);
+}
+
+/*
+ * These functions have to be wrapped around the pki.c functions.
+
+void privatekey_free(ssh_private_key prv);
+ssh_private_key privatekey_from_file(ssh_session session, const char *filename,
+ int type, const char *passphrase);
+void publickey_free(ssh_public_key key);
+int ssh_publickey_to_file(ssh_session session, const char *file,
+ ssh_string pubkey, int type);
+ssh_string publickey_from_file(ssh_session session, const char *filename,
+ int *type);
+ssh_public_key publickey_from_privatekey(ssh_private_key prv);
+ssh_string publickey_to_string(ssh_public_key key);
+ *
+ */
+
+void string_burn(ssh_string str){
+ ssh_string_burn(str);
+}
+
+ssh_string string_copy(ssh_string str){
+ return ssh_string_copy(str);
+}
+
+void *string_data(ssh_string str){
+ return ssh_string_data(str);
+}
+
+int string_fill(ssh_string str, const void *data, size_t len){
+ return ssh_string_fill(str,data,len);
+}
+
+void string_free(ssh_string str){
+ ssh_string_free(str);
+}
+
+ssh_string string_from_char(const char *what){
+ return ssh_string_from_char(what);
+}
+
+size_t string_len(ssh_string str){
+ return ssh_string_len(str);
+}
+
+ssh_string string_new(size_t size){
+ return ssh_string_new(size);
+}
+
+char *string_to_char(ssh_string str){
+ return ssh_string_to_char(str);
+}
+
+int ssh_accept(ssh_session session) {
+ return ssh_handle_key_exchange(session);
+}
diff --git a/src/libcrypto.c b/src/libcrypto.c
new file mode 100644
index 00000000..f43a91eb
--- /dev/null
+++ b/src/libcrypto.c
@@ -0,0 +1,443 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/crypto.h"
+#include "libssh/wrapper.h"
+#include "libssh/libcrypto.h"
+
+#ifdef HAVE_LIBCRYPTO
+
+#include <openssl/sha.h>
+#include <openssl/md5.h>
+#include <openssl/dsa.h>
+#include <openssl/rsa.h>
+#include <openssl/hmac.h>
+#include <openssl/opensslv.h>
+#ifdef HAVE_OPENSSL_AES_H
+#define HAS_AES
+#include <openssl/aes.h>
+#endif
+#ifdef HAVE_OPENSSL_BLOWFISH_H
+#define HAS_BLOWFISH
+#include <openssl/blowfish.h>
+#endif
+#ifdef HAVE_OPENSSL_DES_H
+#define HAS_DES
+#include <openssl/des.h>
+#endif
+
+#if (OPENSSL_VERSION_NUMBER<0x00907000L)
+#define OLD_CRYPTO
+#endif
+
+#include "libssh/crypto.h"
+
+static int alloc_key(struct crypto_struct *cipher) {
+ cipher->key = malloc(cipher->keylen);
+ if (cipher->key == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+SHACTX sha1_init(void) {
+ SHACTX c = malloc(sizeof(*c));
+ if (c == NULL) {
+ return NULL;
+ }
+ SHA1_Init(c);
+
+ return c;
+}
+
+void sha1_update(SHACTX c, const void *data, unsigned long len) {
+ SHA1_Update(c,data,len);
+}
+
+void sha1_final(unsigned char *md, SHACTX c) {
+ SHA1_Final(md, c);
+ SAFE_FREE(c);
+}
+
+void sha1(unsigned char *digest, int len, unsigned char *hash) {
+ SHA1(digest, len, hash);
+}
+
+MD5CTX md5_init(void) {
+ MD5CTX c = malloc(sizeof(*c));
+ if (c == NULL) {
+ return NULL;
+ }
+
+ MD5_Init(c);
+
+ return c;
+}
+
+void md5_update(MD5CTX c, const void *data, unsigned long len) {
+ MD5_Update(c, data, len);
+}
+
+void md5_final(unsigned char *md, MD5CTX c) {
+ MD5_Final(md,c);
+ SAFE_FREE(c);
+}
+
+HMACCTX hmac_init(const void *key, int len, int type) {
+ HMACCTX ctx = NULL;
+
+ ctx = malloc(sizeof(*ctx));
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+#ifndef OLD_CRYPTO
+ HMAC_CTX_init(ctx); // openssl 0.9.7 requires it.
+#endif
+
+ switch(type) {
+ case HMAC_SHA1:
+ HMAC_Init(ctx, key, len, EVP_sha1());
+ break;
+ case HMAC_MD5:
+ HMAC_Init(ctx, key, len, EVP_md5());
+ break;
+ default:
+ SAFE_FREE(ctx);
+ ctx = NULL;
+ }
+
+ return ctx;
+}
+
+void hmac_update(HMACCTX ctx, const void *data, unsigned long len) {
+ HMAC_Update(ctx, data, len);
+}
+
+void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) {
+ HMAC_Final(ctx,hashmacbuf,len);
+
+#ifndef OLD_CRYPTO
+ HMAC_CTX_cleanup(ctx);
+#else
+ HMAC_cleanup(ctx);
+#endif
+
+ SAFE_FREE(ctx);
+}
+
+#ifdef HAS_BLOWFISH
+/* the wrapper functions for blowfish */
+static int blowfish_set_key(struct crypto_struct *cipher, void *key){
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+ BF_set_key(cipher->key, 16, key);
+ }
+
+ return 0;
+}
+
+static void blowfish_encrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len, void *IV) {
+ BF_cbc_encrypt(in, out, len, cipher->key, IV, BF_ENCRYPT);
+}
+
+static void blowfish_decrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len, void *IV) {
+ BF_cbc_encrypt(in, out, len, cipher->key, IV, BF_DECRYPT);
+}
+#endif /* HAS_BLOWFISH */
+
+#ifdef HAS_AES
+static int aes_set_encrypt_key(struct crypto_struct *cipher, void *key) {
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+ if (AES_set_encrypt_key(key,cipher->keysize,cipher->key) < 0) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+static int aes_set_decrypt_key(struct crypto_struct *cipher, void *key) {
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+ if (AES_set_decrypt_key(key,cipher->keysize,cipher->key) < 0) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void aes_encrypt(struct crypto_struct *cipher, void *in, void *out,
+ unsigned long len, void *IV) {
+ AES_cbc_encrypt(in, out, len, cipher->key, IV, AES_ENCRYPT);
+}
+
+static void aes_decrypt(struct crypto_struct *cipher, void *in, void *out,
+ unsigned long len, void *IV) {
+ AES_cbc_encrypt(in, out, len, cipher->key, IV, AES_DECRYPT);
+}
+
+#ifndef BROKEN_AES_CTR
+/* OpenSSL until 0.9.7c has a broken AES_ctr128_encrypt implementation which
+ * increments the counter from 2^64 instead of 1. It's better not to use it
+ */
+
+/** @internal
+ * @brief encrypts/decrypts data with stream cipher AES_ctr128. 128 bits is actually
+ * the size of the CTR counter and incidentally the blocksize, but not the keysize.
+ * @param len[in] must be a multiple of AES128 block size.
+ */
+static void aes_ctr128_encrypt(struct crypto_struct *cipher, void *in, void *out,
+ unsigned long len, void *IV) {
+ unsigned char tmp_buffer[128/8];
+ unsigned int num=0;
+ /* Some things are special with ctr128 :
+ * In this case, tmp_buffer is not being used, because it is used to store temporary data
+ * when an encryption is made on lengths that are not multiple of blocksize.
+ * Same for num, which is being used to store the current offset in blocksize in CTR
+ * function.
+ */
+ AES_ctr128_encrypt(in, out, len, cipher->key, IV, tmp_buffer, &num);
+}
+#endif /* BROKEN_AES_CTR */
+#endif /* HAS_AES */
+
+#ifdef HAS_DES
+static int des3_set_key(struct crypto_struct *cipher, void *key) {
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+
+ DES_set_odd_parity(key);
+ DES_set_odd_parity((void*)((uint8_t*)key + 8));
+ DES_set_odd_parity((void*)((uint8_t*)key + 16));
+ DES_set_key_unchecked(key, cipher->key);
+ DES_set_key_unchecked((void*)((uint8_t*)key + 8), (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)));
+ DES_set_key_unchecked((void*)((uint8_t*)key + 16), (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)));
+ }
+
+ return 0;
+}
+
+static void des3_encrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len, void *IV) {
+ DES_ede3_cbc_encrypt(in, out, len, cipher->key,
+ (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)),
+ (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)),
+ IV, 1);
+}
+
+static void des3_decrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len, void *IV) {
+ DES_ede3_cbc_encrypt(in, out, len, cipher->key,
+ (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)),
+ (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)),
+ IV, 0);
+}
+
+static void des3_1_encrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len, void *IV) {
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Encrypt IV before", IV, 24);
+#endif
+ DES_ncbc_encrypt(in, out, len, cipher->key, IV, 1);
+ DES_ncbc_encrypt(out, in, len, (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)),
+ (void*)((uint8_t*)IV + 8), 0);
+ DES_ncbc_encrypt(in, out, len, (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)),
+ (void*)((uint8_t*)IV + 16), 1);
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Encrypt IV after", IV, 24);
+#endif
+}
+
+static void des3_1_decrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len, void *IV) {
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Decrypt IV before", IV, 24);
+#endif
+
+ DES_ncbc_encrypt(in, out, len, (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)),
+ IV, 0);
+ DES_ncbc_encrypt(out, in, len, (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)),
+ (void*)((uint8_t*)IV + 8), 1);
+ DES_ncbc_encrypt(in, out, len, cipher->key, (void*)((uint8_t*)IV + 16), 0);
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Decrypt IV after", IV, 24);
+#endif
+}
+
+#endif /* HAS_DES */
+
+/*
+ * The table of supported ciphers
+ *
+ * WARNING: If you modify crypto_struct, you must make sure the order is
+ * correct!
+ */
+static struct crypto_struct ssh_ciphertab[] = {
+#ifdef HAS_BLOWFISH
+ {
+ "blowfish-cbc",
+ 8,
+ sizeof (BF_KEY),
+ NULL,
+ 128,
+ blowfish_set_key,
+ blowfish_set_key,
+ blowfish_encrypt,
+ blowfish_decrypt
+ },
+#endif /* HAS_BLOWFISH */
+#ifdef HAS_AES
+#ifndef BROKEN_AES_CTR
+ {
+ "aes128-ctr",
+ 16,
+ sizeof(AES_KEY),
+ NULL,
+ 128,
+ aes_set_encrypt_key,
+ aes_set_encrypt_key,
+ aes_ctr128_encrypt,
+ aes_ctr128_encrypt
+ },
+ {
+ "aes192-ctr",
+ 16,
+ sizeof(AES_KEY),
+ NULL,
+ 192,
+ aes_set_encrypt_key,
+ aes_set_encrypt_key,
+ aes_ctr128_encrypt,
+ aes_ctr128_encrypt
+ },
+ {
+ "aes256-ctr",
+ 16,
+ sizeof(AES_KEY),
+ NULL,
+ 256,
+ aes_set_encrypt_key,
+ aes_set_encrypt_key,
+ aes_ctr128_encrypt,
+ aes_ctr128_encrypt
+ },
+#endif /* BROKEN_AES_CTR */
+ {
+ "aes128-cbc",
+ 16,
+ sizeof(AES_KEY),
+ NULL,
+ 128,
+ aes_set_encrypt_key,
+ aes_set_decrypt_key,
+ aes_encrypt,
+ aes_decrypt
+ },
+ {
+ "aes192-cbc",
+ 16,
+ sizeof(AES_KEY),
+ NULL,
+ 192,
+ aes_set_encrypt_key,
+ aes_set_decrypt_key,
+ aes_encrypt,
+ aes_decrypt
+ },
+ {
+ "aes256-cbc",
+ 16,
+ sizeof(AES_KEY),
+ NULL,
+ 256,
+ aes_set_encrypt_key,
+ aes_set_decrypt_key,
+ aes_encrypt,
+ aes_decrypt
+ },
+#endif /* HAS_AES */
+#ifdef HAS_DES
+ {
+ "3des-cbc",
+ 8,
+ sizeof(DES_key_schedule) * 3,
+ NULL,
+ 192,
+ des3_set_key,
+ des3_set_key,
+ des3_encrypt,
+ des3_decrypt
+ },
+ {
+ "3des-cbc-ssh1",
+ 8,
+ sizeof(DES_key_schedule) * 3,
+ NULL,
+ 192,
+ des3_set_key,
+ des3_set_key,
+ des3_1_encrypt,
+ des3_1_decrypt
+ },
+#endif /* HAS_DES */
+ {
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ }
+};
+
+
+struct crypto_struct *ssh_get_ciphertab(){
+ return ssh_ciphertab;
+}
+
+#endif /* LIBCRYPTO */
+
diff --git a/src/libgcrypt.c b/src/libgcrypt.c
new file mode 100644
index 00000000..f8fe96f2
--- /dev/null
+++ b/src/libgcrypt.c
@@ -0,0 +1,423 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/crypto.h"
+#include "libssh/wrapper.h"
+
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+
+
+static int alloc_key(struct crypto_struct *cipher) {
+ cipher->key = malloc(cipher->keylen);
+ if (cipher->key == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+SHACTX sha1_init(void) {
+ SHACTX ctx = NULL;
+ gcry_md_open(&ctx, GCRY_MD_SHA1, 0);
+
+ return ctx;
+}
+
+void sha1_update(SHACTX c, const void *data, unsigned long len) {
+ gcry_md_write(c, data, len);
+}
+
+void sha1_final(unsigned char *md, SHACTX c) {
+ gcry_md_final(c);
+ memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN);
+ gcry_md_close(c);
+}
+
+void sha1(unsigned char *digest, int len, unsigned char *hash) {
+ gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len);
+}
+
+MD5CTX md5_init(void) {
+ MD5CTX c = NULL;
+ gcry_md_open(&c, GCRY_MD_MD5, 0);
+
+ return c;
+}
+
+void md5_update(MD5CTX c, const void *data, unsigned long len) {
+ gcry_md_write(c,data,len);
+}
+
+void md5_final(unsigned char *md, MD5CTX c) {
+ gcry_md_final(c);
+ memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN);
+ gcry_md_close(c);
+}
+
+HMACCTX hmac_init(const void *key, int len, int type) {
+ HMACCTX c = NULL;
+
+ switch(type) {
+ case HMAC_SHA1:
+ gcry_md_open(&c, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+ break;
+ case HMAC_MD5:
+ gcry_md_open(&c, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
+ break;
+ default:
+ c = NULL;
+ }
+
+ gcry_md_setkey(c, key, len);
+
+ return c;
+}
+
+void hmac_update(HMACCTX c, const void *data, unsigned long len) {
+ gcry_md_write(c, data, len);
+}
+
+void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) {
+ *len = gcry_md_get_algo_dlen(gcry_md_get_algo(c));
+ memcpy(hashmacbuf, gcry_md_read(c, 0), *len);
+ gcry_md_close(c);
+}
+
+/* the wrapper functions for blowfish */
+static int blowfish_set_key(struct crypto_struct *cipher, void *key, void *IV){
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+
+ if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_BLOWFISH,
+ GCRY_CIPHER_MODE_CBC, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setkey(cipher->key[0], key, 16)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setiv(cipher->key[0], IV, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void blowfish_encrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len) {
+ gcry_cipher_encrypt(cipher->key[0], out, len, in, len);
+}
+
+static void blowfish_decrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len) {
+ gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
+}
+
+static int aes_set_key(struct crypto_struct *cipher, void *key, void *IV) {
+ int mode=GCRY_CIPHER_MODE_CBC;
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+ if(strstr(cipher->name,"-ctr"))
+ mode=GCRY_CIPHER_MODE_CTR;
+ switch (cipher->keysize) {
+ case 128:
+ if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128,
+ mode, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ break;
+ case 192:
+ if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES192,
+ mode, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ break;
+ case 256:
+ if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES256,
+ mode, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ break;
+ }
+ if (gcry_cipher_setkey(cipher->key[0], key, cipher->keysize / 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if(mode == GCRY_CIPHER_MODE_CBC){
+ if (gcry_cipher_setiv(cipher->key[0], IV, 16)) {
+
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ } else {
+ if(gcry_cipher_setctr(cipher->key[0],IV,16)){
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void aes_encrypt(struct crypto_struct *cipher, void *in, void *out,
+ unsigned long len) {
+ gcry_cipher_encrypt(cipher->key[0], out, len, in, len);
+}
+
+static void aes_decrypt(struct crypto_struct *cipher, void *in, void *out,
+ unsigned long len) {
+ gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
+}
+
+static int des3_set_key(struct crypto_struct *cipher, void *key, void *IV) {
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+ if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_3DES,
+ GCRY_CIPHER_MODE_CBC, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setkey(cipher->key[0], key, 24)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setiv(cipher->key[0], IV, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void des3_encrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len) {
+ gcry_cipher_encrypt(cipher->key[0], out, len, in, len);
+}
+
+static void des3_decrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len) {
+ gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
+}
+
+static int des3_1_set_key(struct crypto_struct *cipher, void *key, void *IV) {
+ if (cipher->key == NULL) {
+ if (alloc_key(cipher) < 0) {
+ return -1;
+ }
+ if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_DES,
+ GCRY_CIPHER_MODE_CBC, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setkey(cipher->key[0], key, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setiv(cipher->key[0], IV, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+
+ if (gcry_cipher_open(&cipher->key[1], GCRY_CIPHER_DES,
+ GCRY_CIPHER_MODE_CBC, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setkey(cipher->key[1], (unsigned char *)key + 8, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setiv(cipher->key[1], (unsigned char *)IV + 8, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+
+ if (gcry_cipher_open(&cipher->key[2], GCRY_CIPHER_DES,
+ GCRY_CIPHER_MODE_CBC, 0)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setkey(cipher->key[2], (unsigned char *)key + 16, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ if (gcry_cipher_setiv(cipher->key[2], (unsigned char *)IV + 16, 8)) {
+ SAFE_FREE(cipher->key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void des3_1_encrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len) {
+ gcry_cipher_encrypt(cipher->key[0], out, len, in, len);
+ gcry_cipher_decrypt(cipher->key[1], in, len, out, len);
+ gcry_cipher_encrypt(cipher->key[2], out, len, in, len);
+}
+
+static void des3_1_decrypt(struct crypto_struct *cipher, void *in,
+ void *out, unsigned long len) {
+ gcry_cipher_decrypt(cipher->key[2], out, len, in, len);
+ gcry_cipher_encrypt(cipher->key[1], in, len, out, len);
+ gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
+}
+
+/* the table of supported ciphers */
+static struct crypto_struct ssh_ciphertab[] = {
+ {
+ .name = "blowfish-cbc",
+ .blocksize = 8,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 128,
+ .set_encrypt_key = blowfish_set_key,
+ .set_decrypt_key = blowfish_set_key,
+ .cbc_encrypt = blowfish_encrypt,
+ .cbc_decrypt = blowfish_decrypt
+ },
+ {
+ .name = "aes128-ctr",
+ .blocksize = 16,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 128,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .cbc_encrypt = aes_encrypt,
+ .cbc_decrypt = aes_encrypt
+ },
+ {
+ .name = "aes192-ctr",
+ .blocksize = 16,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 192,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .cbc_encrypt = aes_encrypt,
+ .cbc_decrypt = aes_encrypt
+ },
+ {
+ .name = "aes256-ctr",
+ .blocksize = 16,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 256,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .cbc_encrypt = aes_encrypt,
+ .cbc_decrypt = aes_encrypt
+ },
+ {
+ .name = "aes128-cbc",
+ .blocksize = 16,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 128,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .cbc_encrypt = aes_encrypt,
+ .cbc_decrypt = aes_decrypt
+ },
+ {
+ .name = "aes192-cbc",
+ .blocksize = 16,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 192,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .cbc_encrypt = aes_encrypt,
+ .cbc_decrypt = aes_decrypt
+ },
+ {
+ .name = "aes256-cbc",
+ .blocksize = 16,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 256,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .cbc_encrypt = aes_encrypt,
+ .cbc_decrypt = aes_decrypt
+ },
+ {
+ .name = "3des-cbc",
+ .blocksize = 8,
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 192,
+ .set_encrypt_key = des3_set_key,
+ .set_decrypt_key = des3_set_key,
+ .cbc_encrypt = des3_encrypt,
+ .cbc_decrypt = des3_decrypt
+ },
+ {
+ .name = "3des-cbc-ssh1",
+ .blocksize = 8,
+ .keylen = sizeof(gcry_cipher_hd_t) * 3,
+ .key = NULL,
+ .keysize = 192,
+ .set_encrypt_key = des3_1_set_key,
+ .set_decrypt_key = des3_1_set_key,
+ .cbc_encrypt = des3_1_encrypt,
+ .cbc_decrypt = des3_1_decrypt
+ },
+ {
+ .name = NULL,
+ .blocksize = 0,
+ .keylen = 0,
+ .key = NULL,
+ .keysize = 0,
+ .set_encrypt_key = NULL,
+ .set_decrypt_key = NULL,
+ .cbc_encrypt = NULL,
+ .cbc_decrypt = NULL
+ }
+};
+
+struct crypto_struct *ssh_get_ciphertab(){
+ return ssh_ciphertab;
+}
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 00000000..6ec5a8ea
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,82 @@
+/*
+ * log.c - logging and debugging functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2008 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 <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "libssh/priv.h"
+#include "libssh/session.h"
+
+/**
+ * @defgroup libssh_log The SSH logging functions.
+ * @ingroup libssh
+ *
+ * Logging functions for debugging and problem resolving.
+ *
+ * @{
+ */
+
+/**
+ * @brief Log a SSH event.
+ *
+ * @param session The SSH session.
+ *
+ * @param verbosity The verbosity of the event.
+ *
+ * @param format The format string of the log entry.
+ */
+void ssh_log(ssh_session session, int verbosity, const char *format, ...) {
+ char buffer[1024];
+ char indent[256];
+ int min;
+ va_list va;
+
+ if (verbosity <= session->log_verbosity) {
+ va_start(va, format);
+ vsnprintf(buffer, sizeof(buffer), format, va);
+ va_end(va);
+
+ if (session->callbacks && session->callbacks->log_function) {
+ session->callbacks->log_function(session, verbosity, buffer,
+ session->callbacks->userdata);
+ } else if (verbosity == SSH_LOG_FUNCTIONS) {
+ if (session->log_indent > 255) {
+ min = 255;
+ } else {
+ min = session->log_indent;
+ }
+
+ memset(indent, ' ', min);
+ indent[min] = '\0';
+
+ fprintf(stderr, "[func] %s%s\n", indent, buffer);
+ } else {
+ fprintf(stderr, "[%d] %s\n", verbosity, buffer);
+ }
+ }
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/match.c b/src/match.c
new file mode 100644
index 00000000..98adf67e
--- /dev/null
+++ b/src/match.c
@@ -0,0 +1,185 @@
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Simple pattern matching, with '*' and '?' as wildcards.
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+/*
+ * Copyright (c) 2000 Markus Friedl. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "libssh/priv.h"
+
+/*
+ * Returns true if the given string matches the pattern (which may contain ?
+ * and * as wildcards), and zero if it does not match.
+ */
+static int match_pattern(const char *s, const char *pattern) {
+ for (;;) {
+ /* If at end of pattern, accept if also at end of string. */
+ if (!*pattern) {
+ return !*s;
+ }
+
+ if (*pattern == '*') {
+ /* Skip the asterisk. */
+ pattern++;
+
+ /* If at end of pattern, accept immediately. */
+ if (!*pattern)
+ return 1;
+
+ /* If next character in pattern is known, optimize. */
+ if (*pattern != '?' && *pattern != '*') {
+ /*
+ * Look instances of the next character in
+ * pattern, and try to match starting from
+ * those.
+ */
+ for (; *s; s++)
+ if (*s == *pattern && match_pattern(s + 1, pattern + 1)) {
+ return 1;
+ }
+ /* Failed. */
+ return 0;
+ }
+ /*
+ * Move ahead one character at a time and try to
+ * match at each position.
+ */
+ for (; *s; s++) {
+ if (match_pattern(s, pattern)) {
+ return 1;
+ }
+ }
+ /* Failed. */
+ return 0;
+ }
+ /*
+ * There must be at least one more character in the string.
+ * If we are at the end, fail.
+ */
+ if (!*s) {
+ return 0;
+ }
+
+ /* Check if the next character of the string is acceptable. */
+ if (*pattern != '?' && *pattern != *s) {
+ return 0;
+ }
+
+ /* Move to the next character, both in string and in pattern. */
+ s++;
+ pattern++;
+ }
+
+ /* NOTREACHED */
+}
+
+/*
+ * Tries to match the string against the comma-separated sequence of subpatterns
+ * (each possibly preceded by ! to indicate negation).
+ * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is
+ * no match at all.
+ */
+static int match_pattern_list(const char *string, const char *pattern,
+ unsigned int len, int dolower) {
+ char sub[1024];
+ int negated;
+ int got_positive;
+ unsigned int i, subi;
+
+ got_positive = 0;
+ for (i = 0; i < len;) {
+ /* Check if the subpattern is negated. */
+ if (pattern[i] == '!') {
+ negated = 1;
+ i++;
+ } else {
+ negated = 0;
+ }
+
+ /*
+ * Extract the subpattern up to a comma or end. Convert the
+ * subpattern to lowercase.
+ */
+ for (subi = 0;
+ i < len && subi < sizeof(sub) - 1 && pattern[i] != ',';
+ subi++, i++) {
+ sub[subi] = dolower && isupper(pattern[i]) ?
+ (char)tolower(pattern[i]) : pattern[i];
+ }
+
+ /* If subpattern too long, return failure (no match). */
+ if (subi >= sizeof(sub) - 1) {
+ return 0;
+ }
+
+ /* If the subpattern was terminated by a comma, skip the comma. */
+ if (i < len && pattern[i] == ',') {
+ i++;
+ }
+
+ /* Null-terminate the subpattern. */
+ sub[subi] = '\0';
+
+ /* Try to match the subpattern against the string. */
+ if (match_pattern(string, sub)) {
+ if (negated) {
+ return -1; /* Negative */
+ } else {
+ got_positive = 1; /* Positive */
+ }
+ }
+ }
+
+ /*
+ * Return success if got a positive match. If there was a negative
+ * match, we have already returned -1 and never get here.
+ */
+ return got_positive;
+}
+
+/*
+ * Tries to match the host name (which must be in all lowercase) against the
+ * comma-separated sequence of subpatterns (each possibly preceded by ! to
+ * indicate negation).
+ * Returns -1 if negation matches, 1 if there is a positive match, 0 if there
+ * is no match at all.
+ */
+int match_hostname(const char *host, const char *pattern, unsigned int len) {
+ return match_pattern_list(host, pattern, len, 1);
+}
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/messages.c b/src/messages.c
new file mode 100644
index 00000000..027daf24
--- /dev/null
+++ b/src/messages.c
@@ -0,0 +1,848 @@
+/*
+ * messages.c - message parsion for the server
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2009 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 <string.h>
+#include <stdlib.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/libssh.h"
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.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"
+
+/**
+ * @defgroup libssh_messages The SSH message functions
+ * @ingroup libssh
+ *
+ * This file contains the Message parsing utilities for server programs using
+ * libssh. The main loop of the program will call ssh_message_get(session) to
+ * get messages as they come. they are not 1-1 with the protocol messages.
+ * then, the user will know what kind of a message it is and use the appropriate
+ * functions to handle it (or use the default handlers if she doesn't know what to
+ * do
+ *
+ * @{
+ */
+
+static ssh_message ssh_message_new(ssh_session session){
+ ssh_message msg = malloc(sizeof(struct ssh_message_struct));
+ if (msg == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(msg);
+ msg->session = session;
+ return msg;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_service_request){
+ ssh_string service = NULL;
+ char *service_c = NULL;
+ ssh_message msg=NULL;
+
+ enter_function();
+ (void)type;
+ (void)user;
+ service = buffer_get_ssh_string(packet);
+ if (service == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Invalid SSH_MSG_SERVICE_REQUEST packet");
+ goto error;
+ }
+
+ service_c = ssh_string_to_char(service);
+ if (service_c == NULL) {
+ goto error;
+ }
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received a SERVICE_REQUEST for service %s", service_c);
+ msg=ssh_message_new(session);
+ if(!msg){
+ SAFE_FREE(service_c);
+ goto error;
+ }
+ msg->type=SSH_REQUEST_SERVICE;
+ msg->service_request.service=service_c;
+error:
+ ssh_string_free(service);
+ if(msg != NULL)
+ ssh_message_queue(session,msg);
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a
+ * SSH Message
+ */
+SSH_PACKET_CALLBACK(ssh_packet_userauth_request){
+ ssh_string user_s = NULL;
+ ssh_string service = NULL;
+ ssh_string method = NULL;
+ ssh_message msg = NULL;
+ char *service_c = NULL;
+ char *method_c = NULL;
+ uint32_t method_size = 0;
+
+ enter_function();
+
+ (void)user;
+ (void)type;
+ msg = ssh_message_new(session);
+ if (msg == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ user_s = buffer_get_ssh_string(packet);
+ if (user_s == NULL) {
+ goto error;
+ }
+ service = buffer_get_ssh_string(packet);
+ if (service == NULL) {
+ goto error;
+ }
+ method = buffer_get_ssh_string(packet);
+ if (method == NULL) {
+ goto error;
+ }
+
+ msg->type = SSH_REQUEST_AUTH;
+ msg->auth_request.username = ssh_string_to_char(user_s);
+ if (msg->auth_request.username == NULL) {
+ goto error;
+ }
+ ssh_string_free(user_s);
+ user_s = NULL;
+
+ service_c = ssh_string_to_char(service);
+ if (service_c == NULL) {
+ goto error;
+ }
+ method_c = ssh_string_to_char(method);
+ if (method_c == NULL) {
+ goto error;
+ }
+ method_size = ssh_string_len(method);
+
+ ssh_string_free(service);
+ service = NULL;
+ ssh_string_free(method);
+ method = NULL;
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Auth request for service %s, method %s for user '%s'",
+ service_c, method_c,
+ msg->auth_request.username);
+
+
+ if (strncmp(method_c, "none", method_size) == 0) {
+ msg->auth_request.method = SSH_AUTH_METHOD_NONE;
+ SAFE_FREE(service_c);
+ SAFE_FREE(method_c);
+ goto end;
+ }
+
+ if (strncmp(method_c, "password", method_size) == 0) {
+ ssh_string pass = NULL;
+ uint8_t tmp;
+
+ msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD;
+ SAFE_FREE(service_c);
+ SAFE_FREE(method_c);
+ buffer_get_u8(packet, &tmp);
+ pass = buffer_get_ssh_string(packet);
+ if (pass == NULL) {
+ goto error;
+ }
+ msg->auth_request.password = ssh_string_to_char(pass);
+ ssh_string_burn(pass);
+ ssh_string_free(pass);
+ pass = NULL;
+ if (msg->auth_request.password == NULL) {
+ goto error;
+ }
+ goto end;
+ }
+
+ if (strncmp(method_c, "publickey", method_size) == 0) {
+ ssh_string algo = NULL;
+ ssh_string publickey = NULL;
+ uint8_t has_sign;
+
+ msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY;
+ SAFE_FREE(method_c);
+ buffer_get_u8(packet, &has_sign);
+ algo = buffer_get_ssh_string(packet);
+ if (algo == NULL) {
+ goto error;
+ }
+ publickey = buffer_get_ssh_string(packet);
+ if (publickey == NULL) {
+ ssh_string_free(algo);
+ algo = NULL;
+ goto error;
+ }
+ msg->auth_request.public_key = publickey_from_string(session, publickey);
+ ssh_string_free(algo);
+ algo = NULL;
+ ssh_string_free(publickey);
+ publickey = NULL;
+ if (msg->auth_request.public_key == NULL) {
+ goto error;
+ }
+ msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE;
+ // has a valid signature ?
+ if(has_sign) {
+ SIGNATURE *signature = NULL;
+ ssh_public_key public_key = msg->auth_request.public_key;
+ ssh_string sign = NULL;
+ ssh_buffer digest = NULL;
+
+ sign = buffer_get_ssh_string(packet);
+ if(sign == NULL) {
+ ssh_log(session, SSH_LOG_PACKET, "Invalid signature packet from peer");
+ msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR;
+ goto error;
+ }
+ signature = signature_from_string(session, sign, public_key,
+ public_key->type);
+ digest = ssh_userauth_build_digest(session, msg, service_c);
+ if ((digest == NULL || signature == NULL) ||
+ (digest != NULL && signature != NULL &&
+ sig_verify(session, public_key, signature,
+ ssh_buffer_get_begin(digest), ssh_buffer_get_len(digest)) < 0)) {
+ ssh_log(session, SSH_LOG_PACKET, "Wrong signature from peer");
+
+ ssh_string_free(sign);
+ sign = NULL;
+ ssh_buffer_free(digest);
+ digest = NULL;
+ signature_free(signature);
+ signature = NULL;
+
+ msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG;
+ goto error;
+ }
+ else
+ ssh_log(session, SSH_LOG_PACKET, "Valid signature received");
+
+ ssh_buffer_free(digest);
+ digest = NULL;
+ ssh_string_free(sign);
+ sign = NULL;
+ signature_free(signature);
+ signature = NULL;
+
+ msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID;
+ }
+ SAFE_FREE(service_c);
+ goto end;
+ }
+
+ msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN;
+ SAFE_FREE(method_c);
+ goto end;
+error:
+ ssh_string_free(user_s);
+ ssh_string_free(service);
+ ssh_string_free(method);
+
+ SAFE_FREE(method_c);
+ SAFE_FREE(service_c);
+
+ ssh_message_free(msg);
+
+ leave_function();
+ return SSH_PACKET_USED;
+end:
+ ssh_message_queue(session,msg);
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_channel_open){
+ ssh_message msg = NULL;
+ ssh_string type_s = NULL, originator = NULL, destination = NULL;
+ char *type_c = NULL;
+ uint32_t sender, window, packet_size, originator_port, destination_port;
+
+ enter_function();
+ (void)type;
+ (void)user;
+ msg = ssh_message_new(session);
+ if (msg == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ msg->type = SSH_REQUEST_CHANNEL_OPEN;
+
+ type_s = buffer_get_ssh_string(packet);
+ if (type_s == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ type_c = ssh_string_to_char(type_s);
+ if (type_c == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Clients wants to open a %s channel", type_c);
+ ssh_string_free(type_s);
+ type_s=NULL;
+
+ buffer_get_u32(packet, &sender);
+ buffer_get_u32(packet, &window);
+ buffer_get_u32(packet, &packet_size);
+
+ msg->channel_request_open.sender = ntohl(sender);
+ msg->channel_request_open.window = ntohl(window);
+ msg->channel_request_open.packet_size = ntohl(packet_size);
+
+ if (strcmp(type_c,"session") == 0) {
+ msg->channel_request_open.type = SSH_CHANNEL_SESSION;
+ SAFE_FREE(type_c);
+ goto end;
+ }
+
+ if (strcmp(type_c,"direct-tcpip") == 0) {
+ destination = buffer_get_ssh_string(packet);
+ if (destination == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ msg->channel_request_open.destination = ssh_string_to_char(destination);
+ if (msg->channel_request_open.destination == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(destination);
+ goto error;
+ }
+ ssh_string_free(destination);
+
+ buffer_get_u32(packet, &destination_port);
+ msg->channel_request_open.destination_port = ntohl(destination_port);
+
+ originator = buffer_get_ssh_string(packet);
+ if (originator == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ msg->channel_request_open.originator = ssh_string_to_char(originator);
+ if (msg->channel_request_open.originator == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(originator);
+ goto error;
+ }
+ ssh_string_free(originator);
+
+ buffer_get_u32(packet, &originator_port);
+ msg->channel_request_open.originator_port = ntohl(originator_port);
+
+ msg->channel_request_open.type = SSH_CHANNEL_DIRECT_TCPIP;
+ goto end;
+ }
+
+ if (strcmp(type_c,"forwarded-tcpip") == 0) {
+ destination = buffer_get_ssh_string(packet);
+ if (destination == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ msg->channel_request_open.destination = ssh_string_to_char(destination);
+ if (msg->channel_request_open.destination == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(destination);
+ goto error;
+ }
+ ssh_string_free(destination);
+
+ buffer_get_u32(packet, &destination_port);
+ msg->channel_request_open.destination_port = ntohl(destination_port);
+
+ originator = buffer_get_ssh_string(packet);
+ if (originator == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ msg->channel_request_open.originator = ssh_string_to_char(originator);
+ if (msg->channel_request_open.originator == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(originator);
+ goto error;
+ }
+ ssh_string_free(originator);
+
+ buffer_get_u32(packet, &originator_port);
+ msg->channel_request_open.originator_port = ntohl(originator_port);
+
+ msg->channel_request_open.type = SSH_CHANNEL_FORWARDED_TCPIP;
+ goto end;
+ }
+
+ if (strcmp(type_c,"x11") == 0) {
+ originator = buffer_get_ssh_string(packet);
+ if (originator == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ msg->channel_request_open.originator = ssh_string_to_char(originator);
+ if (msg->channel_request_open.originator == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(originator);
+ goto error;
+ }
+ ssh_string_free(originator);
+
+ buffer_get_u32(packet, &originator_port);
+ msg->channel_request_open.originator_port = ntohl(originator_port);
+
+ msg->channel_request_open.type = SSH_CHANNEL_X11;
+ goto end;
+ }
+
+ msg->channel_request_open.type = SSH_CHANNEL_UNKNOWN;
+ goto end;
+
+error:
+ ssh_message_free(msg);
+ msg=NULL;
+end:
+ if(type_s != NULL)
+ ssh_string_free(type_s);
+ SAFE_FREE(type_c);
+ if(msg != NULL)
+ ssh_message_queue(session,msg);
+ leave_function();
+ return SSH_PACKET_USED;
+}
+
+/* TODO: make this function accept a ssh_channel */
+ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) {
+ ssh_session session = msg->session;
+ ssh_channel chan = NULL;
+
+ enter_function();
+
+ if (msg == NULL) {
+ leave_function();
+ return NULL;
+ }
+
+ chan = ssh_channel_new(session);
+ if (chan == NULL) {
+ leave_function();
+ return NULL;
+ }
+
+ chan->local_channel = ssh_channel_new_id(session);
+ chan->local_maxpacket = 35000;
+ chan->local_window = 32000;
+ chan->remote_channel = msg->channel_request_open.sender;
+ chan->remote_maxpacket = msg->channel_request_open.packet_size;
+ chan->remote_window = msg->channel_request_open.window;
+ chan->state = SSH_CHANNEL_STATE_OPEN;
+
+ if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer, htonl(chan->remote_channel)) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer, htonl(chan->local_channel)) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer, htonl(chan->local_window)) < 0) {
+ goto error;
+ }
+ if (buffer_add_u32(session->out_buffer, htonl(chan->local_maxpacket)) < 0) {
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Accepting a channel request_open for chan %d", chan->remote_channel);
+
+ if (packet_send(session) == SSH_ERROR) {
+ goto error;
+ }
+
+ leave_function();
+ return chan;
+error:
+ ssh_channel_free(chan);
+
+ leave_function();
+ return NULL;
+}
+
+/**
+ * @internal
+ *
+ * @brief This function parses the last end of a channel request packet.
+ *
+ * This is normally converted to a SSH message and placed in the queue.
+ *
+ * @param[in] session The SSH session.
+ *
+ * @param[in] channel The channel the request is made on.
+ *
+ * @param[in] packet The rest of the packet to be parsed.
+ *
+ * @param[in] request The type of request.
+ *
+ * @param[in] want_reply The want_reply field from the request.
+ *
+ * @returns SSH_OK on success, SSH_ERROR if an error occured.
+ */
+int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet,
+ const char *request, uint8_t want_reply) {
+ ssh_message msg = NULL;
+ enter_function();
+ msg = ssh_message_new(session);
+ if (msg == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)",
+ request, channel->local_channel, channel->remote_channel, want_reply);
+
+ msg->type = SSH_REQUEST_CHANNEL;
+ msg->channel_request.channel = channel;
+ msg->channel_request.want_reply = want_reply;
+
+ if (strcmp(request, "pty-req") == 0) {
+ ssh_string term = NULL;
+ char *term_c = NULL;
+ term = buffer_get_ssh_string(packet);
+ if (term == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ term_c = ssh_string_to_char(term);
+ if (term_c == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(term);
+ goto error;
+ }
+ ssh_string_free(term);
+
+ msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY;
+ msg->channel_request.TERM = term_c;
+
+ buffer_get_u32(packet, &msg->channel_request.width);
+ buffer_get_u32(packet, &msg->channel_request.height);
+ buffer_get_u32(packet, &msg->channel_request.pxwidth);
+ buffer_get_u32(packet, &msg->channel_request.pxheight);
+
+ msg->channel_request.width = ntohl(msg->channel_request.width);
+ msg->channel_request.height = ntohl(msg->channel_request.height);
+ msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth);
+ msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight);
+ msg->channel_request.modes = buffer_get_ssh_string(packet);
+ if (msg->channel_request.modes == NULL) {
+ SAFE_FREE(term_c);
+ goto error;
+ }
+ goto end;
+ }
+
+ if (strcmp(request, "window-change") == 0) {
+ msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE;
+
+ buffer_get_u32(packet, &msg->channel_request.width);
+ buffer_get_u32(packet, &msg->channel_request.height);
+ buffer_get_u32(packet, &msg->channel_request.pxwidth);
+ buffer_get_u32(packet, &msg->channel_request.pxheight);
+
+ msg->channel_request.width = ntohl(msg->channel_request.width);
+ msg->channel_request.height = ntohl(msg->channel_request.height);
+ msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth);
+ msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight);
+
+ goto end;
+ }
+
+ if (strcmp(request, "subsystem") == 0) {
+ ssh_string subsys = NULL;
+ char *subsys_c = NULL;
+ subsys = buffer_get_ssh_string(packet);
+ if (subsys == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ subsys_c = ssh_string_to_char(subsys);
+ if (subsys_c == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(subsys);
+ goto error;
+ }
+ ssh_string_free(subsys);
+
+ msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM;
+ msg->channel_request.subsystem = subsys_c;
+
+ goto end;
+ }
+
+ if (strcmp(request, "shell") == 0) {
+ msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL;
+ goto end;
+ }
+
+ if (strcmp(request, "exec") == 0) {
+ ssh_string cmd = NULL;
+ cmd = buffer_get_ssh_string(packet);
+ if (cmd == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC;
+ msg->channel_request.command = ssh_string_to_char(cmd);
+ ssh_string_free(cmd);
+ if (msg->channel_request.command == NULL) {
+ goto error;
+ }
+ goto end;
+ }
+
+ if (strcmp(request, "env") == 0) {
+ ssh_string name = NULL;
+ ssh_string value = NULL;
+ name = buffer_get_ssh_string(packet);
+ if (name == NULL) {
+ ssh_set_error_oom(session);
+ goto error;
+ }
+ value = buffer_get_ssh_string(packet);
+ if (value == NULL) {
+ ssh_set_error_oom(session);
+ ssh_string_free(name);
+ goto error;
+ }
+
+ msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV;
+ msg->channel_request.var_name = ssh_string_to_char(name);
+ msg->channel_request.var_value = ssh_string_to_char(value);
+ if (msg->channel_request.var_name == NULL ||
+ msg->channel_request.var_value == NULL) {
+ ssh_string_free(name);
+ ssh_string_free(value);
+ goto error;
+ }
+ ssh_string_free(name);
+ ssh_string_free(value);
+
+ goto end;
+ }
+
+ msg->channel_request.type = SSH_CHANNEL_UNKNOWN;
+end:
+ ssh_message_queue(session,msg);
+ leave_function();
+ return SSH_OK;
+error:
+ ssh_message_free(msg);
+
+ leave_function();
+ return SSH_ERROR;
+}
+
+int ssh_message_channel_request_reply_success(ssh_message msg) {
+ uint32_t channel;
+
+ if (msg == NULL) {
+ return SSH_ERROR;
+ }
+
+ if (msg->channel_request.want_reply) {
+ channel = msg->channel_request.channel->remote_channel;
+
+ ssh_log(msg->session, SSH_LOG_PACKET,
+ "Sending a channel_request success to channel %d", channel);
+
+ if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_CHANNEL_SUCCESS) < 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 succeeded");
+
+ return SSH_OK;
+}
+
+/**
+ * @brief Retrieve a SSH message from a SSH session.
+ *
+ * @param[in] session The SSH session to get the message.
+ *
+ * @returns The SSH message received, NULL in case of error.
+ *
+ * @warning This function blocks until a message has been received. Betterset up
+ * a callback if this behavior is unwanted.
+ */
+ssh_message ssh_message_get(ssh_session session) {
+ ssh_message msg = NULL;
+ enter_function();
+
+ msg=ssh_message_pop_head(session);
+ if(msg) {
+ leave_function();
+ return msg;
+ }
+ if(session->ssh_message_list == NULL) {
+ session->ssh_message_list = ssh_list_new();
+ }
+ do {
+ if (ssh_handle_packets(session,-1) == SSH_ERROR) {
+ leave_function();
+ return NULL;
+ }
+ msg=ssh_list_pop_head(ssh_message, session->ssh_message_list);
+ } while(msg==NULL);
+ leave_function();
+ return msg;
+}
+
+int ssh_message_type(ssh_message msg) {
+ if (msg == NULL) {
+ return -1;
+ }
+
+ return msg->type;
+}
+
+int ssh_message_subtype(ssh_message msg) {
+ if (msg == NULL) {
+ return -1;
+ }
+
+ switch(msg->type) {
+ case SSH_REQUEST_AUTH:
+ return msg->auth_request.method;
+ case SSH_REQUEST_CHANNEL_OPEN:
+ return msg->channel_request_open.type;
+ case SSH_REQUEST_CHANNEL:
+ return msg->channel_request.type;
+ }
+
+ return -1;
+}
+
+void ssh_message_free(ssh_message msg){
+ if (msg == NULL) {
+ return;
+ }
+
+ switch(msg->type) {
+ case SSH_REQUEST_AUTH:
+ SAFE_FREE(msg->auth_request.username);
+ if (msg->auth_request.password) {
+ memset(msg->auth_request.password, 0,
+ strlen(msg->auth_request.password));
+ SAFE_FREE(msg->auth_request.password);
+ }
+ publickey_free(msg->auth_request.public_key);
+ break;
+ case SSH_REQUEST_CHANNEL_OPEN:
+ SAFE_FREE(msg->channel_request_open.originator);
+ SAFE_FREE(msg->channel_request_open.destination);
+ break;
+ case SSH_REQUEST_CHANNEL:
+ SAFE_FREE(msg->channel_request.TERM);
+ SAFE_FREE(msg->channel_request.modes);
+ SAFE_FREE(msg->channel_request.var_name);
+ SAFE_FREE(msg->channel_request.var_value);
+ SAFE_FREE(msg->channel_request.command);
+ SAFE_FREE(msg->channel_request.subsystem);
+ break;
+ case SSH_REQUEST_SERVICE:
+ SAFE_FREE(msg->service_request.service);
+ break;
+ }
+ ZERO_STRUCTP(msg);
+ SAFE_FREE(msg);
+}
+
+/**
+ * @internal
+ *
+ * @brief Add a message to the current queue of messages to be parsed.
+ *
+ * @param[in] session The SSH session to add the message.
+ *
+ * @param[in] message The message to add to the queue.
+ */
+void ssh_message_queue(ssh_session session, ssh_message message){
+ if(message){
+ if(session->ssh_message_list == NULL){
+ session->ssh_message_list=ssh_list_new();
+ }
+ ssh_list_append(session->ssh_message_list, message);
+ }
+}
+
+/**
+ * @internal
+ *
+ * @brief Pop a message from the message list and dequeue it.
+ *
+ * @param[in] session The SSH session to pop the message.
+ *
+ * @returns The head message or NULL if it doesn't exist.
+ */
+ssh_message ssh_message_pop_head(ssh_session session){
+ ssh_message msg=NULL;
+ struct ssh_iterator *i;
+ if(session->ssh_message_list == NULL)
+ return NULL;
+ i=ssh_list_get_iterator(session->ssh_message_list);
+ if(i != NULL){
+ msg=ssh_iterator_value(ssh_message,i);
+ ssh_list_remove(session->ssh_message_list,i);
+ }
+ return msg;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 00000000..bd903b02
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,685 @@
+/*
+ * misc.c - useful client functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2009 by Aris Adamantiadis
+ * Copyright (c) 2008-2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#ifdef _WIN32
+#define _WIN32_IE 0x0501 //SHGetSpecialFolderPath
+#include <winsock2.h> // Must be the first to include
+#include <ws2tcpip.h>
+#include <shlobj.h>
+#include <direct.h>
+#if _MSC_VER >= 1400
+#include <io.h>
+#endif /* _MSC_VER */
+#else /* _WIN32 */
+/* This is needed for a standard getpwuid_r on opensolaris */
+#define _POSIX_PTHREAD_SEMANTICS
+#include <pwd.h>
+#include <arpa/inet.h>
+#endif /* _WIN32 */
+
+#include "libssh/priv.h"
+#include "libssh/misc.h"
+#include "libssh/session.h"
+
+#ifdef HAVE_LIBGCRYPT
+#define GCRYPT_STRING "/gnutls"
+#else
+#define GCRYPT_STRING ""
+#endif
+
+#ifdef HAVE_LIBCRYPTO
+#define CRYPTO_STRING "/openssl"
+#else
+#define CRYPTO_STRING ""
+#endif
+
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+#define LIBZ_STRING "/zlib"
+#else
+#define LIBZ_STRING ""
+#endif
+
+/**
+ * @defgroup libssh_misc The SSH helper functions.
+ * @ingroup libssh
+ *
+ * Different helper functions used in the SSH Library.
+ *
+ * @{
+ */
+
+#ifdef _WIN32
+char *ssh_get_user_home_dir(void) {
+ char tmp[MAX_PATH] = {0};
+ char *szPath = NULL;
+
+ if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) {
+ szPath = malloc(strlen(tmp) + 1);
+ if (szPath == NULL) {
+ return NULL;
+ }
+
+ strcpy(szPath, tmp);
+ return szPath;
+ }
+
+ return NULL;
+}
+
+/* we have read access on file */
+int ssh_file_readaccess_ok(const char *file) {
+ if (_access(file, 4) < 0) {
+ return 0;
+ }
+
+ return 1;
+}
+
+#define SSH_USEC_IN_SEC 1000000LL
+#define SSH_SECONDS_SINCE_1601 11644473600LL
+
+int gettimeofday(struct timeval *__p, void *__t) {
+ union {
+ unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */
+ FILETIME ft;
+ } now;
+
+ GetSystemTimeAsFileTime (&now.ft);
+ __p->tv_usec = (long) ((now.ns100 / 10LL) % SSH_USEC_IN_SEC);
+ __p->tv_sec = (long)(((now.ns100 / 10LL ) / SSH_USEC_IN_SEC) - SSH_SECONDS_SINCE_1601);
+
+ return (0);
+}
+
+char *ssh_get_local_username(ssh_session session) {
+ DWORD size = 0;
+ char *user;
+
+ /* get the size */
+ GetUserName(NULL, &size);
+
+ user = malloc(size);
+ if (user == NULL) {
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+
+ if (GetUserName(user, &size)) {
+ return user;
+ }
+
+ return NULL;
+}
+#else /* _WIN32 */
+
+#ifndef NSS_BUFLEN_PASSWD
+#define NSS_BUFLEN_PASSWD 4096
+#endif /* NSS_BUFLEN_PASSWD */
+
+char *ssh_get_user_home_dir(void) {
+ char *szPath = NULL;
+ struct passwd pwd;
+ struct passwd *pwdbuf;
+ char buf[NSS_BUFLEN_PASSWD];
+ int rc;
+
+ rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf);
+ if (rc != 0) {
+ return NULL;
+ }
+
+ szPath = strdup(pwd.pw_dir);
+
+ return szPath;
+}
+
+/* we have read access on file */
+int ssh_file_readaccess_ok(const char *file) {
+ if (access(file, R_OK) < 0) {
+ return 0;
+ }
+
+ return 1;
+}
+
+char *ssh_get_local_username(ssh_session session) {
+ struct passwd pwd;
+ struct passwd *pwdbuf;
+ char buf[NSS_BUFLEN_PASSWD];
+ char *name;
+ int rc;
+
+ rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf);
+ if (rc != 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Couldn't retrieve information for current user!");
+ return NULL;
+ }
+
+ name = strdup(pwd.pw_name);
+
+ if (name == NULL) {
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+
+ return name;
+}
+#endif /* _WIN32 */
+
+uint64_t ntohll(uint64_t a) {
+#ifdef WORDS_BIGENDIAN
+ return a;
+#else
+ uint32_t low = (uint32_t)(a & 0xffffffff);
+ uint32_t high = (uint32_t)(a >> 32);
+ low = ntohl(low);
+ high = ntohl(high);
+
+ return ((((uint64_t) low) << 32) | ( high));
+#endif
+}
+
+char *ssh_lowercase(const char* str) {
+ char *new, *p;
+
+ if (str == NULL) {
+ return NULL;
+ }
+
+ new = strdup(str);
+ if (new == NULL) {
+ return NULL;
+ }
+
+ for (p = new; *p; p++) {
+ *p = tolower(*p);
+ }
+
+ return new;
+}
+
+char *ssh_hostport(const char *host, int port){
+ char *dest;
+ size_t len;
+ if(host==NULL)
+ return NULL;
+ /* 3 for []:, 5 for 65536 and 1 for nul */
+ len=strlen(host) + 3 + 5 + 1;
+ dest=malloc(len);
+ if(dest==NULL)
+ return NULL;
+ snprintf(dest,len,"[%s]:%d",host,port);
+ return dest;
+}
+
+/**
+ * @brief Check if libssh is the required version or get the version
+ * string.
+ *
+ * @param[in] req_version The version required.
+ *
+ * @return If the version of libssh is newer than the version
+ * required it will return a version string.
+ * NULL if the version is older.
+ *
+ * Example:
+ *
+ * @code
+ * if (ssh_version(SSH_VERSION_INT(0,2,1)) == NULL) {
+ * fprintf(stderr, "libssh version is too old!\n");
+ * exit(1);
+ * }
+ *
+ * if (debug) {
+ * printf("libssh %s\n", ssh_version(0));
+ * }
+ * @endcode
+ */
+const char *ssh_version(int req_version) {
+ if (req_version <= LIBSSH_VERSION_INT) {
+ return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING
+ LIBZ_STRING;
+ }
+
+ return NULL;
+}
+
+struct ssh_list *ssh_list_new(){
+ struct ssh_list *ret=malloc(sizeof(struct ssh_list));
+ if(!ret)
+ return NULL;
+ ret->root=ret->end=NULL;
+ return ret;
+}
+
+void ssh_list_free(struct ssh_list *list){
+ struct ssh_iterator *ptr,*next;
+ ptr=list->root;
+ while(ptr){
+ next=ptr->next;
+ SAFE_FREE(ptr);
+ ptr=next;
+ }
+ SAFE_FREE(list);
+}
+
+struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list){
+ return list->root;
+}
+
+static struct ssh_iterator *ssh_iterator_new(const void *data){
+ struct ssh_iterator *iterator=malloc(sizeof(struct ssh_iterator));
+ if(!iterator)
+ return NULL;
+ iterator->next=NULL;
+ iterator->data=data;
+ return iterator;
+}
+
+int ssh_list_append(struct ssh_list *list,const void *data){
+ struct ssh_iterator *iterator=ssh_iterator_new(data);
+ if(!iterator)
+ return SSH_ERROR;
+ if(!list->end){
+ /* list is empty */
+ list->root=list->end=iterator;
+ } else {
+ /* put it on end of list */
+ list->end->next=iterator;
+ list->end=iterator;
+ }
+ return SSH_OK;
+}
+
+int ssh_list_prepend(struct ssh_list *list, const void *data){
+ struct ssh_iterator *it = ssh_iterator_new(data);
+
+ if (it == NULL) {
+ return SSH_ERROR;
+ }
+
+ if (list->end == NULL) {
+ /* list is empty */
+ list->root = list->end = it;
+ } else {
+ /* set as new root */
+ it->next = list->root;
+ list->root = it;
+ }
+
+ return SSH_OK;
+}
+
+void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){
+ struct ssh_iterator *ptr,*prev;
+ prev=NULL;
+ ptr=list->root;
+ while(ptr && ptr != iterator){
+ prev=ptr;
+ ptr=ptr->next;
+ }
+ if(!ptr){
+ /* we did not find the element */
+ return;
+ }
+ /* unlink it */
+ if(prev)
+ prev->next=ptr->next;
+ /* if iterator was the head */
+ if(list->root == iterator)
+ list->root=iterator->next;
+ /* if iterator was the tail */
+ if(list->end == iterator)
+ list->end = prev;
+ SAFE_FREE(iterator);
+}
+
+/**
+ * @internal
+ *
+ * @brief Removes the top element of the list and returns the data value
+ * attached to it.
+ *
+ * @param[in[ list The ssh_list to remove the element.
+ *
+ * @returns A pointer to the element being stored in head, or NULL
+ * if the list is empty.
+ */
+const void *_ssh_list_pop_head(struct ssh_list *list){
+ struct ssh_iterator *iterator=list->root;
+ const void *data;
+ if(!list->root)
+ return NULL;
+ data=iterator->data;
+ list->root=iterator->next;
+ if(list->end==iterator)
+ list->end=NULL;
+ SAFE_FREE(iterator);
+ return data;
+}
+
+/**
+ * @brief Parse directory component.
+ *
+ * dirname breaks a null-terminated pathname string into a directory component.
+ * In the usual case, ssh_dirname() returns the string up to, but not including,
+ * the final '/'. Trailing '/' characters are not counted as part of the
+ * pathname. The caller must free the memory.
+ *
+ * @param[in] path The path to parse.
+ *
+ * @return The dirname of path or NULL if we can't allocate memory.
+ * If path does not contain a slash, c_dirname() returns
+ * the string ".". If path is the string "/", it returns
+ * the string "/". If path is NULL or an empty string,
+ * "." is returned.
+ */
+char *ssh_dirname (const char *path) {
+ char *new = NULL;
+ unsigned int len;
+
+ if (path == NULL || *path == '\0') {
+ return strdup(".");
+ }
+
+ len = strlen(path);
+
+ /* Remove trailing slashes */
+ while(len > 0 && path[len - 1] == '/') --len;
+
+ /* We have only slashes */
+ if (len == 0) {
+ return strdup("/");
+ }
+
+ /* goto next slash */
+ while(len > 0 && path[len - 1] != '/') --len;
+
+ if (len == 0) {
+ return strdup(".");
+ } else if (len == 1) {
+ return strdup("/");
+ }
+
+ /* Remove slashes again */
+ while(len > 0 && path[len - 1] == '/') --len;
+
+ new = malloc(len + 1);
+ if (new == NULL) {
+ return NULL;
+ }
+
+ strncpy(new, path, len);
+ new[len] = '\0';
+
+ return new;
+}
+
+/**
+ * @brief basename - parse filename component.
+ *
+ * basename breaks a null-terminated pathname string into a filename component.
+ * ssh_basename() returns the component following the final '/'. Trailing '/'
+ * characters are not counted as part of the pathname.
+ *
+ * @param[in] path The path to parse.
+ *
+ * @return The filename of path or NULL if we can't allocate
+ * memory. If path is a the string "/", basename returns
+ * the string "/". If path is NULL or an empty string,
+ * "." is returned.
+ */
+char *ssh_basename (const char *path) {
+ char *new = NULL;
+ const char *s;
+ unsigned int len;
+
+ if (path == NULL || *path == '\0') {
+ return strdup(".");
+ }
+
+ len = strlen(path);
+ /* Remove trailing slashes */
+ while(len > 0 && path[len - 1] == '/') --len;
+
+ /* We have only slashes */
+ if (len == 0) {
+ return strdup("/");
+ }
+
+ while(len > 0 && path[len - 1] != '/') --len;
+
+ if (len > 0) {
+ s = path + len;
+ len = strlen(s);
+
+ while(len > 0 && s[len - 1] == '/') --len;
+ } else {
+ return strdup(path);
+ }
+
+ new = malloc(len + 1);
+ if (new == NULL) {
+ return NULL;
+ }
+
+ strncpy(new, s, len);
+ new[len] = '\0';
+
+ return new;
+}
+
+/**
+ * @brief Attempts to create a directory with the given pathname.
+ *
+ * This is the portable version of mkdir, mode is ignored on Windows systems.
+ *
+ * @param[in] pathname The path name to create the directory.
+ *
+ * @param[in] mode The permissions to use.
+ *
+ * @return 0 on success, < 0 on error with errno set.
+ */
+int ssh_mkdir(const char *pathname, mode_t mode) {
+ int r;
+
+#ifdef _WIN32
+ r = _mkdir(pathname);
+#else
+ r = mkdir(pathname, mode);
+#endif
+
+ return r;
+}
+
+/**
+ * @brief Expand a directory starting with a tilde '~'
+ *
+ * @param[in] d The directory to expand.
+ *
+ * @return The expanded directory, NULL on error.
+ */
+char *ssh_path_expand_tilde(const char *d) {
+ char *h, *r;
+ const char *p;
+ size_t ld;
+ size_t lh = 0;
+
+ if (d[0] != '~') {
+ return strdup(d);
+ }
+ d++;
+
+ /* handle ~user/path */
+ p = strchr(d, '/');
+ if (p != NULL && p > d) {
+#ifdef _WIN32
+ return strdup(d);
+#else
+ struct passwd *pw;
+ size_t s = p - d;
+ char u[128];
+
+ if (s > sizeof(u)) {
+ return NULL;
+ }
+ memcpy(u, d, s);
+ u[s] = '\0';
+ pw = getpwnam(u);
+ if (pw == NULL) {
+ return NULL;
+ }
+ ld = strlen(p);
+ h = strdup(pw->pw_dir);
+#endif
+ } else {
+ ld = strlen(d);
+ p = (char *) d;
+ h = ssh_get_user_home_dir();
+ }
+ if (h == NULL) {
+ return NULL;
+ }
+ lh = strlen(h);
+
+ r = malloc(ld + lh + 1);
+ if (r == NULL) {
+ return NULL;
+ }
+
+ if (lh > 0) {
+ memcpy(r, h, lh);
+ }
+ memcpy(r + lh, p, ld + 1);
+
+ return r;
+}
+
+char *ssh_path_expand_escape(ssh_session session, const char *s) {
+#define MAX_BUF_SIZE 4096
+ char host[NI_MAXHOST];
+ char buf[MAX_BUF_SIZE];
+ char *r, *x = NULL;
+ const char *p;
+ size_t i, l;
+
+ r = ssh_path_expand_tilde(s);
+ if (r == NULL) {
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+
+ if (strlen(r) > MAX_BUF_SIZE) {
+ ssh_set_error(session, SSH_FATAL, "string to expand too long");
+ free(r);
+ return NULL;
+ }
+
+ p = r;
+ buf[0] = '\0';
+
+ for (i = 0; *p != '\0'; p++) {
+ if (*p != '%') {
+ buf[i] = *p;
+ i++;
+ if (i > MAX_BUF_SIZE) {
+ return NULL;
+ }
+ buf[i] = '\0';
+ continue;
+ }
+
+ p++;
+ if (*p == '\0') {
+ break;
+ }
+
+ switch (*p) {
+ case 'd':
+ x = strdup(session->sshdir);
+ break;
+ case 'u':
+ x = ssh_get_local_username(session);
+ break;
+ case 'l':
+ if (gethostname(host, sizeof(host) == 0)) {
+ x = strdup(host);
+ }
+ break;
+ case 'h':
+ x = strdup(session->host);
+ break;
+ case 'r':
+ x = strdup(session->username);
+ break;
+ case 'p':
+ if (session->port < 65536) {
+ char tmp[6];
+
+ snprintf(tmp, sizeof(tmp), "%u", session->port);
+ x = strdup(tmp);
+ }
+ break;
+ default:
+ ssh_set_error(session, SSH_FATAL,
+ "Wrong escape sequence detected");
+ return NULL;
+ }
+
+ if (x == NULL) {
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+
+ i += strlen(x);
+ if (i > MAX_BUF_SIZE) {
+ ssh_set_error(session, SSH_FATAL,
+ "String too long");
+ return NULL;
+ }
+ l = strlen(buf);
+ strcat(buf + l, x);
+ buf[i] = '\0';
+ SAFE_FREE(x);
+ }
+
+ free(r);
+ return strdup(buf);
+#undef MAX_BUF_SIZE
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/options.c b/src/options.c
new file mode 100644
index 00000000..9cbaf6f2
--- /dev/null
+++ b/src/options.c
@@ -0,0 +1,1138 @@
+/*
+ * options.c - handle pre-connection options
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 by Aris Adamantiadis
+ * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <pwd.h>
+#else
+#include <winsock2.h>
+#endif
+#include <sys/types.h>
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/misc.h"
+#ifdef WITH_SERVER
+#include "libssh/server.h"
+#endif
+
+/**
+ * @addtogroup libssh_session
+ * @{
+ */
+
+/**
+ * @brief Duplicate the options of a session structure.
+ *
+ * If you make several sessions with the same options this is useful. You
+ * cannot use twice the same option structure in ssh_session_connect.
+ *
+ * @param src The session to use to copy the options.
+ *
+ * @param dest The session to copy the options to.
+ *
+ * @returns 0 on sucess, -1 on error with errno set.
+ *
+ * @see ssh_session_connect()
+ */
+int ssh_options_copy(ssh_session src, ssh_session *dest) {
+ ssh_session new;
+ int i;
+
+ if (src == NULL || dest == NULL || *dest == NULL) {
+ return -1;
+ }
+
+ new = *dest;
+
+ if (src->username) {
+ new->username = strdup(src->username);
+ if (new->username == NULL) {
+ return -1;
+ }
+ }
+
+ if (src->host) {
+ new->host = strdup(src->host);
+ if (new->host == NULL) {
+ return -1;
+ }
+ }
+
+ if (src->identity) {
+ struct ssh_iterator *it;
+
+ new->identity = ssh_list_new();
+ if (new->identity == NULL) {
+ return -1;
+ }
+
+ it = ssh_list_get_iterator(src->identity);
+ while (it) {
+ char *id;
+ int rc;
+
+ id = strdup((char *) it->data);
+ if (id == NULL) {
+ return -1;
+ }
+
+ rc = ssh_list_append(new->identity, id);
+ if (rc < 0) {
+ return -1;
+ }
+ it = it->next;
+ }
+ }
+
+ if (src->sshdir) {
+ new->sshdir = strdup(src->sshdir);
+ if (new->sshdir == NULL) {
+ return -1;
+ }
+ }
+
+ if (src->knownhosts) {
+ new->knownhosts = strdup(src->knownhosts);
+ if (new->knownhosts == NULL) {
+ return -1;
+ }
+ }
+
+ for (i = 0; i < 10; ++i) {
+ if (src->wanted_methods[i]) {
+ new->wanted_methods[i] = strdup(src->wanted_methods[i]);
+ if (new->wanted_methods[i] == NULL) {
+ return -1;
+ }
+ }
+ }
+
+ if(src->ProxyCommand) {
+ new->ProxyCommand = strdup(src->ProxyCommand);
+ if(new->ProxyCommand == NULL)
+ return -1;
+ }
+ new->fd = src->fd;
+ new->port = src->port;
+ new->callbacks = src->callbacks;
+ new->timeout = src->timeout;
+ new->timeout_usec = src->timeout_usec;
+ new->ssh2 = src->ssh2;
+ new->ssh1 = src->ssh1;
+ new->log_verbosity = src->log_verbosity;
+
+ return 0;
+}
+
+int ssh_options_set_algo(ssh_session session, int algo,
+ const char *list) {
+ if (!verify_existing_algo(algo, list)) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Setting method: no algorithm for method \"%s\" (%s)\n",
+ ssh_kex_nums[algo], list);
+ return -1;
+ }
+
+ SAFE_FREE(session->wanted_methods[algo]);
+ session->wanted_methods[algo] = strdup(list);
+ if (session->wanted_methods[algo] == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief This function can set all possible ssh options.
+ *
+ * @param session An allocated ssh option structure.
+ *
+ * @param type The option type to set. This could be one of the
+ * following:
+ *
+ * - SSH_OPTIONS_HOST:
+ * The hostname or ip address to connect to (string).
+ *
+ * - SSH_OPTIONS_PORT:
+ * The port to connect to (integer).
+ *
+ * - SSH_OPTIONS_PORT_STR:
+ * The port to connect to (string).
+ *
+ * - SSH_OPTIONS_FD:
+ * The file descriptor to use (socket_t).\n
+ * \n
+ * If you wish to open the socket yourself for a reason
+ * or another, set the file descriptor. Don't forget to
+ * set the hostname as the hostname is used as a key in
+ * the known_host mechanism.
+ *
+ * - SSH_OPTIONS_BINDADDR:
+ * The address to bind the client to (string).
+ *
+ * - SSH_OPTIONS_USER:
+ * The username for authentication (string).\n
+ * \n
+ * If the value is NULL, the username is set to the
+ * default username.
+ *
+ * - SSH_OPTIONS_SSH_DIR:
+ * Set the ssh directory (format string).\n
+ * \n
+ * If the value is NULL, the directory is set to the
+ * default ssh directory.\n
+ * \n
+ * The ssh directory is used for files like known_hosts
+ * and identity (private and public key). It may include
+ * "%s" which will be replaced by the user home
+ * directory.
+ *
+ * - SSH_OPTIONS_KNOWNHOSTS:
+ * Set the known hosts file name (format string).\n
+ * \n
+ * If the value is NULL, the directory is set to the
+ * default known hosts file, normally
+ * ~/.ssh/known_hosts.\n
+ * \n
+ * The known hosts file is used to certify remote hosts
+ * are genuine. It may include "%s" which will be
+ * replaced by the user home directory.
+ *
+ * - SSH_OPTIONS_IDENTITY:
+ * Set the identity file name (format string).\n
+ * \n
+ * By default identity, id_dsa and id_rsa are checked.\n
+ * \n
+ * The identity file used authenticate with public key.
+ * It may include "%s" which will be replaced by the
+ * user home directory.
+ *
+ * - SSH_OPTIONS_TIMEOUT:
+ * Set a timeout for the connection in seconds (integer).
+ *
+ * - SSH_OPTIONS_TIMEOUT_USEC:
+ * Set a timeout for the connection in micro seconds
+ * (integer).
+ *
+ * - SSH_OPTIONS_SSH1:
+ * Allow or deny the connection to SSH1 servers
+ * (integer).
+ *
+ * - SSH_OPTIONS_SSH2:
+ * Allow or deny the connection to SSH2 servers
+ * (integer).
+ *
+ * - SSH_OPTIONS_LOG_VERBOSITY:
+ * Set the session logging verbosity (integer).\n
+ * \n
+ * The verbosity of the messages. Every log smaller or
+ * equal to verbosity will be shown.
+ * - SSH_LOG_NOLOG: No logging
+ * - SSH_LOG_RARE: Rare conditions or warnings
+ * - SSH_LOG_ENTRY: API-accessible entrypoints
+ * - SSH_LOG_PACKET: Packet id and size
+ * - SSH_LOG_FUNCTIONS: Function entering and leaving
+ *
+ * - SSH_OPTIONS_LOG_VERBOSITY_STR:
+ * Set the session logging verbosity (string).\n
+ * \n
+ * The verbosity of the messages. Every log smaller or
+ * equal to verbosity will be shown.
+ * - SSH_LOG_NOLOG: No logging
+ * - SSH_LOG_RARE: Rare conditions or warnings
+ * - SSH_LOG_ENTRY: API-accessible entrypoints
+ * - SSH_LOG_PACKET: Packet id and size
+ * - SSH_LOG_FUNCTIONS: Function entering and leaving
+ * \n
+ * See the corresponding numbers in libssh.h.
+ *
+ * - SSH_OPTTIONS_AUTH_CALLBACK:
+ * Set a callback to use your own authentication function
+ * (function pointer).
+ *
+ * - SSH_OPTTIONS_AUTH_USERDATA:
+ * Set the user data passed to the authentication
+ * function (generic pointer).
+ *
+ * - SSH_OPTTIONS_LOG_CALLBACK:
+ * Set a callback to use your own logging function
+ * (function pointer).
+ *
+ * - SSH_OPTTIONS_LOG_USERDATA:
+ * Set the user data passed to the logging function
+ * (generic pointer).
+ *
+ * - SSH_OPTTIONS_STATUS_CALLBACK:
+ * Set a callback to show connection status in realtime
+ * (function pointer).\n
+ * \n
+ * @code
+ * fn(void *arg, float status)
+ * @endcode
+ * \n
+ * During ssh_connect(), libssh will call the callback
+ * with status from 0.0 to 1.0.
+ *
+ * - SSH_OPTTIONS_STATUS_ARG:
+ * Set the status argument which should be passed to the
+ * status callback (generic pointer).
+ *
+ * - SSH_OPTIONS_CIPHERS_C_S:
+ * Set the symmetric cipher client to server (string,
+ * comma-separated list).
+ *
+ * - SSH_OPTIONS_CIPHERS_S_C:
+ * Set the symmetric cipher server to client (string,
+ * comma-separated list).
+ *
+ * - SSH_OPTIONS_COMPRESSION_C_S:
+ * Set the compression to use for client to server
+ * communication (string, "none" or "zlib").
+ *
+ * - SSH_OPTIONS_COMPRESSION_S_C:
+ * Set the compression to use for server to client
+ * communication (string, "none" or "zlib").
+ *
+ * - SSH_OPTIONS_HOSTKEYCHECK:
+ * Set the parameter StrictHostKeyChecking to avoid
+ * asking about a fingerprint
+ * - SSH_OPTIONS_PROXYCOMMAND:
+ * Set the command to be executed in order to connect to
+ * server.
+ *
+ * @param value The value to set. This is a generic pointer and the
+ * datatype which is used should be set according to the
+ * type set.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int ssh_options_set(ssh_session session, enum ssh_options_e type,
+ const void *value) {
+ char *p, *q;
+ long int i;
+ int rc;
+
+ if (session == NULL) {
+ return -1;
+ }
+
+ switch (type) {
+ case SSH_OPTIONS_HOST:
+ q = strdup(value);
+ if (q == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ p = strchr(q, '@');
+
+ SAFE_FREE(session->host);
+
+ if (p) {
+ *p = '\0';
+ session->host = strdup(p + 1);
+ if (session->host == NULL) {
+ SAFE_FREE(q);
+ ssh_set_error_oom(session);
+ return -1;
+ }
+
+ SAFE_FREE(session->username);
+ session->username = strdup(q);
+ SAFE_FREE(q);
+ if (session->username == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ } else {
+ session->host = q;
+ }
+ break;
+ case SSH_OPTIONS_PORT:
+ if (value == NULL) {
+ session->port = 22 & 0xffff;
+ } else {
+ int *x = (int *) value;
+
+ session->port = *x & 0xffff;
+ }
+ break;
+ case SSH_OPTIONS_PORT_STR:
+ if (value == NULL) {
+ session->port = 22 & 0xffff;
+ } else {
+ q = strdup(value);
+ if (q == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ i = strtol(q, &p, 10);
+ if (q == p) {
+ SAFE_FREE(q);
+ }
+ SAFE_FREE(q);
+
+ session->port = i & 0xffff;
+ }
+ break;
+ case SSH_OPTIONS_FD:
+ if (value == NULL) {
+ session->fd = SSH_INVALID_SOCKET;
+ } else {
+ socket_t *x = (socket_t *) value;
+
+ session->fd = *x & 0xffff;
+ }
+ break;
+ case SSH_OPTIONS_BINDADDR:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ }
+ q = strdup(value);
+ if (q == NULL) {
+ return -1;
+ }
+ SAFE_FREE(session->bindaddr);
+ session->bindaddr = q;
+ break;
+ case SSH_OPTIONS_USER:
+ SAFE_FREE(session->username);
+ if (value == NULL) { /* set default username */
+ q = ssh_get_local_username(session);
+ if (q == NULL) {
+ return -1;
+ }
+ session->username = q;
+ } else { /* username provided */
+ session->username = strdup(value);
+ if (session->username == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ }
+ break;
+ case SSH_OPTIONS_SSH_DIR:
+ if (value == NULL) {
+ SAFE_FREE(session->sshdir);
+
+ session->sshdir = ssh_path_expand_tilde("~/.ssh");
+ if (session->sshdir == NULL) {
+ return -1;
+ }
+ } else {
+ SAFE_FREE(session->sshdir);
+ session->sshdir = ssh_path_expand_tilde(value);
+ if (session->sshdir == NULL) {
+ return -1;
+ }
+ }
+ break;
+ case SSH_OPTIONS_IDENTITY:
+ case SSH_OPTIONS_ADD_IDENTITY:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ }
+ q = strdup(value);
+ if (q == NULL) {
+ return -1;
+ }
+ rc = ssh_list_prepend(session->identity, q);
+ if (rc < 0) {
+ return -1;
+ }
+ break;
+ case SSH_OPTIONS_KNOWNHOSTS:
+ if (value == NULL) {
+ SAFE_FREE(session->knownhosts);
+ if (session->sshdir == NULL) {
+ return -1;
+ }
+ session->knownhosts = ssh_path_expand_escape(session, "%d/known_hosts");
+ if (session->knownhosts == NULL) {
+ return -1;
+ }
+ } else {
+ SAFE_FREE(session->knownhosts);
+ session->knownhosts = strdup(value);
+ if (session->knownhosts == NULL) {
+ return -1;
+ }
+ }
+ break;
+ case SSH_OPTIONS_TIMEOUT:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ long *x = (long *) value;
+
+ session->timeout = *x & 0xffffffff;
+ }
+ break;
+ case SSH_OPTIONS_TIMEOUT_USEC:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ long *x = (long *) value;
+
+ session->timeout_usec = *x & 0xffffffff;
+ }
+ break;
+ case SSH_OPTIONS_SSH1:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ int *x = (int *) value;
+ session->ssh1 = *x;
+ }
+ break;
+ case SSH_OPTIONS_SSH2:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ int *x = (int *) value;
+ session->ssh2 = *x & 0xffff;
+ }
+ break;
+ case SSH_OPTIONS_LOG_VERBOSITY:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ int *x = (int *) value;
+
+ session->log_verbosity = *x & 0xffff;
+ }
+ break;
+ case SSH_OPTIONS_LOG_VERBOSITY_STR:
+ if (value == NULL) {
+ session->log_verbosity = 0 & 0xffff;
+ } else {
+ q = strdup(value);
+ if (q == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ i = strtol(q, &p, 10);
+ if (q == p) {
+ SAFE_FREE(q);
+ }
+ SAFE_FREE(q);
+
+ session->log_verbosity = i & 0xffff;
+ }
+ break;
+ case SSH_OPTIONS_CIPHERS_C_S:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ if (ssh_options_set_algo(session, SSH_CRYPT_C_S, value) < 0)
+ return -1;
+ }
+ break;
+ case SSH_OPTIONS_CIPHERS_S_C:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ if (ssh_options_set_algo(session, SSH_CRYPT_S_C, value) < 0)
+ return -1;
+ }
+ break;
+ case SSH_OPTIONS_COMPRESSION_C_S:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ if (ssh_options_set_algo(session, SSH_COMP_C_S, value) < 0)
+ return -1;
+ }
+ break;
+ case SSH_OPTIONS_COMPRESSION_S_C:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ if (ssh_options_set_algo(session, SSH_COMP_S_C, value) < 0)
+ return -1;
+ }
+ break;
+ case SSH_OPTIONS_HOSTKEYCHECK:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ session->StrictHostKeyChecking = *(int*)value;
+ }
+ break;
+ case SSH_OPTIONS_PROXYCOMMAND:
+ if (value == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ } else {
+ SAFE_FREE(session->ProxyCommand);
+ q = strdup(value);
+ if (q == NULL) {
+ return -1;
+ }
+ session->ProxyCommand = q;
+ }
+ break;
+ default:
+ ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
+ return -1;
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * @brief Parse command line arguments.
+ *
+ * This is a helper for your application to generate the appropriate
+ * options from the command line arguments.\n
+ * The argv array and argc value are changed so that the parsed
+ * arguments wont appear anymore in them.\n
+ * The single arguments (without switches) are not parsed. thus,
+ * myssh -l user localhost\n
+ * The command wont set the hostname value of options to localhost.
+ *
+ * @param session The session to configure.
+ *
+ * @param argcptr The pointer to the argument count.
+ *
+ * @param argv The arguments list pointer.
+ *
+ * @returns 0 on success, < 0 on error.
+ *
+ * @see ssh_session_new()
+ */
+int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) {
+ char *user = NULL;
+ char *cipher = NULL;
+ char *localaddr = NULL;
+ char *identity = NULL;
+ char *port = NULL;
+ char *bindport = NULL;
+ char **save = NULL;
+ int i = 0;
+ int argc = *argcptr;
+ int debuglevel = 0;
+ int usersa = 0;
+ int usedss = 0;
+ int compress = 0;
+ int cont = 1;
+ int current = 0;
+#ifdef WITH_SSH1
+ int ssh1 = 1;
+#else
+ int ssh1 = 0;
+#endif
+ int ssh2 = 1;
+#ifdef _MSC_VER
+ /* Not supported with a Microsoft compiler */
+ return -1;
+#else
+ int saveoptind = optind; /* need to save 'em */
+ int saveopterr = opterr;
+
+ save = malloc(argc * sizeof(char *));
+ if (save == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+
+ opterr = 0; /* shut up getopt */
+ while(cont && ((i = getopt(argc, argv, "c:i:Cl:p:vb:t:rd12")) != -1)) {
+ switch(i) {
+ case 'l':
+ user = optarg;
+ break;
+ case 'p':
+ port = optarg;
+ break;
+ case 't':
+ bindport = optarg;
+ break;
+ case 'v':
+ debuglevel++;
+ break;
+ case 'r':
+ usersa++;
+ break;
+ case 'd':
+ usedss++;
+ break;
+ case 'c':
+ cipher = optarg;
+ break;
+ case 'i':
+ identity = optarg;
+ break;
+ case 'b':
+ localaddr = optarg;
+ break;
+ case 'C':
+ compress++;
+ break;
+ case '2':
+ ssh2 = 1;
+ ssh1 = 0;
+ break;
+ case '1':
+ ssh2 = 0;
+ ssh1 = 1;
+ break;
+ default:
+ {
+ char opt[3]="- ";
+ opt[1] = optopt;
+ save[current] = strdup(opt);
+ if (save[current] == NULL) {
+ SAFE_FREE(save);
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ current++;
+ if (optarg) {
+ save[current++] = argv[optind + 1];
+ }
+ }
+ } /* switch */
+ } /* while */
+ opterr = saveopterr;
+ while (optind < argc) {
+ save[current++] = argv[optind++];
+ }
+
+ if (usersa && usedss) {
+ ssh_set_error(session, SSH_FATAL, "Either RSA or DSS must be chosen");
+ cont = 0;
+ }
+
+ ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &debuglevel);
+
+ optind = saveoptind;
+
+ if(!cont) {
+ SAFE_FREE(save);
+ return -1;
+ }
+
+ /* first recopy the save vector into the original's */
+ for (i = 0; i < current; i++) {
+ /* don't erase argv[0] */
+ argv[ i + 1] = save[i];
+ }
+ argv[current + 1] = NULL;
+ *argcptr = current + 1;
+ SAFE_FREE(save);
+
+ /* set a new option struct */
+ if (compress) {
+ if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib,none") < 0) {
+ cont = 0;
+ }
+ if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib,none") < 0) {
+ cont = 0;
+ }
+ }
+
+ if (cont && cipher) {
+ if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) < 0) {
+ cont = 0;
+ }
+ if (cont && ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher) < 0) {
+ cont = 0;
+ }
+ }
+
+ if (cont && user) {
+ if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) {
+ cont = 0;
+ }
+ }
+
+ if (cont && identity) {
+ if (ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity) < 0) {
+ cont = 0;
+ }
+ }
+
+ ssh_options_set(session, SSH_OPTIONS_PORT_STR, port);
+
+ ssh_options_set(session, SSH_OPTIONS_SSH1, &ssh1);
+ ssh_options_set(session, SSH_OPTIONS_SSH2, &ssh2);
+
+ if (!cont) {
+ return SSH_ERROR;
+ }
+
+ return SSH_OK;
+#endif
+}
+
+/**
+ * @brief Parse the ssh config file.
+ *
+ * This should be the last call of all options, it may overwrite options which
+ * are already set. It requires that the host name is already set with
+ * ssh_options_set_host().
+ *
+ * @param session SSH session handle
+ *
+ * @param filename The options file to use, if NULL the default
+ * ~/.ssh/config will be used.
+ *
+ * @return 0 on success, < 0 on error.
+ *
+ * @see ssh_options_set_host()
+ */
+int ssh_options_parse_config(ssh_session session, const char *filename) {
+ char *expanded_filename;
+ int r;
+
+ if (session == NULL) {
+ return -1;
+ }
+ if (session->host == NULL) {
+ ssh_set_error_invalid(session, __FUNCTION__);
+ return -1;
+ }
+
+ if (session->sshdir == NULL) {
+ r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL);
+ if (r < 0) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ }
+
+ /* set default filename */
+ if (filename == NULL) {
+ expanded_filename = ssh_path_expand_escape(session, "%d/config");
+ } else {
+ expanded_filename = ssh_path_expand_escape(session, filename);
+ }
+ if (expanded_filename == NULL) {
+ return -1;
+ }
+
+ r = ssh_config_parse_file(session, expanded_filename);
+ if (r < 0) {
+ goto out;
+ }
+ if (filename == NULL) {
+ r = ssh_config_parse_file(session, "/etc/ssh/ssh_config");
+ }
+
+out:
+ free(expanded_filename);
+ return r;
+}
+
+int ssh_options_apply(ssh_session session) {
+ struct ssh_iterator *it;
+ char *tmp;
+ int rc;
+
+ if (session->sshdir == NULL) {
+ rc = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL);
+ if (rc < 0) {
+ return -1;
+ }
+ }
+
+ if (session->username == NULL) {
+ rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL);
+ if (rc < 0) {
+ return -1;
+ }
+ }
+
+ if (session->knownhosts == NULL) {
+ tmp = ssh_path_expand_escape(session, "%d/known_hosts");
+ } else {
+ tmp = ssh_path_expand_escape(session, session->knownhosts);
+ }
+ if (tmp == NULL) {
+ return -1;
+ }
+ free(session->knownhosts);
+ session->knownhosts = tmp;
+
+ if (session->ProxyCommand != NULL) {
+ tmp = ssh_path_expand_escape(session, session->ProxyCommand);
+ if (tmp == NULL) {
+ return -1;
+ }
+ free(session->ProxyCommand);
+ session->ProxyCommand = tmp;
+ }
+
+ for (it = ssh_list_get_iterator(session->identity);
+ it != NULL;
+ it = it->next) {
+ char *id = (char *) it->data;
+ tmp = ssh_path_expand_escape(session, id);
+ if (tmp == NULL) {
+ return -1;
+ }
+ free(id);
+ it->data = tmp;
+ }
+
+ return 0;
+}
+
+/** @} */
+
+#ifdef WITH_SERVER
+/**
+ * @addtogroup libssh_server
+ * @{
+ */
+static int ssh_bind_options_set_algo(ssh_bind sshbind, int algo,
+ const char *list) {
+ if (!verify_existing_algo(algo, list)) {
+ ssh_set_error(sshbind, SSH_REQUEST_DENIED,
+ "Setting method: no algorithm for method \"%s\" (%s)\n",
+ ssh_kex_nums[algo], list);
+ return -1;
+ }
+
+ SAFE_FREE(sshbind->wanted_methods[algo]);
+ sshbind->wanted_methods[algo] = strdup(list);
+ if (sshbind->wanted_methods[algo] == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief This function can set all possible ssh bind options.
+ *
+ * @param session An allocated ssh option structure.
+ *
+ * @param type The option type to set. This could be one of the
+ * following:
+ *
+ * SSH_BIND_OPTIONS_LOG_VERBOSITY:
+ * Set the session logging verbosity (integer).
+ *
+ * The verbosity of the messages. Every log smaller or
+ * equal to verbosity will be shown.
+ * SSH_LOG_NOLOG: No logging
+ * SSH_LOG_RARE: Rare conditions or warnings
+ * SSH_LOG_ENTRY: API-accessible entrypoints
+ * SSH_LOG_PACKET: Packet id and size
+ * SSH_LOG_FUNCTIONS: Function entering and leaving
+ *
+ * SSH_BIND_OPTIONS_LOG_VERBOSITY_STR:
+ * Set the session logging verbosity (integer).
+ *
+ * The verbosity of the messages. Every log smaller or
+ * equal to verbosity will be shown.
+ * SSH_LOG_NOLOG: No logging
+ * SSH_LOG_RARE: Rare conditions or warnings
+ * SSH_LOG_ENTRY: API-accessible entrypoints
+ * SSH_LOG_PACKET: Packet id and size
+ * SSH_LOG_FUNCTIONS: Function entering and leaving
+ *
+ * SSH_BIND_OPTIONS_BINDADDR:
+ * Set the bind address.
+ *
+ * SSH_BIND_OPTIONS_BINDPORT:
+ * Set the bind port, default is 22.
+ *
+ * SSH_BIND_OPTIONS_HOSTKEY:
+ * Set the server public key type: ssh-rsa or ssh-dss
+ * (string).
+ *
+ * SSH_BIND_OPTIONS_DSAKEY:
+ * Set the path to the dsa ssh host key (string).
+ *
+ * SSH_BIND_OPTIONS_RSAKEY:
+ * Set the path to the ssh host rsa key (string).
+ *
+ * SSH_BIND_OPTIONS_BANNER:
+ * Set the server banner sent to clients (string).
+ *
+ * @param value The value to set. This is a generic pointer and the
+ * datatype which is used should be set according to the
+ * type set.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type,
+ const void *value) {
+ char *p, *q;
+ int i;
+
+ if (sshbind == NULL) {
+ return -1;
+ }
+
+ switch (type) {
+ case SSH_BIND_OPTIONS_HOSTKEY:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ if (ssh_bind_options_set_algo(sshbind, SSH_HOSTKEYS, value) < 0)
+ return -1;
+ }
+ break;
+ case SSH_BIND_OPTIONS_BINDADDR:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ SAFE_FREE(sshbind->bindaddr);
+ sshbind->bindaddr = strdup(value);
+ if (sshbind->bindaddr == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+ }
+ break;
+ case SSH_BIND_OPTIONS_BINDPORT:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ int *x = (int *) value;
+ sshbind->bindport = *x & 0xffff;
+ }
+ break;
+ case SSH_BIND_OPTIONS_BINDPORT_STR:
+ if (value == NULL) {
+ sshbind->bindport = 22 & 0xffff;
+ } else {
+ q = strdup(value);
+ if (q == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+ i = strtol(q, &p, 10);
+ if (q == p) {
+ SAFE_FREE(q);
+ }
+ SAFE_FREE(q);
+
+ sshbind->bindport = i & 0xffff;
+ }
+ break;
+ case SSH_BIND_OPTIONS_LOG_VERBOSITY:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ int *x = (int *) value;
+ sshbind->log_verbosity = *x & 0xffff;
+ }
+ break;
+ case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR:
+ if (value == NULL) {
+ sshbind->log_verbosity = 0;
+ } else {
+ q = strdup(value);
+ if (q == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+ i = strtol(q, &p, 10);
+ if (q == p) {
+ SAFE_FREE(q);
+ }
+ SAFE_FREE(q);
+
+ sshbind->log_verbosity = i & 0xffff;
+ }
+ break;
+ case SSH_BIND_OPTIONS_DSAKEY:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ SAFE_FREE(sshbind->dsakey);
+ sshbind->dsakey = strdup(value);
+ if (sshbind->dsakey == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+ }
+ break;
+ case SSH_BIND_OPTIONS_RSAKEY:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ SAFE_FREE(sshbind->rsakey);
+ sshbind->rsakey = strdup(value);
+ if (sshbind->rsakey == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+ }
+ break;
+ case SSH_BIND_OPTIONS_BANNER:
+ if (value == NULL) {
+ ssh_set_error_invalid(sshbind, __FUNCTION__);
+ return -1;
+ } else {
+ SAFE_FREE(sshbind->banner);
+ sshbind->banner = strdup(value);
+ if (sshbind->banner == NULL) {
+ ssh_set_error_oom(sshbind);
+ return -1;
+ }
+ }
+ break;
+ default:
+ ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
+ return -1;
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/packet.c b/src/packet.c
new file mode 100644
index 00000000..a97db93b
--- /dev/null
+++ b/src/packet.c
@@ -0,0 +1,529 @@
+/*
+ * packet.c - packet building functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/crypto.h"
+#include "libssh/buffer.h"
+#include "libssh/packet.h"
+#include "libssh/socket.h"
+#include "libssh/channels.h"
+#include "libssh/misc.h"
+#include "libssh/session.h"
+#include "libssh/messages.h"
+#include "libssh/pcap.h"
+#include "libssh/kex.h"
+#include "libssh/auth.h"
+
+ssh_packet_callback default_packet_handlers[]= {
+ ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1
+ ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2
+ ssh_packet_unimplemented, // SSH2_MSG_UNIMPLEMENTED 3
+ ssh_packet_ignore_callback, // SSH2_MSG_DEBUG 4
+ ssh_packet_service_request, // SSH2_MSG_SERVICE_REQUEST 5
+ ssh_packet_service_accept, // SSH2_MSG_SERVICE_ACCEPT 6
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, // 7-19
+ ssh_packet_kexinit, // SSH2_MSG_KEXINIT 20
+ ssh_packet_newkeys, // SSH2_MSG_NEWKEYS 21
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, // 22-29
+#if WITH_SERVER
+ ssh_packet_kexdh_init, // SSH2_MSG_KEXDH_INIT 30
+ // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30
+#else
+ NULL,
+#endif
+ ssh_packet_dh_reply, // SSH2_MSG_KEXDH_REPLY 31
+ // SSH2_MSG_KEX_DH_GEX_GROUP 31
+ NULL, // SSH2_MSG_KEX_DH_GEX_INIT 32
+ NULL, // SSH2_MSG_KEX_DH_GEX_REPLY 33
+ NULL, // SSH2_MSG_KEX_DH_GEX_REQUEST 34
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, // 35-49
+ ssh_packet_userauth_request, // SSH2_MSG_USERAUTH_REQUEST 50
+ ssh_packet_userauth_failure, // SSH2_MSG_USERAUTH_FAILURE 51
+ ssh_packet_userauth_success, // SSH2_MSG_USERAUTH_SUCCESS 52
+ ssh_packet_userauth_banner, // SSH2_MSG_USERAUTH_BANNER 53
+ NULL,NULL,NULL,NULL,NULL,NULL, // 54-59
+ 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
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, // 62-79
+ NULL, // SSH2_MSG_GLOBAL_REQUEST 80
+ ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81
+ ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89
+ ssh_packet_channel_open, // SSH2_MSG_CHANNEL_OPEN 90
+ ssh_packet_channel_open_conf, // SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91
+ ssh_packet_channel_open_fail, // SSH2_MSG_CHANNEL_OPEN_FAILURE 92
+ channel_rcv_change_window, // SSH2_MSG_CHANNEL_WINDOW_ADJUST 93
+ channel_rcv_data, // SSH2_MSG_CHANNEL_DATA 94
+ channel_rcv_data, // SSH2_MSG_CHANNEL_EXTENDED_DATA 95
+ channel_rcv_eof, // SSH2_MSG_CHANNEL_EOF 96
+ channel_rcv_close, // SSH2_MSG_CHANNEL_CLOSE 97
+ channel_rcv_request, // SSH2_MSG_CHANNEL_REQUEST 98
+ ssh_packet_channel_success, // SSH2_MSG_CHANNEL_SUCCESS 99
+ ssh_packet_channel_failure, // SSH2_MSG_CHANNEL_FAILURE 100
+};
+
+/* XXX include selected mac size */
+static int macsize=SHA_DIGEST_LEN;
+
+/* in nonblocking mode, socket_read will read as much as it can, and return */
+/* SSH_OK if it has read at least len bytes, otherwise, SSH_AGAIN. */
+/* in blocking mode, it will read at least len bytes and will block until it's ok. */
+
+/** @internal
+ * @handles a data received event. It then calls the handlers for the different packet types
+ * or and exception handler callback.
+ * @param user pointer to current ssh_session
+ * @param data pointer to the data received
+ * @len length of data received. It might not be enough for a complete packet
+ * @returns number of bytes read and processed.
+ */
+int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user){
+ ssh_session session=(ssh_session) user;
+ unsigned int blocksize = (session->current_crypto ?
+ session->current_crypto->in_cipher->blocksize : 8);
+ int current_macsize = session->current_crypto ? macsize : 0;
+ unsigned char mac[30] = {0};
+ char buffer[16] = {0};
+ void *packet=NULL;
+ int to_be_read;
+ int rc;
+ uint32_t len;
+ uint8_t padding;
+ size_t processed=0; /* number of byte processed from the callback */
+
+ enter_function();
+
+ switch(session->packet_state) {
+ case PACKET_STATE_INIT:
+ if(receivedlen < blocksize){
+ /* We didn't receive enough data to read at least one block size, give up */
+ leave_function();
+ return 0;
+ }
+ memset(&session->in_packet, 0, sizeof(PACKET));
+
+ if (session->in_buffer) {
+ if (buffer_reinit(session->in_buffer) < 0) {
+ goto error;
+ }
+ } else {
+ session->in_buffer = ssh_buffer_new();
+ if (session->in_buffer == NULL) {
+ goto error;
+ }
+ }
+
+ memcpy(buffer,data,blocksize);
+ processed += blocksize;
+ len = packet_decrypt_len(session, buffer);
+
+ if (buffer_add_data(session->in_buffer, buffer, blocksize) < 0) {
+ goto error;
+ }
+
+ if(len > MAX_PACKET_LEN) {
+ ssh_set_error(session, SSH_FATAL,
+ "read_packet(): Packet len too high(%u %.4x)", len, len);
+ goto error;
+ }
+
+ to_be_read = len - blocksize + sizeof(uint32_t);
+ if (to_be_read < 0) {
+ /* remote sshd sends invalid sizes? */
+ ssh_set_error(session, SSH_FATAL,
+ "given numbers of bytes left to be read < 0 (%d)!", to_be_read);
+ goto error;
+ }
+
+ /* saves the status of the current operations */
+ session->in_packet.len = len;
+ session->packet_state = PACKET_STATE_SIZEREAD;
+ case PACKET_STATE_SIZEREAD:
+ len = session->in_packet.len;
+ to_be_read = len - blocksize + sizeof(uint32_t) + current_macsize;
+ /* if to_be_read is zero, the whole packet was blocksize bytes. */
+ if (to_be_read != 0) {
+ if(receivedlen - processed < (unsigned int)to_be_read){
+ /* give up, not enough data in buffer */
+ return processed;
+ }
+
+ packet = (unsigned char *)data + processed;
+// ssh_socket_read(session->socket,packet,to_be_read-current_macsize);
+
+ ssh_log(session,SSH_LOG_PACKET,"Read a %d bytes packet",len);
+
+ if (buffer_add_data(session->in_buffer, packet,
+ to_be_read - current_macsize) < 0) {
+ goto error;
+ }
+ processed += to_be_read - current_macsize;
+ }
+
+ if (session->current_crypto) {
+ /*
+ * decrypt the rest of the packet (blocksize bytes already
+ * have been decrypted)
+ */
+ if (packet_decrypt(session,
+ ((uint8_t*)ssh_buffer_get_begin(session->in_buffer) + blocksize),
+ ssh_buffer_get_len(session->in_buffer) - blocksize) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Decrypt error");
+ goto error;
+ }
+ /* copy the last part from the incoming buffer */
+ memcpy(mac,(unsigned char *)packet + to_be_read - current_macsize, macsize);
+
+ if (packet_hmac_verify(session, session->in_buffer, mac) < 0) {
+ ssh_set_error(session, SSH_FATAL, "HMAC error");
+ goto error;
+ }
+ processed += current_macsize;
+ }
+
+ /* skip the size field which has been processed before */
+ buffer_pass_bytes(session->in_buffer, sizeof(uint32_t));
+
+ if (buffer_get_u8(session->in_buffer, &padding) == 0) {
+ ssh_set_error(session, SSH_FATAL, "Packet too short to read padding");
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "%hhd bytes padding, %d bytes left in buffer",
+ padding, buffer_get_rest_len(session->in_buffer));
+
+ if (padding > buffer_get_rest_len(session->in_buffer)) {
+ ssh_set_error(session, SSH_FATAL,
+ "Invalid padding: %d (%d resting)",
+ padding,
+ buffer_get_rest_len(session->in_buffer));
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("incrimined packet",
+ ssh_buffer_get_begin(session->in_buffer),
+ ssh_buffer_get_len(session->in_buffer));
+#endif
+ goto error;
+ }
+ buffer_pass_bytes_end(session->in_buffer, padding);
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "After padding, %d bytes left in buffer",
+ buffer_get_rest_len(session->in_buffer));
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+ if (session->current_crypto && session->current_crypto->do_compress_in) {
+ ssh_log(session, SSH_LOG_PACKET, "Decompressing in_buffer ...");
+ if (decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN) < 0) {
+ goto error;
+ }
+ }
+#endif
+ session->recv_seq++;
+ /* We don't want to rewrite a new packet while still executing the packet callbacks */
+ session->packet_state = PACKET_STATE_PROCESSING;
+ ssh_packet_parse_type(session);
+ /* execute callbacks */
+ ssh_packet_process(session, session->in_packet.type);
+ session->packet_state = PACKET_STATE_INIT;
+ if(processed < receivedlen){
+ /* Handle a potential packet left in socket buffer */
+ ssh_log(session,SSH_LOG_PACKET,"Processing %" PRIdS " bytes left in socket buffer",
+ receivedlen-processed);
+ rc = ssh_packet_socket_callback((char *)data + processed,
+ receivedlen - processed,user);
+ processed += rc;
+ }
+ leave_function();
+ return processed;
+ case PACKET_STATE_PROCESSING:
+ ssh_log(session, SSH_LOG_RARE, "Nested packet processing. Delaying.");
+ return 0;
+ }
+
+ ssh_set_error(session, SSH_FATAL,
+ "Invalid state into packet_read2(): %d",
+ session->packet_state);
+
+error:
+ leave_function();
+ return processed;
+}
+
+void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s){
+ session->socket_callbacks.data=ssh_packet_socket_callback;
+ session->socket_callbacks.connected=NULL;
+ session->socket_callbacks.controlflow=NULL;
+ session->socket_callbacks.exception=NULL;
+ session->socket_callbacks.userdata=session;
+ ssh_socket_set_callbacks(s,&session->socket_callbacks);
+}
+
+/** @internal
+ * @brief sets the callbacks for the packet layer
+ */
+void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){
+ if(session->packet_callbacks == NULL){
+ session->packet_callbacks = ssh_list_new();
+ }
+ ssh_list_append(session->packet_callbacks, callbacks);
+}
+
+/** @internal
+ * @brief sets the default packet handlers
+ */
+void ssh_packet_set_default_callbacks(ssh_session session){
+#ifdef WITH_SSH1
+ if(session->version==1){
+ ssh_packet_set_default_callbacks1(session);
+ return;
+ }
+#endif
+ session->default_packet_callbacks.start=1;
+ session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback);
+ session->default_packet_callbacks.user=session;
+ session->default_packet_callbacks.callbacks=default_packet_handlers;
+ ssh_packet_set_callbacks(session, &session->default_packet_callbacks);
+}
+
+/** @internal
+ * @brief dispatch the call of packet handlers callbacks for a received packet
+ * @param type type of packet
+ */
+void ssh_packet_process(ssh_session session, uint8_t type){
+ struct ssh_iterator *i;
+ int r=SSH_PACKET_NOT_USED;
+ ssh_packet_callbacks cb;
+ enter_function();
+ ssh_log(session,SSH_LOG_PACKET, "Dispatching handler for packet type %d",type);
+ if(session->packet_callbacks == NULL){
+ ssh_log(session,SSH_LOG_RARE,"Packet callback is not initialized !");
+ goto error;
+ }
+ i=ssh_list_get_iterator(session->packet_callbacks);
+ while(i != NULL){
+ cb=ssh_iterator_value(ssh_packet_callbacks,i);
+ i=i->next;
+ if(!cb)
+ continue;
+ if(cb->start > type)
+ continue;
+ if(cb->start + cb->n_callbacks <= type)
+ continue;
+ if(cb->callbacks[type - cb->start]==NULL)
+ continue;
+ r=cb->callbacks[type - cb->start](session,type,session->in_buffer,cb->user);
+ if(r==SSH_PACKET_USED)
+ break;
+ }
+ if(r==SSH_PACKET_NOT_USED){
+ ssh_log(session,SSH_LOG_RARE,"Couldn't do anything with packet type %d",type);
+ ssh_packet_send_unimplemented(session, session->recv_seq-1);
+ }
+error:
+ leave_function();
+}
+
+/** @internal
+ * @brief sends a SSH_MSG_UNIMPLEMENTED answer to an unhandled packet
+ * @param session the SSH session
+ * @param seqnum the sequence number of the unknown packet
+ * @return SSH_ERROR on error, else SSH_OK
+ */
+int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum){
+ int r;
+ enter_function();
+ buffer_add_u8(session->out_buffer, SSH2_MSG_UNIMPLEMENTED);
+ buffer_add_u32(session->out_buffer, htonl(seqnum));
+ r = packet_send(session);
+ leave_function();
+ return r;
+}
+
+/** @internal
+ * @brief handles a SSH_MSG_UNIMPLEMENTED packet
+ */
+SSH_PACKET_CALLBACK(ssh_packet_unimplemented){
+ uint32_t seq;
+ (void)type;
+ (void)user;
+ buffer_get_u32(packet,&seq);
+ seq=ntohl(seq);
+ ssh_log(session,SSH_LOG_RARE,
+ "Received SSH_MSG_UNIMPLEMENTED (sequence number %d)",seq);
+ return SSH_PACKET_USED;
+}
+
+/** @internal
+ * @parse the "Type" header field of a packet and updates the session
+ */
+int ssh_packet_parse_type(ssh_session session) {
+ enter_function();
+
+ memset(&session->in_packet, 0, sizeof(PACKET));
+ if(session->in_buffer == NULL) {
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Final size %d",
+ buffer_get_rest_len(session->in_buffer));
+
+ if(buffer_get_u8(session->in_buffer, &session->in_packet.type) == 0) {
+ ssh_set_error(session, SSH_FATAL, "Packet too short to read type");
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Type %hhd", session->in_packet.type);
+ session->in_packet.valid = 1;
+
+ leave_function();
+ return SSH_OK;
+}
+
+/*
+ * This function places the outgoing packet buffer into an outgoing
+ * socket buffer
+ */
+static int ssh_packet_write(ssh_session session) {
+ int rc = SSH_ERROR;
+
+ enter_function();
+
+ rc=ssh_socket_write(session->socket,
+ ssh_buffer_get_begin(session->out_buffer),
+ ssh_buffer_get_len(session->out_buffer));
+ if(rc == SSH_OK){
+ rc=ssh_socket_nonblocking_flush(session->socket);
+ }
+ leave_function();
+ return rc;
+}
+
+static int packet_send2(ssh_session session) {
+ unsigned int blocksize = (session->current_crypto ?
+ session->current_crypto->out_cipher->blocksize : 8);
+ uint32_t currentlen = ssh_buffer_get_len(session->out_buffer);
+ unsigned char *hmac = NULL;
+ char padstring[32] = {0};
+ int rc = SSH_ERROR;
+ uint32_t finallen;
+ uint8_t padding;
+
+ enter_function();
+
+ ssh_log(session, SSH_LOG_PACKET,
+ "Writing on the wire a packet having %u bytes before", currentlen);
+
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+ if (session->current_crypto && session->current_crypto->do_compress_out) {
+ ssh_log(session, SSH_LOG_PACKET, "Compressing in_buffer ...");
+ if (compress_buffer(session,session->out_buffer) < 0) {
+ goto error;
+ }
+ currentlen = ssh_buffer_get_len(session->out_buffer);
+ }
+#endif
+ padding = (blocksize - ((currentlen +5) % blocksize));
+ if(padding < 4) {
+ padding += blocksize;
+ }
+
+ if (session->current_crypto) {
+ ssh_get_random(padstring, padding, 0);
+ } else {
+ memset(padstring,0,padding);
+ }
+
+ finallen = htonl(currentlen + padding + 1);
+ ssh_log(session, SSH_LOG_PACKET,
+ "%d bytes after comp + %d padding bytes = %lu bytes packet",
+ currentlen, padding, (long unsigned int) ntohl(finallen));
+
+ if (buffer_prepend_data(session->out_buffer, &padding, sizeof(uint8_t)) < 0) {
+ goto error;
+ }
+ if (buffer_prepend_data(session->out_buffer, &finallen, sizeof(uint32_t)) < 0) {
+ goto error;
+ }
+ if (buffer_add_data(session->out_buffer, padstring, padding) < 0) {
+ goto error;
+ }
+#ifdef WITH_PCAP
+ if(session->pcap_ctx){
+ ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT,
+ ssh_buffer_get_begin(session->out_buffer),ssh_buffer_get_len(session->out_buffer)
+ ,ssh_buffer_get_len(session->out_buffer));
+ }
+#endif
+ hmac = packet_encrypt(session, ssh_buffer_get_begin(session->out_buffer),
+ ssh_buffer_get_len(session->out_buffer));
+ if (hmac) {
+ if (buffer_add_data(session->out_buffer, hmac, 20) < 0) {
+ goto error;
+ }
+ }
+
+ rc = ssh_packet_write(session);
+ session->send_seq++;
+
+ if (buffer_reinit(session->out_buffer) < 0) {
+ rc = SSH_ERROR;
+ }
+error:
+ leave_function();
+ return rc; /* SSH_OK, AGAIN or ERROR */
+}
+
+
+int packet_send(ssh_session session) {
+#ifdef WITH_SSH1
+ if (session->version == 1) {
+ return packet_send1(session);
+ }
+#endif
+ return packet_send2(session);
+}
+
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/packet1.c b/src/packet1.c
new file mode 100644
index 00000000..d4b2eaef
--- /dev/null
+++ b/src/packet1.c
@@ -0,0 +1,362 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2010 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 "libssh/priv.h"
+#include "libssh/ssh1.h"
+#include "libssh/packet.h"
+#include "libssh/session.h"
+#include "libssh/buffer.h"
+#include "libssh/socket.h"
+#include "libssh/kex.h"
+#ifdef WITH_SSH1
+
+ssh_packet_callback default_packet_handlers1[]= {
+ NULL, //SSH_MSG_NONE 0
+ ssh_packet_disconnect1, //SSH_MSG_DISCONNECT 1
+ ssh_packet_publickey1, //SSH_SMSG_PUBLIC_KEY 2
+ NULL, //SSH_CMSG_SESSION_KEY 3
+ NULL, //SSH_CMSG_USER 4
+ NULL, //SSH_CMSG_AUTH_RHOSTS 5
+ NULL, //SSH_CMSG_AUTH_RSA 6
+ NULL, //SSH_SMSG_AUTH_RSA_CHALLENGE 7
+ NULL, //SSH_CMSG_AUTH_RSA_RESPONSE 8
+ NULL, //SSH_CMSG_AUTH_PASSWORD 9
+ NULL, //SSH_CMSG_REQUEST_PTY 10
+ NULL, //SSH_CMSG_WINDOW_SIZE 11
+ NULL, //SSH_CMSG_EXEC_SHELL 12
+ NULL, //SSH_CMSG_EXEC_CMD 13
+ ssh_packet_smsg_success1, //SSH_SMSG_SUCCESS 14
+ ssh_packet_smsg_failure1, //SSH_SMSG_FAILURE 15
+ NULL, //SSH_CMSG_STDIN_DATA 16
+ ssh_packet_data1, //SSH_SMSG_STDOUT_DATA 17
+ ssh_packet_data1, //SSH_SMSG_STDERR_DATA 18
+ NULL, //SSH_CMSG_EOF 19
+ NULL, //SSH_SMSG_EXITSTATUS 20
+ NULL, //SSH_MSG_CHANNEL_OPEN_CONFIRMATION 21
+ NULL, //SSH_MSG_CHANNEL_OPEN_FAILURE 22
+ NULL, //SSH_MSG_CHANNEL_DATA 23
+ ssh_packet_close1, //SSH_MSG_CHANNEL_CLOSE 24
+ NULL, //SSH_MSG_CHANNEL_CLOSE_CONFIRMATION 25
+ NULL, //SSH_CMSG_X11_REQUEST_FORWARDING 26
+ NULL, //SSH_SMSG_X11_OPEN 27
+ NULL, //SSH_CMSG_PORT_FORWARD_REQUEST 28
+ NULL, //SSH_MSG_PORT_OPEN 29
+ NULL, //SSH_CMSG_AGENT_REQUEST_FORWARDING 30
+ NULL, //SSH_SMSG_AGENT_OPEN 31
+ ssh_packet_ignore_callback, //SSH_MSG_IGNORE 32
+ NULL, //SSH_CMSG_EXIT_CONFIRMATION 33
+ NULL, //SSH_CMSG_X11_REQUEST_FORWARDING 34
+ NULL, //SSH_CMSG_AUTH_RHOSTS_RSA 35
+ ssh_packet_ignore_callback, //SSH_MSG_DEBUG 36
+};
+
+/** @internal
+ * @brief sets the default packet handlers
+ */
+void ssh_packet_set_default_callbacks1(ssh_session session){
+ session->default_packet_callbacks.start=0;
+ session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers1)/sizeof(ssh_packet_callback);
+ session->default_packet_callbacks.user=session;
+ session->default_packet_callbacks.callbacks=default_packet_handlers1;
+ ssh_packet_set_callbacks(session, &session->default_packet_callbacks);
+}
+
+
+/** @internal
+ * @handles a data received event. It then calls the handlers for the different packet types
+ * or and exception handler callback. Adapted for SSH-1 packets.
+ * @param user pointer to current ssh_session
+ * @param data pointer to the data received
+ * @len length of data received. It might not be enough for a complete packet
+ * @returns number of bytes read and processed.
+ */
+
+int ssh_packet_socket_callback1(const void *data, size_t receivedlen, void *user) {
+ void *packet = NULL;
+ int rc = SSH_ERROR;
+ int to_be_read;
+ size_t processed=0;
+ uint32_t padding;
+ uint32_t crc;
+ uint32_t len;
+ ssh_session session=(ssh_session)user;
+ enter_function();
+
+ switch (session->packet_state){
+ case PACKET_STATE_INIT:
+ memset(&session->in_packet, 0, sizeof(PACKET));
+
+ if (session->in_buffer) {
+ if (buffer_reinit(session->in_buffer) < 0) {
+ goto error;
+ }
+ } else {
+ session->in_buffer = ssh_buffer_new();
+ if (session->in_buffer == NULL) {
+ goto error;
+ }
+ }
+ /* must have at least enough bytes for size */
+ if(receivedlen < sizeof(uint32_t)){
+ leave_function();
+ return 0;
+ }
+ memcpy(&len,data,sizeof(uint32_t));
+ processed += sizeof(uint32_t);
+ rc = SSH_ERROR;
+
+ /* len is not encrypted */
+ len = ntohl(len);
+ if (len > MAX_PACKET_LEN) {
+ ssh_set_error(session, SSH_FATAL,
+ "read_packet(): Packet len too high (%u %.8x)", len, len);
+ goto error;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Reading a %d bytes packet", len);
+
+ session->in_packet.len = len;
+ session->packet_state = PACKET_STATE_SIZEREAD;
+ case PACKET_STATE_SIZEREAD:
+ len = session->in_packet.len;
+ /* SSH-1 has a fixed padding lenght */
+ padding = 8 - (len % 8);
+ to_be_read = len + padding;
+ if(to_be_read + processed > receivedlen){
+ /* wait for rest of packet */
+ leave_function();
+ return processed;
+ }
+ /* it is _not_ possible that to_be_read be < 8. */
+ packet = (char *)data + processed;
+ rc = SSH_ERROR;
+
+ if (buffer_add_data(session->in_buffer,packet,to_be_read) < 0) {
+ SAFE_FREE(packet);
+ goto error;
+ }
+ processed += to_be_read;
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("read packet:", ssh_buffer_get_begin(session->in_buffer),
+ ssh_buffer_get_len(session->in_buffer));
+#endif
+ if (session->current_crypto) {
+ /*
+ * We decrypt everything, missing the lenght part (which was
+ * previously read, unencrypted, and is not part of the buffer
+ */
+ if (packet_decrypt(session,
+ ssh_buffer_get_begin(session->in_buffer),
+ ssh_buffer_get_len(session->in_buffer)) < 0) {
+ ssh_set_error(session, SSH_FATAL, "Packet decrypt error");
+ goto error;
+ }
+ }
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("read packet decrypted:", ssh_buffer_get_begin(session->in_buffer),
+ ssh_buffer_get_len(session->in_buffer));
+#endif
+ ssh_log(session, SSH_LOG_PACKET, "%d bytes padding", padding);
+ if(((len + padding) != buffer_get_rest_len(session->in_buffer)) ||
+ ((len + padding) < sizeof(uint32_t))) {
+ ssh_log(session, SSH_LOG_RARE, "no crc32 in packet");
+ ssh_set_error(session, SSH_FATAL, "no crc32 in packet");
+ goto error;
+ }
+
+ memcpy(&crc,
+ (unsigned char *)buffer_get_rest(session->in_buffer) + (len+padding) - sizeof(uint32_t),
+ sizeof(uint32_t));
+ buffer_pass_bytes_end(session->in_buffer, sizeof(uint32_t));
+ crc = ntohl(crc);
+ if (ssh_crc32(buffer_get_rest(session->in_buffer),
+ (len + padding) - sizeof(uint32_t)) != crc) {
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("crc32 on",buffer_get_rest(session->in_buffer),
+ len + padding - sizeof(uint32_t));
+#endif
+ ssh_log(session, SSH_LOG_RARE, "Invalid crc32");
+ ssh_set_error(session, SSH_FATAL,
+ "Invalid crc32: expected %.8x, got %.8x",
+ crc,
+ ssh_crc32(buffer_get_rest(session->in_buffer),
+ len + padding - sizeof(uint32_t)));
+ goto error;
+ }
+ /* pass the padding */
+ buffer_pass_bytes(session->in_buffer, padding);
+ ssh_log(session, SSH_LOG_PACKET, "The packet is valid");
+
+/* TODO FIXME
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+ if(session->current_crypto && session->current_crypto->do_compress_in){
+ decompress_buffer(session,session->in_buffer);
+ }
+#endif
+*/
+ session->recv_seq++;
+ /* We don't want to rewrite a new packet while still executing the packet callbacks */
+ session->packet_state = PACKET_STATE_PROCESSING;
+ ssh_packet_parse_type(session);
+ /* execute callbacks */
+ ssh_packet_process(session, session->in_packet.type);
+ session->packet_state = PACKET_STATE_INIT;
+ if(processed < receivedlen){
+ /* Handle a potential packet left in socket buffer */
+ ssh_log(session,SSH_LOG_PACKET,"Processing %" PRIdS " bytes left in socket buffer",
+ receivedlen-processed);
+ rc = ssh_packet_socket_callback1((char *)data + processed,
+ receivedlen - processed,user);
+ processed += rc;
+ }
+ leave_function();
+ return processed;
+ case PACKET_STATE_PROCESSING:
+ ssh_log(session, SSH_LOG_RARE, "Nested packet processing. Delaying.");
+ return 0;
+ }
+
+error:
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ leave_function();
+ return processed;
+}
+
+
+int packet_send1(ssh_session session) {
+ unsigned int blocksize = (session->current_crypto ?
+ session->current_crypto->out_cipher->blocksize : 8);
+ uint32_t currentlen = ssh_buffer_get_len(session->out_buffer) + sizeof(uint32_t);
+ char padstring[32] = {0};
+ int rc = SSH_ERROR;
+ uint32_t finallen;
+ uint32_t crc;
+ uint8_t padding;
+
+ enter_function();
+ ssh_log(session,SSH_LOG_PACKET,"Sending a %d bytes long packet",currentlen);
+
+/* TODO FIXME
+#if defined(HAVE_LIBZ) && defined(WITH_LIBZ)
+ if (session->current_crypto && session->current_crypto->do_compress_out) {
+ if (compress_buffer(session, session->out_buffer) < 0) {
+ goto error;
+ }
+ currentlen = buffer_get_len(session->out_buffer);
+ }
+#endif
+*/
+ padding = blocksize - (currentlen % blocksize);
+ if (session->current_crypto) {
+ ssh_get_random(padstring, padding, 0);
+ } else {
+ memset(padstring, 0, padding);
+ }
+
+ finallen = htonl(currentlen);
+ ssh_log(session, SSH_LOG_PACKET,
+ "%d bytes after comp + %d padding bytes = %d bytes packet",
+ currentlen, padding, ntohl(finallen));
+
+ if (buffer_prepend_data(session->out_buffer, &padstring, padding) < 0) {
+ goto error;
+ }
+ if (buffer_prepend_data(session->out_buffer, &finallen, sizeof(uint32_t)) < 0) {
+ goto error;
+ }
+
+ crc = ssh_crc32((char *)ssh_buffer_get_begin(session->out_buffer) + sizeof(uint32_t),
+ ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t));
+
+ if (buffer_add_u32(session->out_buffer, ntohl(crc)) < 0) {
+ goto error;
+ }
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("Clear packet", ssh_buffer_get_begin(session->out_buffer),
+ ssh_buffer_get_len(session->out_buffer));
+#endif
+
+ packet_encrypt(session, (unsigned char *)ssh_buffer_get_begin(session->out_buffer) + sizeof(uint32_t),
+ ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t));
+
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("encrypted packet",ssh_buffer_get_begin(session->out_buffer),
+ ssh_buffer_get_len(session->out_buffer));
+#endif
+ rc=ssh_socket_write(session->socket, ssh_buffer_get_begin(session->out_buffer),
+ ssh_buffer_get_len(session->out_buffer));
+ if(rc== SSH_ERROR) {
+ goto error;
+ }
+
+ session->send_seq++;
+
+ if (buffer_reinit(session->out_buffer) < 0) {
+ rc = SSH_ERROR;
+ }
+error:
+ leave_function();
+ return rc; /* SSH_OK, AGAIN or ERROR */
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_disconnect1){
+ (void)packet;
+ (void)user;
+ (void)type;
+ ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT");
+ ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_DISCONNECT");
+ ssh_socket_close(session->socket);
+ session->alive = 0;
+ session->session_state=SSH_SESSION_STATE_DISCONNECTED;
+ return SSH_PACKET_USED;
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_smsg_success1){
+ if(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){
+ session->session_state=SSH_SESSION_STATE_AUTHENTICATING;
+ return SSH_PACKET_USED;
+ } else if(session->session_state==SSH_SESSION_STATE_AUTHENTICATING){
+ ssh_auth1_handler(session,type);
+ return SSH_PACKET_USED;
+ } else {
+ return ssh_packet_channel_success(session,type,packet,user);
+ }
+}
+
+SSH_PACKET_CALLBACK(ssh_packet_smsg_failure1){
+ if(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ ssh_set_error(session,SSH_FATAL,"Key exchange failed: received SSH_SMSG_FAILURE");
+ return SSH_PACKET_USED;
+ } else if(session->session_state==SSH_SESSION_STATE_AUTHENTICATING){
+ ssh_auth1_handler(session,type);
+ return SSH_PACKET_USED;
+ } else {
+ return ssh_packet_channel_failure(session,type,packet,user);
+ }
+}
+
+
+#endif /* WITH_SSH1 */
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/pcap.c b/src/pcap.c
new file mode 100644
index 00000000..56bf3316
--- /dev/null
+++ b/src/pcap.c
@@ -0,0 +1,434 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009 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.
+ */
+
+/* pcap.c */
+#include "config.h"
+#ifdef WITH_PCAP
+
+#include <stdio.h>
+#ifdef _WIN32
+#include <ws2tcpip.h>
+#else
+#include <sys/time.h>
+#include <sys/socket.h>
+#endif
+#include <errno.h>
+
+#include "libssh/libssh.h"
+#include "libssh/pcap.h"
+#include "libssh/session.h"
+#include "libssh/buffer.h"
+#include "libssh/socket.h"
+
+/**
+ * @internal
+ *
+ * @defgroup libssh_pcap The libssh pcap functions
+ * @ingroup libssh
+ *
+ * The pcap file generation
+ *
+ *
+ * @{
+ */
+
+/* The header of a pcap file is the following. We are not going to make it
+ * very complicated.
+ * Just for information.
+ */
+struct pcap_hdr_s {
+ uint32_t magic_number; /* magic number */
+ uint16_t version_major; /* major version number */
+ uint16_t version_minor; /* minor version number */
+ int32_t thiszone; /* GMT to local correction */
+ uint32_t sigfigs; /* accuracy of timestamps */
+ uint32_t snaplen; /* max length of captured packets, in octets */
+ uint32_t network; /* data link type */
+};
+
+#define PCAP_MAGIC 0xa1b2c3d4
+#define PCAP_VERSION_MAJOR 2
+#define PCAP_VERSION_MINOR 4
+
+#define DLT_RAW 12 /* raw IP */
+
+/* TCP flags */
+#define TH_FIN 0x01
+#define TH_SYN 0x02
+#define TH_RST 0x04
+#define TH_PUSH 0x08
+#define TH_ACK 0x10
+#define TH_URG 0x20
+
+/* The header of a pcap packet.
+ * Just for information.
+ */
+struct pcaprec_hdr_s {
+ uint32_t ts_sec; /* timestamp seconds */
+ uint32_t ts_usec; /* timestamp microseconds */
+ uint32_t incl_len; /* number of octets of packet saved in file */
+ uint32_t orig_len; /* actual length of packet */
+};
+
+/** @private
+ * @brief a pcap context expresses the state of a pcap dump
+ * in a SSH session only. Multiple pcap contexts may be used into
+ * a single pcap file.
+ */
+
+struct ssh_pcap_context_struct {
+ ssh_session session;
+ ssh_pcap_file file;
+ int connected;
+ /* All of these information are useful to generate
+ * the dummy IP and TCP packets
+ */
+ uint32_t ipsource;
+ uint32_t ipdest;
+ uint16_t portsource;
+ uint16_t portdest;
+ uint32_t outsequence;
+ uint32_t insequence;
+};
+
+/** @private
+ * @brief a pcap file expresses the state of a pcap file which may
+ * contain several streams.
+ */
+struct ssh_pcap_file_struct {
+ FILE *output;
+ uint16_t ipsequence;
+};
+
+/**
+ * @brief create a new ssh_pcap_file object
+ */
+ssh_pcap_file ssh_pcap_file_new(){
+ struct ssh_pcap_file_struct *pcap;
+
+ pcap = malloc(sizeof(struct ssh_pcap_file_struct));
+ if (pcap == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(pcap);
+
+ return pcap;
+}
+
+/** @internal
+ * @brief writes a packet on file
+ */
+static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet){
+ int err;
+ uint32_t len;
+ if(pcap == NULL || pcap->output==NULL)
+ return SSH_ERROR;
+ len=ssh_buffer_get_len(packet);
+ err=fwrite(ssh_buffer_get_begin(packet),len,1,pcap->output);
+ if(err<0)
+ return SSH_ERROR;
+ else
+ return SSH_OK;
+}
+
+/** @internal
+ * @brief prepends a packet with the pcap header and writes packet
+ * on file
+ */
+int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len){
+ ssh_buffer header=ssh_buffer_new();
+ struct timeval now;
+ int err;
+ if(header == NULL)
+ return SSH_ERROR;
+ gettimeofday(&now,NULL);
+ buffer_add_u32(header,htonl(now.tv_sec));
+ buffer_add_u32(header,htonl(now.tv_usec));
+ buffer_add_u32(header,htonl(ssh_buffer_get_len(packet)));
+ buffer_add_u32(header,htonl(original_len));
+ buffer_add_buffer(header,packet);
+ err=ssh_pcap_file_write(pcap,header);
+ ssh_buffer_free(header);
+ return err;
+}
+
+/**
+ * @brief opens a new pcap file and create header
+ */
+int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){
+ ssh_buffer header;
+ int err;
+ if(pcap == NULL)
+ return SSH_ERROR;
+ if(pcap->output){
+ fclose(pcap->output);
+ pcap->output=NULL;
+ }
+ pcap->output=fopen(filename,"wb");
+ if(pcap->output==NULL)
+ return SSH_ERROR;
+ header=ssh_buffer_new();
+ if(header==NULL)
+ return SSH_ERROR;
+ buffer_add_u32(header,htonl(PCAP_MAGIC));
+ buffer_add_u16(header,htons(PCAP_VERSION_MAJOR));
+ buffer_add_u16(header,htons(PCAP_VERSION_MINOR));
+ /* currently hardcode GMT to 0 */
+ buffer_add_u32(header,htonl(0));
+ /* accuracy */
+ buffer_add_u32(header,htonl(0));
+ /* size of the biggest packet */
+ buffer_add_u32(header,htonl(MAX_PACKET_LEN));
+ /* we will write sort-of IP */
+ buffer_add_u32(header,htonl(DLT_RAW));
+ err=ssh_pcap_file_write(pcap,header);
+ ssh_buffer_free(header);
+ return err;
+}
+
+int ssh_pcap_file_close(ssh_pcap_file pcap){
+ int err;
+ if(pcap ==NULL || pcap->output==NULL)
+ return SSH_ERROR;
+ err=fclose(pcap->output);
+ pcap->output=NULL;
+ if(err != 0)
+ return SSH_ERROR;
+ else
+ return SSH_OK;
+}
+
+void ssh_pcap_file_free(ssh_pcap_file pcap){
+ ssh_pcap_file_close(pcap);
+ SAFE_FREE(pcap);
+}
+
+
+/** @internal
+ * @brief allocates a new ssh_pcap_context object
+ */
+
+ssh_pcap_context ssh_pcap_context_new(ssh_session session){
+ ssh_pcap_context ctx=malloc(sizeof(struct ssh_pcap_context_struct));
+ if(ctx==NULL){
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+ ZERO_STRUCTP(ctx);
+ ctx->session=session;
+ return ctx;
+}
+
+void ssh_pcap_context_free(ssh_pcap_context ctx){
+ SAFE_FREE(ctx);
+}
+
+void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap){
+ ctx->file=pcap;
+}
+
+/** @internal
+ * @brief sets the IP and port parameters in the connection
+ */
+static int ssh_pcap_context_connect(ssh_pcap_context ctx){
+ ssh_session session=ctx->session;
+ struct sockaddr_in local, remote;
+ socket_t fd;
+ socklen_t len;
+ if(session==NULL)
+ return SSH_ERROR;
+ if(session->socket==NULL)
+ return SSH_ERROR;
+ fd=ssh_socket_get_fd_in(session->socket);
+ /* TODO: adapt for windows */
+ if(fd<0)
+ return SSH_ERROR;
+ len=sizeof(local);
+ if(getsockname(fd,(struct sockaddr *)&local,&len)<0){
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Getting local IP address: %s",strerror(errno));
+ return SSH_ERROR;
+ }
+ len=sizeof(remote);
+ if(getpeername(fd,(struct sockaddr *)&remote,&len)<0){
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Getting remote IP address: %s",strerror(errno));
+ return SSH_ERROR;
+ }
+ if(local.sin_family != AF_INET){
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Only IPv4 supported for pcap logging");
+ return SSH_ERROR;
+ }
+ memcpy(&ctx->ipsource,&local.sin_addr,sizeof(ctx->ipsource));
+ memcpy(&ctx->ipdest,&remote.sin_addr,sizeof(ctx->ipdest));
+ memcpy(&ctx->portsource,&local.sin_port,sizeof(ctx->portsource));
+ memcpy(&ctx->portdest,&remote.sin_port,sizeof(ctx->portdest));
+
+ ctx->connected=1;
+ return SSH_OK;
+}
+
+#define IPHDR_LEN 20
+#define TCPHDR_LEN 20
+#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN)
+/** @internal
+ * @brief write a SSH packet as a TCP over IP in a pcap file
+ * @param ctx open pcap context
+ * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received
+ * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted
+ * @param data pointer to the data to write
+ * @param len data to write in the pcap file. May be smaller than origlen.
+ * @param origlen number of bytes of complete data.
+ * @returns SSH_OK write is successful
+ * @returns SSH_ERROR an error happened.
+ */
+int ssh_pcap_context_write(ssh_pcap_context ctx,enum ssh_pcap_direction direction
+ , void *data, uint32_t len, uint32_t origlen){
+ ssh_buffer ip;
+ int err;
+ if(ctx==NULL || ctx->file ==NULL)
+ return SSH_ERROR;
+ if(ctx->connected==0)
+ if(ssh_pcap_context_connect(ctx)==SSH_ERROR)
+ return SSH_ERROR;
+ ip=ssh_buffer_new();
+ if(ip==NULL){
+ ssh_set_error_oom(ctx->session);
+ return SSH_ERROR;
+ }
+ /* build an IP packet */
+ /* V4, 20 bytes */
+ buffer_add_u8(ip,4 << 4 | 5);
+ /* tos */
+ buffer_add_u8(ip,0);
+ /* total len */
+ buffer_add_u16(ip,htons(origlen + TCPIPHDR_LEN));
+ /* IP id number */
+ buffer_add_u16(ip,htons(ctx->file->ipsequence));
+ ctx->file->ipsequence++;
+ /* fragment offset */
+ buffer_add_u16(ip,htons(0));
+ /* TTL */
+ buffer_add_u8(ip,64);
+ /* protocol TCP=6 */
+ buffer_add_u8(ip,6);
+ /* checksum */
+ buffer_add_u16(ip,0);
+ if(direction==SSH_PCAP_DIR_OUT){
+ buffer_add_u32(ip,ctx->ipsource);
+ buffer_add_u32(ip,ctx->ipdest);
+ } else {
+ buffer_add_u32(ip,ctx->ipdest);
+ buffer_add_u32(ip,ctx->ipsource);
+ }
+ /* TCP */
+ if(direction==SSH_PCAP_DIR_OUT){
+ buffer_add_u16(ip,ctx->portsource);
+ buffer_add_u16(ip,ctx->portdest);
+ } else {
+ buffer_add_u16(ip,ctx->portdest);
+ buffer_add_u16(ip,ctx->portsource);
+ }
+ /* sequence number */
+ if(direction==SSH_PCAP_DIR_OUT){
+ buffer_add_u32(ip,ntohl(ctx->outsequence));
+ ctx->outsequence+=origlen;
+ } else {
+ buffer_add_u32(ip,ntohl(ctx->insequence));
+ ctx->insequence+=origlen;
+ }
+ /* ack number */
+ if(direction==SSH_PCAP_DIR_OUT){
+ buffer_add_u32(ip,ntohl(ctx->insequence));
+ } else {
+ buffer_add_u32(ip,ntohl(ctx->outsequence));
+ }
+ /* header len = 20 = 5 * 32 bits, at offset 4*/
+ buffer_add_u8(ip,5 << 4);
+ /* flags */
+ buffer_add_u8(ip,TH_PUSH | TH_ACK);
+ /* window */
+ buffer_add_u16(ip,htons(65535));
+ /* checksum */
+ buffer_add_u16(ip,htons(0));
+ /* urgent data ptr */
+ buffer_add_u16(ip,0);
+ /* actual data */
+ buffer_add_data(ip,data,len);
+ err=ssh_pcap_file_write_packet(ctx->file,ip,origlen + TCPIPHDR_LEN);
+ ssh_buffer_free(ip);
+ return err;
+}
+
+/** @brief sets the pcap file used to trace the session
+ * @param current session
+ * @param pcap an handler to a pcap file. A pcap file may be used in several
+ * sessions.
+ * @returns SSH_ERROR in case of error, SSH_OK otherwise.
+ */
+int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){
+ ssh_pcap_context ctx=ssh_pcap_context_new(session);
+ if(ctx==NULL){
+ ssh_set_error_oom(session);
+ return SSH_ERROR;
+ }
+ ctx->file=pcap;
+ if(session->pcap_ctx)
+ ssh_pcap_context_free(session->pcap_ctx);
+ session->pcap_ctx=ctx;
+ return SSH_OK;
+}
+
+
+#else /* WITH_PCAP */
+
+/* Simple stub returning errors when no pcap compiled in */
+
+#include "libssh/libssh.h"
+#include "libssh/priv.h"
+
+int ssh_pcap_file_close(ssh_pcap_file pcap){
+ (void) pcap;
+ return SSH_ERROR;
+}
+
+void ssh_pcap_file_free(ssh_pcap_file pcap){
+ (void) pcap;
+}
+
+ssh_pcap_file ssh_pcap_file_new(void){
+ return NULL;
+}
+int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){
+ (void) pcap;
+ (void) filename;
+ return SSH_ERROR;
+}
+
+int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile){
+ (void) pcapfile;
+ ssh_set_error(session,SSH_REQUEST_DENIED,"Pcap support not compiled in");
+ return SSH_ERROR;
+}
+
+#endif
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/pki.c b/src/pki.c
new file mode 100644
index 00000000..ef925dd0
--- /dev/null
+++ b/src/pki.c
@@ -0,0 +1,113 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2010 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.
+ */
+
+/** @defgroup ssh_pki SSH Public Key Infrastructure
+ * @ingroup libssh
+ *
+ * Functions for the creation, importation and manipulation of public and
+ * private keys in the context of the SSH protocol
+ *
+ * @{
+ */
+
+#include "libssh/priv.h"
+#include "libssh/pki.h"
+#include "libssh/keys.h"
+
+/**
+ * @brief creates a new empty SSH key
+ * @returns an empty ssh_key handle
+ */
+ssh_key ssh_key_new (void){
+ ssh_key ptr=malloc (sizeof (struct ssh_key_struct));
+ ZERO_STRUCTP(ptr);
+ return ptr;
+}
+
+/**
+ * @brief clean up the key and deallocate all existing keys
+ * @param[in] key ssh_key to clean
+ */
+void ssh_key_clean (ssh_key key){
+ if(key==NULL)
+ return;
+#ifdef HAVE_LIBGCRYPT
+ gcry_sexp_release(key->dsa);
+ gcry_sexp_release(key->rsa);
+#elif defined HAVE_LIBCRYPTO
+ DSA_free(key->dsa);
+ RSA_free(key->rsa);
+#endif
+ key->flags=SSH_KEY_FLAG_EMPTY;
+ key->type=SSH_KEYTYPE_UNKNOWN;
+ key->type_c=NULL;
+}
+
+/**
+ * @brief deallocate a SSH key
+ * @param[in] key ssh_key handle to free
+ */
+void ssh_key_free (ssh_key key){
+ if(key){
+ ssh_key_clean(key);
+ SAFE_FREE(key);
+ }
+}
+
+/**
+ * @brief returns the type of a ssh key
+ * @param[in] key the ssh_key handle
+ * @returns one of SSH_KEYTYPE_RSA,SSH_KEYTYPE_DSS,SSH_KEYTYPE_RSA1
+ * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown
+ */
+enum ssh_keytypes_e ssh_key_type(ssh_key key){
+ if (key==NULL)
+ return SSH_KEYTYPE_UNKNOWN;
+ return key->type;
+}
+
+/**
+ * @brief import a key from a file
+ * @param[out] key the ssh_key to update
+ * @param[in] session The SSH Session to use. If a key decryption callback is set, it will
+ * be used to ask for the passphrase.
+ * @param[in] filename The filename of the the private key.
+ * @param[in] passphrase The passphrase to decrypt the private key. Set to null
+ * if none is needed or it is unknown.
+ * @returns SSH_OK on success, SSH_ERROR otherwise.
+ **/
+int ssh_key_import_private(ssh_key key, ssh_session session, const char *filename, const char *passphrase){
+ ssh_private_key priv=privatekey_from_file(session,filename,0,passphrase);
+ if(priv==NULL)
+ return SSH_ERROR;
+ ssh_key_clean(key);
+ key->dsa=priv->dsa_priv;
+ key->rsa=priv->rsa_priv;
+ key->type=priv->type;
+ key->flags=SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC;
+ key->type_c=ssh_type_to_char(key->type);
+ SAFE_FREE(priv);
+ return SSH_OK;
+}
+
+/**
+ * @}
+ */
diff --git a/src/poll.c b/src/poll.c
new file mode 100644
index 00000000..7942ea0b
--- /dev/null
+++ b/src/poll.c
@@ -0,0 +1,692 @@
+/*
+ * poll.c - poll wrapper
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009-2010 by Andreas Schneider <mail@cynapses.org>
+ * Copyright (c) 2003-2009 by Aris Adamantiadis
+ * Copyright (c) 2009 Aleksandar Kanchev
+ *
+ * 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.
+ *
+ * vim: ts=2 sw=2 et cindent
+ */
+
+#include "config.h"
+
+#include <errno.h>
+
+#include "libssh/priv.h"
+#include "libssh/libssh.h"
+#include "libssh/poll.h"
+#include "libssh/socket.h"
+
+#ifndef SSH_POLL_CTX_CHUNK
+#define SSH_POLL_CTX_CHUNK 5
+#endif
+
+/**
+ * @defgroup libssh_poll The SSH poll functions.
+ * @ingroup libssh
+ *
+ * Add a generic way to handle sockets asynchronously.
+ *
+ * It's based on poll objects, each of which store a socket, it's events and a
+ * callback, which gets called whenever an event is set. The poll objects are
+ * attached to a poll context, which should be allocated on per thread basis.
+ *
+ * Polling the poll context will poll all the attached poll objects and call
+ * their callbacks (handlers) if any of the socket events are set. This should
+ * be done within the main loop of an application.
+ *
+ * @{
+ */
+
+/** global poll context used for blocking operations */
+static ssh_poll_ctx global_poll_ctx;
+
+struct ssh_poll_handle_struct {
+ ssh_poll_ctx ctx;
+ union {
+ socket_t fd;
+ size_t idx;
+ } x;
+ short events;
+ ssh_poll_callback cb;
+ void *cb_data;
+};
+
+struct ssh_poll_ctx_struct {
+ ssh_poll_handle *pollptrs;
+ ssh_pollfd_t *pollfds;
+ size_t polls_allocated;
+ size_t polls_used;
+ size_t chunk_size;
+};
+
+#ifdef HAVE_POLL
+#include <poll.h>
+
+void ssh_poll_init(void) {
+ return;
+}
+
+void ssh_poll_cleanup(void) {
+ return;
+}
+
+int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) {
+ return poll((struct pollfd *) fds, nfds, timeout);
+}
+
+#else /* HAVE_POLL */
+
+typedef int (*poll_fn)(ssh_pollfd_t *, nfds_t, int);
+static poll_fn ssh_poll_emu;
+
+#include <sys/types.h>
+
+#ifdef _WIN32
+#ifndef STRICT
+#define STRICT
+#endif /* STRICT */
+
+#include <time.h>
+#include <windows.h>
+#include <winsock2.h>
+
+#if (_WIN32_WINNT < 0x0600)
+typedef struct ssh_pollfd_struct WSAPOLLFD;
+#endif
+
+typedef int (WSAAPI* WSAPoll_FunctionType)(WSAPOLLFD fdarray[],
+ ULONG nfds,
+ INT timeout
+);
+
+static WSAPoll_FunctionType wsa_poll;
+
+int win_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) {
+ if (wsa_poll) {
+ return (wsa_poll)(fds, nfds, timeout);
+ }
+
+ return SOCKET_ERROR;
+}
+
+#define WS2_LIBRARY "ws2_32.dll"
+static HINSTANCE hlib;
+
+#else /* _WIN32 */
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/time.h>
+#endif /* _WIN32 */
+
+
+/*
+ * This is a poll(2)-emulation using select for systems not providing a native
+ * poll implementation.
+ *
+ * Keep in mind that select is terribly inefficient. The interface is simply not
+ * meant to be used with maximum descriptor value greater, say, 32 or so. With
+ * a value as high as 1024 on Linux you'll pay dearly in every single call.
+ * poll() will be orders of magnitude faster.
+ */
+static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) {
+ fd_set readfds, writefds, exceptfds;
+ struct timeval tv, *ptv;
+ socket_t max_fd;
+ int rc;
+ nfds_t i;
+
+ if (fds == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+ FD_ZERO (&exceptfds);
+
+ /* compute fd_sets and find largest descriptor */
+ for (rc = -1, max_fd = 0, i = 0; i < nfds; i++) {
+ if (fds[i].fd == SSH_INVALID_SOCKET) {
+ continue;
+ }
+#ifndef _WIN32
+ if (fds[i].fd >= FD_SETSIZE) {
+ rc = -1;
+ break;
+ }
+#endif
+
+ if (fds[i].events & (POLLIN | POLLRDNORM)) {
+ FD_SET (fds[i].fd, &readfds);
+ }
+ if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) {
+ FD_SET (fds[i].fd, &writefds);
+ }
+ if (fds[i].events & (POLLPRI | POLLRDBAND)) {
+ FD_SET (fds[i].fd, &exceptfds);
+ }
+ if (fds[i].fd > max_fd &&
+ (fds[i].events & (POLLIN | POLLOUT | POLLPRI |
+ POLLRDNORM | POLLRDBAND |
+ POLLWRNORM | POLLWRBAND))) {
+ max_fd = fds[i].fd;
+ rc = 0;
+ }
+ }
+
+ if (max_fd == SSH_INVALID_SOCKET || rc == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (timeout < 0) {
+ ptv = NULL;
+ } else {
+ ptv = &tv;
+ if (timeout == 0) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ } else {
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ }
+ }
+
+ rc = select (max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
+ if (rc < 0) {
+ return -1;
+ }
+
+ for (rc = 0, i = 0; i < nfds; i++)
+ if (fds[i].fd >= 0) {
+ fds[i].revents = 0;
+
+ if (FD_ISSET(fds[i].fd, &readfds)) {
+ int save_errno = errno;
+ char data[64] = {0};
+ int ret;
+
+ /* support for POLLHUP */
+ ret = recv(fds[i].fd, data, 64, MSG_PEEK);
+#ifdef _WIN32
+ if ((ret == -1) &&
+ (errno == WSAESHUTDOWN || errno == WSAECONNRESET ||
+ errno == WSAECONNABORTED || errno == WSAENETRESET)) {
+#else
+ if ((ret == -1) &&
+ (errno == ESHUTDOWN || errno == ECONNRESET ||
+ errno == ECONNABORTED || errno == ENETRESET)) {
+#endif
+ fds[i].revents |= POLLHUP;
+ } else {
+ fds[i].revents |= fds[i].events & (POLLIN | POLLRDNORM);
+ }
+
+ errno = save_errno;
+ }
+ if (FD_ISSET(fds[i].fd, &writefds)) {
+ fds[i].revents |= fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+
+ if (FD_ISSET(fds[i].fd, &exceptfds)) {
+ fds[i].revents |= fds[i].events & (POLLPRI | POLLRDBAND);
+ }
+
+ if (fds[i].revents & ~POLLHUP) {
+ rc++;
+ }
+ } else {
+ fds[i].revents = POLLNVAL;
+ }
+
+ return rc;
+}
+
+void ssh_poll_init(void) {
+ ssh_poll_emu = bsd_poll;
+
+#ifdef _WIN32
+ hlib = LoadLibrary(WS2_LIBRARY);
+ if (hlib != NULL) {
+ wsa_poll = (WSAPoll_FunctionType) (void *) GetProcAddress(hlib, "WSAPoll");
+ }
+#endif /* _WIN32 */
+
+ if (wsa_poll != NULL) {
+ ssh_poll_emu = bsd_poll;
+ }
+}
+
+void ssh_poll_cleanup(void) {
+ ssh_poll_emu = bsd_poll;
+#ifdef _WIN32
+ wsa_poll = NULL;
+
+ FreeLibrary(hlib);
+
+ hlib = NULL;
+#endif /* _WIN32 */
+}
+
+int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) {
+ return (ssh_poll_emu)(fds, nfds, timeout);
+}
+
+#endif /* HAVE_POLL */
+
+/**
+ * @brief Allocate a new poll object, which could be used within a poll context.
+ *
+ * @param fd Socket that will be polled.
+ * @param events Poll events that will be monitored for the socket. i.e.
+ * POLLIN, POLLPRI, POLLOUT, POLLERR, POLLHUP, POLLNVAL
+ * @param cb Function to be called if any of the events are set.
+ * @param userdata Userdata to be passed to the callback function. NULL if
+ * not needed.
+ *
+ * @return A new poll object, NULL on error
+ */
+
+ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb,
+ void *userdata) {
+ ssh_poll_handle p;
+
+ p = malloc(sizeof(struct ssh_poll_handle_struct));
+ if (p != NULL) {
+ p->ctx = NULL;
+ p->x.fd = fd;
+ p->events = events;
+ p->cb = cb;
+ p->cb_data = userdata;
+ }
+
+ return p;
+}
+
+
+/**
+ * @brief Free a poll object.
+ *
+ * @param p Pointer to an already allocated poll object.
+ */
+
+void ssh_poll_free(ssh_poll_handle p) {
+ if(p->ctx != NULL){
+ ssh_poll_ctx_remove(p->ctx,p);
+ p->ctx=NULL;
+ }
+ SAFE_FREE(p);
+}
+
+/**
+ * @brief Get the poll context of a poll object.
+ *
+ * @param p Pointer to an already allocated poll object.
+ *
+ * @return Poll context or NULL if the poll object isn't attached.
+ */
+ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) {
+ return p->ctx;
+}
+
+/**
+ * @brief Get the events of a poll object.
+ *
+ * @param p Pointer to an already allocated poll object.
+ *
+ * @return Poll events.
+ */
+short ssh_poll_get_events(ssh_poll_handle p) {
+ return p->events;
+}
+
+/**
+ * @brief Set the events of a poll object. The events will also be propagated
+ * to an associated poll context.
+ *
+ * @param p Pointer to an already allocated poll object.
+ * @param events Poll events.
+ */
+void ssh_poll_set_events(ssh_poll_handle p, short events) {
+ p->events = events;
+ if (p->ctx != NULL) {
+ p->ctx->pollfds[p->x.idx].events = events;
+ }
+}
+
+/**
+ * @brief Set the file descriptor of a poll object. The FD will also be propagated
+ * to an associated poll context.
+ *
+ * @param p Pointer to an already allocated poll object.
+ * @param fd New file descriptor.
+ */
+void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) {
+ if (p->ctx != NULL) {
+ p->ctx->pollfds[p->x.idx].fd = fd;
+ } else {
+ p->x.fd = fd;
+ }
+}
+
+/**
+ * @brief Add extra events to a poll object. Duplicates are ignored.
+ * The events will also be propagated to an associated poll context.
+ *
+ * @param p Pointer to an already allocated poll object.
+ * @param events Poll events.
+ */
+void ssh_poll_add_events(ssh_poll_handle p, short events) {
+ ssh_poll_set_events(p, ssh_poll_get_events(p) | events);
+}
+
+/**
+ * @brief Remove events from a poll object. Non-existent are ignored.
+ * The events will also be propagated to an associated poll context.
+ *
+ * @param p Pointer to an already allocated poll object.
+ * @param events Poll events.
+ */
+void ssh_poll_remove_events(ssh_poll_handle p, short events) {
+ ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events);
+}
+
+/**
+ * @brief Get the raw socket of a poll object.
+ *
+ * @param p Pointer to an already allocated poll object.
+ *
+ * @return Raw socket.
+ */
+
+socket_t ssh_poll_get_fd(ssh_poll_handle p) {
+ if (p->ctx != NULL) {
+ return p->ctx->pollfds[p->x.idx].fd;
+ }
+
+ return p->x.fd;
+}
+/**
+ * @brief Set the callback of a poll object.
+ *
+ * @param p Pointer to an already allocated poll object.
+ * @param cb Function to be called if any of the events are set.
+ * @param userdata Userdata to be passed to the callback function. NULL if
+ * not needed.
+ */
+void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) {
+ if (cb != NULL) {
+ p->cb = cb;
+ p->cb_data = userdata;
+ }
+}
+
+/**
+ * @brief Create a new poll context. It could be associated with many poll object
+ * which are going to be polled at the same time as the poll context. You
+ * would need a single poll context per thread.
+ *
+ * @param chunk_size The size of the memory chunk that will be allocated, when
+ * more memory is needed. This is for efficiency reasons,
+ * i.e. don't allocate memory for each new poll object, but
+ * for the next 5. Set it to 0 if you want to use the
+ * library's default value.
+ */
+ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) {
+ ssh_poll_ctx ctx;
+
+ ctx = malloc(sizeof(struct ssh_poll_ctx_struct));
+ if (ctx != NULL) {
+ if (!chunk_size) {
+ chunk_size = SSH_POLL_CTX_CHUNK;
+ }
+
+ ctx->chunk_size = chunk_size;
+ ctx->pollptrs = NULL;
+ ctx->pollfds = NULL;
+ ctx->polls_allocated = 0;
+ ctx->polls_used = 0;
+ }
+
+ return ctx;
+}
+
+/**
+ * @brief Free a poll context.
+ *
+ * @param ctx Pointer to an already allocated poll context.
+ */
+void ssh_poll_ctx_free(ssh_poll_ctx ctx) {
+ if (ctx->polls_allocated > 0) {
+ register size_t i, used;
+
+ used = ctx->polls_used;
+ for (i = 0; i < used; ) {
+ ssh_poll_handle p = ctx->pollptrs[i];
+ socket_t fd = ctx->pollfds[i].fd;
+
+ /* force poll object removal */
+ if (p->cb(p, fd, POLLERR, p->cb_data) < 0) {
+ used = ctx->polls_used;
+ } else {
+ i++;
+ }
+ }
+
+ SAFE_FREE(ctx->pollptrs);
+ SAFE_FREE(ctx->pollfds);
+ }
+
+ SAFE_FREE(ctx);
+}
+
+static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) {
+ ssh_poll_handle *pollptrs;
+ ssh_pollfd_t *pollfds;
+
+ pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle *) * new_size);
+ if (pollptrs == NULL) {
+ return -1;
+ }
+
+ pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size);
+ if (pollfds == NULL) {
+ ctx->pollptrs = realloc(pollptrs, sizeof(ssh_poll_handle *) * ctx->polls_allocated);
+ return -1;
+ }
+
+ ctx->pollptrs = pollptrs;
+ ctx->pollfds = pollfds;
+ ctx->polls_allocated = new_size;
+
+ return 0;
+}
+
+/**
+ * @brief Add a poll object to a poll context.
+ *
+ * @param ctx Pointer to an already allocated poll context.
+ * @param p Pointer to an already allocated poll object.
+ *
+ * @return 0 on success, < 0 on error
+ */
+int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) {
+ socket_t fd;
+
+ if (p->ctx != NULL) {
+ /* already attached to a context */
+ return -1;
+ }
+
+ if (ctx->polls_used == ctx->polls_allocated &&
+ ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) {
+ return -1;
+ }
+
+ fd = p->x.fd;
+ p->x.idx = ctx->polls_used++;
+ ctx->pollptrs[p->x.idx] = p;
+ ctx->pollfds[p->x.idx].fd = fd;
+ ctx->pollfds[p->x.idx].events = p->events;
+ ctx->pollfds[p->x.idx].revents = 0;
+ p->ctx = ctx;
+
+ return 0;
+}
+
+/**
+ * @brief Add a socket object to a poll context.
+ *
+ * @param ctx Pointer to an already allocated poll context.
+ * @param s A SSH socket handle
+ *
+ * @return 0 on success, < 0 on error
+ */
+int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) {
+ ssh_poll_handle p_in, p_out;
+ int ret;
+ p_in=ssh_socket_get_poll_handle_in(s);
+ if(p_in==NULL)
+ return -1;
+ ret = ssh_poll_ctx_add(ctx,p_in);
+ if(ret != 0)
+ return ret;
+ p_out=ssh_socket_get_poll_handle_out(s);
+ if(p_in != p_out)
+ ret = ssh_poll_ctx_add(ctx,p_out);
+ return ret;
+}
+
+
+/**
+ * @brief Remove a poll object from a poll context.
+ *
+ * @param ctx Pointer to an already allocated poll context.
+ * @param p Pointer to an already allocated poll object.
+ */
+void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) {
+ size_t i;
+
+ i = p->x.idx;
+ p->x.fd = ctx->pollfds[i].fd;
+ p->ctx = NULL;
+
+ ctx->polls_used--;
+
+ /* fill the empty poll slot with the last one */
+ if (ctx->polls_used > 0 && ctx->polls_used != i) {
+ ctx->pollfds[i] = ctx->pollfds[ctx->polls_used];
+ ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used];
+ }
+
+ /* this will always leave at least chunk_size polls allocated */
+ if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) {
+ ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size);
+ }
+}
+
+/**
+ * @brief Poll all the sockets associated through a poll object with a
+ * poll context. If any of the events are set after the poll, the
+ * call back function of the socket will be called.
+ * This function should be called once within the programs main loop.
+ *
+ * @param ctx Pointer to an already allocated poll context.
+ * @param timeout An upper limit on the time for which ssh_poll_ctx() will
+ * block, in milliseconds. Specifying a negative value
+ * means an infinite timeout. This parameter is passed to
+ * the poll() function.
+ * @returns SSH_OK No error.
+ * SSH_ERROR Error happened during the poll.
+ */
+
+int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) {
+ int rc;
+ int i, used;
+ ssh_poll_handle p;
+ socket_t fd;
+ int revents;
+
+ if (!ctx->polls_used)
+ return 0;
+
+ rc = ssh_poll(ctx->pollfds, ctx->polls_used, timeout);
+ if(rc < 0)
+ rc=SSH_ERROR;
+ if(rc <= 0)
+ return rc;
+ used = ctx->polls_used;
+ for (i = 0; i < used && rc > 0; ) {
+ if (!ctx->pollfds[i].revents) {
+ i++;
+ } else {
+ p = ctx->pollptrs[i];
+ fd = ctx->pollfds[i].fd;
+ revents = ctx->pollfds[i].revents;
+
+ if (p->cb(p, fd, revents, p->cb_data) < 0) {
+ /* the poll was removed, reload the used counter and start again */
+ used = ctx->polls_used;
+ i=0;
+ } else {
+ ctx->pollfds[i].revents = 0;
+ i++;
+ }
+
+ rc--;
+ }
+ }
+
+ return rc;
+}
+
+/** @internal
+ * @brief returns a pointer to the global poll context.
+ * Allocates it if it does not exist.
+ * @param session an optional session handler, used to store the error
+ * message if needed.
+ * @returns pointer to the global poll context.
+ */
+ssh_poll_ctx ssh_get_global_poll_ctx(ssh_session session){
+ if(global_poll_ctx != NULL)
+ return global_poll_ctx;
+ global_poll_ctx=ssh_poll_ctx_new(5);
+ if(global_poll_ctx == NULL && session != NULL){
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+ return global_poll_ctx;
+}
+
+/** @internal
+ * @brief Deallocate the global poll context
+ */
+void ssh_free_global_poll_ctx(){
+ if(global_poll_ctx != NULL){
+ ssh_poll_ctx_free(global_poll_ctx);
+ global_poll_ctx=NULL;
+ }
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/scp.c b/src/scp.c
new file mode 100644
index 00000000..4a6f6f14
--- /dev/null
+++ b/src/scp.c
@@ -0,0 +1,752 @@
+/*
+ * scp - SSH scp wrapper functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2009 by Aris Adamantiadis <aris@0xbadc0de.be>
+ *
+ * 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 <stdio.h>
+#include <string.h>
+
+#include "libssh/priv.h"
+#include "libssh/scp.h"
+
+/**
+ * @defgroup libssh_scp The SSH scp functions
+ * @ingroup libssh
+ *
+ * SCP protocol over SSH functions
+ *
+ * @{
+ */
+
+/**
+ * @brief Create a new scp session.
+ *
+ * @param[in] session The SSH session to use.
+ *
+ * @param[in] mode One of SSH_SCP_WRITE or SSH_SCP_READ, depending if you
+ * need to drop files remotely or read them.
+ * It is not possible to combine read and write.
+ *
+ * @param[in] location The directory in which write or read will be done. Any
+ * push or pull will be relative to this place.
+ *
+ * @returns A ssh_scp handle, NULL if the creation was impossible.
+ */
+ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location){
+ ssh_scp scp=malloc(sizeof(struct ssh_scp_struct));
+ if(scp == NULL){
+ ssh_set_error(session,SSH_FATAL,"Error allocating memory for ssh_scp");
+ return NULL;
+ }
+ ZERO_STRUCTP(scp);
+ if((mode&~SSH_SCP_RECURSIVE) != SSH_SCP_WRITE && (mode &~SSH_SCP_RECURSIVE) != SSH_SCP_READ){
+ ssh_set_error(session,SSH_FATAL,"Invalid mode %d for ssh_scp_new()",mode);
+ ssh_scp_free(scp);
+ return NULL;
+ }
+ scp->location=strdup(location);
+ if (scp->location == NULL) {
+ ssh_set_error(session,SSH_FATAL,"Error allocating memory for ssh_scp");
+ ssh_scp_free(scp);
+ return NULL;
+ }
+ scp->session=session;
+ scp->mode=mode & ~SSH_SCP_RECURSIVE;
+ scp->recursive = (mode & SSH_SCP_RECURSIVE) != 0;
+ scp->channel=NULL;
+ scp->state=SSH_SCP_NEW;
+ return scp;
+}
+
+int ssh_scp_init(ssh_scp scp){
+ int r;
+ char execbuffer[1024];
+ uint8_t code;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_NEW){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_init called under invalid state");
+ return SSH_ERROR;
+ }
+ ssh_log(scp->session,SSH_LOG_PROTOCOL,"Initializing scp session %s %son location '%s'",
+ scp->mode==SSH_SCP_WRITE?"write":"read",
+ scp->recursive?"recursive ":"",
+ scp->location);
+ scp->channel=ssh_channel_new(scp->session);
+ if(scp->channel == NULL){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ r= ssh_channel_open_session(scp->channel);
+ if(r==SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(scp->mode == SSH_SCP_WRITE)
+ snprintf(execbuffer,sizeof(execbuffer),"scp -t %s %s",
+ scp->recursive ? "-r":"", scp->location);
+ else
+ snprintf(execbuffer,sizeof(execbuffer),"scp -f %s %s",
+ scp->recursive ? "-r":"", scp->location);
+ if(ssh_channel_request_exec(scp->channel,execbuffer) == SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(scp->mode == SSH_SCP_WRITE){
+ r=ssh_channel_read(scp->channel,&code,1,0);
+ if(r<=0){
+ ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session));
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(code != 0){
+ ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code);
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ } else {
+ ssh_channel_write(scp->channel,"",1);
+ }
+ if(scp->mode == SSH_SCP_WRITE)
+ scp->state=SSH_SCP_WRITE_INITED;
+ else
+ scp->state=SSH_SCP_READ_INITED;
+ return SSH_OK;
+}
+
+int ssh_scp_close(ssh_scp scp){
+ char buffer[128];
+ int err;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->channel != NULL){
+ if(ssh_channel_send_eof(scp->channel) == SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ /* avoid situations where data are buffered and
+ * not yet stored on disk. This can happen if the close is sent
+ * before we got the EOF back
+ */
+ while(!ssh_channel_is_eof(scp->channel)){
+ err=ssh_channel_read(scp->channel,buffer,sizeof(buffer),0);
+ if(err==SSH_ERROR)
+ break;
+ }
+ if(ssh_channel_close(scp->channel) == SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ ssh_channel_free(scp->channel);
+ scp->channel=NULL;
+ }
+ scp->state=SSH_SCP_NEW;
+ return SSH_OK;
+}
+
+void ssh_scp_free(ssh_scp scp){
+ if(scp==NULL)
+ return;
+ if(scp->state != SSH_SCP_NEW)
+ ssh_scp_close(scp);
+ if(scp->channel)
+ ssh_channel_free(scp->channel);
+ SAFE_FREE(scp->location);
+ SAFE_FREE(scp->request_name);
+ SAFE_FREE(scp->warning);
+ SAFE_FREE(scp);
+}
+
+/**
+ * @brief Create a directory in a scp in sink mode.
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @param[in] dirname The name of the directory being created.
+ *
+ * @param[in] mode The UNIX permissions for the new directory, e.g. 0755.
+ *
+ * @returns SSH_OK if the directory has been created, SSH_ERROR if
+ * an error occured.
+ *
+ * @see ssh_scp_leave_directory()
+ */
+int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode){
+ char buffer[1024];
+ int r;
+ uint8_t code;
+ char *dir;
+ char *perms;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_WRITE_INITED){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_push_directory called under invalid state");
+ return SSH_ERROR;
+ }
+ dir=ssh_basename(dirname);
+ perms=ssh_scp_string_mode(mode);
+ snprintf(buffer, sizeof(buffer), "D%s 0 %s\n", perms, dir);
+ SAFE_FREE(dir);
+ SAFE_FREE(perms);
+ r=ssh_channel_write(scp->channel,buffer,strlen(buffer));
+ if(r==SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ r=ssh_channel_read(scp->channel,&code,1,0);
+ if(r<=0){
+ ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session));
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(code != 0){
+ ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code);
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ return SSH_OK;
+}
+
+/**
+ * @brief Leave a directory.
+ *
+ * @returns SSH_OK if the directory has been left,SSH_ERROR if an
+ * error occured.
+ *
+ * @see ssh_scp_push_directory()
+ */
+ int ssh_scp_leave_directory(ssh_scp scp){
+ char buffer[]="E\n";
+ int r;
+ uint8_t code;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_WRITE_INITED){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_leave_directory called under invalid state");
+ return SSH_ERROR;
+ }
+ r=ssh_channel_write(scp->channel,buffer,strlen(buffer));
+ if(r==SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ r=ssh_channel_read(scp->channel,&code,1,0);
+ if(r<=0){
+ ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session));
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(code != 0){
+ ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code);
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ return SSH_OK;
+}
+
+/**
+ * @brief Initialize the sending of a file to a scp in sink mode.
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @param[in] filename The name of the file being sent. It should not contain
+ * any path indicator
+ *
+ * @param[in] size Exact size in bytes of the file being sent.
+ *
+ * @param[in] mode The UNIX permissions for the new file, e.g. 0644.
+ *
+ * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an
+ * error occured.
+ */
+int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode){
+ char buffer[1024];
+ int r;
+ uint8_t code;
+ char *file;
+ char *perms;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_WRITE_INITED){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_push_file called under invalid state");
+ return SSH_ERROR;
+ }
+ file=ssh_basename(filename);
+ perms=ssh_scp_string_mode(mode);
+ ssh_log(scp->session,SSH_LOG_PROTOCOL,"SCP pushing file %s, size %" PRIdS " with permissions '%s'",file,size,perms);
+ snprintf(buffer, sizeof(buffer), "C%s %" PRIdS " %s\n", perms, size, file);
+ SAFE_FREE(file);
+ SAFE_FREE(perms);
+ r=ssh_channel_write(scp->channel,buffer,strlen(buffer));
+ if(r==SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ r=ssh_channel_read(scp->channel,&code,1,0);
+ if(r<=0){
+ ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session));
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(code != 0){
+ ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code);
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ scp->filelen = size;
+ scp->processed = 0;
+ scp->state=SSH_SCP_WRITE_WRITING;
+ return SSH_OK;
+}
+
+/**
+ * @internal
+ *
+ * @brief Wait for a response of the scp server.
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @param[out] response A pointer where the response message must be copied if
+ * any. This pointer must then be free'd.
+ *
+ * @returns The return code, SSH_ERROR a error occured.
+ */
+int ssh_scp_response(ssh_scp scp, char **response){
+ unsigned char code;
+ int r;
+ char msg[128];
+ if(scp==NULL)
+ return SSH_ERROR;
+ r=ssh_channel_read(scp->channel,&code,1,0);
+ if(r == SSH_ERROR)
+ return SSH_ERROR;
+ if(code == 0)
+ return 0;
+ if(code > 2){
+ ssh_set_error(scp->session,SSH_FATAL, "SCP: invalid status code %ud received", code);
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ r=ssh_scp_read_string(scp,msg,sizeof(msg));
+ if(r==SSH_ERROR)
+ return r;
+ /* Warning */
+ if(code == 1){
+ ssh_set_error(scp->session,SSH_REQUEST_DENIED, "SCP: Warning: status code 1 received: %s", msg);
+ ssh_log(scp->session,SSH_LOG_RARE,"SCP: Warning: status code 1 received: %s", msg);
+ if(response)
+ *response=strdup(msg);
+ return 1;
+ }
+ if(code == 2){
+ ssh_set_error(scp->session,SSH_FATAL, "SCP: Error: status code 2 received: %s", msg);
+ if(response)
+ *response=strdup(msg);
+ return 2;
+ }
+ /* Not reached */
+ return SSH_ERROR;
+}
+
+/**
+ * @brief Write into a remote scp file.
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @param[in] buffer The buffer to write.
+ *
+ * @param[in] len The number of bytes to write.
+ *
+ * @returns SSH_OK if the write was successful, SSH_ERROR an error
+ * occured while writing.
+ */
+int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len){
+ int w;
+ //int r;
+ //uint8_t code;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_WRITE_WRITING){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_write called under invalid state");
+ return SSH_ERROR;
+ }
+ if(scp->processed + len > scp->filelen)
+ len = scp->filelen - scp->processed;
+ /* hack to avoid waiting for window change */
+ ssh_channel_poll(scp->channel,0);
+ w=ssh_channel_write(scp->channel,buffer,len);
+ if(w != SSH_ERROR)
+ scp->processed += w;
+ else {
+ scp->state=SSH_SCP_ERROR;
+ //return=channel_get_exit_status(scp->channel);
+ return SSH_ERROR;
+ }
+ /* Check if we arrived at end of file */
+ if(scp->processed == scp->filelen) {
+/* r=channel_read(scp->channel,&code,1,0);
+ if(r==SSH_ERROR){
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ if(code != 0){
+ ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code);
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+*/
+ scp->processed=scp->filelen=0;
+ scp->state=SSH_SCP_WRITE_INITED;
+ }
+ return SSH_OK;
+}
+
+/**
+ * @brief Read a string on a channel, terminated by '\n'
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @param[out] buffer A pointer to a buffer to place the string.
+ *
+ * @param[in] len The size of the buffer in bytes. If the string is bigger
+ * than len-1, only len-1 bytes are read and the string is
+ * null-terminated.
+ *
+ * @returns SSH_OK if the string was read, SSH_ERROR if an error
+ * occured while reading.
+ */
+int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len){
+ size_t r=0;
+ int err=SSH_OK;
+ if(scp==NULL)
+ return SSH_ERROR;
+ while(r<len-1){
+ err=ssh_channel_read(scp->channel,&buffer[r],1,0);
+ if(err==SSH_ERROR){
+ break;
+ }
+ if(err==0){
+ ssh_set_error(scp->session,SSH_FATAL,"End of file while reading string");
+ err=SSH_ERROR;
+ break;
+ }
+ r++;
+ if(buffer[r-1] == '\n')
+ break;
+ }
+ buffer[r]=0;
+ return err;
+}
+
+/**
+ * @brief Wait for a scp request (file, directory).
+ *
+ * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending
+ * a file
+ * SSH_SCP_REQUEST_NEWDIRECTORY: The other side is sending
+ * a directory
+ * SSH_SCP_REQUEST_END_DIRECTORY: The other side has
+ * finished with the current
+ * directory
+ * SSH_ERROR: Some error happened
+ *
+ * @see ssh_scp_read()
+ * @see ssh_scp_deny_request()
+ * @see ssh_scp_accept_request()
+ */
+int ssh_scp_pull_request(ssh_scp scp){
+ char buffer[4096];
+ char *mode=NULL;
+ char *p,*tmp;
+ size_t size;
+ char *name=NULL;
+ int err;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_READ_INITED){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_pull_request called under invalid state");
+ return SSH_ERROR;
+ }
+ err=ssh_scp_read_string(scp,buffer,sizeof(buffer));
+ if(err==SSH_ERROR){
+ if(ssh_channel_is_eof(scp->channel)){
+ scp->state=SSH_SCP_TERMINATED;
+ return SSH_SCP_REQUEST_EOF;
+ }
+ return err;
+ }
+ p=strchr(buffer,'\n');
+ if(p!=NULL)
+ *p='\0';
+ ssh_log(scp->session,SSH_LOG_PROTOCOL,"Received SCP request: '%s'",buffer);
+ switch(buffer[0]){
+ case 'C':
+ /* File */
+ case 'D':
+ /* Directory */
+ p=strchr(buffer,' ');
+ if(p==NULL)
+ goto error;
+ *p='\0';
+ p++;
+ //mode=strdup(&buffer[1]);
+ scp->request_mode=ssh_scp_integer_mode(&buffer[1]);
+ tmp=p;
+ p=strchr(p,' ');
+ if(p==NULL)
+ goto error;
+ *p=0;
+ size=strtoull(tmp,NULL,10);
+ p++;
+ name=strdup(p);
+ SAFE_FREE(scp->request_name);
+ scp->request_name=name;
+ if(buffer[0]=='C'){
+ scp->filelen=size;
+ scp->request_type=SSH_SCP_REQUEST_NEWFILE;
+ } else {
+ scp->filelen='0';
+ scp->request_type=SSH_SCP_REQUEST_NEWDIR;
+ }
+ scp->state=SSH_SCP_READ_REQUESTED;
+ scp->processed = 0;
+ return scp->request_type;
+ break;
+ case 'E':
+ scp->request_type=SSH_SCP_REQUEST_ENDDIR;
+ ssh_channel_write(scp->channel,"",1);
+ return scp->request_type;
+ case 0x1:
+ ssh_set_error(scp->session,SSH_REQUEST_DENIED,"SCP: Warning: %s",&buffer[1]);
+ scp->request_type=SSH_SCP_REQUEST_WARNING;
+ SAFE_FREE(scp->warning);
+ scp->warning=strdup(&buffer[1]);
+ return scp->request_type;
+ case 0x2:
+ ssh_set_error(scp->session,SSH_FATAL,"SCP: Error: %s",&buffer[1]);
+ return SSH_ERROR;
+ case 'T':
+ /* Timestamp */
+ default:
+ ssh_set_error(scp->session,SSH_FATAL,"Unhandled message: (%d)%s",buffer[0],buffer);
+ return SSH_ERROR;
+ }
+
+ /* a parsing error occured */
+ error:
+ SAFE_FREE(name);
+ SAFE_FREE(mode);
+ ssh_set_error(scp->session,SSH_FATAL,"Parsing error while parsing message: %s",buffer);
+ return SSH_ERROR;
+}
+
+/**
+ * @brief Deny the transfer of a file or creation of a directory coming from the
+ * remote party.
+ *
+ * @param[in] scp The scp handle.
+ * @param[in] reason A nul-terminated string with a human-readable
+ * explanation of the deny.
+ *
+ * @returns SSH_OK if the message was sent, SSH_ERROR if the sending
+ * the message failed, or sending it in a bad state.
+ */
+int ssh_scp_deny_request(ssh_scp scp, const char *reason){
+ char buffer[4096];
+ int err;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_READ_REQUESTED){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_deny_request called under invalid state");
+ return SSH_ERROR;
+ }
+ snprintf(buffer,sizeof(buffer),"%c%s\n",2,reason);
+ err=ssh_channel_write(scp->channel,buffer,strlen(buffer));
+ if(err==SSH_ERROR) {
+ return SSH_ERROR;
+ }
+ else {
+ scp->state=SSH_SCP_READ_INITED;
+ return SSH_OK;
+ }
+}
+
+/**
+ * @brief Accepts transfer of a file or creation of a directory coming from the
+ * remote party.
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @returns SSH_OK if the message was sent, SSH_ERROR if sending the
+ * message failed, or sending it in a bad state.
+ */
+int ssh_scp_accept_request(ssh_scp scp){
+ char buffer[]={0x00};
+ int err;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state != SSH_SCP_READ_REQUESTED){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_deny_request called under invalid state");
+ return SSH_ERROR;
+ }
+ err=ssh_channel_write(scp->channel,buffer,1);
+ if(err==SSH_ERROR) {
+ return SSH_ERROR;
+ }
+ if(scp->request_type==SSH_SCP_REQUEST_NEWFILE)
+ scp->state=SSH_SCP_READ_READING;
+ else
+ scp->state=SSH_SCP_READ_INITED;
+ return SSH_OK;
+}
+
+/** @brief Read from a remote scp file
+ * @param[in] scp The scp handle.
+ *
+ * @param[in] buffer The destination buffer.
+ *
+ * @param[in] size The size of the buffer.
+ *
+ * @returns The nNumber of bytes read, SSH_ERROR if an error occured
+ * while reading.
+ */
+int ssh_scp_read(ssh_scp scp, void *buffer, size_t size){
+ int r;
+ int code;
+ if(scp==NULL)
+ return SSH_ERROR;
+ if(scp->state == SSH_SCP_READ_REQUESTED && scp->request_type == SSH_SCP_REQUEST_NEWFILE){
+ r=ssh_scp_accept_request(scp);
+ if(r==SSH_ERROR)
+ return r;
+ }
+ if(scp->state != SSH_SCP_READ_READING){
+ ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_read called under invalid state");
+ return SSH_ERROR;
+ }
+ if(scp->processed + size > scp->filelen)
+ size = scp->filelen - scp->processed;
+ if(size > 65536)
+ size=65536; /* avoid too large reads */
+ r=ssh_channel_read(scp->channel,buffer,size,0);
+ if(r != SSH_ERROR)
+ scp->processed += r;
+ else {
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ /* Check if we arrived at end of file */
+ if(scp->processed == scp->filelen) {
+ scp->processed=scp->filelen=0;
+ ssh_channel_write(scp->channel,"",1);
+ code=ssh_scp_response(scp,NULL);
+ if(code == 0){
+ scp->state=SSH_SCP_READ_INITED;
+ return r;
+ }
+ if(code==1){
+ scp->state=SSH_SCP_READ_INITED;
+ return SSH_ERROR;
+ }
+ scp->state=SSH_SCP_ERROR;
+ return SSH_ERROR;
+ }
+ return r;
+}
+
+/**
+ * @brief Get the name of the directory or file being pushed from the other
+ * party.
+ *
+ * @returns The file name, NULL on error. The string should not be
+ * freed.
+ */
+const char *ssh_scp_request_get_filename(ssh_scp scp){
+ if(scp==NULL)
+ return NULL;
+ return scp->request_name;
+}
+
+/**
+ * @brief Get the permissions of the directory or file being pushed from the
+ * other party.
+ *
+ * @returns The UNIX permission, e.g 0644, -1 on error.
+ */
+int ssh_scp_request_get_permissions(ssh_scp scp){
+ if(scp==NULL)
+ return -1;
+ return scp->request_mode;
+}
+
+/** @brief Get the size of the file being pushed from the other party.
+ *
+ * @returns The numeric size of the file being read.
+ */
+size_t ssh_scp_request_get_size(ssh_scp scp){
+ if(scp==NULL)
+ return 0;
+ return scp->filelen;
+}
+
+/**
+ * @brief Convert a scp text mode to an integer.
+ *
+ * @param[in] mode The mode to convert, e.g. "0644".
+ *
+ * @returns An integer value, e.g. 420 for "0644".
+ */
+int ssh_scp_integer_mode(const char *mode){
+ int value=strtoul(mode,NULL,8) & 0xffff;
+ return value;
+}
+
+/**
+ * @brief Convert a unix mode into a scp string.
+ *
+ * @param[in] mode The mode to convert, e.g. 420 or 0644.
+ *
+ * @returns A pointer to a malloc'ed string containing the scp mode,
+ * e.g. "0644".
+ */
+char *ssh_scp_string_mode(int mode){
+ char buffer[16];
+ snprintf(buffer,sizeof(buffer),"%.4o",mode);
+ return strdup(buffer);
+}
+
+/**
+ * @brief Get the warning string from a scp handle.
+ *
+ * @param[in] scp The scp handle.
+ *
+ * @returns A warning string, or NULL on error. The string should
+ * not be freed.
+ */
+const char *ssh_scp_request_get_warning(ssh_scp scp){
+ if(scp==NULL)
+ return NULL;
+ return scp->warning;
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
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: */
diff --git a/src/session.c b/src/session.c
new file mode 100644
index 00000000..42f5e772
--- /dev/null
+++ b/src/session.c
@@ -0,0 +1,520 @@
+/*
+ * session.c - non-networking functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2005-2008 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 <string.h>
+#include <stdlib.h>
+#include "libssh/libssh.h"
+#include "libssh/priv.h"
+#include "libssh/server.h"
+#include "libssh/socket.h"
+#include "libssh/ssh2.h"
+#include "libssh/agent.h"
+#include "libssh/packet.h"
+#include "libssh/session.h"
+#include "libssh/misc.h"
+#include "libssh/buffer.h"
+#include "libssh/poll.h"
+
+#define FIRST_CHANNEL 42 // why not ? it helps to find bugs.
+
+/**
+ * @defgroup libssh_session The SSH session functions.
+ * @ingroup libssh
+ *
+ * Functions that manage a session.
+ *
+ * @{
+ */
+
+/**
+ * @brief Create a new ssh session.
+ *
+ * @returns A new ssh_session pointer, NULL on error.
+ */
+ssh_session ssh_new(void) {
+ ssh_session session;
+ char *id;
+ int rc;
+
+ session = malloc(sizeof (struct ssh_session_struct));
+ if (session == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(session);
+
+ session->next_crypto = crypto_new();
+ if (session->next_crypto == NULL) {
+ goto err;
+ }
+
+ session->socket = ssh_socket_new(session);
+ if (session->socket == NULL) {
+ goto err;
+ }
+
+ session->out_buffer = ssh_buffer_new();
+ if (session->out_buffer == NULL) {
+ goto err;
+ }
+
+ session->in_buffer=ssh_buffer_new();
+ if (session->in_buffer == NULL) {
+ goto err;
+ }
+
+ session->alive = 0;
+ session->auth_methods = 0;
+ session->blocking = 1;
+ session->log_indent = 0;
+ session->maxchannel = FIRST_CHANNEL;
+
+ /* options */
+ session->StrictHostKeyChecking = 1;
+ session->port = 22;
+ session->fd = -1;
+ session->ssh2 = 1;
+#ifdef WITH_SSH1
+ session->ssh1 = 1;
+#else
+ session->ssh1 = 0;
+#endif
+
+#ifndef _WIN32
+ session->agent = agent_new(session);
+ if (session->agent == NULL) {
+ goto err;
+ }
+#endif /* _WIN32 */
+
+ session->identity = ssh_list_new();
+ if (session->identity == NULL) {
+ goto err;
+ }
+
+ id = strdup("%d/id_rsa");
+ if (id == NULL) {
+ goto err;
+ }
+ rc = ssh_list_append(session->identity, id);
+ if (rc == SSH_ERROR) {
+ goto err;
+ }
+
+ id = strdup("%d/id_dsa");
+ if (id == NULL) {
+ goto err;
+ }
+ rc = ssh_list_append(session->identity, id);
+ if (rc == SSH_ERROR) {
+ goto err;
+ }
+
+ id = strdup("%d/identity");
+ if (id == NULL) {
+ goto err;
+ }
+ rc = ssh_list_append(session->identity, id);
+ if (rc == SSH_ERROR) {
+ goto err;
+ }
+
+ return session;
+
+err:
+ ssh_free(session);
+ return NULL;
+}
+
+/**
+ * @brief Deallocate a SSH session handle.
+ *
+ * @param[in] session The SSH session to free.
+ *
+ * @see ssh_disconnect()
+ * @see ssh_new()
+ */
+void ssh_free(ssh_session session) {
+ int i;
+ enter_function();
+
+ if (session == NULL) {
+ return;
+ }
+
+ SAFE_FREE(session->serverbanner);
+ SAFE_FREE(session->clientbanner);
+ SAFE_FREE(session->banner);
+#ifdef WITH_PCAP
+ if(session->pcap_ctx){
+ ssh_pcap_context_free(session->pcap_ctx);
+ session->pcap_ctx=NULL;
+ }
+#endif
+ ssh_buffer_free(session->in_buffer);
+ ssh_buffer_free(session->out_buffer);
+ session->in_buffer=session->out_buffer=NULL;
+ crypto_free(session->current_crypto);
+ crypto_free(session->next_crypto);
+ ssh_socket_free(session->socket);
+ /* delete all channels */
+ while (session->channels) {
+ ssh_channel_free(session->channels);
+ }
+#ifndef _WIN32
+ agent_free(session->agent);
+#endif /* _WIN32 */
+ if (session->client_kex.methods) {
+ for (i = 0; i < 10; i++) {
+ SAFE_FREE(session->client_kex.methods[i]);
+ }
+ }
+
+ if (session->server_kex.methods) {
+ for (i = 0; i < 10; i++) {
+ SAFE_FREE(session->server_kex.methods[i]);
+ }
+ }
+ SAFE_FREE(session->client_kex.methods);
+ SAFE_FREE(session->server_kex.methods);
+
+ privatekey_free(session->dsa_key);
+ privatekey_free(session->rsa_key);
+ if(session->ssh_message_list){
+ ssh_message msg;
+ while((msg=ssh_list_pop_head(ssh_message ,session->ssh_message_list))
+ != NULL){
+ ssh_message_free(msg);
+ }
+ ssh_list_free(session->ssh_message_list);
+ }
+
+ if (session->packet_callbacks)
+ ssh_list_free(session->packet_callbacks);
+
+ if (session->identity) {
+ char *id;
+
+ for (id = ssh_list_pop_head(char *, session->identity);
+ id != NULL;
+ id = ssh_list_pop_head(char *, session->identity)) {
+ SAFE_FREE(id);
+ }
+ ssh_list_free(session->identity);
+ }
+
+ /* options */
+ SAFE_FREE(session->username);
+ SAFE_FREE(session->host);
+ SAFE_FREE(session->sshdir);
+ SAFE_FREE(session->knownhosts);
+ SAFE_FREE(session->ProxyCommand);
+
+ for (i = 0; i < 10; i++) {
+ if (session->wanted_methods[i]) {
+ SAFE_FREE(session->wanted_methods[i]);
+ }
+ }
+
+ /* burn connection, it could hang sensitive datas */
+ ZERO_STRUCTP(session);
+ SAFE_FREE(session);
+}
+
+/**
+ * @brief Disconnect impolitely from a remote host by closing the socket.
+ *
+ * Suitable if you forked and want to destroy this session.
+ *
+ * @param[in] session The SSH session to disconnect.
+ */
+void ssh_silent_disconnect(ssh_session session) {
+ enter_function();
+
+ if (session == NULL) {
+ return;
+ }
+
+ ssh_socket_close(session->socket);
+ session->alive = 0;
+ ssh_disconnect(session);
+ leave_function();
+}
+
+/**
+ * @brief Set the session in blocking/nonblocking mode.
+ *
+ * @param[in] session The ssh session to change.
+ *
+ * @param[in] blocking Zero for nonblocking mode.
+ *
+ * \bug nonblocking code is in development and won't work as expected
+ */
+void ssh_set_blocking(ssh_session session, int blocking) {
+ if (session == NULL) {
+ return;
+ }
+
+ session->blocking = blocking ? 1 : 0;
+}
+
+/**
+ * @brief Get the fd of a connection.
+ *
+ * In case you'd need the file descriptor of the connection to the server/client.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @return The file descriptor of the connection, or -1 if it is
+ * not connected
+ */
+socket_t ssh_get_fd(ssh_session session) {
+ if (session == NULL) {
+ return -1;
+ }
+
+ return ssh_socket_get_fd_in(session->socket);
+}
+
+/**
+ * @brief Tell the session it has data to read on the file descriptor without
+ * blocking.
+ *
+ * @param[in] session The ssh session to use.
+ */
+void ssh_set_fd_toread(ssh_session session) {
+ if (session == NULL) {
+ return;
+ }
+
+ ssh_socket_set_toread(session->socket);
+}
+
+/**
+ * @brief Tell the session it may write to the file descriptor without blocking.
+ *
+ * @param[in] session The ssh session to use.
+ */
+void ssh_set_fd_towrite(ssh_session session) {
+ if (session == NULL) {
+ return;
+ }
+
+ ssh_socket_set_towrite(session->socket);
+}
+
+/**
+ * @brief Tell the session it has an exception to catch on the file descriptor.
+ *
+ * \param[in] session The ssh session to use.
+ */
+void ssh_set_fd_except(ssh_session session) {
+ if (session == NULL) {
+ return;
+ }
+
+ ssh_socket_set_except(session->socket);
+}
+
+/**
+ * @internal
+ *
+ * @brief Poll the current session for an event and call the appropriate
+ * callbacks.
+ *
+ * This will block until one event happens.
+ *
+ * @param[in] session The session handle to use.
+ *
+ * @param[in] timeout Set an upper limit on the time for which this function
+ * will block, in milliseconds. Specifying a negative value
+ * means an infinite timeout. This parameter is passed to
+ * the poll() function.
+ *
+ * @return SSH_OK on success, SSH_ERROR otherwise.
+ */
+int ssh_handle_packets(ssh_session session, int timeout) {
+ ssh_poll_handle spoll_in,spoll_out;
+ ssh_poll_ctx ctx;
+ if(session==NULL || session->socket==NULL)
+ return SSH_ERROR;
+ enter_function();
+ spoll_in=ssh_socket_get_poll_handle_in(session->socket);
+ spoll_out=ssh_socket_get_poll_handle_out(session->socket);
+ ssh_poll_set_events(spoll_in, POLLIN | POLLERR);
+ ctx=ssh_poll_get_ctx(spoll_in);
+ if(ctx==NULL){
+ ctx=ssh_get_global_poll_ctx(session);
+ ssh_poll_ctx_add(ctx,spoll_in);
+ if(spoll_in != spoll_out)
+ ssh_poll_ctx_add(ctx,spoll_out);
+ }
+ ssh_poll_ctx_dopoll(ctx,timeout);
+ leave_function();
+ if (session->session_state != SSH_SESSION_STATE_ERROR)
+ return SSH_OK;
+ else
+ return SSH_ERROR;
+}
+
+/**
+ * @brief Get session status
+ *
+ * @param session The ssh session to use.
+ *
+ * @returns A bitmask including SSH_CLOSED, SSH_READ_PENDING or SSH_CLOSED_ERROR
+ * which respectively means the session is closed, has data to read on
+ * the connection socket and session was closed due to an error.
+ */
+int ssh_get_status(ssh_session session) {
+ int socketstate;
+ int r = 0;
+
+ if (session == NULL) {
+ return 0;
+ }
+
+ socketstate = ssh_socket_get_status(session->socket);
+
+ if (session->closed) {
+ r |= SSH_CLOSED;
+ }
+ if (socketstate & SSH_READ_PENDING) {
+ r |= SSH_READ_PENDING;
+ }
+ if (session->closed && (socketstate & SSH_CLOSED_ERROR)) {
+ r |= SSH_CLOSED_ERROR;
+ }
+
+ return r;
+}
+
+/**
+ * @brief Get the disconnect message from the server.
+ *
+ * @param[in] session The ssh session to use.
+ *
+ * @return The message sent by the server along with the
+ * disconnect, or NULL in which case the reason of the
+ * disconnect may be found with ssh_get_error.
+ *
+ * @see ssh_get_error()
+ */
+const char *ssh_get_disconnect_message(ssh_session session) {
+ if (session == NULL) {
+ return NULL;
+ }
+
+ if (!session->closed) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Connection not closed yet");
+ } else if(session->closed_by_except) {
+ ssh_set_error(session, SSH_REQUEST_DENIED,
+ "Connection closed by socket error");
+ } else if(!session->discon_msg) {
+ ssh_set_error(session, SSH_FATAL,
+ "Connection correctly closed but no disconnect message");
+ } else {
+ return session->discon_msg;
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Get the protocol version of the session.
+ *
+ * @param session The ssh session to use.
+ *
+ * @return 1 or 2, for ssh1 or ssh2, < 0 on error.
+ */
+int ssh_get_version(ssh_session session) {
+ if (session == NULL) {
+ return -1;
+ }
+
+ return session->version;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_DISCONNECT packet.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){
+ uint32_t code;
+ char *error=NULL;
+ ssh_string error_s;
+ (void)user;
+ (void)type;
+ buffer_get_u32(packet, &code);
+ error_s = buffer_get_ssh_string(packet);
+ if (error_s != NULL) {
+ error = ssh_string_to_char(error_s);
+ ssh_string_free(error_s);
+ }
+ ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %d:%s",code,
+ error != NULL ? error : "no error");
+ ssh_set_error(session, SSH_FATAL,
+ "Received SSH_MSG_DISCONNECT: %d:%s",code,
+ error != NULL ? error : "no error");
+ SAFE_FREE(error);
+
+ ssh_socket_close(session->socket);
+ session->alive = 0;
+ /* TODO: handle a graceful disconnect */
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ *
+ * @brief Handle a SSH_IGNORE and SSH_DEBUG packet.
+ */
+SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){
+ (void)user;
+ (void)type;
+ (void)packet;
+ ssh_log(session,SSH_LOG_PROTOCOL,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG");
+ /* TODO: handle a graceful disconnect */
+ return SSH_PACKET_USED;
+}
+
+/**
+ * @internal
+ * @brief Callback to be called when the socket received an exception code.
+ * @param user is a pointer to session
+ */
+void ssh_socket_exception_callback(int code, int errno_code, void *user){
+ ssh_session session=(ssh_session)user;
+ enter_function();
+ ssh_log(session,SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code);
+ session->session_state=SSH_SESSION_STATE_ERROR;
+ ssh_set_error(session,SSH_FATAL,"Socket error: %s",strerror(errno_code));
+ session->ssh_connection_callback(session);
+ leave_function();
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/sftp.c b/src/sftp.c
new file mode 100644
index 00000000..e5c91af3
--- /dev/null
+++ b/src/sftp.c
@@ -0,0 +1,3207 @@
+/*
+ * sftp.c - Secure FTP functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2005-2008 by Aris Adamantiadis
+ * Copyright (c) 2008-2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * 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.
+ */
+
+/* This file contains code written by Nick Zitzmann */
+
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#else
+#define S_IFSOCK 0140000
+#define S_IFLNK 0120000
+
+#ifdef _MSC_VER
+#define S_IFBLK 0060000
+#define S_IFIFO 0010000
+#endif
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/sftp.h"
+#include "libssh/buffer.h"
+#include "libssh/channels.h"
+#include "libssh/session.h"
+#include "libssh/misc.h"
+
+#ifdef WITH_SFTP
+
+#define sftp_enter_function() _enter_function(sftp->channel->session)
+#define sftp_leave_function() _leave_function(sftp->channel->session)
+
+struct sftp_ext_struct {
+ unsigned int count;
+ char **name;
+ char **data;
+};
+
+/* functions */
+static int sftp_enqueue(sftp_session session, sftp_message msg);
+static void sftp_message_free(sftp_message msg);
+static void sftp_set_error(sftp_session sftp, int errnum);
+static void status_msg_free(sftp_status_message status);
+
+static sftp_ext sftp_ext_new(void) {
+ sftp_ext ext;
+
+ ext = malloc(sizeof(struct sftp_ext_struct));
+ if (ext == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(ext);
+
+ return ext;
+}
+
+static void sftp_ext_free(sftp_ext ext) {
+ unsigned int i;
+
+ if (ext == NULL) {
+ return;
+ }
+
+ if (ext->count) {
+ for (i = 0; i < ext->count; i++) {
+ SAFE_FREE(ext->name[i]);
+ SAFE_FREE(ext->data[i]);
+ }
+ SAFE_FREE(ext->name);
+ SAFE_FREE(ext->data);
+ }
+
+ SAFE_FREE(ext);
+}
+
+sftp_session sftp_new(ssh_session session){
+ sftp_session sftp;
+
+ if (session == NULL) {
+ return NULL;
+ }
+ enter_function();
+
+ sftp = malloc(sizeof(struct sftp_session_struct));
+ if (sftp == NULL) {
+ ssh_set_error_oom(session);
+ leave_function();
+ return NULL;
+ }
+ ZERO_STRUCTP(sftp);
+
+ sftp->ext = sftp_ext_new();
+ if (sftp->ext == NULL) {
+ ssh_set_error_oom(session);
+ SAFE_FREE(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ sftp->session = session;
+ sftp->channel = ssh_channel_new(session);
+ if (sftp->channel == NULL) {
+ SAFE_FREE(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ if (ssh_channel_open_session(sftp->channel)) {
+ ssh_channel_free(sftp->channel);
+ SAFE_FREE(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ if (ssh_channel_request_sftp(sftp->channel)) {
+ sftp_free(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ leave_function();
+ return sftp;
+}
+
+#ifdef WITH_SERVER
+sftp_session sftp_server_new(ssh_session session, ssh_channel chan){
+ sftp_session sftp = NULL;
+
+ sftp = malloc(sizeof(struct sftp_session_struct));
+ if (sftp == NULL) {
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+ ZERO_STRUCTP(sftp);
+
+ sftp->session = session;
+ sftp->channel = chan;
+
+ return sftp;
+}
+
+int sftp_server_init(sftp_session sftp){
+ ssh_session session = sftp->session;
+ sftp_packet packet = NULL;
+ ssh_buffer reply = NULL;
+ uint32_t version;
+
+ sftp_enter_function();
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (packet->type != SSH_FXP_INIT) {
+ ssh_set_error(session, SSH_FATAL,
+ "Packet read of type %d instead of SSH_FXP_INIT",
+ packet->type);
+
+ sftp_packet_free(packet);
+ sftp_leave_function();
+ return -1;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Received SSH_FXP_INIT");
+
+ buffer_get_u32(packet->payload, &version);
+ version = ntohl(version);
+ ssh_log(session, SSH_LOG_PACKET, "Client version: %d", version);
+ sftp->client_version = version;
+
+ sftp_packet_free(packet);
+
+ reply = ssh_buffer_new();
+ if (reply == NULL) {
+ ssh_set_error_oom(session);
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (buffer_add_u32(reply, ntohl(LIBSFTP_VERSION)) < 0) {
+ ssh_set_error_oom(session);
+ ssh_buffer_free(reply);
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) {
+ ssh_buffer_free(reply);
+ sftp_leave_function();
+ return -1;
+ }
+ ssh_buffer_free(reply);
+
+ ssh_log(session, SSH_LOG_RARE, "Server version sent");
+
+ if (version > LIBSFTP_VERSION) {
+ sftp->version = LIBSFTP_VERSION;
+ } else {
+ sftp->version=version;
+ }
+
+ sftp_leave_function();
+ return 0;
+}
+#endif /* WITH_SERVER */
+
+void sftp_free(sftp_session sftp){
+ sftp_request_queue ptr;
+
+ if (sftp == NULL) {
+ return;
+ }
+
+ ssh_channel_send_eof(sftp->channel);
+ ptr = sftp->queue;
+ while(ptr) {
+ sftp_request_queue old;
+ sftp_message_free(ptr->message);
+ old = ptr->next;
+ SAFE_FREE(ptr);
+ ptr = old;
+ }
+
+ ssh_channel_free(sftp->channel);
+ sftp_ext_free(sftp->ext);
+ ZERO_STRUCTP(sftp);
+
+ SAFE_FREE(sftp);
+}
+
+int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload){
+ int size;
+
+ if (buffer_prepend_data(payload, &type, sizeof(uint8_t)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ size = htonl(ssh_buffer_get_len(payload));
+ if (buffer_prepend_data(payload, &size, sizeof(uint32_t)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ size = ssh_channel_write(sftp->channel, ssh_buffer_get_begin(payload),
+ ssh_buffer_get_len(payload));
+ if (size < 0) {
+ return -1;
+ } else if((uint32_t) size != ssh_buffer_get_len(payload)) {
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Had to write %d bytes, wrote only %d",
+ ssh_buffer_get_len(payload),
+ size);
+ }
+
+ return size;
+}
+
+sftp_packet sftp_packet_read(sftp_session sftp) {
+ sftp_packet packet = NULL;
+ uint32_t size;
+
+ sftp_enter_function();
+
+ packet = malloc(sizeof(struct sftp_packet_struct));
+ if (packet == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ packet->sftp = sftp;
+ packet->payload = ssh_buffer_new();
+ if (packet->payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(packet);
+ return NULL;
+ }
+
+ if (channel_read_buffer(sftp->channel, packet->payload, 4, 0) <= 0) {
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ if (buffer_get_u32(packet->payload, &size) != sizeof(uint32_t)) {
+ ssh_set_error(sftp->session, SSH_FATAL, "Short sftp packet!");
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ size = ntohl(size);
+ if (channel_read_buffer(sftp->channel, packet->payload, 1, 0) <= 0) {
+ /* TODO: check if there are cases where an error needs to be set here */
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ buffer_get_u8(packet->payload, &packet->type);
+ if (size > 1) {
+ if (channel_read_buffer(sftp->channel, packet->payload, size - 1, 0) <= 0) {
+ /* TODO: check if there are cases where an error needs to be set here */
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+ }
+
+ sftp_leave_function();
+ return packet;
+}
+
+static void sftp_set_error(sftp_session sftp, int errnum) {
+ if (sftp != NULL) {
+ sftp->errnum = errnum;
+ }
+}
+
+/* Get the last sftp error */
+int sftp_get_error(sftp_session sftp) {
+ if (sftp == NULL) {
+ return -1;
+ }
+
+ return sftp->errnum;
+}
+
+static sftp_message sftp_message_new(sftp_session sftp){
+ sftp_message msg = NULL;
+
+ sftp_enter_function();
+
+ msg = malloc(sizeof(struct sftp_message_struct));
+ if (msg == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(msg);
+
+ msg->payload = ssh_buffer_new();
+ if (msg->payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(msg);
+ return NULL;
+ }
+ msg->sftp = sftp;
+
+ sftp_leave_function();
+ return msg;
+}
+
+static void sftp_message_free(sftp_message msg) {
+ sftp_session sftp;
+
+ if (msg == NULL) {
+ return;
+ }
+
+ sftp = msg->sftp;
+ sftp_enter_function();
+
+ ssh_buffer_free(msg->payload);
+ SAFE_FREE(msg);
+
+ sftp_leave_function();
+}
+
+static sftp_message sftp_get_message(sftp_packet packet) {
+ sftp_session sftp = packet->sftp;
+ sftp_message msg = NULL;
+
+ sftp_enter_function();
+
+ msg = sftp_message_new(sftp);
+ if (msg == NULL) {
+ sftp_leave_function();
+ return NULL;
+ }
+
+ msg->sftp = packet->sftp;
+ msg->packet_type = packet->type;
+
+ if ((packet->type != SSH_FXP_STATUS) && (packet->type!=SSH_FXP_HANDLE) &&
+ (packet->type != SSH_FXP_DATA) && (packet->type != SSH_FXP_ATTRS) &&
+ (packet->type != SSH_FXP_NAME) && (packet->type != SSH_FXP_EXTENDED_REPLY)) {
+ ssh_set_error(packet->sftp->session, SSH_FATAL,
+ "Unknown packet type %d", packet->type);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ if (buffer_get_u32(packet->payload, &msg->id) != sizeof(uint32_t)) {
+ ssh_set_error(packet->sftp->session, SSH_FATAL,
+ "Invalid packet %d: no ID", packet->type);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ ssh_log(packet->sftp->session, SSH_LOG_PACKET,
+ "Packet with id %d type %d",
+ msg->id,
+ msg->packet_type);
+
+ if (buffer_add_data(msg->payload, buffer_get_rest(packet->payload),
+ buffer_get_rest_len(packet->payload)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ sftp_leave_function();
+ return msg;
+}
+
+static int sftp_read_and_dispatch(sftp_session sftp) {
+ sftp_packet packet = NULL;
+ sftp_message msg = NULL;
+
+ sftp_enter_function();
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_leave_function();
+ return -1; /* something nasty happened reading the packet */
+ }
+
+ msg = sftp_get_message(packet);
+ sftp_packet_free(packet);
+ if (msg == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (sftp_enqueue(sftp, msg) < 0) {
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return -1;
+ }
+
+ sftp_leave_function();
+ return 0;
+}
+
+void sftp_packet_free(sftp_packet packet) {
+ if (packet == NULL) {
+ return;
+ }
+
+ ssh_buffer_free(packet->payload);
+ free(packet);
+}
+
+/* Initialize the sftp session with the server. */
+int sftp_init(sftp_session sftp) {
+ sftp_packet packet = NULL;
+ ssh_buffer buffer = NULL;
+ ssh_string ext_name_s = NULL;
+ ssh_string ext_data_s = NULL;
+ char *ext_name = NULL;
+ char *ext_data = NULL;
+ uint32_t version = htonl(LIBSFTP_VERSION);
+
+ sftp_enter_function();
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (buffer_add_u32(buffer, version) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ sftp_leave_function();
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ sftp_leave_function();
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (packet->type != SSH_FXP_VERSION) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received a %d messages instead of SSH_FXP_VERSION", packet->type);
+ sftp_packet_free(packet);
+ sftp_leave_function();
+ return -1;
+ }
+
+ /* TODO: are we sure there are 4 bytes ready? */
+ buffer_get_u32(packet->payload, &version);
+ version = ntohl(version);
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "SFTP server version %d",
+ version);
+
+ ext_name_s = buffer_get_ssh_string(packet->payload);
+ while (ext_name_s != NULL) {
+ int count = sftp->ext->count;
+ char **tmp;
+
+ ext_data_s = buffer_get_ssh_string(packet->payload);
+ if (ext_data_s == NULL) {
+ ssh_string_free(ext_name_s);
+ break;
+ }
+
+ ext_name = ssh_string_to_char(ext_name_s);
+ ext_data = ssh_string_to_char(ext_data_s);
+ if (ext_name == NULL || ext_data == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(ext_name);
+ SAFE_FREE(ext_data);
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+ return -1;
+ }
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "SFTP server extension: %s, version: %s",
+ ext_name, ext_data);
+
+ count++;
+ tmp = realloc(sftp->ext->name, count * sizeof(char *));
+ if (tmp == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(ext_name);
+ SAFE_FREE(ext_data);
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+ return -1;
+ }
+ tmp[count - 1] = ext_name;
+ sftp->ext->name = tmp;
+
+ tmp = realloc(sftp->ext->data, count * sizeof(char *));
+ if (tmp == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(ext_name);
+ SAFE_FREE(ext_data);
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+ return -1;
+ }
+ tmp[count - 1] = ext_data;
+ sftp->ext->data = tmp;
+
+ sftp->ext->count = count;
+
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+
+ ext_name_s = buffer_get_ssh_string(packet->payload);
+ }
+
+ sftp_packet_free(packet);
+
+ sftp->version = sftp->server_version = version;
+
+ sftp_leave_function();
+
+ return 0;
+}
+
+unsigned int sftp_extensions_get_count(sftp_session sftp) {
+ if (sftp == NULL || sftp->ext == NULL) {
+ return 0;
+ }
+
+ return sftp->ext->count;
+}
+
+const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) {
+ if (sftp == NULL)
+ return NULL;
+ if (sftp->ext == NULL || sftp->ext->name == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ if (idx > sftp->ext->count) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ return sftp->ext->name[idx];
+}
+
+const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) {
+ if (sftp == NULL)
+ return NULL;
+ if (sftp->ext == NULL || sftp->ext->name == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ if (idx > sftp->ext->count) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ return sftp->ext->data[idx];
+}
+
+int sftp_extension_supported(sftp_session sftp, const char *name,
+ const char *data) {
+ int i, n;
+
+ n = sftp_extensions_get_count(sftp);
+ for (i = 0; i < n; i++) {
+ if (strcmp(sftp_extensions_get_name(sftp, i), name) == 0 &&
+ strcmp(sftp_extensions_get_data(sftp, i), data) == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static sftp_request_queue request_queue_new(sftp_message msg) {
+ sftp_request_queue queue = NULL;
+
+ queue = malloc(sizeof(struct sftp_request_queue_struct));
+ if (queue == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(queue);
+
+ queue->message = msg;
+
+ return queue;
+}
+
+static void request_queue_free(sftp_request_queue queue) {
+ if (queue == NULL) {
+ return;
+ }
+
+ ZERO_STRUCTP(queue);
+ SAFE_FREE(queue);
+}
+
+static int sftp_enqueue(sftp_session sftp, sftp_message msg) {
+ sftp_request_queue queue = NULL;
+ sftp_request_queue ptr;
+
+ queue = request_queue_new(msg);
+ if (queue == NULL) {
+ return -1;
+ }
+
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Queued msg type %d id %d",
+ msg->id, msg->packet_type);
+
+ if(sftp->queue == NULL) {
+ sftp->queue = queue;
+ } else {
+ ptr = sftp->queue;
+ while(ptr->next) {
+ ptr=ptr->next; /* find end of linked list */
+ }
+ ptr->next = queue; /* add it on bottom */
+ }
+
+ return 0;
+}
+
+/*
+ * Pulls of a message from the queue based on the ID.
+ * Returns NULL if no message has been found.
+ */
+static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){
+ sftp_request_queue prev = NULL;
+ sftp_request_queue queue;
+ sftp_message msg;
+
+ if(sftp->queue == NULL) {
+ return NULL;
+ }
+
+ queue = sftp->queue;
+ while (queue) {
+ if(queue->message->id == id) {
+ /* remove from queue */
+ if (prev == NULL) {
+ sftp->queue = queue->next;
+ } else {
+ prev->next = queue->next;
+ }
+ msg = queue->message;
+ request_queue_free(queue);
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Dequeued msg id %d type %d",
+ msg->id,
+ msg->packet_type);
+ return msg;
+ }
+ prev = queue;
+ queue = queue->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Assigns a new SFTP ID for new requests and assures there is no collision
+ * between them.
+ * Returns a new ID ready to use in a request
+ */
+static inline uint32_t sftp_get_new_id(sftp_session session) {
+ return ++session->id_counter;
+}
+
+static sftp_status_message parse_status_msg(sftp_message msg){
+ sftp_status_message status;
+
+ if (msg->packet_type != SSH_FXP_STATUS) {
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Not a ssh_fxp_status message passed in!");
+ return NULL;
+ }
+
+ status = malloc(sizeof(struct sftp_status_message_struct));
+ if (status == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(status);
+
+ status->id = msg->id;
+ if (buffer_get_u32(msg->payload,&status->status) != 4){
+ SAFE_FREE(status);
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Invalid SSH_FXP_STATUS message");
+ return NULL;
+ }
+ status->error = buffer_get_ssh_string(msg->payload);
+ status->lang = buffer_get_ssh_string(msg->payload);
+ if(status->error == NULL || status->lang == NULL){
+ if(msg->sftp->version >=3){
+ /* These are mandatory from version 3 */
+ ssh_string_free(status->error);
+ /* status->lang never get allocated if something failed */
+ SAFE_FREE(status);
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Invalid SSH_FXP_STATUS message");
+ return NULL;
+ }
+ }
+
+ status->status = ntohl(status->status);
+ if(status->error)
+ status->errormsg = ssh_string_to_char(status->error);
+ else
+ status->errormsg = strdup("No error message in packet");
+ if(status->lang)
+ status->langmsg = ssh_string_to_char(status->lang);
+ else
+ status->langmsg = strdup("");
+ if (status->errormsg == NULL || status->langmsg == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ status_msg_free(status);
+ return NULL;
+ }
+
+ return status;
+}
+
+static void status_msg_free(sftp_status_message status){
+ if (status == NULL) {
+ return;
+ }
+
+ ssh_string_free(status->error);
+ ssh_string_free(status->lang);
+ SAFE_FREE(status->errormsg);
+ SAFE_FREE(status->langmsg);
+ SAFE_FREE(status);
+}
+
+static sftp_file parse_handle_msg(sftp_message msg){
+ sftp_file file;
+
+ if(msg->packet_type != SSH_FXP_HANDLE) {
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Not a ssh_fxp_handle message passed in!");
+ return NULL;
+ }
+
+ file = malloc(sizeof(struct sftp_file_struct));
+ if (file == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(file);
+
+ file->handle = buffer_get_ssh_string(msg->payload);
+ if (file->handle == NULL) {
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Invalid SSH_FXP_HANDLE message");
+ SAFE_FREE(file);
+ return NULL;
+ }
+
+ file->sftp = msg->sftp;
+ file->offset = 0;
+ file->eof = 0;
+
+ return file;
+}
+
+/* Open a directory */
+sftp_dir sftp_opendir(sftp_session sftp, const char *path){
+ sftp_message msg = NULL;
+ sftp_file file = NULL;
+ sftp_dir dir = NULL;
+ sftp_status_message status;
+ ssh_string path_s;
+ ssh_buffer payload;
+ uint32_t id;
+
+ payload = ssh_buffer_new();
+ if (payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ path_s = ssh_string_from_char(path);
+ if (path_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(payload, id) < 0 ||
+ buffer_add_ssh_string(payload, path_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(payload);
+ ssh_string_free(path_s);
+ return NULL;
+ }
+ ssh_string_free(path_s);
+
+ if (sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload) < 0) {
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+ ssh_buffer_free(payload);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return NULL;
+ case SSH_FXP_HANDLE:
+ file = parse_handle_msg(msg);
+ sftp_message_free(msg);
+ if (file != NULL) {
+ dir = malloc(sizeof(struct sftp_dir_struct));
+ if (dir == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(dir);
+
+ dir->sftp = sftp;
+ dir->name = strdup(path);
+ if (dir->name == NULL) {
+ SAFE_FREE(dir);
+ SAFE_FREE(file);
+ return NULL;
+ }
+ dir->handle = file->handle;
+ SAFE_FREE(file);
+ }
+ return dir;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during opendir!", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+/*
+ * Parse the attributes from a payload from some messages. It is coded on
+ * baselines from the protocol version 4.
+ * This code is more or less dead but maybe we need it in future.
+ */
+static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf,
+ int expectnames) {
+ sftp_attributes attr;
+ ssh_string owner = NULL;
+ ssh_string group = NULL;
+ uint32_t flags = 0;
+ int ok = 0;
+
+ /* unused member variable */
+ (void) expectnames;
+
+ attr = malloc(sizeof(struct sftp_attributes_struct));
+ if (attr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(attr);
+
+ /* This isn't really a loop, but it is like a try..catch.. */
+ do {
+ if (buffer_get_u32(buf, &flags) != 4) {
+ break;
+ }
+
+ flags = ntohl(flags);
+ attr->flags = flags;
+
+ if (flags & SSH_FILEXFER_ATTR_SIZE) {
+ if (buffer_get_u64(buf, &attr->size) != 8) {
+ break;
+ }
+ attr->size = ntohll(attr->size);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) {
+ if((owner = buffer_get_ssh_string(buf)) == NULL ||
+ (attr->owner = ssh_string_to_char(owner)) == NULL) {
+ break;
+ }
+ if ((group = buffer_get_ssh_string(buf)) == NULL ||
+ (attr->group = ssh_string_to_char(group)) == NULL) {
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (buffer_get_u32(buf, &attr->permissions) != 4) {
+ break;
+ }
+ attr->permissions = ntohl(attr->permissions);
+
+ /* FIXME on windows! */
+ switch (attr->permissions & S_IFMT) {
+ case S_IFSOCK:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ attr->type = SSH_FILEXFER_TYPE_SPECIAL;
+ break;
+ case S_IFLNK:
+ attr->type = SSH_FILEXFER_TYPE_SYMLINK;
+ break;
+ case S_IFREG:
+ attr->type = SSH_FILEXFER_TYPE_REGULAR;
+ break;
+ case S_IFDIR:
+ attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
+ break;
+ default:
+ attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) {
+ if (buffer_get_u64(buf, &attr->atime64) != 8) {
+ break;
+ }
+ attr->atime64 = ntohll(attr->atime64);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
+ if (buffer_get_u32(buf, &attr->atime_nseconds) != 4) {
+ break;
+ }
+ attr->atime_nseconds = ntohl(attr->atime_nseconds);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_CREATETIME) {
+ if (buffer_get_u64(buf, &attr->createtime) != 8) {
+ break;
+ }
+ attr->createtime = ntohll(attr->createtime);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
+ if (buffer_get_u32(buf, &attr->createtime_nseconds) != 4) {
+ break;
+ }
+ attr->createtime_nseconds = ntohl(attr->createtime_nseconds);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) {
+ if (buffer_get_u64(buf, &attr->mtime64) != 8) {
+ break;
+ }
+ attr->mtime64 = ntohll(attr->mtime64);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
+ if (buffer_get_u32(buf, &attr->mtime_nseconds) != 4) {
+ break;
+ }
+ attr->mtime_nseconds = ntohl(attr->mtime_nseconds);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACL) {
+ if ((attr->acl = buffer_get_ssh_string(buf)) == NULL) {
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
+ if (buffer_get_u32(buf,&attr->extended_count) != 4) {
+ break;
+ }
+ attr->extended_count = ntohl(attr->extended_count);
+
+ while(attr->extended_count &&
+ (attr->extended_type = buffer_get_ssh_string(buf)) &&
+ (attr->extended_data = buffer_get_ssh_string(buf))){
+ attr->extended_count--;
+ }
+
+ if (attr->extended_count) {
+ break;
+ }
+ }
+ ok = 1;
+ } while (0);
+
+ if (ok == 0) {
+ /* break issued somewhere */
+ ssh_string_free(owner);
+ ssh_string_free(group);
+ ssh_string_free(attr->acl);
+ ssh_string_free(attr->extended_type);
+ ssh_string_free(attr->extended_data);
+ SAFE_FREE(attr->owner);
+ SAFE_FREE(attr->group);
+ SAFE_FREE(attr);
+
+ ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure");
+
+ return NULL;
+ }
+
+ return attr;
+}
+
+enum sftp_longname_field_e {
+ SFTP_LONGNAME_PERM = 0,
+ SFTP_LONGNAME_FIXME,
+ SFTP_LONGNAME_OWNER,
+ SFTP_LONGNAME_GROUP,
+ SFTP_LONGNAME_SIZE,
+ SFTP_LONGNAME_DATE,
+ SFTP_LONGNAME_TIME,
+ SFTP_LONGNAME_NAME,
+};
+
+static char *sftp_parse_longname(const char *longname,
+ enum sftp_longname_field_e longname_field) {
+ const char *p, *q;
+ size_t len, field = 0;
+ char *x;
+
+ p = longname;
+ /* Find the beginning of the field which is specified by sftp_longanme_field_e. */
+ while(field != longname_field) {
+ if(isspace(*p)) {
+ field++;
+ p++;
+ while(*p && isspace(*p)) {
+ p++;
+ }
+ } else {
+ p++;
+ }
+ }
+
+ q = p;
+ while (! isspace(*q)) {
+ q++;
+ }
+
+ /* There is no strndup on windows */
+ len = q - p + 1;
+ x = malloc(len);
+ if (x == NULL) {
+ return NULL;
+ }
+
+ snprintf(x, len, "%s", p);
+
+ return x;
+}
+
+/* sftp version 0-3 code. It is different from the v4 */
+/* maybe a paste of the draft is better than the code */
+/*
+ uint32 flags
+ uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
+ uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID
+ uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID
+ uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
+ uint32 atime present only if flag SSH_FILEXFER_ACMODTIME
+ uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME
+ uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
+ string extended_type
+ string extended_data
+ ... more extended data (extended_type - extended_data pairs),
+ so that number of pairs equals extended_count */
+static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf,
+ int expectname) {
+ ssh_string longname = NULL;
+ ssh_string name = NULL;
+ sftp_attributes attr;
+ uint32_t flags = 0;
+ int ok = 0;
+
+ attr = malloc(sizeof(struct sftp_attributes_struct));
+ if (attr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(attr);
+
+ /* This isn't really a loop, but it is like a try..catch.. */
+ do {
+ if (expectname) {
+ if ((name = buffer_get_ssh_string(buf)) == NULL ||
+ (attr->name = ssh_string_to_char(name)) == NULL) {
+ break;
+ }
+ ssh_string_free(name);
+
+ ssh_log(sftp->session, SSH_LOG_RARE, "Name: %s", attr->name);
+
+ if ((longname=buffer_get_ssh_string(buf)) == NULL ||
+ (attr->longname=ssh_string_to_char(longname)) == NULL) {
+ break;
+ }
+ ssh_string_free(longname);
+
+ /* Set owner and group if we talk to openssh and have the longname */
+ if (ssh_get_openssh_version(sftp->session)) {
+ attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER);
+ if (attr->owner == NULL) {
+ break;
+ }
+
+ attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP);
+ if (attr->group == NULL) {
+ break;
+ }
+ }
+ }
+
+ if (buffer_get_u32(buf, &flags) != sizeof(uint32_t)) {
+ break;
+ }
+ flags = ntohl(flags);
+ attr->flags = flags;
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "Flags: %.8lx\n", (long unsigned int) flags);
+
+ if (flags & SSH_FILEXFER_ATTR_SIZE) {
+ if(buffer_get_u64(buf, &attr->size) != sizeof(uint64_t)) {
+ break;
+ }
+ attr->size = ntohll(attr->size);
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "Size: %llu\n",
+ (long long unsigned int) attr->size);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_UIDGID) {
+ if (buffer_get_u32(buf, &attr->uid) != sizeof(uint32_t)) {
+ break;
+ }
+ if (buffer_get_u32(buf, &attr->gid) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->uid = ntohl(attr->uid);
+ attr->gid = ntohl(attr->gid);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (buffer_get_u32(buf, &attr->permissions) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->permissions = ntohl(attr->permissions);
+
+ switch (attr->permissions & S_IFMT) {
+ case S_IFSOCK:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ attr->type = SSH_FILEXFER_TYPE_SPECIAL;
+ break;
+ case S_IFLNK:
+ attr->type = SSH_FILEXFER_TYPE_SYMLINK;
+ break;
+ case S_IFREG:
+ attr->type = SSH_FILEXFER_TYPE_REGULAR;
+ break;
+ case S_IFDIR:
+ attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
+ break;
+ default:
+ attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+ if (buffer_get_u32(buf, &attr->atime) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->atime = ntohl(attr->atime);
+ if (buffer_get_u32(buf, &attr->mtime) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->mtime = ntohl(attr->mtime);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
+ if (buffer_get_u32(buf, &attr->extended_count) != sizeof(uint32_t)) {
+ break;
+ }
+
+ attr->extended_count = ntohl(attr->extended_count);
+ while (attr->extended_count &&
+ (attr->extended_type = buffer_get_ssh_string(buf))
+ && (attr->extended_data = buffer_get_ssh_string(buf))) {
+ attr->extended_count--;
+ }
+
+ if (attr->extended_count) {
+ break;
+ }
+ }
+ ok = 1;
+ } while (0);
+
+ if (!ok) {
+ /* break issued somewhere */
+ ssh_string_free(name);
+ ssh_string_free(longname);
+ ssh_string_free(attr->extended_type);
+ ssh_string_free(attr->extended_data);
+ SAFE_FREE(attr->name);
+ SAFE_FREE(attr->longname);
+ SAFE_FREE(attr->owner);
+ SAFE_FREE(attr->group);
+ SAFE_FREE(attr);
+
+ ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure");
+
+ return NULL;
+ }
+
+ /* everything went smoothly */
+ return attr;
+}
+
+/* FIXME is this really needed as a public function? */
+int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) {
+ uint32_t flags = (attr ? attr->flags : 0);
+
+ flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID |
+ SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
+
+ if (buffer_add_u32(buffer, htonl(flags)) < 0) {
+ return -1;
+ }
+
+ if (attr) {
+ if (flags & SSH_FILEXFER_ATTR_SIZE) {
+ if (buffer_add_u64(buffer, htonll(attr->size)) < 0) {
+ return -1;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_UIDGID) {
+ if (buffer_add_u32(buffer,htonl(attr->uid)) < 0 ||
+ buffer_add_u32(buffer,htonl(attr->gid)) < 0) {
+ return -1;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (buffer_add_u32(buffer, htonl(attr->permissions)) < 0) {
+ return -1;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+ if (buffer_add_u32(buffer, htonl(attr->atime)) < 0 ||
+ buffer_add_u32(buffer, htonl(attr->mtime)) < 0) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf,
+ int expectname) {
+ switch(session->version) {
+ case 4:
+ return sftp_parse_attr_4(session, buf, expectname);
+ case 3:
+ case 2:
+ case 1:
+ case 0:
+ return sftp_parse_attr_3(session, buf, expectname);
+ default:
+ ssh_set_error(session->session, SSH_FATAL,
+ "Version %d unsupported by client", session->server_version);
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/* Get the version of the SFTP protocol supported by the server */
+int sftp_server_version(sftp_session sftp) {
+ return sftp->server_version;
+}
+
+/* Get a single file attributes structure of a directory. */
+sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) {
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ sftp_attributes attr;
+ ssh_buffer payload;
+ uint32_t id;
+
+ if (dir->buffer == NULL) {
+ payload = ssh_buffer_new();
+ if (payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(payload, id) < 0 ||
+ buffer_add_ssh_string(payload, dir->handle) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+
+ if (sftp_packet_write(sftp, SSH_FXP_READDIR, payload) < 0) {
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+ ssh_buffer_free(payload);
+
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Sent a ssh_fxp_readdir with id %d", id);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type){
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_EOF:
+ dir->eof = 1;
+ status_msg_free(status);
+ return NULL;
+ default:
+ break;
+ }
+
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Unknown error status: %d", status->status);
+ status_msg_free(status);
+
+ return NULL;
+ case SSH_FXP_NAME:
+ buffer_get_u32(msg->payload, &dir->count);
+ dir->count = ntohl(dir->count);
+ dir->buffer = msg->payload;
+ msg->payload = NULL;
+ sftp_message_free(msg);
+ break;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Unsupported message back %d", msg->packet_type);
+ sftp_message_free(msg);
+
+ return NULL;
+ }
+ }
+
+ /* now dir->buffer contains a buffer and dir->count != 0 */
+ if (dir->count == 0) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Count of files sent by the server is zero, which is invalid, or "
+ "libsftp bug");
+ return NULL;
+ }
+
+ ssh_log(sftp->session, SSH_LOG_RARE, "Count is %d", dir->count);
+
+ attr = sftp_parse_attr(sftp, dir->buffer, 1);
+ if (attr == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Couldn't parse the SFTP attributes");
+ return NULL;
+ }
+
+ dir->count--;
+ if (dir->count == 0) {
+ ssh_buffer_free(dir->buffer);
+ dir->buffer = NULL;
+ }
+
+ return attr;
+}
+
+/* Tell if the directory has reached EOF (End Of File). */
+int sftp_dir_eof(sftp_dir dir) {
+ return dir->eof;
+}
+
+/* Free a SFTP_ATTRIBUTE handle */
+void sftp_attributes_free(sftp_attributes file){
+ if (file == NULL) {
+ return;
+ }
+
+ ssh_string_free(file->acl);
+ ssh_string_free(file->extended_data);
+ ssh_string_free(file->extended_type);
+
+ SAFE_FREE(file->name);
+ SAFE_FREE(file->longname);
+ SAFE_FREE(file->group);
+ SAFE_FREE(file->owner);
+
+ SAFE_FREE(file);
+}
+
+static int sftp_handle_close(sftp_session sftp, ssh_string handle) {
+ sftp_status_message status;
+ sftp_message msg = NULL;
+ ssh_buffer buffer = NULL;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, handle) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_CLOSE ,buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return -1;
+ }
+ msg = sftp_dequeue(sftp,id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if(status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ break;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during sftp_handle_close!", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Close an open file handle. */
+int sftp_close(sftp_file file){
+ int err = SSH_NO_ERROR;
+
+ SAFE_FREE(file->name);
+ if (file->handle){
+ err = sftp_handle_close(file->sftp,file->handle);
+ ssh_string_free(file->handle);
+ }
+ /* FIXME: check server response and implement errno */
+ SAFE_FREE(file);
+
+ return err;
+}
+
+/* Close an open directory. */
+int sftp_closedir(sftp_dir dir){
+ int err = SSH_NO_ERROR;
+
+ SAFE_FREE(dir->name);
+ if (dir->handle) {
+ err = sftp_handle_close(dir->sftp, dir->handle);
+ ssh_string_free(dir->handle);
+ }
+ /* FIXME: check server response and implement errno */
+ ssh_buffer_free(dir->buffer);
+ SAFE_FREE(dir);
+
+ return err;
+}
+
+/* Open a file on the server. */
+sftp_file sftp_open(sftp_session sftp, const char *file, int flags,
+ mode_t mode) {
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ struct sftp_attributes_struct attr;
+ sftp_file handle;
+ ssh_string filename;
+ ssh_buffer buffer;
+ uint32_t sftp_flags = 0;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ filename = ssh_string_from_char(file);
+ if (filename == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ ZERO_STRUCT(attr);
+ attr.permissions = mode;
+ attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ if (flags == O_RDONLY)
+ sftp_flags |= SSH_FXF_READ; /* if any of the other flag is set,
+ READ should not be set initialy */
+ if (flags & O_WRONLY)
+ sftp_flags |= SSH_FXF_WRITE;
+ if (flags & O_RDWR)
+ sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ);
+ if (flags & O_CREAT)
+ sftp_flags |= SSH_FXF_CREAT;
+ if (flags & O_TRUNC)
+ sftp_flags |= SSH_FXF_TRUNC;
+ if (flags & O_EXCL)
+ sftp_flags |= SSH_FXF_EXCL;
+ ssh_log(sftp->session,SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags);
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, filename) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ return NULL;
+ }
+ ssh_string_free(filename);
+
+ if (buffer_add_u32(buffer, htonl(sftp_flags)) < 0 ||
+ buffer_add_attributes(buffer, &attr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_OPEN, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+
+ return NULL;
+ case SSH_FXP_HANDLE:
+ handle = parse_handle_msg(msg);
+ sftp_message_free(msg);
+ return handle;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during open!", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+void sftp_file_set_nonblocking(sftp_file handle){
+ handle->nonblocking=1;
+}
+void sftp_file_set_blocking(sftp_file handle){
+ handle->nonblocking=0;
+}
+
+/* Read from a file using an opened sftp file handle. */
+ssize_t sftp_read(sftp_file handle, void *buf, size_t count) {
+ sftp_session sftp = handle->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ ssh_string datastring;
+ ssh_buffer buffer;
+ int id;
+
+ if (handle->eof) {
+ return 0;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+ id = sftp_get_new_id(handle->sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, handle->handle) < 0 ||
+ buffer_add_u64(buffer, htonll(handle->offset)) < 0 ||
+ buffer_add_u32(buffer,htonl(count)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (handle->nonblocking) {
+ if (ssh_channel_poll(handle->sftp->channel, 0) == 0) {
+ /* we cannot block */
+ return 0;
+ }
+ }
+ if (sftp_read_and_dispatch(handle->sftp) < 0) {
+ /* something nasty has happened */
+ return -1;
+ }
+ msg = sftp_dequeue(handle->sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_EOF:
+ handle->eof = 1;
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session,SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ case SSH_FXP_DATA:
+ datastring = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (datastring == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received invalid DATA packet from sftp server");
+ return -1;
+ }
+
+ if (ssh_string_len(datastring) > count) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received a too big DATA packet from sftp server: "
+ "%zu and asked for %zu",
+ ssh_string_len(datastring), count);
+ ssh_string_free(datastring);
+ return -1;
+ }
+ count = ssh_string_len(datastring);
+ handle->offset += count;
+ memcpy(buf, ssh_string_data(datastring), count);
+ ssh_string_free(datastring);
+ return count;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during read!", msg->packet_type);
+ sftp_message_free(msg);
+ return -1;
+ }
+
+ return -1; /* not reached */
+}
+
+/* Start an asynchronous read from a file using an opened sftp file handle. */
+int sftp_async_read_begin(sftp_file file, uint32_t len){
+ sftp_session sftp = file->sftp;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ sftp_enter_function();
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0 ||
+ buffer_add_u64(buffer, htonll(file->offset)) < 0 ||
+ buffer_add_u32(buffer, htonl(len)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ file->offset += len; /* assume we'll read len bytes */
+
+ sftp_leave_function();
+ return id;
+}
+
+/* Wait for an asynchronous read to complete and save the data. */
+int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){
+ sftp_session sftp = file->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ ssh_string datastring;
+ int err = SSH_OK;
+ uint32_t len;
+
+ sftp_enter_function();
+
+ if (file->eof) {
+ sftp_leave_function();
+ return 0;
+ }
+
+ /* handle an existing request */
+ while (msg == NULL) {
+ if (file->nonblocking){
+ if (ssh_channel_poll(sftp->channel, 0) == 0) {
+ /* we cannot block */
+ return SSH_AGAIN;
+ }
+ }
+
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+
+ msg = sftp_dequeue(sftp,id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ if (status->status != SSH_FX_EOF) {
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server : %s", status->errormsg);
+ sftp_leave_function();
+ err = SSH_ERROR;
+ } else {
+ file->eof = 1;
+ }
+ status_msg_free(status);
+ sftp_leave_function();
+ return err;
+ case SSH_FXP_DATA:
+ datastring = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (datastring == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received invalid DATA packet from sftp server");
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+ if (ssh_string_len(datastring) > size) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received a too big DATA packet from sftp server: "
+ "%zu and asked for %u",
+ ssh_string_len(datastring), size);
+ ssh_string_free(datastring);
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+ len = ssh_string_len(datastring);
+ //handle->offset+=len;
+ /* We already have set the offset previously. All we can do is warn that the expected len
+ * and effective lengths are different */
+ memcpy(data, ssh_string_data(datastring), len);
+ ssh_string_free(datastring);
+ sftp_leave_function();
+ return len;
+ default:
+ ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+
+ sftp_leave_function();
+ return SSH_ERROR;
+}
+
+ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
+ sftp_session sftp = file->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ ssh_string datastring;
+ ssh_buffer buffer;
+ uint32_t id;
+ int len;
+ int packetlen;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ datastring = ssh_string_new(count);
+ if (datastring == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_string_fill(datastring, buf, count);
+
+ id = sftp_get_new_id(file->sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0 ||
+ buffer_add_u64(buffer, htonll(file->offset)) < 0 ||
+ buffer_add_ssh_string(buffer, datastring) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(datastring);
+ return -1;
+ }
+ ssh_string_free(datastring);
+ len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer);
+ packetlen=ssh_buffer_get_len(buffer);
+ ssh_buffer_free(buffer);
+ if (len < 0) {
+ return -1;
+ } else if (len != packetlen) {
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Could not write as much data as expected");
+ }
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(file->sftp) < 0) {
+ /* something nasty has happened */
+ return -1;
+ }
+ msg = sftp_dequeue(file->sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ file->offset += count;
+ status_msg_free(status);
+ return count;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ file->offset += count;
+ status_msg_free(status);
+ return -1;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during write!", msg->packet_type);
+ sftp_message_free(msg);
+ return -1;
+ }
+
+ return -1; /* not reached */
+}
+
+/* Seek to a specific location in a file. */
+int sftp_seek(sftp_file file, uint32_t new_offset) {
+ if (file == NULL) {
+ return -1;
+ }
+
+ file->offset = new_offset;
+
+ return 0;
+}
+
+int sftp_seek64(sftp_file file, uint64_t new_offset) {
+ if (file == NULL) {
+ return -1;
+ }
+
+ file->offset = new_offset;
+
+ return 0;
+}
+
+/* Report current byte position in file. */
+unsigned long sftp_tell(sftp_file file) {
+ return (unsigned long)file->offset;
+}
+/* Report current byte position in file. */
+uint64_t sftp_tell64(sftp_file file) {
+ return (uint64_t) file->offset;
+}
+
+/* Rewinds the position of the file pointer to the beginning of the file.*/
+void sftp_rewind(sftp_file file) {
+ file->offset = 0;
+}
+
+/* code written by Nick */
+int sftp_unlink(sftp_session sftp, const char *file) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string filename;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ filename = ssh_string_from_char(file);
+ if (filename == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, filename) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ }
+ ssh_string_free(filename);
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp)) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ /* by specification, this command's only supposed to return SSH_FXP_STATUS */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session,SSH_FATAL,
+ "Received message %d when attempting to remove file", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* code written by Nick */
+int sftp_rmdir(sftp_session sftp, const char *directory) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string filename;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ filename = ssh_string_from_char(directory);
+ if (filename == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, filename) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ break;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to remove directory",
+ msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Code written by Nick */
+int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ sftp_attributes errno_attr = NULL;
+ struct sftp_attributes_struct attr;
+ ssh_buffer buffer;
+ ssh_string path;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ path = ssh_string_from_char(directory);
+ if (path == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ ZERO_STRUCT(attr);
+ attr.permissions = mode;
+ attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, path) < 0 ||
+ buffer_add_attributes(buffer, &attr) < 0 ||
+ sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_FAILURE:
+ /*
+ * mkdir always returns a failure, even if the path already exists.
+ * To be POSIX conform and to be able to map it to EEXIST a stat
+ * call is needed here.
+ */
+ errno_attr = sftp_lstat(sftp, directory);
+ if (errno_attr != NULL) {
+ SAFE_FREE(errno_attr);
+ sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS);
+ }
+ break;
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ break;
+ default:
+ break;
+ }
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to make directory",
+ msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* code written by nick */
+int sftp_rename(sftp_session sftp, const char *original, const char *newname) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_buffer buffer;
+ ssh_string oldpath;
+ ssh_string newpath;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ oldpath = ssh_string_from_char(original);
+ if (oldpath == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ newpath = ssh_string_from_char(newname);
+ if (newpath == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, oldpath) < 0 ||
+ buffer_add_ssh_string(buffer, newpath) < 0 ||
+ /* POSIX rename atomically replaces newpath, we should do the same
+ * only available on >=v4 */
+ sftp->version>=4 ? (buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE) < 0):0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ ssh_string_free(newpath);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ ssh_string_free(newpath);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ ssh_string_free(newpath);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ /*
+ * Status should be SSH_FX_OK if the command was successful, if it didn't,
+ * then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to rename",
+ msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Code written by Nick */
+/* Set file attributes on a file, directory or symbolic link. */
+int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) {
+ uint32_t id;
+ ssh_buffer buffer;
+ ssh_string path;
+ sftp_message msg = NULL;
+ sftp_status_message status = NULL;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ path = ssh_string_from_char(file);
+ if (path == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, path) < 0 ||
+ buffer_add_attributes(buffer, attr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Change the file owner and group */
+int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) {
+ struct sftp_attributes_struct attr;
+ ZERO_STRUCT(attr);
+
+ attr.uid = owner;
+ attr.gid = group;
+
+ attr.flags = SSH_FILEXFER_ATTR_UIDGID;
+
+ return sftp_setstat(sftp, file, &attr);
+}
+
+/* Change permissions of a file */
+int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) {
+ struct sftp_attributes_struct attr;
+ ZERO_STRUCT(attr);
+ attr.permissions = mode;
+ attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ return sftp_setstat(sftp, file, &attr);
+}
+
+/* Change the last modification and access time of a file. */
+int sftp_utimes(sftp_session sftp, const char *file,
+ const struct timeval *times) {
+ struct sftp_attributes_struct attr;
+ ZERO_STRUCT(attr);
+
+ attr.atime = times[0].tv_sec;
+ attr.atime_nseconds = times[0].tv_usec;
+
+ attr.mtime = times[1].tv_sec;
+ attr.mtime_nseconds = times[1].tv_usec;
+
+ attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME |
+ SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
+
+ return sftp_setstat(sftp, file, &attr);
+}
+
+int sftp_symlink(sftp_session sftp, const char *target, const char *dest) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string target_s;
+ ssh_string dest_s;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return -1;
+ if (target == NULL || dest == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return -1;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ target_s = ssh_string_from_char(target);
+ if (target_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ dest_s = ssh_string_from_char(dest);
+ if (dest_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_string_free(target_s);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ if (ssh_get_openssh_version(sftp->session)) {
+ /* TODO check for version number if they ever fix it. */
+ if (buffer_add_ssh_string(buffer, target_s) < 0 ||
+ buffer_add_ssh_string(buffer, dest_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ } else {
+ if (buffer_add_ssh_string(buffer, dest_s) < 0 ||
+ buffer_add_ssh_string(buffer, target_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ }
+
+ if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+char *sftp_readlink(sftp_session sftp, const char *path) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string path_s = NULL;
+ ssh_string link_s = NULL;
+ ssh_buffer buffer;
+ char *lnk;
+ uint32_t ignored;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return NULL;
+ if (path == NULL) {
+ ssh_set_error_invalid(sftp, __FUNCTION__);
+ return NULL;
+ }
+ if (sftp->version < 3){
+ ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version);
+ return NULL;
+ }
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ path_s = ssh_string_from_char(path);
+ if (path_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, path_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(path_s);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_READLINK, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(path_s);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(path_s);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_NAME) {
+ /* we don't care about "count" */
+ buffer_get_u32(msg->payload, &ignored);
+ /* we only care about the file name string */
+ link_s = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (link_s == NULL) {
+ /* TODO: what error to set here? */
+ return NULL;
+ }
+ lnk = ssh_string_to_char(link_s);
+ ssh_string_free(link_s);
+
+ return lnk;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) {
+ sftp_statvfs_t statvfs;
+ uint64_t tmp;
+ int ok = 0;
+
+ statvfs = malloc(sizeof(struct sftp_statvfs_struct));
+ if (statvfs == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(statvfs);
+
+ /* try .. catch */
+ do {
+ /* file system block size */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_bsize = ntohll(tmp);
+
+ /* fundamental fs block size */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_frsize = ntohll(tmp);
+
+ /* number of blocks (unit f_frsize) */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_blocks = ntohll(tmp);
+
+ /* free blocks in file system */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_bfree = ntohll(tmp);
+
+ /* free blocks for non-root */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_bavail = ntohll(tmp);
+
+ /* total file inodes */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_files = ntohll(tmp);
+
+ /* free file inodes */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_ffree = ntohll(tmp);
+
+ /* free file inodes for to non-root */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_favail = ntohll(tmp);
+
+ /* file system id */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_fsid = ntohll(tmp);
+
+ /* bit mask of f_flag values */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_flag = ntohll(tmp);
+
+ /* maximum filename length */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_namemax = ntohll(tmp);
+
+ ok = 1;
+ } while(0);
+
+ if (!ok) {
+ SAFE_FREE(statvfs);
+ ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure");
+ return NULL;
+ }
+
+ return statvfs;
+}
+
+sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string pathstr;
+ ssh_string ext;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return NULL;
+ if (path == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+ if (sftp->version < 3){
+ ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version);
+ return NULL;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ ext = ssh_string_from_char("statvfs@openssh.com");
+ if (ext == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ pathstr = ssh_string_from_char(path);
+ if (pathstr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, ext) < 0 ||
+ buffer_add_ssh_string(buffer, pathstr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ ssh_string_free(pathstr);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) {
+ sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload);
+ sftp_message_free(msg);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ return buf;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to get statvfs", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+sftp_statvfs_t sftp_fstatvfs(sftp_file file) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ sftp_session sftp;
+ ssh_string ext;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ if (file == NULL) {
+ return NULL;
+ }
+ sftp = file->sftp;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ ext = ssh_string_from_char("fstatvfs@openssh.com");
+ if (ext == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, ext) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) {
+ sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload);
+ sftp_message_free(msg);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ return buf;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+void sftp_statvfs_free(sftp_statvfs_t statvfs) {
+ if (statvfs == NULL) {
+ return;
+ }
+
+ SAFE_FREE(statvfs);
+}
+
+/* another code written by Nick */
+char *sftp_canonicalize_path(sftp_session sftp, const char *path) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string name = NULL;
+ ssh_string pathstr;
+ ssh_buffer buffer;
+ char *cname;
+ uint32_t ignored;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return NULL;
+ if (path == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ pathstr = ssh_string_from_char(path);
+ if (pathstr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, pathstr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_NAME) {
+ /* we don't care about "count" */
+ buffer_get_u32(msg->payload, &ignored);
+ /* we only care about the file name string */
+ name = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (name == NULL) {
+ /* TODO: error message? */
+ return NULL;
+ }
+ cname = ssh_string_to_char(name);
+ ssh_string_free(name);
+ if (cname == NULL) {
+ ssh_set_error_oom(sftp->session);
+ }
+ return cname;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+static sftp_attributes sftp_xstat(sftp_session sftp, const char *path,
+ int param) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string pathstr;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ pathstr = ssh_string_from_char(path);
+ if (pathstr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, pathstr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, param, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_ATTRS) {
+ return sftp_parse_attr(sftp, msg->payload, 0);
+ } else if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received mesg %d during stat()", msg->packet_type);
+ sftp_message_free(msg);
+
+ return NULL;
+}
+
+sftp_attributes sftp_stat(sftp_session session, const char *path) {
+ return sftp_xstat(session, path, SSH_FXP_STAT);
+}
+
+sftp_attributes sftp_lstat(sftp_session session, const char *path) {
+ return sftp_xstat(session, path, SSH_FXP_LSTAT);
+}
+
+sftp_attributes sftp_fstat(sftp_file file) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(file->sftp->session);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(file->sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0) {
+ ssh_set_error_oom(file->sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ if (sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(file->sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(file->sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_ATTRS){
+ return sftp_parse_attr(file->sftp, msg->payload, 0);
+ } else if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+
+ return NULL;
+ }
+ ssh_set_error(file->sftp->session, SSH_FATAL,
+ "Received msg %d during fstat()", msg->packet_type);
+ sftp_message_free(msg);
+
+ return NULL;
+}
+
+#endif /* WITH_SFTP */
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/sftpserver.c b/src/sftpserver.c
new file mode 100644
index 00000000..77f64198
--- /dev/null
+++ b/src/sftpserver.c
@@ -0,0 +1,490 @@
+/*
+ * sftpserver.c - server based function for the sftp protocol
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/libssh.h"
+#include "libssh/sftp.h"
+#include "libssh/ssh2.h"
+#include "libssh/priv.h"
+#include "libssh/buffer.h"
+#include "libssh/misc.h"
+
+sftp_client_message sftp_get_client_message(sftp_session sftp) {
+ sftp_packet packet;
+ sftp_client_message msg;
+ ssh_buffer payload;
+ ssh_string tmp;
+
+ msg = malloc(sizeof (struct sftp_client_message_struct));
+ if (msg == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(msg);
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+
+ payload = packet->payload;
+ msg->type = packet->type;
+ msg->sftp = sftp;
+
+ buffer_get_u32(payload, &msg->id);
+
+ switch(msg->type) {
+ case SSH_FXP_CLOSE:
+ case SSH_FXP_READDIR:
+ msg->handle = buffer_get_ssh_string(payload);
+ if (msg->handle == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ break;
+ case SSH_FXP_READ:
+ msg->handle = buffer_get_ssh_string(payload);
+ if (msg->handle == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ buffer_get_u64(payload, &msg->offset);
+ buffer_get_u32(payload, &msg->len);
+ break;
+ case SSH_FXP_WRITE:
+ msg->handle = buffer_get_ssh_string(payload);
+ if (msg->handle == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ buffer_get_u64(payload, &msg->offset);
+ msg->data = buffer_get_ssh_string(payload);
+ if (msg->data == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ break;
+ case SSH_FXP_REMOVE:
+ case SSH_FXP_RMDIR:
+ case SSH_FXP_OPENDIR:
+ case SSH_FXP_READLINK:
+ case SSH_FXP_REALPATH:
+ tmp = buffer_get_ssh_string(payload);
+ if (tmp == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->filename = ssh_string_to_char(tmp);
+ ssh_string_free(tmp);
+ if (msg->filename == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ break;
+ case SSH_FXP_RENAME:
+ case SSH_FXP_SYMLINK:
+ tmp = buffer_get_ssh_string(payload);
+ if (tmp == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->filename = ssh_string_to_char(tmp);
+ ssh_string_free(tmp);
+ if (msg->filename == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->data = buffer_get_ssh_string(payload);
+ if (msg->data == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ break;
+ case SSH_FXP_MKDIR:
+ case SSH_FXP_SETSTAT:
+ tmp = buffer_get_ssh_string(payload);
+ if (tmp == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->filename=ssh_string_to_char(tmp);
+ ssh_string_free(tmp);
+ if (msg->filename == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->attr = sftp_parse_attr(sftp, payload, 0);
+ if (msg->attr == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ break;
+ case SSH_FXP_FSETSTAT:
+ msg->handle = buffer_get_ssh_string(payload);
+ if (msg->handle == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->attr = sftp_parse_attr(sftp, payload, 0);
+ if (msg->attr == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ break;
+ case SSH_FXP_LSTAT:
+ case SSH_FXP_STAT:
+ tmp = buffer_get_ssh_string(payload);
+ if (tmp == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->filename = ssh_string_to_char(tmp);
+ ssh_string_free(tmp);
+ if (msg->filename == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ if(sftp->version > 3) {
+ buffer_get_u32(payload,&msg->flags);
+ }
+ break;
+ case SSH_FXP_OPEN:
+ tmp=buffer_get_ssh_string(payload);
+ if (tmp == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ msg->filename = ssh_string_to_char(tmp);
+ ssh_string_free(tmp);
+ if (msg->filename == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ buffer_get_u32(payload,&msg->flags);
+ msg->attr = sftp_parse_attr(sftp, payload, 0);
+ if (msg->attr == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ case SSH_FXP_FSTAT:
+ msg->handle = buffer_get_ssh_string(payload);
+ if (msg->handle == NULL) {
+ sftp_client_message_free(msg);
+ return NULL;
+ }
+ buffer_get_u32(payload, &msg->flags);
+ break;
+ default:
+ fprintf(stderr, "Received unhandled sftp message %d\n", msg->type);
+ }
+
+ msg->flags = ntohl(msg->flags);
+ msg->offset = ntohll(msg->offset);
+ msg->len = ntohl(msg->len);
+ sftp_packet_free(packet);
+
+ return msg;
+}
+
+void sftp_client_message_free(sftp_client_message msg) {
+ if (msg == NULL) {
+ return;
+ }
+
+ SAFE_FREE(msg->filename);
+ ssh_string_free(msg->data);
+ ssh_string_free(msg->handle);
+ sftp_attributes_free(msg->attr);
+
+ ZERO_STRUCTP(msg);
+ SAFE_FREE(msg);
+}
+
+int sftp_reply_name(sftp_client_message msg, const char *name,
+ sftp_attributes attr) {
+ ssh_buffer out;
+ ssh_string file;
+
+ out = ssh_buffer_new();
+ if (out == NULL) {
+ return -1;
+ }
+
+ file = ssh_string_from_char(name);
+ if (file == NULL) {
+ ssh_buffer_free(out);
+ return -1;
+ }
+
+ if (buffer_add_u32(out, msg->id) < 0 ||
+ buffer_add_u32(out, htonl(1)) < 0 ||
+ buffer_add_ssh_string(out, file) < 0 ||
+ buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */
+ buffer_add_attributes(out, attr) < 0 ||
+ sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) {
+ ssh_buffer_free(out);
+ ssh_string_free(file);
+ return -1;
+ }
+ ssh_buffer_free(out);
+ ssh_string_free(file);
+
+ return 0;
+}
+
+int sftp_reply_handle(sftp_client_message msg, ssh_string handle){
+ ssh_buffer out;
+
+ out = ssh_buffer_new();
+ if (out == NULL) {
+ return -1;
+ }
+
+ if (buffer_add_u32(out, msg->id) < 0 ||
+ buffer_add_ssh_string(out, handle) < 0 ||
+ sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) {
+ ssh_buffer_free(out);
+ return -1;
+ }
+ ssh_buffer_free(out);
+
+ return 0;
+}
+
+int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) {
+ ssh_buffer out;
+
+ out = ssh_buffer_new();
+ if (out == NULL) {
+ return -1;
+ }
+
+ if (buffer_add_u32(out, msg->id) < 0 ||
+ buffer_add_attributes(out, attr) < 0 ||
+ sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) {
+ ssh_buffer_free(out);
+ return -1;
+ }
+ ssh_buffer_free(out);
+
+ return 0;
+}
+
+int sftp_reply_names_add(sftp_client_message msg, const char *file,
+ const char *longname, sftp_attributes attr) {
+ ssh_string name;
+
+ name = ssh_string_from_char(file);
+ if (name == NULL) {
+ return -1;
+ }
+
+ if (msg->attrbuf == NULL) {
+ msg->attrbuf = ssh_buffer_new();
+ if (msg->attrbuf == NULL) {
+ ssh_string_free(name);
+ return -1;
+ }
+ }
+
+ if (buffer_add_ssh_string(msg->attrbuf, name) < 0) {
+ ssh_string_free(name);
+ return -1;
+ }
+
+ ssh_string_free(name);
+ name = ssh_string_from_char(longname);
+ if (name == NULL) {
+ return -1;
+ }
+ if (buffer_add_ssh_string(msg->attrbuf,name) < 0 ||
+ buffer_add_attributes(msg->attrbuf,attr) < 0) {
+ ssh_string_free(name);
+ return -1;
+ }
+ ssh_string_free(name);
+ msg->attr_num++;
+
+ return 0;
+}
+
+int sftp_reply_names(sftp_client_message msg) {
+ ssh_buffer out;
+
+ out = ssh_buffer_new();
+ if (out == NULL) {
+ ssh_buffer_free(msg->attrbuf);
+ return -1;
+ }
+
+ if (buffer_add_u32(out, msg->id) < 0 ||
+ buffer_add_u32(out, htonl(msg->attr_num)) < 0 ||
+ buffer_add_data(out, ssh_buffer_get_begin(msg->attrbuf),
+ ssh_buffer_get_len(msg->attrbuf)) < 0 ||
+ sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) {
+ ssh_buffer_free(out);
+ ssh_buffer_free(msg->attrbuf);
+ return -1;
+ }
+
+ ssh_buffer_free(out);
+ ssh_buffer_free(msg->attrbuf);
+
+ msg->attr_num = 0;
+ msg->attrbuf = NULL;
+
+ return 0;
+}
+
+int sftp_reply_status(sftp_client_message msg, uint32_t status,
+ const char *message) {
+ ssh_buffer out;
+ ssh_string s;
+
+ out = ssh_buffer_new();
+ if (out == NULL) {
+ return -1;
+ }
+
+ s = ssh_string_from_char(message ? message : "");
+ if (s == NULL) {
+ ssh_buffer_free(out);
+ return -1;
+ }
+
+ if (buffer_add_u32(out, msg->id) < 0 ||
+ buffer_add_u32(out, htonl(status)) < 0 ||
+ buffer_add_ssh_string(out, s) < 0 ||
+ buffer_add_u32(out, 0) < 0 || /* language string */
+ sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) {
+ ssh_buffer_free(out);
+ ssh_string_free(s);
+ return -1;
+ }
+
+ ssh_buffer_free(out);
+ ssh_string_free(s);
+
+ return 0;
+}
+
+int sftp_reply_data(sftp_client_message msg, const void *data, int len) {
+ ssh_buffer out;
+
+ out = ssh_buffer_new();
+ if (out == NULL) {
+ return -1;
+ }
+
+ if (buffer_add_u32(out, msg->id) < 0 ||
+ buffer_add_u32(out, ntohl(len)) < 0 ||
+ buffer_add_data(out, data, len) < 0 ||
+ sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) {
+ ssh_buffer_free(out);
+ return -1;
+ }
+ ssh_buffer_free(out);
+
+ return 0;
+}
+
+/*
+ * This function will return you a new handle to give the client.
+ * the function accepts an info that can be retrieved later with
+ * the handle. Care is given that a corrupted handle won't give a
+ * valid info (or worse).
+ */
+ssh_string sftp_handle_alloc(sftp_session sftp, void *info) {
+ ssh_string ret;
+ uint32_t val;
+ int i;
+
+ if (sftp->handles == NULL) {
+ sftp->handles = malloc(sizeof(void *) * SFTP_HANDLES);
+ if (sftp->handles == NULL) {
+ return NULL;
+ }
+ memset(sftp->handles, 0, sizeof(void *) * SFTP_HANDLES);
+ }
+
+ for (i = 0; i < SFTP_HANDLES; i++) {
+ if (sftp->handles[i] == NULL) {
+ break;
+ }
+ }
+
+ if (i == SFTP_HANDLES) {
+ return NULL; /* no handle available */
+ }
+
+ val = i;
+ ret = ssh_string_new(4);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ memcpy(ssh_string_data(ret), &val, sizeof(uint32_t));
+ sftp->handles[i] = info;
+
+ return ret;
+}
+
+void *sftp_handle(sftp_session sftp, ssh_string handle){
+ uint32_t val;
+
+ if (sftp->handles == NULL) {
+ return NULL;
+ }
+
+ if (ssh_string_len(handle) != sizeof(uint32_t)) {
+ return NULL;
+ }
+
+ memcpy(&val, ssh_string_data(handle), sizeof(uint32_t));
+
+ if (val > SFTP_HANDLES) {
+ return NULL;
+ }
+
+ return sftp->handles[val];
+}
+
+void sftp_handle_remove(sftp_session sftp, void *handle) {
+ int i;
+
+ for (i = 0; i < SFTP_HANDLES; i++) {
+ if (sftp->handles[i] == handle) {
+ sftp->handles[i] = NULL;
+ break;
+ }
+ }
+}
+
+/* vim: set ts=2 sw=2 et cindent: */
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 00000000..2f1f5534
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,719 @@
+/*
+ * socket.c - socket functions for the library
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2008-2010 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 <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#if _MSC_VER >= 1400
+#include <io.h>
+#undef open
+#define open _open
+#undef close
+#define close _close
+#undef read
+#define read _read
+#undef write
+#define write _write
+#endif /* _MSC_VER */
+#else /* _WIN32 */
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif /* _WIN32 */
+
+#include "libssh/priv.h"
+#include "libssh/callbacks.h"
+#include "libssh/socket.h"
+#include "libssh/buffer.h"
+#include "libssh/poll.h"
+#include "libssh/session.h"
+
+#ifndef _WIN32
+extern char **environ;
+#endif
+/**
+ * @internal
+ *
+ * @defgroup libssh_socket The SSH socket functions.
+ * @ingroup libssh
+ *
+ * Functions for handling sockets.
+ *
+ * @{
+ */
+
+enum ssh_socket_states_e {
+ SSH_SOCKET_NONE,
+ SSH_SOCKET_CONNECTING,
+ SSH_SOCKET_CONNECTED,
+ SSH_SOCKET_EOF,
+ SSH_SOCKET_ERROR,
+ SSH_SOCKET_CLOSED
+};
+
+struct ssh_socket_struct {
+ socket_t fd_in;
+ socket_t fd_out;
+ int fd_is_socket;
+ int last_errno;
+ int data_to_read; /* reading now on socket will
+ not block */
+ int data_to_write;
+ int data_except;
+ enum ssh_socket_states_e state;
+ ssh_buffer out_buffer;
+ ssh_buffer in_buffer;
+ ssh_session session;
+ ssh_socket_callbacks callbacks;
+ ssh_poll_handle poll_in;
+ ssh_poll_handle poll_out;
+};
+
+static int ssh_socket_unbuffered_read(ssh_socket s, void *buffer, uint32_t len);
+static int ssh_socket_unbuffered_write(ssh_socket s, const void *buffer,
+ uint32_t len);
+
+/**
+ * \internal
+ * \brief inits the socket system (windows specific)
+ */
+int ssh_socket_init(void) {
+#ifdef _WIN32
+ struct WSAData wsaData;
+
+ /* Initiates use of the Winsock DLL by a process. */
+ if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) {
+ return -1;
+ }
+
+#endif
+ ssh_poll_init();
+
+ return 0;
+}
+
+/**
+ * @brief Cleanup the socket system.
+ */
+void ssh_socket_cleanup(void) {
+ ssh_poll_cleanup();
+}
+
+
+/**
+ * \internal
+ * \brief creates a new Socket object
+ */
+ssh_socket ssh_socket_new(ssh_session session) {
+ ssh_socket s;
+
+ s = malloc(sizeof(struct ssh_socket_struct));
+ if (s == NULL) {
+ return NULL;
+ }
+ s->fd_in = SSH_INVALID_SOCKET;
+ s->fd_out= SSH_INVALID_SOCKET;
+ s->last_errno = -1;
+ s->fd_is_socket = 1;
+ s->session = session;
+ s->in_buffer = ssh_buffer_new();
+ if (s->in_buffer == NULL) {
+ SAFE_FREE(s);
+ return NULL;
+ }
+ s->out_buffer=ssh_buffer_new();
+ if (s->out_buffer == NULL) {
+ ssh_buffer_free(s->in_buffer);
+ SAFE_FREE(s);
+ return NULL;
+ }
+ s->data_to_read = 0;
+ s->data_to_write = 0;
+ s->data_except = 0;
+ s->poll_in=s->poll_out=NULL;
+ s->state=SSH_SOCKET_NONE;
+ return s;
+}
+
+/**
+ * @internal
+ * @brief the socket callbacks, i.e. callbacks to be called
+ * upon a socket event.
+ * @param s socket to set callbacks on.
+ * @param callbacks a ssh_socket_callback object reference.
+ */
+
+void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks){
+ s->callbacks=callbacks;
+}
+
+int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s){
+ ssh_socket s=(ssh_socket )v_s;
+ char buffer[4096];
+ int r,w;
+ int err=0;
+ socklen_t errlen=sizeof(err);
+ /* Do not do anything if this socket was already closed */
+ if(!ssh_socket_is_open(s)){
+ return -1;
+ }
+ if(revents & POLLERR){
+ /* Check if we are in a connecting state */
+ if(s->state==SSH_SOCKET_CONNECTING){
+ s->state=SSH_SOCKET_ERROR;
+ getsockopt(fd,SOL_SOCKET,SO_ERROR,(void *)&err,&errlen);
+ s->last_errno=err;
+ ssh_socket_close(s);
+ if(s->callbacks && s->callbacks->connected)
+ s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR,err,
+ s->callbacks->userdata);
+ return -1;
+ }
+ /* Then we are in a more standard kind of error */
+ /* force a read to get an explanation */
+ revents |= POLLIN;
+ }
+ if(revents & POLLIN){
+ s->data_to_read=1;
+ r=ssh_socket_unbuffered_read(s,buffer,sizeof(buffer));
+ if(r<0){
+ err=-1;
+ if(p != NULL)
+ ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLIN);
+ if(s->callbacks && s->callbacks->exception){
+ s->callbacks->exception(
+ SSH_SOCKET_EXCEPTION_ERROR,
+ s->last_errno,s->callbacks->userdata);
+ }
+ }
+ if(r==0){
+ ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLIN);
+ if(s->callbacks && s->callbacks->exception){
+ s->callbacks->exception(
+ SSH_SOCKET_EXCEPTION_EOF,
+ 0,s->callbacks->userdata);
+ }
+ }
+ if(r>0){
+ /* Bufferize the data and then call the callback */
+ buffer_add_data(s->in_buffer,buffer,r);
+ if(s->callbacks && s->callbacks->data){
+ r= s->callbacks->data(buffer_get_rest(s->in_buffer),
+ buffer_get_rest_len(s->in_buffer),
+ s->callbacks->userdata);
+ buffer_pass_bytes(s->in_buffer,r);
+ }
+ }
+ }
+#ifdef _WIN32
+ if(revents & POLLOUT || revents & POLLWRNORM){
+#else
+ if(revents & POLLOUT){
+#endif
+ /* First, POLLOUT is a sign we may be connected */
+ if(s->state == SSH_SOCKET_CONNECTING){
+ ssh_log(s->session,SSH_LOG_PACKET,"Received POLLOUT in connecting state");
+ s->state = SSH_SOCKET_CONNECTED;
+ ssh_poll_set_events(p,POLLOUT | POLLIN | POLLERR);
+ ssh_sock_set_blocking(ssh_socket_get_fd_in(s));
+ if(s->callbacks && s->callbacks->connected)
+ s->callbacks->connected(SSH_SOCKET_CONNECTED_OK,0,s->callbacks->userdata);
+ return 0;
+ }
+ /* So, we can write data */
+ s->data_to_write=1;
+ /* If buffered data is pending, write it */
+ if(buffer_get_rest_len(s->out_buffer) > 0){
+ w=ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer),
+ buffer_get_rest_len(s->out_buffer));
+ if(w>0)
+ buffer_pass_bytes(s->out_buffer,w);
+ } else if(s->callbacks && s->callbacks->controlflow){
+ /* Otherwise advertise the upper level that write can be done */
+ s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK,s->callbacks->userdata);
+ }
+ ssh_poll_remove_events(p,POLLOUT);
+ /* TODO: Find a way to put back POLLOUT when buffering occurs */
+ }
+ return err;
+}
+
+/** @internal
+ * @brief returns the input poll handle corresponding to the socket,
+ * creates it if it does not exist.
+ * @returns allocated and initialized ssh_poll_handle object
+ */
+ssh_poll_handle ssh_socket_get_poll_handle_in(ssh_socket s){
+ if(s->poll_in)
+ return s->poll_in;
+ s->poll_in=ssh_poll_new(s->fd_in,0,ssh_socket_pollcallback,s);
+ if(s->fd_in == s->fd_out && s->poll_out == NULL)
+ s->poll_out=s->poll_in;
+ return s->poll_in;
+}
+
+/** @internal
+ * @brief returns the output poll handle corresponding to the socket,
+ * creates it if it does not exist.
+ * @returns allocated and initialized ssh_poll_handle object
+ */
+ssh_poll_handle ssh_socket_get_poll_handle_out(ssh_socket s){
+ if(s->poll_out)
+ return s->poll_out;
+ s->poll_out=ssh_poll_new(s->fd_out,0,ssh_socket_pollcallback,s);
+ if(s->fd_in == s->fd_out && s->poll_in == NULL)
+ s->poll_in=s->poll_out;
+ return s->poll_out;
+}
+
+/** \internal
+ * \brief Deletes a socket object
+ */
+void ssh_socket_free(ssh_socket s){
+ if (s == NULL) {
+ return;
+ }
+ ssh_socket_close(s);
+ ssh_buffer_free(s->in_buffer);
+ ssh_buffer_free(s->out_buffer);
+ SAFE_FREE(s);
+}
+
+#ifndef _WIN32
+int ssh_socket_unix(ssh_socket s, const char *path) {
+ struct sockaddr_un sunaddr;
+ socket_t fd;
+ sunaddr.sun_family = AF_UNIX;
+ snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == SSH_INVALID_SOCKET) {
+ return -1;
+ }
+
+ if (fcntl(fd, F_SETFD, 1) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ if (connect(fd, (struct sockaddr *) &sunaddr,
+ sizeof(sunaddr)) < 0) {
+ close(fd);
+ return -1;
+ }
+ ssh_socket_set_fd(s,fd);
+ return 0;
+}
+#endif
+
+/** \internal
+ * \brief closes a socket
+ */
+void ssh_socket_close(ssh_socket s){
+ if (ssh_socket_is_open(s)) {
+#ifdef _WIN32
+ closesocket(s->fd_in);
+ /* fd_in = fd_out under win32 */
+ s->last_errno = WSAGetLastError();
+#else
+ close(s->fd_in);
+ if(s->fd_out != s->fd_in && s->fd_out != -1)
+ close(s->fd_out);
+ s->last_errno = errno;
+#endif
+ s->fd_in = s->fd_out = SSH_INVALID_SOCKET;
+ }
+ if(s->poll_in != NULL){
+ if(s->poll_out == s->poll_in)
+ s->poll_out = NULL;
+ ssh_poll_free(s->poll_in);
+ s->poll_in=NULL;
+ }
+ if(s->poll_out != NULL){
+ ssh_poll_free(s->poll_out);
+ s->poll_out=NULL;
+ }
+}
+
+/**
+ * @internal
+ * @brief sets the file descriptor of the socket.
+ * @param[out] s ssh_socket to update
+ * @param[in] fd file descriptor to set
+ * @warning this function updates boths the input and output
+ * file descriptors
+ */
+void ssh_socket_set_fd(ssh_socket s, socket_t fd) {
+ s->fd_in = s->fd_out = fd;
+ if(s->poll_in)
+ ssh_poll_set_fd(s->poll_in,fd);
+}
+
+/**
+ * @internal
+ * @brief sets the input file descriptor of the socket.
+ * @param[out] s ssh_socket to update
+ * @param[in] fd file descriptor to set
+ */
+void ssh_socket_set_fd_in(ssh_socket s, socket_t fd) {
+ s->fd_in = fd;
+ if(s->poll_in)
+ ssh_poll_set_fd(s->poll_in,fd);
+}
+
+/**
+ * @internal
+ * @brief sets the output file descriptor of the socket.
+ * @param[out] s ssh_socket to update
+ * @param[in] fd file descriptor to set
+ */
+void ssh_socket_set_fd_out(ssh_socket s, socket_t fd) {
+ s->fd_out = fd;
+ if(s->poll_out)
+ ssh_poll_set_fd(s->poll_out,fd);
+}
+
+
+
+/** \internal
+ * \brief returns the input file descriptor of the socket
+ */
+socket_t ssh_socket_get_fd_in(ssh_socket s) {
+ return s->fd_in;
+}
+
+/** \internal
+ * \brief returns nonzero if the socket is open
+ */
+int ssh_socket_is_open(ssh_socket s) {
+ return s->fd_in != SSH_INVALID_SOCKET;
+}
+
+/** \internal
+ * \brief read len bytes from socket into buffer
+ */
+static int ssh_socket_unbuffered_read(ssh_socket s, void *buffer, uint32_t len) {
+ int rc = -1;
+
+ if (s->data_except) {
+ return -1;
+ }
+ if(s->fd_is_socket)
+ rc = recv(s->fd_in,buffer, len, 0);
+ else
+ rc = read(s->fd_in,buffer, len);
+#ifdef _WIN32
+ s->last_errno = WSAGetLastError();
+#else
+ s->last_errno = errno;
+#endif
+ s->data_to_read = 0;
+
+ if (rc < 0) {
+ s->data_except = 1;
+ }
+
+ return rc;
+}
+
+/** \internal
+ * \brief writes len bytes from buffer to socket
+ */
+static int ssh_socket_unbuffered_write(ssh_socket s, const void *buffer,
+ uint32_t len) {
+ int w = -1;
+
+ if (s->data_except) {
+ return -1;
+ }
+ if (s->fd_is_socket)
+ w = send(s->fd_out,buffer, len, 0);
+ else
+ w = write(s->fd_out, buffer, len);
+#ifdef _WIN32
+ s->last_errno = WSAGetLastError();
+#else
+ s->last_errno = errno;
+#endif
+ s->data_to_write = 0;
+ /* Reactive the POLLOUT detector in the poll multiplexer system */
+ if(s->poll_out){
+ ssh_log(s->session, SSH_LOG_PACKET, "Enabling POLLOUT for socket");
+ ssh_poll_set_events(s->poll_out,ssh_poll_get_events(s->poll_out) | POLLOUT);
+ }
+ if (w < 0) {
+ s->data_except = 1;
+ }
+
+ return w;
+}
+
+/** \internal
+ * \brief returns nonzero if the current socket is in the fd_set
+ */
+int ssh_socket_fd_isset(ssh_socket s, fd_set *set) {
+ if(s->fd_in == SSH_INVALID_SOCKET) {
+ return 0;
+ }
+ return FD_ISSET(s->fd_in,set) || FD_ISSET(s->fd_out,set);
+}
+
+/** \internal
+ * \brief sets the current fd in a fd_set and updates the max_fd
+ */
+
+void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) {
+ if (s->fd_in == SSH_INVALID_SOCKET) {
+ return;
+ }
+
+ FD_SET(s->fd_in,set);
+ FD_SET(s->fd_out,set);
+
+ if (s->fd_in >= 0 && s->fd_in != SSH_INVALID_SOCKET) {
+ *max_fd = s->fd_in + 1;
+ }
+ if (s->fd_out >= 0 && s->fd_in != SSH_INVALID_SOCKET) {
+ *max_fd = s->fd_out + 1;
+ }
+}
+
+/** \internal
+ * \brief buffered write of data
+ * \returns SSH_OK, or SSH_ERROR
+ * \warning has no effect on socket before a flush
+ */
+int ssh_socket_write(ssh_socket s, const void *buffer, int len) {
+ ssh_session session = s->session;
+ enter_function();
+ if(len > 0) {
+ if (buffer_add_data(s->out_buffer, buffer, len) < 0) {
+ return SSH_ERROR;
+ }
+ ssh_socket_set_towrite(s);
+ }
+ leave_function();
+ return SSH_OK;
+}
+
+
+/** \internal
+ * \brief starts a nonblocking flush of the output buffer
+ *
+ */
+int ssh_socket_nonblocking_flush(ssh_socket s) {
+ ssh_session session = s->session;
+ int w;
+
+ enter_function();
+
+ if (!ssh_socket_is_open(s)) {
+ session->alive = 0;
+ /* FIXME use ssh_socket_get_errno */
+ ssh_set_error(session, SSH_FATAL,
+ "Writing packet: error on socket (or connection closed): %s",
+ strerror(s->last_errno));
+
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ if (s->data_to_write && buffer_get_rest_len(s->out_buffer) > 0) {
+ w = ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer),
+ buffer_get_rest_len(s->out_buffer));
+ if (w < 0) {
+ session->alive = 0;
+ ssh_socket_close(s);
+ /* FIXME use ssh_socket_get_errno() */
+ /* FIXME use callback for errors */
+ ssh_set_error(session, SSH_FATAL,
+ "Writing packet: error on socket (or connection closed): %s",
+ strerror(s->last_errno));
+ leave_function();
+ return SSH_ERROR;
+ }
+ buffer_pass_bytes(s->out_buffer, w);
+ }
+
+ /* Is there some data pending? */
+ if (buffer_get_rest_len(s->out_buffer) > 0 && s->poll_out) {
+ /* force the poll system to catch pollout events */
+ ssh_poll_set_events(s->poll_out, ssh_poll_get_events(s->poll_out) |POLLOUT);
+ leave_function();
+ return SSH_AGAIN;
+ }
+
+ /* all data written */
+ leave_function();
+ return SSH_OK;
+}
+
+void ssh_socket_set_towrite(ssh_socket s) {
+ s->data_to_write = 1;
+}
+
+void ssh_socket_set_toread(ssh_socket s) {
+ s->data_to_read = 1;
+}
+
+void ssh_socket_set_except(ssh_socket s) {
+ s->data_except = 1;
+}
+
+int ssh_socket_data_available(ssh_socket s) {
+ return s->data_to_read;
+}
+
+int ssh_socket_data_writable(ssh_socket s) {
+ return s->data_to_write;
+}
+
+int ssh_socket_get_status(ssh_socket s) {
+ int r = 0;
+
+ if (s->data_to_read) {
+ r |= SSH_READ_PENDING;
+ }
+
+ if (s->data_except) {
+ r |= SSH_CLOSED_ERROR;
+ }
+
+ return r;
+}
+
+/**
+ * @internal
+ * @brief Launches a socket connection
+ * If a the socket connected callback has been defined and
+ * a poll object exists, this call will be non blocking.
+ * @param s socket to connect.
+ * @param host hostname or ip address to connect to.
+ * @param port port number to connect to.
+ * @param bind_addr address to bind to, or NULL for default.
+ * @returns SSH_OK socket is being connected.
+ * @returns SSH_ERROR error while connecting to remote host.
+ * @bug It only tries connecting to one of the available AI's
+ * which is problematic for hosts having DNS fail-over.
+ */
+
+int ssh_socket_connect(ssh_socket s, const char *host, int port, const char *bind_addr){
+ socket_t fd;
+ ssh_session session=s->session;
+ enter_function();
+ if(s->state != SSH_SOCKET_NONE)
+ return SSH_ERROR;
+ fd=ssh_connect_host_nonblocking(s->session,host,bind_addr,port);
+ ssh_log(session,SSH_LOG_PROTOCOL,"Nonblocking connection socket: %d",fd);
+ if(fd == SSH_INVALID_SOCKET)
+ return SSH_ERROR;
+ ssh_socket_set_fd(s,fd);
+ s->state=SSH_SOCKET_CONNECTING;
+ /* POLLOUT is the event to wait for in a nonblocking connect */
+ ssh_poll_set_events(ssh_socket_get_poll_handle_in(s),POLLOUT);
+#ifdef _WIN32
+ ssh_poll_add_events(ssh_socket_get_poll_handle_in(s),POLLWRNORM);
+#endif
+ leave_function();
+ return SSH_OK;
+}
+
+#ifndef _WIN32
+/**
+ * @internal
+ * @brief executes a command and redirect input and outputs
+ * @param command command to execute
+ * @param in input file descriptor
+ * @param out output file descriptor
+ */
+void ssh_execute_command(const char *command, socket_t in, socket_t out){
+ const char *args[]={"/bin/sh","-c",command,NULL};
+ /* redirect in and out to stdin, stdout and stderr */
+ dup2(in, 0);
+ dup2(out,1);
+ dup2(out,2);
+ close(in);
+ close(out);
+ execve(args[0],(char * const *)args,(char * const *)environ);
+ exit(1);
+}
+
+/**
+ * @internal
+ * @brief Open a socket on a ProxyCommand
+ * This call will always be nonblocking.
+ * @param s socket to connect.
+ * @param command Command to execute.
+ * @returns SSH_OK socket is being connected.
+ * @returns SSH_ERROR error while executing the command.
+ */
+
+int ssh_socket_connect_proxycommand(ssh_socket s, const char *command){
+ socket_t in_pipe[2];
+ socket_t out_pipe[2];
+ int pid;
+ int rc;
+ ssh_session session=s->session;
+ enter_function();
+ if(s->state != SSH_SOCKET_NONE)
+ return SSH_ERROR;
+
+ rc = pipe(in_pipe);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+ rc = pipe(out_pipe);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+
+ ssh_log(session,SSH_LOG_PROTOCOL,"Executing proxycommand '%s'",command);
+ pid = fork();
+ if(pid == 0){
+ ssh_execute_command(command,out_pipe[0],in_pipe[1]);
+ }
+ close(in_pipe[1]);
+ close(out_pipe[0]);
+ ssh_log(session,SSH_LOG_PROTOCOL,"ProxyCommand connection pipe: [%d,%d]",in_pipe[0],out_pipe[1]);
+ ssh_socket_set_fd_in(s,in_pipe[0]);
+ ssh_socket_set_fd_out(s,out_pipe[1]);
+ s->state=SSH_SOCKET_CONNECTED;
+ s->fd_is_socket=0;
+ /* POLLOUT is the event to wait for in a nonblocking connect */
+ ssh_poll_set_events(ssh_socket_get_poll_handle_in(s),POLLIN | POLLERR);
+ ssh_poll_set_events(ssh_socket_get_poll_handle_out(s),POLLOUT);
+ if(s->callbacks && s->callbacks->connected)
+ s->callbacks->connected(SSH_SOCKET_CONNECTED_OK,0,s->callbacks->userdata);
+ leave_function();
+ return SSH_OK;
+}
+
+#endif /* _WIN32 */
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/string.c b/src/string.c
new file mode 100644
index 00000000..06042aea
--- /dev/null
+++ b/src/string.c
@@ -0,0 +1,212 @@
+/*
+ * string.c - ssh string functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003-2008 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 <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/string.h"
+
+/**
+ * @defgroup libssh_string The SSH string functions
+ * @ingroup libssh
+ *
+ * @brief String manipulations used in libssh.
+ *
+ * @{
+ */
+
+/**
+ * @brief Create a new SSH String object.
+ *
+ * @param[in] size The size of the string.
+ *
+ * @return The newly allocated string, NULL on error.
+ */
+struct ssh_string_struct *ssh_string_new(size_t size) {
+ struct ssh_string_struct *str = NULL;
+
+ str = malloc(size + 4);
+ if (str == NULL) {
+ return NULL;
+ }
+
+ str->size = htonl(size);
+ return str;
+}
+
+/**
+ * @brief Fill a string with given data. The string should be big enough.
+ *
+ * @param s An allocated string to fill with data.
+ *
+ * @param data The data to fill the string with.
+ *
+ * @param len Size of data.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) {
+ if ((s == NULL) || (data == NULL) ||
+ (len == 0) || (len > s->size)) {
+ return -1;
+ }
+
+ memcpy(s->string, data, len);
+ return 0;
+}
+
+/**
+ * @brief Create a ssh string using a C string
+ *
+ * @param[in] what The source 0-terminated C string.
+ *
+ * @return The newly allocated string, NULL on error with errno
+ * set.
+ *
+ * @note The nul byte is not copied nor counted in the ouput string.
+ */
+struct ssh_string_struct *ssh_string_from_char(const char *what) {
+ struct ssh_string_struct *ptr = NULL;
+ size_t len = strlen(what);
+
+ ptr = malloc(4 + len);
+ if (ptr == NULL) {
+ return NULL;
+ }
+ ptr->size = htonl(len);
+ memcpy(ptr->string, what, len);
+
+ return ptr;
+}
+
+/**
+ * @brief Return the size of a SSH string.
+ *
+ * @param[in] s The the input SSH string.
+ *
+ * @return The size of the content of the string, 0 on error.
+ */
+size_t ssh_string_len(struct ssh_string_struct *s) {
+ if (s == NULL) {
+ return ntohl(0);
+ }
+
+ return ntohl(s->size);
+}
+
+/**
+ * @brief Convert a SSH string to a C nul-terminated string.
+ *
+ * @param[in] s The SSH input string.
+ *
+ * @return An allocated string pointer, NULL on error with errno
+ * set.
+ *
+ * @note If the input SSH string contains zeroes, some parts of the output
+ * string may not be readable with regular libc functions.
+ */
+char *ssh_string_to_char(struct ssh_string_struct *s) {
+ size_t len = ntohl(s->size) + 1;
+ char *new = malloc(len);
+
+ if (new == NULL) {
+ return NULL;
+ }
+ memcpy(new, s->string, len - 1);
+ new[len - 1] = '\0';
+ return new;
+}
+
+/**
+ * @brief Deallocate a char string object.
+ *
+ * @param[in] s The string to delete.
+ */
+void ssh_string_free_char(char *s) {
+ SAFE_FREE(s);
+}
+
+/**
+ * @brief Copy a string, return a newly allocated string. The caller has to
+ * free the string.
+ *
+ * @param[in] s String to copy.
+ *
+ * @return Newly allocated copy of the string, NULL on error.
+ */
+struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) {
+ struct ssh_string_struct *new = malloc(ntohl(s->size) + 4);
+
+ if (new == NULL) {
+ return NULL;
+ }
+ new->size = s->size;
+ memcpy(new->string, s->string, ntohl(s->size));
+
+ return new;
+}
+
+/**
+ * @brief Destroy the data in a string so it couldn't appear in a core dump.
+ *
+ * @param[in] s The string to burn.
+ */
+void ssh_string_burn(struct ssh_string_struct *s) {
+ if (s == NULL) {
+ return;
+ }
+ memset(s->string, 'X', ssh_string_len(s));
+}
+
+/**
+ * @brief Get the payload of the string.
+ *
+ * @param s The string to get the data from.
+ *
+ * @return Return the data of the string or NULL on error.
+ */
+void *ssh_string_data(struct ssh_string_struct *s) {
+ if (s == NULL) {
+ return NULL;
+ }
+
+ return s->string;
+}
+
+/**
+ * @brief Deallocate a SSH string object.
+ *
+ * \param[in] s The SSH string to delete.
+ */
+void ssh_string_free(struct ssh_string_struct *s) {
+ SAFE_FREE(s);
+}
+
+/** @} */
+
+/* vim: set ts=4 sw=4 et cindent: */
diff --git a/src/threads.c b/src/threads.c
new file mode 100644
index 00000000..7cac6dbf
--- /dev/null
+++ b/src/threads.c
@@ -0,0 +1,159 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2010 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.
+ */
+
+/**
+ * @defgroup libssh_threads Threading with libssh
+ * @ingroup libssh
+ *
+ * Threading with libssh
+ * @{
+ */
+
+#include "libssh/priv.h"
+#include "libssh/threads.h"
+
+#ifndef _WIN32
+
+#ifndef HAVE_PTHREAD
+#warning "You do not have any threading library installed. If the linked"
+#warning "application doesn't provide the threading callbacks, you're screwed"
+#endif
+
+
+#ifdef HAVE_PTHREAD
+
+#include <errno.h>
+#include <pthread.h>
+SSH_THREADS_PTHREAD(ssh_pthread_user_callbacks);
+#endif /* HAVE_PTHREAD */
+#endif /* _WIN32 */
+
+static struct ssh_threads_callbacks_struct *user_callbacks;
+
+#ifdef HAVE_LIBGCRYPT
+
+/* Libgcrypt specific way of handling thread callbacks */
+
+static struct gcry_thread_cbs gcrypt_threads_callbacks;
+
+static int libgcrypt_thread_init(void){
+ if(user_callbacks == NULL)
+ return SSH_ERROR;
+ gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || GCRY_THREAD_OPTION_USER;
+ gcrypt_threads_callbacks.mutex_init=user_callbacks->mutex_init;
+ gcrypt_threads_callbacks.mutex_destroy=user_callbacks->mutex_destroy;
+ gcrypt_threads_callbacks.mutex_lock=user_callbacks->mutex_lock;
+ gcrypt_threads_callbacks.mutex_unlock=user_callbacks->mutex_unlock;
+ gcry_control(GCRYCTL_SET_THREAD_CBS, &gcrypt_threads_callbacks);
+ return SSH_OK;
+}
+#else
+
+/* Libcrypto specific stuff */
+
+void **libcrypto_mutexes;
+
+static void libcrypto_lock_callback(int mode, int i, const char *file, int line){
+ (void)file;
+ (void)line;
+ if(mode & CRYPTO_LOCK){
+ user_callbacks->mutex_lock(&libcrypto_mutexes[i]);
+ } else {
+ user_callbacks->mutex_unlock(&libcrypto_mutexes[i]);
+ }
+}
+
+static int libcrypto_thread_init(){
+ int n=CRYPTO_num_locks();
+ int i;
+ libcrypto_mutexes=malloc(sizeof(void *) * n);
+ if (libcrypto_mutexes == NULL)
+ return SSH_ERROR;
+ for (i=0;i<n;++i){
+ user_callbacks->mutex_init(&libcrypto_mutexes[i]);
+ }
+ CRYPTO_set_id_callback(user_callbacks->thread_id);
+ CRYPTO_set_locking_callback(libcrypto_lock_callback);
+
+ return SSH_OK;
+}
+
+static void libcrypto_thread_finalize(){
+ int n=CRYPTO_num_locks();
+ int i;
+ if (libcrypto_mutexes==NULL)
+ return;
+ for (i=0;i<n;++i){
+ user_callbacks->mutex_destroy(&libcrypto_mutexes[i]);
+ }
+ SAFE_FREE(libcrypto_mutexes);
+
+}
+
+#endif
+
+/** @internal
+ * @brief inits the threading with the backend cryptographic libraries
+ */
+
+int ssh_threads_init(void){
+ static int threads_initialized=0;
+ int ret;
+ if(threads_initialized)
+ return SSH_OK;
+ /* first initialize the user_callbacks with our default handlers if not
+ * already the case
+ */
+ if(user_callbacks == NULL){
+#ifdef HAVE_PTHREAD
+ user_callbacks=&ssh_pthread_user_callbacks;
+ }
+#else
+ return SSH_ERROR; // Can't do anything to initialize threading
+ }
+#endif
+
+ /* Then initialize the crypto libraries threading callbacks */
+#ifdef HAVE_LIBGCRYPT
+ ret = libgcrypt_thread_init();
+#else /* Libcrypto */
+ ret = libcrypto_thread_init();
+#endif
+ if(ret == SSH_OK)
+ threads_initialized=1;
+ return ret;
+}
+
+void ssh_threads_finalize(void){
+#ifdef HAVE_LIBGCRYPT
+#else
+ libcrypto_thread_finalize();
+#endif
+}
+
+int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct *cb){
+ user_callbacks=cb;
+ return SSH_OK;
+}
+
+/**
+ * @}
+ */
diff --git a/src/wrapper.c b/src/wrapper.c
new file mode 100644
index 00000000..a78a93d9
--- /dev/null
+++ b/src/wrapper.c
@@ -0,0 +1,325 @@
+/*
+ * wrapper.c - wrapper for crytpo functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2003 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.
+ */
+
+/*
+ * Why a wrapper?
+ *
+ * Let's say you want to port libssh from libcrypto of openssl to libfoo
+ * you are going to spend hours to remove every references to SHA1_Update()
+ * to libfoo_sha1_update after the work is finished, you're going to have
+ * only this file to modify it's not needed to say that your modifications
+ * are welcome.
+ */
+
+#include "config.h"
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/crypto.h"
+#include "libssh/wrapper.h"
+
+/* it allocates a new cipher structure based on its offset into the global table */
+static struct crypto_struct *cipher_new(int offset) {
+ struct crypto_struct *cipher = NULL;
+
+ cipher = malloc(sizeof(struct crypto_struct));
+ if (cipher == NULL) {
+ return NULL;
+ }
+
+ /* note the memcpy will copy the pointers : so, you shouldn't free them */
+ memcpy(cipher, &ssh_get_ciphertab()[offset], sizeof(*cipher));
+
+ return cipher;
+}
+
+static void cipher_free(struct crypto_struct *cipher) {
+#ifdef HAVE_LIBGCRYPT
+ unsigned int i;
+#endif
+
+ if (cipher == NULL) {
+ return;
+ }
+
+ if(cipher->key) {
+#ifdef HAVE_LIBGCRYPT
+ for (i = 0; i < (cipher->keylen / sizeof(gcry_cipher_hd_t)); i++) {
+ gcry_cipher_close(cipher->key[i]);
+ }
+#elif defined HAVE_LIBCRYPTO
+ /* destroy the key */
+ memset(cipher->key, 0, cipher->keylen);
+#endif
+ SAFE_FREE(cipher->key);
+ }
+ SAFE_FREE(cipher);
+}
+
+struct ssh_crypto_struct *crypto_new(void) {
+ struct ssh_crypto_struct *crypto;
+
+ crypto = malloc(sizeof(struct ssh_crypto_struct));
+ if (crypto == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(crypto);
+ return crypto;
+}
+
+void crypto_free(struct ssh_crypto_struct *crypto){
+ if (crypto == NULL) {
+ return;
+ }
+
+ SAFE_FREE(crypto->server_pubkey);
+
+ cipher_free(crypto->in_cipher);
+ cipher_free(crypto->out_cipher);
+
+ bignum_free(crypto->e);
+ bignum_free(crypto->f);
+ bignum_free(crypto->x);
+ bignum_free(crypto->y);
+ bignum_free(crypto->k);
+ /* lot of other things */
+ /* i'm lost in my own code. good work */
+ memset(crypto,0,sizeof(*crypto));
+
+ SAFE_FREE(crypto);
+}
+
+static int crypt_set_algorithms2(ssh_session session){
+ const char *wanted;
+ int i = 0;
+ struct crypto_struct *ssh_ciphertab=ssh_get_ciphertab();
+ /* we must scan the kex entries to find crypto algorithms and set their appropriate structure */
+ /* out */
+ wanted = session->client_kex.methods[SSH_CRYPT_C_S];
+ while (ssh_ciphertab[i].name && strcmp(wanted, ssh_ciphertab[i].name)) {
+ i++;
+ }
+
+ if (ssh_ciphertab[i].name == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Crypt_set_algorithms2: no crypto algorithm function found for %s",
+ wanted);
+ return SSH_ERROR;
+ }
+ ssh_log(session, SSH_LOG_PACKET, "Set output algorithm to %s", wanted);
+
+ session->next_crypto->out_cipher = cipher_new(i);
+ if (session->next_crypto->out_cipher == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No space left");
+ return SSH_ERROR;
+ }
+ i = 0;
+
+ /* in */
+ wanted = session->client_kex.methods[SSH_CRYPT_S_C];
+ while (ssh_ciphertab[i].name && strcmp(wanted, ssh_ciphertab[i].name)) {
+ i++;
+ }
+
+ if (ssh_ciphertab[i].name == NULL) {
+ ssh_set_error(session, SSH_FATAL,
+ "Crypt_set_algorithms: no crypto algorithm function found for %s",
+ wanted);
+ return SSH_ERROR;
+ }
+ ssh_log(session, SSH_LOG_PACKET, "Set input algorithm to %s", wanted);
+
+ session->next_crypto->in_cipher = cipher_new(i);
+ if (session->next_crypto->in_cipher == NULL) {
+ ssh_set_error(session, SSH_FATAL, "Not enough space");
+ return SSH_ERROR;
+ }
+
+ /* compression */
+ if (strcmp(session->client_kex.methods[SSH_COMP_C_S], "zlib") == 0) {
+ session->next_crypto->do_compress_out = 1;
+ }
+ if (strcmp(session->client_kex.methods[SSH_COMP_S_C], "zlib") == 0) {
+ session->next_crypto->do_compress_in = 1;
+ }
+ if (strcmp(session->client_kex.methods[SSH_COMP_C_S], "zlib@openssh.org") == 0) {
+ session->next_crypto->delayed_compress_out = 1;
+ }
+ if (strcmp(session->client_kex.methods[SSH_COMP_S_C], "zlib@openssh.org") == 0) {
+ session->next_crypto->delayed_compress_in = 1;
+ }
+ return SSH_OK;
+}
+
+static int crypt_set_algorithms1(ssh_session session) {
+ int i = 0;
+ struct crypto_struct *ssh_ciphertab=ssh_get_ciphertab();
+
+ /* right now, we force 3des-cbc to be taken */
+ while (ssh_ciphertab[i].name && strcmp(ssh_ciphertab[i].name,
+ "3des-cbc-ssh1")) {
+ i++;
+ }
+
+ if (ssh_ciphertab[i].name == NULL) {
+ ssh_set_error(session, SSH_FATAL, "cipher 3des-cbc-ssh1 not found!");
+ return -1;
+ }
+
+ session->next_crypto->out_cipher = cipher_new(i);
+ if (session->next_crypto->out_cipher == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No space left");
+ return SSH_ERROR;
+ }
+
+ session->next_crypto->in_cipher = cipher_new(i);
+ if (session->next_crypto->in_cipher == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No space left");
+ return SSH_ERROR;
+ }
+
+ return SSH_OK;
+}
+
+int crypt_set_algorithms(ssh_session session) {
+ return (session->version == 1) ? crypt_set_algorithms1(session) :
+ crypt_set_algorithms2(session);
+}
+
+// TODO Obviously too much cut and paste here
+int crypt_set_algorithms_server(ssh_session session){
+ char *server = NULL;
+ char *client = NULL;
+ char *match = NULL;
+ int i = 0;
+ struct crypto_struct *ssh_ciphertab=ssh_get_ciphertab();
+
+ /* we must scan the kex entries to find crypto algorithms and set their appropriate structure */
+ enter_function();
+ /* out */
+ server = session->server_kex.methods[SSH_CRYPT_S_C];
+ if(session && session->client_kex.methods) {
+ client = session->client_kex.methods[SSH_CRYPT_S_C];
+ } else {
+ ssh_log(session,SSH_LOG_PROTOCOL, "Client KEX empty");
+ }
+ /* That's the client algorithms that are more important */
+ match = ssh_find_matching(server,client);
+
+
+ if(!match){
+ ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server : no matching algorithm function found for %s",server);
+ free(match);
+ leave_function();
+ return SSH_ERROR;
+ }
+ while(ssh_ciphertab[i].name && strcmp(match,ssh_ciphertab[i].name))
+ i++;
+ if(!ssh_ciphertab[i].name){
+ ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server : no crypto algorithm function found for %s",server);
+ free(match);
+ leave_function();
+ return SSH_ERROR;
+ }
+ ssh_log(session,SSH_LOG_PACKET,"Set output algorithm %s",match);
+ SAFE_FREE(match);
+
+ session->next_crypto->out_cipher = cipher_new(i);
+ if (session->next_crypto->out_cipher == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No space left");
+ leave_function();
+ return SSH_ERROR;
+ }
+ i=0;
+ /* in */
+ client=session->client_kex.methods[SSH_CRYPT_C_S];
+ server=session->server_kex.methods[SSH_CRYPT_S_C];
+ match=ssh_find_matching(server,client);
+ if(!match){
+ ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server : no matching algorithm function found for %s",server);
+ free(match);
+ leave_function();
+ return SSH_ERROR;
+ }
+ while(ssh_ciphertab[i].name && strcmp(match,ssh_ciphertab[i].name))
+ i++;
+ if(!ssh_ciphertab[i].name){
+ ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server : no crypto algorithm function found for %s",server);
+ free(match);
+ leave_function();
+ return SSH_ERROR;
+ }
+ ssh_log(session,SSH_LOG_PACKET,"Set input algorithm %s",match);
+ SAFE_FREE(match);
+
+ session->next_crypto->in_cipher = cipher_new(i);
+ if (session->next_crypto->in_cipher == NULL) {
+ ssh_set_error(session, SSH_FATAL, "No space left");
+ leave_function();
+ return SSH_ERROR;
+ }
+
+ /* compression */
+ client=session->client_kex.methods[SSH_CRYPT_C_S];
+ server=session->server_kex.methods[SSH_CRYPT_C_S];
+ match=ssh_find_matching(server,client);
+ if(match && !strcmp(match,"zlib")){
+ ssh_log(session,SSH_LOG_PACKET,"enabling C->S compression");
+ session->next_crypto->do_compress_in=1;
+ }
+ SAFE_FREE(match);
+
+ client=session->client_kex.methods[SSH_CRYPT_S_C];
+ server=session->server_kex.methods[SSH_CRYPT_S_C];
+ match=ssh_find_matching(server,client);
+ if(match && !strcmp(match,"zlib")){
+ ssh_log(session,SSH_LOG_PACKET,"enabling S->C compression\n");
+ session->next_crypto->do_compress_out=1;
+ }
+ SAFE_FREE(match);
+
+ server=session->server_kex.methods[SSH_HOSTKEYS];
+ client=session->client_kex.methods[SSH_HOSTKEYS];
+ match=ssh_find_matching(server,client);
+ if(match && !strcmp(match,"ssh-dss"))
+ session->hostkeys=SSH_KEYTYPE_DSS;
+ else if(match && !strcmp(match,"ssh-rsa"))
+ session->hostkeys=SSH_KEYTYPE_RSA;
+ else {
+ ssh_set_error(session, SSH_FATAL, "Cannot know what %s is into %s",
+ match ? match : NULL, server);
+ SAFE_FREE(match);
+ leave_function();
+ return SSH_ERROR;
+ }
+ SAFE_FREE(match);
+ leave_function();
+ return SSH_OK;
+}
+
+/* vim: set ts=2 sw=2 et cindent: */