commit 7a5ffc8cee259bbde82ab92515cd8fea2166854b Author: Sara Golemon Date: Tue Dec 7 21:17:20 2004 +0000 Initial revision diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cfccdad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,37 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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. + */ + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 00000000..ddca02c5 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,48 @@ +subdirs = src/ +top_srcdir = @top_srcdir@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ +libdir = @exec_prefix@/lib +incldir = @prefix@/include +distdir = @top_srcdir@/dist + +CC = @CC@ +CFLAGS = -c @CFLAGS@ -Iinclude/ -Wall -g +LIBS = -lssh2 -Lsrc/ +INSTALL = @INSTALL@ +VERSION=0.1-dev +DISTLIB=libssh2-$(VERSION) + +all: + @for dir in ${subdirs}; do \ + (cd $$dir && $(MAKE) all) \ + || case "$(MFLAGS)" in *k*) fail=yes;; *) exit 1;; esac; \ + done && test -z "$$fail" + $(CC) -o ssh2_sample.o ssh2_sample.c $(CFLAGS) + $(CC) -o ssh2_sample ssh2_sample.o $(LIBS) +install: + $(top_srcdir)/mkinstalldirs $(incldir) + $(top_srcdir)/mkinstalldirs $(libdir) + @for dir in ${subdirs}; do \ + (cd $$dir && $(MAKE) install) \ + || case "$(MFLAGS)" in *k*) fail=yes;; *) exit 1;; esac; \ + done && test -z "$$fail" + $(INSTALL) -m 644 include/libssh2.h $(incldir)/ +clean: + @for dir in ${subdirs}; do \ + (cd $$dir && $(MAKE) clean) \ + || case "$(MFLAGS)" in *k*) fail=yes;; *) exit 1;; esac; \ + done && test -z "$$fail" + rm -f ssh2_sample.o ssh2_sample +dist: + autoheader + autoconf + rm -f $(DISTLIB) + ln -s . $(DISTLIB) + tar -zcf $(DISTLIB).tar.gz \ + $(DISTLIB)/configure.in $(DISTLIB)/configure $(DISTLIB)/Makefile.in $(DISTLIB)/ssh2_sample.c \ + $(DISTLIB)/LICENSE $(DISTLIB)/README $(DISTLIB)/TODO \ + $(DISTLIB)/mkinstalldirs $(DISTLIB)/install-sh \ + $(DISTLIB)/src/*.c $(DISTLIB)/src/Makefile.in \ + $(DISTLIB)/include/libssh2.h $(DISTLIB)/include/libssh2_priv.h $(DISTLIB)/include/libssh2_config.h.in + rm -f $(DISTLIB) diff --git a/README b/README new file mode 100644 index 00000000..1bdc8f17 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +libssh2 - SSH2 library +====================== + +Version 0.1-dev +--------------- + + Initial Release: + KEX methods: diffie-hellman-group14-sha1, diffie-hellman-group-exchange-sha1, diffie-hellman-group1-sha1 + Hostkey methods: ssh-rsa, ssh-dss + Cipher methods: aes256-cbc, rijndael-cbc@lysator.liu.se, aes192-cbc, aes128-cbc, blowfish-cbc, arcfour, cast128-cbc, 3des-cbc, none* + Compression methods: zlib, none + MAC methods: hmac-sha1, hmac-sha1-96, hmac-ripemd160, hmac-ripemd160@openssh.com none* + *Cipher/MAC "none" is disabled by default for security purposes, + Use --enable-crypt-none and/or --enable-mac-none with ./configure to enable diff --git a/TODO b/TODO new file mode 100644 index 00000000..9a7fcc2e --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ +* More Crypt Methods +* hmac-md5, hmac-md5-96 +* SFTP support +* Review callbacks diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..5ee33f49 --- /dev/null +++ b/configure.in @@ -0,0 +1,150 @@ +# AC_PREREQ(2.57) +AC_INIT(libssh2, 0.1 , pollita@php.net) +AC_CONFIG_SRCDIR([src]) +AC_CONFIG_HEADER([include/libssh2_config.h]) + +SHLIB_SUFFIX_NAME="so" +SHLIB_LDFLAGS="-shared" + +AC_SUBST(SHLIB_SUFFIX_NAME) +AC_SUBST(SHLIB_LDFLAGS) + +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_RANLIB +AC_C_BIGENDIAN +if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) +fi + +# +# Look for OpenSSL +# +AC_ARG_WITH(openssl, + AC_HELP_STRING([--with-openssl=DIR],[Look for OpenSSL in PATH]), + [LIBSSH2_OPENSSL_DIR=$withval],[LIBSSH2_OPENSSL_DIR=yes]) + +if test "$LIBSSH2_OPENSSL_DIR" = "no" || test "$LIBSSH2_OPENSSL_DIR" = "yes"; then + unset LIBSSH2_OPENSSL_DIR +fi + +found_openssl=no +unset OPENSSL_INCDIR +unset OPENSSL_LIBDIR + +AC_MSG_CHECKING([for OpenSSL]) + +# Explicit path given, use it rather than pkg-config +if test ! -z "$LIBSSH2_OPENSSL_DIR"; then + found_openssl=yes + OPENSSL_LIBDIR=$LIBSSH2_OPENSSL_DIR/lib + OPENSSL_INCDIR=$LIBSSH2_OPENSSL_DIR/include + AC_MSG_RESULT([Using explicit path $LIBSSH2_OPENSSL_DIR]) +fi + +# If pkg-config is found try using it +if test "$found_openssl" = "no" && test -x "$PKG_CONFIG" && $PKG_CONFIG --exists openssl; then + found_openssl=yes + OPENSSL_LIBDIR=`$PKG_CONFIG --libs openssl` + OPENSSL_INCDIR=`$PKG_CONFIG --variable=includedir openssl` + AC_MSG_RESULT([Using paths from pkg-config]) +fi + +# Elsewise, search for OpenSSL wherever it might be +if test "$found_openssl" = "no"; then + OPENSSL_SEARCH_PATH="/usr/local/ssl /usr/local /usr /usr/local/openssl" + + for i in $OPENSSL_SEARCH_PATH; do + if test -r $i/include/openssl/evp.h; then + OPENSSL_INCDIR=$i/include + fi + if test -r $i/include/openssl/hmac.h; then + OPENSSL_INCDIR=$i/include + fi + if test -r $i/lib/libcrypto.a -o -r $i/lib/libcrypto.$SHLIB_SUFFIX_NAME; then + OPENSSL_LIBDIR=$i/lib + fi + test -n "$OPENSSL_INCDIR" && test -n "$OPENSSL_LIBDIR" && break + done + + if test -z "$OPENSSL_INCDIR"; then + AC_MSG_ERROR([Cannot find OpenSSL's or ]) + fi + + if test -z "$OPENSSL_LIBDIR"; then + AC_MSG_ERROR([Cannot find OpenSSL's libcrypto]) + fi + + AC_MSG_RESULT([$OPENSSL_INCDIR $OPENSSL_LIBDIR]) +fi + +# +# Confirm required OpenSSL libs +# +if test ! -r $OPENSSL_INCDIR/openssl/bn.h || test ! -r $OPENSSL_INCDIR/openssl/evp.h || \ + test ! -r $OPENSSL_INCDIR/openssl/hmac.h || test ! -r $OPENSSL_INCDIR/openssl/pem.h || \ + test ! -r $OPENSSL_INCDIR/openssl/sha.h; then + AC_MSG_ERROR([Missing one or more of , , , , ]) +fi + +CFLAGS="$CFLAGS -I$OPENSSL_INCDIR" +LDFLAGS="$LDFLAGS -L$OPENSSL_LIBDIR -lcrypto" + +# +# zlib +# +AC_ARG_WITH(libz, + AC_HELP_STRING([--with-libz=PATH],[Look for libz in PATH]), + [LIBSSH2_LIBZ_DIR=$withval],[LIBSSH2_LIBZ_DIR="/usr/local /usr /usr/local/libz /usr/libz /usr/local/zlib /usr/zlib"]) + +if test "$LIBSSH2_LIBZ_DIR" = "no" || test "$LIBSSH2_LIBZ_DIR" = "yes"; then + unset LIBSSH2_LIBZ_DIR +fi + +unset LIBZ_INCDIR +unset LIBZ_LIBDIR + +AC_MSG_CHECKING([for libz]) + +for i in $LIBSSH2_LIBZ_DIR; do + if test -r $i/include/zlib.h; then + LIBZ_INCDIR=$i/include + fi + if test -r $i/lib/libz.a -o -r $i/lib/libz.$SHLIB_SUFFIX_NAME; then + LIBZ_LIBDIR=$i/lib + fi + test -n "$LIBZ_INCDIR" && test -n "$LIBZ_LIBDIR" && break +done + +if test -n "$LIBZ_INCDIR" && test -n "$LIBZ_LIBDIR"; then + AC_MSG_RESULT([Found in $LIBZ_INCDIR $LIBZ_LIBDIR]) + CFLAGS="$CFLAGS -I$LIBZ_INCDIR" + LDFLAGS="$LDFLAGS -L$LIBZ_LIBDIR -lz" + AC_DEFINE(LIBSSH2_HAVE_ZLIB, 1, [Compile in zlib support]) +else + AC_MSG_RESULT([Cannot find libz's ]) +fi + +# +# Optional Settings +# +AC_ARG_ENABLE(crypt-none, + AC_HELP_STRING([--enable-crypt-none],[Permit "none" cipher -- NOT RECOMMENDED]), + [AC_DEFINE(LIBSSH2_CRYPT_NONE, 1, [Enable "none" cipher -- NOT RECOMMENDED])]) + +AC_ARG_ENABLE(mac-none, + AC_HELP_STRING([--enable-mac-none],[Permit "none" MAC -- NOT RECOMMENDED]), + [AC_DEFINE(LIBSSH2_MAC_NONE, 1, [Enable "none" MAC -- NOT RECOMMENDED])]) + +# Checks for header files. +# AC_HEADER_STDC +AC_CHECK_HEADERS([errno.h fcntl.h stdio.h stdlib.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST + +AC_CONFIG_FILES([Makefile + src/Makefile]) +AC_OUTPUT diff --git a/include/libssh2.h b/include/libssh2.h new file mode 100644 index 00000000..abe9d36d --- /dev/null +++ b/include/libssh2.h @@ -0,0 +1,217 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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. + */ + +#ifndef LIBSSH2_H +#define LIBSSH2_H 1 + +#include +#include +#include + +#define LIBSSH2_VERSION "0.1dev" + +/* Part of every banner, user specified or not */ +#define LIBSSH2_SSH_BANNER "SSH-2.0-libssh2_" LIBSSH2_VERSION + +/* We *could* add a comment here if we so chose */ +#define LIBSSH2_SSH_DEFAULT_BANNER LIBSSH2_SSH_BANNER +#define LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF LIBSSH2_SSH_DEFAULT_BANNER "\r\n" + +/* Enable the "new" version of diffie-hellman-group-exchange-sha1 */ +#define LIBSSH2_DH_GEX_NEW + +/* Default generate and safe prime sizes for diffie-hellman-group-exchange-sha1 */ +#define LIBSSH2_DH_GEX_MINGROUP 1024 +#define LIBSSH2_DH_GEX_OPTGROUP 1536 +#define LIBSSH2_DH_GEX_MAXGROUP 2048 + +/* Defaults for pty requests */ +#define LIBSSH2_TERM_WIDTH 80 +#define LIBSSH2_TERM_HEIGHT 24 +#define LIBSSH2_TERM_WIDTH_PX 0 +#define LIBSSH2_TERM_HEIGHT_PX 0 + +/* 1/4 second */ +#define LIBSSH2_SOCKET_POLL_UDELAY 250000 +/* 0.25 * 120 == 30 seconds */ +#define LIBSSH2_SOCKET_POLL_MAXLOOPS 120 + +/* Maximum size to allow a payload to compress to, plays it safe by falling short of spec limits */ +#define LIBSSH2_PACKET_MAXCOMP 32000 + +/* Maximum size to allow a payload to deccompress to, plays it safe by allowing more than spec requires */ +#define LIBSSH2_PACKET_MAXDECOMP 40000 + +/* Malloc callbacks */ +#define LIBSSH2_ALLOC_FUNC(name) void *name(size_t count, void **abstract) +#define LIBSSH2_REALLOC_FUNC(name) void *name(void *ptr, size_t count, void **abstract) +#define LIBSSH2_FREE_FUNC(name) void name(void *ptr, void **abstract) + +/* Callbacks for special SSH packets */ +#define LIBSSH2_IGNORE_FUNC(name) void name(LIBSSH2_SESSION *session, const char *message, int message_len, void **abstract) +#define LIBSSH2_DEBUG_FUNC(name) void name(LIBSSH2_SESSION *session, int always_display, const char *message, int message_len, const char *language, int language_len,void **abstract) +#define LIBSSH2_DISCONNECT_FUNC(name) void name(LIBSSH2_SESSION *session, int reason, const char *message, int message_len, const char *language, int language_len, void **abstract) +#define LIBSSH2_PASSWD_CHANGEREQ_FUNC(name) void name(LIBSSH2_SESSION *session, char **newpw, int *newpw_len, void **abstract) +#define LIBSSH2_MACERROR_FUNC(name) int name(LIBSSH2_SESSION *session, const char *packet, int packet_len, void **abstract) + +typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; +typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; + +#ifdef WIN_32 +#define LIBSSH2_API __declspec(dllexport) +#else +#define LIBSSH2_API +#endif + +#define LIBSSH2_HOSTKEY_HASH_MD5 1 +#define LIBSSH2_HOSTKEY_HASH_SHA1 2 + +/* Disconnect Codes (defined by SSH protocol) */ +#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH_DISCONNECT_RESERVED 4 +#define SSH_DISCONNECT_MAC_ERROR 5 +#define SSH_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH_DISCONNECT_CONNECTION_LOST 10 +#define SSH_DISCONNECT_BY_APPLICATION 11 +#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 + +/* Error Codes (defined by libssh2) */ +#define LIBSSH2_ERROR_SOCKET_NONE -1 +#define LIBSSH2_ERROR_BANNER_NONE -2 +#define LIBSSH2_ERROR_BANNER_SEND -3 +#define LIBSSH2_ERROR_INVALID_MAC -4 +#define LIBSSH2_ERROR_KEX_FAILURE -5 +#define LIBSSH2_ERROR_ALLOC -6 +#define LIBSSH2_ERROR_SOCKET_SEND -7 +#define LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE -8 +#define LIBSSH2_ERROR_TIMEOUT -9 +#define LIBSSH2_ERROR_HOSTKEY_INIT -10 +#define LIBSSH2_ERROR_HOSTKEY_SIGN -11 +#define LIBSSH2_ERROR_DECRYPT -12 +#define LIBSSH2_ERROR_SOCKET_DISCONNECT -13 +#define LIBSSH2_ERROR_PROTO -14 +#define LIBSSH2_ERROR_PASSWORD_EXPIRED -15 +#define LIBSSH2_ERROR_FILE -16 +#define LIBSSH2_ERROR_METHOD_NONE -17 +#define LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED -18 +#define LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED -19 +#define LIBSSH2_ERROR_CHANNEL_OUTOFORDER -20 +#define LIBSSH2_ERROR_CHANNEL_FAILURE -21 +#define LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED -22 +#define LIBSSH2_ERROR_CHANNEL_UNKNOWN -23 +#define LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED -24 +#define LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED -25 +#define LIBSSH2_ERROR_CHANNEL_CLOSED -26 +#define LIBSSH2_ERROR_CHANNEL_EOF_SENT -27 +#define LIBSSH2_ERROR_SCP_PROTOCOL -28 +#define LIBSSH2_ERROR_ZLIB -29 +#define LIBSSH2_ERROR_SOCKET_TIMEOUT -30 + +/* Session API */ +LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), LIBSSH2_FREE_FUNC((*my_free)), LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); +#define libssh2_session_init() libssh2_session_init_ex(NULL, NULL, NULL, NULL) + +LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int socket); +LIBSSH2_API void libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, char *description, char *lang); +#define libssh2_session_disconnect(session, description) libssh2_session_disconnect_ex((session), SSH_DISCONNECT_BY_APPLICATION, (description), "") +LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session); + +LIBSSH2_API char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type); + +/* Userauth API */ +LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, char *username, int username_len); +LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_userauth_password_ex(LIBSSH2_SESSION *session, char *username, int username_len, char *password, int password_len, LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))); +#define libssh2_userauth_password(session, username, password) libssh2_userauth_password_ex((session), (username), strlen(username), (password), strlen(password), NULL) + +LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, char *username, int username_len, + char *publickey, char *privatekey, + char *passphrase); +#define libssh2_userauth_publickey_fromfile(session, username, publickey, privatekey, passphrase) \ + libssh2_userauth_publickey_fromfile_ex((session), (username), strlen(username), (publickey), (privatekey), (passphrase)) + +/* Channel API */ +#define LIBSSH2_CHANNEL_WINDOW_DEFAULT 65536 +#define LIBSSH2_CHANNEL_PACKET_DEFAULT 16384 + +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_open_ex(LIBSSH2_SESSION *session, char *channel_type, int channel_type_len, int window_size, int packet_size, char *message, int message_len); +#define libssh2_channel_open_session(session) libssh2_channel_open_ex((session), "session", sizeof("session") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0) +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, char *host, int port, char *shost, int sport); +#define libssh2_channel_direct_tcpip(session, host, port) libssh2_channel_direct_tcpip_ex((session), (host), (port), "127.0.0.1", 22) + +LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varname, int varname_len, char *value, int value_len); +#define libssh2_channel_setenv(channel, varname, value) libssh2_channel_setenv_ex((channel), (varname), strlen(varname), (value), strlen(value)) + +LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *term, int term_len, char *modes, int modes_len, int width, int height, int width_px, int height_px); +#define libssh2_channel_request_pty(channel, term) libssh2_channel_request_pty_ex((channel), (term), strlen(term), NULL, 0, LIBSSH2_TERM_WIDTH, LIBSSH2_TERM_HEIGHT, LIBSSH2_TERM_WIDTH_PX, LIBSSH2_TERM_HEIGHT_PX) + +LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, char *request, int request_len, char *message, int message_len); +#define libssh2_channel_shell(channel) libssh2_channel_process_startup((channel), "shell", sizeof("shell") - 1, NULL, 0) +#define libssh2_channel_exec(channel, command) libssh2_channel_process_startup((channel), "exec", sizeof("exec") - 1, (command), strlen(command)) +#define libssh2_channel_subsystem(channel, subsystem) libssh2_channel_process_startup((channel), "subsystem", sizeof("subsystem") - 1, (subsystem), strlen(subsystem)) + +#define SSH_EXTENDED_DATA_STDERR 1 +LIBSSH2_API int libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen); +#define libssh2_channel_read(channel, buf, buflen) libssh2_channel_read_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_read_stderr(channel, buf, buflen) libssh2_channel_read_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + +LIBSSH2_API int libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen); +#define libssh2_channel_write(channel, buf, buflen) libssh2_channel_write_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_write_stderr(channel, buf, buflen) libssh2_channel_write_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + +LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, int blocking); + +LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel); + +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, char *path, struct stat *sb); +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, char *path, int mode, size_t size, long mtime, long atime); +#define libssh2_scp_send(session, path, mode, size) libssh2_scp_send_ex((session), (path), (mode), (size), 0, 0) + +LIBSSH2_API int libssh2_base64_decode(LIBSSH2_SESSION *session, char **dest, int *dest_len, char *src, int src_len); + +#endif /* LIBSSH2_H */ diff --git a/include/libssh2_config.h.in b/include/libssh2_config.h.in new file mode 100644 index 00000000..f5ebd8a9 --- /dev/null +++ b/include/libssh2_config.h.in @@ -0,0 +1,71 @@ +/* include/libssh2_config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the header file. */ +#undef HAVE_ERRNO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDIO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Enable "none" cipher -- NOT RECOMMENDED */ +#undef LIBSSH2_CRYPT_NONE + +/* Compile in zlib support */ +#undef LIBSSH2_HAVE_ZLIB + +/* Enable "none" MAC -- NOT RECOMMENDED */ +#undef LIBSSH2_MAC_NONE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +#undef WORDS_BIGENDIAN + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const diff --git a/include/libssh2_priv.h b/include/libssh2_priv.h new file mode 100644 index 00000000..76254716 --- /dev/null +++ b/include/libssh2_priv.h @@ -0,0 +1,379 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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. + */ + +#ifndef LIBSSH2_PRIV_H +#define LIBSSH2_PRIV_H 1 + +/* Definitions shared with the public */ +#include "libssh2_config.h" +#include "libssh2.h" + +#include +#include + +#define LIBSSH2_ALLOC(session, count) session->alloc((count), &(session)->abstract) +#define LIBSSH2_REALLOC(session, ptr, count) session->realloc((ptr), (count), &(session)->abstract) +#define LIBSSH2_FREE(session, ptr) session->free((ptr), &(session)->abstract) + +#define LIBSSH2_IGNORE(session, data, datalen) session->ssh_msg_ignore((session), (data), (datalen), &(session)->abstract) +#define LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len) \ + session->ssh_msg_disconnect((session), (always_display), (message), (message_len), (language), (language_len), &(session)->abstract) +#define LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len) \ + session->ssh_msg_disconnect((session), (reason), (message), (message_len), (language), (language_len), &(session)->abstract) + +#define LIBSSH2_MACERROR(session, data, datalen) session->macerror((session), (data), (datalen), (session)->abstract) + +typedef struct _LIBSSH2_KEX_METHOD LIBSSH2_KEX_METHOD; +typedef struct _LIBSSH2_HOSTKEY_METHOD LIBSSH2_HOSTKEY_METHOD; +typedef struct _LIBSSH2_MAC_METHOD LIBSSH2_MAC_METHOD; +typedef struct _LIBSSH2_CRYPT_METHOD LIBSSH2_CRYPT_METHOD; +typedef struct _LIBSSH2_COMP_METHOD LIBSSH2_COMP_METHOD; + +typedef struct _LIBSSH2_PACKET LIBSSH2_PACKET; +typedef struct _LIBSSH2_PACKET_BRIGADE LIBSSH2_PACKET_BRIGADE; +typedef struct _LIBSSH2_CHANNEL_BRIGADE LIBSSH2_CHANNEL_BRIGADE; + +struct _LIBSSH2_PACKET { + unsigned char type; + + /* Unencrypted Payload (no type byte, no padding, just the facts ma'am) */ + unsigned char *data; + unsigned long data_len; + + /* Where to start reading data from, + * used for channel data that's been partially consumed */ + unsigned long data_head; + + /* Can the message be confirmed? */ + int mac; + + LIBSSH2_PACKET_BRIGADE *brigade; + + LIBSSH2_PACKET *next, *prev; +}; + +struct _LIBSSH2_PACKET_BRIGADE { + LIBSSH2_PACKET *head, *tail; +}; + +typedef struct _libssh2_channel_data { + /* Identifier */ + unsigned long id; + + /* Limits and restrictions */ + unsigned long window_size_initial, window_size, packet_size; + + /* Set to 1 when CHANNEL_CLOSE / CHANNEL_EOF sent/received */ + int close, eof; +} libssh2_channel_data; + +struct _LIBSSH2_CHANNEL { + unsigned char *channel_type; + unsigned channel_type_len; + + int blocking; + + libssh2_channel_data local, remote; + + LIBSSH2_SESSION *session; + + LIBSSH2_CHANNEL *next, *prev; +}; + +struct _LIBSSH2_CHANNEL_BRIGADE { + LIBSSH2_CHANNEL *head, *tail; +}; + +typedef struct _libssh2_endpoint_data { + unsigned char *banner; + + unsigned char *kexinit; + unsigned long kexinit_len; + + LIBSSH2_CRYPT_METHOD *crypt; + void *crypt_abstract; + + LIBSSH2_MAC_METHOD *mac; + unsigned long seqno; + void *mac_abstract; + + LIBSSH2_COMP_METHOD *comp; + void *comp_abstract; + + /* Method Preferences -- NULL yields "load order" */ + LIBSSH2_CRYPT_METHOD **crypt_prefs; + LIBSSH2_MAC_METHOD **mac_prefs; + LIBSSH2_COMP_METHOD **comp_prefs; +} libssh2_endpoint_data; + +struct _LIBSSH2_SESSION { + /* Memory management callbacks */ + void *abstract; + LIBSSH2_ALLOC_FUNC((*alloc)); + LIBSSH2_REALLOC_FUNC((*realloc)); + LIBSSH2_FREE_FUNC((*free)); + + /* Other callbacks */ + LIBSSH2_IGNORE_FUNC((*ssh_msg_ignore)); + LIBSSH2_DEBUG_FUNC((*ssh_msg_debug)); + LIBSSH2_DISCONNECT_FUNC((*ssh_msg_disconnect)); + LIBSSH2_MACERROR_FUNC((*macerror)); + + /* Method preferences -- NULL yields "load order" */ + LIBSSH2_KEX_METHOD **kex_prefs; + LIBSSH2_HOSTKEY_METHOD **hostkey_prefs; + + int exchanging_keys; + int newkeys; + int authenticated; + + /* Agreed Key Exchange Method */ + LIBSSH2_KEX_METHOD *kex; + + unsigned char *session_id; + unsigned long session_id_len; + + /* Server's public key */ + LIBSSH2_HOSTKEY_METHOD *hostkey; + void *server_hostkey_abstract; + + /* Either set with libssh2_session_hostkey() (for server mode) + * Or read from server in (eg) KEXDH_INIT (for client mode) + */ + unsigned char *server_hostkey; + unsigned long server_hostkey_len; +#ifndef OPENSSL_NO_MD5 + unsigned char server_hostkey_md5[MD5_DIGEST_LENGTH]; +#endif /* ! OPENSSL_NO_MD5 */ +#ifndef OPENSSL_NO_SHA + unsigned char server_hostkey_sha1[SHA_DIGEST_LENGTH]; +#endif + + /* (remote as source of data -- packet_read ) */ + libssh2_endpoint_data remote; + + /* (local as source of data -- packet_write ) */ + libssh2_endpoint_data local; + + /* Inbound Data buffer -- Sometimes the packet that comes in isn't the packet we're ready for */ + LIBSSH2_PACKET_BRIGADE packets; + + /* Active connection channels */ + LIBSSH2_CHANNEL_BRIGADE channels; + unsigned long next_channel; + + /* Actual I/O socket */ + int socket_fd; + int socket_block; + int socket_state; + + /* Error tracking */ + char *err_msg; + unsigned long err_msglen; + int err_should_free; + int err_code; +}; + +/* libssh2 extensible ssh api, ultimately I'd like to allow loading additional methods via .so/.dll */ + +struct _LIBSSH2_KEX_METHOD { + char *name; + + /* integrity key length */ + unsigned long key_len; + + /* Key exchange, populates session->* and returns 0 on success, non-0 on error */ + int (*exchange_keys)(LIBSSH2_SESSION *session); + + long flags; +}; + +struct _LIBSSH2_HOSTKEY_METHOD { + char *name; + unsigned long hash_len; + + int (*init)(LIBSSH2_SESSION *session, unsigned char *hostkey_data, unsigned long hostkey_data_len, void **abstract); + int (*initPEM)(LIBSSH2_SESSION *session, unsigned char *privkeyfile, unsigned char *passphrase, void **abstract); + int (*sig_verify)(LIBSSH2_SESSION *session, const unsigned char *sig, unsigned long sig_len, const unsigned char *m, unsigned long m_len, void **abstract); + int (*sign)(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, const unsigned char *data, unsigned long data_len, void **abstract); + int (*signv)(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, unsigned long veccount, const struct iovec datavec[], void **abstract); + int (*encrypt)(LIBSSH2_SESSION *session, unsigned char **dst, unsigned long *dst_len, const unsigned char *src, unsigned long src_len, void **abstract); + int (*dtor)(LIBSSH2_SESSION *session, void **abstract); +}; + +/* When FLAG_EVP is set, crypt contains a pointer to an EVP_CIPHER generator and init and dtor are ignored + * Yes, I know it's a hack. + */ + +#define LIBSSH2_CRYPT_METHOD_FLAG_EVP 0x0001 + +struct _LIBSSH2_CRYPT_METHOD { + char *name; + + int blocksize; + + /* iv and key sizes (-1 for variable length) */ + int iv_len; + int secret_len; + + long flags; + + int (*init)(LIBSSH2_SESSION *session, unsigned char *iv, int *free_iv, unsigned char *secret, int *free_secret, int encrypt, void **abstract); + int (*crypt)(LIBSSH2_SESSION *session, unsigned char *block, void **abstract); + int (*dtor)(LIBSSH2_SESSION *session, void **abstract); +}; + +struct _LIBSSH2_COMP_METHOD { + char *name; + + int (*init)(LIBSSH2_SESSION *session, int compress, void **abstract); + int (*comp)(LIBSSH2_SESSION *session, int compress, unsigned char **dest, unsigned long *dest_len, unsigned long payload_limit, int *free_dest, + const unsigned char *src, unsigned long src_len, void **abstract); + int (*dtor)(LIBSSH2_SESSION *session, int compress, void **abstract); +}; + +struct _LIBSSH2_MAC_METHOD { + char *name; + + /* The length of a given MAC packet */ + int mac_len; + + /* Message Authentication Code Hashing algo */ + int (*init)(LIBSSH2_SESSION *session, unsigned char *key, int *free_key, void **abstract); + int (*hash)(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, const unsigned char *packet, unsigned long packet_len, const unsigned char *addtl, unsigned long addtl_len, void **abstract); + int (*dtor)(LIBSSH2_SESSION *session, void **abstract); +}; + +#define libssh2_error(session, errcode, errmsg, should_free) \ +{ \ + if (session->err_msg && session->err_should_free) { \ + LIBSSH2_FREE(session, session->err_msg); \ + } \ + session->err_msg = errmsg; \ + session->err_msglen = strlen(errmsg); \ + session->err_should_free = should_free; \ + session->err_code = errcode; \ +} + +#define LIBSSH2_SOCKET_UNKNOWN 1 +#define LIBSSH2_SOCKET_CONNECTED 0 +#define LIBSSH2_SOCKET_DISCONNECTED -1 + +/* Initial packet state, prior to MAC check */ +#define LIBSSH2_MAC_UNCONFIRMED 1 +/* When MAC type is "none" (proto initiation phase) all packets are deemed "confirmed" */ +#define LIBSSH2_MAC_CONFIRMED 0 +/* Something very bad is going on */ +#define LIBSSH2_MAC_INVALID -1 + +/* SSH Packet Types -- Defined by internet draft */ +/* Transport Layer */ +#define SSH_MSG_DISCONNECT 1 +#define SSH_MSG_IGNORE 2 +#define SSH_MSG_UNIMPLEMENTED 3 +#define SSH_MSG_DEBUG 4 +#define SSH_MSG_SERVICE_REQUEST 5 +#define SSH_MSG_SERVICE_ACCEPT 6 + +#define SSH_MSG_KEXINIT 20 +#define SSH_MSG_NEWKEYS 21 + +/* diffie-hellman-group1-sha1 */ +#define SSH_MSG_KEXDH_INIT 30 +#define SSH_MSG_KEXDH_REPLY 31 + +/* diffie-hellman-group-exchange-sha1 */ +#define SSH_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH_MSG_KEX_DH_GEX_GROUP 31 +#define SSH_MSG_KEX_DH_GEX_INIT 32 +#define SSH_MSG_KEX_DH_GEX_REPLY 33 + +/* User Authentication */ +#define SSH_MSG_USERAUTH_REQUEST 50 +#define SSH_MSG_USERAUTH_FAILURE 51 +#define SSH_MSG_USERAUTH_SUCCESS 52 +#define SSH_MSG_USERAUTH_BANNER 53 + +/* "public key" method */ +#define SSH_MSG_USERAUTH_PK_OK 60 +/* "password" method */ +#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + +/* Channels */ +#define SSH_MSG_GLOBAL_REQUEST 80 +#define SSH_MSG_REQUEST_SUCCESS 81 +#define SSH_MSG_REQUEST_FAILURE 82 + +#define SSH_MSG_CHANNEL_OPEN 90 +#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH_MSG_CHANNEL_DATA 94 +#define SSH_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH_MSG_CHANNEL_EOF 96 +#define SSH_MSG_CHANNEL_CLOSE 97 +#define SSH_MSG_CHANNEL_REQUEST 98 +#define SSH_MSG_CHANNEL_SUCCESS 99 +#define SSH_MSG_CHANNEL_FAILURE 100 + +void libssh2_session_shutdown(LIBSSH2_SESSION *session); + +unsigned long libssh2_ntohu32(const unsigned char *buf); +void libssh2_htonu32(unsigned char *buf, unsigned long val); + +int libssh2_packet_read(LIBSSH2_SESSION *session, int block); +int libssh2_packet_ask_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket); +#define libssh2_packet_ask(session, packet_type, data, data_len, poll_socket) \ + libssh2_packet_ask_ex((session), (packet_type), (data), (data_len), 0, NULL, 0, (poll_socket)) +int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len); +#define libssh2_packet_require(session, packet_type, data, data_len) \ + libssh2_packet_require_ex((session), (packet_type), (data), (data_len), 0, NULL, 0) +int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data, unsigned long data_len); +int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange); +LIBSSH2_CHANNEL *libssh2_channel_locate(LIBSSH2_SESSION *session, unsigned long channel_id); + +/* Let crypt.c/hostkey.c/comp.c/mac.c expose their method structs */ +LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void); +LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void); +LIBSSH2_COMP_METHOD **libssh2_comp_methods(void); +LIBSSH2_MAC_METHOD **libssh2_mac_methods(void); + +/* Language API doesn't exist yet. Just act like we've agreed on a language */ +#define libssh2_kex_agree_lang(session, endpoint, str, str_len) 0 + +#endif /* LIBSSH2_H */ diff --git a/install-sh b/install-sh new file mode 100755 index 00000000..e9de2384 --- /dev/null +++ b/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/mkinstalldirs b/mkinstalldirs new file mode 100755 index 00000000..6890839c --- /dev/null +++ b/mkinstalldirs @@ -0,0 +1,40 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Public domain + +# $Id: mkinstalldirs,v 1.1 2004/12/07 21:17:20 sarag Exp $ + +errstatus=0 + +for file +do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d + do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" + + mkdir "$pathcomp" || lasterr=$? + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 00000000..66e2c781 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,60 @@ +OBJECTS = channel.o comp.o crypt.o hostkey.o kex.o mac.o misc.o packet.o scp.o session.o userauth.o + +top_srcdir = @top_srcdir@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ +libdir = @exec_prefix@/lib +incldir = @prefix@/include + +CC = @CC@ +CFLAGS = -c @CFLAGS@ -Wall -g -I../include/ -fPIC +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ +INSTALL = @INSTALL@ + +channel.o: channel.c + $(CC) -o channel.o channel.c $(CFLAGS) $(LIBS) + +comp.o: comp.c + $(CC) -o comp.o comp.c $(CFLAGS) $(LIBS) + +crypt.o: crypt.c + $(CC) -o crypt.o crypt.c $(CFLAGS) $(LIBS) + +hostkey.o: hostkey.c + $(CC) -o hostkey.o hostkey.c $(CFLAGS) $(LIBS) + +kex.o: kex.c + $(CC) -o kex.o kex.c $(CFLAGS) $(LIBS) + +mac.o: mac.c + $(CC) -o mac.o mac.c $(CFLAGS) $(LIBS) + +misc.o: misc.c + $(CC) -o misc.o misc.c $(CFLAGS) $(LIBS) + +packet.o: packet.c + $(CC) -o packet.o packet.c $(CFLAGS) $(LIBS) + +scp.o: scp.c + $(CC) -o scp.o scp.c $(CFLAGS) $(LIBS) + +session.o: session.c + $(CC) -o session.o session.c $(CFLAGS) $(LIBS) + +userauth.o: userauth.c + $(CC) -o userauth.o userauth.c $(CFLAGS) $(LIBS) + +all: libssh2.@SHLIB_SUFFIX_NAME@ + +libssh2.@SHLIB_SUFFIX_NAME@: $(OBJECTS) + $(CC) -o libssh2.@SHLIB_SUFFIX_NAME@ $(SHLIB_LDFLAGS) $(OBJECTS) $(LIBS) $(LDFLAGS) @SHLIB_LDFLAGS@ +libssh2.a: $(OBJECTS) + rm -f libssh2.a + ar q libssh2.a $(OBJECTS) + @RANLIB@ libssh2.a +install: all + $(INSTALL) libssh2.@SHLIB_SUFFIX_NAME@ $(libdir) +clean: + rm -f *~ libssh2.a libssh2.@SHLIB_SUFFIX_NAME@ *.o + diff --git a/src/channel.c b/src/channel.c new file mode 100644 index 00000000..bd2645cb --- /dev/null +++ b/src/channel.c @@ -0,0 +1,682 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include + +/* {{{ libssh2_channel_nextid + * Determine the next channel ID we can use at our end + */ +static unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session) +{ + unsigned long id = session->next_channel; + LIBSSH2_CHANNEL *channel; + + channel = session->channels.head; + + while (channel) { + if (channel->local.id > id) { + id = channel->local.id; + } + channel = channel->next; + } + + /* This is a shortcut to avoid waiting for close packets on channels we've forgotten about, + * This *could* be a problem if we request and close 4 billion or so channels in too rapid succession + * for the remote end to respond, but the worst case scenario is that some data meant for another channel + * Gets picked up by the new one.... Pretty unlikely all told... + */ + session->next_channel = id + 1; + + return id; +} +/* }}} */ + +/* {{{ libssh2_channel_locate + * Locate a channel pointer by number + */ +LIBSSH2_CHANNEL *libssh2_channel_locate(LIBSSH2_SESSION *session, unsigned long channel_id) +{ + LIBSSH2_CHANNEL *channel = session->channels.head; + + while (channel) { + if (channel->local.id == channel_id) { + return channel; + } + channel = channel->next; + } + + return NULL; +} +/* }}} */ + +#define libssh2_channel_add(session, channel) \ +{ \ + if ((session)->channels.tail) { \ + (session)->channels.tail->next = (channel); \ + (channel)->prev = (session)->channels.tail; \ + } else { \ + (session)->channels.head = (channel); \ + (channel)->prev = NULL; \ + } \ + (channel)->next = NULL; \ + (session)->channels.tail = (channel); \ + (channel)->session = (session); \ +} + +/* {{{ libssh2_channel_open_session + * Establish a generic session channel + */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_open_ex(LIBSSH2_SESSION *session, char *channel_type, int channel_type_len, int window_size, int packet_size, + char *message, int message_len) +{ + LIBSSH2_CHANNEL *channel; + unsigned long local_channel = libssh2_channel_nextid(session); + unsigned char *s, *packet; + unsigned long packet_len = channel_type_len + message_len + 17; /* packet_type(1) + channel_type_len(4) + sender_channel(4) + + window_size(4) + packet_size(4) */ + unsigned char *data; + unsigned long data_len; + int polls = 0; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate temporary space for packet", 0); + return NULL; + } + *(s++) = SSH_MSG_CHANNEL_OPEN; + libssh2_htonu32(s, channel_type_len); s += 4; + memcpy(s, channel_type, channel_type_len); s += channel_type_len; + + libssh2_htonu32(s, local_channel); s += 4; + libssh2_htonu32(s, window_size); s += 4; + libssh2_htonu32(s, packet_size); s += 4; + + if (message && message_len) { + memcpy(s, message, message_len); s += message_len; + } + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-open request", 0); + LIBSSH2_FREE(session, packet); + return NULL; + } + + while (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED) { + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &data, &data_len, 1, packet + 5 + channel_type_len, 4, 1) == 0) { + /* YAY! You like me! */ + break; + } + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_OPEN_FAILURE, &data, &data_len, 1, packet + 5 + channel_type_len, 4, 0) == 0) { + /* But! Dear! I thought we had something! */ + + /* TODO: provide reason code and description */ + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Channel open failure", 0); + LIBSSH2_FREE(session, data); + LIBSSH2_FREE(session, packet); + return NULL; + } + usleep(LIBSSH2_SOCKET_POLL_UDELAY); + if (polls++ > LIBSSH2_SOCKET_POLL_MAXLOOPS) { + /* Give up waiting */ + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timed out waiting for response", 0); + LIBSSH2_FREE(session, packet); + return NULL; + } + } + LIBSSH2_FREE(session, packet); + + channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!channel) { + /* Play nice and close that channel that we're not going to use after all */ + data[3] = SSH_MSG_CHANNEL_CLOSE; + libssh2_packet_write(session, data + 3, 5); + + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for channel data", 0); + LIBSSH2_FREE(session, data); + + return NULL; + } + memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); + + channel->channel_type_len = channel_type_len; + channel->channel_type = LIBSSH2_ALLOC(session, channel_type_len); + if (!channel->channel_type) { + /* Play nice and close that channel that we're not going to use after all */ + data[4] = SSH_MSG_CHANNEL_CLOSE; + libssh2_packet_write(session, data + 4, 5); + + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating memory for channel type name", 0); + LIBSSH2_FREE(session, channel); + return NULL; + } + memcpy(channel->channel_type, channel_type, channel_type_len); + + /* REMEMBER: local as in locally sourced */ + channel->local.id = local_channel; + channel->local.window_size = libssh2_ntohu32(data + 9); + channel->local.window_size_initial = libssh2_ntohu32(data + 9); + channel->local.packet_size = libssh2_ntohu32(data + 13); + + channel->remote.id = libssh2_ntohu32(data + 5); + channel->remote.window_size = window_size; + channel->remote.window_size_initial = window_size; + channel->remote.packet_size = packet_size; + + LIBSSH2_FREE(session, data); + + libssh2_channel_add(session, channel); + + return channel; +} +/* }}} */ + +/* {{{ libssh2_channel_direct_tcpip_ex + * Tunnel TCP/IP connect through the SSH session to direct host/port + */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, char *host, int port, char *shost, int sport) +{ + unsigned char *message, *s; + unsigned long host_len = strlen(host), shost_len = strlen(shost); + unsigned long message_len = host_len + shost_len + 16; /* host_len(4) + port(4) + shost_len(4) + sport(4) */ + + s = message = LIBSSH2_ALLOC(session, message_len); + if (!message) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for direct-tcpip connection", 0); + return NULL; + } + libssh2_htonu32(s, host_len); s += 4; + memcpy(s, host, host_len); s += host_len; + libssh2_htonu32(s, port); s += 4; + + libssh2_htonu32(s, shost_len); s += 4; + memcpy(s, shost, shost_len); s += shost_len; + libssh2_htonu32(s, sport); s += 4; + + return libssh2_channel_open_ex(session, "direct-tcpip", sizeof("direct-tcpip") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, message, message_len); +} +/* }}} */ + +/* {{{ libssh2_channel_setenv_ex + * Set an environment variable prior to requesting a shell/program/subsystem + */ +LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varname, int varname_len, char *value, int value_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s, *packet; + unsigned long packet_len = varname_len + value_len + 21; /* packet_type(1) + channel_id(4) + request_len(4) + request(3)"env" + + want_reply(1) + varname_len(4) + value_len(4) */ + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0); + return -1; + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + libssh2_htonu32(s, channel->remote.id); s += 4; + libssh2_htonu32(s, sizeof("env") - 1); s += 4; + memcpy(s, "env", sizeof("env") - 1); s += sizeof("env") - 1; + + *(s++) = 0xFF; + + libssh2_htonu32(s, varname_len); s += 4; + memcpy(s, varname, varname_len); s += varname_len; + + libssh2_htonu32(s, value_len); s += 4; + memcpy(s, value, value_len); s += value_len; + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-request packet for setenv request", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + while (1) { + unsigned char *data; + unsigned long data_len; + unsigned char local_channel[4]; + + libssh2_htonu32(local_channel, channel->local.id); + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_SUCCESS, &data, &data_len, 1, local_channel, 4, 1) == 0) { + LIBSSH2_FREE(session, data); + return 0; + } + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_FAILURE, &data, &data_len, 1, local_channel, 4, 0) == 0) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-setenv", 0); + return -1; + } + } + + /* Never reached, just giving the compiler something to not complain about */ + return -1; +} +/* }}} */ + +/* {{{ libssh2_channel_request_pty_ex + * Duh... Request a PTY + */ +LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *term, int term_len, + char *modes, int modes_len, + int width, int height, + int width_px, int height_px) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s, *packet; + unsigned long packet_len = term_len + modes_len + 41; /* packet_type(1) + channel(4) + pty_req_len(4) + "pty_req"(7) + want_reply(1) + + term_len(4) + width(4) + height(4) + width_px(4) + height_px(4) + modes_len(4) */ + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for pty-request", 0); + return -1; + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + libssh2_htonu32(s, channel->remote.id); s += 4; + libssh2_htonu32(s, sizeof("pty-req") - 1); s += 4; + memcpy(s, "pty-req", sizeof("pty-req") - 1); s += sizeof("pty-req") - 1; + + *(s++) = 0xFF; + + libssh2_htonu32(s, term_len); s += 4; + if (term) { + memcpy(s, term, term_len); s += term_len; + } + + libssh2_htonu32(s, width); s += 4; + libssh2_htonu32(s, height); s += 4; + libssh2_htonu32(s, width_px); s += 4; + libssh2_htonu32(s, height_px); s += 4; + + libssh2_htonu32(s, modes_len); s += 4; + if (modes) { + memcpy(s, modes, modes_len); s += modes_len; + } + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send pty-request packet", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + + while (1) { + unsigned char *data; + unsigned long data_len; + unsigned char local_channel[4]; + + libssh2_htonu32(local_channel, channel->local.id); + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_SUCCESS, &data, &data_len, 1, local_channel, 4, 1) == 0) { + LIBSSH2_FREE(session, data); + return 0; + } + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_FAILURE, &data, &data_len, 1, local_channel, 4, 1) == 0) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel request-pty", 0); + return -1; + } + } + + /* Never reached, just giving the compiler something to not complain about */ + return -1; +} +/* }}} */ + +/* {{{ libssh2_channel_process_startup + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, char *request, int request_len, char *message, int message_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s, *packet; + unsigned long packet_len = request_len + 10; /* packet_type(1) + channel(4) + request_len(4) + want_reply(1) */ + + if (message) { + packet_len += message_len + 4; + } + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for channel-process request", 0); + return -1; + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + libssh2_htonu32(s, channel->remote.id); s += 4; + libssh2_htonu32(s, request_len); s += 4; + memcpy(s, request, request_len); s += request_len; + + *(s++) = 0xFF; + + if (message) { + libssh2_htonu32(s, message_len); s += 4; + memcpy(s, message, message_len); s += message_len; + } + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel request", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + while (1) { + unsigned char *data; + unsigned long data_len; + unsigned char local_channel[4]; + + libssh2_htonu32(local_channel, channel->local.id); + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_SUCCESS, &data, &data_len, 1, local_channel, 4, 1) == 0) { + LIBSSH2_FREE(session, data); + return 0; + } + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_FAILURE, &data, &data_len, 1, local_channel, 4, 0) == 0) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-process-startup", 0); + return -1; + } + } + + /* Never reached, just giving the compiler something to not complain about */ + return -1; +} +/* }}} */ + +/* {{{ libssh2_channel_set_blocking + * Set a channel's blocking mode on or off, similar to a socket's fcntl(fd, F_SETFL, O_NONBLOCK); type command + */ +LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, int blocking) +{ + channel->blocking = blocking; +} +/* }}} */ + +/* {{{ libssh2_channel_read_ex + * Read data from a channel + */ +LIBSSH2_API int libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen) +{ + LIBSSH2_SESSION *session = channel->session; + int bytes_read = 0, blocking_read = 0; + + do { + LIBSSH2_PACKET *packet = session->packets.head; + + /* Process any waiting packets */ + while (libssh2_packet_read(session, blocking_read) > 0) blocking_read = 0; + + while (packet && (bytes_read < buflen)) { + /* In case packet gets destroyed during this iteration */ + LIBSSH2_PACKET *next = packet->next; + + if ((stream_id && (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1))) || + (!stream_id && (packet->data[0] == SSH_MSG_CHANNEL_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1)))) { + int want = buflen - bytes_read; + int unlink_packet = 0; + + if (want >= (packet->data_len - packet->data_head)) { + want = packet->data_len - packet->data_head; + unlink_packet = 1; + } + + memcpy(buf + bytes_read, packet->data + packet->data_head, want); + packet->data_head += want; + bytes_read += want; + + if (unlink_packet) { + unsigned char adjust[9]; /* packet_type(1) + channel(4) + adjustment(4) */ + + if (packet->prev) { + packet->prev->next = packet->next; + } else { + session->packets.head = packet->next; + } + if (packet->next) { + packet->next->prev = packet->prev; + } else { + session->packets.tail = packet->prev; + } + LIBSSH2_FREE(session, packet->data); + + /* Adjust the window based on the block we just freed */ + adjust[0] = SSH_MSG_CHANNEL_WINDOW_ADJUST; + libssh2_htonu32(adjust + 1, channel->remote.id); + libssh2_htonu32(adjust + 5, packet->data_len - (stream_id ? 13 : 9)); + + if (libssh2_packet_write(session, adjust, 9)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send transfer-window adjustment packet", 0); + } + + LIBSSH2_FREE(session, packet); + } + } + packet = next; + } + blocking_read = 1; + } while (channel->blocking && (bytes_read == 0) && !channel->remote.close); + + if (channel->blocking && (bytes_read == 0)) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "Remote end has closed this channel", 0); + } + + return bytes_read; +} +/* }}} */ + +/* {{{ libssh2_channel_write_ex + * Send data to a channel + */ +LIBSSH2_API int libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *packet, *s; + unsigned long packet_len; + + if (channel->local.close) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "We've already closed this channel", 0); + return -1; + } + + if (channel->local.eof) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_EOF_SENT, "EOF has already been sight, data might be ignored", 0); + } + + if (channel->blocking && channel->local.window_size_initial && (channel->local.window_size <= 0)) { + /* twiddle our thumbs until there's window space available */ + if (libssh2_packet_read(session, 1) < 0) { + /* Error occured, disconnect? */ + return 0; + } + } + + packet_len = buflen + (stream_id ? 13 : 9); /* packet_type(1) + channelno(4) [ + streamid(4) ] + buflen(4) */ + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocte space for data transmission packet", 0); + return -1; + } + + *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : SSH_MSG_CHANNEL_DATA; + libssh2_htonu32(s, channel->remote.id); s += 4; + if (stream_id) { + libssh2_htonu32(s, stream_id); s += 4; + } + + /* Don't exceed the remote end's limits */ + /* REMEMBER local means local as the SOURCE of the data */ + if (channel->local.window_size_initial && (buflen > channel->local.window_size)) { + buflen = channel->local.window_size; + } + if (buflen > channel->local.packet_size) { + buflen = channel->local.packet_size; + } + libssh2_htonu32(s, buflen); s += 4; + memcpy(s, buf, buflen); s += buflen; + + if (libssh2_packet_write(session, packet, s - packet)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel data", 0); + return -1; + } + /* Shrink local window size */ + channel->local.window_size -= buflen; + + LIBSSH2_FREE(session, packet); + + return buflen; +} +/* }}} */ + +/* {{{ libssh2_channel_send_eof + * Send EOF on channel + */ +LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char packet[5]; /* packet_type(1) + channelno(4) */ + + packet[0] = SSH_MSG_CHANNEL_EOF; + libssh2_htonu32(packet + 1, channel->remote.id); + if (libssh2_packet_write(session, packet, 5)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send EOF on channel", 0); + return -1; + } + channel->local.eof = 1; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_channel_eof + * Read channel's eof status + */ +LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_PACKET *packet = session->packets.head; + + while (packet) { + if (((packet->data[0] == SSH_MSG_CHANNEL_DATA) || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) && + (channel->local.id == libssh2_ntohu32(packet->data + 1))) { + /* There's data waiting to be read yet, mask the EOF status */ + return 0; + } + packet = packet->next; + } + + return channel->remote.eof; +} +/* }}} */ + +/* {{{ libssh2_channel_close + * Close a channel + */ +LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char packet[5]; + + if (channel->local.close) { + /* Already closed, act like we sent another close, even though we didn't... shhhhhh */ + return 0; + } + + packet[0] = SSH_MSG_CHANNEL_CLOSE; + libssh2_htonu32(packet + 1, channel->remote.id); + if (libssh2_packet_write(session, packet, 5)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send close-channel request", 0); + return -1; + } + channel->local.close = 1; + + /* TODO: Wait up to a timeout value for a CHANNEL_CLOSE to come back, to avoid the problem alluded to in channel_nextid */ + + return 0; +} +/* }}} */ + +/* {{{ libssh2_channel_free + * Make sure a channel is closed, then remove the channel from the session and free its resource(s) + */ +LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char channel_id[4], *data; + unsigned long data_len; + + /* Allow channel freeing even when the socket has lost its connection */ + if (!channel->local.close && (session->socket_state == LIBSSH2_SOCKET_CONNECTED) && + libssh2_channel_close(channel)) { + return -1; + } + + /* channel->remote.close *might* not be set yet, Well... + * We've sent the close packet, what more do you want? + * Just let packet_add ignore it when it finally arrives + */ + + /* Clear out packets meant for this channel */ + libssh2_htonu32(channel_id, channel->local.id); + while ((libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0) || + (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0)) { + LIBSSH2_FREE(session, data); + } + + /* Unlink from channel brigade */ + if (channel->prev) { + channel->prev->next = channel->next; + } else { + session->channels.head = channel->next; + } + if (channel->next) { + channel->next->prev = channel->prev; + } else { + session->channels.tail = channel->prev; + } + + LIBSSH2_FREE(session, channel); + + return 0; +} +/* }}} */ diff --git a/src/comp.c b/src/comp.c new file mode 100644 index 00000000..6afc7383 --- /dev/null +++ b/src/comp.c @@ -0,0 +1,249 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include + +/* ******** + * none * + ******** */ + +/* {{{ libssh2_comp_method_none_comp + * Minimalist compression: Absolutely none + */ +static int libssh2_comp_method_none_comp(LIBSSH2_SESSION *session, int compress, + unsigned char **dest, unsigned long *dest_len, unsigned long payload_limit, int *free_dest, + const unsigned char *src, unsigned long src_len, void **abstract) +{ + *dest = (unsigned char *)src; + *dest_len = src_len; + + *free_dest = 0; + + return 0; +} +/* }}} */ + +static LIBSSH2_COMP_METHOD libssh2_comp_method_none = { + "none", + NULL, + libssh2_comp_method_none_comp, + NULL +}; + +#ifdef LIBSSH2_HAVE_ZLIB +/* ******** + * zlib * + ******** */ + +/* {{{ Memory management wrappers + * Yes, I realize we're doing a callback to a callback, + * Deal... + */ + +static voidpf libssh2_comp_method_zlib_alloc(voidpf opaque, uInt items, uInt size) +{ + LIBSSH2_SESSION *session = (LIBSSH2_SESSION*)opaque; + + return (voidpf)LIBSSH2_ALLOC(session, items * size); +} + +static void libssh2_comp_method_zlib_free(voidpf opaque, voidpf address) +{ + LIBSSH2_SESSION *session = (LIBSSH2_SESSION*)opaque; + + LIBSSH2_FREE(session, address); +} +/* }}} */ + +/* {{{ libssh2_comp_method_zlib_init + * All your bandwidth are belong to us (so save some) + */ +static int libssh2_comp_method_zlib_init(LIBSSH2_SESSION *session, int compress, void **abstract) +{ + z_stream *strm; + int status; + + strm = LIBSSH2_ALLOC(session, sizeof(z_stream)); + if (!strm) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for zlib compression/decompression", 0); + return -1; + } + memset(strm, 0, sizeof(z_stream)); + + strm->opaque = (voidpf)session; + strm->zalloc = (alloc_func)libssh2_comp_method_zlib_alloc; + strm->zfree = (free_func)libssh2_comp_method_zlib_free; + if (compress) { + /* deflate */ + status = deflateInit(strm, Z_DEFAULT_COMPRESSION); + } else { + /* inflate */ + status = inflateInit(strm); + } + + if (status != Z_OK) { + LIBSSH2_FREE(session, strm); + return -1; + } + *abstract = strm; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_comp_method_zlib_comp + * zlib, a compression standard for all occasions + */ +static int libssh2_comp_method_zlib_comp(LIBSSH2_SESSION *session, int compress, + unsigned char **dest, unsigned long *dest_len, unsigned long payload_limit, int *free_dest, + const unsigned char *src, unsigned long src_len, void **abstract) +{ + z_stream *strm = *abstract; + /* A short-term alloc of a full data chunk is better than a series of reallocs */ + char *out; + int out_maxlen = compress ? src_len : (2 * src_len); + int limiter = 0; + + /* In practice they never come smaller than this */ + if (out_maxlen < 21) { + out_maxlen = 21; + } + + if (out_maxlen > payload_limit) { + out_maxlen = payload_limit; + } + + strm->next_in = (char *)src; + strm->avail_in = src_len; + out = strm->next_out = LIBSSH2_ALLOC(session, out_maxlen); + strm->avail_out = out_maxlen; + if (!strm->next_out) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate compression/decompression buffer", 0); + return -1; + } + while (strm->avail_in) { + int status; + + if (compress) { + status = deflate(strm, Z_PARTIAL_FLUSH); + } else { + status = inflate(strm, Z_PARTIAL_FLUSH); + } + if (status != Z_OK) { + libssh2_error(session, LIBSSH2_ERROR_ZLIB, "compress/decompression failure", 0); + LIBSSH2_FREE(session, strm->next_out); + return -1; + } + if (strm->avail_in) { + unsigned long out_ofs = out_maxlen - strm->avail_out; + + out_maxlen += compress ? strm->avail_in : (2 * strm->avail_in); + + if ((out_maxlen > payload_limit) && !compress && limiter++) { + libssh2_error(session, LIBSSH2_ERROR_ZLIB, "Excessive growth in decompression phase", 0); + LIBSSH2_FREE(session, strm->next_out); + return -1; + } + + out = LIBSSH2_REALLOC(session, out, out_maxlen); + if (!out) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to expand compress/decompression buffer", 0); + return -1; + } + strm->next_out = out + out_ofs; + strm->avail_out += compress ? strm->avail_in : (2 * strm->avail_in); + } + } + + *dest = out; + *dest_len = out_maxlen - strm->avail_out; + *free_dest = 1; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_comp_method_zlib_dtor + * All done, no more compression for you + */ +static int libssh2_comp_method_zlib_dtor(LIBSSH2_SESSION *session, int compress, void **abstract) +{ + z_stream *strm = *abstract; + + if (strm) { + if (compress) { + /* deflate */ + deflateEnd(strm); + } else { + /* inflate */ + inflateEnd(strm); + } + + LIBSSH2_FREE(session, strm); + } + + *abstract = NULL; + + return 0; +} +/* }}} */ + +static LIBSSH2_COMP_METHOD libssh2_comp_method_zlib = { + "zlib", + libssh2_comp_method_zlib_init, + libssh2_comp_method_zlib_comp, + libssh2_comp_method_zlib_dtor, +}; +#endif /* LIBSSH2_HAVE_ZLIB */ + +/* *********************** + * Compression Methods * + *********************** */ + +static LIBSSH2_COMP_METHOD *_libssh2_comp_methods[] = { +#ifdef LIBSSH2_HAVE_ZLIB + &libssh2_comp_method_zlib, +#endif /* LIBSSH2_HAVE_ZLIB */ + &libssh2_comp_method_none, + NULL +}; + +LIBSSH2_COMP_METHOD **libssh2_comp_methods(void) { + return _libssh2_comp_methods; +} + diff --git a/src/crypt.c b/src/crypt.c new file mode 100644 index 00000000..1ac9cc41 --- /dev/null +++ b/src/crypt.c @@ -0,0 +1,189 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include + +#ifdef LIBSSH2_CRYPT_NONE +/* {{{ libssh2_crypt_none_crypt + * Minimalist cipher: VERY secure *wink* + */ +static int libssh2_crypt_none_crypt(LIBSSH2_SESSION *session, unsigned char *buf, void **abstract) +{ + /* Do nothing to the data! */ + return 0; +} +/* }}} */ + +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_none = { + "none", + 8, /* blocksize (SSH2 defines minimum blocksize as 8) */ + 0, /* iv_len */ + 0, /* secret_len */ + 0, /* flags */ + NULL, + libssh2_crypt_none_crypt, + NULL +}; +#endif + +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_3des_cbc = { + "3des-cbc", + 8, /* blocksize */ + 8, /* initial value length */ + 24, /* secret length */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_des_ede3_cbc, + NULL, +}; + +#if OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES) +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_cbc = { + "aes128-cbc", + 16, /* blocksize */ + 16, /* initial value length */ + 16, /* secret length -- 16*8 == 128bit */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_aes_128_cbc, + NULL, +}; + +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes192_cbc = { + "aes192-cbc", + 16, /* blocksize */ + 16, /* initial value length */ + 24, /* secret length -- 24*8 == 192bit */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_aes_192_cbc, + NULL, +}; + +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_cbc = { + "aes256-cbc", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_aes_256_cbc, + NULL, +}; + +/* rijndael-cbc@lysator.liu.se == aes256-cbc */ +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_rijndael_cbc_lysator_liu_se = { + "rijndael-cbc@lysator.liu.se", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_aes_256_cbc, + NULL, +}; +#endif /* OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES)*/ + +#ifndef OPENSSL_NO_BLOWFISH +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_blowfish_cbc = { + "blowfish-cbc", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_bf_cbc, + NULL, +}; +#endif /* ! OPENSSL_NO_BLOWFISH */ + +#ifndef OPENSSL_NO_CAST +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_cast128_cbc = { + "cast128-cbc", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_cast5_cbc, + NULL, +}; +#endif /* ! OPENSSL_NO_CAST */ + +#ifndef OPENSSL_NO_RC4 +static LIBSSH2_CRYPT_METHOD libssh2_crypt_method_arcfour = { + "arcfour", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + LIBSSH2_CRYPT_METHOD_FLAG_EVP, + NULL, + (void*)EVP_rc4, + NULL, +}; +#endif /* ! OPENSSL_NO_RC4 */ + +static LIBSSH2_CRYPT_METHOD *_libssh2_crypt_methods[] = { +#if OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES) + &libssh2_crypt_method_aes256_cbc, + &libssh2_crypt_method_rijndael_cbc_lysator_liu_se, /* == aes256-cbc */ + &libssh2_crypt_method_aes192_cbc, + &libssh2_crypt_method_aes128_cbc, +#endif /* OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES) */ +#ifndef OPENSSL_NO_BLOWFISH + &libssh2_crypt_method_blowfish_cbc, +#endif /* ! OPENSSL_NO_BLOWFISH */ +#ifndef OPENSSL_NO_RC4 + &libssh2_crypt_method_arcfour, +#endif /* ! OPENSSL_NO_RC4 */ +#ifndef OPENSSL_NO_CAST + &libssh2_crypt_method_cast128_cbc, +#endif /* ! OPENSSL_NO_CAST */ +#ifndef OPENSSL_NO_DES + &libssh2_crypt_method_3des_cbc, +#endif /* ! OPENSSL_NO_DES */ +#ifdef LIBSSH2_CRYPT_NONE + &libssh2_crypt_method_none, +#endif + NULL +}; + +/* Expose to kex.c */ +LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void) { + return _libssh2_crypt_methods; +} diff --git a/src/hostkey.c b/src/hostkey.c new file mode 100644 index 00000000..179d54cf --- /dev/null +++ b/src/hostkey.c @@ -0,0 +1,509 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include +#include + +#ifndef OPENSSL_NO_RSA +/* *********** + * ssh-rsa * + *********** */ + +static int libssh2_hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION *session, void **abstract); + +/* {{{ libssh2_hostkey_method_ssh_rsa_init + * Initialize the server hostkey working area with e/n pair + */ +static int libssh2_hostkey_method_ssh_rsa_init(LIBSSH2_SESSION *session, unsigned char *hostkey_data, unsigned long hostkey_data_len, void **abstract) +{ + RSA *rsactx; + unsigned char *s, *e, *n; + unsigned long len, e_len, n_len; + + if (*abstract) { + libssh2_hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + s = hostkey_data; + len = libssh2_ntohu32(s); s += 4; + if (len != 7 || strncmp(s, "ssh-rsa", 7) != 0) { + return -1; + } s += 7; + + e_len = libssh2_ntohu32(s); s += 4; + e = s; s += e_len; + n_len = libssh2_ntohu32(s); s += 4; + n = s; s += n_len; + + rsactx = RSA_new(); + rsactx->e = BN_new(); + BN_bin2bn(e, e_len, rsactx->e); + rsactx->n = BN_new(); + BN_bin2bn(n, n_len, rsactx->n); + + *abstract = rsactx; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_rsa_passphrase_cb + * TODO: Optionally call a passphrase callback specified by the calling program + */ +static int libssh2_hostkey_method_ssh_rsadsa_passphrase_cb(char *buf, int size, int rwflag, char *passphrase){ + int passphrase_len = strlen(passphrase); + + if (passphrase_len > (size - 1)) { + passphrase_len = size - 1; + } + memcpy(buf, passphrase, passphrase_len); + buf[passphrase_len] = '\0'; + + return passphrase_len; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_rsa_initPEM + * Load a Private Key from a PEM file + */ +static int libssh2_hostkey_method_ssh_rsa_initPEM(LIBSSH2_SESSION *session, unsigned char *privkeyfile, unsigned char *passphrase, void **abstract) +{ + RSA *rsactx; + FILE *fp; + + if (*abstract) { + libssh2_hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + fp = fopen(privkeyfile, "r"); + if (!fp) { + return -1; + } + rsactx = PEM_read_RSAPrivateKey(fp, NULL, (void*)libssh2_hostkey_method_ssh_rsadsa_passphrase_cb, passphrase); + if (!rsactx) { + fclose(fp); + return -1; + } + fclose(fp); + + *abstract = rsactx; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_rsa_sign + * Verify signature created by remote + */ +static int libssh2_hostkey_method_ssh_rsa_sig_verify(LIBSSH2_SESSION *session, const unsigned char *sig, unsigned long sig_len, + const unsigned char *m, unsigned long m_len, void **abstract) +{ + RSA *rsactx = (RSA*)(*abstract); + unsigned char hash[SHA_DIGEST_LENGTH]; + int ret; + + /* Skip past keyname_len(4) + keyname(7){"ssh-rsa"} + signature_len(4) */ + sig += 15; sig_len -= 15; + SHA1(m, m_len, hash); + ret = RSA_verify(NID_sha1, hash, SHA_DIGEST_LENGTH, (char *)sig, sig_len, rsactx); + + return (ret == 1) ? 0 : -1; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_rsa_sign + * Sign data to send to remote + */ +static int libssh2_hostkey_method_ssh_rsa_sign(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, + const unsigned char *buf, unsigned long buf_len, void **abstract) +{ + RSA *rsactx = (RSA*)(*abstract); + int ret; + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA_CTX ctx; + char *sig; + int sig_len; + + sig_len = RSA_size(rsactx); + sig = LIBSSH2_ALLOC(session, sig_len); + + if (!sig) { + return -1; + } + + SHA1_Init(&ctx); + SHA1_Update(&ctx, buf, buf_len); + SHA1_Final(hash, &ctx); + + ret = RSA_sign(NID_sha1, hash, SHA_DIGEST_LENGTH, sig, &sig_len, rsactx); + if (!ret) { + LIBSSH2_FREE(session, sig); + return -1; + } + + *signature = sig; + *signature_len = sig_len; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_rsa_signv + * Construct a signature from an array of vectors + */ +static int libssh2_hostkey_method_ssh_rsa_signv(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, + unsigned long veccount, const struct iovec datavec[], void **abstract) +{ + RSA *rsactx = (RSA*)(*abstract); + int ret, i; + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA_CTX ctx; + char *sig; + int sig_len; + + sig_len = RSA_size(rsactx); + sig = LIBSSH2_ALLOC(session, sig_len); + + if (!sig) { + return -1; + } + + SHA1_Init(&ctx); + for(i = 0; i < veccount; i++) { + SHA1_Update(&ctx, datavec[i].iov_base, datavec[i].iov_len); + } + SHA1_Final(hash, &ctx); + + ret = RSA_sign(NID_sha1, hash, SHA_DIGEST_LENGTH, sig, &sig_len, rsactx); + + if (!ret) { + LIBSSH2_FREE(session, sig); + return -1; + } + + *signature = sig; + *signature_len = sig_len; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_rsa_dtor + * Shutdown the hostkey + */ +static int libssh2_hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION *session, void **abstract) +{ + RSA *rsactx = (RSA*)(*abstract); + + RSA_free(rsactx); + + *abstract = NULL; + + return 0; +} +/* }}} */ + +static LIBSSH2_HOSTKEY_METHOD libssh2_hostkey_method_ssh_rsa = { + "ssh-rsa", + MD5_DIGEST_LENGTH, + libssh2_hostkey_method_ssh_rsa_init, + libssh2_hostkey_method_ssh_rsa_initPEM, + libssh2_hostkey_method_ssh_rsa_sig_verify, + libssh2_hostkey_method_ssh_rsa_sign, + libssh2_hostkey_method_ssh_rsa_signv, + NULL, /* encrypt */ + libssh2_hostkey_method_ssh_rsa_dtor, +}; +#endif /* ! OPENSSL_NO_RSA */ + +#ifndef OPENSSL_NO_DSA +/* *********** + * ssh-dss * + *********** */ + +static int libssh2_hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION *session, void **abstract); + +/* {{{ libssh2_hostkey_method_ssh_dss_init + * Initialize the server hostkey working area with p/q/g/y set + */ +static int libssh2_hostkey_method_ssh_dss_init(LIBSSH2_SESSION *session, unsigned char *hostkey_data, unsigned long hostkey_data_len, void **abstract) +{ + DSA *dsactx; + unsigned char *p, *q, *g, *y, *s; + unsigned long p_len, q_len, g_len, y_len, len; + + if (*abstract) { + libssh2_hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + s = hostkey_data; + len = libssh2_ntohu32(s); s += 4; + if (len != 7 || strncmp(s, "ssh-dss", 7) != 0) { + return -1; + } s += 7; + + p_len = libssh2_ntohu32(s); s += 4; + p = s; s += p_len; + q_len = libssh2_ntohu32(s); s += 4; + q = s; s += q_len; + g_len = libssh2_ntohu32(s); s += 4; + g = s; s += g_len; + y_len = libssh2_ntohu32(s); s += 4; + y = s; s += y_len; + + dsactx = DSA_new(); + dsactx->p = BN_new(); + BN_bin2bn(p, p_len, dsactx->p); + dsactx->q = BN_new(); + BN_bin2bn(q, q_len, dsactx->q); + dsactx->g = BN_new(); + BN_bin2bn(g, g_len, dsactx->g); + dsactx->pub_key = BN_new(); + BN_bin2bn(y, y_len, dsactx->pub_key); + + *abstract = dsactx; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_dss_initPEM + * Load a Private Key from a PEM file + */ +static int libssh2_hostkey_method_ssh_dss_initPEM(LIBSSH2_SESSION *session, unsigned char *privkeyfile, unsigned char *passphrase, void **abstract) +{ + DSA *dsactx; + FILE *fp; + + if (*abstract) { + libssh2_hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + fp = fopen(privkeyfile, "r"); + if (!fp) { + return -1; + } + dsactx = PEM_read_DSAPrivateKey(fp, NULL, (void*)libssh2_hostkey_method_ssh_rsadsa_passphrase_cb, passphrase); + if (!dsactx) { + fclose(fp); + return -1; + } + fclose(fp); + + *abstract = dsactx; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_dss_sign + * Verify signature created by remote + */ +static int libssh2_hostkey_method_ssh_dss_sig_verify(LIBSSH2_SESSION *session, const unsigned char *sig, unsigned long sig_len, + const unsigned char *m, unsigned long m_len, void **abstract) +{ + DSA *dsactx = (DSA*)(*abstract); + unsigned char hash[SHA_DIGEST_LENGTH]; + DSA_SIG dsasig; + int ret; + + /* Skip past keyname_len(4) + keyname(7){"ssh-dss"} + signature_len(4) */ + sig += 15; sig_len -= 15; + if (sig_len != 40) { + libssh2_error(session, LIBSSH2_ERROR_PROTO, "Invalid DSS signature length", 0); + return -1; + } + dsasig.r = BN_new(); + BN_bin2bn(sig, 20, dsasig.r); + dsasig.s = BN_new(); + BN_bin2bn(sig + 20, 20, dsasig.s); + + SHA1(m, m_len, hash); + ret = DSA_do_verify(hash, SHA_DIGEST_LENGTH, &dsasig, dsactx); + + return (ret == 1) ? 0 : -1; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_dss_sign + * Sign data to send to remote + */ +static int libssh2_hostkey_method_ssh_dss_sign(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, + const unsigned char *buf, unsigned long buf_len, void **abstract) +{ + DSA *dsactx = (DSA*)(*abstract); + int ret; + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA_CTX ctx; + char *sig; + int sig_len; + + sig_len = DSA_size(dsactx); + sig = LIBSSH2_ALLOC(session, sig_len); + + if (!sig) { + return -1; + } + + SHA1_Init(&ctx); + SHA1_Update(&ctx, buf, buf_len); + SHA1_Final(hash, &ctx); + + ret = DSA_sign(NID_sha1, hash, SHA_DIGEST_LENGTH, sig, &sig_len, dsactx); + if (!ret) { + LIBSSH2_FREE(session, sig); + return -1; + } + + *signature = sig; + *signature_len = sig_len; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_dss_signv + * Construct a signature from an array of vectors + */ +static int libssh2_hostkey_method_ssh_dss_signv(LIBSSH2_SESSION *session, unsigned char **signature, unsigned long *signature_len, + unsigned long veccount, const struct iovec datavec[], void **abstract) +{ + DSA *dsactx = (DSA*)(*abstract); + int ret, i; + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA_CTX ctx; + char *sig; + int sig_len; + + sig_len = DSA_size(dsactx); + sig = LIBSSH2_ALLOC(session, sig_len); + + if (!sig) { + return -1; + } + + SHA1_Init(&ctx); + for(i = 0; i < veccount; i++) { + SHA1_Update(&ctx, datavec[i].iov_base, datavec[i].iov_len); + } + SHA1_Final(hash, &ctx); + + ret = DSA_sign(NID_sha1, hash, SHA_DIGEST_LENGTH, sig, &sig_len, dsactx); + + if (!ret) { + LIBSSH2_FREE(session, sig); + return -1; + } + + *signature = sig; + *signature_len = sig_len; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_hostkey_method_ssh_dss_dtor + * Shutdown the hostkey method + */ +static int libssh2_hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION *session, void **abstract) +{ + DSA *dsactx = (DSA*)(*abstract); + + DSA_free(dsactx); + + *abstract = NULL; + + return 0; +} +/* }}} */ + +static LIBSSH2_HOSTKEY_METHOD libssh2_hostkey_method_ssh_dss = { + "ssh-dss", + MD5_DIGEST_LENGTH, + libssh2_hostkey_method_ssh_dss_init, + libssh2_hostkey_method_ssh_dss_initPEM, + libssh2_hostkey_method_ssh_dss_sig_verify, + libssh2_hostkey_method_ssh_dss_sign, + libssh2_hostkey_method_ssh_dss_signv, + NULL, /* encrypt */ + libssh2_hostkey_method_ssh_dss_dtor, +}; +#endif /* ! OPENSSL_NO_DSA */ + +static LIBSSH2_HOSTKEY_METHOD *_libssh2_hostkey_methods[] = { +#ifndef OPENSSL_NO_RSA + &libssh2_hostkey_method_ssh_rsa, +#endif /* ! OPENSSL_NO_RSA */ +#ifndef OPENSSL_NO_DSA + &libssh2_hostkey_method_ssh_dss, +#endif /* ! OPENSSL_NO_DSA */ + NULL +}; + +LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void) +{ + return _libssh2_hostkey_methods; +} + +/* {{{ libssh2_hostkey_hash + * Returns NULL terminated hash signature + */ +LIBSSH2_API char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type) +{ + switch (hash_type) { +#ifndef OPENSSL_NO_MD5 + case LIBSSH2_HOSTKEY_HASH_MD5: + return session->server_hostkey_md5; + break; +#endif /* ! OPENSSL_NO_MD5 */ +#ifndef OPENSSL_NO_SHA + case LIBSSH2_HOSTKEY_HASH_SHA1: + return session->server_hostkey_sha1; + break; +#endif /* ! OPENSSL_NO_SHA */ + default: + return NULL; + } +} +/* }}} */ + + diff --git a/src/kex.c b/src/kex.c new file mode 100644 index 00000000..f0bccd4b --- /dev/null +++ b/src/kex.c @@ -0,0 +1,1056 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include +#include +#include + +/* TODO: Switch this to an inline and handle alloc() failures */ +/* Helper macro called from libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange */ +#define LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(value, reqlen, version) \ +{ \ + SHA_CTX hash; \ + unsigned long len = 0; \ + if (!(value)) { \ + value = LIBSSH2_ALLOC(session, reqlen + SHA_DIGEST_LENGTH); \ + } \ + while (len < reqlen) { \ + SHA1_Init(&hash); \ + SHA1_Update(&hash, k_value, k_value_len); \ + SHA1_Update(&hash, h_sig_comp, SHA_DIGEST_LENGTH); \ + if (len > 0) { \ + SHA1_Update(&hash, value, len); \ + } else { \ + SHA1_Update(&hash, (version), 1); \ + SHA1_Update(&hash, session->session_id, session->session_id_len); \ + } \ + SHA1_Final((value) + len, &hash); \ + len += SHA_DIGEST_LENGTH; \ + } \ +} + +/* {{{ libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange + * Diffie Hellman Key Exchange, Group Agnostic + */ +static int libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(LIBSSH2_SESSION *session, BIGNUM *g, BIGNUM *p, + unsigned char packet_type_init, unsigned char packet_type_reply, + unsigned char *midhash, unsigned long midhash_len) +{ + unsigned char *e_packet = NULL, *s_packet = NULL, *tmp, h_sig_comp[SHA_DIGEST_LENGTH], c; + unsigned long e_packet_len, s_packet_len, tmp_len; + int ret = 0; + BN_CTX *ctx = BN_CTX_new(); + BIGNUM *x = BN_new(); /* Random from client */ + BIGNUM *e = BN_new(); /* g^x mod p */ + BIGNUM *f = BN_new(); /* g^(Random from server) mod p */ + BIGNUM *k = BN_new(); /* The shared secret: f^x mod p */ + unsigned char *s, *f_value, *k_value = NULL, *h_sig; + unsigned long f_value_len, k_value_len, h_sig_len; + SHA_CTX exchange_hash; + + /* Generate x and e */ + BN_rand(x, 128, 0, -1); + BN_mod_exp(e, g, x, p, ctx); + + /* Send KEX init */ + e_packet_len = BN_num_bytes(e) + 6; /* packet_type(1) + String Length(4) + leading 0(1) */ + if (BN_num_bits(e) % 8) { + /* Leading 00 not needed */ + e_packet_len--; + } + e_packet = LIBSSH2_ALLOC(session, e_packet_len); + if (!e_packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Out of memory error", 0); + ret = -1; + goto clean_exit; + } + e_packet[0] = packet_type_init; + libssh2_htonu32(e_packet + 1, e_packet_len - 5); + if (BN_num_bits(e) % 8) { + BN_bn2bin(e, e_packet + 5); + } else { + e_packet[5] = 0; + BN_bn2bin(e, e_packet + 6); + } + + if (libssh2_packet_write(session, e_packet, e_packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send KEX init message", 0); + ret = -11; + goto clean_exit; + } + + /* Wait for KEX reply */ + if (libssh2_packet_require(session, packet_type_reply, &s_packet, &s_packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timed out waiting for KEX reply", 0); + ret = -1; + goto clean_exit; + } + + /* Parse KEXDH_REPLY */ + s = s_packet + 1; + + session->server_hostkey_len = libssh2_ntohu32(s); s += 4; + session->server_hostkey = LIBSSH2_ALLOC(session, session->server_hostkey_len); + if (!session->server_hostkey) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for a copy of the host key", 0); + ret = -1; + goto clean_exit; + } + memcpy(session->server_hostkey, s, session->server_hostkey_len); + s += session->server_hostkey_len; + +#ifndef OPENSSL_NO_MD5 +{ + MD5_CTX fingerprint_ctx; + + MD5_Init(&fingerprint_ctx); + MD5_Update(&fingerprint_ctx, session->server_hostkey, session->server_hostkey_len); + MD5_Final(session->server_hostkey_md5, &fingerprint_ctx); +} +#endif + +#ifndef OPENSSL_NO_SHA +{ + SHA_CTX fingerprint_ctx; + + SHA1_Init(&fingerprint_ctx); + SHA1_Update(&fingerprint_ctx, session->server_hostkey, session->server_hostkey_len); + SHA1_Final(session->server_hostkey_sha1, &fingerprint_ctx); +} +#endif + + if (session->hostkey->init(session, session->server_hostkey, session->server_hostkey_len, &session->server_hostkey_abstract)) { + libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unable to initialize hostkey importer", 0); + ret = -1; + goto clean_exit; + } + + + f_value_len = libssh2_ntohu32(s); s += 4; + f_value = s; s += f_value_len; + BN_bin2bn(f_value, f_value_len, f); + + h_sig_len = libssh2_ntohu32(s); s += 4; + h_sig = s; + + /* Compute the shared secret */ + BN_mod_exp(k, f, x, p, ctx); + k_value_len = BN_num_bytes(k) + 5; + if (BN_num_bits(k) % 8) { + /* don't need leading 00 */ + k_value_len--; + } + k_value = LIBSSH2_ALLOC(session, k_value_len); + if (!k_value) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for K", 0); + ret = -1; + goto clean_exit; + } + libssh2_htonu32(k_value, k_value_len - 4); + if (BN_num_bits(k) % 8) { + BN_bn2bin(k, k_value + 4); + } else { + k_value[4] = 0; + BN_bn2bin(k, k_value + 5); + } + + SHA1_Init(&exchange_hash); + if (session->local.banner) { + libssh2_htonu32(h_sig_comp, strlen(session->local.banner) - 2); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, session->local.banner, strlen(session->local.banner) - 2); + } else { + libssh2_htonu32(h_sig_comp, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, LIBSSH2_SSH_DEFAULT_BANNER, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); + } + + libssh2_htonu32(h_sig_comp, strlen(session->remote.banner)); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, session->remote.banner, strlen(session->remote.banner)); + + libssh2_htonu32(h_sig_comp, session->local.kexinit_len); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, session->local.kexinit, session->local.kexinit_len); + + libssh2_htonu32(h_sig_comp, session->remote.kexinit_len); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, session->remote.kexinit, session->remote.kexinit_len); + + libssh2_htonu32(h_sig_comp, session->server_hostkey_len); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, session->server_hostkey, session->server_hostkey_len); + + if (packet_type_init == SSH_MSG_KEX_DH_GEX_INIT) { + /* diffie-hellman-group-exchange hashes additional fields */ +#ifdef LIBSSH2_DH_GEX_NEW + libssh2_htonu32(h_sig_comp, LIBSSH2_DH_GEX_MINGROUP); + libssh2_htonu32(h_sig_comp + 4, LIBSSH2_DH_GEX_OPTGROUP); + libssh2_htonu32(h_sig_comp + 8, LIBSSH2_DH_GEX_MAXGROUP); + SHA1_Update(&exchange_hash, h_sig_comp, 12); +#else + libssh2_htonu32(h_sig_comp, LIBSSH2_DH_GEX_OPTGROUP); + SHA1_Update(&exchange_hash, h_sig_comp, 4); +#endif + } + + if (midhash) { + SHA1_Update(&exchange_hash, midhash, midhash_len); + } + + SHA1_Update(&exchange_hash, e_packet + 1, e_packet_len - 1); + + libssh2_htonu32(h_sig_comp, f_value_len); + SHA1_Update(&exchange_hash, h_sig_comp, 4); + SHA1_Update(&exchange_hash, f_value, f_value_len); + + SHA1_Update(&exchange_hash, k_value, k_value_len); + + SHA1_Final(h_sig_comp, &exchange_hash); + + if (session->hostkey->sig_verify(session, h_sig, h_sig_len, h_sig_comp, 20, &session->server_hostkey_abstract)) { + libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, "Unable to verify hostkey signature", 0); + ret = -1; + goto clean_exit; + } + + c = SSH_MSG_NEWKEYS; + if (libssh2_packet_write(session, &c, 1)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send NEWKEYS message", 0); + ret = -1; + goto clean_exit; + } + + if (libssh2_packet_require(session, SSH_MSG_NEWKEYS, &tmp, &tmp_len)) { + libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timed out waiting for NEWKEYS", 0); + ret = -1; + goto clean_exit; + } + /* The first key exchange has been performed, switch to active crypt/comp/mac mode */ + session->newkeys = 1; + + /* This will actually end up being just packet_type(1) for this packet type anyway */ + LIBSSH2_FREE(session, tmp); + + if (!session->session_id) { + session->session_id = LIBSSH2_ALLOC(session, SHA_DIGEST_LENGTH); + if (!session->session_id) { + ret = -1; + goto clean_exit; + } + memcpy(session->session_id, h_sig_comp, SHA_DIGEST_LENGTH); + session->session_id_len = SHA_DIGEST_LENGTH; + } + + /* Calculate IV/Secret/Key for each direction */ + if (session->local.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + if (session->local.crypt_abstract) { + LIBSSH2_FREE(session, session->local.crypt_abstract); + session->local.crypt_abstract = NULL; + } + } else { + if (session->local.crypt->dtor) { + /* Cleanup any existing cipher */ + session->local.crypt->dtor(session, &session->local.crypt_abstract); + } + } + + if (session->local.crypt->init || (session->local.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP)) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv, session->local.crypt->iv_len, "A"); + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret, session->local.crypt->secret_len, "C"); + if (session->local.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + EVP_CIPHER *(*get_cipher)(void) = (void*)session->local.crypt->crypt; + EVP_CIPHER *cipher = get_cipher(); + EVP_CIPHER_CTX *ctx; + + ctx = LIBSSH2_ALLOC(session, sizeof(EVP_CIPHER_CTX)); + if (!ctx) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = -1; + goto clean_exit; + } + EVP_CipherInit(ctx, cipher, secret, iv, 1); + session->local.crypt_abstract = ctx; + free_iv = 1; + free_secret = 1; + } else { + session->local.crypt->init(session, iv, &free_iv, secret, &free_secret, 1, &session->local.crypt_abstract); + } + + if (free_iv) { + memset(iv, 0, session->local.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if (free_secret) { + memset(secret, 0, session->local.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + + if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + if (session->remote.crypt_abstract) { + LIBSSH2_FREE(session, session->remote.crypt_abstract); + session->remote.crypt_abstract = NULL; + } + } else { + if (session->remote.crypt->dtor) { + /* Cleanup any existing cipher */ + session->remote.crypt->dtor(session, &session->remote.crypt_abstract); + } + } + + if (session->remote.crypt->init || (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP)) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv, session->remote.crypt->iv_len, "B"); + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret, session->remote.crypt->secret_len, "D"); + if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + EVP_CIPHER *(*get_cipher)(void) = (void*)session->remote.crypt->crypt; + EVP_CIPHER *cipher = get_cipher(); + EVP_CIPHER_CTX *ctx; + + ctx = LIBSSH2_ALLOC(session, sizeof(EVP_CIPHER_CTX)); + if (!ctx) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = -1; + goto clean_exit; + } + EVP_CipherInit(ctx, cipher, secret, iv, 0); + session->remote.crypt_abstract = ctx; + free_iv = 1; + free_secret = 1; + } else { + session->remote.crypt->init(session, iv, &free_iv, secret, &free_secret, 0, &session->remote.crypt_abstract); + } + + if (free_iv) { + memset(iv, 0, session->remote.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if (free_secret) { + memset(secret, 0, session->remote.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + + if (session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + if (session->local.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key, session->kex->key_len, "E"); + session->local.mac->init(session, key, &free_key, &session->local.mac_abstract); + + if (free_key) { + memset(key, 0, session->kex->key_len); + LIBSSH2_FREE(session, key); + } + } + + if (session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + if (session->remote.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key, session->kex->key_len, "F"); + session->remote.mac->init(session, key, &free_key, &session->remote.mac_abstract); + + if (free_key) { + memset(key, 0, session->kex->key_len); + LIBSSH2_FREE(session, key); + } + } + + clean_exit: + BN_clear_free(x); + BN_clear_free(e); + BN_clear_free(f); + BN_clear_free(k); + BN_CTX_free(ctx); + + if (e_packet) { + LIBSSH2_FREE(session, e_packet); + } + + if (s_packet) { + LIBSSH2_FREE(session, s_packet); + } + + if (k_value) { + LIBSSH2_FREE(session, k_value); + } + + if (session->server_hostkey) { + LIBSSH2_FREE(session, session->server_hostkey); + session->server_hostkey = NULL; + } + + return ret; +} +/* }}} */ + +/* {{{ libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange + * Diffie-Hellman Group1 (Actually Group2) Key Exchange using SHA1 + */ +static int libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange(LIBSSH2_SESSION *session) +{ + unsigned char p_value[128] = { + 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 }; + /* g == 2 */ + BIGNUM *p = BN_new(); /* SSH2 defined value (p_value) */ + BIGNUM *g = BN_new(); /* SSH2 defined value (2) */ + int ret; + + /* Initialize P and G */ + BN_set_word(g, 2); + BN_bin2bn(p_value, 128, p); + + ret = libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(session, g, p, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0); + + BN_clear_free(p); + BN_clear_free(g); + + return ret; +} +/* }}} */ + +/* {{{ libssh2_kex_method_diffie_hellman_group14_sha1_key_exchange + * Diffie-Hellman Group14 Key Exchange using SHA1 + */ +static int libssh2_kex_method_diffie_hellman_group14_sha1_key_exchange(LIBSSH2_SESSION *session) +{ + unsigned char p_value[256] = { + 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, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, + 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, + 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, + 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, + 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, + 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + /* g == 2 */ + BIGNUM *p = BN_new(); /* SSH2 defined value (p_value) */ + BIGNUM *g = BN_new(); /* SSH2 defined value (2) */ + int ret; + + /* Initialize P and G */ + BN_set_word(g, 2); + BN_bin2bn(p_value, 256, p); + + ret = libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(session, g, p, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0); + + BN_clear_free(p); + BN_clear_free(g); + + return ret; +} +/* }}} */ + +/* {{{ libssh2_kex_method_diffie_hellman_group_exchange_sha1_key_exchange + * Diffie-Hellman Group Exchange Key Exchange using SHA1 + * Negotiates random(ish) group for secret derivation + */ +static int libssh2_kex_method_diffie_hellman_group_exchange_sha1_key_exchange(LIBSSH2_SESSION *session) +{ + unsigned char request[13], *s, *data; + unsigned long data_len, len, request_len; + BIGNUM *p = BN_new(); + BIGNUM *g = BN_new(); + int ret; + + /* Ask for a P and G pair */ +#ifdef LIBSSH2_DH_GEX_NEW + request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; + libssh2_htonu32(request + 1, LIBSSH2_DH_GEX_MINGROUP); + libssh2_htonu32(request + 5, LIBSSH2_DH_GEX_OPTGROUP); + libssh2_htonu32(request + 9, LIBSSH2_DH_GEX_MAXGROUP); + request_len = 13; +#else + request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD; + libssh2_htonu32(request + 1, LIBSSH2_DH_GEX_OPTGROUP); + request_len = 5; +#endif + + if (libssh2_packet_write(session, request, request_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send Group Exchange Request", 0); + ret = -1; + goto dh_gex_clean_exit; + } + + if (libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, "Timeout waiting for GEX_GROUP reply", 0); + ret = -1; + goto dh_gex_clean_exit; + } + + s = data + 1; + len = libssh2_ntohu32(s); s += 4; + BN_bin2bn(s, len, p); s += len; + + len = libssh2_ntohu32(s); s += 4; + BN_bin2bn(s, len, g); s += len; + + ret = libssh2_kex_method_diffie_hellman_groupGP_sha1_key_exchange(session, g, p, SSH_MSG_KEX_DH_GEX_INIT, SSH_MSG_KEX_DH_GEX_REPLY, data + 1, data_len - 1); + + LIBSSH2_FREE(session, data); + + dh_gex_clean_exit: + BN_clear_free(g); + BN_clear_free(p); + + return ret; +} +/* }}} */ + +#define LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001 +#define LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002 + +LIBSSH2_KEX_METHOD libssh2_kex_method_diffie_helman_group1_sha1 = { + "diffie-hellman-group1-sha1", + SHA_DIGEST_LENGTH, + libssh2_kex_method_diffie_hellman_group1_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +LIBSSH2_KEX_METHOD libssh2_kex_method_diffie_helman_group14_sha1 = { + "diffie-hellman-group14-sha1", + SHA_DIGEST_LENGTH, + libssh2_kex_method_diffie_hellman_group14_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +LIBSSH2_KEX_METHOD libssh2_kex_method_diffie_helman_group_exchange_sha1 = { + "diffie-hellman-group-exchange-sha1", + SHA_DIGEST_LENGTH, + libssh2_kex_method_diffie_hellman_group_exchange_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { + &libssh2_kex_method_diffie_helman_group14_sha1, + &libssh2_kex_method_diffie_helman_group_exchange_sha1, + &libssh2_kex_method_diffie_helman_group1_sha1, + NULL +}; + +typedef struct _LIBSSH2_COMMON_METHOD { + char *name; +} LIBSSH2_COMMON_METHOD; + +/* {{{ libssh2_kex_method_strlen + * Calculate the length of a particular method list's resulting string + * Includes SUM(strlen() of each individual method plus 1 (for coma)) - 1 (because the last coma isn't used) + * Another sign of bad coding practices gone mad. Pretend you don't see this. + */ +static size_t libssh2_kex_method_strlen(LIBSSH2_COMMON_METHOD **method) +{ + size_t len = 0; + + if (!method || !*method) { + return 0; + } + + while (*method && (*method)->name) { + len += strlen((*method)->name) + 1; + method++; + } + + return len - 1; +} +/* }}} */ + +/* {{{ libssh2_kex_method_list + * Generate formatted preference list in buf + */ +static size_t libssh2_kex_method_list(unsigned char *buf, size_t list_strlen, LIBSSH2_COMMON_METHOD **method) +{ + libssh2_htonu32(buf, list_strlen); + buf += 4; + + if (!method || !*method) { + return 4; + } + + while (*method && (*method)->name) { + int mlen = strlen((*method)->name); + memcpy(buf, (*method)->name, mlen); + buf += mlen; + *(buf++) = ','; + method++; + } + + return list_strlen + 4; +} +/* }}} */ + +#define LIBSSH2_METHOD_PREFS \ + LIBSSH2_KEX_METHOD **kex_prefs = session->kex_prefs ? session->kex_prefs : libssh2_kex_methods; \ + LIBSSH2_HOSTKEY_METHOD **hostkey_prefs = session->hostkey_prefs ? session->hostkey_prefs : libssh2_hostkey_methods(); \ + LIBSSH2_CRYPT_METHOD **crypt_cs_prefs = session->local.crypt_prefs ? session->local.crypt_prefs : libssh2_crypt_methods(); \ + LIBSSH2_CRYPT_METHOD **crypt_sc_prefs = session->remote.crypt_prefs ? session->remote.crypt_prefs : libssh2_crypt_methods(); \ + LIBSSH2_COMP_METHOD **comp_cs_prefs = session->local.comp_prefs ? session->local.comp_prefs : libssh2_comp_methods(); \ + LIBSSH2_COMP_METHOD **comp_sc_prefs = session->remote.comp_prefs ? session->remote.comp_prefs : libssh2_comp_methods(); \ + LIBSSH2_MAC_METHOD **mac_cs_prefs = session->local.mac_prefs ? session->local.mac_prefs : libssh2_mac_methods(); \ + LIBSSH2_MAC_METHOD **mac_sc_prefs = session->remote.mac_prefs ? session->remote.mac_prefs : libssh2_mac_methods(); + + +/* {{{ libssh2_kexinit + * Send SSH_MSG_KEXINIT packet + */ +static int libssh2_kexinit(LIBSSH2_SESSION *session) +{ + size_t data_len = 62; /* packet_type(1) + cookie(16) + first_packet_follows(1) + reserved(4) + length longs(40) */ + size_t kex_len, hostkey_len = 0; + size_t crypt_cs_len, crypt_sc_len; + size_t comp_cs_len, comp_sc_len; + size_t mac_cs_len, mac_sc_len; + size_t lang_cs_len, lang_sc_len; + unsigned char *data, *s; + LIBSSH2_METHOD_PREFS + + kex_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)kex_prefs); + hostkey_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)hostkey_prefs); + crypt_cs_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)crypt_cs_prefs); + crypt_sc_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)crypt_sc_prefs); + mac_cs_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)mac_cs_prefs); + mac_sc_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)mac_sc_prefs); + comp_cs_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)comp_cs_prefs); + comp_sc_len = libssh2_kex_method_strlen((LIBSSH2_COMMON_METHOD**)comp_sc_prefs); + lang_cs_len = 0; /* No langs in this version */ + lang_sc_len = 0; /* No langs in this version */ + + data_len += kex_len + hostkey_len + \ + crypt_cs_len + crypt_sc_len + \ + comp_cs_len + comp_sc_len + \ + mac_cs_len + mac_sc_len + \ + lang_cs_len + lang_sc_len; + + s = data = LIBSSH2_ALLOC(session, data_len); + if (!data) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory", 0); + return -1; + } + + *(s++) = SSH_MSG_KEXINIT; + + /* TODO: Build a better cookie (and the mice will beat a path to my door...) */ + memcpy(s, "mysecretMYSECRET", 16); + s += 16; + + /* Ennumerating through these lists twice is probably (certainly?) inefficient from a CPU standpoint, but it saves multiple malloc/realloc calls */ + s += libssh2_kex_method_list(s, kex_len, (LIBSSH2_COMMON_METHOD**)kex_prefs); + s += libssh2_kex_method_list(s, hostkey_len, (LIBSSH2_COMMON_METHOD**)hostkey_prefs); + s += libssh2_kex_method_list(s, crypt_cs_len, (LIBSSH2_COMMON_METHOD**)crypt_cs_prefs); + s += libssh2_kex_method_list(s, crypt_sc_len, (LIBSSH2_COMMON_METHOD**)crypt_sc_prefs); + s += libssh2_kex_method_list(s, mac_cs_len, (LIBSSH2_COMMON_METHOD**)mac_cs_prefs); + s += libssh2_kex_method_list(s, mac_sc_len, (LIBSSH2_COMMON_METHOD**)mac_sc_prefs); + s += libssh2_kex_method_list(s, comp_cs_len, (LIBSSH2_COMMON_METHOD**)comp_cs_prefs); + s += libssh2_kex_method_list(s, comp_sc_len, (LIBSSH2_COMMON_METHOD**)comp_sc_prefs); + s += libssh2_kex_method_list(s, lang_cs_len, NULL); + s += libssh2_kex_method_list(s, lang_sc_len, NULL); + + /* No optimistic KEX packet follows */ + /* Deal with optimistic packets + * session->flags |= KEXINIT_OPTIMISTIC + * session->flags |= KEXINIT_METHODSMATCH + */ + *(s++) = 0; + + /* Reserved == 0 */ + *(s++) = 0; + *(s++) = 0; + *(s++) = 0; + *(s++) = 0; + + if (libssh2_packet_write(session, data, data_len)) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send KEXINIT packet to remote host", 0); + return -1; + } + + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + + session->local.kexinit = data; + session->local.kexinit_len = data_len; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_kex_agree_instr + * Kex specific variant of strstr() + * Needle must be preceed by BOL or ',', and followed by ',' or EOL + */ +static unsigned char *libssh2_kex_agree_instr(unsigned char *haystack, unsigned long haystack_len, + unsigned char *needle, unsigned long needle_len) +{ + unsigned char *s; + + /* Haystack too short to bother trying */ + if (haystack_len < needle_len) { + return NULL; + } + + /* Needle at start of haystack */ + if ((strncmp(haystack, needle, needle_len) == 0) && + (needle_len == haystack_len || haystack[needle_len] == ',')) { + return haystack; + } + + s = haystack; + /* Search until we run out of comas or we run out of haystack, + whichever comes first */ + while ((s = strchr(s, ',')) && ((haystack_len - (s - haystack)) > needle_len)) { + s++; + /* Needle at X position */ + if ((strncmp(s, needle, needle_len) == 0) && + (((s - haystack) + needle_len) == haystack_len || s[needle_len] == ',')) { + return s; + } + } + + return NULL; +} +/* }}} */ + +/* {{{ libssh2_kex_agree_hostkey + * Agree on a Hostkey which works with this kex + */ +static int libssh2_kex_agree_hostkey(LIBSSH2_SESSION *session, unsigned long kex_flags, unsigned char *hostkey, unsigned long hostkey_len) +{ + LIBSSH2_HOSTKEY_METHOD **hostkeyp = session->hostkey_prefs ? session->hostkey_prefs : libssh2_hostkey_methods(); + unsigned char *s; + + while ((*hostkeyp)->name) { + s = libssh2_kex_agree_instr(hostkey, hostkey_len, (*hostkeyp)->name, strlen((*hostkeyp)->name)); + if (s) { + /* So far so good, but does it suit our purposes? (Encrypting vs Signing) */ + if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0) || + ((*hostkeyp)->encrypt)) { + /* Either this hostkey can do encryption or this kex just doesn't require it */ + if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == 0) || + ((*hostkeyp)->sig_verify)) { + /* Either this hostkey can do signing or this kex just doesn't require it */ + session->hostkey = *hostkeyp; + return 0; + } + } + } + hostkeyp++; + } + + return -1; +} +/* }}} */ + +/* {{{ libssh2_kex_agree_kex_hostkey + * Agree on a Key Exchange method and a hostkey encoding type + */ +static int libssh2_kex_agree_kex_hostkey(LIBSSH2_SESSION *session, unsigned char *kex, unsigned long kex_len, + unsigned char *hostkey, unsigned long hostkey_len) +{ + LIBSSH2_KEX_METHOD **kexp = session->kex_prefs ? session->kex_prefs : libssh2_kex_methods; + unsigned char *s; + + while (*kexp && (*kexp)->name) { + s = libssh2_kex_agree_instr(kex, kex_len, (*kexp)->name, strlen((*kexp)->name)); + if (s) { + /* We've agreed on a key exchange method, + * Can we agree on a hostkey that works with this kex? + */ + if (libssh2_kex_agree_hostkey(session, (*kexp)->flags, hostkey, hostkey_len) == 0) { + session->kex = *kexp; + return 0; + } + } + kexp++; + } + return -1; +} +/* }}} */ + +/* {{{ libssh2_kex_agree_crypt + * Agree on a cipher algo + */ +static int libssh2_kex_agree_crypt(LIBSSH2_SESSION *session, libssh2_endpoint_data *endpoint, unsigned char *crypt, unsigned long crypt_len) +{ + LIBSSH2_CRYPT_METHOD **cryptp = endpoint->crypt_prefs ? endpoint->crypt_prefs : libssh2_crypt_methods(); + unsigned char *s; + + while ((*cryptp)->name) { + s = libssh2_kex_agree_instr(crypt, crypt_len, (*cryptp)->name, strlen((*cryptp)->name)); + if (s) { + endpoint->crypt = *cryptp; + return 0; + } + cryptp++; + } + + return -1; +} +/* }}} */ + +/* {{{ libssh2_kex_agree_mac + * Agree on a message authentication hash + */ +static int libssh2_kex_agree_mac(LIBSSH2_SESSION *session, libssh2_endpoint_data *endpoint, unsigned char *mac, unsigned long mac_len) +{ + LIBSSH2_MAC_METHOD **macp = endpoint->mac_prefs ? endpoint->mac_prefs : libssh2_mac_methods(); + unsigned char *s; + + while ((*macp)->name) { + s = libssh2_kex_agree_instr(mac, mac_len, (*macp)->name, strlen((*macp)->name)); + if (s) { + endpoint->mac = *macp; + return 0; + } + macp++; + } + + return -1; +} +/* }}} */ + +/* {{{ libssh2_kex_agree_comp + * Agree on a compression scheme + */ +static int libssh2_kex_agree_comp(LIBSSH2_SESSION *session, libssh2_endpoint_data *endpoint, unsigned char *comp, unsigned long comp_len) +{ + LIBSSH2_COMP_METHOD **compp = endpoint->comp_prefs ? endpoint->comp_prefs : libssh2_comp_methods(); + unsigned char *s; + + while ((*compp)->name) { + s = libssh2_kex_agree_instr(comp, comp_len, (*compp)->name, strlen((*compp)->name)); + if (s) { + endpoint->comp = *compp; + return 0; + } + compp++; + } + + return -1; +} +/* }}} */ + +/* TODO: When in server mode we need to turn this logic on its head + * The Client gets to make the final call on "agreed methods" + */ + +/* {{{ libssh2_kex_agree_methods + * Decide which specific method to use of the methods offered by each party + */ +static int libssh2_kex_agree_methods(LIBSSH2_SESSION *session, unsigned char *data, unsigned data_len) +{ + unsigned char *kex, *hostkey, *crypt_cs, *crypt_sc, *comp_cs, *comp_sc, *mac_cs, *mac_sc, *lang_cs, *lang_sc; + size_t kex_len, hostkey_len, crypt_cs_len, crypt_sc_len, comp_cs_len, comp_sc_len, mac_cs_len, mac_sc_len, lang_cs_len, lang_sc_len; + unsigned char *s = data; + + /* Skip packet_type, we know it already */ + s++; + + /* Skip cookie, don't worry, it's preserved in the kexinit field */ + s += 16; + + /* Locate each string */ + kex_len = libssh2_ntohu32(s); kex = s + 4; s += 4 + kex_len; + hostkey_len = libssh2_ntohu32(s); hostkey = s + 4; s += 4 + hostkey_len; + crypt_cs_len = libssh2_ntohu32(s); crypt_cs = s + 4; s += 4 + crypt_cs_len; + crypt_sc_len = libssh2_ntohu32(s); crypt_sc = s + 4; s += 4 + crypt_sc_len; + mac_cs_len = libssh2_ntohu32(s); mac_cs = s + 4; s += 4 + mac_cs_len; + mac_sc_len = libssh2_ntohu32(s); mac_sc = s + 4; s += 4 + mac_sc_len; + comp_cs_len = libssh2_ntohu32(s); comp_cs = s + 4; s += 4 + comp_cs_len; + comp_sc_len = libssh2_ntohu32(s); comp_sc = s + 4; s += 4 + comp_sc_len; + lang_cs_len = libssh2_ntohu32(s); lang_cs = s + 4; s += 4 + lang_cs_len; + lang_sc_len = libssh2_ntohu32(s); lang_sc = s + 4; s += 4 + lang_sc_len; + + if (libssh2_kex_agree_kex_hostkey(session, kex, kex_len, hostkey, hostkey_len)) { + return -1; + } + + if (libssh2_kex_agree_crypt(session, &session->local, crypt_cs, crypt_cs_len) || + libssh2_kex_agree_crypt(session, &session->remote, crypt_sc, crypt_sc_len)) { + return -1; + } + + if (libssh2_kex_agree_mac(session, &session->local, mac_cs, mac_cs_len) || + libssh2_kex_agree_mac(session, &session->remote, mac_sc, mac_sc_len)) { + return -1; + } + + if (libssh2_kex_agree_comp(session, &session->local, comp_cs, comp_cs_len) || + libssh2_kex_agree_comp(session, &session->remote, comp_sc, comp_sc_len)) { + return -1; + } + + if (libssh2_kex_agree_lang(session, &session->local, lang_cs, lang_cs_len) || + libssh2_kex_agree_lang(session, &session->remote, lang_sc, lang_sc_len)) { + return -1; + } + + /* Initialize compression layer */ + if (session->local.comp && session->local.comp->init && + session->local.comp->init(session, 1, &session->local.comp_abstract)) { + return -1; + } + + if (session->remote.comp && session->remote.comp->init && + session->remote.comp->init(session, 0, &session->remote.comp_abstract)) { + return -1; + } + + return 0; +} +/* }}} */ + +/* {{{ libssh2_kex_exchange + * Exchange keys + * Returns 0 on success, non-zero on failure + */ +int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange) /* session->flags |= SERVER */ +{ + unsigned char *data; + unsigned long data_len; + + /* Prevent loop in packet_add() */ + session->exchanging_keys = 1; + + if (reexchange) { + session->kex = NULL; + + if (session->hostkey && session->hostkey->dtor) { + session->hostkey->dtor(session, &session->server_hostkey_abstract); + } + session->hostkey = NULL; + } + + if (!session->kex || !session->hostkey) { + if (libssh2_packet_require(session, SSH_MSG_KEXINIT, &data, &data_len)) { + return -1; + } + + if (session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + } + session->remote.kexinit = data; + session->remote.kexinit_len = data_len; + + if (libssh2_kexinit(session)) { + return -1; + } + + if (libssh2_kex_agree_methods(session, data, data_len)) { + return -1; + } + } + + if (session->kex->exchange_keys(session)) { + libssh2_error(session, LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE, "Unrecoverable error exchanging keys", 0); + return -1; + } + + /* Done with kexinit buffers */ + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + session->local.kexinit = NULL; + } + if (session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + session->remote.kexinit = NULL; + } + + session->exchanging_keys = 0; + + return 0; +} +/* }}} */ + diff --git a/src/mac.c b/src/mac.c new file mode 100644 index 00000000..5d346d41 --- /dev/null +++ b/src/mac.c @@ -0,0 +1,268 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include + +#ifdef LIBSSH2_MAC_NONE +/* {{{ libssh2_mac_none_MAC + * Minimalist MAC: No MAC + */ +static int libssh2_mac_none_MAC(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, + const unsigned char *packet, unsigned long packet_len, + const unsigned char *addtl, unsigned long addtl_len, void **abstract) +{ + return 0; +} +/* }}} */ + + +static LIBSSH2_MAC_METHOD libssh2_mac_method_none = { + "none", + 0, + NULL, + libssh2_mac_none_MAC, + NULL +}; +#endif /* LIBSSH2_MAC_NONE */ + +/* {{{ libssh2_mac_method_common_init + * Initialize simple mac methods + */ +static int libssh2_mac_method_common_init(LIBSSH2_SESSION *session, unsigned char *key, int *free_key, void **abstract) +{ + *abstract = key; + *free_key = 0; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_mac_method_common_dtor + * Cleanup simple mac methods + */ +static int libssh2_mac_method_common_dtor(LIBSSH2_SESSION *session, void **abstract) +{ + if (*abstract) { + LIBSSH2_FREE(session, *abstract); + } + *abstract = NULL; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_mac_method_hmac_sha1_hash + * Calculate hash using full sha1 value + */ +static int libssh2_mac_method_hmac_sha1_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, + const unsigned char *packet, unsigned long packet_len, + const unsigned char *addtl, unsigned long addtl_len, void **abstract) +{ + HMAC_CTX ctx; + unsigned char seqno_buf[4]; + + libssh2_htonu32(seqno_buf, seqno); + + HMAC_Init(&ctx, *abstract, session->kex->key_len, EVP_sha1()); + HMAC_Update(&ctx, seqno_buf, 4); + HMAC_Update(&ctx, packet, packet_len); + if (addtl && addtl_len) { + HMAC_Update(&ctx, addtl, addtl_len); + } + HMAC_Final(&ctx, buf, NULL); + HMAC_cleanup(&ctx); + + return 0; +} +/* }}} */ + +static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_sha1 = { + "hmac-sha1", + SHA_DIGEST_LENGTH, + libssh2_mac_method_common_init, + libssh2_mac_method_hmac_sha1_hash, + libssh2_mac_method_common_dtor, +}; + +/* {{{ libssh2_mac_method_hmac_sha1_96_hash + * Calculate hash using first 96 bits of sha1 value + */ +static int libssh2_mac_method_hmac_sha1_96_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, + const unsigned char *packet, unsigned long packet_len, + const unsigned char *addtl, unsigned long addtl_len, void **abstract) +{ + char temp[SHA_DIGEST_LENGTH]; + + libssh2_mac_method_hmac_sha1_hash(session, temp, seqno, packet, packet_len, addtl, addtl_len, abstract); + memcpy(buf, temp, 96 / 8); + + return 0; +} +/* }}} */ + +static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_sha1_96 = { + "hmac-sha1-96", + 96 / 8, + libssh2_mac_method_common_init, + libssh2_mac_method_hmac_sha1_96_hash, + libssh2_mac_method_common_dtor, +}; + +#ifdef WHY_DOESNT_MD5_WORK +/* {{{ libssh2_mac_method_hmac_md5_hash + * Calculate hash using full md5 value + */ +static int libssh2_mac_method_hmac_md5_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, + const unsigned char *packet, unsigned long packet_len, + const unsigned char *addtl, unsigned long addtl_len, void **abstract) +{ + HMAC_CTX ctx; + unsigned char seqno_buf[4]; + + libssh2_htonu32(seqno_buf, seqno); + + HMAC_Init(&ctx, *abstract, session->kex->key_len, EVP_md5()); + HMAC_Update(&ctx, seqno_buf, 4); + HMAC_Update(&ctx, packet, packet_len); + if (addtl && addtl_len) { + HMAC_Update(&ctx, addtl, addtl_len); + } + HMAC_Final(&ctx, buf, NULL); + HMAC_cleanup(&ctx); + + return 0; +} +/* }}} */ + +static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_md5 = { + "hmac-md5", + MD5_DIGEST_LENGTH, + libssh2_mac_method_common_init, + libssh2_mac_method_hmac_md5_hash, + libssh2_mac_method_common_dtor, +}; + +/* {{{ libssh2_mac_method_hmac_md5_96_hash + * Calculate hash using first 96 bits of md5 value + */ +static int libssh2_mac_method_hmac_md5_96_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned seqno, + const unsigned char *packet, unsigned packet_len, + const unsigned char *addtl, unsigned long addtl_len, void **abstract) +{ + char temp[MD5_DIGEST_LENGTH]; + + libssh2_mac_method_hmac_md5_hash(session, temp, seqno, packet, packet_len, addtl, addtl_len, abstract); + memcpy(buf, temp, 96 / 8); + + return 0; +} +/* }}} */ + +static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_md5_96 = { + "hmac-md5-96", + 96 / 8, + libssh2_mac_method_common_init, + libssh2_mac_method_hmac_md5_96_hash, + libssh2_mac_method_common_dtor, +}; +#endif /* WHY_DOESNT_MD5_WORK */ + +#ifndef OPENSSL_NO_RIPEMD +/* {{{ libssh2_mac_method_hmac_ripemd160_hash + * Calculate hash using ripemd160 value + */ +static int libssh2_mac_method_hmac_ripemd160_hash(LIBSSH2_SESSION *session, unsigned char *buf, unsigned long seqno, + const unsigned char *packet, unsigned long packet_len, + const unsigned char *addtl, unsigned long addtl_len, void **abstract) +{ + HMAC_CTX ctx; + unsigned char seqno_buf[4]; + + libssh2_htonu32(seqno_buf, seqno); + + HMAC_Init(&ctx, *abstract, session->kex->key_len, EVP_ripemd160()); + HMAC_Update(&ctx, seqno_buf, 4); + HMAC_Update(&ctx, packet, packet_len); + if (addtl && addtl_len) { + HMAC_Update(&ctx, addtl, addtl_len); + } + HMAC_Final(&ctx, buf, NULL); + HMAC_cleanup(&ctx); + + return 0; +} +/* }}} */ + +static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_ripemd160 = { + "hmac-ripemd160", + 160 / 8, + libssh2_mac_method_common_init, + libssh2_mac_method_hmac_ripemd160_hash, + libssh2_mac_method_common_dtor, +}; + +static LIBSSH2_MAC_METHOD libssh2_mac_method_hmac_ripemd160_openssh_com = { + "hmac-ripemd160@openssh.com", + 160 / 8, + libssh2_mac_method_common_init, + libssh2_mac_method_hmac_ripemd160_hash, + libssh2_mac_method_common_dtor, +}; +#endif /* ! OPENSSL_NO_RIPEMD */ + +static LIBSSH2_MAC_METHOD *_libssh2_mac_methods[] = { + &libssh2_mac_method_hmac_sha1, + &libssh2_mac_method_hmac_sha1_96, +#ifdef WHY_DOESNT_MD5_WORK + &libssh2_mac_method_hmac_md5, + &libssh2_mac_method_hmac_md5_96, +#endif /* WHY_DOESNT_MD5_WORK */ +#ifndef OPENSSL_NO_RIPEMD + &libssh2_mac_method_hmac_ripemd160, + &libssh2_mac_method_hmac_ripemd160_openssh_com, +#endif /* ! OPENSSL_NO_RIPEMD */ +#ifdef LIBSSH2_MAC_NONE + &libssh2_mac_method_none, +#endif /* LIBSSH2_MAC_NONE */ + NULL +}; + +LIBSSH2_MAC_METHOD **libssh2_mac_methods(void) { + return _libssh2_mac_methods; +} + diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 00000000..13e6da6b --- /dev/null +++ b/src/misc.c @@ -0,0 +1,138 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" + +/* {{{ libssh2_ntohu32 + */ +unsigned long libssh2_ntohu32(const unsigned char *buf) +{ + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} +/* }}} */ + +/* {{{ libssh2_htonu32 + */ +void libssh2_htonu32(unsigned char *buf, unsigned long value) +{ + buf[0] = (value >> 24) & 0xFF; + buf[1] = (value >> 16) & 0xFF; + buf[2] = (value >> 8) & 0xFF; + buf[3] = value & 0xFF; +} +/* }}} */ + +/* Base64 Conversion */ + +/* {{{ */ +static const char libssh2_base64_table[] = + { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0' + }; + +static const char libssh2_base64_pad = '='; + +static const short libssh2_base64_reverse_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; +/* }}} */ + + +/* {{{ libssh2_base64_decode + * Decode a base64 chunk and store it into a newly alloc'd buffer + */ +LIBSSH2_API int libssh2_base64_decode(LIBSSH2_SESSION *session, char **data, int *datalen, + char *src, int src_len) +{ + unsigned char *s, *d; + short v; + int i = 0, len = 0; + + *data = d = LIBSSH2_ALLOC(session, (3 * src_len / 4) + 1); + if (!d) { + return -1; + } + + for(s = src; ((char*)s) < (src + src_len); s++) { + if ((v = libssh2_base64_reverse_table[*s]) < 0) continue; + switch (i % 4) { + case 0: + d[len] = v << 2; + break; + case 1: + d[len++] |= v >> 4; + d[len] = v << 4; + break; + case 2: + d[len++] |= v >> 2; + d[len] = v << 6; + break; + case 3: + d[len++] |= v; + break; + } + i++; + } + if ((i % 4) == 1) { + /* Invalid -- We have a byte which belongs exclusively to a partial octet */ + LIBSSH2_FREE(session, *data); + return -1; + } + + *datalen = len; + return 0; +} +/* }}} */ + diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 00000000..8c0ae5be --- /dev/null +++ b/src/packet.c @@ -0,0 +1,665 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include +#include +#include +#include + +/* {{{ libssh2_packet_new + * Create a new packet and attach it to the brigade + */ +static int libssh2_packet_add(LIBSSH2_SESSION *session, unsigned char *data, size_t datalen, int macstate) +{ + LIBSSH2_PACKET *packet; + unsigned long data_head = 0; + + if (macstate == LIBSSH2_MAC_INVALID) { + if (session->macerror) { + if (LIBSSH2_MACERROR(session, data, datalen) == 0) { + /* Calling app has given the OK, Process it anyway */ + macstate = LIBSSH2_MAC_CONFIRMED; + } else { + libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); + if (session->ssh_msg_disconnect) { + LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); + } + return -1; + } + } else { + libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); + if (session->ssh_msg_disconnect) { + LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); + } + return -1; + } + } + + /* A couple exceptions to the packet adding rule: */ + switch (data[0]) { + case SSH_MSG_DISCONNECT: + { + char *message, *language; + int reason, message_len, language_len; + + reason = libssh2_ntohu32(data + 1); + message_len = libssh2_ntohu32(data + 5); + message = data + 9; /* packet_type(1) + reason(4) + message_len(4) */ + language_len = libssh2_ntohu32(data + 9 + message_len); + /* This is where we hack on the data a little, + * Use the MSB of language_len to to a terminating NULL (In all liklihood it is already) + * Shift the language tag back a byte (In all likelihood it's zero length anyway + * Store a NULL in the last byte of the packet to terminate the language string + * With the lengths passed this isn't *REALLY* necessary, but it's "kind" + */ + message[message_len] = '\0'; + language = data + 9 + message_len + 3; + if (language_len) { + memcpy(language, language + 1, language_len); + } + language[language_len] = '\0'; + + if (session->ssh_msg_disconnect) { + LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len); + } + LIBSSH2_FREE(session, data); + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + return -1; + } + break; + case SSH_MSG_IGNORE: + /* As with disconnect, back it up one and add a trailing NULL */ + memcpy(data + 4, data + 5, datalen - 5); + data[datalen] = '\0'; + if (session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, data + 4, datalen - 5); + } + LIBSSH2_FREE(session, data); + return 0; + break; + case SSH_MSG_DEBUG: + { + int always_display = data[0]; + char *message, *language; + int message_len, language_len; + + message_len = libssh2_ntohu32(data + 2); + message = data + 6; /* packet_type(1) + display(1) + message_len(4) */ + language_len = libssh2_ntohu32(data + 6 + message_len); + /* This is where we hack on the data a little, + * Use the MSB of language_len to to a terminating NULL (In all liklihood it is already) + * Shift the language tag back a byte (In all likelihood it's zero length anyway + * Store a NULL in the last byte of the packet to terminate the language string + * With the lengths passed this isn't *REALLY* necessary, but it's "kind" + */ + message[message_len] = '\0'; + language = data + 6 + message_len + 3; + if (language_len) { + memcpy(language, language + 1, language_len); + } + language[language_len] = '\0'; + + if (session->ssh_msg_debug) { + LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len); + } + LIBSSH2_FREE(session, data); + return 0; + } + break; + case SSH_MSG_CHANNEL_EXTENDED_DATA: + data_head += 4; /* streamid(4) */ + case SSH_MSG_CHANNEL_DATA: + data_head += 9; /* packet_type(1) + channelno(4) + datalen(4) */ + { + LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); + + if (!channel) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, "Packet received for unknown channel, ignoring", 0); + LIBSSH2_FREE(session, data); + return 0; + } + + /* REMEMBER! remote means remote as source of data, NOT remote window! */ + if (channel->remote.packet_size < (datalen - data_head)) { + /* Spec says we MAY ignore bytes sent beyond packet_size */ + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "Packet contains more data than we offered to receive, truncating", 0); + datalen = channel->remote.packet_size + data_head; + } + if (channel->remote.window_size_initial && (channel->remote.window_size <= 0)) { + /* Spec says we MAY ignore bytes sent beyond window_size */ + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "The current receive window is full, data ignored", 0); + LIBSSH2_FREE(session, data); + return 0; + } + /* Reset EOF status */ + channel->remote.eof = 0; + + if (channel->remote.window_size_initial && ((datalen - data_head) > channel->remote.window_size)) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "Remote sent more data than current window allows, truncating", 0); + datalen = channel->remote.window_size + data_head; + } else { + /* Now that we've received it, shrink our window */ + channel->remote.window_size -= datalen - data_head; + } + } + break; + case SSH_MSG_CHANNEL_EOF: + { + LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); + + if (!channel) { + /* We may have freed already, just quietly ignore this... */ + LIBSSH2_FREE(session, data); + return 0; + } + + channel->remote.eof = 1; + + LIBSSH2_FREE(session, data); + return 0; + } + break; + case SSH_MSG_CHANNEL_CLOSE: + { + LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); + + if (!channel) { + /* We may have freed already, just quietly ignore this... */ + LIBSSH2_FREE(session, data); + return 0; + } + + channel->remote.close = 1; + /* TODO: Add a callback for this */ + + LIBSSH2_FREE(session, data); + return 0; + } + break; + } + + packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); + memset(packet, 0, sizeof(LIBSSH2_PACKET)); + + packet->data = data; + packet->data_len = datalen; + packet->data_head = data_head; + packet->mac = macstate; + packet->brigade = &session->packets; + packet->next = NULL; + + if (session->packets.tail) { + packet->prev = session->packets.tail; + packet->prev->next = packet; + session->packets.tail = packet; + } else { + session->packets.head = packet; + session->packets.tail = packet; + packet->prev = NULL; + } + + if (data[0] == SSH_MSG_KEXINIT && !session->exchanging_keys) { + /* Remote wants new keys + * Well, it's already in the brigade, + * let's just call back into ourselves + */ + libssh2_kex_exchange(session, 1); + /* If there was a key reexchange failure, let's just hope we didn't send NEWKEYS yet, otherwise remote will drop us like a rock */ + } + + return 0; +} +/* }}} */ + +/* {{{ libssh2_blocking_read + * Force a blocking read, regardless of socket settings + */ +static int libssh2_blocking_read(LIBSSH2_SESSION *session, unsigned char *buf, size_t count) +{ + size_t bytes_read = 0; + int polls = 0; + + while (bytes_read < count) { + int ret; + + ret = read(session->socket_fd, buf + bytes_read, count - bytes_read); + if (ret < 0) { + if (errno == EAGAIN) { + if (polls++ > LIBSSH2_SOCKET_POLL_MAXLOOPS) { + return -1; + } + usleep(LIBSSH2_SOCKET_POLL_UDELAY); + continue; + } + if (errno == EINTR) { + continue; + } + if ((errno == EBADF) || (errno == EIO)) { + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + } + return -1; + } + if (ret == 0) continue; + + bytes_read += ret; + } + + return bytes_read; +} +/* }}} */ + +/* {{{ libssh2_packet_read + * Collect a packet into the input brigade + * block only controls whether or not to wait for a packet to start, + * Once a packet starts, libssh2 will block until it is complete + * Returns packet type added to input brigade (0 if nothing added), or -1 on failure + */ +int libssh2_packet_read(LIBSSH2_SESSION *session, int should_block) +{ + int packet_type = -1; + + if (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { + return 0; + } + + fcntl(session->socket_fd, F_SETFL, O_NONBLOCK); + if (session->newkeys) { + unsigned char *block, *payload, *s, tmp[6]; + long read_len; + unsigned long blocksize = session->remote.crypt->blocksize; + unsigned long packet_len, payload_len; + int padding_len; + int macstate; + int free_payload = 1; + /* Safely ignored in CUSTOM cipher mode */ + EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)session->remote.crypt_abstract; + + /* Temporary Buffer */ + block = LIBSSH2_ALLOC(session, 2 * (blocksize > session->remote.mac->mac_len ? blocksize : session->remote.mac->mac_len)); + + /* Note: If we add any cipher with a blocksize less than 6 we'll need to get more creative with this + * For now, all blocksize sizes are 8+ + */ + if (should_block) { + read_len = libssh2_blocking_read(session, block, blocksize); + } else { + read_len = read(session->socket_fd, block, 1); + if (read_len <= 0) { + LIBSSH2_FREE(session, block); + return 0; + } + read_len += libssh2_blocking_read(session, block + read_len, blocksize - read_len); + } + if (read_len < blocksize) { + LIBSSH2_FREE(session, block); + return (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) ? 0 : -1; + } + + if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + EVP_Cipher(ctx, block + blocksize, block, blocksize); + memcpy(block, block + blocksize, blocksize); + } else { + if (session->remote.crypt->crypt(session, block, &session->remote.crypt_abstract)) { + libssh2_error(session, LIBSSH2_ERROR_DECRYPT, "Error decrypting packet preamble", 0); + LIBSSH2_FREE(session, block); + return -1; + } + } + + packet_len = libssh2_ntohu32(block); + padding_len = block[4]; + memcpy(tmp, block, 5); /* Use this for MAC later */ + + payload_len = packet_len - 1; /* padding_len(1) */ + s = payload = LIBSSH2_ALLOC(session, payload_len); + memcpy(s, block + 5, blocksize - 5); + s += blocksize - 5; + + while ((s - payload) < payload_len) { + read_len = libssh2_blocking_read(session, block, blocksize); + if (read_len < blocksize) { + LIBSSH2_FREE(session, block); + LIBSSH2_FREE(session, payload); + return -1; + } + if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + EVP_Cipher(ctx, block + blocksize, block, blocksize); + memcpy(s, block + blocksize, blocksize); + } else { + if (session->remote.crypt->crypt(session, block, &session->remote.crypt_abstract)) { + libssh2_error(session, LIBSSH2_ERROR_DECRYPT, "Error decrypting packet preamble", 0); + LIBSSH2_FREE(session, block); + LIBSSH2_FREE(session, payload); + return -1; + } + memcpy(s, block, blocksize); + } + + s += blocksize; + } + + read_len = libssh2_blocking_read(session, block, session->remote.mac->mac_len); + if (read_len < session->remote.mac->mac_len) { + LIBSSH2_FREE(session, block); + LIBSSH2_FREE(session, payload); + return -1; + } + + /* Calculate MAC hash */ + session->remote.mac->hash(session, block + session->remote.mac->mac_len, session->remote.seqno, tmp, 5, payload, payload_len, &session->remote.mac_abstract); + + macstate = (strncmp(block, block + session->remote.mac->mac_len, session->remote.mac->mac_len) == 0) ? LIBSSH2_MAC_CONFIRMED : LIBSSH2_MAC_INVALID; +/* SMG */ +if (macstate == LIBSSH2_MAC_INVALID) libssh2_error(session, -255, "EEEK an error!", 0); + + session->remote.seqno++; + + LIBSSH2_FREE(session, block); + + /* Ignore padding */ + payload_len -= padding_len; + + if (session->remote.comp && + strcmp(session->remote.comp->name, "none")) { + /* Decompress */ + unsigned char *data; + unsigned long data_len; + + if (session->remote.comp->comp(session, 0, &data, &data_len, LIBSSH2_PACKET_MAXDECOMP, &free_payload, payload, payload_len, &session->remote.comp_abstract)) { + LIBSSH2_FREE(session, payload); + return -1; + } + if (free_payload) { + LIBSSH2_FREE(session, payload); + payload = data; + payload_len = data_len; + } else { + if (data == payload) { + /* It's not to be freed, because the compression layer reused payload, + * So let's do the same! + */ + payload_len = data_len; + } else { + /* No comp_method actually lets this happen, but let's prepare for the future */ + + LIBSSH2_FREE(session, payload); + + /* We need a freeable struct otherwise the brigade won't know what to do with it */ + payload = LIBSSH2_ALLOC(session, data_len); + if (!payload) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for copy of uncompressed data", 0); + LIBSSH2_FREE(session, block); + return -1; + } + memcpy(payload, data, data_len); + payload_len = data_len; + } + } + } + + libssh2_packet_add(session, payload, payload_len, macstate); + + packet_type = payload[0]; + } else { /* No cipher active */ + unsigned char *payload; + unsigned char buf[24]; + unsigned long buf_len, payload_len; + unsigned long packet_length; + unsigned long padding_length; + + if (should_block) { + buf_len = libssh2_blocking_read(session, buf, 5); + } else { + buf_len = read(session->socket_fd, buf, 1); + if (buf_len <= 0) { + return 0; + } + buf_len += libssh2_blocking_read(session, buf, 5 - buf_len); + } + if (buf_len < 5) { + /* Something bad happened */ + return -1; + } + packet_length = libssh2_ntohu32(buf); + padding_length = buf[4]; + + payload_len = packet_length - padding_length - 1; /* padding_length(1) */ + payload = LIBSSH2_ALLOC(session, payload_len); + + if (libssh2_blocking_read(session, payload, payload_len) < payload_len) { + return (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) ? 0 : -1; + } + while (padding_length) { + /* Flush padding */ + padding_length -= libssh2_blocking_read(session, buf, padding_length); + } + + /* MACs don't exist in non-encrypted mode */ + libssh2_packet_add(session, payload, payload_len, LIBSSH2_MAC_CONFIRMED); + session->remote.seqno++; + + packet_type = payload[0]; + } + return packet_type; +} +/* }}} */ + +/* {{{ libssh2_packet_ask + * Scan the brigade for a matching packet type, optionally poll the socket for a packet first + */ +int libssh2_packet_ask_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, + unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket) +{ + LIBSSH2_PACKET *packet = session->packets.head; + + if (poll_socket) { + if (libssh2_packet_read(session, 0) < 0) { + return -1; + } + } + while (packet) { + if (packet->data[0] == packet_type && + (packet->data_len >= (match_ofs + match_len)) && + (!match_buf || (strncmp(packet->data + match_ofs, match_buf, match_len) == 0))) { + *data = packet->data; + *data_len = packet->data_len; + + if (packet->prev) { + packet->prev->next = packet->next; + } else { + session->packets.head = packet->next; + } + + if (packet->next) { + packet->next->prev = packet->prev; + } else { + session->packets.tail = packet->prev; + } + + LIBSSH2_FREE(session, packet); + + return 0; + } + packet = packet->next; + } + return -1; +} +/* }}} */ + +/* {{{ libssh2_packet_require + * Loops libssh2_packet_read() until the packet requested is available + * SSH_DISCONNECT will cause a bailout though + */ +int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, + unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len) +{ + if (libssh2_packet_ask_ex(session, packet_type, data, data_len, match_ofs, match_buf, match_len, 0) == 0) { + /* A packet was available in the packet brigade */ + return 0; + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + int ret = libssh2_packet_read(session, 1); + if (ret < 0) { + return -1; + } + if (ret == 0) continue; + + if (packet_type == ret) { + /* Be lazy, let packet_ask pull it out of the brigade */ + return libssh2_packet_ask_ex(session, packet_type, data, data_len, match_ofs, match_buf, match_len, 0); + } + } + + /* Only reached if the socket died */ + return -1; +} +/* }}} */ + +/* {{{ libssh2_packet_write + * Send a packet, encrypting it and adding a MAC code if necessary + * Returns 0 on success, non-zero on failure + */ +int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data, unsigned long data_len) +{ + unsigned long packet_length = data_len + 1; + unsigned long block_size = (session->newkeys) ? session->local.crypt->blocksize : 8; + /* At this point packet_length doesn't include the packet_len field itself */ + unsigned long padding_length; + int free_data = 0; + unsigned char buf[246]; /* 6 byte header plus max padding size(240) */ + int i; + + if (session->newkeys && + strcmp(session->local.comp->name, "none")) { + + if (session->local.comp->comp(session, 1, &data, &data_len, LIBSSH2_PACKET_MAXCOMP, &free_data, data, data_len, &session->local.comp_abstract)) { + return -1; + } + } + + fcntl(session->socket_fd, F_SETFL, 0); + packet_length = data_len + 1; /* padding_length(1) -- MAC doesn't count -- Padding to be added soon */ + padding_length = block_size - ((packet_length + 4) % block_size); + if (padding_length < 4) { + padding_length += block_size; + } + /* TODO: Maybe add 1 or 2 times block_size to padding_length randomly -- shake things up a bit... */ + + packet_length += padding_length; + libssh2_htonu32(buf, packet_length); + buf[4] = padding_length; + + for (i = 0; i < padding_length; i++) { + /* Make random */ + buf[5 + i] = '\0'; + } + + if (session->newkeys) { + /* Encryption is in effect */ + unsigned char *encbuf, *s; + int ret; + + /* Safely ignored in CUSTOM cipher mode */ + EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)session->local.crypt_abstract; + + /* include packet_length(4) itself and room for the hash at the end */ + encbuf = LIBSSH2_ALLOC(session, 4 + packet_length + session->local.mac->mac_len); + if (!encbuf) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate encryption buffer", 0); + if (free_data) { + LIBSSH2_FREE(session, data); + } + return -1; + } + + /* Copy packet to encoding buffer */ + memcpy(encbuf, buf, 5); + memcpy(encbuf + 5, data, data_len); + memcpy(encbuf + 5 + data_len, buf + 5, padding_length); + if (free_data) { + LIBSSH2_FREE(session, data); + } + + /* Calculate MAC hash */ + session->local.mac->hash(session, encbuf + 4 + packet_length , session->local.seqno, encbuf, 4 + packet_length, NULL, 0, &session->local.mac_abstract); + + /* Encrypt data */ + for(s = encbuf; (s - encbuf) < (4 + packet_length) ; s += session->local.crypt->blocksize) { + if (session->local.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + EVP_Cipher(ctx, buf, s, session->local.crypt->blocksize); + memcpy(s, buf, session->local.crypt->blocksize); + } else { + session->local.crypt->crypt(session, s, &session->local.crypt_abstract); + } + } + + session->local.seqno++; + + /* Send It */ + ret = ((4 + packet_length + session->local.mac->mac_len) == write(session->socket_fd, encbuf, 4 + packet_length + session->local.mac->mac_len)) ? 0 : -1; + + /* Cleanup environment */ + LIBSSH2_FREE(session, encbuf); + + return ret; + } else { /* LIBSSH2_ENDPOINT_CRYPT_NONE */ + /* Simplified write for non-encrypted mode */ + struct iovec data_vector[3]; + + /* Using vectors means we don't have to alloc a new buffer -- a byte saved is a byte earned + * No MAC during unencrypted phase + */ + data_vector[0].iov_base = buf; + data_vector[0].iov_len = 5; + data_vector[1].iov_base = (char*)data; + data_vector[1].iov_len = data_len; + data_vector[2].iov_base = buf + 5; + data_vector[2].iov_len = padding_length; + + session->local.seqno++; + + /* Ignore this, it can't actually happen :) */ + if (free_data) { + LIBSSH2_FREE(session, data); + } + + return ((packet_length + 4) == writev(session->socket_fd, data_vector, 3)) ? 0 : 1; + } +} +/* }}} */ diff --git a/src/scp.c b/src/scp.c new file mode 100644 index 00000000..07d4ec53 --- /dev/null +++ b/src/scp.c @@ -0,0 +1,408 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include +#include +#include + +#define LIBSSH2_SCP_RESPONSE_BUFLEN 256 + +/* {{{ libssh2_scp_recv + * Open a channel and request a remote file via SCP + */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, char *path, struct stat *sb) +{ + int path_len = strlen(path); + unsigned char *command, response[LIBSSH2_SCP_RESPONSE_BUFLEN]; + unsigned long command_len = path_len + sizeof("scp -f "), response_len; + LIBSSH2_CHANNEL *channel; + long mode = 0, size = 0, mtime = 0, atime = 0; + + if (sb) { + command_len++; + } + + command = LIBSSH2_ALLOC(session, command_len); + if (!command) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a command buffer for scp session", 0); + return NULL; + } + if (sb) { + memcpy(command, "scp -pf ", sizeof("scp -pf ") - 1); + memcpy(command + sizeof("scp -pf ") - 1, path, path_len); + } else { + memcpy(command, "scp -f ", sizeof("scp -f ") - 1); + memcpy(command + sizeof("scp -f ") - 1, path, path_len); + } + command[command_len - 1] = '\0'; + + /* Allocate a channel */ + if ((channel = libssh2_channel_open_session(session)) == NULL) { + LIBSSH2_FREE(session, command); + return NULL; + } + + /* Request SCP for the desired file */ + if (libssh2_channel_process_startup(channel, "exec", sizeof("exec") - 1, command, command_len)) { + LIBSSH2_FREE(session, command); + libssh2_channel_free(channel); + return NULL; + } + LIBSSH2_FREE(session, command); + + /* SCP ACK */ + response[0] = '\0'; + if (libssh2_channel_write(channel, response, 1) != 1) { + libssh2_channel_free(channel); + return NULL; + } + + /* Parse SCP response */ + response_len = 0; + while (sb && (response_len < LIBSSH2_SCP_RESPONSE_BUFLEN)) { + unsigned char *s, *p; + + if (libssh2_channel_read(channel, response + response_len, 1) <= 0) { + /* Timeout, give up */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0); + libssh2_channel_free(channel); + return NULL; + } + response_len++; + + if (response[0] != 'T') { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response, missing Time data", 0); + libssh2_channel_free(channel); + return NULL; + } + + if ((response_len > 1) && + ((response[response_len-1] < '0') || (response[response_len-1] > '9')) && + (response[response_len-1] != ' ') && + (response[response_len-1] != '\r') && + (response[response_len-1] != '\n')) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0); + libssh2_channel_free(channel); + return NULL; + } + + if ((response_len < 9) || (response[response_len-1] != '\n')) { + if (response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) { + /* You had your chance */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0); + libssh2_channel_free(channel); + return NULL; + } + /* Way too short to be an SCP response, or not done yet, short circuit */ + continue; + } + + /* We're guaranteed not to go under response_len == 0 by the logic above */ + while ((response[response_len-1] == '\r') || (response[response_len-1] == '\n')) response_len--; + response[response_len] = '\0'; + + if (response_len < 8) { + /* EOL came too soon */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0); + libssh2_channel_free(channel); + return NULL; + } + + s = response + 1; + + p = strchr(s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mtime", 0); + libssh2_channel_free(channel); + return NULL; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + errno = 0; + mtime = strtol(s, NULL, 10); + if (errno) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mtime", 0); + libssh2_channel_free(channel); + return NULL; + } + s = strchr(p, ' '); + if (!s || ((s - p) <= 0)) { + /* No spaces or space in the wrong spot */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mtime.usec", 0); + libssh2_channel_free(channel); + return NULL; + } + + /* Ignore mtime.usec */ + s++; + p = strchr(s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short or malformed", 0); + libssh2_channel_free(channel); + return NULL; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + errno = 0; + atime = strtol(s, NULL, 10); + if (errno) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid atime", 0); + libssh2_channel_free(channel); + return NULL; + } + + /* SCP ACK */ + response[0] = '\0'; + if (libssh2_channel_write(channel, response, 1) != 1) { + libssh2_channel_free(channel); + return NULL; + } + + /* We *should* check that atime.usec is valid, but why let that stop use? */ + break; + } + + response_len = 0; + while (response_len < LIBSSH2_SCP_RESPONSE_BUFLEN) { + char *s, *p, *e = NULL; + + if (libssh2_channel_read(channel, response + response_len, 1) <= 0) { + /* Timeout, give up */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0); + libssh2_channel_free(channel); + return NULL; + } + response_len++; + + if (response[0] != 'C') { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server", 0); + libssh2_channel_free(channel); + return NULL; + } + + if ((response_len > 1) && + (response[response_len-1] != '\r') && + (response[response_len-1] != '\n') && + ((response[response_len-1] < 32) || (response[response_len-1] > 126))) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0); + libssh2_channel_free(channel); + return NULL; + } + + if ((response_len < 7) || (response[response_len-1] != '\n')) { + if (response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) { + /* You had your chance */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0); + libssh2_channel_free(channel); + return NULL; + } + /* Way too short to be an SCP response, or not done yet, short circuit */ + continue; + } + + /* We're guaranteed not to go under response_len == 0 by the logic above */ + while ((response[response_len-1] == '\r') || (response[response_len-1] == '\n')) response_len--; + response[response_len] = '\0'; + + if (response_len < 6) { + /* EOL came too soon */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0); + libssh2_channel_free(channel); + return NULL; + } + + s = response + 1; + + p = strchr(s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mode", 0); + libssh2_channel_free(channel); + return NULL; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + errno = 0; + mode = strtol(s, &e, 8); + if ((e && *e) || errno) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mode", 0); + libssh2_channel_free(channel); + return NULL; + } + + s = strchr(p, ' '); + if (!s || ((s - p) <= 0)) { + /* No spaces or space in the wrong spot */ + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short or malformed", 0); + libssh2_channel_free(channel); + return NULL; + } + + *(s++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + errno = 0; + size = strtol(p, &e, 10); + if ((e && *e) || errno) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid size", 0); + libssh2_channel_free(channel); + return NULL; + } + + /* SCP ACK */ + response[0] = '\0'; + if (libssh2_channel_write(channel, response, 1) != 1) { + libssh2_channel_free(channel); + return NULL; + } + + /* We *should* check that basename is valid, but why let that stop us? */ + break; + } + + if (sb) { + memset(sb, 0, sizeof(struct stat)); + + sb->st_mtime = mtime; + sb->st_atime = atime; + sb->st_size = size; + sb->st_mode = mode; + } + /* Let the data BEGIN! */ + + return channel; +} +/* }}} */ + +/* {{{ libssh2_scp_send_ex + * Send a file using SCP + */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, char *path, int mode, size_t size, long mtime, long atime) +{ + int path_len = strlen(path); + unsigned char *command, *base, response[LIBSSH2_SCP_RESPONSE_BUFLEN]; + unsigned long response_len, command_len = path_len + sizeof("scp -t "); + LIBSSH2_CHANNEL *channel; + + if (mtime || atime) { + command_len++; + } + + command = LIBSSH2_ALLOC(session, command_len); + if (!command) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a command buffer for scp session", 0); + return NULL; + } + + if (mtime || atime) { + memcpy(command, "scp -pt ", sizeof("scp -pt ") - 1); + memcpy(command + sizeof("scp -pt ") - 1, path, path_len); + } else { + memcpy(command, "scp -t ", sizeof("scp -t ") - 1); + memcpy(command + sizeof("scp -t ") - 1, path, path_len); + } + command[command_len - 1] = '\0'; + + /* Allocate a channel */ + if ((channel = libssh2_channel_open_session(session)) == NULL) { + LIBSSH2_FREE(session, command); + return NULL; + } + + /* Request SCP for the desired file */ + if (libssh2_channel_process_startup(channel, "exec", sizeof("exec") - 1, command, command_len)) { + LIBSSH2_FREE(session, command); + libssh2_channel_free(channel); + return NULL; + } + LIBSSH2_FREE(session, command); + + /* Wait for ACK */ + if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); + libssh2_channel_free(channel); + return NULL; + } + + /* Send mtime and atime to be used for file */ + if (mtime || atime) { + response_len = snprintf(response, LIBSSH2_SCP_RESPONSE_BUFLEN, "T%ld 0 %ld 0\n", mtime, atime); + if (libssh2_channel_write(channel, response, response_len) != response_len) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send time data for SCP file", 0); + libssh2_channel_free(channel); + return NULL; + } + /* Wait for ACK */ + if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); + libssh2_channel_free(channel); + return NULL; + } + } + + /* Send mode, size, and basename */ + base = strrchr(path, '/'); + if (base) { + base++; + } else { + base = path; + } + + response_len = snprintf(response, LIBSSH2_SCP_RESPONSE_BUFLEN, "C0%o %lu %s\n", mode, (unsigned long)size, base); + if (libssh2_channel_write(channel, response, response_len) != response_len) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send core file data for SCP file", 0); + libssh2_channel_free(channel); + return NULL; + } + /* Wait for ACK */ + if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) { + libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); + libssh2_channel_free(channel); + return NULL; + } + + /* Ready to send file */ + + return channel; +} +/* }}} */ + diff --git a/src/session.c b/src/session.c new file mode 100644 index 00000000..0be6a79a --- /dev/null +++ b/src/session.c @@ -0,0 +1,372 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include +#include +#include + +/* {{{ libssh2_default_alloc + */ +static LIBSSH2_ALLOC_FUNC(libssh2_default_alloc) +{ + return malloc(count); +} +/* }}} */ + +/* {{{ libssh2_default_free + */ +static LIBSSH2_FREE_FUNC(libssh2_default_free) +{ + free(ptr); +} +/* }}} */ + +/* {{{ libssh2_default_realloc + */ +static LIBSSH2_REALLOC_FUNC(libssh2_default_realloc) +{ + return realloc(ptr, count); +} +/* }}} */ + +/* {{{ libssh2_banner_receive + * Wait for a hello from the remote host + * Allocate a buffer and store the banner in session->remote.banner + * Returns: 0 on success, 1 on failure + */ +static int libssh2_banner_receive(LIBSSH2_SESSION *session) +{ + char banner[256]; + int banner_len = 0; + + while ((banner_len < sizeof(banner)) && + ((banner_len == 0) || (banner[banner_len-1] != '\n'))) { + char c = '\0'; + int ret; + + ret = read(session->socket_fd, &c, 1); + + if ((ret < 0) && (ret != EAGAIN)) { + /* Some kinda error, but don't break for non-blocking issues */ + return 1; + } + + if (ret <= 0) continue; + + if (c == '\0') { + /* NULLs are not allowed in SSH banners */ + return 1; + } + + banner[banner_len++] = c; + } + + while (banner_len && + ((banner[banner_len-1] == '\n') || (banner[banner_len-1] == '\r'))) { + banner_len--; + } + + if (!banner_len) return 1; + + session->remote.banner = LIBSSH2_ALLOC(session, banner_len + 1); + memcpy(session->remote.banner, banner, banner_len); + session->remote.banner[banner_len] = '\0'; + return 0; +} +/* }}} */ + +/* {{{ libssh2_banner_send + * Send the default banner, or the one set via libssh2_setopt_string + */ +static int libssh2_banner_send(LIBSSH2_SESSION *session) +{ + char *banner = LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF; + int banner_len = sizeof(LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF) - 1; + + if (session->local.banner) { + /* setopt_string will have given us our \r\n characters */ + banner_len = strlen(session->local.banner); + banner = session->local.banner; + } + + return (write(session->socket_fd, banner, banner_len) == banner_len) ? 0 : 1; +} +/* }}} */ + +/* {{{ proto libssh2_session_init + * Allocate and initialize a libssh2 session structure + * Allows for malloc callbacks in case the calling program has its own memory manager + * It's allowable (but unadvisable) to define some but not all of the malloc callbacks + * An additional pointer value may be optionally passed to be sent to the callbacks (so they know who's asking) + */ +LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex( + LIBSSH2_ALLOC_FUNC((*my_alloc)), + LIBSSH2_FREE_FUNC((*my_free)), + LIBSSH2_REALLOC_FUNC((*my_realloc)), + void *abstract) +{ + LIBSSH2_ALLOC_FUNC((*local_alloc)) = libssh2_default_alloc; + LIBSSH2_FREE_FUNC((*local_free)) = libssh2_default_free; + LIBSSH2_REALLOC_FUNC((*local_realloc)) = libssh2_default_realloc; + LIBSSH2_SESSION *session; + + if (my_alloc) local_alloc = my_alloc; + if (my_free) local_free = my_free; + if (my_realloc) local_realloc = my_realloc; + + session = local_alloc(sizeof(LIBSSH2_SESSION), abstract); + memset(session, 0, sizeof(LIBSSH2_SESSION)); + session->alloc = local_alloc; + session->free = local_free; + session->realloc = local_realloc; + session->abstract = abstract; + + return session; +} +/* }}} */ + +/* {{{ proto libssh2_session_startup + * session: LIBSSH2_SESSION struct allocated and owned by the calling program + * Returns: 0 on success, or non-zero on failure + * Any memory allocated by libssh2 will use alloc/realloc/free callbacks in session + * socket *must* be populated with an opened socket + */ +LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int socket) +{ + unsigned char *data; + unsigned long data_len; + unsigned char service[sizeof("ssh-userauth") + 5 - 1]; + unsigned long service_length; + + if (socket <= 0) { + /* Did we forget something? */ + libssh2_error(session, LIBSSH2_ERROR_SOCKET_NONE, "No socket provided", 0); + return LIBSSH2_ERROR_SOCKET_NONE; + } + session->socket_fd = socket; + + /* TODO: Liveness check */ + + if (libssh2_banner_receive(session)) { + /* Unable to receive banner from remote */ + libssh2_error(session, LIBSSH2_ERROR_BANNER_NONE, "Timeout waiting for banner", 0); + return LIBSSH2_ERROR_BANNER_NONE; + } + + if (libssh2_banner_send(session)) { + /* Unable to send banner? */ + libssh2_error(session, LIBSSH2_ERROR_BANNER_SEND, "Error sending banner to remote host", 0); + return LIBSSH2_ERROR_BANNER_SEND; + } + + if (libssh2_kex_exchange(session, 0)) { + libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to exchange encryption keys", 0); + return LIBSSH2_ERROR_KEX_FAILURE; + } + + /* Request the userauth service */ + service[0] = SSH_MSG_SERVICE_REQUEST; + libssh2_htonu32(service + 1, sizeof("ssh-userauth") - 1); + memcpy(service + 5, "ssh-userauth", sizeof("ssh-userauth") - 1); + if (libssh2_packet_write(session, service, sizeof("ssh-userauth") + 5 - 1)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to ask for ssh-userauth service", 0); + return LIBSSH2_ERROR_SOCKET_SEND; + } + + if (libssh2_packet_require(session, SSH_MSG_SERVICE_ACCEPT, &data, &data_len)) { + return LIBSSH2_ERROR_SOCKET_DISCONNECT; + } + service_length = libssh2_ntohu32(data + 1); + + if ((service_length != (sizeof("ssh-userauth") - 1)) || + strncmp("ssh-userauth", data + 5, service_length)) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_PROTO, "Invalid response received from server", 0); + return LIBSSH2_ERROR_PROTO; + } + LIBSSH2_FREE(session, data); + + return 0; +} +/* }}} */ + +/* {{{ proto libssh2_session_free + * Frees the memory allocated to the session + * Also closes and frees any channels attached to this session + */ +LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session) +{ + while (session->channels.head) { + LIBSSH2_CHANNEL *tmp = session->channels.head; + + libssh2_channel_free(session->channels.head); + if (tmp == session->channels.head) { + /* channel_free couldn't do it's job, perform a messy cleanup */ + tmp = session->channels.head; + + /* unlink */ + session->channels.head = tmp->next; + + /* free */ + LIBSSH2_FREE(session, tmp); + + /* reverse linking isn't important here, we're killing the structure */ + } + } + + if (session->newkeys) { + /* hostkey */ + if (session->hostkey && session->hostkey->dtor) { + session->hostkey->dtor(session, &session->server_hostkey_abstract); + } + + /* Client to Server */ + /* crypt */ + if (session->local.crypt) { + if (session->local.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + if (session->local.crypt_abstract) { + LIBSSH2_FREE(session, session->local.crypt_abstract); + session->local.crypt_abstract = NULL; + } + } else if (session->local.crypt->dtor) { + session->local.crypt->dtor(session, &session->local.crypt_abstract); + } + } + /* comp */ + if (session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, &session->local.comp_abstract); + } + /* mac */ + if (session->local.mac && session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + /* Server to Client */ + /* crypt */ + if (session->remote.crypt) { + if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { + if (session->remote.crypt_abstract) { + LIBSSH2_FREE(session, session->remote.crypt_abstract); + session->remote.crypt_abstract = NULL; + } + } else if (session->remote.crypt->dtor) { + session->remote.crypt->dtor(session, &session->remote.crypt_abstract); + } + } + /* comp */ + if (session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, &session->remote.comp_abstract); + } + /* mac */ + if (session->remote.mac && session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + /* session_id */ + if (session->session_id) { + LIBSSH2_FREE(session, session->session_id); + } + } + + if (session->remote.banner) { + LIBSSH2_FREE(session, session->remote.banner); + } + if (session->local.banner) { + LIBSSH2_FREE(session, session->local.banner); + } + + /* Cleanup any remaining packets */ + while (session->packets.head) { + LIBSSH2_PACKET *tmp = session->packets.head; + + /* unlink */ + session->packets.head = tmp->next; + + /* free */ + LIBSSH2_FREE(session, tmp->data); + LIBSSH2_FREE(session, tmp); + } + + if (session->local.banner) { + LIBSSH2_FREE(session, session->local.banner); + } + + LIBSSH2_FREE(session, session); +} +/* }}} */ + +/* {{{ libssh2_session_disconnect_ex + */ +LIBSSH2_API void libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, char *description, char *lang) +{ + unsigned char *data; + unsigned long data_len, descr_len = 0, lang_len = 0; + + if (description) { + descr_len = strlen(description); + } + if (lang) { + lang_len = strlen(lang); + } + data_len = descr_len + lang_len + 13; /* packet_type(1) + reason code(4) + descr_len(4) + lang_len(4) */ + + data = LIBSSH2_ALLOC(session, data_len); + if (data) { + unsigned char *s = data; + + *(s++) = SSH_MSG_DISCONNECT; + libssh2_htonu32(s, reason); s += 4; + + libssh2_htonu32(s, descr_len); s += 4; + if (description) { + memcpy(s, description, descr_len); + s += descr_len; + } + + libssh2_htonu32(s, lang_len); s += 4; + if (lang) { + memcpy(s, lang, lang_len); + s += lang_len; + } + + libssh2_packet_write(session, data, data_len); + + LIBSSH2_FREE(session, data); + } +} +/* }}} */ diff --git a/src/userauth.c b/src/userauth.c new file mode 100644 index 00000000..a84bf3c5 --- /dev/null +++ b/src/userauth.c @@ -0,0 +1,503 @@ +/* Copyright (c) 2004, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include + +/* {{{ proto libssh2_userauth_list + * List authentication methods + * Will yield successful login if "none" happens to be allowable for this user + * Not a common configuration for any SSH server though + * username should be NULL, or a null terminated string + */ +LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, char *username, int username_len) +{ + unsigned long data_len = username_len + 31; /* packet_type(1) + username_len(4) + service_len(4) + service(14)"ssh-connection" + + method_len(4) + method(4)"none" */ + unsigned long methods_len; + unsigned char *data, *s; + + s = data = LIBSSH2_ALLOC(session, data_len); + if (!data) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth_list", 0); + return NULL; + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + libssh2_htonu32(s, username_len); s += 4; + if (username) { + memcpy(s, username, username_len); s += username_len; + } + + libssh2_htonu32(s, 14); s += 4; + memcpy(s, "ssh-connection", 14); s += 14; + + libssh2_htonu32(s, 4); s += 4; + memcpy(s, "none", 4); s += 4; + + if (libssh2_packet_write(session, data, data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-none request", 0); + LIBSSH2_FREE(session, data); + return NULL; + } + LIBSSH2_FREE(session, data); + + while (1) { + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_SUCCESS, &data, &data_len, 1) == 0) { + /* Wow, who'dve thought... */ + LIBSSH2_FREE(session, data); + session->authenticated = 1; + return NULL; + } + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_FAILURE, &data, &data_len, 0) == 0) { + /* What we *actually* wanted to happen */ + break; + } + /* TODO: Timeout? */ + } + + methods_len = libssh2_ntohu32(data + 1); + memcpy(data, data + 5, methods_len); + data[methods_len] = '\0'; + + return data; +} +/* }}} */ + +/* {{{ libssh2_userauth_authenticated + * 0 if not yet authenticated + * non-zero is already authenticated + */ +LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session) +{ + return session->authenticated; +} +/* }}} */ + +/* {{{ libssh2_userauth_password + * Plain ol' login + */ +LIBSSH2_API int libssh2_userauth_password_ex(LIBSSH2_SESSION *session, char *username, int username_len, + char *password, int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))) +{ + unsigned char *data, *s; + unsigned long data_len = username_len + password_len + 40; /* packet_type(1) + username_len(4) + service_len(4) + service(14)"ssh-connection" + + method_len(4) + method(8)"password" + chgpwdbool(1) + password_len(4) */ + + s = data = LIBSSH2_ALLOC(session, data_len); + if (!data) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth-password request", 0); + return -1; + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + libssh2_htonu32(s, username_len); s += 4; + memcpy(s, username, username_len); s += username_len; + + libssh2_htonu32(s, sizeof("ssh-connection") - 1); s += 4; + memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1); s += sizeof("ssh-connection") - 1; + + libssh2_htonu32(s, sizeof("password") - 1); s += 4; + memcpy(s, "password", sizeof("password") - 1); s += sizeof("password") - 1; + + *s = '\0'; s++; + + libssh2_htonu32(s, password_len); s += 4; + memcpy(s, password, password_len); s += password_len; + + if (libssh2_packet_write(session, data, data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-password request", 0); + LIBSSH2_FREE(session, data); + return -1; + } + LIBSSH2_FREE(session, data); + + while (1) { + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_SUCCESS, &data, &data_len, 1) == 0) { + LIBSSH2_FREE(session, data); + session->authenticated = 1; + return 0; + } + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_FAILURE, &data, &data_len, 0) == 0) { + LIBSSH2_FREE(session, data); + return -1; + } + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, &data, &data_len, 0) == 0) { + char *newpw = NULL; + int newpw_len = 0; + + LIBSSH2_FREE(session, data); + if (passwd_change_cb) { + passwd_change_cb(session, &newpw, &newpw_len, &session->abstract); + if (!newpw) { + libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, "Password expired, and callback failed", 0); + return -1; + } + data_len = username_len + password_len + 44 + newpw_len; /* basic data_len + newpw_len(4) */ + s = data = LIBSSH2_ALLOC(session, data_len); + if (!data) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth-password-change request", 0); + return -1; + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + libssh2_htonu32(s, username_len); s += 4; + memcpy(s, username, username_len); s += username_len; + + libssh2_htonu32(s, sizeof("ssh-connection") - 1); s += 4; + memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1); s += sizeof("ssh-connection") - 1; + + libssh2_htonu32(s, sizeof("password") - 1); s += 4; + memcpy(s, "password", sizeof("password") - 1); s += sizeof("password") - 1; + + *s = 0xFF; s++; + + libssh2_htonu32(s, password_len); s += 4; + memcpy(s, password, password_len); s += password_len; + + libssh2_htonu32(s, newpw_len); s += 4; + memcpy(s, newpw, newpw_len); s += newpw_len; + + if (libssh2_packet_write(session, data, data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-password-change request", 0); + LIBSSH2_FREE(session, data); + return -1; + } + LIBSSH2_FREE(session, data); + LIBSSH2_FREE(session, newpw); + /* TODO: Reset timeout? */ + } else { + libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, "Password Expired, and no callback specified", 0); + return -1; + } + } + /* TODO: Timeout? */ + } + + return 0; +} +/* }}} */ + +/* {{{ libssh2_file_read_publickey + * Read a public key from an id_???.pub style file + */ +static int libssh2_file_read_publickey(LIBSSH2_SESSION *session, unsigned char **method, unsigned long *method_len, + unsigned char **pubkeydata, unsigned long *pubkeydata_len, + char *pubkeyfile) +{ + FILE *fd; + char *pubkey = NULL, c, *sp1, *sp2, *tmp; + int pubkey_len = 0, tmp_len; + + /* Read Public Key */ + fd = fopen(pubkeyfile, "r"); + if (!fd) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to open public key file", 0); + return -1; + } + while (!feof(fd) && (c = fgetc(fd)) != '\r' && c != '\n') pubkey_len++; + rewind(fd); + + if (pubkey_len <= 1) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid data in public key file", 0); + fclose(fd); + return -1; + } + + pubkey = LIBSSH2_ALLOC(session, pubkey_len); + if (!pubkey) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for public key data", 0); + fclose(fd); + return -1; + } + if (fread(pubkey, 1, pubkey_len, fd) != pubkey_len) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to read public key from file", 0); + LIBSSH2_FREE(session, pubkey); + fclose(fd); + return -1; + } + fclose(fd); + while (pubkey_len && (pubkey[pubkey_len-1] == '\r' || pubkey[pubkey_len-1] == '\n')) pubkey_len--; + + if (!pubkey_len) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Missing public key data", 0); + LIBSSH2_FREE(session, pubkey); + return -1; + } + + if ((sp1 = memchr(pubkey, ' ', pubkey_len)) == NULL) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid public key data", 0); + LIBSSH2_FREE(session, pubkey); + return -1; + } + /* Wasting some bytes here (okay, more than some), + * but since it's likely to be freed soon anyway, + * we'll just avoid the extra free/alloc and call it a wash */ + *method = pubkey; + *method_len = sp1 - pubkey; + + sp1++; + + if ((sp2 = memchr(sp1, ' ', pubkey_len - *method_len)) == NULL) { + /* Assume that the id string is missing, but that it's okay */ + sp2 = pubkey + pubkey_len; + } + + if (libssh2_base64_decode(session, &tmp, &tmp_len, sp1, sp2 - sp1)) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid key data, not base64 encoded", 0); + LIBSSH2_FREE(session, pubkey); + return -1; + } + *pubkeydata = tmp; + *pubkeydata_len = tmp_len; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_file_read_publickey + * Read a PEM encoded private key from an id_??? style file + */ +static int libssh2_file_read_privatekey(LIBSSH2_SESSION *session, LIBSSH2_HOSTKEY_METHOD **hostkey_method, void **hostkey_abstract, + char *method, int method_len, + char *privkeyfile, char *passphrase) +{ + LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = session->hostkey_prefs ? session->hostkey_prefs : libssh2_hostkey_methods(); + + *hostkey_method = NULL; + *hostkey_abstract = NULL; + while (*hostkey_methods_avail && (*hostkey_methods_avail)->name) { + if ((*hostkey_methods_avail)->initPEM && + strncmp((*hostkey_methods_avail)->name, method, method_len) == 0) { + *hostkey_method = *hostkey_methods_avail; + break; + } + hostkey_methods_avail++; + } + if (!*hostkey_method) { + libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, "No handler for specified private key", 0); + return -1; + } + + if ((*hostkey_method)->initPEM(session, privkeyfile, passphrase, hostkey_abstract)) { + libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to initialize private key from file", 0); + return -1; + } + + return 0; +} +/* }}} */ + +/* {{{ libssh2_userauth_publickey_fromfile_ex + * Authenticate using a keypair found in the named files + */ +LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, char *username, int username_len, + char *publickey, char *privatekey, + char *passphrase) +{ + LIBSSH2_HOSTKEY_METHOD *privkeyobj; + void *abstract; + unsigned char buf[5]; + struct iovec datavec[4]; + unsigned char *method, *pubkeydata, *packet, *s, *b, *sig; + unsigned long method_len, pubkeydata_len, packet_len, sig_len; + + if (libssh2_file_read_publickey(session, &method, &method_len, &pubkeydata, &pubkeydata_len, publickey)) { + return -1; + } + + packet_len = username_len + method_len + pubkeydata_len + 45; /* packet_type(1) + username_len(4) + servicename_len(4) + + service_name(14)"ssh-connection" + authmethod_len(4) + + authmethod(9)"publickey" + sig_included(1)'\0' + + algmethod_len(4) + publickey_len(4) */ + /* Preallocate space for an overall length, method name again, + * and the signature, which won't be any larger than the size of the publickeydata itself */ + s = packet = LIBSSH2_ALLOC(session, packet_len + 4 + (4 + method_len) + (4 + pubkeydata_len)); + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + libssh2_htonu32(s, username_len); s += 4; + memcpy(s, username, username_len); s += username_len; + + libssh2_htonu32(s, 14); s += 4; + memcpy(s, "ssh-connection", 14); s += 14; + + libssh2_htonu32(s, 9); s += 4; + memcpy(s, "publickey", 9); s += 9; + + b = s; + *(s++) = 0; /* Not sending signature with *this* packet */ + + libssh2_htonu32(s, method_len); s += 4; + memcpy(s, method, method_len); s += method_len; + + libssh2_htonu32(s, pubkeydata_len); s += 4; + memcpy(s, pubkeydata, pubkeydata_len); s += pubkeydata_len; + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-publickey request", 0); + LIBSSH2_FREE(session, packet); + LIBSSH2_FREE(session, method); + LIBSSH2_FREE(session, pubkeydata); + return -1; + } + + while (1) { + unsigned char *data; + unsigned long data_len; + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_SUCCESS, &data, &data_len, 1) == 0) { + /* God help any SSH server that allows an UNVERIFIED public key to validate the user */ + LIBSSH2_FREE(session, data); + LIBSSH2_FREE(session, packet); + LIBSSH2_FREE(session, method); + LIBSSH2_FREE(session, pubkeydata); + session->authenticated = 1; + return 0; + } + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_FAILURE, &data, &data_len, 0) == 0) { + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, data); + LIBSSH2_FREE(session, packet); + LIBSSH2_FREE(session, method); + LIBSSH2_FREE(session, pubkeydata); + libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED, "Username/PublicKey combination invalid", 0); + return -1; + } + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_PK_OK, &data, &data_len, 0) == 0) { + /* Semi-Success! */ + if ((libssh2_ntohu32(data + 1) != method_len) || + strncmp(data + 5, method, method_len) || + (libssh2_ntohu32(data + 5 + method_len) != pubkeydata_len) || + strncmp(data + 5 + method_len + 4, pubkeydata, pubkeydata_len)) { + /* Unlikely but possible, the server has responded to a different userauth public key request */ + LIBSSH2_FREE(session, data); + continue; + } + LIBSSH2_FREE(session, data); + break; + } + /* TODO: Timeout? */ + } + LIBSSH2_FREE(session, pubkeydata); + + if (libssh2_file_read_privatekey(session, &privkeyobj, &abstract, method, method_len, privatekey, passphrase)) { + LIBSSH2_FREE(session, method); + LIBSSH2_FREE(session, packet); + return -1; + } + + *b = 0xFF; + + libssh2_htonu32(buf, session->session_id_len); + datavec[0].iov_base = buf; + datavec[0].iov_len = 4; + datavec[1].iov_base = session->session_id; + datavec[1].iov_len = session->session_id_len; + datavec[2].iov_base = packet; + datavec[2].iov_len = packet_len; + + if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) { + LIBSSH2_FREE(session, method); + LIBSSH2_FREE(session, packet); + if (privkeyobj->dtor) { + privkeyobj->dtor(session, &abstract); + } + return -1; + } + + if (privkeyobj->dtor) { + privkeyobj->dtor(session, &abstract); + } + + if (sig_len > pubkeydata_len) { + /* Should *NEVER* happen, but...well.. better safe than sorry */ + packet = LIBSSH2_REALLOC(session, packet, packet_len + 4 + (4 + method_len) + (4 + sig_len)); /* PK sigblob */ + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating additional space for userauth-publickey packet", 0); + LIBSSH2_FREE(session, method); + return -1; + } + } + + s = packet + packet_len; + + libssh2_htonu32(s, 4 + method_len + 4 + sig_len); s += 4; + + libssh2_htonu32(s, method_len); s += 4; + memcpy(s, method, method_len); s += method_len; + LIBSSH2_FREE(session, method); + + libssh2_htonu32(s, sig_len); s += 4; + memcpy(s, sig, sig_len); s += sig_len; + LIBSSH2_FREE(session, sig); + + if (libssh2_packet_write(session, packet, s - packet)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-publickey request", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + while (1) { + unsigned char *data; + unsigned long data_len; + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_SUCCESS, &data, &data_len, 1) == 0) { + /* We are us and we've proved it. */ + LIBSSH2_FREE(session, data); + session->authenticated = 1; + return 0; + } + + if (libssh2_packet_ask(session, SSH_MSG_USERAUTH_FAILURE, &data, &data_len, 0) == 0) { + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Invalid signature for supplied public key, or bad username/public key combination", 0); + return -1; + } + /* TODO: Timeout? */ + } + + return 0; +} +/* }}} */ diff --git a/ssh2_sample.c b/ssh2_sample.c new file mode 100644 index 00000000..21063c5d --- /dev/null +++ b/ssh2_sample.c @@ -0,0 +1,121 @@ +#include "libssh2.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int sock, i, auth_pw = 1; + struct sockaddr_in sin; + char *fingerprint; + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + + /* Ultra basic "connect to port 22 on localhost" + * Your code is responsible for creating the socket establishing the connection + */ + sock = socket(AF_INET, SOCK_STREAM, 0); + fcntl(sock, F_SETFL, 0); + sin.sin_family = AF_INET; + sin.sin_port = htons(22); + sin.sin_addr.s_addr = htonl(0x7F000001); + connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)); + + /* Create a session instance and start it up + * This will trade welcome banners, exchange keys, and setup crypto, compression, and MAC layers + */ + session = libssh2_session_init(); + if (libssh2_session_startup(session, sock)) { + fprintf(stderr, "Failure establishing SSH session\n"); + return -1; + } + + /* At this point we havn't authenticated, + * The first thing to do is check the hostkey's fingerprint against our known hosts + * Your app may have it hard coded, may go to a file, may present it to the user, that's your call + */ + fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); + printf("Fingerprint: "); + for(i = 0; i < 16; i++) { + printf("%02X ", (unsigned char)fingerprint[i]); + } + printf("\n"); + + if (auth_pw) { + /* We could authenticate via password */ + if (libssh2_userauth_password(session, "username", "password")) { + printf("Authentication by password failed.\n"); + goto shutdown; + } + } else { + /* Or by public key */ + if (libssh2_userauth_publickey_fromfile(session, "username", "/home/username/.ssh/id_rsa.pub", "/home/username/.ssh/id_rsa", "pasphrase")) { + printf("\tAuthentication by public key failed\n"); + goto shutdown; + } + } + + /* Request a shell */ + if (!(channel = libssh2_channel_open_session(session))) { + fprintf(stderr, "Unable to open a session\n"); + goto shutdown; + } + + /* Some environment variables may be set, + * It's up to the server which ones it'll allow though + */ + libssh2_channel_setenv(channel, "FOO", "bar"); + + /* Request a terminal with 'vanilla' terminal emulation + * See /etc/termcap for more options + */ + if (libssh2_channel_request_pty(channel, "vanilla")) { + fprintf(stderr, "Failed requesting pty\n"); + goto skip_shell; + } + + /* Open a SHELL on that pty */ + if (libssh2_channel_shell(channel)) { + fprintf(stderr, "Unable to request shell on allocated pty\n"); + goto shutdown; + } + + /* At this point the shell can be interacted with using + * libssh2_channel_read() + * libssh2_channel_read_stderr() + * libssh2_channel_write() + * libssh2_channel_write_stderr() + * + * Blocking mode may be (en|dis)abled with: libssh2_channel_set_blocking() + * If the server send EOF, libssh2_channel_eof() will return non-0 + * To send EOF to the server use: libssh2_channel_send_eof() + * A channel can be closed with: libssh2_channel_close() + * A channel can be freed with: libssh2_channel_free() + */ + + skip_shell: + if (channel) { + libssh2_channel_free(channel); + channel = NULL; + } + + /* Other channel types are supported via: + * libssh2_scp_send() + * libssh2_scp_recv() + * libssh2_channel_direct_tcpip() + */ + + shutdown: + + libssh2_session_disconnect(session, "Normal Shutdown, Thank you for playing"); + libssh2_session_free(session); + + sleep(1); + close(sock); + + return 0; +}