From: Petr Tesarik <ptesarik@suse.cz>
Date: Wed, 15 Apr 2015 12:50:49 +0200
Subject: Class to encode/decode SFTP packets
References: FATE#318874, bsc#917747
Patch-mainline: v0.8.16
Git-commit: 6854130651fc82a4055fe49e60be7e70a3458e6d
Git-commit: bb2833a563182ae9e0d3f0dc68d9c49536f61a54
Git-commit: e8a8df2b30120d8130b6007e7b14a489bd61e79b
Git-commit: 149dd520cc1a6419c1ec36a999c80072df74ec74
Git-commit: e7699507d1b2c54452687af22c155222f65a550a
Git-commit: d49008c86d26836ad6fcdc967741bf8dd3970953
Git-commit: 1988f5798476edcde4ad7bb2c577e755ccf610ff
Git-commit: c88dd77c52d591855a9f31217fcc43513d0e6d05
Git-commit: 920b35981bd0dcd4e7ada32d39bbd191f1d63281
Git-commit: 1854f1b84fe2bc95a7339f33ee5db34dc0af0320
Git-commit: 08c884ea6e9d9e0f537035be25cf6c37cc0dc269
Git-commit: 8c18a0bd9b7fefde98333ad97ee4ee4709465357

The sftp protocol is sufficiently flexible to allow saving makedumpfile's
output directly, but the sftp client is not (it has no way to copy standard
input to a target file). This is why kdumptool must implement the necessary
protocol commands itself.

The SFTPPacket class can be used both for creating client requests and for
parsing server replies.

Signed-off-by: Petr Tesarik <ptesarik@suse.cz>

---
 kdumptool/CMakeLists.txt    |    5 +
 kdumptool/sshtransfer.cc    |   83 +++++++++++++++++++
 kdumptool/sshtransfer.h     |   49 +++++++++++
 kdumptool/testsftppacket.cc |  189 ++++++++++++++++++++++++++++++++++++++++++++
 tests/CMakeLists.txt        |    4 
 tests/testsftppacket.sh     |  181 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 511 insertions(+)

--- a/kdumptool/CMakeLists.txt
+++ b/kdumptool/CMakeLists.txt
@@ -149,3 +149,8 @@ add_executable(testurldecode
     testurldecode.cc
 )
 target_link_libraries(testurldecode common ${EXTRA_LIBS})
+
+add_executable(testsftppacket
+    testsftppacket.cc
+)
+target_link_libraries(testsftppacket common ${EXTRA_LIBS})
--- a/kdumptool/sshtransfer.cc
+++ b/kdumptool/sshtransfer.cc
@@ -20,6 +20,7 @@
 #include <string>
 #include <cstdlib>
 #include <cerrno>
+#include <stdint.h>
 
 #if HAVE_LIBSSH2
 #   include <libssh2.h>
@@ -165,6 +166,88 @@ StringVector SSHTransfer::makeArgs(std::
 }
 
 //}}}
