diff --git a/.gitignore b/.gitignore index ac9c931..b91513d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ __pycache__ /external/mongoose /external/openssl /external/python +/external/libssh /external/libssh-win-static/include/libssh /external/libssh-win-static/src /external/libssh-win-static/lib @@ -99,3 +100,4 @@ xcuserdata profile *.moved-aside /server/share/tmp +/build.sh diff --git a/build.bat.in b/build.bat.in index 3de6757..6738eb4 100644 --- a/build.bat.in +++ b/build.bat.in @@ -2,10 +2,12 @@ rem ============================================ rem 请调整以下路径,以适配您自己的的系统环境。 -rem 注意:必须使用Pyhont 3.4,32位版本!! +rem 注意:必须使用Pyhont 3.7,32位版本!! +rem 可以从这里下载: +rem https://www.python.org/ftp/python/3.7.0/python-3.7.0.exe rem ============================================ + SET PYEXEC=C:\Program Files\Python\python.exe - "%PYEXEC%" -B build/build.py %1 %2 %3 %4 %5 diff --git a/build/.idea/encodings.xml b/build/.idea/encodings.xml index 97626ba..d574609 100644 --- a/build/.idea/encodings.xml +++ b/build/.idea/encodings.xml @@ -1,6 +1,7 @@ + \ No newline at end of file diff --git a/build/build.py b/build/build.py index d05680b..241bd29 100644 --- a/build/build.py +++ b/build/build.py @@ -128,6 +128,7 @@ def do_opt(opt): elif 'external' == opt['name']: script = 'build-external.py' + arg = '%s %s' % (ctx.target_path, opt['bits']) elif 'server' == opt['name']: script = 'build-server.py' diff --git a/build/builder/build-external.py b/build/builder/build-external.py index 61ba1ae..240fff7 100644 --- a/build/builder/build-external.py +++ b/build/builder/build-external.py @@ -41,15 +41,12 @@ class BuilderBase: def _build_mongoose(self, file_name): cc.e("this is a pure-virtual function.") - # def build_openssl(self): - # file_name = 'openssl-{}.zip'.format(env.ver_openssl) - # _alt_ver = '_'.join(env.ver_openssl.split('.')) - # if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name): - # return - # self._build_openssl(file_name) - # - # def _build_openssl(self, file_name): - # cc.e("this is a pure-virtual function.") + def build_openssl(self): + file_name = 'openssl-{}.zip'.format(env.ver_ossl) + self._build_openssl(file_name) + + def _build_openssl(self, file_name): + cc.e("this is a pure-virtual function.") def build_libuv(self): file_name = 'libuv-{}.zip'.format(env.ver_libuv) @@ -102,12 +99,13 @@ class BuilderWin(BuilderBase): super().__init__() def _init_path(self): - # self.OPENSSL_PATH_SRC = os.path.join(PATH_EXTERNAL, 'openssl') + self.OPENSSL_PATH_SRC = os.path.join(PATH_EXTERNAL, 'openssl') self.JSONCPP_PATH_SRC = os.path.join(PATH_EXTERNAL, 'jsoncpp') self.MONGOOSE_PATH_SRC = os.path.join(PATH_EXTERNAL, 'mongoose') self.MBEDTLS_PATH_SRC = os.path.join(PATH_EXTERNAL, 'mbedtls') self.LIBUV_PATH_SRC = os.path.join(PATH_EXTERNAL, 'libuv') - self.LIBSSH_PATH_SRC = os.path.join(PATH_EXTERNAL, 'libssh-win-static') + # self.LIBSSH_PATH_SRC = os.path.join(PATH_EXTERNAL, 'libssh-win-static') + self.LIBSSH_PATH_SRC = os.path.join(PATH_EXTERNAL, 'libssh') # self._prepare_python_header() @@ -119,68 +117,84 @@ class BuilderWin(BuilderBase): return cc.v('') - if os.path.exists(os.path.join(env.path_py_inc, 'Python.h')): - cc.e('can not locate python development include path, make sure miniconda installed.') + # if os.path.exists(os.path.join(env.path_py_inc, 'Python.h')): + # cc.e('can not locate python development include path, make sure miniconda installed.') + # return + # cc.v('') + # utils.copy_ex(env.path_py_inc, os.path.join(PATH_EXTERNAL, 'python', 'include')) + + _header_path = None + for p in sys.path: + if os.path.exists(os.path.join(p, 'include', 'Python.h')): + _header_path = os.path.join(p, 'include') + if _header_path is None: + cc.e('\ncan not locate python development include path in:') + for p in sys.path: + cc.e(' ', p) + raise RuntimeError() + + utils.copy_ex(_header_path, os.path.join(PATH_EXTERNAL, 'python', 'include')) + + def _build_openssl(self, file_name): + cc.n('build openssl static library from source code... ', end='') + + _alt_ver = '_'.join(env.ver_ossl.split('.')) + if not utils.download_file('openssl source tarball', 'https://github.com/openssl/openssl/archive/OpenSSL_{}.zip'.format(_alt_ver), PATH_DOWNLOAD, file_name): + return + + _chk_output = [ + os.path.join(self.OPENSSL_PATH_SRC, 'out32', 'libeay32.lib'), + os.path.join(self.OPENSSL_PATH_SRC, 'out32', 'ssleay32.lib'), + os.path.join(self.OPENSSL_PATH_SRC, 'inc32', 'openssl', 'opensslconf.h'), + ] + + need_build = False + for f in _chk_output: + if not os.path.exists(f): + need_build = True + break + + if not need_build: + cc.w('already exists, skip.') return cc.v('') - # _header_path = None - # for p in sys.path: - # if os.path.exists(os.path.join(p, 'include', 'pyctype.h')): - # _header_path = os.path.join(p, 'include') - # if _header_path is None: - # cc.e('\ncan not locate python development include path in:') - # for p in sys.path: - # cc.e(' ', p) - # raise RuntimeError() + cc.n('prepare openssl source code...') + _alt_ver = '_'.join(env.ver_ossl.split('.')) + if not os.path.exists(self.OPENSSL_PATH_SRC): + utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL) + os.rename(os.path.join(PATH_EXTERNAL, 'openssl-OpenSSL_{}'.format(_alt_ver)), self.OPENSSL_PATH_SRC) + if not os.path.exists(self.OPENSSL_PATH_SRC): + raise RuntimeError('can not prepare openssl source code.') + else: + cc.w('already exists, skip.') - # utils.copy_ex(_header_path, os.path.join(PATH_EXTERNAL, 'python', 'include')) - utils.copy_ex(env.path_py_inc, os.path.join(PATH_EXTERNAL, 'python', 'include')) + os.chdir(self.OPENSSL_PATH_SRC) + os.system('""{}" Configure VC-WIN32"'.format(env.perl)) + os.system(r'ms\do_nasm') + os.system(r'"{}\VC\bin\vcvars32.bat" && nmake -f ms\nt.mak'.format(env.visual_studio_path)) - # def _build_openssl(self, file_name): - # cc.n('build openssl static library from source code... ', end='') - # _chk_output = [ - # os.path.join(self.OPENSSL_PATH_SRC, 'out32', 'libeay32.lib'), - # os.path.join(self.OPENSSL_PATH_SRC, 'out32', 'ssleay32.lib'), - # os.path.join(self.OPENSSL_PATH_SRC, 'inc32', 'openssl', 'opensslconf.h'), - # ] - # - # need_build = False - # for f in _chk_output: - # if not os.path.exists(f): - # need_build = True - # break - # - # if not need_build: - # cc.w('already exists, skip.') - # return - # cc.v('') - # - # cc.n('prepare openssl source code...') - # _alt_ver = '_'.join(env.ver_openssl.split('.')) - # if not os.path.exists(self.OPENSSL_PATH_SRC): - # utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL) - # os.rename(os.path.join(PATH_EXTERNAL, 'openssl-OpenSSL_{}'.format(_alt_ver)), self.OPENSSL_PATH_SRC) - # if not os.path.exists(self.OPENSSL_PATH_SRC): - # raise RuntimeError('can not prepare openssl source code.') - # else: - # cc.w('already exists, skip.') - # - # os.chdir(self.OPENSSL_PATH_SRC) - # os.system('""{}" Configure VC-WIN32"'.format(env.perl)) - # os.system(r'ms\do_nasm') - # os.system(r'"{}\VC\bin\vcvars32.bat" && nmake -f ms\nt.mak'.format(env.visual_studio_path)) - # - # for f in _chk_output: - # if not os.path.exists(f): - # raise RuntimeError('build openssl static library from source code failed.') + for f in _chk_output: + if not os.path.exists(f): + raise RuntimeError('build openssl static library from source code failed.') def _build_libssh(self, file_name): cc.n('build libssh static library from source code... ', end='') - out_file = os.path.join(self.LIBSSH_PATH_SRC, 'lib', 'libsshMT.lib') + + if not os.path.exists(self.LIBSSH_PATH_SRC): + cc.v('') + utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL) + os.rename(os.path.join(PATH_EXTERNAL, 'libssh-{}'.format(env.ver_libssh)), self.LIBSSH_PATH_SRC) + + cc.n('fix libssh source code... ', end='') + utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', 'src', 'sftp.c')) + utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', 'src'), os.path.join(self.LIBSSH_PATH_SRC, 'src'), 'sftp.c') + + out_file_lib = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.lib') + out_file_dll = os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path, 'ssh.dll') need_build = False - if not os.path.exists(out_file): + if not (os.path.exists(out_file_lib) and (os.path.exists(out_file_dll))): need_build = True if not need_build: @@ -188,30 +202,43 @@ class BuilderWin(BuilderBase): return cc.v('') - cc.n('prepare libssh source code... ', end='') - _include = os.path.join(self.LIBSSH_PATH_SRC, 'include', 'libssh') - _src = os.path.join(self.LIBSSH_PATH_SRC, 'src') + # cc.n('prepare libssh source code... ', end='') + # _include = os.path.join(self.LIBSSH_PATH_SRC, 'include', 'libssh') + # _src = os.path.join(self.LIBSSH_PATH_SRC, 'src') + # + # if not os.path.exists(_include) or not os.path.exists(_src): + # utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL) + # # os.rename(os.path.join(PATH_EXTERNAL, 'openssl-OpenSSL_{}'.format(_alt_ver)), self.OPENSSL_PATH_SRC) + # + # _unzipped_path = os.path.join(PATH_EXTERNAL, 'libssh-{}'.format(env.ver_libssh)) + # + # utils.copy_ex(os.path.join(_unzipped_path, 'include', 'libssh'), _include) + # utils.copy_ex(os.path.join(_unzipped_path, 'src'), _src) + # + # utils.remove(_unzipped_path) + # + # if not os.path.exists(_include) or not os.path.exists(_src): + # raise RuntimeError('\ncan not prepare libssh source code.') + # else: + # cc.w('already exists, skip.') - if not os.path.exists(_include) or not os.path.exists(_src): - utils.unzip(os.path.join(PATH_DOWNLOAD, file_name), PATH_EXTERNAL) - # os.rename(os.path.join(PATH_EXTERNAL, 'openssl-OpenSSL_{}'.format(_alt_ver)), self.OPENSSL_PATH_SRC) - - _unzipped_path = os.path.join(PATH_EXTERNAL, 'libssh-{}'.format(env.ver_libssh)) - - utils.copy_ex(os.path.join(_unzipped_path, 'include', 'libssh'), _include) - utils.copy_ex(os.path.join(_unzipped_path, 'src'), _src) - - utils.remove(_unzipped_path) - - if not os.path.exists(_include) or not os.path.exists(_src): - raise RuntimeError('\ncan not prepare libssh source code.') - else: - cc.w('already exists, skip.') + cc.w('\nOnce the libssh.sln generated, press Enter to continue or Q to quit...', end='') + try: + x = env.input() + except EOFError: + x = 'q' + if x == 'q': + return cc.i('build libssh...') - sln_file = os.path.join(self.LIBSSH_PATH_SRC, 'libssh.vs2015.sln') - utils.msvc_build(sln_file, 'libssh', ctx.target_path, ctx.bits_path, False) - utils.ensure_file_exists(out_file) + sln_file = os.path.join(self.LIBSSH_PATH_SRC, 'build', 'libssh.sln') + utils.msvc_build(sln_file, 'ssh_shared', ctx.target_path, 'win32', False) + utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', ctx.target_path, 'ssh.lib')) + utils.ensure_file_exists(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', ctx.target_path, 'ssh.dll')) + utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', ctx.target_path), os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path), 'ssh.lib') + utils.copy_file(os.path.join(self.LIBSSH_PATH_SRC, 'build', 'src', ctx.target_path), os.path.join(self.LIBSSH_PATH_SRC, 'lib', ctx.target_path), 'ssh.dll') + utils.ensure_file_exists(out_file_lib) + utils.ensure_file_exists(out_file_dll) def _build_jsoncpp(self, file_name): cc.n('prepare jsoncpp source code... ', end='') @@ -245,8 +272,8 @@ class BuilderWin(BuilderBase): # fix source file utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'mbedtls', 'include', 'mbedtls', 'config.h')) utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'mbedtls', 'include', 'mbedtls'), os.path.join(self.MBEDTLS_PATH_SRC, 'include', 'mbedtls'), 'config.h') - utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'mbedtls', 'library', 'rsa.c')) - utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'mbedtls', 'library'), os.path.join(self.MBEDTLS_PATH_SRC, 'library'), 'rsa.c') + # utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'mbedtls', 'library', 'rsa.c')) + # utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'mbedtls', 'library'), os.path.join(self.MBEDTLS_PATH_SRC, 'library'), 'rsa.c') def _build_libuv(self, file_name): cc.n('prepare libuv source code... ', end='') @@ -272,7 +299,7 @@ class BuilderLinux(BuilderBase): def _init_path(self): self.PATH_TMP = os.path.join(PATH_EXTERNAL, 'linux', 'tmp') self.PATH_RELEASE = os.path.join(PATH_EXTERNAL, 'linux', 'release') - # self.OPENSSL_PATH_SRC = os.path.join(self.PATH_TMP, 'openssl-{}'.format(env.ver_openssl)) + # self.OPENSSL_PATH_SRC = os.path.join(self.PATH_TMP, 'openssl-{}'.format(env.ver_ossl)) self.LIBUV_PATH_SRC = os.path.join(self.PATH_TMP, 'libuv-{}'.format(env.ver_libuv)) self.MBEDTLS_PATH_SRC = os.path.join(self.PATH_TMP, 'mbedtls-mbedtls-{}'.format(env.ver_mbedtls)) self.LIBSSH_PATH_SRC = os.path.join(self.PATH_TMP, 'libssh-{}'.format(env.ver_libssh)) @@ -322,8 +349,8 @@ class BuilderLinux(BuilderBase): else: cc.w('already exists, skip.') - # def _build_openssl(self, file_name): - # pass # we do not need build openssl anymore, because first time run build.sh we built Python, it include openssl. + def _build_openssl(self, file_name): + pass # we do not need build openssl anymore, because first time run build.sh we built Python, it include openssl. def _build_libuv(self, file_name): if not os.path.exists(self.LIBUV_PATH_SRC): @@ -570,8 +597,8 @@ class BuilderMacOS(BuilderBase): else: cc.w('already exists, skip.') - # def _build_openssl(self, file_name): - # # we do not need build openssl anymore, because first time run build.sh we built Python, it include openssl. + def _build_openssl(self, file_name): + pass # we do not need build openssl anymore, because first time run build.sh we built Python, it include openssl. # # if not os.path.exists(self.OPENSSL_PATH_SRC): # os.system('tar -zxvf "{}/{}" -C "{}"'.format(PATH_DOWNLOAD, file_name, self.PATH_TMP)) @@ -797,7 +824,7 @@ def main(): builder.build_jsoncpp() builder.build_mongoose() - # builder.build_openssl() + builder.build_openssl() builder.build_libuv() builder.build_mbedtls() builder.build_libssh() diff --git a/build/builder/build-pysrt.py b/build/builder/build-pysrt.py index 8051f5d..5625ffc 100644 --- a/build/builder/build-pysrt.py +++ b/build/builder/build-pysrt.py @@ -12,12 +12,13 @@ from core.env import env ctx = BuildContext() -MODULES_WIN = ['_bz2', '_ctypes', '_hashlib', '_lzma', '_overlapped', '_socket', '_sqlite3', '_ssl', 'select', 'sqlite3', 'unicodedata'] +MODULES_WIN = ['_asyncio', '_bz2', '_ctypes', '_hashlib', '_lzma', '_overlapped', '_socket', '_sqlite3', '_ssl', 'select', 'sqlite3', + 'libcrypto-1_1', 'libssl-1_1', 'unicodedata'] PY_LIB_REMOVE_WIN = ['ctypes/test', 'curses', 'dbm', 'distutils', 'email/test', 'ensurepip', 'idlelib', 'lib2to3', 'lib-dynload', 'pydoc_data', 'site-packages', 'sqlite3/test', 'test', 'tkinter', 'turtledemo', 'unittest', 'venv', 'wsgiref', 'dis.py', 'doctest.py', 'pdb.py', 'py_compile.py', 'pydoc.py', 'this.py', 'wave.py', 'webbrowser.py', 'zipapp.py'] -PY_LIB_REMOVE_LINUX = ['ctypes/test', 'curses', 'config-3.7m-x86_64-linux-gnu', 'dbm', 'distutils', 'ensurepip', 'idlelib', 'lib2to3', +PY_LIB_REMOVE_LINUX = ['ctypes/test', 'curses', 'dbm', 'distutils', 'ensurepip', 'idlelib', 'lib2to3', 'lib-dynload', 'pydoc_data', 'site-packages', 'sqlite3/test', 'test', 'tkinter', 'turtledemo', 'unittest', 'venv', 'wsgiref', 'dis.py', 'doctest.py', 'pdb.py', 'py_compile.py', 'pydoc_data', 'pydoc.py', 'this.py', 'wave.py', 'webbrowser.py', 'zipapp.py'] @@ -150,23 +151,24 @@ class PYSWin(PYSBase): utils.makedirs(self.base_path) cc.v('copy python core dll...') - _win_system_path = os.path.join(os.getenv('SystemRoot'), 'system32') - if ctx.bits == BITS_32 and ctx.host_os_is_win_x64: - _win_system_path = os.path.join(os.getenv('SystemRoot'), 'SysWOW64') + _exec_path = os.path.dirname(env.py_exec) + # _win_system_path = os.path.join(os.getenv('SystemRoot'), 'system32') + # if ctx.bits == BITS_32 and ctx.host_os_is_win_x64: + # _win_system_path = os.path.join(os.getenv('SystemRoot'), 'SysWOW64') - if not os.path.exists(_win_system_path): - raise RuntimeError('can not locate windows system folder at:', _win_system_path) + if not os.path.exists(_exec_path): + raise RuntimeError('can not locate python folder at:', _exec_path) pydll = self._get_py_dll_name() - shutil.copy(os.path.join(_win_system_path, pydll), os.path.join(self.base_path, pydll)) + shutil.copy(os.path.join(_exec_path, pydll), os.path.join(self.base_path, pydll)) if ctx.py_ver == '34': msvcrdll = 'msvcr100.dll' elif ctx.py_ver == '37': - msvcrdll = 'msvcr140.dll' + msvcrdll = 'vcruntime140.dll' else: raise RuntimeError('unknown msvc runtime for this python version.') - shutil.copy(os.path.join(_win_system_path, msvcrdll), os.path.join(self.base_path, msvcrdll)) + shutil.copy(os.path.join(_exec_path, msvcrdll), os.path.join(self.base_path, msvcrdll)) super()._copy_modules() diff --git a/build/builder/build-server.py b/build/builder/build-server.py index e7d835c..8605b25 100644 --- a/build/builder/build-server.py +++ b/build/builder/build-server.py @@ -49,6 +49,7 @@ class BuilderWin(BuilderBase): utils.remove(out_file) utils.msvc_build(sln_file, 'tpssh', ctx.target_path, ctx.bits_path, False) utils.ensure_file_exists(out_file) + utils.copy_file(os.path.join(env.root_path, 'external', 'libssh', 'lib', ctx.target_path), os.path.join(env.root_path, 'out', 'server', ctx.bits_path, ctx.target_path), 'ssh.dll') cc.n('build TELNET protocol ...') sln_file = os.path.join(env.root_path, 'server', 'tp_core', 'protocol', 'telnet', 'tptelnet.vs2015.sln') diff --git a/build/builder/core/context.py b/build/builder/core/context.py index 9237f3f..0f21b12 100644 --- a/build/builder/core/context.py +++ b/build/builder/core/context.py @@ -59,6 +59,7 @@ class BuildContext(object): if self.host_os == 'windows': self.host_os_is_win_x64 = 'PROGRAMFILES(X86)' in os.environ + self.dist_path = '' self.make_dist_path() def make_dist_path(self): diff --git a/build/builder/core/env.py b/build/builder/core/env.py index f572a66..4739f82 100644 --- a/build/builder/core/env.py +++ b/build/builder/core/env.py @@ -24,11 +24,15 @@ class Env(object): self.root_path = os.path.abspath(os.path.join(_this_path, '..', '..', '..')) self.build_path = os.path.abspath(os.path.join(_this_path, '..', '..')) self.builder_path = os.path.join(self.build_path, 'builder') - self.win32_tools_path = os.path.join(self.build_path, 'tools', 'win32') self.is_py2 = sys.version_info[0] == 2 self.is_py3 = sys.version_info[0] == 3 + if self.is_py2: + self.input = raw_input + else: + self.input = input + self.py_ver = platform.python_version_tuple() self.py_ver_str = '%s%s' % (self.py_ver[0], self.py_ver[1]) self.py_ver_dot = '%s.%s' % (self.py_ver[0], self.py_ver[1]) @@ -53,19 +57,9 @@ class Env(object): self.is_win = True self.plat = 'windows' self.is_win_x64 = 'PROGRAMFILES(X86)' in os.environ - self.path_miniconda = os.path.abspath(os.path.dirname(self.py_exec)) - self.path_ossl_inc = os.path.join(self.path_miniconda, 'Library', 'include') - self.path_ossl_lib = os.path.join(self.path_miniconda, 'Library', 'lib') - self.path_py_inc = os.path.join(self.path_miniconda, 'include') - self.path_py_lib = os.path.join(self.path_miniconda, 'libs') elif _os == 'linux': self.is_linux = True self.plat = 'linux' - self.path_miniconda = os.path.abspath(os.path.join(os.path.dirname(self.py_exec), '..')) - self.path_ossl_inc = os.path.join(self.path_miniconda, 'include') - self.path_ossl_lib = os.path.join(self.path_miniconda, 'lib') - self.path_py_inc = os.path.join(self.path_miniconda, 'include', 'python{}m'.format(self.py_ver_dot)) - self.path_py_lib = os.path.join(self.path_miniconda, 'lib') elif _os == 'darwin': self.is_macos = True self.plat = 'macos' @@ -77,16 +71,12 @@ class Env(object): if not self._load_version(): return False - if not os.path.exists(os.path.join(self.path_miniconda, 'conda-meta')): - cc.e('can not find conda, please install miniconda from https://conda.io/miniconda.html and try again.') - return False - return True def _load_config(self, warn_miss_tool): _cfg_file = os.path.join(self.root_path, 'config.ini') if not os.path.exists(_cfg_file): - cc.e('can not load configuration.\n\nplease copy `config.ini.in` into `config.ini` and modify it to fit your condition and try again.') + cc.e('can not load configuration.\n\nplease copy `config.ini.in` to `config.ini` and modify it to fit your condition and try again.') return False _cfg = configparser.ConfigParser() diff --git a/external/fix-external/libssh/src/session.c b/external/fix-external/libssh/src/session.c deleted file mode 100644 index f046408..0000000 --- a/external/fix-external/libssh/src/session.c +++ /dev/null @@ -1,953 +0,0 @@ -/* - * session.c - non-networking functions - * - * This file is part of the SSH Library - * - * Copyright (c) 2005-2013 by Aris Adamantiadis - * - * The SSH Library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or (at your - * option) any later version. - * - * The SSH Library is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the SSH Library; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#include "config.h" - -#include -#include - -#include "libssh/priv.h" -#include "libssh/libssh.h" -#include "libssh/crypto.h" -#include "libssh/server.h" -#include "libssh/socket.h" -#ifdef WITH_SSH1 -#include "libssh/ssh1.h" -#endif /* WITH_SSH1 */ -#include "libssh/ssh2.h" -#include "libssh/agent.h" -#include "libssh/packet.h" -#include "libssh/session.h" -#include "libssh/misc.h" -#include "libssh/buffer.h" -#include "libssh/poll.h" - -#define FIRST_CHANNEL 42 // why not ? it helps to find bugs. - -/** - * @defgroup libssh_session The SSH session functions. - * @ingroup libssh - * - * Functions that manage a session. - * - * @{ - */ - -/** - * @brief Create a new ssh session. - * - * @returns A new ssh_session pointer, NULL on error. - */ -ssh_session ssh_new(void) { - ssh_session session; - char *id = NULL; - int rc; - - session = malloc(sizeof (struct ssh_session_struct)); - if (session == NULL) { - return NULL; - } - ZERO_STRUCTP(session); - - session->next_crypto = crypto_new(); - if (session->next_crypto == NULL) { - goto err; - } - - session->socket = ssh_socket_new(session); - if (session->socket == NULL) { - goto err; - } - - session->out_buffer = ssh_buffer_new(); - if (session->out_buffer == NULL) { - goto err; - } - - session->in_buffer=ssh_buffer_new(); - if (session->in_buffer == NULL) { - goto err; - } - - session->alive = 0; - session->auth_methods = 0; - ssh_set_blocking(session, 1); - session->maxchannel = FIRST_CHANNEL; - -#ifndef _WIN32 - session->agent = agent_new(session); - if (session->agent == NULL) { - goto err; - } -#endif /* _WIN32 */ - - /* OPTIONS */ - session->opts.StrictHostKeyChecking = 1; - session->opts.port = 0; - session->opts.fd = -1; - session->opts.ssh2 = 1; - session->opts.compressionlevel=7; -#ifdef WITH_SSH1 - session->opts.ssh1 = 1; -#else - session->opts.ssh1 = 0; -#endif - - session->opts.identity = ssh_list_new(); - if (session->opts.identity == NULL) { - goto err; - } - - id = strdup("%d/id_ed25519"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } - -#ifdef HAVE_ECC - id = strdup("%d/id_ecdsa"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } -#endif - - id = strdup("%d/id_rsa"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } - - id = strdup("%d/id_dsa"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } - - id = strdup("%d/identity"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } - - return session; - -err: - free(id); - ssh_free(session); - return NULL; -} - -/** - * @brief Deallocate a SSH session handle. - * - * @param[in] session The SSH session to free. - * - * @see ssh_disconnect() - * @see ssh_new() - */ -void ssh_free(ssh_session session) { - int i; - struct ssh_iterator *it; - - if (session == NULL) { - return; - } - - /* - * Delete all channels - * - * This needs the first thing we clean up cause if there is still an open - * channel we call ssh_channel_close() first. So we need a working socket - * and poll context for it. - */ - for (it = ssh_list_get_iterator(session->channels); - it != NULL; - it = ssh_list_get_iterator(session->channels)) { - ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); - ssh_list_remove(session->channels, it); - } - ssh_list_free(session->channels); - session->channels = NULL; - -#ifdef WITH_PCAP - if (session->pcap_ctx) { - ssh_pcap_context_free(session->pcap_ctx); - session->pcap_ctx = NULL; - } -#endif - - ssh_socket_free(session->socket); - session->socket = NULL; - - if (session->default_poll_ctx) { - ssh_poll_ctx_free(session->default_poll_ctx); - } - - ssh_buffer_free(session->in_buffer); - ssh_buffer_free(session->out_buffer); - session->in_buffer = session->out_buffer = NULL; - - if (session->in_hashbuf != NULL) { - ssh_buffer_free(session->in_hashbuf); - } - if (session->out_hashbuf != NULL) { - ssh_buffer_free(session->out_hashbuf); - } - - crypto_free(session->current_crypto); - crypto_free(session->next_crypto); - -#ifndef _WIN32 - agent_free(session->agent); -#endif /* _WIN32 */ - - ssh_key_free(session->srv.dsa_key); - session->srv.dsa_key = NULL; - ssh_key_free(session->srv.rsa_key); - session->srv.rsa_key = NULL; - ssh_key_free(session->srv.ecdsa_key); - session->srv.ecdsa_key = NULL; - ssh_key_free(session->srv.ed25519_key); - session->srv.ed25519_key = NULL; - - if (session->ssh_message_list) { - ssh_message msg; - - for (msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); - msg != NULL; - msg = ssh_list_pop_head(ssh_message, session->ssh_message_list)) { - ssh_message_free(msg); - } - ssh_list_free(session->ssh_message_list); - } - - if (session->packet_callbacks) { - ssh_list_free(session->packet_callbacks); - } - - /* options */ - if (session->opts.identity) { - char *id; - - for (id = ssh_list_pop_head(char *, session->opts.identity); - id != NULL; - id = ssh_list_pop_head(char *, session->opts.identity)) { - SAFE_FREE(id); - } - ssh_list_free(session->opts.identity); - } - -#ifndef _WIN32 - ssh_agent_state_free (session->agent_state); -#endif - session->agent_state = NULL; - - SAFE_FREE(session->auth_auto_state); - SAFE_FREE(session->serverbanner); - SAFE_FREE(session->clientbanner); - SAFE_FREE(session->banner); - - SAFE_FREE(session->opts.bindaddr); - SAFE_FREE(session->opts.custombanner); - SAFE_FREE(session->opts.username); - SAFE_FREE(session->opts.host); - SAFE_FREE(session->opts.sshdir); - SAFE_FREE(session->opts.knownhosts); - SAFE_FREE(session->opts.ProxyCommand); - SAFE_FREE(session->opts.gss_server_identity); - SAFE_FREE(session->opts.gss_client_identity); - - for (i = 0; i < 10; i++) { - if (session->opts.wanted_methods[i]) { - SAFE_FREE(session->opts.wanted_methods[i]); - } - } - - /* burn connection, it could contain sensitive data */ - BURN_BUFFER(session, sizeof(struct ssh_session_struct)); - SAFE_FREE(session); -} - -/** - * @brief get the client banner - * - * @param[in] session The SSH session - * - * @return Returns the client banner string or NULL. - */ -const char* ssh_get_clientbanner(ssh_session session) { - if (session == NULL) { - return NULL; - } - - return session->clientbanner; -} - -/** - * @brief get the server banner - * - * @param[in] session The SSH session - * - * @return Returns the server banner string or NULL. - */ -const char* ssh_get_serverbanner(ssh_session session) { - if(!session) { - return NULL; - } - return session->serverbanner; -} - -/** - * @brief get the name of the current key exchange algorithm. - * - * @param[in] session The SSH session - * - * @return Returns the key exchange algorithm string or NULL. - */ -const char* ssh_get_kex_algo(ssh_session session) { - if ((session == NULL) || - (session->current_crypto == NULL)) { - return NULL; - } - - switch (session->current_crypto->kex_type) { - case SSH_KEX_DH_GROUP1_SHA1: - return "diffie-hellman-group1-sha1"; - case SSH_KEX_DH_GROUP14_SHA1: - return "diffie-hellman-group14-sha1"; - case SSH_KEX_ECDH_SHA2_NISTP256: - return "ecdh-sha2-nistp256"; - case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: - return "curve25519-sha256@libssh.org"; - default: - break; - } - - return NULL; -} - -/** - * @brief get the name of the input cipher for the given session. - * - * @param[in] session The SSH session. - * - * @return Returns cipher name or NULL. - */ -const char* ssh_get_cipher_in(ssh_session session) { - if ((session != NULL) && - (session->current_crypto != NULL) && - (session->current_crypto->in_cipher != NULL)) { - return session->current_crypto->in_cipher->name; - } - return NULL; -} - -/** - * @brief get the name of the output cipher for the given session. - * - * @param[in] session The SSH session. - * - * @return Returns cipher name or NULL. - */ -const char* ssh_get_cipher_out(ssh_session session) { - if ((session != NULL) && - (session->current_crypto != NULL) && - (session->current_crypto->out_cipher != NULL)) { - return session->current_crypto->out_cipher->name; - } - return NULL; -} - -/** - * @brief get the name of the input HMAC algorithm for the given session. - * - * @param[in] session The SSH session. - * - * @return Returns HMAC algorithm name or NULL if unknown. - */ -const char* ssh_get_hmac_in(ssh_session session) { - if ((session != NULL) && - (session->current_crypto != NULL)) { - return ssh_hmac_type_to_string(session->current_crypto->in_hmac); - } - return NULL; -} - -/** - * @brief get the name of the output HMAC algorithm for the given session. - * - * @param[in] session The SSH session. - * - * @return Returns HMAC algorithm name or NULL if unknown. - */ -const char* ssh_get_hmac_out(ssh_session session) { - if ((session != NULL) && - (session->current_crypto != NULL)) { - return ssh_hmac_type_to_string(session->current_crypto->out_hmac); - } - return NULL; -} - -/** - * @brief Disconnect impolitely from a remote host by closing the socket. - * - * Suitable if you forked and want to destroy this session. - * - * @param[in] session The SSH session to disconnect. - */ -void ssh_silent_disconnect(ssh_session session) { - if (session == NULL) { - return; - } - - ssh_socket_close(session->socket); - session->alive = 0; - ssh_disconnect(session); -} - -/** - * @brief Set the session in blocking/nonblocking mode. - * - * @param[in] session The ssh session to change. - * - * @param[in] blocking Zero for nonblocking mode. - */ -void ssh_set_blocking(ssh_session session, int blocking) { - if (session == NULL) { - return; - } - session->flags &= ~SSH_SESSION_FLAG_BLOCKING; - session->flags |= blocking ? SSH_SESSION_FLAG_BLOCKING : 0; -} - -/** - * @brief Return the blocking mode of libssh - * @param[in] session The SSH session - * @returns 0 if the session is nonblocking, - * @returns 1 if the functions may block. - */ -int ssh_is_blocking(ssh_session session){ - return (session->flags&SSH_SESSION_FLAG_BLOCKING) ? 1 : 0; -} - -/* Waits until the output socket is empty */ -static int ssh_flush_termination(void *c){ - ssh_session session = c; - if (ssh_socket_buffered_write_bytes(session->socket) == 0 || - session->session_state == SSH_SESSION_STATE_ERROR) - return 1; - else - return 0; -} - -/** - * @brief Blocking flush of the outgoing buffer - * @param[in] session The SSH session - * @param[in] timeout Set an upper limit on the time for which this function - * will block, in milliseconds. Specifying -1 - * means an infinite timeout. This parameter is passed to - * the poll() function. - * @returns SSH_OK on success, SSH_AGAIN if timeout occurred, - * SSH_ERROR otherwise. - */ - -int ssh_blocking_flush(ssh_session session, int timeout){ - int rc; - if (session == NULL) { - return SSH_ERROR; - } - - rc = ssh_handle_packets_termination(session, timeout, - ssh_flush_termination, session); - if (rc == SSH_ERROR) { - return rc; - } - if (!ssh_flush_termination(session)) { - rc = SSH_AGAIN; - } - - return rc; -} - -/** - * @brief Check if we are connected. - * - * @param[in] session The session to check if it is connected. - * - * @return 1 if we are connected, 0 if not. - */ -int ssh_is_connected(ssh_session session) { - if (session == NULL) { - return 0; - } - - return session->alive; -} - -/** - * @brief Get the fd of a connection. - * - * In case you'd need the file descriptor of the connection to the server/client. - * - * @param[in] session The ssh session to use. - * - * @return The file descriptor of the connection, or -1 if it is - * not connected - */ -socket_t ssh_get_fd(ssh_session session) { - if (session == NULL) { - return -1; - } - - return ssh_socket_get_fd_in(session->socket); -} - -/** - * @brief Tell the session it has data to read on the file descriptor without - * blocking. - * - * @param[in] session The ssh session to use. - */ -void ssh_set_fd_toread(ssh_session session) { - if (session == NULL) { - return; - } - - ssh_socket_set_read_wontblock(session->socket); -} - -/** - * @brief Tell the session it may write to the file descriptor without blocking. - * - * @param[in] session The ssh session to use. - */ -void ssh_set_fd_towrite(ssh_session session) { - if (session == NULL) { - return; - } - - ssh_socket_set_write_wontblock(session->socket); -} - -/** - * @brief Tell the session it has an exception to catch on the file descriptor. - * - * \param[in] session The ssh session to use. - */ -void ssh_set_fd_except(ssh_session session) { - if (session == NULL) { - return; - } - - ssh_socket_set_except(session->socket); -} - -/** - * @internal - * - * @brief Poll the current session for an event and call the appropriate - * callbacks. This function will not loop until the timeout is expired. - * - * This will block until one event happens. - * - * @param[in] session The session handle to use. - * - * @param[in] timeout Set an upper limit on the time for which this function - * will block, in milliseconds. Specifying SSH_TIMEOUT_INFINITE - * (-1) means an infinite timeout. - * Specifying SSH_TIMEOUT_USER means to use the timeout - * specified in options. 0 means poll will return immediately. - * This parameter is passed to the poll() function. - * - * @return SSH_OK on success, SSH_ERROR otherwise. - */ -int ssh_handle_packets(ssh_session session, int timeout) { - ssh_poll_handle spoll_in,spoll_out; - ssh_poll_ctx ctx; - int tm = timeout; - int rc; - - if (session == NULL || session->socket == NULL) { - return SSH_ERROR; - } - - spoll_in = ssh_socket_get_poll_handle_in(session->socket); - spoll_out = ssh_socket_get_poll_handle_out(session->socket); - ssh_poll_add_events(spoll_in, POLLIN); - ctx = ssh_poll_get_ctx(spoll_in); - - if (!ctx) { - ctx = ssh_poll_get_default_ctx(session); - ssh_poll_ctx_add(ctx, spoll_in); - if (spoll_in != spoll_out) { - ssh_poll_ctx_add(ctx, spoll_out); - } - } - - if (timeout == SSH_TIMEOUT_USER) { - if (ssh_is_blocking(session)) - tm = ssh_make_milliseconds(session->opts.timeout, - session->opts.timeout_usec); - else - tm = 0; - } - rc = ssh_poll_ctx_dopoll(ctx, tm); - if (rc == SSH_ERROR) { - session->session_state = SSH_SESSION_STATE_ERROR; - } - - return rc; -} - -/** - * @internal - * - * @brief Poll the current session for an event and call the appropriate - * callbacks. - * - * This will block until termination function returns true, or timeout expired. - * - * @param[in] session The session handle to use. - * - * @param[in] timeout Set an upper limit on the time for which this function - * will block, in milliseconds. Specifying SSH_TIMEOUT_INFINITE - * (-1) means an infinite timeout. - * Specifying SSH_TIMEOUT_USER means to use the timeout - * specified in options. 0 means poll will return immediately. - * SSH_TIMEOUT_DEFAULT uses blocking parameters of the session. - * This parameter is passed to the poll() function. - * - * @param[in] fct Termination function to be used to determine if it is - * possible to stop polling. - * @param[in] user User parameter to be passed to fct termination function. - * @return SSH_OK on success, SSH_ERROR otherwise. - */ -int ssh_handle_packets_termination(ssh_session session, - int timeout, - ssh_termination_function fct, - void *user) -{ - struct ssh_timestamp ts; - int ret = SSH_OK; - int tm; - - if (timeout == SSH_TIMEOUT_USER) { - if (ssh_is_blocking(session)) { - timeout = ssh_make_milliseconds(session->opts.timeout, - session->opts.timeout_usec); - } else { - timeout = SSH_TIMEOUT_NONBLOCKING; - } - } else if (timeout == SSH_TIMEOUT_DEFAULT) { - if (ssh_is_blocking(session)) { - // Apex. - // timeout = SSH_TIMEOUT_INFINITE; - timeout = ssh_make_milliseconds(session->opts.timeout, - session->opts.timeout_usec); - } else { - timeout = SSH_TIMEOUT_NONBLOCKING; - } - } - - /* avoid unnecessary syscall for the SSH_TIMEOUT_NONBLOCKING case */ - if (timeout != SSH_TIMEOUT_NONBLOCKING) { - ssh_timestamp_init(&ts); - } - - tm = timeout; - while(!fct(user)) { - ret = ssh_handle_packets(session, tm); - if (ret == SSH_ERROR) { - break; - } - if (ssh_timeout_elapsed(&ts,timeout)) { - ret = fct(user) ? SSH_OK : SSH_AGAIN; - break; - } - - tm = ssh_timeout_update(&ts, timeout); - } - - return ret; -} - -/** - * @brief Get session status - * - * @param session The ssh session to use. - * - * @returns A bitmask including SSH_CLOSED, SSH_READ_PENDING, SSH_WRITE_PENDING - * or SSH_CLOSED_ERROR which respectively means the session is closed, - * has data to read on the connection socket and session was closed - * due to an error. - */ -int ssh_get_status(ssh_session session) { - int socketstate; - int r = 0; - - if (session == NULL) { - return 0; - } - - socketstate = ssh_socket_get_status(session->socket); - - if (session->session_state == SSH_SESSION_STATE_DISCONNECTED) { - r |= SSH_CLOSED; - } - if (socketstate & SSH_READ_PENDING) { - r |= SSH_READ_PENDING; - } - if (socketstate & SSH_WRITE_PENDING) { - r |= SSH_WRITE_PENDING; - } - if ((session->session_state == SSH_SESSION_STATE_DISCONNECTED && - (socketstate & SSH_CLOSED_ERROR)) || - session->session_state == SSH_SESSION_STATE_ERROR) { - r |= SSH_CLOSED_ERROR; - } - - return r; -} - -/** - * @brief Get poll flags for an external mainloop - * - * @param session The ssh session to use. - * - * @returns A bitmask including SSH_READ_PENDING or SSH_WRITE_PENDING. - * For SSH_READ_PENDING, your invocation of poll() should include - * POLLIN. For SSH_WRITE_PENDING, your invocation of poll() should - * include POLLOUT. - */ -int ssh_get_poll_flags(ssh_session session) -{ - if (session == NULL) { - return 0; - } - - return ssh_socket_get_poll_flags (session->socket); -} - -/** - * @brief Get the disconnect message from the server. - * - * @param[in] session The ssh session to use. - * - * @return The message sent by the server along with the - * disconnect, or NULL in which case the reason of the - * disconnect may be found with ssh_get_error. - * - * @see ssh_get_error() - */ -const char *ssh_get_disconnect_message(ssh_session session) { - if (session == NULL) { - return NULL; - } - - if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Connection not closed yet"); - } else if(!session->discon_msg) { - ssh_set_error(session, SSH_FATAL, - "Connection correctly closed but no disconnect message"); - } else { - return session->discon_msg; - } - - return NULL; -} - -/** - * @brief Get the protocol version of the session. - * - * @param session The ssh session to use. - * - * @return 1 or 2, for ssh1 or ssh2, < 0 on error. - */ -int ssh_get_version(ssh_session session) { - if (session == NULL) { - return -1; - } - - return session->version; -} - -/** - * @internal - * @brief Callback to be called when the socket received an exception code. - * @param user is a pointer to session - */ -void ssh_socket_exception_callback(int code, int errno_code, void *user){ - ssh_session session=(ssh_session)user; - - SSH_LOG(SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code); - session->session_state = SSH_SESSION_STATE_ERROR; - if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { - ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); - } else { - ssh_set_error(session, SSH_FATAL, "Socket error: %s", strerror(errno_code)); - } - - session->ssh_connection_callback(session); -} - -/** - * @brief Send a message that should be ignored - * - * @param[in] session The SSH session - * @param[in] data Data to be sent - * - * @return SSH_OK on success, SSH_ERROR otherwise. - */ -int ssh_send_ignore (ssh_session session, const char *data) { -#ifdef WITH_SSH1 - const int type = session->version == 1 ? SSH_MSG_IGNORE : SSH2_MSG_IGNORE; -#else /* WITH_SSH1 */ - const int type = SSH2_MSG_IGNORE; -#endif /* WITH_SSH1 */ - int rc; - - if (ssh_socket_is_open(session->socket)) { - rc = ssh_buffer_pack(session->out_buffer, - "bs", - type, - data); - if (rc != SSH_OK){ - ssh_set_error_oom(session); - goto error; - } - packet_send(session); - ssh_handle_packets(session, 0); - } - - return SSH_OK; - -error: - ssh_buffer_reinit(session->out_buffer); - return SSH_ERROR; -} - -/** - * @brief Send a debug message - * - * @param[in] session The SSH session - * @param[in] message Data to be sent - * @param[in] always_display Message SHOULD be displayed by the server. It - * SHOULD NOT be displayed unless debugging - * information has been explicitly requested. - * - * @return SSH_OK on success, SSH_ERROR otherwise. - */ -int ssh_send_debug (ssh_session session, const char *message, int always_display) { - int rc; - - if (ssh_socket_is_open(session->socket)) { -#ifdef WITH_SSH1 - if (session->version == 1) { - rc = ssh_buffer_pack(session->out_buffer, - "bs", - SSH_MSG_DEBUG, - message); - } else -#endif /* WITH_SSH1 */ - { - rc = ssh_buffer_pack(session->out_buffer, - "bbsd", - SSH2_MSG_DEBUG, - always_display != 0 ? 1 : 0, - message, - 0); /* empty language tag */ - } - if (rc != SSH_OK) { - ssh_set_error_oom(session); - goto error; - } - packet_send(session); - ssh_handle_packets(session, 0); - } - - return SSH_OK; - -error: - ssh_buffer_reinit(session->out_buffer); - return SSH_ERROR; -} - - /** - * @brief Set the session data counters. - * - * This functions sets the counter structures to be used to calculate data - * which comes in and goes out through the session at different levels. - * - * @code - * struct ssh_counter_struct scounter = { - * .in_bytes = 0, - * .out_bytes = 0, - * .in_packets = 0, - * .out_packets = 0 - * }; - * - * struct ssh_counter_struct rcounter = { - * .in_bytes = 0, - * .out_bytes = 0, - * .in_packets = 0, - * .out_packets = 0 - * }; - * - * ssh_set_counters(session, &scounter, &rcounter); - * @endcode - * - * @param[in] session The SSH session. - * - * @param[in] scounter Counter for byte data handled by the session sockets. - * - * @param[in] rcounter Counter for byte and packet data handled by the session, - * prior compression and SSH overhead. - */ -void ssh_set_counters(ssh_session session, ssh_counter scounter, - ssh_counter rcounter) { - if (session != NULL) { - session->socket_counter = scounter; - session->raw_counter = rcounter; - } -} - -/** @} */ - -/* vim: set ts=4 sw=4 et cindent: */ diff --git a/external/fix-external/libssh/src/sftp.c b/external/fix-external/libssh/src/sftp.c new file mode 100644 index 0000000..be1fa47 --- /dev/null +++ b/external/fix-external/libssh/src/sftp.c @@ -0,0 +1,3368 @@ +/* + * sftp.c - Secure FTP functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* This file contains code written by Nick Zitzmann */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/sftp.h" +#include "libssh/buffer.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" + +#ifdef WITH_SFTP + +struct sftp_ext_struct { + unsigned int count; + char **name; + char **data; +}; + +/* functions */ +static int sftp_enqueue(sftp_session session, sftp_message msg); +static void sftp_message_free(sftp_message msg); +static void sftp_set_error(sftp_session sftp, int errnum); +static void status_msg_free(sftp_status_message status); + +static sftp_ext sftp_ext_new(void) { + sftp_ext ext; + + ext = calloc(1, sizeof(struct sftp_ext_struct)); + if (ext == NULL) { + return NULL; + } + + return ext; +} + +static void sftp_ext_free(sftp_ext ext) { + unsigned int i; + + if (ext == NULL) { + return; + } + + if (ext->count) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->name[i]); + SAFE_FREE(ext->data[i]); + } + SAFE_FREE(ext->name); + SAFE_FREE(ext->data); + } + + SAFE_FREE(ext); +} + +sftp_session sftp_new(ssh_session session){ + sftp_session sftp; + + if (session == NULL) { + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(sftp); + + return NULL; + } + + sftp->session = session; + sftp->channel = ssh_channel_new(session); + if (sftp->channel == NULL) { + sftp_ext_free(sftp->ext); + SAFE_FREE(sftp); + + return NULL; + } + + if (ssh_channel_open_session(sftp->channel)) { + ssh_channel_free(sftp->channel); + sftp_ext_free(sftp->ext); + SAFE_FREE(sftp); + + return NULL; + } + + if (ssh_channel_request_sftp(sftp->channel)) { + sftp_free(sftp); + + return NULL; + } + + return sftp; +} + +sftp_session sftp_new_channel(ssh_session session, ssh_channel channel){ + sftp_session sftp; + + if (session == NULL) { + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(sftp); + + return NULL; + } + + sftp->session = session; + sftp->channel = channel; + + return sftp; +} + +#ifdef WITH_SERVER +sftp_session sftp_server_new(ssh_session session, ssh_channel chan){ + sftp_session sftp = NULL; + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + sftp->session = session; + sftp->channel = chan; + + return sftp; +} + +int sftp_server_init(sftp_session sftp){ + ssh_session session = sftp->session; + sftp_packet packet = NULL; + ssh_buffer reply = NULL; + uint32_t version; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; + } + + if (packet->type != SSH_FXP_INIT) { + ssh_set_error(session, SSH_FATAL, + "Packet read of type %d instead of SSH_FXP_INIT", + packet->type); + + sftp_packet_free(packet); + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); + + ssh_buffer_get_u32(packet->payload, &version); + version = ntohl(version); + SSH_LOG(SSH_LOG_PACKET, "Client version: %d", version); + sftp->client_version = version; + + sftp_packet_free(packet); + + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_set_error_oom(session); + return -1; + } + + if (ssh_buffer_add_u32(reply, ntohl(LIBSFTP_VERSION)) < 0) { + ssh_set_error_oom(session); + ssh_buffer_free(reply); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) { + ssh_buffer_free(reply); + return -1; + } + ssh_buffer_free(reply); + + SSH_LOG(SSH_LOG_RARE, "Server version sent"); + + if (version > LIBSFTP_VERSION) { + sftp->version = LIBSFTP_VERSION; + } else { + sftp->version=version; + } + + return 0; +} +#endif /* WITH_SERVER */ + +void sftp_free(sftp_session sftp){ + sftp_request_queue ptr; + + if (sftp == NULL) { + return; + } + + ssh_channel_send_eof(sftp->channel); + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + ssh_channel_free(sftp->channel); + + SAFE_FREE(sftp->handles); + + sftp_ext_free(sftp->ext); + ZERO_STRUCTP(sftp); + + SAFE_FREE(sftp); +} + +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload){ + int size; + + if (ssh_buffer_prepend_data(payload, &type, sizeof(uint8_t)) < 0) { + ssh_set_error_oom(sftp->session); + return -1; + } + + size = htonl(ssh_buffer_get_len(payload)); + if (ssh_buffer_prepend_data(payload, &size, sizeof(uint32_t)) < 0) { + ssh_set_error_oom(sftp->session); + return -1; + } + + size = ssh_channel_write(sftp->channel, ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (size < 0) { + return -1; + } else if((uint32_t) size != ssh_buffer_get_len(payload)) { + SSH_LOG(SSH_LOG_PACKET, + "Had to write %d bytes, wrote only %d", + ssh_buffer_get_len(payload), + size); + } + + return size; +} + +sftp_packet sftp_packet_read(sftp_session sftp) { + unsigned char buffer[MAX_BUF_SIZE]; + sftp_packet packet = NULL; + uint32_t tmp; + size_t size; + int r, s, is_eof; + + packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (packet == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + packet->sftp = sftp; + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(packet); + return NULL; + } + + r = ssh_buffer_allocate_size(packet->payload, 4); + if (r < 0) { + ssh_set_error_oom(sftp->session); + goto error; + } + r=0; + do { + // read from channel until 4 bytes have been read or an error occurs + s=ssh_channel_read(sftp->channel, buffer+r, 4-r, 0); + if (s < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + goto error; + } + } else { + r += s; + } + } while (r<4); + ssh_buffer_add_data(packet->payload, buffer, r); + if (ssh_buffer_get_u32(packet->payload, &tmp) != sizeof(uint32_t)) { + ssh_set_error(sftp->session, SSH_FATAL, "Short sftp packet!"); + goto error; + } + + do { + r = ssh_channel_read(sftp->channel, buffer, 1, 0); + if (r < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + goto error; + } + } + } while (r < 1); + ssh_buffer_add_data(packet->payload, buffer, r); + ssh_buffer_get_u8(packet->payload, &packet->type); + + size = ntohl(tmp); + if (size == 0 || size > UINT32_MAX) { + return packet; + } + size--; + + r = ssh_buffer_allocate_size(packet->payload, size); + if (r < 0) { + ssh_set_error_oom(sftp->session); + goto error; + } + while (size > 0 && size < UINT_MAX) { + r=ssh_channel_read(sftp->channel,buffer, + sizeof(buffer)>size ? size:sizeof(buffer),0); + + if (r < 0) { + /* TODO: check if there are cases where an error needs to be set here */ + goto error; + } else if (r == 0) { + /* Retry the reading unless the remote was closed */ + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + goto error; + } + } else if (ssh_buffer_add_data(packet->payload, buffer, r) == SSH_ERROR) { + ssh_set_error_oom(sftp->session); + goto error; + } + size -= r; + } + + return packet; +error: + ssh_buffer_free(packet->payload); + SAFE_FREE(packet); + return NULL; +} + +static void sftp_set_error(sftp_session sftp, int errnum) { + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +/* Get the last sftp error */ +int sftp_get_error(sftp_session sftp) { + if (sftp == NULL) { + return -1; + } + + return sftp->errnum; +} + +static sftp_message sftp_message_new(sftp_session sftp){ + sftp_message msg = NULL; + + msg = calloc(1, sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + msg->payload = ssh_buffer_new(); + if (msg->payload == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(msg); + return NULL; + } + msg->sftp = sftp; + + return msg; +} + +static void sftp_message_free(sftp_message msg) { + if (msg == NULL) { + return; + } + + ssh_buffer_free(msg->payload); + SAFE_FREE(msg); +} + +static sftp_message sftp_get_message(sftp_packet packet) { + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; + int rc; + + msg = sftp_message_new(sftp); + if (msg == NULL) { + return NULL; + } + + msg->sftp = packet->sftp; + msg->packet_type = packet->type; + + if ((packet->type != SSH_FXP_STATUS) && (packet->type!=SSH_FXP_HANDLE) && + (packet->type != SSH_FXP_DATA) && (packet->type != SSH_FXP_ATTRS) && + (packet->type != SSH_FXP_NAME) && (packet->type != SSH_FXP_EXTENDED_REPLY)) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Unknown packet type %d", packet->type); + sftp_message_free(msg); + return NULL; + } + + rc = ssh_buffer_unpack(packet->payload, "d", &msg->id); + if (rc != SSH_OK) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Packet with id %d type %d", + msg->id, + msg->packet_type); + + if (ssh_buffer_add_data(msg->payload, ssh_buffer_get(packet->payload), + ssh_buffer_get_len(packet->payload)) < 0) { + ssh_set_error_oom(sftp->session); + sftp_message_free(msg); + return NULL; + } + + return msg; +} + +static int sftp_read_and_dispatch(sftp_session sftp) { + sftp_packet packet = NULL; + sftp_message msg = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; /* something nasty happened reading the packet */ + } + + msg = sftp_get_message(packet); + sftp_packet_free(packet); + if (msg == NULL) { + return -1; + } + + if (sftp_enqueue(sftp, msg) < 0) { + sftp_message_free(msg); + return -1; + } + + return 0; +} + +void sftp_packet_free(sftp_packet packet) { + if (packet == NULL) { + return; + } + + ssh_buffer_free(packet->payload); + free(packet); +} + +/* Initialize the sftp session with the server. */ +int sftp_init(sftp_session sftp) { + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; + } + + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", packet->type); + sftp_packet_free(packet); + return -1; + } + + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK){ + return -1; + } + SSH_LOG(SSH_LOG_RARE, + "SFTP server version %d", + version); + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + while (rc == SSH_OK) { + int count = sftp->ext->count; + char **tmp; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } + + SSH_LOG(SSH_LOG_RARE, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); + + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + return -1; + } + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; + + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + return -1; + } + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; + + sftp->ext->count = count; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + } + + sftp_packet_free(packet); + + sftp->version = sftp->server_version = version; + + + return 0; +} + +unsigned int sftp_extensions_get_count(sftp_session sftp) { + if (sftp == NULL || sftp->ext == NULL) { + return 0; + } + + return sftp->ext->count; +} + +const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) { + if (sftp == NULL) + return NULL; + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx > sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->name[idx]; +} + +const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) { + if (sftp == NULL) + return NULL; + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx > sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->data[idx]; +} + +int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data) { + int i, n; + + if (sftp == NULL || name == NULL || data == NULL) { + return 0; + } + + n = sftp_extensions_get_count(sftp); + for (i = 0; i < n; i++) { + const char *ext_name = sftp_extensions_get_name(sftp, i); + const char *ext_data = sftp_extensions_get_data(sftp, i); + + if (ext_name != NULL && ext_data != NULL && + strcmp(ext_name, name) == 0 && + strcmp(ext_data, data) == 0) { + return 1; + } + } + + return 0; +} + +static sftp_request_queue request_queue_new(sftp_message msg) { + sftp_request_queue queue = NULL; + + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + return NULL; + } + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) { + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int sftp_enqueue(sftp_session sftp, sftp_message msg) { + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %d type %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls of a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if(queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %d type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +/* + * Assigns a new SFTP ID for new requests and assures there is no collision + * between them. + * Returns a new ID ready to use in a request + */ +static inline uint32_t sftp_get_new_id(sftp_session session) { + return ++session->id_counter; +} + +static sftp_status_message parse_status_msg(sftp_message msg){ + sftp_status_message status; + int rc; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + return NULL; + } + + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + return NULL; + } + + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK){ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + return NULL; + } + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if(rc != SSH_OK && msg->sftp->version >=3){ + /* These are mandatory from version 3 */ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + return NULL; + } + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + if (status->langmsg == NULL) + status->langmsg = strdup(""); + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + status_msg_free(status); + return NULL; + } + + return status; +} + +static void status_msg_free(sftp_status_message status){ + if (status == NULL) { + return; + } + + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +static sftp_file parse_handle_msg(sftp_message msg){ + sftp_file file; + + if(msg->packet_type != SSH_FXP_HANDLE) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_handle message passed in!"); + return NULL; + } + + file = calloc(1, sizeof(struct sftp_file_struct)); + if (file == NULL) { + ssh_set_error_oom(msg->sftp->session); + return NULL; + } + + file->handle = ssh_buffer_get_ssh_string(msg->payload); + if (file->handle == NULL) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_HANDLE message"); + SAFE_FREE(file); + return NULL; + } + + file->sftp = msg->sftp; + file->offset = 0; + file->eof = 0; + + return file; +} + +/* Open a directory */ +sftp_dir sftp_opendir(sftp_session sftp, const char *path){ + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_dir dir = NULL; + sftp_status_message status; + ssh_string path_s; + ssh_buffer payload; + uint32_t id; + int rc; + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + path_s = ssh_string_from_char(path); + if (path_s == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + return NULL; + } + + id = sftp_get_new_id(sftp); + rc = ssh_buffer_allocate_size(payload, + sizeof(uint32_t) * 2 + ssh_string_len(path_s)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + ssh_string_free(path_s); + return NULL; + } + if (ssh_buffer_add_u32(payload, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(payload, path_s) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + ssh_string_free(path_s); + return NULL; + } + ssh_string_free(path_s); + + if (sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload) < 0) { + ssh_buffer_free(payload); + return NULL; + } + ssh_buffer_free(payload); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + case SSH_FXP_HANDLE: + file = parse_handle_msg(msg); + sftp_message_free(msg); + if (file != NULL) { + dir = calloc(1, sizeof(struct sftp_dir_struct)); + if (dir == NULL) { + ssh_set_error_oom(sftp->session); + free(file); + return NULL; + } + + dir->sftp = sftp; + dir->name = strdup(path); + if (dir->name == NULL) { + SAFE_FREE(dir); + SAFE_FREE(file); + return NULL; + } + dir->handle = file->handle; + SAFE_FREE(file); + } + return dir; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during opendir!", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we need it in future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, + int expectnames) { + sftp_attributes attr; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + ssh_string_free(owner); + if (attr->owner == NULL) { + break; + } + + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + ssh_string_free(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + } + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + } + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + } + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while(attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))){ + attr->extended_count--; + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + ssh_string_free(attr->acl); + ssh_string_free(attr->extended_type); + ssh_string_free(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char *sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) { + const char *p, *q; + size_t len, field = 0; + + p = longname; + /* Find the beginning of the field which is specified by sftp_longanme_field_e. */ + while(field != longname_field) { + if(isspace(*p)) { + field++; + p++; + while(*p && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + q = p; + while (! isspace(*q)) { + q++; + } + + len = q - p; + + return strndup(p, len); +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, + int expectname) { + sftp_attributes attr; + int rc; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + if (expectname) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->name, + &attr->longname); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_RARE, "Name: %s", attr->name); + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + goto error; + } + + attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + goto error; + } + } + } + + rc = ssh_buffer_unpack(buf, "d", &attr->flags); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_RARE, + "Flags: %.8lx\n", (long unsigned int) attr->flags); + + if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_unpack(buf, "q", &attr->size); + if(rc != SSH_OK) { + goto error; + } + SSH_LOG(SSH_LOG_RARE, + "Size: %llu\n", + (long long unsigned int) attr->size); + } + + if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->uid, + &attr->gid); + if (rc != SSH_OK){ + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_unpack(buf, "d", &attr->permissions); + if (rc != SSH_OK){ + goto error; + } + + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->atime, + &attr->mtime); + if (rc != SSH_OK){ + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { + rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); + if (rc != SSH_OK){ + goto error; + } + + if (attr->extended_count > 0){ + rc = ssh_buffer_unpack(buf, "ss", + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK){ + goto error; + } + attr->extended_count--; + } + /* just ignore the remaining extensions */ + + while (attr->extended_count > 0){ + ssh_string tmp1,tmp2; + rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); + if (rc != SSH_OK){ + goto error; + } + SAFE_FREE(tmp1); + SAFE_FREE(tmp2); + attr->extended_count--; + } + } + + return attr; + + error: + ssh_string_free(attr->extended_type); + ssh_string_free(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; +} + +/* FIXME is this really needed as a public function? */ +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) { + uint32_t flags = (attr ? attr->flags : 0); + int rc; + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { + return -1; + } + + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } + } + return 0; +} + + +sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf, + int expectname) { + switch(session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", session->server_version); + return NULL; + } + + return NULL; +} + +/* Get the version of the SFTP protocol supported by the server */ +int sftp_server_version(sftp_session sftp) { + return sftp->server_version; +} + +/* Get a single file attributes structure of a directory. */ +sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) { + sftp_message msg = NULL; + sftp_status_message status; + sftp_attributes attr; + ssh_buffer payload; + uint32_t id; + int rc; + + if (dir->buffer == NULL) { + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + rc = ssh_buffer_allocate_size(payload, + sizeof(uint32_t) * 2 + + ssh_string_len(dir->handle)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + return NULL; + } + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(payload, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(payload, dir->handle) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + return NULL; + } + + if (sftp_packet_write(sftp, SSH_FXP_READDIR, payload) < 0) { + ssh_buffer_free(payload); + return NULL; + } + ssh_buffer_free(payload); + + SSH_LOG(SSH_LOG_PACKET, + "Sent a ssh_fxp_readdir with id %d", id); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + switch (msg->packet_type){ + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + dir->eof = 1; + status_msg_free(status); + return NULL; + default: + break; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Unknown error status: %d", status->status); + status_msg_free(status); + + return NULL; + case SSH_FXP_NAME: + ssh_buffer_get_u32(msg->payload, &dir->count); + dir->count = ntohl(dir->count); + dir->buffer = msg->payload; + msg->payload = NULL; + sftp_message_free(msg); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Unsupported message back %d", msg->packet_type); + sftp_message_free(msg); + + return NULL; + } + } + + /* now dir->buffer contains a buffer and dir->count != 0 */ + if (dir->count == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Count of files sent by the server is zero, which is invalid, or " + "libsftp bug"); + return NULL; + } + + SSH_LOG(SSH_LOG_RARE, "Count is %d", dir->count); + + attr = sftp_parse_attr(sftp, dir->buffer, 1); + if (attr == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + + dir->count--; + if (dir->count == 0) { + ssh_buffer_free(dir->buffer); + dir->buffer = NULL; + } + + return attr; +} + +/* Tell if the directory has reached EOF (End Of File). */ +int sftp_dir_eof(sftp_dir dir) { + return dir->eof; +} + +/* Free a SFTP_ATTRIBUTE handle */ +void sftp_attributes_free(sftp_attributes file){ + if (file == NULL) { + return; + } + + ssh_string_free(file->acl); + ssh_string_free(file->extended_data); + ssh_string_free(file->extended_type); + + SAFE_FREE(file->name); + SAFE_FREE(file->longname); + SAFE_FREE(file->group); + SAFE_FREE(file->owner); + + SAFE_FREE(file); +} + +static int sftp_handle_close(sftp_session sftp, ssh_string handle) { + sftp_status_message status; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(handle)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, handle) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_CLOSE ,buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(sftp,id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if(status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during sftp_handle_close!", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Close an open file handle. */ +int sftp_close(sftp_file file){ + int err = SSH_NO_ERROR; + + SAFE_FREE(file->name); + if (file->handle){ + err = sftp_handle_close(file->sftp,file->handle); + ssh_string_free(file->handle); + } + /* FIXME: check server response and implement errno */ + SAFE_FREE(file); + + return err; +} + +/* Close an open directory. */ +int sftp_closedir(sftp_dir dir){ + int err = SSH_NO_ERROR; + + SAFE_FREE(dir->name); + if (dir->handle) { + err = sftp_handle_close(dir->sftp, dir->handle); + ssh_string_free(dir->handle); + } + /* FIXME: check server response and implement errno */ + ssh_buffer_free(dir->buffer); + SAFE_FREE(dir); + + return err; +} + +/* Open a file on the server. */ +sftp_file sftp_open(sftp_session sftp, const char *file, int flags, + mode_t mode) { + sftp_message msg = NULL; + sftp_status_message status; + struct sftp_attributes_struct attr; + sftp_file handle; + ssh_string filename; + ssh_buffer buffer; + sftp_attributes stat_data; + uint32_t sftp_flags = 0; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + filename = ssh_string_from_char(file); + if (filename == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + if ((flags & O_RDWR) == O_RDWR) { + sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ); + } else if ((flags & O_WRONLY) == O_WRONLY) { + sftp_flags |= SSH_FXF_WRITE; + } else { + sftp_flags |= SSH_FXF_READ; + } + if ((flags & O_CREAT) == O_CREAT) + sftp_flags |= SSH_FXF_CREAT; + if ((flags & O_TRUNC) == O_TRUNC) + sftp_flags |= SSH_FXF_TRUNC; + if ((flags & O_EXCL) == O_EXCL) + sftp_flags |= SSH_FXF_EXCL; + if ((flags & O_APPEND) == O_APPEND) { + sftp_flags |= SSH_FXF_APPEND; + } + SSH_LOG(SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags); + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 4 + + ssh_string_len(filename)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(filename); + return NULL; + } + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, filename) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(filename); + return NULL; + } + ssh_string_free(filename); + + if (ssh_buffer_add_u32(buffer, htonl(sftp_flags)) < 0 || + buffer_add_attributes(buffer, &attr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_OPEN, buffer) < 0) { + ssh_buffer_free(buffer); + return NULL; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + sftp_message_free(msg); + if ((flags & O_APPEND) == O_APPEND) { + stat_data = sftp_stat(sftp, file); + if (stat_data == NULL) { + sftp_close(handle); + return NULL; + } + if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Cannot open in append mode. Unknown file size."); + sftp_close(handle); + return NULL; + } + + handle->offset = stat_data->size; + } + return handle; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during open!", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +void sftp_file_set_nonblocking(sftp_file handle){ + handle->nonblocking=1; +} +void sftp_file_set_blocking(sftp_file handle){ + handle->nonblocking=0; +} + +/* Read from a file using an opened sftp file handle. */ +ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { + sftp_session sftp = handle->sftp; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring; + size_t datalen; + ssh_buffer buffer; + int id; + int rc; + + if (handle->eof) { + return 0; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(handle->sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + handle->handle, + handle->offset, + count); + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (handle->nonblocking) { + if (ssh_channel_poll(handle->sftp->channel, 0) == 0) { + /* we cannot block */ + return 0; + } + } + if (sftp_read_and_dispatch(handle->sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(handle->sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + handle->eof = 1; + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session,SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return -1; + } + + datalen = ssh_string_len(datastring); + if (datalen > count) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%" PRIdS " and asked for %" PRIdS, + datalen, count); + ssh_string_free(datastring); + return -1; + } + handle->offset += (uint64_t)datalen; + memcpy(buf, ssh_string_data(datastring), datalen); + ssh_string_free(datastring); + return datalen; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_message_free(msg); + return -1; + } + + return -1; /* not reached */ +} + +/* Start an asynchronous read from a file using an opened sftp file handle. */ +int sftp_async_read_begin(sftp_file file, uint32_t len){ + sftp_session sftp = file->sftp; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + file->handle, + file->offset, + len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + file->offset += len; /* assume we'll read len bytes */ + + return id; +} + +/* Wait for an asynchronous read to complete and save the data. */ +int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ + sftp_session sftp; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring; + int err = SSH_OK; + uint32_t len; + + if (file == NULL) { + return SSH_ERROR; + } + sftp = file->sftp; + + if (file->eof) { + return 0; + } + + /* handle an existing request */ + while (msg == NULL) { + if (file->nonblocking){ + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp,id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server : %s", status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + } + status_msg_free(status); + return err; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return SSH_ERROR; + } + if (ssh_string_len(datastring) > size) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%" PRIdS " and asked for %u", + ssh_string_len(datastring), size); + ssh_string_free(datastring); + return SSH_ERROR; + } + len = ssh_string_len(datastring); + /* Update the offset with the correct value */ + file->offset = file->offset - (size - len); + memcpy(data, ssh_string_data(datastring), len); + ssh_string_free(datastring); + return len; + default: + ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type); + sftp_message_free(msg); + return SSH_ERROR; + } + + return SSH_ERROR; +} + +ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { + sftp_session sftp = file->sftp; + sftp_message msg = NULL; + sftp_status_message status; + ssh_buffer buffer; + uint32_t id; + int len; + int packetlen; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(file->sftp); + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + count, /* len of datastring */ + (size_t)count, buf); + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + packetlen=ssh_buffer_get_len(buffer); + len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + ssh_buffer_free(buffer); + if (len < 0) { + return -1; + } else if (len != packetlen) { + SSH_LOG(SSH_LOG_PACKET, + "Could not write as much data as expected"); + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(file->sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(file->sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + file->offset += count; + status_msg_free(status); + return count; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + file->offset += count; + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", msg->packet_type); + sftp_message_free(msg); + return -1; + } + + return -1; /* not reached */ +} + +/* Seek to a specific location in a file. */ +int sftp_seek(sftp_file file, uint32_t new_offset) { + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +int sftp_seek64(sftp_file file, uint64_t new_offset) { + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +/* Report current byte position in file. */ +unsigned long sftp_tell(sftp_file file) { + return (unsigned long)file->offset; +} +/* Report current byte position in file. */ +uint64_t sftp_tell64(sftp_file file) { + return (uint64_t) file->offset; +} + +/* Rewinds the position of the file pointer to the beginning of the file.*/ +void sftp_rewind(sftp_file file) { + file->offset = 0; + file->eof = 0; +} + +/* code written by Nick */ +int sftp_unlink(sftp_session sftp, const char *file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp)) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_STATUS) { + /* by specification, this command's only supposed to return SSH_FXP_STATUS */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session,SSH_FATAL, + "Received message %d when attempting to remove file", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* code written by Nick */ +int sftp_rmdir(sftp_session sftp, const char *directory) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to remove directory", + msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Code written by Nick */ +int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_attributes errno_attr = NULL; + struct sftp_attributes_struct attr; + ssh_buffer buffer; + ssh_string path; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + path = ssh_string_from_char(directory); + if (path == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(path)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, path) < 0 || + buffer_add_attributes(buffer, &attr) < 0 || + sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + ssh_buffer_free(buffer); + ssh_string_free(path); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_FAILURE: + /* + * mkdir always returns a failure, even if the path already exists. + * To be POSIX conform and to be able to map it to EEXIST a stat + * call is needed here. + */ + errno_attr = sftp_lstat(sftp, directory); + if (errno_attr != NULL) { + SAFE_FREE(errno_attr); + sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS); + } + break; + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to make directory", + msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* code written by nick */ +int sftp_rename(sftp_session sftp, const char *original, const char *newname) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dss", + id, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + if (sftp->version >= 4){ + /* POSIX rename atomically replaces newpath, we should do the same + * only available on >=v4 */ + ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); + } + + if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, if it didn't, + * then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Code written by Nick */ +/* Set file attributes on a file, directory or symbolic link. */ +int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) { + uint32_t id; + ssh_buffer buffer; + ssh_string path; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + path = ssh_string_from_char(file); + if (path == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(path)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, path) < 0 || + buffer_add_attributes(buffer, attr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + ssh_buffer_free(buffer); + ssh_string_free(path); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Change the file owner and group */ +int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.uid = owner; + attr.gid = group; + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change permissions of a file */ +int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change the last modification and access time of a file. */ +int sftp_utimes(sftp_session sftp, const char *file, + const struct timeval *times) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.atime = times[0].tv_sec; + attr.atime_nseconds = times[0].tv_usec; + + attr.mtime = times[1].tv_sec; + attr.mtime_nseconds = times[1].tv_usec; + + attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES; + + return sftp_setstat(sftp, file, &attr); +} + +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return -1; + if (target == NULL || dest == NULL) { + ssh_set_error_invalid(sftp->session); + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + + /* TODO check for version number if they ever fix it. */ + if (ssh_get_openssh_version(sftp->session)) { + rc = ssh_buffer_pack(buffer, + "dss", + id, + target, + dest); + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + dest, + target); + } + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +char *sftp_readlink(sftp_session sftp, const char *path) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string path_s = NULL; + ssh_string link_s = NULL; + ssh_buffer buffer; + char *lnk; + uint32_t ignored; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); + return NULL; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + path_s = ssh_string_from_char(path); + if (path_s == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(path_s)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path_s); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, path_s) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path_s); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_READLINK, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(path_s); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(path_s); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + /* we don't care about "count" */ + ssh_buffer_get_u32(msg->payload, &ignored); + /* we only care about the file name string */ + link_s = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (link_s == NULL) { + /* TODO: what error to set here? */ + return NULL; + } + lnk = ssh_string_to_char(link_s); + ssh_string_free(link_s); + + return lnk; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { + sftp_statvfs_t statvfs; + int rc; + + statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (statvfs == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", + &statvfs->f_bsize, /* file system block size */ + &statvfs->f_frsize, /* fundamental fs block size */ + &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ + &statvfs->f_bfree, /* free blocks in file system */ + &statvfs->f_bavail, /* free blocks for non-root */ + &statvfs->f_files, /* total file inodes */ + &statvfs->f_ffree, /* free file inodes */ + &statvfs->f_favail, /* free file inodes for to non-root */ + &statvfs->f_fsid, /* file system id */ + &statvfs->f_flag, /* bit mask of f_flag values */ + &statvfs->f_namemax/* maximum filename length */ + ); + if (rc != SSH_OK) { + SAFE_FREE(statvfs); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); + return NULL; + } + + return statvfs; +} + +sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string pathstr; + ssh_string ext; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ext = ssh_string_from_char("statvfs@openssh.com"); + if (ext == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + pathstr = ssh_string_from_char(path); + if (pathstr == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 3 + + ssh_string_len(ext) + + ssh_string_len(pathstr)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, ext) < 0 || + ssh_buffer_add_ssh_string(buffer, pathstr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to get statvfs", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +int sftp_fsync(sftp_file file) +{ + sftp_session sftp; + sftp_message msg = NULL; + ssh_string ext; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return -1; + } + sftp = file->sftp; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + ext = ssh_string_from_char("fsync@openssh.com"); + if (ext == NULL) { + ssh_set_error_oom(sftp->session); + rc = -1; + goto done; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 3 + + ssh_string_len(ext) + + ssh_string_len(file->handle)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + id = sftp_get_new_id(sftp); + rc = ssh_buffer_add_u32(buffer, htonl(id)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + rc = ssh_buffer_add_ssh_string(buffer, ext); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + rc = ssh_buffer_add_ssh_string(buffer, file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + do { + rc = sftp_read_and_dispatch(sftp); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + rc = -1; + goto done; + } + msg = sftp_dequeue(sftp, id); + } while (msg == NULL); + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + sftp_status_message status; + + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + rc = -1; + goto done; + } + + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + /* SUCCESS, LEAVE */ + status_msg_free(status); + rc = 0; + goto done; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + + rc = -1; + goto done; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to set stats", + msg->packet_type); + sftp_message_free(msg); + } + + rc = -1; +done: + ssh_buffer_free(buffer); + ssh_string_free(ext); + + return rc; +} + +sftp_statvfs_t sftp_fstatvfs(sftp_file file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_session sftp; + ssh_string ext; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + sftp = file->sftp; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ext = ssh_string_from_char("fstatvfs@openssh.com"); + if (ext == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 3 + + ssh_string_len(ext) + + ssh_string_len(file->handle)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, ext) < 0 || + ssh_buffer_add_ssh_string(buffer, file->handle) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(ext); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +void sftp_statvfs_free(sftp_statvfs_t statvfs) { + if (statvfs == NULL) { + return; + } + + SAFE_FREE(statvfs); +} + +/* another code written by Nick */ +char *sftp_canonicalize_path(sftp_session sftp, const char *path) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string name = NULL; + ssh_string pathstr; + ssh_buffer buffer; + char *cname; + uint32_t ignored; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + pathstr = ssh_string_from_char(path); + if (pathstr == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(pathstr)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, pathstr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + /* we don't care about "count" */ + ssh_buffer_get_u32(msg->payload, &ignored); + /* we only care about the file name string */ + name = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (name == NULL) { + /* TODO: error message? */ + return NULL; + } + cname = ssh_string_to_char(name); + ssh_string_free(name); + if (cname == NULL) { + ssh_set_error_oom(sftp->session); + } + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +static sftp_attributes sftp_xstat(sftp_session sftp, const char *path, + int param) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string pathstr; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + pathstr = ssh_string_from_char(path); + if (pathstr == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(pathstr)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, pathstr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + if (sftp_packet_write(sftp, param, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_ATTRS) { + sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + } + ssh_set_error(sftp->session, SSH_FATAL, + "Received mesg %d during stat()", msg->packet_type); + sftp_message_free(msg); + + return NULL; +} + +sftp_attributes sftp_stat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_STAT); +} + +sftp_attributes sftp_lstat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_LSTAT); +} + +sftp_attributes sftp_fstat(sftp_file file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(file->sftp->session); + return NULL; + } + + rc = ssh_buffer_allocate_size(buffer, + sizeof(uint32_t) * 2 + + ssh_string_len(file->handle)); + if (rc < 0) { + ssh_set_error_oom(file->sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + id = sftp_get_new_id(file->sftp); + if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 || + ssh_buffer_add_ssh_string(buffer, file->handle) < 0) { + ssh_set_error_oom(file->sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + if (sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer) < 0) { + ssh_buffer_free(buffer); + return NULL; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(file->sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(file->sftp, id); + } + + if (msg->packet_type == SSH_FXP_ATTRS){ + sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + } + ssh_set_error(file->sftp->session, SSH_FATAL, + "Received msg %d during fstat()", msg->packet_type); + sftp_message_free(msg); + + return NULL; +} + +#endif /* WITH_SFTP */ diff --git a/external/version.ini b/external/version.ini index 07e5c26..df3ce56 100644 --- a/external/version.ini +++ b/external/version.ini @@ -2,7 +2,7 @@ openssl = 1.0.2p,1000210f libuv = 1.23.0 mbedtls = 2.12.0 -libssh = 0.7.5 +libssh = 0.8.2 jsoncpp = 0.10.6 mongoose = 6.12 diff --git a/server/.idea/server.iml b/server/.idea/server.iml index f08604b..6d70257 100644 --- a/server/.idea/server.iml +++ b/server/.idea/server.iml @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/server/tp_core/protocol/ssh/stdafx.cpp b/server/tp_core/protocol/ssh/stdafx.cpp index d38f7dd..cdf02c4 100644 --- a/server/tp_core/protocol/ssh/stdafx.cpp +++ b/server/tp_core/protocol/ssh/stdafx.cpp @@ -11,9 +11,9 @@ #ifdef EX_OS_WIN32 # ifdef EX_DEBUG -# pragma comment(lib, "libsshMTd.lib") +# pragma comment(lib, "..\\..\\..\\..\\external\\libssh\\lib\\debug\\ssh.lib") # else -# pragma comment(lib, "libsshMT.lib") +# pragma comment(lib, "..\\..\\..\\..\\external\\libssh\\lib\\release\\ssh.lib") # endif # pragma comment(lib, "libeay32.lib") # pragma comment(lib, "ws2_32.lib")