diff options
author | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
---|---|---|
committer | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
commit | f7842e3a4b9acea2126ff725f993c299aef0e6db (patch) | |
tree | 18239f819a5edbcfc7f2961c48f3f9297314ef22 /src | |
parent | 38421403d2dc45636e597f2a909daa6ae31976de (diff) | |
download | libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.gz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.xz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.zip |
misc: Rename libssh/ to src/
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 204 | ||||
-rw-r--r-- | src/agent.c | 502 | ||||
-rw-r--r-- | src/auth.c | 1682 | ||||
-rw-r--r-- | src/auth1.c | 206 | ||||
-rw-r--r-- | src/base64.c | 287 | ||||
-rw-r--r-- | src/buffer.c | 578 | ||||
-rw-r--r-- | src/callbacks.c | 43 | ||||
-rw-r--r-- | src/channels.c | 2495 | ||||
-rw-r--r-- | src/channels1.c | 298 | ||||
-rw-r--r-- | src/client.c | 814 | ||||
-rw-r--r-- | src/config.c | 347 | ||||
-rw-r--r-- | src/connect.c | 600 | ||||
-rw-r--r-- | src/crc32.c | 92 | ||||
-rw-r--r-- | src/crypt.c | 216 | ||||
-rw-r--r-- | src/dh.c | 1049 | ||||
-rw-r--r-- | src/error.c | 123 | ||||
-rw-r--r-- | src/gcrypt_missing.c | 99 | ||||
-rw-r--r-- | src/gzip.c | 222 | ||||
-rw-r--r-- | src/init.c | 94 | ||||
-rw-r--r-- | src/kex.c | 835 | ||||
-rw-r--r-- | src/keyfiles.c | 1902 | ||||
-rw-r--r-- | src/keys.c | 1497 | ||||
-rw-r--r-- | src/legacy.c | 237 | ||||
-rw-r--r-- | src/libcrypto.c | 443 | ||||
-rw-r--r-- | src/libgcrypt.c | 423 | ||||
-rw-r--r-- | src/log.c | 82 | ||||
-rw-r--r-- | src/match.c | 185 | ||||
-rw-r--r-- | src/messages.c | 848 | ||||
-rw-r--r-- | src/misc.c | 685 | ||||
-rw-r--r-- | src/options.c | 1138 | ||||
-rw-r--r-- | src/packet.c | 529 | ||||
-rw-r--r-- | src/packet1.c | 362 | ||||
-rw-r--r-- | src/pcap.c | 434 | ||||
-rw-r--r-- | src/pki.c | 113 | ||||
-rw-r--r-- | src/poll.c | 692 | ||||
-rw-r--r-- | src/scp.c | 752 | ||||
-rw-r--r-- | src/server.c | 1174 | ||||
-rw-r--r-- | src/session.c | 520 | ||||
-rw-r--r-- | src/sftp.c | 3207 | ||||
-rw-r--r-- | src/sftpserver.c | 490 | ||||
-rw-r--r-- | src/socket.c | 719 | ||||
-rw-r--r-- | src/string.c | 212 | ||||
-rw-r--r-- | src/threads.c | 159 | ||||
-rw-r--r-- | src/wrapper.c | 325 |
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: */ |