+//{{{ SFTPPacket ---------------------------------------------------------------
+
+/* -------------------------------------------------------------------------- */
+SFTPPacket::SFTPPacket(void)
+    : m_vector(sizeof(uint32_t)),
+      m_gpos(0)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+void SFTPPacket::addByteVector(ByteVector const &val)
+{
+    m_vector.insert(m_vector.end(), val.begin(), val.end());
+}
+
+/* -------------------------------------------------------------------------- */
+void SFTPPacket::addInt32(unsigned long val)
+{
+    int i;
+    for (i = sizeof(uint32_t) - 1; i >= 0; --i)
+	m_vector.push_back((val >> (i*8)) & 0xff);
+}
+
+/* -------------------------------------------------------------------------- */
+unsigned long SFTPPacket::getInt32(void)
+{
+    size_t i;
+    unsigned long ret = 0UL;
+    for (i = 0; i < sizeof(uint32_t); ++i) {
+	ret <<= 8;
+	ret |= m_vector.at(m_gpos++);
+    }
+    return ret;
+}
+
+/* -------------------------------------------------------------------------- */
+void SFTPPacket::addInt64(unsigned long long val)
+{
+    int i;
+    for (i = sizeof(uint64_t) - 1; i >= 0; --i)
+	m_vector.push_back((val >> (i*8)) & 0xff);
+}
+
+/* -------------------------------------------------------------------------- */
+unsigned long long SFTPPacket::getInt64(void)
+{
+    size_t i;
+    unsigned long long ret = 0ULL;
+    for (i = 0; i < sizeof(uint64_t); ++i) {
+	ret <<= 8;
+	ret |= m_vector.at(m_gpos++);
+    }
+    return ret;
+}
+
+/* -------------------------------------------------------------------------- */
+void SFTPPacket::addString(KString const &val)
+{
+    addInt32(val.length());
+    m_vector.insert(m_vector.end(), val.begin(), val.end());
+}
+
+/* -------------------------------------------------------------------------- */
+std::string SFTPPacket::getString(void)
+{
+    unsigned long len = getInt32();
+    ByteVector::iterator it = m_vector.begin() + m_gpos;
+    return string(it, it + len);
+}
+
+/* -------------------------------------------------------------------------- */
+ByteVector const &SFTPPacket::update(void)
+{
+    uint_fast32_t len = m_vector.size() - sizeof(uint32_t);
+    m_vector[0] = (len >> 24) & 0xff;
+    m_vector[1] = (len >> 16) & 0xff;
+    m_vector[2] = (len >>  8) & 0xff;
+    m_vector[3] = (len      ) & 0xff;
+    return m_vector;
+}
+
+//}}}
 //{{{ SFTPTransfer -------------------------------------------------------------
 
 #if HAVE_LIBSSH2
--- a/kdumptool/sshtransfer.h
+++ b/kdumptool/sshtransfer.h
@@ -72,6 +72,55 @@ class SSHTransfer : public URLTransfer {
 };
 
 //}}}
+//{{{ SFTPPacket ---------------------------------------------------------------
+
+/**
+ * Encode/decode an SFTP packet.
+ */
+class SFTPPacket {
+
+    public:
+
+	SFTPPacket(void);
+
+	ByteVector const &data(void) const
+	throw ()
+	{ return m_vector; }
+
+	ByteVector const &update(void);
+
+	void setData(ByteVector const &val)
+	{
+	    m_vector = val;
+	    m_gpos = 0;
+	}
+
+	void addByte(unsigned char val)
+	{ m_vector.push_back(val); }
+
+	void addByteVector(ByteVector const &val);
+
+	void addInt32(unsigned long val);
+
+	void addInt64(unsigned long long val);
+
+	void addString(KString const &val);
+
+	unsigned char getByte(void)
+	{ return m_vector.at(m_gpos++); }
+
+	unsigned long getInt32(void);
+
+	unsigned long long getInt64(void);
+
+	std::string getString(void);
+
+    private:
+	ByteVector m_vector;
+	size_t m_gpos;
+};
+
+//}}}
 //{{{ SFTPTransfer -------------------------------------------------------------
 
 #if HAVE_LIBSSH2
--- /dev/null
+++ b/kdumptool/testsftppacket.cc
@@ -0,0 +1,189 @@
+/*
+ * (c) 2015, Petr Tesarik <ptesarik@suse.de>, SUSE LINUX Products GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#include <iostream>
+#include <cstdlib>
+
+#include "global.h"
+#include "stringutil.h"
+#include "sshtransfer.h"
+#include "debug.h"
+
+using std::cout;
+using std::cerr;
+using std::setw;
+using std::endl;
+
+// -----------------------------------------------------------------------------
+static void
+dumpvec(ByteVector const &bv)
+{
+    cout << std::hex << std::setfill('0');
+    ByteVector::const_iterator it = bv.begin();
+    if (it != bv.end()) {
+	cout << setw(2) << unsigned(*it);
+	++it;
+    }
+    while (it != bv.end()) {
+	cout << ' ' << setw(2) << unsigned(*it);
+	++it;
+    }
+    cout << endl;
+}
+
+// -----------------------------------------------------------------------------
+static unsigned long long
+parseval(const char *str, unsigned maxdigits)
+{
+    unsigned long long ret = 0;
+    unsigned i;
+
+    for (i = 0; i < maxdigits; ++i) {
+	if (!str[i])
+	    break;
+	ret <<= 4;
+	ret |= Stringutil::hex2int(str[i]);
+    }
+    if (str[i])
+	throw KError(KString("Number too big: '") + str + "'");
+
+    return ret;
+}
+
+// -----------------------------------------------------------------------------
+static ByteVector
+parsevec(const char *str)
+{
+    ByteVector ret;
+
+    while(*str) {
+	unsigned char byte;
+	byte = Stringutil::hex2int(*str++);
+	if (*str) {
+	    byte <<= 4;
+	    byte |= Stringutil::hex2int(*str++);
+	}
+	ret.push_back(byte);
+    }
+    return ret;
+}
+
+// -----------------------------------------------------------------------------
+static void
+getbyte(SFTPPacket &pkt)
+{
+    cout << std::hex << std::setfill('0') << setw(2)
+	 << unsigned(pkt.getByte()) << endl;
+}
+
+// -----------------------------------------------------------------------------
+static void
+getint32(SFTPPacket &pkt)
+{
+    cout << std::hex << std::setfill('0') << setw(8)
+	 << pkt.getInt32() << endl;
+}
+
+// -----------------------------------------------------------------------------
+static void
+getint64(SFTPPacket &pkt)
+{
+    cout << std::hex << std::setfill('0') << setw(16)
+	 << pkt.getInt64() << endl;
+}
+
+// -----------------------------------------------------------------------------
+static void
+getstring(SFTPPacket &pkt)
+{
+    cout << pkt.getString() << endl;
+}
+
+// -----------------------------------------------------------------------------
+int main(int argc, char *argv[])
+{
+    Debug::debug()->setStderrLevel(Debug::DL_TRACE);
+
+    try {
+	SFTPPacket pkt;
+
+	int i;
+	for (i = 1; i < argc; ++i) {
+	    char *arg = argv[i];
+
+	    switch (arg[0]) {
+	    case 'd':
+		if (arg[1])
+		    pkt.setData(parsevec(arg + 1));
+		else
+		    dumpvec(pkt.data());
+		break;
+
+	    case 'u':
+		dumpvec(pkt.update());
+		break;
+
+	    case 'b':
+		if (arg[1])
+		    pkt.addByte(parseval(arg + 1, 2));
+		else
+		    getbyte(pkt);
+		break;
+
+	    case 'w':
+		if (arg[1])
+		    pkt.addInt32(parseval(arg + 1, 8));
+		else
+		    getint32(pkt);
+		break;
+
+	    case 'l':
+		if (arg[1])
+		    pkt.addInt64(parseval(arg + 1, 16));
+		else
+		    getint64(pkt);
+		break;
+
+	    case 's':
+		if (arg[1])
+		    pkt.addString(arg + 1);
+		else
+		    getstring(pkt);
+		break;
+
+	    case 'v':
+	      if (arg[1])
+		  pkt.addByteVector(parsevec(arg + 1));
+	      break;
+
+	    case '\0':
+		// Ignore empty arguments
+		break;
+
+	    default:
+		throw KError("Invalid format specifier");
+	    }
+	}
+
+    } catch(const std::exception &ex) {
+	cerr << "Fatal exception: " << ex.what() << endl;
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -64,3 +64,7 @@ ADD_TEST(process
 ADD_TEST(urldecode
          ${CMAKE_CURRENT_SOURCE_DIR}/testurldecode.sh
          ${CMAKE_BINARY_DIR}/kdumptool/testurldecode)
+
+ADD_TEST(sftppacket
+         ${CMAKE_CURRENT_SOURCE_DIR}/testsftppacket.sh
+         ${CMAKE_BINARY_DIR}/kdumptool/testsftppacket)
--- /dev/null
+++ b/tests/testsftppacket.sh
@@ -0,0 +1,181 @@
+#!/bin/bash
+#
+# (c) 2014, Petr Tesarik <ptesarik@suse.de>, SUSE LINUX Products GmbH
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+
+# Check that results match expectation
+#                                                                            {{{
+function check()
+{
+    local arg="$1"
+    local expect="$2"
+    local result="$3"
+    if [ "$result" != "$expect" ] ; then
+	echo "failed packet: $arg"
+	echo "Expected:"
+	echo "$expect"
+	echo "Result:"
+	echo "$result"
+	errornumber=$(( errornumber + 1 ))
+    fi
+}
+# }}}
+
+# Add random byte to ARG and EXPECT
+#                                                                            {{{
+function add_random_byte()
+{
+    local ch=$(( $RANDOM % 256 ))
+    local hex=$( printf "%02x" $ch )
+    ARG="$ARG$hex"
+    EXPECT="$EXPECT $hex"
+}
+# }}}
+
+#
+# Program                                                                    {{{
+#
+
+TESTPACKET=$1
+HEXDUMP="od -Ax -tx1"
+
+if [ -z "$TESTPACKET" ] ; then
+    echo "Usage: $0 testsftppacket"
+    exit 1
+fi
+
+errornumber=0
+
+# TEST #1: Empty packet
+
+ARG="d"
+EXPECT="00 00 00 00"
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #2: Updated empty packet
+
+ARG="u"
+EXPECT="00 00 00 00"
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #3: Any 8-bit value
+ARG=""
+EXPECT="00 00 01 00"
+i=0
+while [ $i -le 255 ]
+do
+    ARG="$ARG "$(printf "b%02x" $i)
+    EXPECT="$EXPECT "$(printf "%02x" $i)
+    i=$(( $i + 1 ))
+done
+RESULT=$( "$TESTPACKET" $ARG u )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #4: 100 random 32-bit values
+ARG=""
+EXPECT="00 00 01 90"		# 4*100 in hex
+i=0
+while [ $i -lt 100 ]
+do
+    ARG="$ARG w"
+    add_random_byte		# bits 0-7
+    add_random_byte		# bits 8-15
+    add_random_byte		# bits 16-23
+    add_random_byte		# bits 24-31
+
+    i=$(( $i + 1 ))
+done
+RESULT=$( "$TESTPACKET" $ARG u )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #5: 100 random 64-bit values
+ARG=""
+EXPECT="00 00 03 20"		# 8*100 in hex
+i=0
+while [ $i -lt 100 ]
+do
+    ARG="$ARG l"
+    add_random_byte		# bits 0-7
+    add_random_byte		# bits 8-15
+    add_random_byte		# bits 16-23
+    add_random_byte		# bits 24-31
+    add_random_byte		# bits 32-39
+    add_random_byte		# bits 40-47
+    add_random_byte		# bits 48-55
+    add_random_byte		# bits 56-63
+
+    i=$(( $i + 1 ))
+done
+RESULT=$( "$TESTPACKET" $ARG u )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #6: String
+ARG="sHello, world!"
+EXPECT="00 00 00 11 00 00 00 0d 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21"
+RESULT=$( "$TESTPACKET" "$ARG" u )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #7: Vector
+ARG="v0123456789abcdef u"
+EXPECT="00 00 00 08 01 23 45 67 89 ab cd ef"
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #8: Get byte - note that packet length field is still zero!
+ARG="v0123456789abcdef b b b b b b b b b b b b"
+EXPECT=$(echo -e "00\n00\n00\n00\n01\n23\n45\n67\n89\nab\ncd\nef")
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #9: Get 32-bit integers - note that packet length field is zero!
+ARG="v0123456789abcdef w w w"
+EXPECT=$(echo -e "00000000\n01234567\n89abcdef")
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #10: Get 64-bit integer - note that packet length field is zero!
+ARG="v0123456789abcdef w l"
+EXPECT=$(echo -e "00000000\n0123456789abcdef")
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #11: Get string value
+ARG="sHello! w s"
+EXPECT=$(echo -e "00000000\nHello!")
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #12: Set data - note the bogus packet length
+ARG="dbadf1e1d0123456789abcdef d"
+EXPECT="ba df 1e 1d 01 23 45 67 89 ab cd ef"
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+# TEST #13: Set data - note the updated packet length
+ARG="dbadf1e1d0123456789abcdef u"
+EXPECT="00 00 00 08 01 23 45 67 89 ab cd ef"
+RESULT=$( "$TESTPACKET" $ARG )
+check "$ARG" "$EXPECT" "$RESULT"
+
+exit $errornumber
+
+# }}}
+
+# vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
