diff --git a/Makefile.am b/Makefile.am index 94097eac..ae219bb9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,6 +22,7 @@ CFLAGS += -D_GNU_SOURCE endif CFLAGS += -Wno-unused-parameter -Wno-unused-variable +CFLAGS += -Ilibssh/include/ # Set flags for gcc. gcc4 whines abouts silly stuff so it needs slightly @@ -240,3 +241,9 @@ endif if NO_B64_NTOP nodist_tmate_server_SOURCES += compat/b64_ntop.c endif + +tmate_server_LDADD = libssh/build/src/libssh.a + +libssh/build/src/libssh.a: + cd libssh/build && cmake .. -DWITH_SFTP=OFF -DWITH_SERVER=ON -DWITH_PCAP=OFF -DWITH_STATIC_LIB=ON + +make -C libssh/build ssh_static diff --git a/configure.ac b/configure.ac index bfffefa1..013cd655 100644 --- a/configure.ac +++ b/configure.ac @@ -107,8 +107,12 @@ AC_MSG_RESULT($found_glibc) # Look for clock_gettime. Must come before event_init. AC_SEARCH_LIBS(clock_gettime, rt) -AC_SEARCH_LIBS(ssh_new, ssh) -AC_SEARCH_LIBS(msgpack_object_print, msgpack) +AC_CHECK_LIB(z, inflate, [], + [AC_MSG_ERROR([zlib library required])]) +AC_CHECK_LIB(crypto,CRYPTO_new_ex_data, [], + [AC_MSG_ERROR([OpenSSL library required])]) +AC_CHECK_LIB(ssl, SSL_library_init, [], + [AC_MSG_ERROR([OpenSSL library required])]) # Look for libevent. PKG_CHECK_MODULES( diff --git a/libssh/.gitignore b/libssh/.gitignore new file mode 100644 index 00000000..c3c0e572 --- /dev/null +++ b/libssh/.gitignore @@ -0,0 +1,8 @@ +*.a +*.o +.* +*.swp +*~$ +build +cscope.* +tags diff --git a/libssh/AUTHORS b/libssh/AUTHORS new file mode 100644 index 00000000..fd753860 --- /dev/null +++ b/libssh/AUTHORS @@ -0,0 +1,15 @@ +Author(s): +Aris Adamantiadis (project initiator) + +Andreas Schneider (developer) + +Nick Zitzmann (mostly client SFTP stuff) + +Norbert Kiesel (getaddrinfo and other patches) + +Jean-Philippe Garcia Ballester (Port to libgcrypt and configure.in voodoo, debian packaging) + +Contributor(s): + +Laurent Bigonville (debian packaging) + diff --git a/libssh/BSD b/libssh/BSD new file mode 100644 index 00000000..b8dba0d2 --- /dev/null +++ b/libssh/BSD @@ -0,0 +1,24 @@ +Some parts are under the BSDv2 License : + + +Copyright (c) 2000 Markus Friedl. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/libssh/CMakeLists.txt b/libssh/CMakeLists.txt new file mode 100644 index 00000000..81ac5884 --- /dev/null +++ b/libssh/CMakeLists.txt @@ -0,0 +1,136 @@ +project(libssh C) + +# Required cmake version +cmake_minimum_required(VERSION 2.6.0) + +# global needed variables +set(APPLICATION_NAME ${PROJECT_NAME}) + +set(APPLICATION_VERSION_MAJOR "0") +set(APPLICATION_VERSION_MINOR "5") +set(APPLICATION_VERSION_PATCH "90") + +set(APPLICATION_VERSION "${APPLICATION_VERSION_MAJOR}.${APPLICATION_VERSION_MINOR}.${APPLICATION_VERSION_PATCH}") + +# SOVERSION scheme: CURRENT.AGE.REVISION +# If there was an incompatible interface change: +# Increment CURRENT. Set AGE and REVISION to 0 +# If there was a compatible interface change: +# Increment AGE. Set REVISION to 0 +# If the source code was changed, but there were no interface changes: +# Increment REVISION. +set(LIBRARY_VERSION "4.3.0") +set(LIBRARY_SOVERSION "4") + +# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked +set(CMAKE_MODULE_PATH + ${CMAKE_SOURCE_DIR}/cmake/Modules +) + +# add definitions +include(DefineCMakeDefaults) +include(DefinePlatformDefaults) +include(DefineCompilerFlags) +include(DefineInstallationPaths) +include(DefineOptions.cmake) +include(CPackConfig.cmake) + +# disallow in-source build +include(MacroEnsureOutOfSourceBuild) +macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out of source build. Please create a separate build directory and run 'cmake /path/to/${PROJECT_NAME} [options]' there.") + +# add macros +include(MacroAddPlugin) +include(MacroCopyFile) + +# search for libraries +if (WITH_ZLIB) + find_package(ZLIB REQUIRED) +endif (WITH_ZLIB) + +if (WITH_GCRYPT) + find_package(GCrypt 1.5.0 REQUIRED) + if (NOT GCRYPT_FOUND) + message(FATAL_ERROR "Could not find GCrypt") + endif (NOT GCRYPT_FOUND) +else (WITH_GCRYPT) + find_package(OpenSSL) + if (NOT OPENSSL_FOUND) + find_package(GCrypt) + if (NOT GCRYPT_FOUND) + message(FATAL_ERROR "Could not find OpenSSL or GCrypt") + endif (NOT GCRYPT_FOUND) + endif (NOT OPENSSL_FOUND) +endif(WITH_GCRYPT) + +# Find out if we have threading available +set(CMAKE_THREAD_PREFER_PTHREADS ON) +find_package(Threads) + +# config.h checks +include(ConfigureChecks.cmake) +configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +# check subdirectories +add_subdirectory(doc) +add_subdirectory(include) +add_subdirectory(src) + +# pkg-config file +configure_file(libssh.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc) +configure_file(libssh_threads.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh_threads.pc) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc + ${CMAKE_CURRENT_BINARY_DIR}/libssh_threads.pc + DESTINATION + ${LIB_INSTALL_DIR}/pkgconfig + COMPONENT + pkgconfig +) + +# cmake config files +configure_file(libssh-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/libssh-config.cmake @ONLY) +configure_file(libssh-config-version.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/libssh-config-version.cmake @ONLY) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/libssh-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/libssh-config-version.cmake + DESTINATION + ${CMAKE_INSTALL_DIR} + COMPONENT + devel +) + +# in tree build settings +configure_file(libssh-build-tree-settings.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/libssh-build-tree-settings.cmake @ONLY) + +add_subdirectory(examples) + +if (WITH_TESTING) + find_package(CMocka REQUIRED) + include(AddCMockaTest) + add_subdirectory(tests) +endif (WITH_TESTING) + + +message(STATUS "********************************************") +message(STATUS "********** ${PROJECT_NAME} build options : **********") + +message(STATUS "zlib support: ${WITH_ZLIB}") +message(STATUS "libgcrypt support: ${WITH_GCRYPT}") +message(STATUS "SSH-1 support: ${WITH_SSH1}") +message(STATUS "SFTP support: ${WITH_SFTP}") +message(STATUS "Server support : ${WITH_SERVER}") +message(STATUS "Pcap debugging support : ${WITH_PCAP}") +message(STATUS "With static library: ${WITH_STATIC_LIB}") +message(STATUS "Unit testing: ${WITH_TESTING}") +message(STATUS "Client code Unit testing: ${WITH_CLIENT_TESTING}") +if (WITH_INTERNAL_DOC) + message(STATUS "Internal documentation generation") +else (WITH_INTERNAL_DOC) + message(STATUS "Public API documentation generation") +endif (WITH_INTERNAL_DOC) +message(STATUS "Benchmarks: ${WITH_BENCHMARKS}") +message(STATUS "********************************************") + diff --git a/libssh/COPYING b/libssh/COPYING new file mode 100644 index 00000000..cb7d9b73 --- /dev/null +++ b/libssh/COPYING @@ -0,0 +1,460 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + Linking with OpenSSL +17. In addition, as a special exception, we give permission to link the code of its release of libssh with the OpenSSL project's "OpenSSL" library (or with modified versions of it that use the same license as the "OpenSSL" library), and distribute the linked executables. You must obey the GNU Lesser General Public License in all respects for all of the code used other than "OpenSSL". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. + END OF TERMS AND CONDITIONS diff --git a/libssh/CPackConfig.cmake b/libssh/CPackConfig.cmake new file mode 100644 index 00000000..2e4c9fee --- /dev/null +++ b/libssh/CPackConfig.cmake @@ -0,0 +1,53 @@ +# For help take a look at: +# http://www.cmake.org/Wiki/CMake:CPackConfiguration + +### general settings +set(CPACK_PACKAGE_NAME ${APPLICATION_NAME}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The SSH library") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README") +set(CPACK_PACKAGE_VENDOR "The SSH Library Development Team") +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING") + + +### versions +set(CPACK_PACKAGE_VERSION_MAJOR "0") +set(CPACK_PACKAGE_VERSION_MINOR "5") +set(CPACK_PACKAGE_VERSION_PATCH "90") +set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") + + +### source generator +set(CPACK_SOURCE_GENERATOR "TGZ") +set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]svn/;/[.]git/;.gitignore;/build/;tags;cscope.*") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + +if (WIN32) + set(CPACK_GENERATOR "ZIP") + + ### nsis generator + find_package(NSIS) + if (NSIS_MAKE) + set(CPACK_GENERATOR "${CPACK_GENERATOR};NSIS") + set(CPACK_NSIS_DISPLAY_NAME "The SSH Library") + set(CPACK_NSIS_COMPRESSOR "/SOLID zlib") + set(CPACK_NSIS_MENU_LINKS "http://www.libssh.org/" "libssh homepage") + endif (NSIS_MAKE) +endif (WIN32) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "libssh") + +set(CPACK_PACKAGE_FILE_NAME ${APPLICATION_NAME}-${CPACK_PACKAGE_VERSION}) + +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C/C++ Headers") +set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION + "Libraries used to build programs which use libssh") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION + "C/C++ header files for use with libssh") +set(CPACK_COMPONENT_HEADERS_DEPENDS libraries) +#set(CPACK_COMPONENT_APPLICATIONS_GROUP "Runtime") +set(CPACK_COMPONENT_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +include(CPack) diff --git a/libssh/CTestConfig.cmake b/libssh/CTestConfig.cmake new file mode 100644 index 00000000..d8a41831 --- /dev/null +++ b/libssh/CTestConfig.cmake @@ -0,0 +1,9 @@ +set(UPDATE_TYPE "true") + +set(CTEST_PROJECT_NAME "libssh") +set(CTEST_NIGHTLY_START_TIME "01:00:00 CET") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "test.libssh.org") +set(CTEST_DROP_LOCATION "/submit.php?project=libssh") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/libssh/ChangeLog b/libssh/ChangeLog new file mode 100644 index 00000000..5bc0784a --- /dev/null +++ b/libssh/ChangeLog @@ -0,0 +1,298 @@ +ChangeLog +========== + +version 0.5.x (released 2012-xx-xx) + * Added new PKI infrastructure. + * Added simplified user auth functions. + * Added ECDSA pubkey support. + * Added ECDSA hostkey support. + * Added diffie-hellman-group14-sha1 support. + * Fixed a ton of bugs. + +version 0.5.0 (released 2011-06-01) + * Added ssh_ prefix to all functions. + * Added complete Windows support. + * Added improved server support. + * Added unit tests for a lot of functions. + * Added asynchronous service request. + * Added a multiplatform ssh_getpass() function. + * Added a tutorial. + * Added a lot of documentation. + * Fixed a lot of bugs. + * Fixed several memory leaks. + +version 0.4.8 (released 2011-01-15) + * Fixed memory leaks in session signing. + * Fixed memory leak in ssh_print_hexa. + * Fixed problem with ssh_connect w/ timeout and fd > 1024. + * Fixed some warnings on OS/2. + * Fixed installation path for OS/2. + +version 0.4.7 (released 2010-12-28) + * Fixed a possible memory leak in ssh_get_user_home(). + * Fixed a memory leak in sftp_xstat. + * Fixed uninitialized fd->revents member. + * Fixed timout value in ssh_channel_accept(). + * Fixed length checks in ssh_analyze_banner(). + * Fixed a possible data overread and crash bug. + * Fixed setting max_fd which breaks ssh_select(). + * Fixed some pedantic build warnings. + * Fixed a memory leak with session->bindaddr. + +version 0.4.6 (released 2010-09-03) + * Added a cleanup function to free the ws2_32 library. + * Fixed build with gcc 3.4. + * Fixed the Windows build on Vista and newer. + * Fixed the usage of WSAPoll() on Windows. + * Fixed "@deprecated" in doxygen + * Fixed some mingw warnings. + * Fixed handling of opened channels. + * Fixed keepalive problem on older openssh servers. + * Fixed testing for big endian on Windows. + * Fixed the Windows preprocessor macros and defines. + +version 0.4.5 (released 2010-07-13) + * Added option to bind a client to an ip address. + * Fixed the ssh socket polling function. + * Fixed Windows related bugs in bsd_poll(). + * Fixed serveral build warnings. + +version 0.4.4 (released 2010-06-01) + * Fixed a bug in the expand function for escape sequences. + * Fixed a bug in the tilde expand function. + * Fixed a bug in setting the options. + +version 0.4.3 (released 2010-05-18) + * Added global/keepalive responses. + * Added runtime detection of WSAPoll(). + * Added a select(2) based poll-emulation if poll(2) is not available. + * Added a function to expand an escaped string. + * Added a function to expand the tilde from a path. + * Added a proxycommand support. + * Added ssh_privatekey_type public function + * Added the possibility to define _OPENSSL_DIR and _ZLIB_DIR. + * Fixed sftp_chown. + * Fixed sftp_rename on protocol version 3. + * Fixed a blocking bug in channel_poll. + * Fixed config parsing wich has overwritten user specified values. + * Fixed hashed [host]:port format in knownhosts + * Fixed Windows build. + * Fixed doublefree happening after a negociation error. + * Fixed aes*-ctr with <= OpenSSL 0.9.7b. + * Fixed some documentation. + * Fixed exec example which has broken read usage. + * Fixed broken algorithm choice for server. + * Fixed a typo that we don't export all symbols. + * Removed the unneeded dependency to doxygen. + * Build examples only on the Linux plattform. + +version 0.4.2 (released 2010-03-15) + * Added owner and group information in sftp attributes. + * Added missing SSH_OPTIONS_FD option. + * Added printout of owner and group in the sftp example. + * Added a prepend function for ssh_list. + * Added send back replies to openssh's keepalives. + * Fixed documentation in scp code + * Fixed longname parsing, this only workings with readdir. + * Fixed and added support for several identity files. + * Fixed sftp_parse_longname() on Windows. + * Fixed a race condition bug in ssh_scp_close() + * Remove config support for SSHv1 Cipher variable. + * Rename ssh_list_add to ssh_list_append. + * Rename ssh_list_get_head to ssh_list_pop_head + +version 0.4.1 (released 2010-02-13) + * Added support for aes128-ctr, aes192-ctr and aes256-ctr encryption. + * Added an example for exec. + * Added private key type detection feature in privatekey_from_file(). + * Fixed zlib compression fallback. + * Fixed kex bug that client preference should be prioritary + * Fixed known_hosts file set by the user. + * Fixed a memleak in channel_accept(). + * Fixed underflow when leave_function() are unbalanced + * Fixed memory corruption in handle_channel_request_open(). + * Fixed closing of a file handle case of errors in privatekey_from_file(). + * Fixed ssh_get_user_home_dir() to be thread safe. + * Fixed the doxygen documentation. + +version 0.4.0 (released 2009-12-10) + * Added scp support. + * Added support for sending signals (RFC 4254, section 6.9). + * Added MSVC support. + * Added support for ~/.ssh/config. + * Added sftp extension support. + * Added X11 forwarding support for client. + * Added forward listening. + * Added support for openssh extensions (statvfs, fstatvfs). + * Added a cleaned up interface for setting options. + * Added a generic way to handle sockets asynchronously. + * Added logging of the sftp flags used to open a file. + * Added full poll() support and poll-emulation for win32. + * Added missing 64bit functions in sftp. + * Added support for ~/ and SSH_DIR/ in filenames instead of %s/. + * Fixed Fix channel_get_exit_status bug. + * Fixed calltrace logging to make it optional. + * Fixed compilation on Solaris. + * Fixed resolving of ip addresses. + * Fixed libssh compilation without server support. + * Fixed possible memory corruptions (ticket #14). + +version 0.3.4 (released 2009-09-14) + * Added ssh_basename and ssh_dirname. + * Added a portable ssh_mkdir function. + * Added a sftp_tell64() function. + * Added missing NULL pointer checks to crypt_set_algorithms_server. + * Fixed ssh_write_knownhost if ~/.ssh doesn't exist. + * Fixed a possible integer overflow in buffer_get_data(). + * Fixed possible security bug in packet_decrypt(). + * Fixed a possible stack overflow in agent code. + +version 0.3.3 (released 2009-08-18) + * Fixed double free pointer crash in dsa_public_to_string. + * Fixed channel_get_exit_status bug. + * Fixed ssh_finalize which didn't clear the flag. + * Fixed memory leak introduced by previous bugfix. + * Fixed channel_poll broken when delayed EOF recvd. + * Fixed stupid "can't parse known host key" bug. + * Fixed possible memory corruption (ticket #14). + +version 0.3.2 (released 2009-08-05) + * Added ssh_init() function. + * Added sftp_readlink() function. + * Added sftp_symlink() function. + * Fixed ssh_write_knownhost(). + * Fixed compilation on Solaris. + * Fixed SSHv1 compilation. + +version 0.3.1 (released 2009-07-14) + * Added return code SSH_SERVER_FILE_NOT_FOUND. + * Fixed compilation of SSHv1. + * Fixed several memory leaks. + * Fixed possible infinite loops. + * Fixed a possible crash bug. + * Fixed build warnings. + * Fixed cmake on BSD. +version 0.3.1 (released 2009-07-14) + * Added return code SSH_SERVER_FILE_NOT_FOUND. + * Fixed compilation of SSHv1. + * Fixed several memory leaks. + * Fixed possible infinite loops. + * Fixed a possible crash bug. + * Fixed build warnings. + * Fixed cmake on BSD. + +version 0.3 (released 2009-05-21) + * Added support for ssh-agent authentication. + * Added POSIX like sftp implementation. + * Added error checking to all functions. + * Added const to arguments where it was needed. + * Added a channel_get_exit_status() function. + * Added a channel_read_buffer() function, channel_read() is now + a POSIX like function. + * Added a more generic auth callback function. + * Added printf attribute checking for log and error functions. + * Added runtime function tracer support. + * Added NSIS build support with CPack. + * Added openssh hashed host support. + * Added API documentation for all public functions. + * Added asynchronous SFTP read function. + * Added a ssh_bind_set_fd() function. + * Fixed known_hosts parsing. + * Fixed a lot of build warnings. + * Fixed the Windows build. + * Fixed a lot of memory leaks. + * Fixed a double free corruption in the server support. + * Fixed the "ssh_accept:" bug in server support. + * Fixed important channel bugs. + * Refactored the socket handling. + * Switched to CMake build system. + * Improved performance. + +version 0.2 (released 2007-11-29) + * General cleanup + * More comprehensive API + * Up-to-date Doxygen documentation of each public function + * Basic server-based support + * Libgcrypt support (alternative to openssl and its license) + * SSH1 support (disabled by default) + * Added 3des-cbc + * A lot of bugfixes + +version 0.11-dev + * Server implementation development. + * Small bug corrected when connecting to sun ssh servers. + * Channel wierdness corrected (writing huge data packets) + * Channel_read_nonblocking added + * Channel bug where stderr wasn't correctly read fixed. + * Added sftp_file_set_nonblocking(), which is nonblocking SFTP IO + * Connect_status callback. + * Priv.h contains the internal functions, libssh.h the public interface + * Options_set_timeout (thx marcelo) really working. + * Tcp tunneling through channel_open_forward. + * Channel_request_exec() + * Channel_request_env() + * Ssh_get_pubkey_hash() + * Ssh_is_server_known() + * Ssh_write_known_host() + * Options_set_ssh_dir + * How could this happen ! there weren't any channel_close ! + * Nasty channel_free bug resolved. + * Removed the unsigned long all around the code. use only u8,u32 & u64. + * It now compiles and runs under amd64 ! + * Channel_request_pty_size + * Channel_change_pty_size + * Options_copy() + * Ported the doc to an HTML file. + * Small bugfix in packet.c + * Prefixed error constants with SSH_ + * Sftp_stat, sftp_lstat, sftp_fstat. thanks Michel Bardiaux for the patch. + * Again channel number mismatch fixed. + * Fixed a bug in ssh_select making the select fail when a signal has been + caught. + * Keyboard-interactive authentication working. + +version 0.1 (released 2004-03-05) + * Begining of sftp subsystem implementation. + * Some cleanup into channels implementation + * Now every channel functions is called by its CHANNEL handler. + * Added channel_poll() and channel_read(). + * Changed the client so it uses the new channel_poll and channel_read interface + * Small use-after-free bug with channels resolved + * Changed stupidities in lot of function names. + * Removed a debug output file opened by default. + * Added API.txt, the libssh programmer handbook. + * Various bug fixes from Nick Zitzmann. + * Developed a cryptographic structure for handling protocols. + * An autoconf script which took me half of a day to set up. + * A ssh_select wrapper has been written. + +version 0.0.4 (released 2003-10-10) + * Some terminal code (eof handling) added + * Channels bugfix (it still needs some tweaking though) + * Zlib support + * Added a wrapper.c file. The goal is to provide a similar API to every + cryptographic functions. bignums and sha/md5 are wrapped now. + * More work than it first looks. + * Support for other crypto libs planed (lighter libs) + * Fixed stupid select() bug. + * Libssh now compiles and links with openssl 0.9.6 + * RSA pubkey authentication code now works ! + +version 0.0.3 (released 2003-09-15) + * Added install target in makefile + * Some cleanup in headers files and source code + * Change default banner and project name to libssh. + * New file auth.c to support more and more authentication ways + * Bugfix(read offbyone) in send_kex + * A base64 parser. don't read the source, it's awful. pure 0xbadc0de. + * Changed the client filename to "ssh". logic isn't it ? + * Dss publickey authentication ! still need to wait for the rsa one + * Bugfix in packet.c + * New misc.c contains misc functions + +version 0.0.2 (released 2003-09-03) + * Initial release. + * Client supports both ssh and dss hostkey verification, but doesn't compare them to openssh's files. (~/.ssh/known_hosts) + * The only supported authentication method is password. + * Compiles on linux and openbsd. freebsd and netbsd should work, too + * Lot of work which hasn't been discussed here. diff --git a/libssh/CodingStyle b/libssh/CodingStyle new file mode 100644 index 00000000..93cc382c --- /dev/null +++ b/libssh/CodingStyle @@ -0,0 +1,59 @@ +Coding Style Conventions +======================== + +Coding style guidelines are about reducing the number of unnecessary +reformatting patches and making things easier for developers to work together. + +You don't have to like them or even agree with them, but once put in place we +all have to abide by them (or vote to change them). However, coding style +should never outweigh coding itself and so the guidelines described here are +hopefully easy enough to follow as they are very common and supported by tools +and editors. + +The basic style for C code is the Linux kernel coding style [1] with one +excecption, we use 4 spaces instead of tabs. This closely matches what most +libssh developers use already anyways, with a few exceptions as mentioned +below. + +To shorthen this here are the highlights: + +* Maximum line width is 80 characters + + The reason is not about people with low-res screens but rather sticking + to 80 columns prevents you from easily nesting more than one level of + if statements or other code blocks. + +* Use 4 spaces to indent + +* No trailing whitespaces + +* Follow the K&R guidelines. We won't go through all of them here. Do you + have a copy of "The C Programming Language" anyways right? + +Editors +======== + +VIM +---- + +set ts=4 sw=4 et cindent + +For Vim, the following settings in $HOME/.vimrc will also deal with +displaying trailing whitespace: + + if has("syntax") && (&t_Co > 2 || has("gui_running")) + syntax on + function! ActivateInvisibleCharIndicator() + syntax match TrailingSpace "[ \t]\+$" display containedin=ALL + highlight TrailingSpace ctermbg=Red + endf + autocmd BufNewFile,BufRead * call ActivateInvisibleCharIndicator() + endif + " Show tabs, trailing whitespace, and continued lines visually + set list listchars=tab:»·,trail:·,extends:… + + " highlight overly long lines same as TODOs. + set textwidth=80 + autocmd BufNewFile,BufRead *.c,*.h exec 'match Todo /\%>' . &textwidth . 'v.\+/' + +[1] https://www.kernel.org/doc/Documentation/CodingStyle diff --git a/libssh/ConfigureChecks.cmake b/libssh/ConfigureChecks.cmake new file mode 100644 index 00000000..b0485d98 --- /dev/null +++ b/libssh/ConfigureChecks.cmake @@ -0,0 +1,179 @@ +include(CheckIncludeFile) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckTypeSize) +include(CheckCXXSourceCompiles) +include(TestBigEndian) + +set(PACKAGE ${APPLICATION_NAME}) +set(VERSION ${APPLICATION_VERSION}) +set(DATADIR ${DATA_INSTALL_DIR}) +set(LIBDIR ${LIB_INSTALL_DIR}) +set(PLUGINDIR "${PLUGIN_INSTALL_DIR}-${LIBRARY_SOVERSION}") +set(SYSCONFDIR ${SYSCONF_INSTALL_DIR}) + +set(BINARYDIR ${CMAKE_BINARY_DIR}) +set(SOURCEDIR ${CMAKE_SOURCE_DIR}) + +function(COMPILER_DUMPVERSION _OUTPUT_VERSION) + # Remove whitespaces from the argument. + # This is needed for CC="ccache gcc" cmake .. + string(REPLACE " " "" _C_COMPILER_ARG "${CMAKE_C_COMPILER_ARG1}") + + execute_process( + COMMAND + ${CMAKE_C_COMPILER} ${_C_COMPILER_ARG} -dumpversion + OUTPUT_VARIABLE _COMPILER_VERSION + ) + + string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2" + _COMPILER_VERSION "${_COMPILER_VERSION}") + + set(${_OUTPUT_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) +endfunction() + +if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + compiler_dumpversion(GNUCC_VERSION) + if (NOT GNUCC_VERSION EQUAL 34) + set(CMAKE_REQUIRED_FLAGS "-fvisibility=hidden") + check_c_source_compiles( +"void __attribute__((visibility(\"default\"))) test() {} +int main(void){ return 0; } +" WITH_VISIBILITY_HIDDEN) + set(CMAKE_REQUIRED_FLAGS "") + endif (NOT GNUCC_VERSION EQUAL 34) +endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + +# HEADER FILES +check_include_file(argp.h HAVE_ARGP_H) +check_include_file(pty.h HAVE_PTY_H) +check_include_file(termios.h HAVE_TERMIOS_H) + +if (WIN32) + check_include_files("winsock2.h;ws2tcpip.h;wspiapi.h" HAVE_WSPIAPI_H) + if (NOT HAVE_WSPIAPI_H) + message(STATUS "WARNING: Without wspiapi.h, this build will only work on Windows XP and newer versions") + endif (NOT HAVE_WSPIAPI_H) + check_include_files("winsock2.h;ws2tcpip.h" HAVE_WS2TCPIP_H) + if (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + set(HAVE_GETADDRINFO TRUE) + set(HAVE_GETHOSTBYNAME TRUE) + endif (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + + set(HAVE_SELECT TRUE) +endif (WIN32) + +set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS}) +check_include_file(openssl/aes.h HAVE_OPENSSL_AES_H) + +set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS}) +check_include_file(openssl/blowfish.h HAVE_OPENSSL_BLOWFISH_H) + +set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS}) +check_include_file(openssl/des.h HAVE_OPENSSL_DES_H) + +set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS}) +check_include_file(openssl/ecdh.h HAVE_OPENSSL_ECDH_H) + +set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS}) +check_include_file(openssl/ec.h HAVE_OPENSSL_EC_H) + +set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS}) +check_include_file(openssl/ecdsa.h HAVE_OPENSSL_ECDSA_H) + +if (CMAKE_HAVE_PTHREAD_H) + set(HAVE_PTHREAD_H 1) +endif (CMAKE_HAVE_PTHREAD_H) + +if (NOT WITH_GCRYPT) + if (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + set(HAVE_OPENSSL_ECC 1) + endif (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + + if (HAVE_OPENSSL_ECC) + set(HAVE_ECC 1) + endif (HAVE_OPENSSL_ECC) +endif (NOT WITH_GCRYPT) + +# FUNCTIONS + +check_function_exists(strncpy HAVE_STRNCPY) +check_function_exists(vsnprintf HAVE_VSNPRINTF) +check_function_exists(snprintf HAVE_SNPRINTF) + +if (WIN32) + check_function_exists(_vsnprintf_s HAVE__VSNPRINTF_S) + check_function_exists(_vsnprintf HAVE__VSNPRINTF) + check_function_exists(_snprintf HAVE__SNPRINTF) + check_function_exists(_snprintf_s HAVE__SNPRINTF_S) +endif (WIN32) + +if (UNIX) + if (NOT LINUX) + # libsocket (Solaris) + check_library_exists(socket getaddrinfo "" HAVE_LIBSOCKET) + if (HAVE_LIBSOCKET) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} socket) + endif (HAVE_LIBSOCKET) + + # libnsl/inet_pton (Solaris) + check_library_exists(nsl inet_pton "" HAVE_LIBNSL) + if (HAVE_LIBNSL) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} nsl) + endif (HAVE_LIBNSL) + + # librt + check_library_exists(rt nanosleep "" HAVE_LIBRT) + endif (NOT LINUX) + + check_library_exists(rt clock_gettime "" HAVE_CLOCK_GETTIME) + if (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} rt) + endif (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + + check_library_exists(util forkpty "" HAVE_LIBUTIL) + check_function_exists(getaddrinfo HAVE_GETADDRINFO) + check_function_exists(poll HAVE_POLL) + check_function_exists(select HAVE_SELECT) + check_function_exists(cfmakeraw HAVE_CFMAKERAW) + check_function_exists(ntohll HAVE_NTOHLL) + check_function_exists(htonll HAVE_HTONLL) + check_function_exists(strtoull HAVE_STRTOULL) + check_function_exists(__strtoull HAVE___STRTOULL) +endif (UNIX) + +set(LIBSSH_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} CACHE INTERNAL "libssh required system libraries") + +# LIBRARIES +if (OPENSSL_FOUND) + set(HAVE_LIBCRYPTO 1) +endif (OPENSSL_FOUND) + +if (GCRYPT_FOUND) + set(HAVE_LIBGCRYPT 1) + if (GCRYPT_VERSION VERSION_GREATER "1.4.6") + #set(HAVE_GCRYPT_ECC 1) + #set(HAVE_ECC 1) + endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") +endif (GCRYPT_FOUND) + +if (CMAKE_HAVE_THREADS_LIBRARY) + if (CMAKE_USE_PTHREADS_INIT) + set(HAVE_PTHREAD 1) + endif (CMAKE_USE_PTHREADS_INIT) +endif (CMAKE_HAVE_THREADS_LIBRARY) + +# OPTIONS +if (WITH_DEBUG_CRYPTO) + set(DEBUG_CRYPTO 1) +endif (WITH_DEBUG_CRYPTO) + +if (WITH_DEBUG_CALLTRACE) + set(DEBUG_CALLTRACE 1) +endif (WITH_DEBUG_CALLTRACE) + +# ENDIAN +if (NOT WIN32) + test_big_endian(WORDS_BIGENDIAN) +endif (NOT WIN32) diff --git a/libssh/DefineOptions.cmake b/libssh/DefineOptions.cmake new file mode 100644 index 00000000..ea8265c0 --- /dev/null +++ b/libssh/DefineOptions.cmake @@ -0,0 +1,27 @@ +option(WITH_ZLIB "Build with ZLIB support" ON) +option(WITH_SSH1 "Build with SSH1 support" OFF) +option(WITH_SFTP "Build with SFTP support" ON) +option(WITH_SERVER "Build with SSH server support" ON) +option(WITH_STATIC_LIB "Build with a static library" OFF) +option(WITH_DEBUG_CRYPTO "Build with cryto debug output" OFF) +option(WITH_DEBUG_CALLTRACE "Build with calltrace debug output" ON) +option(WITH_GCRYPT "Compile against libgcrypt" OFF) +option(WITH_PCAP "Compile with Pcap generation support" ON) +option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) +option(WITH_TESTING "Build with unit tests" OFF) +option(WITH_CLIENT_TESTING "Build with client tests; requires a running sshd" OFF) +option(WITH_BENCHMARKS "Build benchmarks tools" OFF) + +if (WITH_ZLIB) + set(WITH_LIBZ ON) +else (WITH_ZLIB) + set(WITH_LIBZ OFF) +endif (WITH_ZLIB) + +if(WITH_BENCHMARKS) + set(WITH_TESTING ON) +endif(WITH_BENCHMARKS) + +if (WITH_TESTING) + set(WITH_STATIC_LIB ON) +endif (WITH_TESTING) diff --git a/libssh/INSTALL b/libssh/INSTALL new file mode 100644 index 00000000..a772b824 --- /dev/null +++ b/libssh/INSTALL @@ -0,0 +1,99 @@ +# How to build from source + +## Requirements + +### Common requirements + +In order to build libssh, you need to install several components: + +- A C compiler +- [CMake](http://www.cmake.org) >= 2.6.0. +- [openssl](http://www.openssl.org) >= 0.9.8 +or +- [gcrypt](http://www.gnu.org/directory/Security/libgcrypt.html) >= 1.4 + +optional: +- [libz](http://www.zlib.net) >= 1.2 + +Note that these version numbers are version we know works correctly. If you +build and run libssh successfully with an older version, please let us know. + +Windows binaries known to be working: + +- http://www.slproweb.com/products/Win32OpenSSL.html +- http://www.winimage.com/zLibDll/index.html + +We installed them in C:\Program Files + +## Building +First, you need to configure the compilation, using CMake. Go inside the +`build` dir. Create it if it doesn't exist. + +GNU/Linux, MacOS X, MSYS/MinGW: + + cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. + make + +On Windows you should choose a makefile gernerator with -G. + +### CMake standard options +Here is a list of the most interesting options provided out of the box by +CMake. + +- CMAKE_BUILD_TYPE: The type of build (can be Debug Release MinSizeRel + RelWithDebInfo) +- CMAKE_INSTALL_PREFIX: The prefix to use when running make install (Default + to /usr/local on GNU/Linux and MacOS X) +- CMAKE_C_COMPILER: The path to the C compiler +- CMAKE_CXX_COMPILER: The path to the C++ compiler + +### CMake options defined for libssh + +Options are defined in the following files: + +- DefineOptions.cmake + +They can be changed with the -D option: + +`cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_ZLIB=OFF ..` + +### Browsing/editing CMake options + +In addition to passing options on the command line, you can browse and edit +CMake options using `cmakesetup` (Windows), `cmake-gui` or `ccmake` (GNU/Linux +and MacOS X). + +- Go to the build dir +- On Windows: run `cmakesetup` +- On GNU/Linux and MacOS X: run `ccmake ..` + +### Useful Windows options: + +If you have installed OpenSSL or ZLIB in non standard directories, maybe you +want to set: + +OPENSSL_ROOT_DIR + +and + +ZLIB_ROOT_DIR + +## Installing + +If you want to install libssh after compilation run: + + make install + +## Running + +The libssh binary can be found in the `build/libssh` directory. +You can use `build/examples/samplessh` which is a sample client to +test libssh on UNIX. + +## About this document + +This document is written using [Markdown][] syntax, making it possible to +provide usable information in both plain text and HTML format. Whenever +modifying this document please use [Markdown][] syntax. + +[markdown]: http://www.daringfireball.net/projects/markdown diff --git a/libssh/README b/libssh/README new file mode 100644 index 00000000..e3170ce3 --- /dev/null +++ b/libssh/README @@ -0,0 +1,163 @@ + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1* Why ? +-_-_-_-_-_ + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +2* How/Who ? +-_-_-_-_-_-_-_ + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +3* Where ? +-_-_-_-_-_-_ + +http://www.libssh.org + +4* API Changes ! +-_-_-_-_-_-_-_-_-_ + +Changes between 0.4 and 0.5 +--------------------------- + +We use the ssh_ prefix as namespace for every function now. There is a legacy.h +which could be used to get the old function names. + +Changes between 0.3 and 0.4 +--------------------------- + +We changed libssh to be typesafe now: + +SSH_SESSION *session -> ssh_session session +SFTP_SESSION *sftp -> sftp_session sftp +CHANNEL *channel -> ssh_channel channel +STRING *string -> ssh_string string +... + +The options structure has been removed and there is a new function. This +function can set all available options now. You can find the enum in the +header file and it is documented. Example: + +ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + +5* Copyright policy +-_-_-_-_-_-_-_-_-_-_ + +libssh is a project with distributed copyright ownership, which means we prefer +the copyright on parts of libssh to be held by individuals rather than +corporations if possible. There are historical legal reasons for this, but one +of the best ways to explain it is that it’s much easier to work with +individuals who have ownership than corporate legal departments if we ever need +to make reasonable compromises with people using and working with libssh. + +We track the ownership of every part of libssh via git, our source code control +system, so we know the provenance of every piece of code that is committed to +libssh. + +So if possible, if you’re doing libssh changes on behalf of a company who +normally owns all the work you do please get them to assign personal copyright +ownership of your changes to you as an individual, that makes things very easy +for us to work with and avoids bringing corporate legal departments into the +picture. + +If you can’t do this we can still accept patches from you owned by your +employer under a standard employment contract with corporate copyright +ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux Kernel +community, so it should be very easy to get a sign off from your corporate +legal department. The only changes we’ve made are to accommodate the license we +use, which is LGPLv2 (or later) whereas the Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + +-------------------------------------------------------------------------- +libssh Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project 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 the option of the project) any later version. + +http://www.gnu.org/licenses/lgpl-2.1.html +-------------------------------------------------------------------------- + +We will maintain a copy of that email as a record that you have the rights to +contribute code to libssh under the required licenses whilst working for the +company where the email came from. + +Then when sending in a patch via the normal mechanisms described above, add a +line that states: + + + Signed-off-by: Random J Developer + + +using your real name and the email address you sent the original email you used +to send the libssh Developer’s Certificate of Origin to us (sorry, no +pseudonyms or anonymous contributions.) + +That’s it! Such code can then quite happily contain changes that have copyright +messages such as: + + + (c) Example Corporation. + + +and can be merged into the libssh codebase in the same way as patches from any +other individual. You don’t need to send in a copy of the libssh Developer’s +Certificate of Origin for each patch, or inside each patch. Just the sign-off +message is all that is required once we’ve received the initial email. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/libssh/SubmittingPatches b/libssh/SubmittingPatches new file mode 100644 index 00000000..66b54e76 --- /dev/null +++ b/libssh/SubmittingPatches @@ -0,0 +1,118 @@ +How to contribute a patch to libssh +==================================== + +Simple, just make the code change, and email it as either a "diff -u" +change, or as a "git format-patch" change against the original source +code to libssh@libssh.org, or attach it to a bug report at +https://red.libssh.org/ + +For larger code changes, breaking the changes up into a set of simple +patches, each of which does a single thing, are much easier to review. +Patch sets like that will most likely have an easier time being merged +into the libssh code than large single patches that make lots of +changes in one large diff. + +Ownership of the contributed code +================================== + +libssh is a project with distributed copyright ownership, which means +we prefer the copyright on parts of libssh to be held by individuals +rather than corporations if possible. There are historical legal +reasons for this, but one of the best ways to explain it is that it's +much easier to work with individuals who have ownership than corporate +legal departments if we ever need to make reasonable compromises with +people using and working with libssh. + +We track the ownership of every part of libssh via http://git.libssh.org, +our source code control system, so we know the provenance of every piece +of code that is committed to libssh. + +So if possible, if you're doing libssh changes on behalf of a company +who normally owns all the work you do please get them to assign +personal copyright ownership of your changes to you as an individual, +that makes things very easy for us to work with and avoids bringing +corporate legal departments into the picture. + +If you can't do this we can still accept patches from you owned by +your employer under a standard employment contract with corporate +copyright ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux +Kernel community, so it should be very easy to get a sign off from +your corporate legal department. The only changes we've made are to +accommodate the license we use, which is LGPLv2 (or later) whereas the +Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + + + +libssh Developer's Certificate of Origin. Version 1.0 + + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project 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 the option of the project) any later version. + + http://www.gnu.org/licenses/lgpl-2.1.html + + +We will maintain a copy of that email as a record that you have the +rights to contribute code to libssh under the required licenses whilst +working for the company where the email came from. + +Then when sending in a patch via the normal mechanisms described +above, add a line that states: + + Signed-off-by: Random J Developer + +using your real name and the email address you sent the original email +you used to send the libssh Developer's Certificate of Origin to us +(sorry, no pseudonyms or anonymous contributions.) + +That's it! Such code can then quite happily contain changes that have +copyright messages such as: + + (c) Example Corporation. + +and can be merged into the libssh codebase in the same way as patches +from any other individual. You don't need to send in a copy of the +libssh Developer's Certificate of Origin for each patch, or inside each +patch. Just the sign-off message is all that is required once we've +received the initial email. + +Have fun and happy libssh hacking ! + +The libssh Team + diff --git a/libssh/build/build_make.sh b/libssh/build/build_make.sh new file mode 100755 index 00000000..ba03c99c --- /dev/null +++ b/libssh/build/build_make.sh @@ -0,0 +1,197 @@ +#!/bin/bash +# +# Last Change: 2008-06-18 14:13:46 +# +# Script to build libssh on UNIX. +# +# Copyright (c) 2006-2007 Andreas Schneider +# + +SOURCE_DIR=".." + +LANG=C +export LANG + +SCRIPT="$0" +COUNT=0 +while [ -L "${SCRIPT}" ] +do + SCRIPT=$(readlink ${SCRIPT}) + COUNT=$(expr ${COUNT} + 1) + if [ ${COUNT} -gt 100 ]; then + echo "Too many symbolic links" + exit 1 + fi +done +BUILDDIR=$(dirname ${SCRIPT}) + +cleanup_and_exit () { + if test "$1" = 0 -o -z "$1" ; then + exit 0 + else + exit $1 + fi +} + +function configure() { + if [ -n "${CMAKEDIR}" ]; then + ${CMAKEDIR}/bin/cmake "$@" ${SOURCE_DIR} || cleanup_and_exit $? + else + cmake "$@" ${SOURCE_DIR} || cleanup_and_exit $? + fi +} + +function compile() { + if [ -f /proc/cpuinfo ]; then + CPUCOUNT=$(grep -c processor /proc/cpuinfo) + elif test `uname` = "SunOS" ; then + CPUCOUNT=$(psrinfo -p) + else + CPUCOUNT="1" + fi + + if [ "${CPUCOUNT}" -gt "1" ]; then + ${MAKE} -j${CPUCOUNT} $1 || cleanup_and_exit $? + else + ${MAKE} $1 || exit $? + fi +} + +function clean_build_dir() { + find ! -path "*.svn*" ! -name "*.bat" ! -name "*.sh" ! -name "." -print0 | xargs -0 rm -rf +} + +function usage () { +echo "Usage: `basename $0` [--prefix /install_prefix|--build [debug|final]|--clean|--verbose|--libsuffix (32|64)|--help|--clang|--cmakedir /directory|--make +(gmake|make)|--ccompiler (gcc|cc)|--withstaticlib|--unittesting|--clientunittesting|--withssh1|--withserver]" + cleanup_and_exit +} + +cd ${BUILDDIR} + +# the default CMake options: +OPTIONS="--graphviz=${BUILDDIR}/libssh.dot" + +# the default 'make' utility: +MAKE="make" + +while test -n "$1"; do + PARAM="$1" + ARG="$2" + shift + case ${PARAM} in + *-*=*) + ARG=${PARAM#*=} + PARAM=${PARAM%%=*} + set -- "----noarg=${PARAM}" "$@" + esac + case ${PARAM} in + *-help|-h) + #echo_help + usage + cleanup_and_exit + ;; + *-build) + DOMAKE="1" + BUILD_TYPE="${ARG}" + test -n "${BUILD_TYPE}" && shift + ;; + *-clean) + clean_build_dir + cleanup_and_exit + ;; + *-clang) + OPTIONS="${OPTIONS} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++" + ;; + *-verbose) + DOVERBOSE="1" + ;; + *-memtest) + OPTIONS="${OPTIONS} -DMEM_NULL_TESTS=ON" + ;; + *-libsuffix) + OPTIONS="${OPTIONS} -DLIB_SUFFIX=${ARG}" + shift + ;; + *-prefix) + OPTIONS="${OPTIONS} -DCMAKE_INSTALL_PREFIX=${ARG}" + shift + ;; + *-sysconfdir) + OPTIONS="${OPTIONS} -DSYSCONF_INSTALL_DIR=${ARG}" + shift + ;; + *-cmakedir) + CMAKEDIR="${ARG}" + shift + ;; + *-make) + MAKE="${ARG}" + shift + ;; + *-ccompiler) + OPTIONS="${OPTIONS} -DCMAKE_C_COMPILER=${ARG}" + shift + ;; + *-withstaticlib) + OPTIONS="${OPTIONS} -DWITH_STATIC_LIB=ON" + ;; + *-unittesting) + OPTIONS="${OPTIONS} -DWITH_TESTING=ON" + ;; + *-clientunittesting) + OPTIONS="${OPTIONS} -DWITH_CLIENT_TESTING=ON" + ;; + *-withssh1) + OPTIONS="${OPTIONS} -DWITH_SSH1=ON" + ;; + *-withserver) + OPTIONS="${OPTIONS} -DWITH_SERVER=ON" + ;; + ----noarg) + echo "$ARG does not take an argument" + cleanup_and_exit + ;; + -*) + echo Unknown Option "$PARAM". Exit. + cleanup_and_exit 1 + ;; + *) + usage + ;; + esac +done + +if [ "${DOMAKE}" == "1" ]; then + OPTIONS="${OPTIONS} -DCMAKE_BUILD_TYPE=${BUILD_TYPE}" +fi + +if [ -n "${DOVERBOSE}" ]; then + OPTIONS="${OPTIONS} -DCMAKE_VERBOSE_MAKEFILE=1" +else + OPTIONS="${OPTIONS} -DCMAKE_VERBOSE_MAKEFILE=0" +fi + +test -f "${BUILDDIR}/.build.log" && rm -f ${BUILDDIR}/.build.log +touch ${BUILDDIR}/.build.log +# log everything from here to .build.log +exec 1> >(exec -a 'build logging tee' tee -a ${BUILDDIR}/.build.log) 2>&1 +echo "${HOST} started build at $(date)." +echo + +configure ${OPTIONS} "$@" + +if [ -n "${DOMAKE}" ]; then + test -n "${DOVERBOSE}" && compile VERBOSE=1 || compile +fi + +DOT=$(which dot 2>/dev/null) +if [ -n "${DOT}" ]; then + ${DOT} -Tpng -o${BUILDDIR}/libssh.png ${BUILDDIR}/libssh.dot + ${DOT} -Tsvg -o${BUILDDIR}/libssh.svg ${BUILDDIR}/libssh.dot +fi + +exec >&0 2>&0 # so that the logging tee finishes +sleep 1 # wait till tee terminates + +cleanup_and_exit 0 diff --git a/libssh/cmake/Modules/AddCMockaTest.cmake b/libssh/cmake/Modules/AddCMockaTest.cmake new file mode 100644 index 00000000..b2d1ca8a --- /dev/null +++ b/libssh/cmake/Modules/AddCMockaTest.cmake @@ -0,0 +1,23 @@ +# - ADD_CHECK_TEST(test_name test_source linklib1 ... linklibN) + +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007-2010 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +enable_testing() +include(CTest) + +if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW) + set(CMAKE_C_FLAGS_PROFILING "-g -O0 -Wall -W -Wshadow -Wunused-variable -Wunused-parameter -Wunused-function -Wunused -Wno-system-headers -Wwrite-strings -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Compiler Flags") + set(CMAKE_SHARED_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") + set(CMAKE_MODULE_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") + set(CMAKE_EXEC_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") +endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW) + +function (ADD_CMOCKA_TEST _testName _testSource) + add_executable(${_testName} ${_testSource}) + target_link_libraries(${_testName} ${ARGN}) + add_test(${_testName} ${CMAKE_CURRENT_BINARY_DIR}/${_testName}) +endfunction (ADD_CMOCKA_TEST) diff --git a/libssh/cmake/Modules/COPYING-CMAKE-SCRIPTS b/libssh/cmake/Modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 00000000..4b417765 --- /dev/null +++ b/libssh/cmake/Modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libssh/cmake/Modules/CheckCCompilerFlagSSP.cmake b/libssh/cmake/Modules/CheckCCompilerFlagSSP.cmake new file mode 100644 index 00000000..2fe43954 --- /dev/null +++ b/libssh/cmake/Modules/CheckCCompilerFlagSSP.cmake @@ -0,0 +1,26 @@ +# - Check whether the C compiler supports a given flag in the +# context of a stack checking compiler option. + +# CHECK_C_COMPILER_FLAG_SSP(FLAG VARIABLE) +# +# FLAG - the compiler flag +# VARIABLE - variable to store the result +# +# This actually calls check_c_source_compiles. +# See help for CheckCSourceCompiles for a listing of variables +# that can modify the build. + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +include(CheckCSourceCompiles) + +function(CHECK_C_COMPILER_FLAG_SSP _FLAG _RESULT) + set(SAFE_CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS}") + set(CMAKE_REQUIRED_DEFINITIONS "${_FLAG}") + check_c_source_compiles("int main(int argc, char **argv) { char buffer[256]; return buffer[argc]=0;}" ${_RESULT}) + set(CMAKE_REQUIRED_DEFINITIONS "${SAFE_CMAKE_REQUIRED_DEFINITIONS}") +endfunction(CHECK_C_COMPILER_FLAG_SSP) diff --git a/libssh/cmake/Modules/DefineCMakeDefaults.cmake b/libssh/cmake/Modules/DefineCMakeDefaults.cmake new file mode 100644 index 00000000..72893c3c --- /dev/null +++ b/libssh/cmake/Modules/DefineCMakeDefaults.cmake @@ -0,0 +1,27 @@ +# Always include srcdir and builddir in include path +# This saves typing ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY} in +# about every subdir +# since cmake 2.4.0 +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the include dirs which are in the source or build tree +# before all other include dirs, so the headers in the sources +# are prefered over the already installed ones +# since cmake 2.4.1 +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +# Use colored output +# since cmake 2.4.0 +set(CMAKE_COLOR_MAKEFILE ON) + +# Define the generic version of the libraries here +set(GENERIC_LIB_VERSION "0.1.0") +set(GENERIC_LIB_SOVERSION "0") + +# Set the default build type to release with debug info +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo + CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + ) +endif (NOT CMAKE_BUILD_TYPE) diff --git a/libssh/cmake/Modules/DefineCompilerFlags.cmake b/libssh/cmake/Modules/DefineCompilerFlags.cmake new file mode 100644 index 00000000..582ea1ca --- /dev/null +++ b/libssh/cmake/Modules/DefineCompilerFlags.cmake @@ -0,0 +1,77 @@ +# define system dependent compiler flags + +include(CheckCCompilerFlag) +include(CheckCCompilerFlagSSP) + +if (UNIX AND NOT WIN32) + # + # Define GNUCC compiler flags + # + if (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)") + + # add -Wconversion ? + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -pedantic-errors") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wmissing-prototypes -Wdeclaration-after-statement") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunused -Wfloat-equal -Wpointer-arith -Wwrite-strings -Wformat-security") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-format-attribute") + + # with -fPIC + check_c_compiler_flag("-fPIC" WITH_FPIC) + if (WITH_FPIC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + endif (WITH_FPIC) + + check_c_compiler_flag_ssp("-fstack-protector" WITH_STACK_PROTECTOR) + if (WITH_STACK_PROTECTOR) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector") + endif (WITH_STACK_PROTECTOR) + + if (CMAKE_BUILD_TYPE) + string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + if (NOT CMAKE_BUILD_TYPE_LOWER MATCHES debug) + check_c_compiler_flag("-D_FORTIFY_SOURCE=2" WITH_FORTIFY_SOURCE) + if (WITH_FORTIFY_SOURCE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2") + endif (WITH_FORTIFY_SOURCE) + endif() + endif() + endif (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)") + + # + # Check for large filesystem support + # + if (CMAKE_SIZEOF_VOID_P MATCHES "8") + # with large file support + execute_process( + COMMAND + getconf LFS64_CFLAGS + OUTPUT_VARIABLE + _lfs_CFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else (CMAKE_SIZEOF_VOID_P MATCHES "8") + # with large file support + execute_process( + COMMAND + getconf LFS_CFLAGS + OUTPUT_VARIABLE + _lfs_CFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif (CMAKE_SIZEOF_VOID_P MATCHES "8") + if (_lfs_CFLAGS) + string(REGEX REPLACE "[\r\n]" " " "${_lfs_CFLAGS}" "${${_lfs_CFLAGS}}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_lfs_CFLAGS}") + endif (_lfs_CFLAGS) + +endif (UNIX AND NOT WIN32) + +if (MSVC) + # Use secure functions by defaualt and suppress warnings about + #"deprecated" functions + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1") +endif (MSVC) diff --git a/libssh/cmake/Modules/DefineInstallationPaths.cmake b/libssh/cmake/Modules/DefineInstallationPaths.cmake new file mode 100644 index 00000000..88e08cad --- /dev/null +++ b/libssh/cmake/Modules/DefineInstallationPaths.cmake @@ -0,0 +1,109 @@ +if (UNIX OR OS2) + IF (NOT APPLICATION_NAME) + MESSAGE(STATUS "${PROJECT_NAME} is used as APPLICATION_NAME") + SET(APPLICATION_NAME ${PROJECT_NAME}) + ENDIF (NOT APPLICATION_NAME) + + # Suffix for Linux + SET(LIB_SUFFIX + CACHE STRING "Define suffix of directory name (32/64)" + ) + + SET(EXEC_INSTALL_PREFIX + "${CMAKE_INSTALL_PREFIX}" + CACHE PATH "Base directory for executables and libraries" + ) + SET(SHARE_INSTALL_PREFIX + "${CMAKE_INSTALL_PREFIX}/share" + CACHE PATH "Base directory for files which go to share/" + ) + SET(DATA_INSTALL_PREFIX + "${SHARE_INSTALL_PREFIX}/${APPLICATION_NAME}" + CACHE PATH "The parent directory where applications can install their data") + + # The following are directories where stuff will be installed to + SET(BIN_INSTALL_DIR + "${EXEC_INSTALL_PREFIX}/bin" + CACHE PATH "The ${APPLICATION_NAME} binary install dir (default prefix/bin)" + ) + SET(SBIN_INSTALL_DIR + "${EXEC_INSTALL_PREFIX}/sbin" + CACHE PATH "The ${APPLICATION_NAME} sbin install dir (default prefix/sbin)" + ) + SET(LIB_INSTALL_DIR + "${EXEC_INSTALL_PREFIX}/lib${LIB_SUFFIX}" + CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is prefix/lib)" + ) + SET(LIBEXEC_INSTALL_DIR + "${EXEC_INSTALL_PREFIX}/libexec" + CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is prefix/libexec)" + ) + SET(PLUGIN_INSTALL_DIR + "${LIB_INSTALL_DIR}/${APPLICATION_NAME}" + CACHE PATH "The subdirectory relative to the install prefix where plugins will be installed (default is prefix/lib/${APPLICATION_NAME})" + ) + SET(INCLUDE_INSTALL_DIR + "${CMAKE_INSTALL_PREFIX}/include" + CACHE PATH "The subdirectory to the header prefix (default prefix/include)" + ) + + set(CMAKE_INSTALL_DIR + "${LIB_INSTALL_DIR}/cmake" + CACHE PATH "The subdirectory to install cmake config files") + + SET(DATA_INSTALL_DIR + "${DATA_INSTALL_PREFIX}" + CACHE PATH "The parent directory where applications can install their data (default prefix/share/${APPLICATION_NAME})" + ) + SET(HTML_INSTALL_DIR + "${DATA_INSTALL_PREFIX}/doc/HTML" + CACHE PATH "The HTML install dir for documentation (default data/doc/html)" + ) + SET(ICON_INSTALL_DIR + "${DATA_INSTALL_PREFIX}/icons" + CACHE PATH "The icon install dir (default data/icons/)" + ) + SET(SOUND_INSTALL_DIR + "${DATA_INSTALL_PREFIX}/sounds" + CACHE PATH "The install dir for sound files (default data/sounds)" + ) + + SET(LOCALE_INSTALL_DIR + "${SHARE_INSTALL_PREFIX}/locale" + CACHE PATH "The install dir for translations (default prefix/share/locale)" + ) + + SET(XDG_APPS_DIR + "${SHARE_INSTALL_PREFIX}/applications/" + CACHE PATH "The XDG apps dir" + ) + SET(XDG_DIRECTORY_DIR + "${SHARE_INSTALL_PREFIX}/desktop-directories" + CACHE PATH "The XDG directory" + ) + + SET(SYSCONF_INSTALL_DIR + "${EXEC_INSTALL_PREFIX}/etc" + CACHE PATH "The ${APPLICATION_NAME} sysconfig install dir (default prefix/etc)" + ) + SET(MAN_INSTALL_DIR + "${SHARE_INSTALL_PREFIX}/man" + CACHE PATH "The ${APPLICATION_NAME} man install dir (default prefix/man)" + ) + SET(INFO_INSTALL_DIR + "${SHARE_INSTALL_PREFIX}/info" + CACHE PATH "The ${APPLICATION_NAME} info install dir (default prefix/info)" + ) +else() + # Same same + set(BIN_INSTALL_DIR "bin" CACHE PATH "-") + set(SBIN_INSTALL_DIR "sbin" CACHE PATH "-") + set(LIB_INSTALL_DIR "lib${LIB_SUFFIX}" CACHE PATH "-") + set(INCLUDE_INSTALL_DIR "include" CACHE PATH "-") + set(CMAKE_INSTALL_DIR "CMake" CACHE PATH "-") + set(PLUGIN_INSTALL_DIR "plugins" CACHE PATH "-") + set(HTML_INSTALL_DIR "doc/HTML" CACHE PATH "-") + set(ICON_INSTALL_DIR "icons" CACHE PATH "-") + set(SOUND_INSTALL_DIR "soudns" CACHE PATH "-") + set(LOCALE_INSTALL_DIR "lang" CACHE PATH "-") +endif () diff --git a/libssh/cmake/Modules/DefinePlatformDefaults.cmake b/libssh/cmake/Modules/DefinePlatformDefaults.cmake new file mode 100644 index 00000000..502d936b --- /dev/null +++ b/libssh/cmake/Modules/DefinePlatformDefaults.cmake @@ -0,0 +1,28 @@ +# Set system vars + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(LINUX TRUE) +endif(CMAKE_SYSTEM_NAME MATCHES "Linux") + +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(FREEBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(OPENBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + set(NETBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + set(SOLARIS TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + +if (CMAKE_SYSTEM_NAME MATCHES "OS2") + set(OS2 TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OS2") diff --git a/libssh/cmake/Modules/FindArgp.cmake b/libssh/cmake/Modules/FindArgp.cmake new file mode 100644 index 00000000..aa228557 --- /dev/null +++ b/libssh/cmake/Modules/FindArgp.cmake @@ -0,0 +1,60 @@ +# - Try to find Argp +# Once done this will define +# +# ARGP_FOUND - system has Argp +# ARGP_INCLUDE_DIRS - the Argp include directory +# ARGP_LIBRARIES - Link these to use Argp +# ARGP_DEFINITIONS - Compiler switches required for using Argp +# +# Copyright (c) 2010 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (ARGP_LIBRARIES AND ARGP_INCLUDE_DIRS) + # in cache already + set(ARGP_FOUND TRUE) +else (ARGP_LIBRARIES AND ARGP_INCLUDE_DIRS) + + find_path(ARGP_INCLUDE_DIR + NAMES + argp.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(ARGP_LIBRARY + NAMES + argp + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(ARGP_INCLUDE_DIRS + ${ARGP_INCLUDE_DIR} + ) + + if (ARGP_LIBRARY) + set(ARGP_LIBRARIES + ${ARGP_LIBRARIES} + ${ARGP_LIBRARY} + ) + endif (ARGP_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Argp DEFAULT_MSG ARGP_LIBRARIES ARGP_INCLUDE_DIRS) + + # show the ARGP_INCLUDE_DIRS and ARGP_LIBRARIES variables only in the advanced view + mark_as_advanced(ARGP_INCLUDE_DIRS ARGP_LIBRARIES) + +endif (ARGP_LIBRARIES AND ARGP_INCLUDE_DIRS) + diff --git a/libssh/cmake/Modules/FindCMocka.cmake b/libssh/cmake/Modules/FindCMocka.cmake new file mode 100644 index 00000000..2dd9fc5f --- /dev/null +++ b/libssh/cmake/Modules/FindCMocka.cmake @@ -0,0 +1,49 @@ +# - Try to find CMocka +# Once done this will define +# +# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka +# +# Read-Only variables: +# CMOCKA_FOUND - system has CMocka +# CMOCKA_INCLUDE_DIR - the CMocka include directory +# CMOCKA_LIBRARIES - Link these to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka +# +#============================================================================= +# Copyright (c) 2011-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +find_path(CMOCKA_INCLUDE_DIR + NAMES + cmocka.h + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +find_library(CMOCKA_LIBRARY + NAMES + cmocka + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +if (CMOCKA_LIBRARY) + set(CMOCKA_LIBRARIES + ${CMOCKA_LIBRARIES} + ${CMOCKA_LIBRARY} + ) +endif (CMOCKA_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) + +# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/libssh/cmake/Modules/FindGCrypt.cmake b/libssh/cmake/Modules/FindGCrypt.cmake new file mode 100644 index 00000000..5f1fe40b --- /dev/null +++ b/libssh/cmake/Modules/FindGCrypt.cmake @@ -0,0 +1,75 @@ +# - Try to find GCrypt +# Once done this will define +# +# GCRYPT_FOUND - system has GCrypt +# GCRYPT_INCLUDE_DIRS - the GCrypt include directory +# GCRYPT_LIBRARIES - Link these to use GCrypt +# GCRYPT_DEFINITIONS - Compiler switches required for using GCrypt +# +#============================================================================= +# Copyright (c) 2009-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_GCRYPT_ROOT_HINTS + $ENV{GCRYTPT_ROOT_DIR} + ${GCRYPT_ROOT_DIR}) + +set(_GCRYPT_ROOT_PATHS + "$ENV{PROGRAMFILES}/libgcrypt") + +set(_GCRYPT_ROOT_HINTS_AND_PATHS + HINTS ${_GCRYPT_ROOT_HINTS} + PATHS ${_GCRYPT_ROOT_PATHS}) + + +find_path(GCRYPT_INCLUDE_DIR + NAMES + gcrypt.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} +) + +find_library(GCRYPT_LIBRARY + NAMES + gcrypt + gcrypt11 + libgcrypt-11 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} +) +set(GCRYPT_LIBRARIES ${GCRYPT_LIBRARY}) + +if (GCRYPT_INCLUDE_DIR) + file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" _gcrypt_version_str REGEX "^#define GCRYPT_VERSION \"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*GCRYPT_VERSION.*([0-9]+.[0-9]+.[0-9]+).*" "\\1" GCRYPT_VERSION "${_gcrypt_version_str}") +endif (GCRYPT_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +if (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + REQUIRED_VARS + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES + VERSION_VAR + GCRYPT_VERSION + FAIL_MESSAGE + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + ) +else (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES) +endif (GCRYPT_VERSION) + +# show the GCRYPT_INCLUDE_DIRS and GCRYPT_LIBRARIES variables only in the advanced view +mark_as_advanced(GCRYPT_INCLUDE_DIR GCRYPT_LIBRARIES) diff --git a/libssh/cmake/Modules/FindNSIS.cmake b/libssh/cmake/Modules/FindNSIS.cmake new file mode 100644 index 00000000..98a17c78 --- /dev/null +++ b/libssh/cmake/Modules/FindNSIS.cmake @@ -0,0 +1,39 @@ +# - Try to find NSIS +# Once done this will define +# +# NSIS_ROOT_DIR - Set this variable to the root installation of ZLIB +# +# Read-Only variables: +# NSIS_FOUND - system has NSIS +# NSIS_MAKE - NSIS creator executable +# +#============================================================================= +# Copyright (c) 2010-2011 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_NSIS_ROOT_PATHS + C:/NSIS/Bin + "$ENV{PROGRAMFILES}/NSIS" +) + +find_program(NSIS_MAKE + NAMES + makensis + PATHS + ${NSIS_ROOT_PATH} + ${NSIS_ROOT_PATH}/Bin + ${_NSIS_ROOT_PATHS} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(NSIS DEFAULT_MSG NSIS_MAKE) + +mark_as_advanced(NSIS_MAKE) diff --git a/libssh/cmake/Modules/FindOpenSSL.cmake b/libssh/cmake/Modules/FindOpenSSL.cmake new file mode 100644 index 00000000..565190c6 --- /dev/null +++ b/libssh/cmake/Modules/FindOpenSSL.cmake @@ -0,0 +1,208 @@ +# - Try to find OpenSSL +# Once done this will define +# +# OPENSSL_ROOT_DIR - Set this variable to the root installation of OpenSSL +# +# Read-Only variables: +# OPENSSL_FOUND - system has OpenSSL +# OPENSSL_INCLUDE_DIRS - the OpenSSL include directory +# OPENSSL_LIBRARIES - Link these to use OpenSSL +# OPENSSL_DEFINITIONS - Compiler switches required for using OpenSSL +# +#============================================================================= +# Copyright (c) 2006-2009 Kitware, Inc. +# Copyright (c) 2006 Alexander Neundorf +# Copyright (c) 2009-2010 Mathieu Malaterre +# Copyright (c) 2011 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +if (OPENSSL_LIBRARIES AND OPENSSL_INCLUDE_DIRS) + # in cache already + set(OPENSSL_FOUND TRUE) +else (OPENSSL_LIBRARIES AND OPENSSL_INCLUDE_DIRS) + + if (UNIX) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_OPENSSL openssl) + endif (PKG_CONFIG_FOUND) + endif (UNIX) + + # http://www.slproweb.com/products/Win32OpenSSL.html + set(_OPENSSL_ROOT_HINTS + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" + ) + + set(_OPENSSL_ROOT_PATHS + "C:/OpenSSL/" + "C:/OpenSSL-Win32/" + "C:/OpenSSL-Win64/" + "$ENV{PROGRAMFILES}/OpenSSL" + "$ENV{PROGRAMFILES}/OpenSSL-Win32" + "$ENV{PROGRAMFILES}/OpenSSL-Win64" + ) + + find_path(OPENSSL_ROOT_DIR + NAMES + include/openssl/ssl.h + HINTS + ${_OPENSSL_ROOT_HINTS} + PATHS + ${_OPENSSL_ROOT_PATHS} + ) + mark_as_advanced(OPENSSL_ROOT_DIR) + + find_path(OPENSSL_INCLUDE_DIR + NAMES + openssl/ssl.h + PATHS + /usr/local/include + /opt/local/include + /sw/include + /usr/lib/sfw/include + ${OPENSSL_ROOT_DIR}/include + ) + + set(OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + mark_as_advanced(OPENSSL_INCLUDE_DIRS) + + if (WIN32 AND NOT CYGWIN) + # MINGW should go here too + if (MSVC) + # /MD and /MDd are the standard values - if someone wants to use + # others, the libnames have to change here too + # use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b + # TODO: handle /MT and static lib + # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: + # * MD for dynamic-release + # * MDd for dynamic-debug + # * MT for static-release + # * MTd for static-debug + + # Implementation details: + # We are using the libraries located in the VC subdir instead of the parent directory eventhough : + # libeay32MD.lib is identical to ../libeay32.lib, and + # ssleay32MD.lib is identical to ../ssleay32.lib + find_library(LIB_EAY_DEBUG + NAMES + libeay32MDd + libeay32 + PATHS + ${OPENSSL_ROOT_DIR}/lib/VC + ) + + find_library(LIB_EAY_RELEASE + NAMES + libeay32MD + libeay32 + PATHS + ${OPENSSL_ROOT_DIR}/lib/VC + ) + + find_library(SSL_EAY_DEBUG + NAMES + ssleay32MDd + ssleay32 + ssl + PATHS ${OPENSSL_ROOT_DIR}/lib/VC + ) + + find_library(SSL_EAY_RELEASE + NAMES + ssleay32MD + ssleay32 + ssl + PATHS + ${OPENSSL_ROOT_DIR}/lib/VC + ) + + if (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + set(OPENSSL_LIBRARIES + optimized ${SSL_EAY_RELEASE} debug ${SSL_EAY_DEBUG} + optimized ${LIB_EAY_RELEASE} debug ${LIB_EAY_DEBUG} + ) + else (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + set( OPENSSL_LIBRARIES ${SSL_EAY_RELEASE} ${LIB_EAY_RELEASE} ) + endif (CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE) + + mark_as_advanced(SSL_EAY_DEBUG SSL_EAY_RELEASE) + mark_as_advanced(LIB_EAY_DEBUG LIB_EAY_RELEASE) + elseif (MINGW) + # same player, for MingW + find_library(LIB_EAY + NAMES + libeay32 + PATHS + ${OPENSSL_ROOT_DIR}/lib/MinGW + ) + + find_library(SSL_EAY + NAMES + ssleay32 + PATHS + ${OPENSSL_ROOT_DIR}/lib/MinGW + ) + + mark_as_advanced(SSL_EAY LIB_EAY) + set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY}) + else(MSVC) + # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues: + find_library(LIB_EAY + NAMES + libeay32 + PATHS + ${OPENSSL_ROOT_DIR}/lib + ) + + find_library(SSL_EAY + NAMES + ssleay32 + PATHS + ${OPENSSL_ROOT_DIR}/lib + ) + + mark_as_advanced(SSL_EAY LIB_EAY) + set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY}) + endif(MSVC) + else (WIN32 AND NOT CYGWIN) + find_library(OPENSSL_SSL_LIBRARIES + NAMES + ssl + ssleay32 + ssleay32MD + PATHS + ${_OPENSSL_LIBDIR} + /opt/local/lib + /sw/lib + /usr/sfw/lib/64 + /usr/sfw/lib + ) + + find_library(OPENSSL_CRYPTO_LIBRARIES + NAMES + crypto + PATHS + ${_OPENSSL_LIBDIR} + /opt/local/lib + /sw/lib + /usr/sfw/lib/64 + /usr/sfw/lib + ) + + mark_as_advanced(OPENSSL_CRYPTO_LIBRARIES OPENSSL_SSL_LIBRARIES) + set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES}) + endif (WIN32 AND NOT CYGWIN) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(OpenSSL DEFAULT_MSG OPENSSL_LIBRARIES OPENSSL_INCLUDE_DIRS) + +endif (OPENSSL_LIBRARIES AND OPENSSL_INCLUDE_DIRS) diff --git a/libssh/cmake/Modules/FindZLIB.cmake b/libssh/cmake/Modules/FindZLIB.cmake new file mode 100644 index 00000000..f68207e4 --- /dev/null +++ b/libssh/cmake/Modules/FindZLIB.cmake @@ -0,0 +1,120 @@ +# - Try to find ZLIB +# Once done this will define +# +# ZLIB_ROOT_DIR - Set this variable to the root installation of ZLIB +# +# Read-Only variables: +# ZLIB_FOUND - system has ZLIB +# ZLIB_INCLUDE_DIRS - the ZLIB include directory +# ZLIB_LIBRARIES - Link these to use ZLIB +# +# ZLIB_VERSION_STRING - The version of zlib found (x.y.z) +# ZLIB_VERSION_MAJOR - The major version of zlib +# ZLIB_VERSION_MINOR - The minor version of zlib +# ZLIB_VERSION_PATCH - The patch version of zlib +# ZLIB_VERSION_TWEAK - The tweak version of zlib +# +# The following variable are provided for backward compatibility +# +# ZLIB_MAJOR_VERSION - The major version of zlib +# ZLIB_MINOR_VERSION - The minor version of zlib +# ZLIB_PATCH_VERSION - The patch version of zlib +# +#============================================================================= +# Copyright (c) 2001-2009 Kitware, Inc. +# Copyright (c) 2011 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +if (ZLIB_LIBRARIES AND ZLIB_INCLUDE_DIRS) + # in cache already + set(ZLIB_FOUND TRUE) +else (ZLIB_LIBRARIES AND ZLIB_INCLUDE_DIRS) + + set(_ZLIB_ROOT_HINTS + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32\\Zlib;InstallPath]/include" + ) + + set(_ZLIB_ROOT_PATHS + "$ENV{PROGRAMFILES}/zlib" + ) + + find_path(ZLIB_ROOT_DIR + NAMES + include/zlib.h + HINTS + ${_ZLIB_ROOT_HINTS} + PATHS + ${_ZLIB_ROOT_PATHS} + ) + mark_as_advanced(ZLIB_ROOT_DIR) + + # check for header file + find_path(ZLIB_INCLUDE_DIR + NAMES + zlib.h + PATHS + /usr/local/include + /opt/local/include + /sw/include + /usr/lib/sfw/include + ${ZLIB_ROOT_DIR}/include + ) + mark_as_advanced(ZLIB_INCLUDE_DIR) + + # check version number + if (ZLIB_INCLUDE_DIR AND EXISTS "${ZLIB_INCLUDE_DIR}/zlib.h") + file(STRINGS "${ZLIB_INCLUDE_DIR}/zlib.h" ZLIB_H REGEX "^#define ZLIB_VERSION \"[^\"]*\"$") + + string(REGEX REPLACE "^.*ZLIB_VERSION \"([0-9]+).*$" "\\1" ZLIB_VERSION_MAJOR "${ZLIB_H}") + string(REGEX REPLACE "^.*ZLIB_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" ZLIB_VERSION_MINOR "${ZLIB_H}") + string(REGEX REPLACE "^.*ZLIB_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" ZLIB_VERSION_PATCH "${ZLIB_H}") + + set(ZLIB_VERSION_STRING "${ZLIB_VERSION_MAJOR}.${ZLIB_VERSION_MINOR}.${ZLIB_VERSION_PATCH}") + + # only append a TWEAK version if it exists: + set(ZLIB_VERSION_TWEAK "") + if ("${ZLIB_H}" MATCHES "^.*ZLIB_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+).*$") + set(ZLIB_VERSION_TWEAK "${CMAKE_MATCH_1}") + set(ZLIB_VERSION_STRING "${ZLIB_VERSION_STRING}.${ZLIB_VERSION_TWEAK}") + endif ("${ZLIB_H}" MATCHES "^.*ZLIB_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]+\\.([0-9]+).*$") + + set(ZLIB_MAJOR_VERSION "${ZLIB_VERSION_MAJOR}") + set(ZLIB_MINOR_VERSION "${ZLIB_VERSION_MINOR}") + set(ZLIB_PATCH_VERSION "${ZLIB_VERSION_PATCH}") + endif (ZLIB_INCLUDE_DIR AND EXISTS "${ZLIB_INCLUDE_DIR}/zlib.h") + + find_library(ZLIB_LIBRARY + NAMES + z + zdll + zlib + zlib1 + zlibd + PATHS + /usr/local/lib + /opt/local/lib + /sw/lib + /usr/sfw/lib/64 + /usr/sfw/lib + ${ZLIB_ROOT_DIR}/lib + ) + mark_as_advanced(ZLIB_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(ZLIB DEFAULT_MSG ZLIB_INCLUDE_DIR ZLIB_LIBRARY) + #find_package_handle_standard_args(ZLIB REQUIRED_VARS ZLIB_INCLUDE_DIR ZLIB_LIBRARY + # VERSION_VAR ZLIB_VERSION_STRING) + + if (ZLIB_FOUND) + set(ZLIB_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR}) + set(ZLIB_LIBRARIES ${ZLIB_LIBRARY}) + endif (ZLIB_FOUND) +endif (ZLIB_LIBRARIES AND ZLIB_INCLUDE_DIRS) diff --git a/libssh/cmake/Modules/MacroAddCompileFlags.cmake b/libssh/cmake/Modules/MacroAddCompileFlags.cmake new file mode 100644 index 00000000..a866689d --- /dev/null +++ b/libssh/cmake/Modules/MacroAddCompileFlags.cmake @@ -0,0 +1,21 @@ +# - MACRO_ADD_COMPILE_FLAGS(target_name flag1 ... flagN) + +# Copyright (c) 2006, Oswald Buddenhagen, +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +macro (MACRO_ADD_COMPILE_FLAGS _target) + + get_target_property(_flags ${_target} COMPILE_FLAGS) + if (_flags) + set(_flags ${_flags} ${ARGN}) + else (_flags) + set(_flags ${ARGN}) + endif (_flags) + + set_target_properties(${_target} PROPERTIES COMPILE_FLAGS ${_flags}) + +endmacro (MACRO_ADD_COMPILE_FLAGS) diff --git a/libssh/cmake/Modules/MacroAddLinkFlags.cmake b/libssh/cmake/Modules/MacroAddLinkFlags.cmake new file mode 100644 index 00000000..91cad306 --- /dev/null +++ b/libssh/cmake/Modules/MacroAddLinkFlags.cmake @@ -0,0 +1,20 @@ +# - MACRO_ADD_LINK_FLAGS(target_name flag1 ... flagN) + +# Copyright (c) 2006, Oswald Buddenhagen, +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ADD_LINK_FLAGS _target) + + get_target_property(_flags ${_target} LINK_FLAGS) + if (_flags) + set(_flags "${_flags} ${ARGN}") + else (_flags) + set(_flags "${ARGN}") + endif (_flags) + + set_target_properties(${_target} PROPERTIES LINK_FLAGS "${_flags}") + +endmacro (MACRO_ADD_LINK_FLAGS) diff --git a/libssh/cmake/Modules/MacroAddPlugin.cmake b/libssh/cmake/Modules/MacroAddPlugin.cmake new file mode 100644 index 00000000..36b5e57e --- /dev/null +++ b/libssh/cmake/Modules/MacroAddPlugin.cmake @@ -0,0 +1,30 @@ +# - MACRO_ADD_PLUGIN(name [WITH_PREFIX] file1 .. fileN) +# +# Create a plugin from the given source files. +# If WITH_PREFIX is given, the resulting plugin will have the +# prefix "lib", otherwise it won't. +# +# Copyright (c) 2006, Alexander Neundorf, +# Copyright (c) 2006, Laurent Montel, +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +macro (MACRO_ADD_PLUGIN _target_NAME _with_PREFIX) + + if (${_with_PREFIX} STREQUAL "WITH_PREFIX") + set(_first_SRC) + else (${_with_PREFIX} STREQUAL "WITH_PREFIX") + set(_first_SRC ${_with_PREFIX}) + endif (${_with_PREFIX} STREQUAL "WITH_PREFIX") + + add_library(${_target_NAME} MODULE ${_first_SRC} ${ARGN}) + + if (_first_SRC) + set_target_properties(${_target_NAME} PROPERTIES PREFIX "") + endif (_first_SRC) + +endmacro (MACRO_ADD_PLUGIN _name _sources) + diff --git a/libssh/cmake/Modules/MacroCopyFile.cmake b/libssh/cmake/Modules/MacroCopyFile.cmake new file mode 100644 index 00000000..cee1cae3 --- /dev/null +++ b/libssh/cmake/Modules/MacroCopyFile.cmake @@ -0,0 +1,33 @@ +# - macro_copy_file(_src _dst) +# Copies a file to ${_dst} only if ${_src} is different (newer) than ${_dst} +# +# Example: +# macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/icon.png ${CMAKE_CURRENT_BINARY_DIR}/.) +# Copies file icon.png to ${CMAKE_CURRENT_BINARY_DIR} directory +# +# Copyright (c) 2006-2007 Wengo +# Copyright (c) 2006-2008 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING file. + + +macro (macro_copy_file _src _dst) + # Removes all path containing .svn or CVS or CMakeLists.txt during the copy + if (NOT ${_src} MATCHES ".*\\.svn|CVS|CMakeLists\\.txt.*") + + if (CMAKE_VERBOSE_MAKEFILE) + message(STATUS "Copy file from ${_src} to ${_dst}") + endif (CMAKE_VERBOSE_MAKEFILE) + + # Creates directory if necessary + get_filename_component(_path ${_dst} PATH) + file(MAKE_DIRECTORY ${_path}) + + execute_process( + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${_src} ${_dst} + OUTPUT_QUIET + ) + endif (NOT ${_src} MATCHES ".*\\.svn|CVS|CMakeLists\\.txt.*") +endmacro (macro_copy_file) diff --git a/libssh/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake b/libssh/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake new file mode 100644 index 00000000..a2e94809 --- /dev/null +++ b/libssh/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake @@ -0,0 +1,17 @@ +# - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() +# MACRO_ENSURE_OUT_OF_SOURCE_BUILD() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) + + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) + if (_insource) + message(SEND_ERROR "${_errorMessage}") + message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") + endif (_insource) + +endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) diff --git a/libssh/cmake/Modules/UseDoxygen.cmake b/libssh/cmake/Modules/UseDoxygen.cmake new file mode 100644 index 00000000..c4ab7ccc --- /dev/null +++ b/libssh/cmake/Modules/UseDoxygen.cmake @@ -0,0 +1,100 @@ +# - Run Doxygen +# +# Adds a doxygen target that runs doxygen to generate the html +# and optionally the LaTeX API documentation. +# The doxygen target is added to the doc target as dependency. +# i.e.: the API documentation is built with: +# make doc +# +# USAGE: INCLUDE IN PROJECT +# +# set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +# include(UseDoxygen) +# Add the Doxyfile.in and UseDoxygen.cmake files to the projects source directory. +# +# +# Variables you may define are: +# DOXYFILE_OUTPUT_DIR - Path where the Doxygen output is stored. Defaults to "doc". +# +# DOXYFILE_LATEX_DIR - Directory where the Doxygen LaTeX output is stored. Defaults to "latex". +# +# DOXYFILE_HTML_DIR - Directory where the Doxygen html output is stored. Defaults to "html". +# + +# +# Copyright (c) 2009-2010 Tobias Rautenkranz +# Copyright (c) 2010 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +macro(usedoxygen_set_default name value) + if(NOT DEFINED "${name}") + set("${name}" "${value}") + endif() +endmacro() + +find_package(Doxygen) + +if(DOXYGEN_FOUND) + find_file(DOXYFILE_IN + NAMES + doxy.config.in + PATHS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_ROOT}/Modules/ + NO_DEFAULT_PATH) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(DOXYFILE_IN DEFAULT_MSG "DOXYFILE_IN") +endif() + +if(DOXYGEN_FOUND AND DOXYFILE_IN_FOUND) + add_custom_target(doxygen ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.config) + + usedoxygen_set_default(DOXYFILE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + usedoxygen_set_default(DOXYFILE_HTML_DIR "html") + + set_property(DIRECTORY APPEND PROPERTY + ADDITIONAL_MAKE_CLEAN_FILES "${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_HTML_DIR}") + + set(DOXYFILE_LATEX FALSE) + set(DOXYFILE_PDFLATEX FALSE) + set(DOXYFILE_DOT FALSE) + + find_package(LATEX) + if(LATEX_COMPILER AND MAKEINDEX_COMPILER) + set(DOXYFILE_LATEX TRUE) + usedoxygen_set_default(DOXYFILE_LATEX_DIR "latex") + + set_property(DIRECTORY APPEND PROPERTY + ADDITIONAL_MAKE_CLEAN_FILES + "${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_LATEX_DIR}") + + if(PDFLATEX_COMPILER) + set(DOXYFILE_PDFLATEX TRUE) + endif() + if(DOXYGEN_DOT_EXECUTABLE) + set(DOXYFILE_DOT TRUE) + endif() + + add_custom_command(TARGET doxygen + POST_BUILD + COMMAND ${CMAKE_MAKE_PROGRAM} + WORKING_DIRECTORY "${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_LATEX_DIR}") + endif() + + configure_file(${DOXYFILE_IN} ${CMAKE_CURRENT_BINARY_DIR}/doxy.config ESCAPE_QUOTES IMMEDIATE @ONLY) + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/doxy.trac.in) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doxy.trac.in ${CMAKE_CURRENT_BINARY_DIR}/doxy.trac ESCAPE_QUOTES IMMEDIATE @ONLY) + add_custom_target(doxygen-trac ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.trac) + endif() + + get_target_property(DOC_TARGET doc TYPE) + if(NOT DOC_TARGET) + add_custom_target(doc) + endif() + + add_dependencies(doc doxygen) +endif() diff --git a/libssh/config.h.cmake b/libssh/config.h.cmake new file mode 100644 index 00000000..2014e8d9 --- /dev/null +++ b/libssh/config.h.cmake @@ -0,0 +1,148 @@ +/* Name of package */ +#cmakedefine PACKAGE "${APPLICATION_NAME}" + +/* Version number of package */ +#cmakedefine VERSION "${APPLICATION_VERSION}" + +#cmakedefine LOCALEDIR "${LOCALE_INSTALL_DIR}" +#cmakedefine DATADIR "${DATADIR}" +#cmakedefine LIBDIR "${LIBDIR}" +#cmakedefine PLUGINDIR "${PLUGINDIR}" +#cmakedefine SYSCONFDIR "${SYSCONFDIR}" +#cmakedefine BINARYDIR "${BINARYDIR}" +#cmakedefine SOURCEDIR "${SOURCEDIR}" + +/************************** HEADER FILES *************************/ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARGP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTY_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_AES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WSPIAPI_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_BLOWFISH_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_DES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDH_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_EC_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDSA_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTHREAD_H 1 + +/* Define to 1 if you have eliptic curve cryptography in openssl */ +#cmakedefine HAVE_OPENSSL_ECC 1 + +/* Define to 1 if you have eliptic curve cryptography in gcrypt */ +#cmakedefine HAVE_GCRYPT_ECC 1 + +/* Define to 1 if you have eliptic curve cryptography */ +#cmakedefine HAVE_ECC 1 + +/*************************** FUNCTIONS ***************************/ + +/* Define to 1 if you have the `snprintf' function. */ +#cmakedefine HAVE_SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf' function. */ +#cmakedefine HAVE__SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf_s' function. */ +#cmakedefine HAVE__SNPRINTF_S 1 + +/* Define to 1 if you have the `vsnprintf' function. */ +#cmakedefine HAVE_VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf' function. */ +#cmakedefine HAVE__VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf_s' function. */ +#cmakedefine HAVE__VSNPRINTF_S 1 + +/* Define to 1 if you have the `strncpy' function. */ +#cmakedefine HAVE_STRNCPY 1 + +/* Define to 1 if you have the `cfmakeraw' function. */ +#cmakedefine HAVE_CFMAKERAW 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#cmakedefine HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `poll' function. */ +#cmakedefine HAVE_POLL 1 + +/* Define to 1 if you have the `select' function. */ +#cmakedefine HAVE_SELECT 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#cmakedefine HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `ntohll' function. */ +#cmakedefine HAVE_NTOHLL 1 + +/* Define to 1 if you have the `htonll' function. */ +#cmakedefine HAVE_HTONLL 1 + +/* Define to 1 if you have the `strtoull' function. */ +#cmakedefine HAVE_STRTOULL 1 + +/* Define to 1 if you have the `__strtoull' function. */ +#cmakedefine HAVE___STRTOULL 1 + +/*************************** LIBRARIES ***************************/ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#cmakedefine HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ +#cmakedefine HAVE_LIBGCRYPT 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#cmakedefine HAVE_PTHREAD 1 + + +/**************************** OPTIONS ****************************/ + +/* Define to 1 if you want to enable ZLIB */ +#cmakedefine WITH_ZLIB 1 + +/* Define to 1 if you want to enable SFTP */ +#cmakedefine WITH_SFTP 1 + +/* Define to 1 if you want to enable SSH1 */ +#cmakedefine WITH_SSH1 1 + +/* Define to 1 if you want to enable server support */ +#cmakedefine WITH_SERVER 1 + +/* Define to 1 if you want to enable debug output for crypto functions */ +#cmakedefine DEBUG_CRYPTO 1 + +/* Define to 1 if you want to enable pcap output support (experimental) */ +#cmakedefine WITH_PCAP 1 + +/* Define to 1 if you want to enable calltrace debug output */ +#cmakedefine DEBUG_CALLTRACE 1 + +/*************************** ENDIAN *****************************/ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#cmakedefine WORDS_BIGENDIAN 1 diff --git a/libssh/doc/CMakeLists.txt b/libssh/doc/CMakeLists.txt new file mode 100644 index 00000000..31242811 --- /dev/null +++ b/libssh/doc/CMakeLists.txt @@ -0,0 +1,5 @@ +# +# Build the documentation +# +include(UseDoxygen OPTIONAL) + diff --git a/libssh/doc/TracFooter.html b/libssh/doc/TracFooter.html new file mode 100644 index 00000000..867baf0e --- /dev/null +++ b/libssh/doc/TracFooter.html @@ -0,0 +1 @@ + diff --git a/libssh/doc/TracHeader.html b/libssh/doc/TracHeader.html new file mode 100644 index 00000000..280a869b --- /dev/null +++ b/libssh/doc/TracHeader.html @@ -0,0 +1,4 @@ + + + + diff --git a/libssh/doc/authentication.dox b/libssh/doc/authentication.dox new file mode 100644 index 00000000..fbc2103b --- /dev/null +++ b/libssh/doc/authentication.dox @@ -0,0 +1,375 @@ +/** +@page libssh_tutor_authentication Chapter 2: A deeper insight on authentication +@section authentication_details A deeper insight on authentication + +In our guided tour, we merely mentioned that the user needed to authenticate. +We didn't explain much in detail how that was supposed to happen. +This chapter explains better the four authentication methods: with public keys, +with a password, with challenges and responses (keyboard-interactive), and with +no authentication at all. + +If your software is supposed to connect to an arbitrary server, then you +might need to support all authentication methods. If your software will +connect only to a given server, then it might be enough for your software +to support only the authentication methods used by that server. If you are +the administrator of the server, it might be your call to choose those +authentication methods. + +It is not the purpose of this document to review in detail the advantages +and drawbacks of each authentication method. You are therefore invited +to read the abundant documentation on this topic to fully understand the +advantages and security risks linked to each method. + + +@subsection pubkeys Authenticating with public keys + +libssh is fully compatible with the openssh public and private keys. You +can either use the automatic public key authentication method provided by +libssh, or roll your own using the public key functions. + +The process of authenticating by public key to a server is the following: + - you scan a list of files that contain public keys. each key is sent to + the SSH server, until the server acknowledges a key (a key it knows can be + used to authenticate the user). + - then, you retrieve the private key for this key and send a message + proving that you know that private key. + +The function ssh_userauth_autopubkey() does this using the available keys in +"~/.ssh/". The return values are the following: + - SSH_AUTH_ERROR: some serious error happened during authentication + - SSH_AUTH_DENIED: no key matched + - SSH_AUTH_SUCCESS: you are now authenticated + - SSH_AUTH_PARTIAL: some key matched but you still have to provide an other + mean of authentication (like a password). + +The ssh_userauth_publickey_auto() function also tries to authenticate using the +SSH agent, if you have one running, or the "none" method otherwise. + +If you wish to authenticate with public key by your own, follow these steps: + - Retrieve the public key with ssh_import_pubkey_file(). + - Offer the public key to the SSH server using ssh_userauth_try_publickey(). + If the return value is SSH_AUTH_SUCCESS, the SSH server accepts to + authenticate using the public key and you can go to the next step. + - Retrieve the private key, using the ssh_pki_import_privkey_file() function. + If a passphrase is needed, either the passphrase specified as argument or + a callback will be used. + - Authenticate using ssh_userauth_publickey() with your private key. + - Do not forget cleaning up memory using ssh_key_free(). + +Here is a minimalistic example of public key authentication: + +@code +int authenticate_pubkey(ssh_session session) +{ + int rc; + + rc = ssh_userauth_publickey_auto(session, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_publickey_auto() +@see ssh_userauth_try_publickey() +@see ssh_userauth_publickey() +@see ssh_pki_import_pubkey_file() +@see ssh_pki_import_privkey_file() +@see ssh_key_free() + + +@subsection password Authenticating with a password + +The function ssh_userauth_password() serves the purpose of authenticating +using a password. It will return SSH_AUTH_SUCCESS if the password worked, +or one of other constants otherwise. It's your work to ask the password +and to deallocate it in a secure manner. + +If your server complains that the password is wrong, but you can still +authenticate using openssh's client (issuing password), it's probably +because openssh only accept keyboard-interactive. Switch to +keyboard-interactive authentication, or try to configure plain text passwords +on the SSH server. + +Here is a small example of password authentication: + +@code +int authenticate_password(ssh_session session) +{ + char *password; + int rc; + + password = getpass("Enter your password: "); + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_password + + +@subsection keyb_int The keyboard-interactive authentication method + +The keyboard-interactive method is, as its name tells, interactive. The +server will issue one or more challenges that the user has to answer, +until the server takes an authentication decision. + +ssh_userauth_kbdint() is the the main keyboard-interactive function. +It will return SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, +SSH_AUTH_ERROR, or SSH_AUTH_INFO, depending on the result of the request. + +The keyboard-interactive authentication method of SSH2 is a feature that +permits the server to ask a certain number of questions in an interactive +manner to the client, until it decides to accept or deny the login. + +To begin, you call ssh_userauth_kbdint() (just set user and submethods to +NULL) and store the answer. + +If the answer is SSH_AUTH_INFO, it means that the server has sent a few +questions that you should ask the user. You can retrieve these questions +with the following functions: ssh_userauth_kbdint_getnprompts(), +ssh_userauth_kbdint_getname(), ssh_userauth_kbdint_getinstruction(), and +ssh_userauth_kbdint_getprompt(). + +Set the answer for each question in the challenge using +ssh_userauth_kbdint_setanswer(). + +Then, call again ssh_userauth_kbdint() and start the process again until +these functions returns something else than SSH_AUTH_INFO. + +Here are a few remarks: + - Even the first call can return SSH_AUTH_DENIED or SSH_AUTH_SUCCESS. + - The server can send an empty question set (this is the default behavior + on my system) after you have sent the answers to the first questions. + You must still parse the answer, it might contain some + message from the server saying hello or such things. Just call + ssh_userauth_kbdint() until needed. + - The meaning of "name", "prompt", "instruction" may be a little + confusing. An explanation is given in the RFC section that follows. + +Here is a little note about how to use the information from +keyboard-interactive authentication, coming from the RFC itself (rfc4256): + +@verbatim + + 3.3 User Interface Upon receiving a request message, the client SHOULD + prompt the user as follows: A command line interface (CLI) client SHOULD + print the name and instruction (if non-empty), adding newlines. Then for + each prompt in turn, the client SHOULD display the prompt and read the + user input. + + A graphical user interface (GUI) client has many choices on how to prompt + the user. One possibility is to use the name field (possibly prefixed + with the application's name) as the title of a dialog window in which + the prompt(s) are presented. In that dialog window, the instruction field + would be a text message, and the prompts would be labels for text entry + fields. All fields SHOULD be presented to the user, for example an + implementation SHOULD NOT discard the name field because its windows lack + titles; it SHOULD instead find another way to display this information. If + prompts are presented in a dialog window, then the client SHOULD NOT + present each prompt in a separate window. + + All clients MUST properly handle an instruction field with embedded + newlines. They SHOULD also be able to display at least 30 characters for + the name and prompts. If the server presents names or prompts longer than 30 + characters, the client MAY truncate these fields to the length it can + display. If the client does truncate any fields, there MUST be an obvious + indication that such truncation has occured. + + The instruction field SHOULD NOT be truncated. Clients SHOULD use control + character filtering as discussed in [SSH-ARCH] to avoid attacks by + including terminal control characters in the fields to be displayed. + + For each prompt, the corresponding echo field indicates whether or not + the user input should be echoed as characters are typed. Clients SHOULD + correctly echo/mask user input for each prompt independently of other + prompts in the request message. If a client does not honor the echo field + for whatever reason, then the client MUST err on the side of + masking input. A GUI client might like to have a checkbox toggling + echo/mask. Clients SHOULD NOT add any additional characters to the prompt + such as ": " (colon-space); the server is responsible for supplying all + text to be displayed to the user. Clients MUST also accept empty responses + from the user and pass them on as empty strings. +@endverbatim + +The following example shows how to perform keyboard-interactive authentication: + +@code +int authenticate_kbdint(ssh_session session) +{ + int rc; + + rc = ssh_userauth_kbdint(session, NULL, NULL); + while (rc == SSH_AUTH_INFO) + { + const char *name, *instruction; + int nprompts, iprompt; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + nprompts = ssh_userauth_kbdint_getnprompts(session); + + if (strlen(name) > 0) + printf("%s\n", name); + if (strlen(instruction) > 0) + printf("%s\n", instruction); + for (iprompt = 0; iprompt < nprompts; iprompt++) + { + const char *prompt; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo); + if (echo) + { + char buffer[128], *ptr; + + printf("%s", prompt); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + return SSH_AUTH_ERROR; + buffer[sizeof(buffer) - 1] = '\0'; + if ((ptr = strchr(buffer, '\n')) != NULL) + *ptr = '\0'; + if (ssh_userauth_kbdint_setanswer(session, iprompt, buffer) < 0) + return SSH_AUTH_ERROR; + memset(buffer, 0, strlen(buffer)); + } + else + { + char *ptr; + + ptr = getpass(prompt); + if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0) + return SSH_AUTH_ERROR; + } + } + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + return rc; +} +@endcode + +@see ssh_userauth_kbdint() +@see ssh_userauth_kbdint_getnprompts() +@see ssh_userauth_kbdint_getname() +@see ssh_userauth_kbdint_getinstruction() +@see ssh_userauth_kbdint_getprompt() +@see ssh_userauth_kbdint_setanswer() + + +@subsection none Authenticating with "none" method + +The primary purpose of the "none" method is to get authenticated **without** +any credential. Don't do that, use one of the other authentication methods, +unless you really want to grant anonymous access. + +If the account has no password, and if the server is configured to let you +pass, ssh_userauth_none() might answer SSH_AUTH_SUCCESS. + +The following example shows how to perform "none" authentication: + +@code +int authenticate_kbdint(ssh_session session) +{ + int rc; + + rc = ssh_userauth_none(session, NULL, NULL); + return rc; +} +@endcode + +@subsection auth_list Getting the list of supported authentications + +You are not meant to choose a given authentication method, you can +let the server tell you which methods are available. Once you know them, +you try them one after the other. + +The following example shows how to get the list of available authentication +methods with ssh_userauth_list() and how to use the result: + +@code +int test_several_auth_methods(ssh_session session) +{ + int method, rc; + + rc = ssh_userauth_none(session, NULL, NULL); + if (rc != SSH_AUTH_SUCCESS) { + return rc; + } + + method = ssh_userauth_list(session, NULL); + + if (method & SSH_AUTH_METHOD_NONE) + { // For the source code of function authenticate_none(), + // refer to the corresponding example + rc = authenticate_none(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PUBLICKEY) + { // For the source code of function authenticate_pubkey(), + // refer to the corresponding example + rc = authenticate_pubkey(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_INTERACTIVE) + { // For the source code of function authenticate_kbdint(), + // refer to the corresponding example + rc = authenticate_kbdint(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PASSWORD) + { // For the source code of function authenticate_password(), + // refer to the corresponding example + rc = authenticate_password(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + return SSH_AUTH_ERROR; +} +@endcode + + +@subsection banner Getting the banner + +The SSH server might send a banner, which you can retrieve with +ssh_get_issue_banner(), then display to the user. + +The following example shows how to retrieve and dispose the issue banner: + +@code +int display_banner(ssh_session session) +{ + int rc; + char *banner; + +/* + *** Does not work without calling ssh_userauth_none() first *** + *** That will be fixed *** +*/ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR) + return rc; + + banner = ssh_get_issue_banner(session); + if (banner) + { + printf("%s\n", banner); + free(banner); + } + + return rc; +} +@endcode + +*/ diff --git a/libssh/doc/command.dox b/libssh/doc/command.dox new file mode 100644 index 00000000..76113543 --- /dev/null +++ b/libssh/doc/command.dox @@ -0,0 +1,94 @@ +/** +@page libssh_tutor_command Chapter 4: Passing a remote command +@section remote_command Passing a remote command + +Previous chapter has shown how to open a full shell session, with an attached +terminal or not. If you only need to execute a command on the remote end, +you don't need all that complexity. + +The method described here is suited for executing only one remote command. +If you need to issue several commands in a row, you should consider using +a non-interactive remote shell, as explained in previous chapter. + +@see shell + + +@subsection exec_remote Executing a remote command + +The first steps for executing a remote command are identical to those +for opening remote shells. You first need a SSH channel, and then +a SSH session that uses this channel: + +@code +int show_remote_files(ssh_session session) +{ + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } +@endcode + +Once a session is open, you can start the remote command with +ssh_channel_request_exec(): + +@code + rc = ssh_channel_request_exec(channel, "ls -l"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } +@endcode + +If the remote command displays data, you get them with ssh_channel_read(). +This function returns the number of bytes read. If there is no more +data to read on the channel, this function returns 0, and you can go to next step. +If an error has been encountered, it returns a negative value: + +@code + char buffer[256]; + unsigned int nbytes; + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (fwrite(buffer, 1, nbytes, stdout) != nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } +@endcode + +Once you read the result of the remote command, you send an +end-of-file to the channel, close it, and free the memory +that it used: + +@code + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +*/ diff --git a/libssh/doc/doxy.config.in b/libssh/doc/doxy.config.in new file mode 100644 index 00000000..9810518f --- /dev/null +++ b/libssh/doc/doxy.config.in @@ -0,0 +1,1835 @@ +# Doxyfile 1.8.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = @APPLICATION_NAME@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @APPLICATION_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@ + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented classes, +# or namespaces to their corresponding documentation. Such a link can be +# prevented in individual cases by by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter and setter methods for a property. Setting this option to YES (the default) will make doxygen replace the get and set methods by a property in the documentation. This will only work if the methods are indeed getting or setting a simple type. If this is not the case, or you want to show the methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = YES + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = @CMAKE_INTERNAL_DOC@ + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @CMAKE_SOURCE_DIR@/include/libssh \ + @CMAKE_SOURCE_DIR@/src \ + @CMAKE_SOURCE_DIR@/doc + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.cpp \ + *.cc \ + *.c \ + *.h \ + *.hh \ + *.hpp \ + *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */.git/* \ + */.svn/* \ + */cmake/* \ + */build/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/examples + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = *.c \ + *.h \ + INSTALL \ + DEPENDENCIES \ + CHANGELOG \ + LICENSE \ + LGPL + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 2 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = @DOXYFILE_LATEX@ + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = @LATEX_COMPILER@ + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = @MAKEINDEX_COMPILER@ + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = YES + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = @CMAKE_CURRENT_BINARY_DIR@/html/@PROJECT_NAME@.TAGFILE + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = @DOXYGEN_DOT_FOUND@ + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = @DOXYGEN_DOT_EXECUTABLE_PATH@ + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/libssh/doc/doxy.trac.in b/libssh/doc/doxy.trac.in new file mode 100644 index 00000000..dbd719aa --- /dev/null +++ b/libssh/doc/doxy.trac.in @@ -0,0 +1,1546 @@ +# Doxyfile 1.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @APPLICATION_NAME@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @APPLICATION_VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@ + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = YES + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = @CMAKE_INTERNAL_DOC@ + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @CMAKE_SOURCE_DIR@/include \ + @CMAKE_SOURCE_DIR@/libssh \ + @CMAKE_SOURCE_DIR@/doc + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.cpp \ + *.cc \ + *.c \ + *.h \ + *.hh \ + *.hpp \ + *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */.git/* \ + */.svn/* \ + */cmake/* \ + */build/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/examples + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = *.c \ + *.h \ + INSTALL \ + DEPENDENCIES \ + CHANGELOG \ + LICENSE \ + LGPL + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 2 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = trac + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = @CMAKE_CURRENT_SOURCE_DIR@/TracHeader.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = @CMAKE_CURRENT_SOURCE_DIR@/TracFooter.html + +# If the HTML_TIMESTAMP tag is set to YES then the generated HTML +# documentation will contain the timesstamp. + +HTML_TIMESTAMP = NO + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP) +# there is already a search function so this one should typically +# be disabled. + +SEARCHENGINE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = YES + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = @CMAKE_CURRENT_BINARY_DIR@/trac/@PROJECT_NAME@.TAGFILE + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = @DOXYGEN_DOT_FOUND@ + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = @DOXYGEN_DOT_EXECUTABLE_PATH@ + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/libssh/doc/forwarding.dox b/libssh/doc/forwarding.dox new file mode 100644 index 00000000..9dc0df36 --- /dev/null +++ b/libssh/doc/forwarding.dox @@ -0,0 +1,229 @@ +/** +@page libssh_tutor_forwarding Chapter 7: Forwarding connections (tunnel) +@section forwarding_connections Forwarding connections + +Port forwarding comes in SSH protocol in two different flavours: +direct or reverse port forwarding. Direct port forwarding is also +named local port forwardind, and reverse port forwarding is also called +remote port forwarding. SSH also allows X11 tunnels. + + + +@subsection forwarding_direct Direct port forwarding + +Direct port forwarding is from client to server. The client opens a tunnel, +and forwards whatever data to the server. Then, the server connects to an +end point. The end point can reside on another machine or on the SSH +server itself. + +Example of use of direct port forwarding: +@verbatim +Mail client application Google Mail + | ^ + 5555 (arbitrary) | + | 143 (IMAP2) + V | + SSH client =====> SSH server + +Legend: +--P-->: port connexion through port P +=====>: SSH tunnel +@endverbatim +A mail client connects to port 5555 of a client. An encrypted tunnel is +established to the server. The server connects to port 143 of Google Mail (the +end point). Now the local mail client can retreive mail. + + +@subsection forwarding_reverse Reverse port forwarding + +The reverse forwarding is slightly different. It goes from server to client, +even though the client has the initiative of establishing the tunnel. +Once the tunnel is established, the server will listen on a port. Whenever +a connection to this port is made, the server forwards the data to the client. + +Example of use of reverse port forwarding: +@verbatim + Local mail server Mail client application + ^ | + | 5555 (arbitrary) + 143 (IMAP2) | + | V + SSH client <===== SSH server + +Legend: +--P-->: port connexion through port P +=====>: SSH tunnel +@endverbatim +In this example, the SSH client establishes the tunnel, +but it is used to forward the connections established at +the server to the client. + + +@subsection forwarding_x11 X11 tunnels + +X11 tunnels allow a remote application to display locally. + +Example of use of X11 tunnels: +@verbatim + Local display Graphical application + (X11 server) (X11 client) + ^ | + | V + SSH client <===== SSH server + +Legend: +----->: X11 connection through X11 display number +=====>: SSH tunnel +@endverbatim +The SSH tunnel is established by the client. + +How to establish X11 tunnels with libssh has already been described in +this tutorial. + +@see x11 + + +@subsection libssh_direct Doing direct port forwarding with libssh + +To do direct port forwarding, call function ssh_channel_open_forward(): + - you need a separate channel for the tunnel as first parameter; + - second and third parameters are the remote endpoint; + - fourth and fifth parameters are sent to the remote server + so that they can be logged on that server. + +If you don't plan to forward the data you will receive to any local port, +just put fake values like "localhost" and 5555 as your local host and port. + +The example below shows how to open a direct channel that would be +used to retrieve google's home page from the remote SSH server. + +@code +int direct_forwarding(ssh_session session) +{ + ssh_channel forwarding_channel; + int rc; + char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n"; + int nbytes, nwritten; + + forwarding_channel = ssh_channel_new(session); + if (forwarding_channel == NULL) { + return rc; + } + + rc = ssh_channel_open_forward(forwarding_channel, + "www.google.com", 80, + "localhost", 5555); + if (rc != SSH_OK) + { + ssh_channel_free(forwarding_channel); + return rc; + } + + nbytes = strlen(http_get); + nwritten = ssh_channel_write(forwarding_channel, + http_get, + nbytes); + if (nbytes != nwritten) + { + ssh_channel_free(forwarding_channel); + return SSH_ERROR; + } + + ... + + ssh_channel_free(forwarding_channel); + return SSH_OK; +} +@endcode + +The data sent by Google can be retrieved for example with ssh_select() +and ssh_channel_read(). Goggle's home page can then be displayed on the +local SSH client, saved into a local file, made available on a local port, +or whatever use you have for it. + + +@subsection libssh_reverse Doing reverse port forwarding with libssh + +To do reverse port forwarding, call ssh_forward_listen(), +then ssh_forward_accept(). + +When you call ssh_forward_listen(), you can let the remote server +chose the non-priviledged port it should listen to. Otherwise, you can chose +your own priviledged or non-priviledged port. Beware that you should have +administrative priviledges on the remote server to open a priviledged port +(port number < 1024). + +Below is an example of a very rough web server waiting for connections on port +8080 of remote SSH server. The incoming connections are passed to the +local libssh application, which handles them: + +@code +int web_server(ssh_session session) +{ + int rc; + ssh_channel channel; + char buffer[256]; + int nbytes, nwritten; + char *helloworld = "" +"HTTP/1.1 200 OK\n" +"Content-Type: text/html\n" +"Content-Length: 113\n" +"\n" +"\n" +" \n" +" Hello, World!\n" +" \n" +" \n" +"

Hello, World!

\n" +" \n" +"\n"; + + rc = ssh_forward_listen(session, NULL, 8080, NULL); + if (rc != SSH_OK) + { + fprintf(stderr, "Error opening remote port: %s\n", + ssh_get_error(session)); + return rc; + } + + channel = ssh_forward_accept(session, 60000); + if (channel == NULL) + { + fprintf(stderr, "Error waiting for incoming connection: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + while (1) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + { + fprintf(stderr, "Error reading incoming data: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + if (strncmp(buffer, "GET /", 5)) continue; + + nbytes = strlen(helloworld); + nwritten = ssh_channel_write(channel, helloworld, nbytes); + if (nwritten != nbytes) + { + fprintf(stderr, "Error sending answer: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + printf("Sent answer\n"); + } + + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return SSH_OK; +} +@endcode + +*/ diff --git a/libssh/doc/guided_tour.dox b/libssh/doc/guided_tour.dox new file mode 100644 index 00000000..2fa906e0 --- /dev/null +++ b/libssh/doc/guided_tour.dox @@ -0,0 +1,455 @@ +/** +@page libssh_tutor_guided_tour Chapter 1: A typical SSH session +@section ssh_session A typical SSH session + +A SSH session goes through the following steps: + + - Before connecting to the server, you can set up if you wish one or other + server public key authentication, i.e. DSA or RSA. You can choose + cryptographic algorithms you trust and compression algorithms if any. You + must of course set up the hostname. + + - The connection is established. A secure handshake is made, and resulting from + it, a public key from the server is gained. You MUST verify that the public + key is legitimate, using for instance the MD5 fingerprint or the known hosts + file. + + - The client must authenticate: the classical ways are password, or + public keys (from dsa and rsa key-pairs generated by openssh). + If a SSH agent is running, it is possible to use it. + + - Now that the user has been authenticated, you must open one or several + channels. Channels are different subways for information into a single ssh + connection. Each channel has a standard stream (stdout) and an error stream + (stderr). You can theoretically open an infinity of channels. + + - With the channel you opened, you can do several things: + - Execute a single command. + - Open a shell. You may want to request a pseudo-terminal before. + - Invoke the sftp subsystem to transfer files. + - Invoke the scp subsystem to transfer files. + - Invoke your own subsystem. This is outside the scope of this document, + but can be done. + + - When everything is finished, just close the channels, and then the connection. + +The sftp and scp subsystems use channels, but libssh hides them to +the programmer. If you want to use those subsystems, instead of a channel, +you'll usually open a "sftp session" or a "scp session". + + +@subsection setup Creating the session and setting options + +The most important object in a SSH connection is the SSH session. In order +to allocate a new SSH session, you use ssh_new(). Don't forget to +always verify that the allocation successed. +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ... + ssh_free(my_ssh_session); +} +@endcode + +libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate +using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_new() +does the allocation and ssh_free() does the contrary. + +The ssh_options_set() function sets the options of the session. The most important options are: + - SSH_OPTIONS_HOST: the name of the host you want to connect to + - SSH_OPTIONS_PORT: the used port (default is port 22) + - SSH_OPTIONS_USER: the system user under which you want to connect + - SSH_OPTIONS_LOG_VERBOSITY: the quantity of messages that are printed + +The complete list of options can be found in the documentation of ssh_options_set(). +The only mandatory option is SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, +the local username of your account will be used. + +Here is a small example of how to use it: + +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session; + int verbosity = SSH_LOG_PROTOCOL; + int port = 22; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port); + + ... + + ssh_free(my_ssh_session); +} +@endcode + +Please notice that all parameters are passed to ssh_options_set() as pointers, +even if you need to set an integer value. + +@see ssh_new +@see ssh_free +@see ssh_options_set +@see ssh_options_parse_config +@see ssh_options_copy +@see ssh_options_getopt + + +@subsection connect Connecting to the server + +Once all settings have been made, you can connect using ssh_connect(). That +function will return SSH_OK if the connection worked, SSH_ERROR otherwise. + +You can get the English error string with ssh_get_error() in order to show the +user what went wrong. Then, use ssh_disconnect() when you want to stop +the session. + +Here's an example: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session; + int rc; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + + +@subsection serverauth Authenticating the server + +Once you're connected, the following step is mandatory: you must check that the server +you just connected to is known and safe to use (remember, SSH is about security and +authentication). + +There are two ways of doing this: + - The first way (recommended) is to use the ssh_is_server_known() + function. This function will look into the known host file + (~/.ssh/known_hosts on UNIX), look for the server hostname's pattern, + and determine whether this host is present or not in the list. + - The second way is to use ssh_get_pubkey_hash() to get a binary version + of the public key hash value. You can then use your own database to check + if this public key is known and secure. + +You can also use the ssh_get_pubkey_hash() to show the public key hash +value to the user, in case he knows what the public key hash value is +(some paranoid people write their public key hash values on paper before +going abroad, just in case ...). + +If the remote host is being used to for the first time, you can ask the user whether +he/she trusts it. Once he/she concluded that the host is valid and worth being +added in the known hosts file, you use ssh_write_knownhost() to register it in +the known hosts file, or any other way if you use your own database. + +The following example is part of the examples suite available in the +examples/ directory: + +@code +#include +#include + +int verify_knownhost(ssh_session session) +{ + int state, hlen; + unsigned char *hash = NULL; + char *hexa; + char buf[10]; + + state = ssh_is_server_known(session); + + hlen = ssh_get_pubkey_hash(session, &hash); + if (hlen < 0) + return -1; + + switch (state) + { + case SSH_SERVER_KNOWN_OK: + break; /* ok */ + + case SSH_SERVER_KNOWN_CHANGED: + fprintf(stderr, "Host key for server changed: it is now:\n"); + ssh_print_hexa("Public key hash", hash, hlen); + fprintf(stderr, "For security reasons, connection will be stopped\n"); + free(hash); + return -1; + + case SSH_SERVER_FOUND_OTHER: + fprintf(stderr, "The host key for this server was not found but an other" + "type of key exists.\n"); + fprintf(stderr, "An attacker might change the default server key to" + "confuse your client into thinking the key does not exist\n"); + free(hash); + return -1; + + case SSH_SERVER_FILE_NOT_FOUND: + fprintf(stderr, "Could not find known host file.\n"); + fprintf(stderr, "If you accept the host key here, the file will be" + "automatically created.\n"); + /* fallback to SSH_SERVER_NOT_KNOWN behavior */ + + case SSH_SERVER_NOT_KNOWN: + hexa = ssh_get_hexa(hash, hlen); + fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); + fprintf(stderr, "Public key hash: %s\n", hexa); + free(hexa); + if (fgets(buf, sizeof(buf), stdin) == NULL) + { + free(hash); + return -1; + } + if (strncasecmp(buf, "yes", 3) != 0) + { + free(hash); + return -1; + } + if (ssh_write_knownhost(session) < 0) + { + fprintf(stderr, "Error %s\n", strerror(errno)); + free(hash); + return -1; + } + break; + + case SSH_SERVER_ERROR: + fprintf(stderr, "Error %s", ssh_get_error(session)); + free(hash); + return -1; + } + + free(hash); + return 0; +} +@endcode + +@see ssh_connect +@see ssh_disconnect +@see ssh_get_error +@see ssh_get_error_code +@see ssh_get_pubkey_hash +@see ssh_is_server_known +@see ssh_write_knownhost + + +@subsection auth Authenticating the user + +The authentication process is the way a service provider can identify a +user and verify his/her identity. The authorization process is about enabling +the authenticated user the access to ressources. In SSH, the two concepts +are linked. After authentication, the server can grant the user access to +several ressources such as port forwarding, shell, sftp subsystem, and so on. + +libssh supports several methods of authentication: + - "none" method. This method allows to get the available authentications + methods. It also gives the server a chance to authenticate the user with + just his/her login. Some very old hardware uses this feature to fallback + the user on a "telnet over SSH" style of login. + - password method. A password is sent to the server, which accepts it or not. + - keyboard-interactive method. The server sends several challenges to the + user, who must answer correctly. This makes possible the authentication + via a codebook for instance ("give code at 23:R on page 3"). + - public key method. The host knows the public key of the user, and the + user must prove he knows the associated private key. This can be done + manually, or delegated to the SSH agent as we'll see later. + +All these methods can be combined. You can for instance force the user to +authenticate with at least two of the authentication methods. In that case, +one speaks of "Partial authentication". A partial authentication is a +response from authentication functions stating that your credential was +accepted, but yet another one is required to get in. + +The example below shows an authentication with password: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session; + int rc; + char *password; + + // Open session and set options + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + // Connect to server + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + ssh_free(my_ssh_session); + exit(-1); + } + + // Verify the server's identity + // For the source code of verify_knowhost(), check previous example + if (verify_knownhost(my_ssh_session) < 0) + { + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + // Authenticate ourselves + password = getpass("Password: "); + rc = ssh_userauth_password(my_ssh_session, NULL, password); + if (rc != SSH_AUTH_SUCCESS) + { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(my_ssh_session)); + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + +@see @ref authentication_details + + +@subsection using_ssh Doing something + +At this point, the authenticity of both server and client is established. +Time has come to take advantage of the many possibilities offered by the SSH +protocol: execute a remote command, open remote shells, transfer files, +forward ports, etc. + +The example below shows how to execute a remote command: + +@code +int show_remote_processes(ssh_session session) +{ + ssh_channel channel; + int rc; + char buffer[256]; + unsigned int nbytes; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (write(1, buffer, nbytes) != nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +@see @ref opening_shell +@see @ref remote_command +@see @ref sftp_subsystem +@see @ref scp_subsystem + + +@subsection errors Handling the errors + +All the libssh functions which return an error value also set an English error message +describing the problem. + +Error values are typically SSH_ERROR for integer values, or NULL for pointers. + +The function ssh_get_error() returns a pointer to the static error message. + +ssh_error_code() returns the error code number : SSH_NO_ERROR, +SSH_REQUEST_DENIED, SSH_INVALID_REQUEST, SSH_CONNECTION_LOST, SSH_FATAL, +or SSH_INVALID_DATA. SSH_REQUEST_DENIED means the ssh server refused your +request, but the situation is recoverable. The others mean something happened +to the connection (some encryption problems, server problems, ...). +SSH_INVALID_REQUEST means the library got some garbage from server, but +might be recoverable. SSH_FATAL means the connection has an important +problem and isn't probably recoverable. + +Most of time, the error returned are SSH_FATAL, but some functions +(generaly the ssh_request_xxx ones) may fail because of server denying request. +In these cases, SSH_REQUEST_DENIED is returned. + +ssh_get_error() and ssh_get_error_code() take a ssh_session as a parameter. +That's for thread safety, error messages that can be attached to a session +aren't static anymore. Any error that happens during ssh_options_xxx() +or ssh_connect() (i.e., outside of any session) can be retrieved by +giving NULL as argument. + +The SFTP subsystem has its own error codes, in addition to libssh ones. + + +*/ diff --git a/libssh/doc/introduction.dox b/libssh/doc/introduction.dox new file mode 100644 index 00000000..cd786497 --- /dev/null +++ b/libssh/doc/introduction.dox @@ -0,0 +1,49 @@ +/** +@page libssh_tutorial The Tutorial +@section introduction Introduction + +libssh is a C library that enables you to write a program that uses the +SSH protocol. With it, you can remotely execute programs, transfer +files, or use a secure and transparent tunnel for your remote programs. +The SSH protocol is encrypted, ensures data integrity, and provides strong +means of authenticating both the server of the client. The library hides +a lot of technical details from the SSH protocol, but this does not +mean that you should not try to know about and understand these details. + +libssh is a Free Software / Open Source project. The libssh library +is distributed under LGPL license. The libssh project has nothing to do with +"libssh2", which is a completly different and independant project. + +libssh can run on top of either libgcrypt or libcrypto, +two general-purpose cryptographic libraries. + +This tutorial concentrates for its main part on the "client" side of libssh. +To learn how to accept incoming SSH connexions (how to write a SSH server), +you'll have to jump to the end of this document. + +This tutorial describes libssh version 0.5.0. This version is a little different +from the 0.4.X series. However, the examples should work with +little changes on versions like 0.4.2 and later. + + +Table of contents: + +@subpage libssh_tutor_guided_tour + +@subpage libssh_tutor_authentication + +@subpage libssh_tutor_shell + +@subpage libssh_tutor_command + +@subpage libssh_tutor_sftp + +@subpage libssh_tutor_scp + +@subpage libssh_tutor_forwarding + +@subpage libssh_tutor_threads + +@subpage libssh_tutor_todo + +*/ diff --git a/libssh/doc/linking.dox b/libssh/doc/linking.dox new file mode 100644 index 00000000..16dfab98 --- /dev/null +++ b/libssh/doc/linking.dox @@ -0,0 +1,24 @@ +/** + +@page libssh_linking The Linking HowTo + +@section dynamic Dynamic Linking + +On UNIX and Windows systems its the same, you need at least the libssh.h +header file and the libssh shared library. + +@section static Static Linking + +@warning The libssh library is licensed under the LGPL! Make sure you +understand what this means to your codebase if you want to distribute +binaries and link statically against LGPL code! + +On UNIX systems linking against the static version of the library is the +same as linking against the shared library. Both have the same name. Some +build system require to use the full path to the static library. + +On Windows you need to define LIBSSH_STATIC in the compiler command +line. This is required cause the dynamic library needs to specify the +dllimport attribute. + +*/ diff --git a/libssh/doc/mainpage.dox b/libssh/doc/mainpage.dox new file mode 100644 index 00000000..fc65e413 --- /dev/null +++ b/libssh/doc/mainpage.dox @@ -0,0 +1,211 @@ +/** + +@mainpage + +This is the online reference for developing with the libssh library. It +documents the libssh C API and the C++ wrapper. + +@section main-linking Linking + +We created a small howto how to link libssh against your application, read +@subpage libssh_linking. + +@section main-tutorial Tutorial + +You should start by reading @subpage libssh_tutorial, then reading the documentation of +the interesting functions as you go. + +@section main-features Features + +The libssh library provides: + + - Full C library functions for manipulating a client-side SSH connection + - SSH2 and SSH1 protocol compliant + - Fully configurable sessions + - Server support + - SSH agent authentication support + - Support for AES-128, AES-192, AES-256, Blowfish, 3DES in CBC mode, and AES in CTR mode + - Supports OpenSSL and GCrypt + - Use multiple SSH connections in a same process, at same time + - Use multiple channels in the same connection + - Thread safety when using different sessions at same time + - POSIX-like SFTP (Secure File Transfer) implementation with openssh extension support + - SCP implementation + - Large file system support (files bigger than 4GB) + - RSA and DSS server public key supported + - Compression support (with zlib) + - Public key (RSA and DSS), password and keyboard-interactive authentication + - Full poll()/WSAPoll() support and a poll-emulation for Win32. + - Runs and tested under x86_64, x86, ARM, Sparc32, PPC under Linux, BSD, MacOSX, Solaris and Windows + +@section main-copyright Copyright Policy + +libssh is a project with distributed copyright ownership, which means we prefer +the copyright on parts of libssh to be held by individuals rather than +corporations if possible. There are historical legal reasons for this, but one +of the best ways to explain it is that it’s much easier to work with +individuals who have ownership than corporate legal departments if we ever need +to make reasonable compromises with people using and working with libssh. + +We track the ownership of every part of libssh via git, our source code control +system, so we know the provenance of every piece of code that is committed to +libssh. + +So if possible, if you’re doing libssh changes on behalf of a company who +normally owns all the work you do please get them to assign personal copyright +ownership of your changes to you as an individual, that makes things very easy +for us to work with and avoids bringing corporate legal departments into the +picture. + +If you can’t do this we can still accept patches from you owned by your +employer under a standard employment contract with corporate copyright +ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux Kernel +community, so it should be very easy to get a sign off from your corporate +legal department. The only changes we’ve made are to accommodate the license we +use, which is LGPLv2 (or later) whereas the Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + +@verbatim +libssh Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project 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 the option of the project) any later version. + +http://www.gnu.org/licenses/lgpl-2.1.html +@endverbatim + +We will maintain a copy of that email as a record that you have the rights to +contribute code to libssh under the required licenses whilst working for the +company where the email came from. + +Then when sending in a patch via the normal mechanisms described above, add a +line that states: + +@verbatim + Signed-off-by: Random J Developer +@endverbatim + +using your real name and the email address you sent the original email you used +to send the libssh Developer’s Certificate of Origin to us (sorry, no +pseudonyms or anonymous contributions.) + +That’s it! Such code can then quite happily contain changes that have copyright +messages such as: + +@verbatim + (c) Example Corporation. +@endverbatim + +and can be merged into the libssh codebase in the same way as patches from any +other individual. You don’t need to send in a copy of the libssh Developer’s +Certificate of Origin for each patch, or inside each patch. Just the sign-off +message is all that is required once we’ve received the initial email. + +Have fun and happy libssh hacking! + +The libssh Team + +@section main-rfc Internet standard + +@subsection main-rfc-secsh Secure Shell (SSH) + +The following RFC documents described SSH-2 protcol as an Internet standard. + + - RFC 4250, + The Secure Shell (SSH) Protocol Assigned Numbers + - RFC 4251, + The Secure Shell (SSH) Protocol Architecture + - RFC 4252, + The Secure Shell (SSH) Authentication Protocol + - RFC 4253, + The Secure Shell (SSH) Transport Layer Protocol + - RFC 4254, + The Secure Shell (SSH) Connection Protocol + - RFC 4255, + Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints + - RFC 4256, + Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) + - RFC 4335, + The Secure Shell (SSH) Session Channel Break Extension + - RFC 4344, + The Secure Shell (SSH) Transport Layer Encryption Modes + - RFC 4345, + Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol + +It was later modified and expanded by the following RFCs. + + - RFC 4419, + Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer + Protocol + - RFC 4432, + RSA Key Exchange for the Secure Shell (SSH) Transport Layer Protocol + - RFC 4462, + Generic Security Service Application Program Interface (GSS-API) + Authentication and Key Exchange for the Secure Shell (SSH) Protocol + - RFC 4716, + The Secure Shell (SSH) Public Key File Format + - RFC 5656, + Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer + +Interesting cryptography documents: + + - PKCS #11, PKCS #11 reference documents, describing interface with smartcards. + +@subsection main-rfc-sftp Secure Shell File Transfer Protocol (SFTP) + +The protocol is not an Internet standard but it is still widely implemented. +OpenSSH and most other implementation implement Version 3 of the protocol. We +do the same in libssh. + + - + draft-ietf-secsh-filexfer-02.txt, + SSH File Transfer Protocol + +@subsection main-rfc-extensions Secure Shell Extensions + +The OpenSSH project has defined some extensions to the protocol. We support some of +them like the statvfs calls in SFTP or the ssh-agent. + + - + OpenSSH's deviations and extensions + - + OpenSSH's ssh-agent + - + OpenSSH's pubkey certificate authentication + +*/ diff --git a/libssh/doc/scp.dox b/libssh/doc/scp.dox new file mode 100644 index 00000000..1e7db780 --- /dev/null +++ b/libssh/doc/scp.dox @@ -0,0 +1,268 @@ +/** +@page libssh_tutor_scp Chapter 6: The SCP subsystem +@section scp_subsystem The SCP subsystem + +The SCP subsystem has far less functionnality than the SFTP subsystem. +However, if you only need to copy files from and to the remote system, +it does its job. + + +@subsection scp_session Opening and closing a SCP session + +Like in the SFTP subsystem, you don't handle the SSH channels directly. +Instead, you open a "SCP session". + +When you open your SCP session, you have to choose between read or write mode. +You can't do both in the same session. So you specify either SSH_SCP_READ or +SSH_SCP_WRITE as the second parameter of function ssh_scp_new(). + +Another important mode flag for opening your SCP session is SSH_SCP_RECURSIVE. +When you use SSH_SCP_RECURSIVE, you declare that you are willing to emulate +the behaviour of "scp -r" command in your program, no matter it is for +reading or for writing. + +Once your session is created, you initialize it with ssh_scp_init(). When +you have finished transferring files, you terminate the SCP connection with +ssh_scp_close(). Finally, you can dispose the SCP connection with +ssh_scp_free(). + +The example below does the maintenance work to open a SCP connection for writing in +recursive mode: + +@code +int scp_write(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, "."); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} +@endcode + +The example below shows how to open a connection to read a single file: + +@code +int scp_read(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_READ, "helloworld/helloworld.txt"); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} + +@endcode + + +@subsection scp_write Creating files and directories + +You create directories with ssh_scp_push_directory(). In recursive mode, +you are placed in this directory once it is created. If the directory +already exists and if you are in recursive mode, you simply enter that +directory. + +Creating files is done in two steps. First, you prepare the writing with +ssh_scp_push_file(). Then, you write the data with ssh_scp_write(). +The length of the data to write must be identical between both function calls. +There's no need to "open" nor "close" the file, this is done automatically +on the remote end. If the file already exists, it is overwritten and truncated. + +The following example creates a new directory named "helloworld/", then creates +a file named "helloworld.txt" in that directory: + +@code +int scp_helloworld(ssh_session session, ssh_scp scp) +{ + int rc; + const char *helloworld = "Hello, world!\n"; + int length = strlen(helloworld); + + rc = ssh_scp_push_directory(scp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't create remote directory: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_push_file + (scp, "helloworld.txt", length, S_IRUSR | S_IWUSR); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't open remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_write(scp, helloworld, length); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't write to remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection scp_recursive_write Copying full directory trees to the remote server + +Let's say you want to copy the following tree of files to the remote site: + +@verbatim + +-- file1 + +-- B --+ + | +-- file2 +-- A --+ + | +-- file3 + +-- C --+ + +-- file4 +@endverbatim + +You would do it that way: + - open the session in recursive mode + - enter directory A + - enter its subdirectory B + - create file1 in B + - create file2 in B + - leave directory B + - enter subdirectory C + - create file3 in C + - create file4 in C + - leave directory C + - leave directory A + +To leave a directory, call ssh_scp_leave_directory(). + + +@subsection scp_read Reading files and directories + + +To receive files, you pull requests from the other side with ssh_scp_pull_request(). +If this function returns SSH_SCP_REQUEST_NEWFILE, then you must get ready for +the reception. You can get the size of the data to receive with ssh_scp_request_get_size() +and allocate a buffer accordingly. When you are ready, you accept the request with +ssh_scp_accept_request(), then read the data with ssh_scp_read(). + +The following example receives a single file. The name of the file to +receive has been given earlier, when the scp session was opened: + +@code +int scp_receive(ssh_session session, ssh_scp scp) +{ + int rc; + int size, mode; + char *filename, *buffer; + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_NEWFILE) + { + fprintf(stderr, "Error receiving information about file: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("Receiving file %s, size %d, permisssions 0%o\n", + filename, size, mode); + free(filename); + + buffer = malloc(size); + if (buffer == NULL) + { + fprintf(stderr, "Memory allocation error\n"); + return SSH_ERROR; + } + + ssh_scp_accept_request(scp); + rc = ssh_scp_read(scp, buffer, size); + if (rc == SSH_ERROR) + { + fprintf(stderr, "Error receiving file data: %s\n", + ssh_get_error(session)); + free(buffer); + return rc; + } + printf("Done\n"); + + write(1, buffer, size); + free(buffer); + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_EOF) + { + fprintf(stderr, "Unexpected request: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + return SSH_OK; +} +@endcode + +In this example, since we just requested a single file, we expect ssh_scp_request() +to return SSH_SCP_REQUEST_NEWFILE first, then SSH_SCP_REQUEST_EOF. That's quite a +naive approach; for example, the remote server might send a warning as well +(return code SSH_SCP_REQUEST_WARNING) and the example would fail. A more comprehensive +reception program would receive the requests in a loop and analyze them carefully +until SSH_SCP_REQUEST_EOF has been received. + + +@subsection scp_recursive_read Receiving full directory trees from the remote server + +If you opened the SCP session in recursive mode, the remote end will be +telling you when to change directory. + +In that case, when ssh_scp_pull_request() answers +SSH_SCP_REQUEST_NEWDIRECTORY, you should make that local directory (if +it does not exist yet) and enter it. When ssh_scp_pull_request() answers +SSH_SCP_REQUEST_ENDDIRECTORY, you should leave the current directory. + +*/ diff --git a/libssh/doc/sftp.dox b/libssh/doc/sftp.dox new file mode 100644 index 00000000..97f9afbb --- /dev/null +++ b/libssh/doc/sftp.dox @@ -0,0 +1,415 @@ +/** +@page libssh_tutor_sftp Chapter 5: The SFTP subsystem +@section sftp_subsystem The SFTP subsystem + +SFTP stands for "Secure File Transfer Protocol". It enables you to safely +transfer files between the local and the remote computer. It reminds a lot +of the old FTP protocol. + +SFTP is a rich protocol. It lets you do over the network almost everything +that you can do with local files: + - send files + - modify only a portion of a file + - receive files + - receive only a portion of a file + - get file owner and group + - get file permissions + - set file owner and group + - set file permissions + - remove files + - rename files + - create a directory + - remove a directory + - retrieve the list of files in a directory + - get the target of a symbolic link + - create symbolic links + - get information about mounted filesystems. + +The current implemented version of the SFTP protocol is version 3. All functions +aren't implemented yet, but the most important are. + + +@subsection sftp_session Opening and closing a SFTP session + +Unlike with remote shells and remote commands, when you use the SFTP subsystem, +you don't handle directly the SSH channels. Instead, you open a "SFTP session". + +The function sftp_new() creates a new SFTP session. The function sftp_init() +initializes it. The function sftp_free() deletes it. + +As you see, all the SFTP-related functions start with the "sftp_" prefix +instead of the usual "ssh_" prefix. + +The example below shows how to use these functions: + +@code +#include + +int sftp_helloworld(ssh_session session) +{ + sftp_session sftp; + int rc; + + sftp = sftp_new(session); + if (sftp == NULL) + { + fprintf(stderr, "Error allocating SFTP session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = sftp_init(sftp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing SFTP session: %s.\n", + sftp_get_error(sftp)); + sftp_free(sftp); + return rc; + } + + ... + + sftp_free(sftp); + return SSH_OK; +} +@endcode + + +@subsection sftp_errors Analyzing SFTP errors + +In case of a problem, the function sftp_get_error() returns a SFTP-specific +error number, in addition to the regular SSH error number returned by +ssh_get_error_number(). + +Possible errors are: + - SSH_FX_OK: no error + - SSH_FX_EOF: end-of-file encountered + - SSH_FX_NO_SUCH_FILE: file does not exist + - SSH_FX_PERMISSION_DENIED: permission denied + - SSH_FX_FAILURE: generic failure + - SSH_FX_BAD_MESSAGE: garbage received from server + - SSH_FX_NO_CONNECTION: no connection has been set up + - SSH_FX_CONNECTION_LOST: there was a connection, but we lost it + - SSH_FX_OP_UNSUPPORTED: operation not supported by libssh yet + - SSH_FX_INVALID_HANDLE: invalid file handle + - SSH_FX_NO_SUCH_PATH: no such file or directory path exists + - SSH_FX_FILE_ALREADY_EXISTS: an attempt to create an already existing file or directory has been made + - SSH_FX_WRITE_PROTECT: write-protected filesystem + - SSH_FX_NO_MEDIA: no media was in remote drive + + +@subsection sftp_mkdir Creating a directory + +The function sftp_mkdir() tahes the "SFTP session" we juste created as +its first argument. It also needs the name of the file to create, and the +desired permissions. The permissions are the same as for the usual mkdir() +function. To get a comprehensive list of the available permissions, use the +"man 2 stat" command. The desired permissions are combined with the remote +user's mask to determine the effective permissions. + +The code below creates a directory named "helloworld" in the current directory that +can be read and written only by its owner: + +@code +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int rc; + + rc = sftp_mkdir(sftp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + if (sftp_get_error(sftp) != SSH_FX_FILE_ALREADY_EXISTS) + { + fprintf(stderr, "Can't create directory: %s\n", + ssh_get_error(session)); + return rc; + } + } + + ... + + return SSH_OK; +} +@endcode + +Unlike its equivalent in the SCP subsystem, this function does NOT change the +current directory to the newly created subdirectory. + + +@subsection sftp_write Copying a file to the remote computer + +You handle the contents of a remote file just like you would do with a +local file: you open the file in a given mode, move the file pointer in it, +read or write data, and close the file. + +The sftp_open() function is very similar to the regular open() function, +excepted that it returns a file handle of type sftp_file. This file handle +is then used by the other file manipulation functions and remains valid +until you close the remote file with sftp_close(). + +The example below creates a new file named "helloworld.txt" in the +newly created "helloworld" directory. If the file already exists, it will +be truncated. It then writes the famous "Hello, World!" sentence to the +file, followed by a new line character. Finally, the file is closed: + +@code +#include +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int access_type = O_WRONLY | O_CREAT | O_TRUNC; + sftp_file file; + const char *helloworld = "Hello, World!\n"; + int length = strlen(helloworld); + int rc, nwritten; + + ... + + file = sftp_open(sftp, "helloworld/helloworld.txt", + access_type, S_IRWXU); + if (file == NULL) + { + fprintf(stderr, "Can't open file for writing: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + nwritten = sftp_write(file, helloworld, length); + if (nwritten != length) + { + fprintf(stderr, "Can't write data to file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + rc = sftp_close(file); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close the written file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection sftp_read Reading a file from the remote computer + +The nice thing with reading a file over the network through SFTP is that it +can be done both in a synchronous way or an asynchronous way. If you read the file +asynchronously, your program can do something else while it waits for the +results to come. + +Synchronous read is done with sftp_read(). + +The following example prints the contents of remote file "/etc/profile". For +each 1024 bytes of information read, it waits until the end of the read operation: + +@code +int sftp_read_sync(ssh_session session, sftp_session sftp) +{ + int access_type; + sftp_file file; + char buffer[1024]; + int nbytes, rc; + + access_type = O_RDONLY; + file = sftp_open(sftp, "/etc/profile", + access_type, 0); + if (file == NULL) + { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + nbytes = sftp_read(file, buffer, sizeof(buffer)); + while (nbytes > 0) + { + if (write(1, buffer, nbytes) != nbytes) + { + sftp_close(file); + return SSH_ERROR; + } + nbytes = sftp_read(file, buffer, sizeof(buffer)); + } + + if (nbytes < 0) + { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + rc = sftp_close(file); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + +Asynchronous read is done in two steps, first sftp_async_read_begin(), which +returns a "request handle", and then sftp_async_read(), which uses that request handle. +If the file has been opened in nonblocking mode, then sftp_async_read() +might return SSH_AGAIN, which means that the request hasn't completed yet +and that the function should be called again later on. Otherwise, +sftp_async_read() waits for the data to come. To open a file in nonblocking mode, +call sftp_file_set_nonblocking() right after you opened it. Default is blocking mode. + +The example below reads a very big file in asynchronous, nonblocking, mode. Each +time the data are not ready yet, a counter is incrementer. + +@code +int sftp_read_async(ssh_session session, sftp_session sftp) +{ + int access_type; + sftp_file file; + char buffer[1024]; + int async_request; + int nbytes; + long counter; + int rc; + + access_type = O_RDONLY; + file = sftp_open(sftp, "some_very_big_file", + access_type, 0); + if (file == NULL) + { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + sftp_file_set_nonblocking(file); + + async_request = sftp_async_read_begin(file, sizeof(buffer)); + counter = 0L; + usleep(10000); + if (async_request >= 0) + nbytes = sftp_async_read(file, buffer, sizeof(buffer), + async_request); + else nbytes = -1; + while (nbytes > 0 || nbytes == SSH_AGAIN) + { + if (nbytes > 0) + { + write(1, buffer, nbytes); + async_request = sftp_async_read_begin(file, sizeof(buffer)); + } + else counter++; + usleep(10000); + if (async_request >= 0) + nbytes = sftp_async_read(file, buffer, sizeof(buffer), + async_request); + else nbytes = -1; + } + + if (nbytes < 0) + { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + printf("The counter has reached value: %ld\n", counter); + + rc = sftp_close(file); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + +@subsection sftp_ls Listing the contents of a directory + +The functions sftp_opendir(), sftp_readdir(), sftp_dir_eof(), +and sftp_closedir() enable to list the contents of a directory. +They use a new handle_type, "sftp_dir", which gives access to the +directory being read. + +In addition, sftp_readdir() returns a "sftp_attributes" which is a pointer +to a structure with informations about a directory entry: + - name: the name of the file or directory + - size: its size in bytes + - etc. + +sftp_readdir() might return NULL under two conditions: + - when the end of the directory has been met + - when an error occured + +To tell the difference, call sftp_dir_eof(). + +The attributes must be freed with sftp_attributes_free() when no longer +needed. + +The following example reads the contents of some remote directory: + +@code +int sftp_list_dir(ssh_session session, sftp_session sftp) +{ + sftp_dir dir; + sftp_attributes attributes; + int rc; + + dir = sftp_opendir(sftp, "/var/log"); + if (!dir) + { + fprintf(stderr, "Directory not opened: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + printf("Name Size Perms Owner\tGroup\n"); + + while ((attributes = sftp_readdir(sftp, dir)) != NULL) + { + printf("%-20s %10llu %.8o %s(%d)\t%s(%d)\n", + attributes->name, + (long long unsigned int) attributes->size, + attributes->permissions, + attributes->owner, + attributes->uid, + attributes->group, + attributes->gid); + + sftp_attributes_free(attributes); + } + + if (!sftp_dir_eof(dir)) + { + fprintf(stderr, "Can't list directory: %s\n", + ssh_get_error(session)); + sftp_closedir(dir); + return SSH_ERROR; + } + + rc = sftp_closedir(dir); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close directory: %s\n", + ssh_get_error(session)); + return rc; + } +} +@endcode + +*/ diff --git a/libssh/doc/shell.dox b/libssh/doc/shell.dox new file mode 100644 index 00000000..35fdfc82 --- /dev/null +++ b/libssh/doc/shell.dox @@ -0,0 +1,361 @@ +/** +@page libssh_tutor_shell Chapter 3: Opening a remote shell +@section opening_shell Opening a remote shell + +We already mentioned that a single SSH connection can be shared +between several "channels". Channels can be used for different purposes. + +This chapter shows how to open one of these channels, and how to use it to +start a command interpreter on a remote computer. + + +@subsection open_channel Opening and closing a channel + +The ssh_channel_new() function creates a channel. It returns the channel as +a variable of type ssh_channel. + +Once you have this channel, you open a SSH session that uses it with +ssh_channel_open_session(). + +Once you don't need the channel anymore, you can send an end-of-file +to it with ssh_channel_close(). At this point, you can destroy the channel +with ssh_channel_free(). + +The code sample below achieves these tasks: + +@code +int shell_session(ssh_session session) +{ + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + ... + + ssh_channel_close(channel); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + + +@subsection interactive Interactive and non-interactive sessions + +A "shell" is a command interpreter. It is said to be "interactive" +if there is a human user typing the commands, one after the +other. The contrary, a non-interactive shell, is similar to +the execution of commands in the background: there is no attached +terminal. + +If you plan using an interactive shell, you need to create a +pseud-terminal on the remote side. A remote terminal is usually referred +to as a "pty", for "pseudo-teletype". The remote processes won't see the +difference with a real text-oriented terminal. + +If needed, you request the pty with the function ssh_channel_request_pty(). +Then you define its dimensions (number of rows and columns) +with ssh_channel_change_pty_size(). + +Be your session interactive or not, the next step is to request a +shell with ssh_channel_request_shell(). + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + ... + + return rc; +} +@endcode + + +@subsection read_data Displaying the data sent by the remote computer + +In your program, you will usually need to receive all the data "displayed" +into the remote pty. You will usually analyse, log, or display this data. + +ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest +way to read data from a channel. If you only need to read from a single +channel, they should be enough. + +The example below shows how to wait for remote data using ssh_channel_read(): + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + char buffer[256]; + int nbytes; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + return SSH_ERROR; + + if (nbytes > 0) + write(1, buffer, nbytes); + } + + return rc; +} +@endcode + +Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for +remote data to be ready. It returns immediately. + +If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop, +you should use a "passive wait" function like usleep(3) in the same +loop. Otherwise, your program will consume all the CPU time, and your +computer might become unresponsive. + + +@subsection write_data Sending user input to the remote computer + +User's input is sent to the remote site with ssh_channel_write(). + +The following example shows how to combine a nonblocking read from a SSH +channel with a nonblocking read from the keyboard. The local input is then +sent to the remote computer: + +@code +/* Under Linux, this function determines whether a key has been pressed. + Under Windows, it is a standard function, so you need not redefine it. +*/ +int kbhit() +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + return select(1, &fds, NULL, NULL, &tv); +} + +/* A very simple terminal emulator: + - print data received from the remote computer + - send keyboard input to the remote computer +*/ +int interactive_shell_session(ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + + if (!kbhit()) + { + usleep(50000L); // 0.05 second + continue; + } + + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + return rc; +} +@endcode + +Of course, this is a poor terminal emulator, since the echo from the keys +pressed should not be done locally, but should be done by the remote side. +Also, user's input should not be sent once "Enter" key is pressed, but +immediately after each key is pressed. This can be accomplished +by setting the local terminal to "raw" mode with the cfmakeraw(3) function. +cfmakeraw() is a standard function under Linux, on other systems you can +recode it with: + +@code +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +@endcode + +If you are not using a local terminal, but some kind of graphical +environment, the solution to this kind of "echo" problems will be different. + + +@subsection select_loop A more elaborate way to get the remote data + +*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore, + since libssh is about to provide an easier system for asynchronous + communications. This subsection should be removed then. *** + +ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple, +but they are not adapted when you expect data from more than one SSH channel, +or from other file descriptors. Last example showed how getting data from +the standard input (the keyboard) at the same time as data from the SSH +channel was complicated. The functions ssh_select() and ssh_channel_select() +provide a more elegant way to wait for data coming from many sources. + +The functions ssh_select() and ssh_channel_select() remind of the standard +UNIX select(2) function. The idea is to wait for "something" to happen: +incoming data to be read, outcoming data to block, or an exception to +occur. Both these functions do a "passive wait", i.e. you can safely use +them repeatedly in a loop, it will not consume exaggerate processor time +and make your computer unresponsive. It is quite common to use these +functions in your application's main loop. + +The difference between ssh_select() and ssh_channel_select() is that +ssh_channel_select() is simpler, but allows you only to watch SSH channels. +ssh_select() is more complete and enables watching regular file descriptors +as well, in the same function call. + +Below is an example of a function that waits both for remote SSH data to come, +as well as standard input from the keyboard: + +@code +int interactive_shell_session(ssh_session session, ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + struct timeval timeout; + ssh_channel in_channels[2], out_channels[2]; + fd_set fds; + int maxfd; + + timeout.tv_sec = 30; + timeout.tv_usec = 0; + in_channels[0] = channel; + in_channels[1] = NULL; + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(ssh_get_fd(session), &fds); + maxfd = ssh_get_fd(session) + 1; + + ssh_select(in_channels, out_channels, maxfd, &fds, &timeout); + + if (out_channels[0] != NULL) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + if (FD_ISSET(0, &fds)) + { + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nbytes != nwritten) return SSH_ERROR; + } + } + } + + return rc; +} +@endcode + + +@subsection x11 Using graphical applications on the remote side + +If your remote application is graphical, you can forward the X11 protocol to +your local computer. + +To do that, you first declare that you accept X11 connections with +ssh_channel_accept_x11(). Then you create the forwarding tunnel for +the X11 protocol with ssh_channel_request_x11(). + +The following code performs channel initialization and shell session +opening, and handles a parallel X11 connection: + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + ssh_channel x11channel; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + /* Read the data sent by the remote computer here */ + ... +} +@endcode + +Don't forget to set the $DISPLAY environment variable on the remote +side, or the remote applications won't try using the X11 tunnel: + +@code +$ export DISPLAY=:0 +$ xclock & +@endcode + +*/ diff --git a/libssh/doc/tbd.dox b/libssh/doc/tbd.dox new file mode 100644 index 00000000..921337ed --- /dev/null +++ b/libssh/doc/tbd.dox @@ -0,0 +1,14 @@ +/** +@page libssh_tutor_todo To be done + +*** To be written *** + +@section sshd Writing a libssh-based server + +*** To be written *** + +@section cpp The libssh C++ wrapper + +*** To be written *** + +*/ diff --git a/libssh/doc/threading.dox b/libssh/doc/threading.dox new file mode 100644 index 00000000..a11c82f7 --- /dev/null +++ b/libssh/doc/threading.dox @@ -0,0 +1,65 @@ +/** +@page libssh_tutor_threads Chapter 8: Threads with libssh +@section threads_with_libssh How to use libssh with threads + +libssh may be used in multithreaded applications, but under several conditions : + - Threading must be initialized during the initialization of libssh. This + initialization must be done outside of any threading context. + - If pthreads is being used by your application (or your framework's backend), + you must link with libssh_threads dynamic library and initialize + threading with the ssh_threads_pthreads threading object. + - If an other threading library is being used by your application, you must + implement all the methods of the ssh_threads_callbacks_struct structure + and initialize libssh with it. + - At all times, you may use different sessions inside threads, make parallel + connections, read/write on different sessions and so on. You *cannot* use a + single session (or channels for a single session) in several threads at the same + time. This will most likely lead to internal state corruption. This limitation is + being worked out and will maybe disappear later. + +@subsection threads_init Initialization of threads + +To initialize threading, you must first select the threading model you want to +use, using ssh_threads_set_callbacks(), then call ssh_init(). + +@code +#include +... +ssh_threads_set_callbacks(ssh_threads_get_noop()); +ssh_init(); +@endcode + +ssh_threads_noop is the threading structure that does nothing. It's the +threading callbacks being used by default when you're not using threading. + +@subsection threads_pthread Using libpthread with libssh + +If your application is using libpthread, you may simply use the libpthread +threading backend: + +@code +#include +... +ssh_threads_set_callbacks(ssh_threads_get_pthread()); +ssh_init(); +@endcode + +However, you must be sure to link with the library ssh_threads. If +you're using gcc, you must use the commandline +@code +gcc -o output input.c -lssh -lssh_threads +@endcode + + +@subsection threads_other Using another threading library + +You must find your way in the ssh_threads_callbacks_struct structure. You must +implement the following methods : +- mutex_lock +- mutex_unlock +- mutex_init +- mutex_destroy +- thread_id + +Good luck ! +*/ diff --git a/libssh/examples/CMakeLists.txt b/libssh/examples/CMakeLists.txt new file mode 100644 index 00000000..5513b758 --- /dev/null +++ b/libssh/examples/CMakeLists.txt @@ -0,0 +1,57 @@ +project(libssh-examples C CXX) + +set(examples_SRCS + authentication.c + knownhosts.c + connect_ssh.c +) + +include_directories( + ${LIBSSH_PUBLIC_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR} +) + +if (LINUX) + add_executable(libssh_scp libssh_scp.c ${examples_SRCS}) + target_link_libraries(libssh_scp ${LIBSSH_SHARED_LIBRARY}) + + add_executable(scp_download scp_download.c ${examples_SRCS}) + target_link_libraries(scp_download ${LIBSSH_SHARED_LIBRARY}) + + add_executable(samplessh sample.c ${examples_SRCS}) + target_link_libraries(samplessh ${LIBSSH_SHARED_LIBRARY}) + + add_executable(sshnetcat sshnetcat.c ${examples_SRCS}) + target_link_libraries(sshnetcat ${LIBSSH_SHARED_LIBRARY}) + + if (WITH_SFTP) + add_executable(samplesftp samplesftp.c ${examples_SRCS}) + target_link_libraries(samplesftp ${LIBSSH_SHARED_LIBRARY}) + endif (WITH_SFTP) + + if (WITH_SERVER) + add_executable(samplesshd samplesshd.c) + target_link_libraries(samplesshd ${LIBSSH_SHARED_LIBRARY}) + + add_executable(samplesshd-kbdint samplesshd-kbdint.c) + target_link_libraries(samplesshd-kbdint ${LIBSSH_SHARED_LIBRARY}) + + if (HAVE_LIBUTIL) + add_executable(samplesshd-tty samplesshd-tty.c) + target_link_libraries(samplesshd-tty ${LIBSSH_SHARED_LIBRARY} util) + endif (HAVE_LIBUTIL) + + endif (WITH_SERVER) +endif (LINUX) + +add_executable(exec exec.c ${examples_SRCS}) +target_link_libraries(exec ${LIBSSH_SHARED_LIBRARY}) + +add_executable(senddata senddata.c ${examples_SRCS}) +target_link_libraries(senddata ${LIBSSH_SHARED_LIBRARY}) + +add_executable(libsshpp libsshpp.cpp) +target_link_libraries(libsshpp ${LIBSSH_SHARED_LIBRARY}) + +add_executable(libsshpp_noexcept libsshpp_noexcept.cpp) +target_link_libraries(libsshpp_noexcept ${LIBSSH_SHARED_LIBRARY}) diff --git a/libssh/examples/authentication.c b/libssh/examples/authentication.c new file mode 100644 index 00000000..0e749e54 --- /dev/null +++ b/libssh/examples/authentication.c @@ -0,0 +1,167 @@ +/* + * authentication.c + * This file contains an example of how to do an authentication to a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include +#include + +#include +#include "examples_common.h" + +int authenticate_kbdint(ssh_session session, const char *password) { + int err; + + err = ssh_userauth_kbdint(session, NULL, NULL); + while (err == SSH_AUTH_INFO) { + const char *instruction; + const char *name; + char buffer[128]; + int i, n; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + n = ssh_userauth_kbdint_getnprompts(session); + + if (name && strlen(name) > 0) { + printf("%s\n", name); + } + + if (instruction && strlen(instruction) > 0) { + printf("%s\n", instruction); + } + + for (i = 0; i < n; i++) { + const char *answer; + const char *prompt; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, i, &echo); + if (prompt == NULL) { + break; + } + + if (echo) { + char *p; + + printf("%s", prompt); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + buffer[sizeof(buffer) - 1] = '\0'; + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + if (ssh_userauth_kbdint_setanswer(session, i, buffer) < 0) { + return SSH_AUTH_ERROR; + } + + memset(buffer, 0, strlen(buffer)); + } else { + if (password && strstr(prompt, "Password:")) { + answer = password; + } else { + buffer[0] = '\0'; + + if (ssh_getpass(prompt, buffer, sizeof(buffer), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + answer = buffer; + } + err = ssh_userauth_kbdint_setanswer(session, i, answer); + memset(buffer, 0, sizeof(buffer)); + if (err < 0) { + return SSH_AUTH_ERROR; + } + } + } + err=ssh_userauth_kbdint(session,NULL,NULL); + } + + return err; +} + +static void error(ssh_session session){ + fprintf(stderr,"Authentication failed: %s\n",ssh_get_error(session)); +} + +int authenticate_console(ssh_session session){ + int rc; + int method; + char password[128] = {0}; + char *banner; + + // Try to authenticate + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } + + method = ssh_auth_list(session); + while (rc != SSH_AUTH_SUCCESS) { + // Try to authenticate with public key first + if (method & SSH_AUTH_METHOD_PUBLICKEY) { + rc = ssh_userauth_autopubkey(session, NULL); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + + // Try to authenticate with keyboard interactive"; + if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = authenticate_kbdint(session, NULL); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + + if (ssh_getpass("Password: ", password, sizeof(password), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + + // Try to authenticate with password + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + memset(password, 0, sizeof(password)); + } + + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n",banner); + ssh_string_free_char(banner); + } + + return rc; +} diff --git a/libssh/examples/connect_ssh.c b/libssh/examples/connect_ssh.c new file mode 100644 index 00000000..c9e4ef6e --- /dev/null +++ b/libssh/examples/connect_ssh.c @@ -0,0 +1,67 @@ +/* + * connect_ssh.c + * This file contains an example of how to connect to a + * SSH server using libssh + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include "examples_common.h" +#include + +ssh_session connect_ssh(const char *host, const char *user,int verbosity){ + ssh_session session; + int auth=0; + + session=ssh_new(); + if (session == NULL) { + return NULL; + } + + if(user != NULL){ + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + ssh_free(session); + return NULL; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + ssh_free(session); + return NULL; + } + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if(ssh_connect(session)){ + fprintf(stderr,"Connection failed : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + if(verify_knownhost(session)<0){ + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + auth=authenticate_console(session); + if(auth==SSH_AUTH_SUCCESS){ + return session; + } else if(auth==SSH_AUTH_DENIED){ + fprintf(stderr,"Authentication failed\n"); + } else { + fprintf(stderr,"Error while authenticating : %s\n",ssh_get_error(session)); + } + ssh_disconnect(session); + ssh_free(session); + return NULL; +} diff --git a/libssh/examples/examples_common.h b/libssh/examples/examples_common.h new file mode 100644 index 00000000..13eb455c --- /dev/null +++ b/libssh/examples/examples_common.h @@ -0,0 +1,22 @@ +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ +#ifndef EXAMPLES_COMMON_H_ +#define EXAMPLES_COMMON_H_ + +#include +int authenticate_console(ssh_session session); +int authenticate_kbdint(ssh_session session, const char *password); +int verify_knownhost(ssh_session session); +ssh_session connect_ssh(const char *hostname, const char *user, int verbosity); + +#endif /* EXAMPLES_COMMON_H_ */ diff --git a/libssh/examples/exec.c b/libssh/examples/exec.c new file mode 100644 index 00000000..4d5e0c1a --- /dev/null +++ b/libssh/examples/exec.c @@ -0,0 +1,66 @@ +/* simple exec example */ +#include + +#include +#include "examples_common.h" + +int main(void) { + ssh_session session; + ssh_channel channel; + char buffer[256]; + int nbytes; + int rc; + + session = connect_ssh("localhost", NULL, 0); + if (session == NULL) { + ssh_finalize(); + return 1; + } + + channel = ssh_channel_new(session);; + if (channel == NULL) { + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + goto failed; + } + + rc = ssh_channel_request_exec(channel, "lsof"); + if (rc < 0) { + goto failed; + } + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) { + if (fwrite(buffer, 1, nbytes, stdout) != (unsigned int) nbytes) { + goto failed; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) { + goto failed; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 0; +failed: + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 1; +} diff --git a/libssh/examples/knownhosts.c b/libssh/examples/knownhosts.c new file mode 100644 index 00000000..37c0ba4e --- /dev/null +++ b/libssh/examples/knownhosts.c @@ -0,0 +1,98 @@ +/* + * knownhosts.c + * This file contains an example of how verify the identity of a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include +#include +#include + +#include +#include "examples_common.h" + +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + +int verify_knownhost(ssh_session session){ + char *hexa; + int state; + char buf[10]; + unsigned char *hash = NULL; + int hlen; + + state=ssh_is_server_known(session); + + hlen = ssh_get_pubkey_hash(session, &hash); + if (hlen < 0) { + return -1; + } + switch(state){ + case SSH_SERVER_KNOWN_OK: + break; /* ok */ + case SSH_SERVER_KNOWN_CHANGED: + fprintf(stderr,"Host key for server changed : server's one is now :\n"); + ssh_print_hexa("Public key hash",hash, hlen); + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"For security reason, connection will be stopped\n"); + return -1; + case SSH_SERVER_FOUND_OTHER: + fprintf(stderr,"The host key for this server was not found but an other type of key exists.\n"); + fprintf(stderr,"An attacker might change the default server key to confuse your client" + "into thinking the key does not exist\n" + "We advise you to rerun the client with -d or -r for more safety.\n"); + return -1; + case SSH_SERVER_FILE_NOT_FOUND: + fprintf(stderr,"Could not find known host file. If you accept the host key here,\n"); + fprintf(stderr,"the file will be automatically created.\n"); + /* fallback to SSH_SERVER_NOT_KNOWN behavior */ + case SSH_SERVER_NOT_KNOWN: + hexa = ssh_get_hexa(hash, hlen); + fprintf(stderr,"The server is unknown. Do you trust the host key ?\n"); + fprintf(stderr, "Public key hash: %s\n", hexa); + ssh_string_free_char(hexa); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)!=0){ + ssh_clean_pubkey_hash(&hash); + return -1; + } + fprintf(stderr,"This new key will be written on disk for further usage. do you agree ?\n"); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)==0){ + if (ssh_write_knownhost(session) < 0) { + ssh_clean_pubkey_hash(&hash); + fprintf(stderr, "error %s\n", strerror(errno)); + return -1; + } + } + + break; + case SSH_SERVER_ERROR: + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"%s",ssh_get_error(session)); + return -1; + } + ssh_clean_pubkey_hash(&hash); + return 0; +} diff --git a/libssh/examples/libssh_scp.c b/libssh/examples/libssh_scp.c new file mode 100644 index 00000000..d443f8f2 --- /dev/null +++ b/libssh/examples/libssh_scp.c @@ -0,0 +1,304 @@ +/* libssh_scp.c + * Sample implementation of a SCP client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +static char **sources; +static int nsources; +static char *destination; +static int verbosity=0; + +struct location { + int is_ssh; + char *user; + char *host; + char *path; + ssh_session session; + ssh_scp scp; + FILE *file; +}; + +enum { + READ, + WRITE +}; + +static void usage(const char *argv0){ + fprintf(stderr,"Usage : %s [options] [[user@]host1:]file1 ... \n" + " [[user@]host2:]destination\n" + "sample scp client - libssh-%s\n", +// "Options :\n", +// " -r : use RSA to verify host public key\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv){ + int i; + while((i=getopt(argc,argv,"v"))!=-1){ + switch(i){ + case 'v': + verbosity++; + break; + default: + fprintf(stderr,"unknown option %c\n",optopt); + usage(argv[0]); + return -1; + } + } + nsources=argc-optind-1; + if(nsources < 1){ + usage(argv[0]); + return -1; + } + sources=malloc((nsources + 1) * sizeof(char *)); + if(sources == NULL) + return -1; + for(i=0;ihost=location->user=NULL; + ptr=strchr(loc,':'); + if(ptr != NULL){ + location->is_ssh=1; + location->path=strdup(ptr+1); + *ptr='\0'; + ptr=strchr(loc,'@'); + if(ptr != NULL){ + location->host=strdup(ptr+1); + *ptr='\0'; + location->user=strdup(loc); + } else { + location->host=strdup(loc); + } + } else { + location->is_ssh=0; + location->path=strdup(loc); + } + return location; +} + +static int open_location(struct location *loc, int flag){ + if(loc->is_ssh && flag==WRITE){ + loc->session=connect_ssh(loc->host,loc->user,verbosity); + if(!loc->session){ + fprintf(stderr,"Couldn't connect to %s\n",loc->host); + return -1; + } + loc->scp=ssh_scp_new(loc->session,SSH_SCP_WRITE,loc->path); + if(!loc->scp){ + fprintf(stderr,"error : %s\n",ssh_get_error(loc->session)); + return -1; + } + if(ssh_scp_init(loc->scp)==SSH_ERROR){ + fprintf(stderr,"error : %s\n",ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + return -1; + } + return 0; + } else if(loc->is_ssh && flag==READ){ + loc->session=connect_ssh(loc->host, loc->user,verbosity); + if(!loc->session){ + fprintf(stderr,"Couldn't connect to %s\n",loc->host); + return -1; + } + loc->scp=ssh_scp_new(loc->session,SSH_SCP_READ,loc->path); + if(!loc->scp){ + fprintf(stderr,"error : %s\n",ssh_get_error(loc->session)); + return -1; + } + if(ssh_scp_init(loc->scp)==SSH_ERROR){ + fprintf(stderr,"error : %s\n",ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + return -1; + } + return 0; + } else { + loc->file=fopen(loc->path,flag==READ ? "r":"w"); + if(!loc->file){ + if(errno==EISDIR){ + if(chdir(loc->path)){ + fprintf(stderr,"Error changing directory to %s: %s\n",loc->path,strerror(errno)); + return -1; + } + return 0; + } + fprintf(stderr,"Error opening %s: %s\n",loc->path,strerror(errno)); + return -1; + } + return 0; + } + return -1; +} + +/** @brief copies files from source location to destination + * @param src source location + * @param dest destination location + * @param recursive Copy also directories + */ +static int do_copy(struct location *src, struct location *dest, int recursive){ + int size; + socket_t fd; + struct stat s; + int w,r; + char buffer[16384]; + int total=0; + int mode; + char *filename; + /* recursive mode doesn't work yet */ + (void)recursive; + /* Get the file name and size*/ + if(!src->is_ssh){ + fd=fileno(src->file); + fstat(fd,&s); + size=s.st_size; + mode = s.st_mode & ~S_IFMT; + filename=ssh_basename(src->path); + } else { + size=0; + do { + r=ssh_scp_pull_request(src->scp); + if(r==SSH_SCP_REQUEST_NEWDIR){ + ssh_scp_deny_request(src->scp,"Not in recursive mode"); + continue; + } + if(r==SSH_SCP_REQUEST_NEWFILE){ + size=ssh_scp_request_get_size(src->scp); + filename=strdup(ssh_scp_request_get_filename(src->scp)); + mode=ssh_scp_request_get_permissions(src->scp); + //ssh_scp_accept_request(src->scp); + break; + } + if(r==SSH_ERROR){ + fprintf(stderr,"Error: %s\n",ssh_get_error(src->session)); + return -1; + } + } while(r != SSH_SCP_REQUEST_NEWFILE); + } + + if(dest->is_ssh){ + r=ssh_scp_push_file(dest->scp,src->path, size, mode); + // snprintf(buffer,sizeof(buffer),"C0644 %d %s\n",size,src->path); + if(r==SSH_ERROR){ + fprintf(stderr,"error: %s\n",ssh_get_error(dest->session)); + ssh_scp_free(dest->scp); + return -1; + } + } else { + if(!dest->file){ + dest->file=fopen(filename,"w"); + if(!dest->file){ + fprintf(stderr,"Cannot open %s for writing: %s\n",filename,strerror(errno)); + if(src->is_ssh) + ssh_scp_deny_request(src->scp,"Cannot open local file"); + return -1; + } + } + if(src->is_ssh){ + ssh_scp_accept_request(src->scp); + } + } + do { + if(src->is_ssh){ + r=ssh_scp_read(src->scp,buffer,sizeof(buffer)); + if(r==SSH_ERROR){ + fprintf(stderr,"Error reading scp: %s\n",ssh_get_error(src->session)); + return -1; + } + if(r==0) + break; + } else { + r=fread(buffer,1,sizeof(buffer),src->file); + if(r==0) + break; + if(r<0){ + fprintf(stderr,"Error reading file: %s\n",strerror(errno)); + return -1; + } + } + if(dest->is_ssh){ + w=ssh_scp_write(dest->scp,buffer,r); + if(w == SSH_ERROR){ + fprintf(stderr,"Error writing in scp: %s\n",ssh_get_error(dest->session)); + ssh_scp_free(dest->scp); + dest->scp=NULL; + return -1; + } + } else { + w=fwrite(buffer,r,1,dest->file); + if(w<=0){ + fprintf(stderr,"Error writing in local file: %s\n",strerror(errno)); + return -1; + } + } + total+=r; + + } while(total < size); + printf("wrote %d bytes\n",total); + return 0; +} + +int main(int argc, char **argv){ + struct location *dest, *src; + int i; + int r; + if(opts(argc,argv)<0) + return EXIT_FAILURE; + dest=parse_location(destination); + if(open_location(dest,WRITE)<0) + return EXIT_FAILURE; + for(i=0;iis_ssh){ + r=ssh_scp_close(dest->scp); + if(r == SSH_ERROR){ + fprintf(stderr,"Error closing scp: %s\n",ssh_get_error(dest->session)); + ssh_scp_free(dest->scp); + dest->scp=NULL; + return -1; + } + } else { + fclose(dest->file); + dest->file=NULL; + } + ssh_disconnect(dest->session); + ssh_finalize(); + return 0; +} diff --git a/libssh/examples/libsshpp.cpp b/libssh/examples/libsshpp.cpp new file mode 100644 index 00000000..8f042a45 --- /dev/null +++ b/libssh/examples/libsshpp.cpp @@ -0,0 +1,33 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh */ + +#include +#include +#include + +int main(int argc, const char **argv){ + ssh::Session session; + try { + if(argc>1) + session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + session.setOption(SSH_OPTIONS_HOST,"localhost"); + session.connect(); + session.userauthPublickeyAuto(); + session.disconnect(); + } catch (ssh::SshException e){ + std::cout << "Error during connection : "; + std::cout << e.getError() << std::endl; + } + return 0; +} diff --git a/libssh/examples/libsshpp_noexcept.cpp b/libssh/examples/libsshpp_noexcept.cpp new file mode 100644 index 00000000..eff8cc19 --- /dev/null +++ b/libssh/examples/libsshpp_noexcept.cpp @@ -0,0 +1,41 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh + * specifically, without C++ exceptions + */ + +#include +#define SSH_NO_CPP_EXCEPTIONS +#include + +int main(int argc, const char **argv){ + ssh::Session session,s2; + int err; + if(argc>1) + err=session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + err=session.setOption(SSH_OPTIONS_HOST,"localhost"); + if(err==SSH_ERROR) + goto error; + err=session.connect(); + if(err==SSH_ERROR) + goto error; + err=session.userauthPublickeyAuto(); + if(err==SSH_ERROR) + goto error; + + return 0; + error: + std::cout << "Error during connection : "; + std::cout << session.getError() << std::endl; + return 1; +} diff --git a/libssh/examples/sample.c b/libssh/examples/sample.c new file mode 100644 index 00000000..cfe4b3a6 --- /dev/null +++ b/libssh/examples/sample.c @@ -0,0 +1,519 @@ +/* client.c */ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_PTY_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include + +#include "examples_common.h" +#define MAXCMD 10 + +static char *host; +static char *user; +static char *cmds[MAXCMD]; +static struct termios terminal; + +static char *pcap_file=NULL; + +static char *proxycommand; + +static int auth_callback(const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) { + (void) verify; + (void) userdata; + + return ssh_getpass(prompt, buf, len, echo, verify); +} + +struct ssh_callbacks_struct cb = { + .auth_function=auth_callback, + .userdata=NULL +}; + +static void add_cmd(char *cmd){ + int n; + for(n=0;cmds[n] && (nc_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +#endif + + +static void do_cleanup(int i) { + /* unused variable */ + (void) i; + + tcsetattr(0,TCSANOW,&terminal); +} + +static void do_exit(int i) { + /* unused variable */ + (void) i; + + do_cleanup(0); + exit(0); +} + +ssh_channel chan; +int signal_delayed=0; + +static void sigwindowchanged(int i){ + (void) i; + signal_delayed=1; +} + +static void setsignal(void){ + signal(SIGWINCH, sigwindowchanged); + signal_delayed=0; +} + +static void sizechanged(void){ + struct winsize win = { 0, 0, 0, 0 }; + ioctl(1, TIOCGWINSZ, &win); + ssh_channel_change_pty_size(chan,win.ws_col, win.ws_row); +// printf("Changed pty size\n"); + setsignal(); +} + +/* There are two flavors of select loop: the one based on + * ssh_select and the one based on channel_select. + * The ssh_select one permits you to give your own file descriptors to + * follow. It is thus a complete select loop. + * The second one only selects on channels. It is simplier to use + * but doesn't permit you to fill in your own file descriptor. It is + * more adapted if you can't use ssh_select as a main loop (because + * you already have another main loop system). + */ + +#ifdef USE_CHANNEL_SELECT + +/* channel_select base main loop, with a standard select(2) + */ +static void select_loop(ssh_session session,ssh_channel channel){ + fd_set fds; + struct timeval timeout; + char buffer[4096]; + ssh_buffer readbuf=ssh_buffer_new(); + ssh_channel channels[2]; + int lus; + int eof=0; + int maxfd; + int ret; + while(channel){ + /* when a signal is caught, ssh_select will return + * with SSH_EINTR, which means it should be started + * again. It lets you handle the signal the faster you + * can, like in this window changed example. Of course, if + * your signal handler doesn't call libssh at all, you're + * free to handle signals directly in sighandler. + */ + do{ + FD_ZERO(&fds); + if(!eof) + FD_SET(0,&fds); + timeout.tv_sec=30; + timeout.tv_usec=0; + FD_SET(ssh_get_fd(session),&fds); + maxfd=ssh_get_fd(session)+1; + ret=select(maxfd,&fds,NULL,NULL,&timeout); + if(ret==EINTR) + continue; + if(FD_ISSET(0,&fds)){ + lus=read(0,buffer,sizeof(buffer)); + if(lus) + ssh_channel_write(channel,buffer,lus); + else { + eof=1; + ssh_channel_send_eof(channel); + } + } + if(FD_ISSET(ssh_get_fd(session),&fds)){ + ssh_set_fd_toread(session); + } + channels[0]=channel; // set the first channel we want to read from + channels[1]=NULL; + ret=ssh_channel_select(channels,NULL,NULL,NULL); // no specific timeout - just poll + if(signal_delayed) + sizechanged(); + } while (ret==EINTR || ret==SSH_EINTR); + + // we already looked for input from stdin. Now, we are looking for input from the channel + + if(channel && ssh_channel_is_closed(channel)){ + ssh_log(session,SSH_LOG_RARE,"exit-status : %d",ssh_channel_get_exit_status(channel)); + + ssh_channel_free(channel); + channel=NULL; + channels[0]=NULL; + } + if(channels[0]){ + while(channel && ssh_channel_is_open(channel) && ssh_channel_poll(channel,0)>0){ + lus=channel_read_buffer(channel,readbuf,0,0); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_log(session,SSH_LOG_RARE,"EOF received"); + ssh_log(session,SSH_LOG_RARE,"exit-status : %d",ssh_channel_get_exit_status(channel)); + + ssh_channel_free(channel); + channel=channels[0]=NULL; + } else + if (write(1,ssh_buffer_get_begin(readbuf),lus) < 0) { + fprintf(stderr, "Error writing to buffer\n"); + return; + } + } + while(channel && ssh_channel_is_open(channel) && ssh_channel_poll(channel,1)>0){ /* stderr */ + lus=channel_read_buffer(channel,readbuf,0,1); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_log(session,SSH_LOG_RARE,"EOF received"); + ssh_log(session,SSH_LOG_RARE,"exit-status : %d",ssh_channel_get_exit_status(channel)); + ssh_channel_free(channel); + channel=channels[0]=NULL; + } else + if (write(2,ssh_buffer_get_begin(readbuf),lus) < 0) { + fprintf(stderr, "Error writing to buffer\n"); + return; + } + } + } + if(channel && ssh_channel_is_closed(channel)){ + ssh_channel_free(channel); + channel=NULL; + } + } + ssh_buffer_free(readbuf); +} +#else /* CHANNEL_SELECT */ + +static void select_loop(ssh_session session,ssh_channel channel){ + fd_set fds; + struct timeval timeout; + char buffer[4096]; + /* channels will be set to the channels to poll. + * outchannels will contain the result of the poll + */ + ssh_channel channels[2], outchannels[2]; + int lus; + int eof=0; + int maxfd; + unsigned int r; + int ret; + while(channel){ + do{ + FD_ZERO(&fds); + if(!eof) + FD_SET(0,&fds); + timeout.tv_sec=30; + timeout.tv_usec=0; + FD_SET(ssh_get_fd(session),&fds); + maxfd=ssh_get_fd(session)+1; + channels[0]=channel; // set the first channel we want to read from + channels[1]=NULL; + ret=ssh_select(channels,outchannels,maxfd,&fds,&timeout); + if(signal_delayed) + sizechanged(); + if(ret==EINTR) + continue; + if(FD_ISSET(0,&fds)){ + lus=read(0,buffer,sizeof(buffer)); + if(lus) + ssh_channel_write(channel,buffer,lus); + else { + eof=1; + ssh_channel_send_eof(channel); + } + } + if(channel && ssh_channel_is_closed(channel)){ + ssh_log(session,SSH_LOG_RARE,"exit-status : %d",ssh_channel_get_exit_status(channel)); + + ssh_channel_free(channel); + channel=NULL; + channels[0]=NULL; + } + if(outchannels[0]){ + while(channel && ssh_channel_is_open(channel) && (r = ssh_channel_poll(channel,0))!=0){ + lus=ssh_channel_read(channel,buffer,sizeof(buffer) > r ? r : sizeof(buffer),0); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_log(session,SSH_LOG_RARE,"EOF received"); + ssh_log(session,SSH_LOG_RARE,"exit-status : %d",ssh_channel_get_exit_status(channel)); + + ssh_channel_free(channel); + channel=channels[0]=NULL; + } else + if (write(1,buffer,lus) < 0) { + fprintf(stderr, "Error writing to buffer\n"); + return; + } + } + while(channel && ssh_channel_is_open(channel) && (r = ssh_channel_poll(channel,1))!=0){ /* stderr */ + lus=ssh_channel_read(channel,buffer,sizeof(buffer) > r ? r : sizeof(buffer),1); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_log(session,SSH_LOG_RARE,"EOF received"); + ssh_log(session,SSH_LOG_RARE,"exit-status : %d",ssh_channel_get_exit_status(channel)); + ssh_channel_free(channel); + channel=channels[0]=NULL; + } else + if (write(2,buffer,lus) < 0) { + fprintf(stderr, "Error writing to buffer\n"); + return; + } + } + } + if(channel && ssh_channel_is_closed(channel)){ + ssh_channel_free(channel); + channel=NULL; + } + } while (ret==EINTR || ret==SSH_EINTR); + + } +} + +#endif + +static void shell(ssh_session session){ + ssh_channel channel; + struct termios terminal_local; + int interactive=isatty(0); + channel = ssh_channel_new(session); + if(interactive){ + tcgetattr(0,&terminal_local); + memcpy(&terminal,&terminal_local,sizeof(struct termios)); + } + if(ssh_channel_open_session(channel)){ + printf("error opening channel : %s\n",ssh_get_error(session)); + return; + } + chan=channel; + if(interactive){ + ssh_channel_request_pty(channel); + sizechanged(); + } + if(ssh_channel_request_shell(channel)){ + printf("Requesting shell : %s\n",ssh_get_error(session)); + return; + } + if(interactive){ + cfmakeraw(&terminal_local); + tcsetattr(0,TCSANOW,&terminal_local); + setsignal(); + } + signal(SIGTERM,do_cleanup); + select_loop(session,channel); + if(interactive) + do_cleanup(0); +} + +static void batch_shell(ssh_session session){ + ssh_channel channel; + char buffer[1024]; + int i,s=0; + for(i=0;i +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "examples_common.h" +#ifdef WITH_SFTP + +static int verbosity; +static char *destination; + +#define DATALEN 65536 +static void do_sftp(ssh_session session){ + sftp_session sftp=sftp_new(session); + sftp_dir dir; + sftp_attributes file; + sftp_statvfs_t sftpstatvfs; + struct statvfs sysstatvfs; + sftp_file fichier; + sftp_file to; + int len=1; + unsigned int i; + char data[DATALEN]={0}; + char *lnk; + + unsigned int count; + + if(!sftp){ + fprintf(stderr, "sftp error initialising channel: %s\n", + ssh_get_error(session)); + return; + } + if(sftp_init(sftp)){ + fprintf(stderr, "error initialising sftp: %s\n", + ssh_get_error(session)); + return; + } + + printf("Additional SFTP extensions provided by the server:\n"); + count = sftp_extensions_get_count(sftp); + for (i = 0; i < count; i++) { + printf("\t%s, version: %s\n", + sftp_extensions_get_name(sftp, i), + sftp_extensions_get_data(sftp, i)); + } + + /* test symlink and readlink */ + if (sftp_symlink(sftp, "/tmp/this_is_the_link", + "/tmp/sftp_symlink_test") < 0) { + fprintf(stderr, "Could not create link (%s)\n", ssh_get_error(session)); + return; + } + + lnk = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + if (lnk == NULL) { + fprintf(stderr, "Could not read link (%s)\n", ssh_get_error(session)); + return; + } + printf("readlink /tmp/sftp_symlink_test: %s\n", lnk); + + sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + + if (sftp_extension_supported(sftp, "statvfs@openssh.com", "2")) { + sftpstatvfs = sftp_statvfs(sftp, "/tmp"); + if (sftpstatvfs == NULL) { + fprintf(stderr, "statvfs failed (%s)\n", ssh_get_error(session)); + return; + } + + printf("sftp statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sftpstatvfs->f_bsize, + (unsigned long long) sftpstatvfs->f_frsize, + (unsigned long long) sftpstatvfs->f_blocks, + (unsigned long long) sftpstatvfs->f_bfree, + (unsigned long long) sftpstatvfs->f_bavail, + (unsigned long long) sftpstatvfs->f_files, + (unsigned long long) sftpstatvfs->f_ffree, + (unsigned long long) sftpstatvfs->f_favail, + (unsigned long long) sftpstatvfs->f_fsid, + (unsigned long long) sftpstatvfs->f_flag, + (unsigned long long) sftpstatvfs->f_namemax); + + sftp_statvfs_free(sftpstatvfs); + + if (statvfs("/tmp", &sysstatvfs) < 0) { + fprintf(stderr, "statvfs failed (%s)\n", strerror(errno)); + return; + } + + printf("sys statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sysstatvfs.f_bsize, + (unsigned long long) sysstatvfs.f_frsize, + (unsigned long long) sysstatvfs.f_blocks, + (unsigned long long) sysstatvfs.f_bfree, + (unsigned long long) sysstatvfs.f_bavail, + (unsigned long long) sysstatvfs.f_files, + (unsigned long long) sysstatvfs.f_ffree, + (unsigned long long) sysstatvfs.f_favail, + (unsigned long long) sysstatvfs.f_fsid, + (unsigned long long) sysstatvfs.f_flag, + (unsigned long long) sysstatvfs.f_namemax); + } + + /* the connection is made */ + /* opening a directory */ + dir=sftp_opendir(sftp,"./"); + if(!dir) { + fprintf(stderr, "Directory not opened(%s)\n", ssh_get_error(session)); + return ; + } + /* reading the whole directory, file by file */ + while((file=sftp_readdir(sftp,dir))){ + fprintf(stderr, "%30s(%.8o) : %s(%.5d) %s(%.5d) : %.10llu bytes\n", + file->name, + file->permissions, + file->owner, + file->uid, + file->group, + file->gid, + (long long unsigned int) file->size); + sftp_attributes_free(file); + } + /* when file=NULL, an error has occured OR the directory listing is end of file */ + if(!sftp_dir_eof(dir)){ + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + return; + } + if(sftp_closedir(dir)){ + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + return; + } + /* this will open a file and copy it into your /home directory */ + /* the small buffer size was intended to stress the library. of course, you can use a buffer till 20kbytes without problem */ + + fichier=sftp_open(sftp,"/usr/bin/ssh",O_RDONLY, 0); + if(!fichier){ + fprintf(stderr, "Error opening /usr/bin/ssh: %s\n", + ssh_get_error(session)); + return; + } + /* open a file for writing... */ + to=sftp_open(sftp,"ssh-copy",O_WRONLY | O_CREAT, 0700); + if(!to){ + fprintf(stderr, "Error opening ssh-copy for writing: %s\n", + ssh_get_error(session)); + return; + } + while((len=sftp_read(fichier,data,4096)) > 0){ + if(sftp_write(to,data,len)!=len){ + fprintf(stderr, "Error writing %d bytes: %s\n", + len, ssh_get_error(session)); + return; + } + } + printf("finished\n"); + if(len<0) + fprintf(stderr, "Error reading file: %s\n", ssh_get_error(session)); + sftp_close(fichier); + sftp_close(to); + printf("fichiers ferm\n"); + to=sftp_open(sftp,"/tmp/grosfichier",O_WRONLY|O_CREAT, 0644); + for(i=0;i<1000;++i){ + len=sftp_write(to,data,DATALEN); + printf("wrote %d bytes\n",len); + if(len != DATALEN){ + printf("chunk %d : %d (%s)\n",i,len,ssh_get_error(session)); + } + } + sftp_close(to); + + /* close the sftp session */ + sftp_free(sftp); + printf("sftp session terminated\n"); +} + +static void usage(const char *argv0){ + fprintf(stderr,"Usage : %s [-v] remotehost\n" + "sample sftp test client - libssh-%s\n" + "Options :\n" + " -v : increase log verbosity\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv){ + int i; + while((i=getopt(argc,argv,"v"))!=-1){ + switch(i){ + case 'v': + verbosity++; + break; + default: + fprintf(stderr,"unknown option %c\n",optopt); + usage(argv[0]); + return -1; + } + } + + destination=argv[optind]; + if(destination == NULL){ + usage(argv[0]); + return -1; + } + return 0; +} + +int main(int argc, char **argv){ + ssh_session session; + if(opts(argc,argv)<0) + return EXIT_FAILURE; + session=connect_ssh(destination,NULL,verbosity); + if(session == NULL) + return EXIT_FAILURE; + do_sftp(session); + ssh_disconnect(session); + ssh_free(session); + return 0; +} + + + +#endif diff --git a/libssh/examples/samplesshd-kbdint.c b/libssh/examples/samplesshd-kbdint.c new file mode 100644 index 00000000..faecfd9b --- /dev/null +++ b/libssh/examples/samplesshd-kbdint.c @@ -0,0 +1,413 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2011 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#define SSHD_USER "libssh" +#define SSHD_PASSWORD "libssh" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#ifdef WITH_PCAP +static const char *pcap_file = "debug.server.pcap"; +static ssh_pcap_file pcap; + +static void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(void) { + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(const char *user, const char *password){ + if(strcmp(user, SSHD_USER)) + return 0; + if(strcmp(password, SSHD_PASSWORD)) + return 0; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +static int port = 22; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, 0, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + port = atoi(arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +static const char *name; +static const char *instruction; +static const char *prompts[2]; +static char echo[] = { 1, 0 }; + +static int kbdint_check_response(ssh_session session) { + int count; + + count = ssh_userauth_kbdint_getnanswers(session); + if(count != 2) { + instruction = "Something weird happened :("; + return 0; + } + if(strcasecmp("Arthur Dent", + ssh_userauth_kbdint_getanswer(session, 0)) != 0) { + instruction = "OK, this is not YOUR name, " + "but it's a reference to the HGTG..."; + prompts[0] = "The main character's full name: "; + return 0; + } + if(strcmp("42", ssh_userauth_kbdint_getanswer(session, 1)) != 0) { + instruction = "Make an effort !!! What is the Answer to the Ultimate " + "Question of Life, the Universe, and Everything ?"; + prompts[1] = "Answer to the Ultimate Question of Life, the Universe, " + "and Everything: "; + return 0; + } + + return 1; +} + +static int authenticate(ssh_session session) { + ssh_message message; + + name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + instruction = "Please enter your real name and your password"; + prompts[0] = "Real name: "; + prompts[1] = "Password: "; + + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + // not authenticated, send default message + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_INTERACTIVE: + if(!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, name, + instruction, 2, prompts, echo); + } else { + if(kbdint_check_response(session)) { + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + break; + case SSH_AUTH_METHOD_NONE: + default: + printf("User %s wants to auth with unknown auth %d\n", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + return 0; +} + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_message message; + ssh_channel chan=0; + char buf[2048]; + int auth=0; + int shell=0; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, + KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + printf("Started sample libssh sshd on port %d\n", port); + printf("You can login as the user %s with the password %s\n", SSHD_USER, + SSHD_PASSWORD); + r = ssh_bind_accept(sshbind, session); + if(r==SSH_ERROR){ + printf("Error accepting a connection: %s\n", ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + + /* proceed to authentication */ + auth = authenticate(session); + if(!auth){ + printf("Authentication error: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + + + /* wait for a channel session */ + do { + message = ssh_message_get(session); + if(message){ + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while(!chan); + + if(!chan) { + printf("Error: cleint did not ask for a channel session (%s)\n", + ssh_get_error(session)); + ssh_finalize(); + return 1; + } + + + /* wait for a shell */ + do { + message = ssh_message_get(session); + if(message != NULL) { + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL && + ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while(!shell); + + if(!shell) { + printf("Error: No shell requested (%s)\n", ssh_get_error(session)); + return 1; + } + + + printf("it works !\n"); + do{ + i=ssh_channel_read(chan,buf, 2048, 0); + if(i>0) { + if(*buf == '' || *buf == '') + break; + if(i == 1 && *buf == '\r') + ssh_channel_write(chan, "\r\n", 2); + else + ssh_channel_write(chan, buf, i); + if (write(1,buf,i) < 0) { + printf("error writing to buffer\n"); + return 1; + } + } + } while (i>0); + ssh_channel_close(chan); + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} + diff --git a/libssh/examples/samplesshd-tty.c b/libssh/examples/samplesshd-tty.c new file mode 100644 index 00000000..373ec079 --- /dev/null +++ b/libssh/examples/samplesshd-tty.c @@ -0,0 +1,453 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2011 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include +#include +#include + +#define SSHD_USER "libssh" +#define SSHD_PASSWORD "libssh" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#ifdef WITH_PCAP +const char *pcap_file="debug.server.pcap"; +ssh_pcap_file pcap; + +static void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(){ + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(const char *user, const char *password){ + if(strcmp(user, SSHD_USER)) + return 0; + if(strcmp(password, SSHD_PASSWORD)) + return 0; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +static int port = 22; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, 0, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + port = atoi(arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +static int authenticate(ssh_session session) { + ssh_message message; + + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + // not authenticated, send default message + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_NONE: + default: + printf("User %s wants to auth with unknown auth %d\n", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + return 0; +} + +static int copy_fd_to_chan(socket_t fd, int revents, void *userdata) { + ssh_channel chan = (ssh_channel)userdata; + char buf[2048]; + int sz = 0; + + if(!chan) { + close(fd); + return -1; + } + if(revents & POLLIN) { + sz = read(fd, buf, 2048); + if(sz > 0) { + ssh_channel_write(chan, buf, sz); + } + } + if(revents & POLLHUP) { + ssh_channel_close(chan); + sz = -1; + } + return sz; +} + +static int copy_chan_to_fd(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) { + int fd = *(int*)userdata; + int sz; + (void)session; + (void)channel; + (void)is_stderr; + + sz = write(fd, data, len); + return sz; +} + +static void chan_close(ssh_session session, ssh_channel channel, void *userdata) { + int fd = *(int*)userdata; + (void)session; + (void)channel; + + close(fd); +} + +struct ssh_channel_callbacks_struct cb = { + .channel_data_function = copy_chan_to_fd, + .channel_eof_function = chan_close, + .channel_close_function = chan_close, + .userdata = NULL +}; + +static int main_loop(ssh_channel chan) { + ssh_session session = ssh_channel_get_session(chan); + socket_t fd; + struct termios *term = NULL; + struct winsize *win = NULL; + pid_t childpid; + ssh_event event; + short events; + + + childpid = forkpty(&fd, NULL, term, win); + if(childpid == 0) { + execl("/bin/bash", "/bin/bash", (char *)NULL); + abort(); + } + + cb.userdata = &fd; + ssh_callbacks_init(&cb); + ssh_set_channel_callbacks(chan, &cb); + + events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; + + event = ssh_event_new(); + if(event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + if(ssh_event_add_fd(event, fd, events, copy_fd_to_chan, chan) != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + if(ssh_event_add_session(event, session) != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + ssh_event_dopoll(event, 1000); + } while(!ssh_channel_is_closed(chan)); + + ssh_event_remove_fd(event, fd); + + ssh_event_remove_session(event, session); + + ssh_event_free(event); + return 0; +} + + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_message message; + ssh_channel chan=0; + int auth=0; + int shell=0; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, + KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + printf("Started sample libssh sshd on port %d\n", port); + printf("You can login as the user %s with the password %s\n", SSHD_USER, + SSHD_PASSWORD); + r = ssh_bind_accept(sshbind, session); + if(r==SSH_ERROR){ + printf("Error accepting a connection: %s\n", ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + + /* proceed to authentication */ + auth = authenticate(session); + if(!auth){ + printf("Authentication error: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + + + /* wait for a channel session */ + do { + message = ssh_message_get(session); + if(message){ + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while(!chan); + + if(!chan) { + printf("Error: cleint did not ask for a channel session (%s)\n", + ssh_get_error(session)); + ssh_finalize(); + return 1; + } + + + /* wait for a shell */ + do { + message = ssh_message_get(session); + if(message != NULL) { + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL) { + if(ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } else if(ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_PTY) { + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + continue; + } + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while(!shell); + + if(!shell) { + printf("Error: No shell requested (%s)\n", ssh_get_error(session)); + return 1; + } + + printf("it works !\n"); + + main_loop(chan); + + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} + diff --git a/libssh/examples/samplesshd.c b/libssh/examples/samplesshd.c new file mode 100644 index 00000000..f9e0dc8c --- /dev/null +++ b/libssh/examples/samplesshd.c @@ -0,0 +1,314 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#ifdef WITH_PCAP +static const char *pcap_file="debug.server.pcap"; +static ssh_pcap_file pcap; + +static void set_pcap(ssh_session session) { + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(void) { + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(const char *user, const char *password){ + if(strcmp(user,"aris")) + return 0; + if(strcmp(password,"lala")) + return 0; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_message message; + ssh_channel chan=0; + char buf[2048]; + int auth=0; + int sftp=0; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + auth=1; + ssh_message_auth_reply_success(message,0); + break; + } + // not authenticated, send default message + case SSH_AUTH_METHOD_NONE: + default: + ssh_message_auth_set_methods(message,SSH_AUTH_METHOD_PASSWORD); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (!auth); + if(!auth){ + printf("auth error: %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + do { + message=ssh_message_get(session); + if(message){ + switch(ssh_message_type(message)){ + case SSH_REQUEST_CHANNEL_OPEN: + if(ssh_message_subtype(message)==SSH_CHANNEL_SESSION){ + chan=ssh_message_channel_request_open_reply_accept(message); + break; + } + default: + ssh_message_reply_default(message); + } + ssh_message_free(message); + } + } while(message && !chan); + if(!chan){ + printf("error : %s\n",ssh_get_error(session)); + ssh_finalize(); + return 1; + } + do { + message=ssh_message_get(session); + if(message && ssh_message_type(message)==SSH_REQUEST_CHANNEL && + (ssh_message_subtype(message)==SSH_CHANNEL_REQUEST_SHELL || + ssh_message_subtype(message)==SSH_CHANNEL_REQUEST_PTY)) { +// if(!strcmp(ssh_message_channel_request_subsystem(message),"sftp")){ + sftp=1; + ssh_message_channel_request_reply_success(message); + break; + // } + } + if(!sftp){ + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (message && !sftp); + if(!sftp){ + printf("error : %s\n",ssh_get_error(session)); + return 1; + } + printf("it works !\n"); + do{ + i=ssh_channel_read(chan,buf, 2048, 0); + if(i>0) { + ssh_channel_write(chan, buf, i); + if (write(1,buf,i) < 0) { + printf("error writing to buffer\n"); + return 1; + } + if (buf[0] == '\x0d') { + if (write(1, "\n", 1) < 0) { + printf("error writing to buffer\n"); + return 1; + } + ssh_channel_write(chan, "\n", 1); + } + } + } while (i>0); + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} + diff --git a/libssh/examples/scp_download.c b/libssh/examples/scp_download.c new file mode 100644 index 00000000..bfd8dc4e --- /dev/null +++ b/libssh/examples/scp_download.c @@ -0,0 +1,165 @@ +/* scp_download.c + * Sample implementation of a tiny SCP downloader client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +int verbosity=0; +const char *createcommand="rm -fr /tmp/libssh_tests && mkdir /tmp/libssh_tests && cd /tmp/libssh_tests && date > a && date > b && mkdir c && date > d"; +char *host=NULL; +static void usage(const char *argv0){ + fprintf(stderr,"Usage : %s [options] host\n" + "sample tiny scp downloader client - libssh-%s\n" + "This program will create files in /tmp and try to fetch them\n", +// "Options :\n", +// " -r : use RSA to verify host public key\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv){ + int i; + while((i=getopt(argc,argv,"v"))!=-1){ + switch(i){ + case 'v': + verbosity++; + break; + default: + fprintf(stderr,"unknown option %c\n",optopt); + usage(argv[0]); + return -1; + } + } + host = argv[optind]; + if(host == NULL) + usage(argv[0]); + return 0; +} + +static void create_files(ssh_session session){ + ssh_channel channel=ssh_channel_new(session); + char buffer[1]; + if(channel == NULL){ + fprintf(stderr,"Error creating channel: %s\n",ssh_get_error(session)); + exit(EXIT_FAILURE); + } + if(ssh_channel_open_session(channel) != SSH_OK){ + fprintf(stderr,"Error creating channel: %s\n",ssh_get_error(session)); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + if(ssh_channel_request_exec(channel,createcommand) != SSH_OK){ + fprintf(stderr,"Error executing command: %s\n",ssh_get_error(session)); + ssh_channel_close(channel); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + while(!ssh_channel_is_eof(channel)){ + ssh_channel_read(channel,buffer,1,1); + if (write(1,buffer,1) < 0) { + fprintf(stderr, "Error writing to buffer\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + } + ssh_channel_close(channel); + ssh_channel_free(channel); +} + + +static int fetch_files(ssh_session session){ + int size; + char buffer[16384]; + int mode; + char *filename; + int r; + ssh_scp scp=ssh_scp_new(session, SSH_SCP_READ | SSH_SCP_RECURSIVE, "/tmp/libssh_tests/*"); + if(ssh_scp_init(scp) != SSH_OK){ + fprintf(stderr,"error initializing scp: %s\n",ssh_get_error(session)); + ssh_scp_free(scp); + return -1; + } + printf("Trying to download 3 files (a,b,d) and 1 directory (c)\n"); + do { + + r=ssh_scp_pull_request(scp); + switch(r){ + case SSH_SCP_REQUEST_NEWFILE: + size=ssh_scp_request_get_size(scp); + filename=strdup(ssh_scp_request_get_filename(scp)); + mode=ssh_scp_request_get_permissions(scp); + printf("downloading file %s, size %d, perms 0%o\n",filename,size,mode); + free(filename); + ssh_scp_accept_request(scp); + r=ssh_scp_read(scp,buffer,sizeof(buffer)); + if(r==SSH_ERROR){ + fprintf(stderr,"Error reading scp: %s\n",ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + } + printf("done\n"); + break; + case SSH_ERROR: + fprintf(stderr,"Error: %s\n",ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + case SSH_SCP_REQUEST_WARNING: + fprintf(stderr,"Warning: %s\n",ssh_scp_request_get_warning(scp)); + break; + case SSH_SCP_REQUEST_NEWDIR: + filename=strdup(ssh_scp_request_get_filename(scp)); + mode=ssh_scp_request_get_permissions(scp); + printf("downloading directory %s, perms 0%o\n",filename,mode); + free(filename); + ssh_scp_accept_request(scp); + break; + case SSH_SCP_REQUEST_ENDDIR: + printf("End of directory\n"); + break; + case SSH_SCP_REQUEST_EOF: + printf("End of requests\n"); + goto end; + } + } while (1); + end: + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +} + +int main(int argc, char **argv){ + ssh_session session; + if(opts(argc,argv)<0) + return EXIT_FAILURE; + session=connect_ssh(host,NULL,verbosity); + if(session == NULL) + return EXIT_FAILURE; + create_files(session); + fetch_files(session); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 0; +} diff --git a/libssh/examples/senddata.c b/libssh/examples/senddata.c new file mode 100644 index 00000000..acc1bebc --- /dev/null +++ b/libssh/examples/senddata.c @@ -0,0 +1,64 @@ +#include + +#include +#include "examples_common.h" + +#define LIMIT 0x100000000 + +int main(void) { + ssh_session session; + ssh_channel channel; + char buffer[1024*1024]; + int rc; + uint64_t total=0; + uint64_t lastshown=4096; + session = connect_ssh("localhost", NULL, 0); + if (session == NULL) { + return 1; + } + + channel = ssh_channel_new(session);; + if (channel == NULL) { + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_request_exec(channel, "cat > /dev/null"); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + + while ((rc = ssh_channel_write(channel, buffer, sizeof(buffer))) > 0) { + total += rc; + if(total/2 >= lastshown){ + printf("written %llx\n", (long long unsigned int) total); + lastshown=total; + } + if(total > LIMIT) + break; + } + + if (rc < 0) { + printf("error : %s\n",ssh_get_error(session)); + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + + ssh_disconnect(session); + + return 0; +} diff --git a/libssh/examples/sshnetcat.c b/libssh/examples/sshnetcat.c new file mode 100644 index 00000000..cad777bb --- /dev/null +++ b/libssh/examples/sshnetcat.c @@ -0,0 +1,258 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "examples_common.h" +char *host; +const char *desthost="localhost"; +const char *port="22"; + +#ifdef WITH_PCAP +#include +char *pcap_file=NULL; +#endif + +static void usage(){ + fprintf(stderr,"Usage : sshnetcat [user@]host forwarded_host forwarded_port\n"); + exit(1); +} + +static int opts(int argc, char **argv){ + int i; + while((i=getopt(argc,argv,"P:"))!=-1){ + switch(i){ +#ifdef WITH_PCAP + case 'P': + pcap_file=optarg; + break; +#endif + default: + fprintf(stderr,"unknown option %c\n",optopt); + usage(); + } + } + if(optind < argc) + host=argv[optind++]; + if(optind < argc) + desthost=argv[optind++]; + if(optind < argc) + port=argv[optind++]; + if(host==NULL) + usage(); + return 0; +} + +static void select_loop(ssh_session session,ssh_channel channel){ + fd_set fds; + struct timeval timeout; + char buffer[4096]; + /* channels will be set to the channels to poll. + * outchannels will contain the result of the poll + */ + ssh_channel channels[2], outchannels[2]; + int lus; + int eof=0; + int maxfd; + int ret; + while(channel){ + do{ + FD_ZERO(&fds); + if(!eof) + FD_SET(0,&fds); + timeout.tv_sec=30; + timeout.tv_usec=0; + FD_SET(ssh_get_fd(session),&fds); + maxfd=ssh_get_fd(session)+1; + channels[0]=channel; // set the first channel we want to read from + channels[1]=NULL; + ret=ssh_select(channels,outchannels,maxfd,&fds,&timeout); + if(ret==EINTR) + continue; + if(FD_ISSET(0,&fds)){ + lus=read(0,buffer,sizeof(buffer)); + if(lus) + channel_write(channel,buffer,lus); + else { + eof=1; + channel_send_eof(channel); + } + } + if(channel && channel_is_closed(channel)){ + ssh_log(session,SSH_LOG_RARE,"exit-status : %d\n",channel_get_exit_status(channel)); + + channel_free(channel); + channel=NULL; + channels[0]=NULL; + } + if(outchannels[0]){ + while(channel && channel_is_open(channel) && channel_poll(channel,0)){ + lus=channel_read(channel,buffer,sizeof(buffer),0); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_log(session,SSH_LOG_RARE,"EOF received\n"); + ssh_log(session,SSH_LOG_RARE,"exit-status : %d\n",channel_get_exit_status(channel)); + + channel_free(channel); + channel=channels[0]=NULL; + } else { + ret = write(1, buffer, lus); + if (ret < 0) { + fprintf(stderr, "Error writing to stdin: %s", + strerror(errno)); + return; + } + } + } + while(channel && channel_is_open(channel) && channel_poll(channel,1)){ /* stderr */ + lus=channel_read(channel,buffer,sizeof(buffer),1); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_log(session,SSH_LOG_RARE,"EOF received\n"); + ssh_log(session,SSH_LOG_RARE,"exit-status : %d\n",channel_get_exit_status(channel)); + channel_free(channel); + channel=channels[0]=NULL; + } else + ret = write(2, buffer, lus); + if (ret < 0) { + fprintf(stderr, "Error writing to stderr: %s", + strerror(errno)); + return; + } + } + } + if(channel && channel_is_closed(channel)){ + channel_free(channel); + channel=NULL; + } + } while (ret==EINTR || ret==SSH_EINTR); + + } +} + +static void forwarding(ssh_session session){ + ssh_channel channel; + int r; + channel=channel_new(session); + r=channel_open_forward(channel,desthost,atoi(port),"localhost",22); + if(r<0) { + printf("error forwarding port : %s\n",ssh_get_error(session)); + return; + } + select_loop(session,channel); +} + +static int client(ssh_session session){ + int auth=0; + char *banner; + int state; + + if (ssh_options_set(session, SSH_OPTIONS_HOST ,host) < 0) + return -1; + ssh_options_parse_config(session, NULL); + + if(ssh_connect(session)){ + fprintf(stderr,"Connection failed : %s\n",ssh_get_error(session)); + return -1; + } + state=verify_knownhost(session); + if (state != 0) + return -1; + ssh_userauth_none(session, NULL); + banner=ssh_get_issue_banner(session); + if(banner){ + printf("%s\n",banner); + free(banner); + } + auth=authenticate_console(session); + if(auth != SSH_AUTH_SUCCESS){ + return -1; + } + ssh_log(session, SSH_LOG_FUNCTIONS, "Authentication success"); + forwarding(session); + return 0; +} + +#ifdef WITH_PCAP +ssh_pcap_file pcap; +void set_pcap(ssh_session session); +void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +void cleanup_pcap(void); +void cleanup_pcap(){ + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + +int main(int argc, char **argv){ + ssh_session session; + + session = ssh_new(); + + if(ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, "error parsing command line :%s\n", + ssh_get_error(session)); + usage(); + } + opts(argc,argv); +#ifdef WITH_PCAP + set_pcap(session); +#endif + client(session); + + ssh_disconnect(session); + ssh_free(session); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + + ssh_finalize(); + + return 0; +} diff --git a/libssh/include/CMakeLists.txt b/libssh/include/CMakeLists.txt new file mode 100644 index 00000000..cb3bd962 --- /dev/null +++ b/libssh/include/CMakeLists.txt @@ -0,0 +1,3 @@ +project(headers C) + +add_subdirectory(libssh) diff --git a/libssh/include/libssh/CMakeLists.txt b/libssh/include/libssh/CMakeLists.txt new file mode 100644 index 00000000..78ee1c61 --- /dev/null +++ b/libssh/include/libssh/CMakeLists.txt @@ -0,0 +1,39 @@ +project(libssh-headers C) + +set(libssh_HDRS + callbacks.h + libssh.h + ssh2.h + legacy.h +) + +if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftp.h + ) +endif (WITH_SFTP) + +if (WITH_SSH1) + set(libssh_HDRS + ${libssh_HDRS} + ssh1.h + ) +endif (WITH_SSH1) + +if (WITH_SERVER) + set(libssh_HDRS + ${libssh_HDRS} + server.h + ) +endif (WITH_SERVER) + +install( + FILES + ${libssh_HDRS} + DESTINATION + ${INCLUDE_INSTALL_DIR}/${APPLICATION_NAME} + COMPONENT + headers +) + diff --git a/libssh/include/libssh/agent.h b/libssh/include/libssh/agent.h new file mode 100644 index 00000000..4641c9e2 --- /dev/null +++ b/libssh/include/libssh/agent.h @@ -0,0 +1,117 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 Andreas Schneider + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AGENT_H +#define __AGENT_H + +#include "libssh/libssh.h" + +/* Messages for the authentication agent connection. */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 + +/* private OpenSSH extensions for SSH2 */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* smartcard */ +#define SSH_AGENTC_ADD_SMARTCARD_KEY 20 +#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 + +/* lock/unlock the agent */ +#define SSH_AGENTC_LOCK 22 +#define SSH_AGENTC_UNLOCK 23 + +/* add key with constraints */ +#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 +#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 + +#define SSH_AGENT_CONSTRAIN_LIFETIME 1 +#define SSH_AGENT_CONSTRAIN_CONFIRM 2 + +/* extended failure messages */ +#define SSH2_AGENT_FAILURE 30 + +/* additional error code for ssh.com's ssh-agent2 */ +#define SSH_COM_AGENT2_FAILURE 102 + +#define SSH_AGENT_OLD_SIGNATURE 0x01 + +struct ssh_agent_struct { + struct ssh_socket_struct *sock; + ssh_buffer ident; + unsigned int count; +}; + +#ifndef _WIN32 +/* agent.c */ +/** + * @brief Create a new ssh agent structure. + * + * @return An allocated ssh agent structure or NULL on error. + */ +struct ssh_agent_struct *agent_new(struct ssh_session_struct *session); + +void agent_close(struct ssh_agent_struct *agent); + +/** + * @brief Free an allocated ssh agent structure. + * + * @param agent The ssh agent structure to free. + */ +void agent_free(struct ssh_agent_struct *agent); + +/** + * @brief Check if the ssh agent is running. + * + * @param session The ssh session to check for the agent. + * + * @return 1 if it is running, 0 if not. + */ +int agent_is_running(struct ssh_session_struct *session); + +int ssh_agent_get_ident_count(struct ssh_session_struct *session); + +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment); + +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment); + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data); +#endif + +#endif /* __AGENT_H */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/include/libssh/auth.h b/libssh/include/libssh/auth.h new file mode 100644 index 00000000..3a6012ec --- /dev/null +++ b/libssh/include/libssh/auth.h @@ -0,0 +1,106 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AUTH_H_ +#define AUTH_H_ +#include "config.h" +#include "libssh/callbacks.h" + +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner); +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); +SSH_PACKET_CALLBACK(ssh_packet_userauth_success); +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response); + +/** @internal + * kdbint structure must be shared with message.c + * and server.c + */ +struct ssh_kbdint_struct { + uint32_t nprompts; + uint32_t nanswers; + char *name; + char *instruction; + char **prompts; + unsigned char *echo; /* bool array */ + char **answers; +}; +typedef struct ssh_kbdint_struct* ssh_kbdint; + +ssh_kbdint ssh_kbdint_new(void); +void ssh_kbdint_clean(ssh_kbdint kbd); +void ssh_kbdint_free(ssh_kbdint kbd); + + +#ifdef WITH_SSH1 +void ssh_auth1_handler(ssh_session session, uint8_t type); + +/* auth1.c */ +int ssh_userauth1_none(ssh_session session, const char *username); +int ssh_userauth1_offer_pubkey(ssh_session session, const char *username, + int type, ssh_string pubkey); +int ssh_userauth1_password(ssh_session session, const char *username, + const char *password); + + +#endif + +/** @internal + * States of authentication in the client-side. They describe + * what was the last response from the server + */ +enum ssh_auth_state_e { + /** No authentication asked */ + SSH_AUTH_STATE_NONE=0, + /** Last authentication response was a partial success */ + SSH_AUTH_STATE_PARTIAL, + /** Last authentication response was a success */ + SSH_AUTH_STATE_SUCCESS, + /** Last authentication response was failed */ + SSH_AUTH_STATE_FAILED, + /** Last authentication was erroneous */ + SSH_AUTH_STATE_ERROR, + /** Last state was a keyboard-interactive ask for info */ + SSH_AUTH_STATE_INFO, + /** Last state was a public key accepted for authentication */ + SSH_AUTH_STATE_PK_OK, + /** We asked for a keyboard-interactive authentication */ + SSH_AUTH_STATE_KBDINT_SENT + +}; + +/** @internal + * @brief states of the authentication service request + */ +enum ssh_auth_service_state_e { + /** initial state */ + SSH_AUTH_SERVICE_NONE=0, + /** Authentication service request packet sent */ + SSH_AUTH_SERVICE_SENT, + /** Service accepted */ + SSH_AUTH_SERVICE_ACCEPTED, + /** Access to service denied (fatal) */ + SSH_AUTH_SERVICE_DENIED, + /** Specific to SSH1 */ + SSH_AUTH_SERVICE_USER_SENT +}; + +#endif /* AUTH_H_ */ diff --git a/libssh/include/libssh/bind.h b/libssh/include/libssh/bind.h new file mode 100644 index 00000000..ced1c494 --- /dev/null +++ b/libssh/include/libssh/bind.h @@ -0,0 +1,53 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIND_H_ +#define BIND_H_ + +#include "libssh/priv.h" +#include "libssh/session.h" + +struct ssh_bind_struct { + struct ssh_common_struct common; /* stuff common to ssh_bind and ssh_session */ + struct ssh_bind_callbacks_struct *bind_callbacks; + void *bind_callbacks_userdata; + + struct ssh_poll_handle_struct *poll; + /* options */ + char *wanted_methods[10]; + char *banner; + char *ecdsakey; + char *dsakey; + char *rsakey; + ssh_key ecdsa; + ssh_key dsa; + ssh_key rsa; + char *bindaddr; + socket_t bindfd; + unsigned int bindport; + int blocking; + int toaccept; +}; + +struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct + *sshbind); + + +#endif /* BIND_H_ */ diff --git a/libssh/include/libssh/buffer.h b/libssh/include/libssh/buffer.h new file mode 100644 index 00000000..d7cdfbf4 --- /dev/null +++ b/libssh/include/libssh/buffer.h @@ -0,0 +1,72 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BUFFER_H_ +#define BUFFER_H_ + +#include "libssh/libssh.h" +/* + * Describes a buffer state + * [XXXXXXXXXXXXDATA PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXX] + * ^ ^ ^ ^] + * \_data points\_pos points here \_used points here | / + * here Allocated + */ +struct ssh_buffer_struct { + char *data; + uint32_t used; + uint32_t allocated; + uint32_t pos; +}; + +LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); +LIBSSH_API void *ssh_buffer_get_begin(ssh_buffer buffer); +LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); +LIBSSH_API ssh_buffer ssh_buffer_new(void); +int buffer_add_ssh_string(ssh_buffer buffer, ssh_string string); +int buffer_add_u8(ssh_buffer buffer, uint8_t data); +int buffer_add_u16(ssh_buffer buffer, uint16_t data); +int buffer_add_u32(ssh_buffer buffer, uint32_t data); +int buffer_add_u64(ssh_buffer buffer, uint64_t data); +int buffer_add_data(ssh_buffer buffer, const void *data, uint32_t len); +int buffer_prepend_data(ssh_buffer buffer, const void *data, uint32_t len); +int buffer_add_buffer(ssh_buffer buffer, ssh_buffer source); +int buffer_reinit(ssh_buffer buffer); + +/* buffer_get_rest returns a pointer to the current position into the buffer */ +void *buffer_get_rest(ssh_buffer buffer); +/* buffer_get_rest_len returns the number of bytes which can be read */ +uint32_t buffer_get_rest_len(ssh_buffer buffer); + +/* buffer_read_*() returns the number of bytes read, except for ssh strings */ +int buffer_get_u8(ssh_buffer buffer, uint8_t *data); +int buffer_get_u32(ssh_buffer buffer, uint32_t *data); +int buffer_get_u64(ssh_buffer buffer, uint64_t *data); + +uint32_t buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); +/* buffer_get_ssh_string() is an exception. if the String read is too large or invalid, it will answer NULL. */ +ssh_string buffer_get_ssh_string(ssh_buffer buffer); +/* gets a string out of a SSH-1 mpint */ +ssh_string buffer_get_mpint(ssh_buffer buffer); +/* buffer_pass_bytes acts as if len bytes have been read (used for padding) */ +uint32_t buffer_pass_bytes_end(ssh_buffer buffer, uint32_t len); +uint32_t buffer_pass_bytes(ssh_buffer buffer, uint32_t len); + +#endif /* BUFFER_H_ */ diff --git a/libssh/include/libssh/callbacks.h b/libssh/include/libssh/callbacks.h new file mode 100644 index 00000000..e15a0bd8 --- /dev/null +++ b/libssh/include/libssh/callbacks.h @@ -0,0 +1,447 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* callback.h + * This file includes the public declarations for the libssh callback mechanism + */ + +#ifndef _SSH_CALLBACK_H +#define _SSH_CALLBACK_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libssh_callbacks The libssh callbacks + * @ingroup libssh + * + * Callback which can be replaced in libssh. + * + * @{ + */ + +/** @internal + * @brief callback to process simple codes + * @param code value to transmit + * @param user Userdata to pass in callback + */ +typedef void (*ssh_callback_int) (int code, void *user); + +/** @internal + * @brief callback for data received messages. + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @param user user-supplied pointer sent along with all callback messages + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_callback_data) (const void *data, size_t len, void *user); + +typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); + +typedef int (*ssh_message_callback) (ssh_session, ssh_message message, void *user); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, int code, void *user); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, int code, void *data, size_t len, void *user); + +/** + * @brief SSH log callback. All logging messages will go through this callback + * @param session Current session handler + * @param priority Priority of the log, the smaller being the more important + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_log_callback) (ssh_session session, int priority, + const char *message, void *userdata); + +/** + * @brief SSH Connection status callback. + * @param session Current session handler + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_status_callback) (ssh_session session, float status, + void *userdata); + +/** + * @brief SSH global request callback. All global request will go through this + * callback. + * @param session Current session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_global_request_callback) (ssh_session session, + ssh_message message, void *userdata); + +/** + * The structure to replace libssh functions with appropriate callbacks. + */ +struct ssh_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called if e.g. a keyphrase is needed. + */ + ssh_auth_callback auth_function; + /** + * This function will be called each time a loggable event happens. + */ + ssh_log_callback log_function; + /** + * This function gets called during connection time to indicate the + * percentage of connection steps completed. + */ + void (*connect_status_function)(void *userdata, float status); + /** + * This function will be called each time a global request is received. + */ + ssh_global_request_callback global_request_function; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/** + * These are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This function will be called each time data appears on socket. The data + * not consumed will appear on the next data event. + */ + ssh_callback_data data; + /** This function will be called each time a controlflow state changes, i.e. + * the socket is available for reading or writing. + */ + ssh_callback_int controlflow; + /** This function will be called each time an exception appears on socket. An + * exception can be a socket problem (timeout, ...) or an end-of-file. + */ + ssh_callback_int_int exception; + /** This function is called when the ssh_socket_connect was used on the socket + * on nonblocking state, and the connection successed. + */ + ssh_callback_int_int connected; +}; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; + +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK 1 +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK 2 + +#define SSH_SOCKET_EXCEPTION_EOF 1 +#define SSH_SOCKET_EXCEPTION_ERROR 2 + +#define SSH_SOCKET_CONNECTED_OK 1 +#define SSH_SOCKET_CONNECTED_ERROR 2 +#define SSH_SOCKET_CONNECTED_TIMEOUT 3 + +/** + * @brief Initializes an ssh_callbacks_struct + * A call to this macro is mandatory when you have set a new + * ssh_callback_struct structure. Its goal is to maintain the binary + * compatibility with future versions of libssh as the structure + * evolves with time. + */ +#define ssh_callbacks_init(p) do {\ + (p)->size=sizeof(*(p)); \ +} while(0); + +/** + * @internal + * @brief tests if a callback can be called without crash + * verifies that the struct size if big enough + * verifies that the callback pointer exists + * @param p callback pointer + * @param c callback name + * @returns nonzero if callback can be called + */ +#define ssh_callbacks_exists(p,c) (\ + (p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \ + ((p)-> c != NULL) \ + ) + +/** @brief Prototype for a packet callback, to be called when a new packet arrives + * @param session The current session of the packet + * @param type packet type (see ssh2.h) + * @param packet buffer containing the packet, excluding size, type and padding fields + * @param user user argument to the callback + * and are called each time a packet shows up + * @returns SSH_PACKET_USED Packet was parsed and used + * @returns SSH_PACKET_NOT_USED Packet was not used or understood, processing must continue + */ +typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); + +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 + + +/** @brief This macro declares a packet callback handler + * @code + * SSH_PACKET_CALLBACK(mycallback){ + * ... + * } + * @endcode + */ +#define SSH_PACKET_CALLBACK(name) \ + int name (ssh_session session, uint8_t type, ssh_buffer packet, void *user) + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + uint8_t start; + /** Number of packets being handled by this callback struct */ + uint8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + /** + * User-provided data. User is free to set anything he wants here + */ + void *user; +}; + +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; + +/** + * @brief Set the session callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for auth, logging and status. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); + +/** + * @brief SSH channel data callback. Called when data is available on a channel + * @param session Current session handler + * @param channel the actual channel + * @param data the data that has been read on the channel + * @param len the length of the data + * @param is_stderr is 0 for stdout or 1 for stderr + * @param userdata Userdata to be passed to the callback function. + */ +typedef int (*ssh_channel_data_callback) (ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** + * @brief SSH channel eof callback. Called when a channel receives EOF + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_eof_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel close callback. Called when a channel is closed by remote peer + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_close_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel signal callback. Called when a channel has received a signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +/** + * @brief SSH channel exit status callback. Called when a channel has received an exit status + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_status_callback) (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +/** + * @brief SSH channel exit signal callback. Called when a channel has received an exit signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param core a boolean telling wether a core has been dumped or not + * @param errmsg the description of the exception + * @param lang the language of the description (format: RFC 3066) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +struct ssh_channel_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called when there is data available. + */ + ssh_channel_data_callback channel_data_function; + /** + * This functions will be called when the channel has received an EOF. + */ + ssh_channel_eof_callback channel_eof_function; + /** + * This functions will be called when the channel has been closed by remote + */ + ssh_channel_close_callback channel_close_function; + /** + * This functions will be called when a signal has been received + */ + ssh_channel_signal_callback channel_signal_function; + /** + * This functions will be called when an exit status has been received + */ + ssh_channel_exit_status_callback channel_exit_status_function; + /** + * This functions will be called when an exit signal has been received + */ + ssh_channel_exit_signal_callback channel_exit_signal_function; +}; +typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; + +/** + * @brief Set the channel callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for channel data and exceptions + * + * @code + * struct ssh_channel_callbacks_struct cb = { + * .userdata = data, + * .channel_data = my_channel_data_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_channel_callbacks(channel, &cb); + * @endcode + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** @} */ + +/** @group libssh_threads + * @{ + */ + +typedef int (*ssh_thread_callback) (void **lock); + +typedef unsigned long (*ssh_thread_id_callback) (void); +struct ssh_threads_callbacks_struct { + const char *type; + ssh_thread_callback mutex_init; + ssh_thread_callback mutex_destroy; + ssh_thread_callback mutex_lock; + ssh_thread_callback mutex_unlock; + ssh_thread_id_callback thread_id; +}; + +/** + * @brief sets the thread callbacks necessary if your program is using + * libssh in a multithreaded fashion. This function must be called first, + * outside of any threading context (in your main() for instance), before + * ssh_init(). + * @param cb pointer to a ssh_threads_callbacks_struct structure, which contains + * the different callbacks to be set. + * @see ssh_threads_callbacks_struct + * @see SSH_THREADS_PTHREAD + */ +LIBSSH_API int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct + *cb); + +/** + * @brief returns a pointer on the pthread threads callbacks, to be used with + * ssh_threads_set_callbacks. + * @warning you have to link with the library ssh_threads. + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); + +/** + * @brief returns a pointer on the noop threads callbacks, to be used with + * ssh_threads_set_callbacks. These callbacks do nothing and are being used by + * default. + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /*_SSH_CALLBACK_H */ + +/* @} */ diff --git a/libssh/include/libssh/channels.h b/libssh/include/libssh/channels.h new file mode 100644 index 00000000..45152236 --- /dev/null +++ b/libssh/include/libssh/channels.h @@ -0,0 +1,118 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CHANNELS_H_ +#define CHANNELS_H_ +#include "libssh/priv.h" + +/** @internal + * Describes the different possible states in a + * outgoing (client) channel request + */ +enum ssh_channel_request_state_e { + /** No request has been made */ + SSH_CHANNEL_REQ_STATE_NONE = 0, + /** A request has been made and answer is pending */ + SSH_CHANNEL_REQ_STATE_PENDING, + /** A request has been replied and accepted */ + SSH_CHANNEL_REQ_STATE_ACCEPTED, + /** A request has been replied and refused */ + SSH_CHANNEL_REQ_STATE_DENIED, + /** A request has been replied and an error happend */ + SSH_CHANNEL_REQ_STATE_ERROR +}; + +enum ssh_channel_state_e { + SSH_CHANNEL_STATE_NOT_OPEN = 0, + SSH_CHANNEL_STATE_OPENING, + SSH_CHANNEL_STATE_OPEN_DENIED, + SSH_CHANNEL_STATE_OPEN, + SSH_CHANNEL_STATE_CLOSED +}; + +/* The channel has been closed by the remote side */ +#define SSH_CHANNEL_FLAG_CLOSED_REMOTE 0x1 +/* The channel has been freed by the calling program */ +#define SSH_CHANNEL_FLAG_FREED_LOCAL 0x2 +/* the channel has not yet been bound to a remote one */ +#define SSH_CHANNEL_FLAG_NOT_BOUND 0x4 + +struct ssh_channel_struct { + ssh_session session; /* SSH_SESSION pointer */ + uint32_t local_channel; + uint32_t local_window; + int local_eof; + uint32_t local_maxpacket; + + uint32_t remote_channel; + uint32_t remote_window; + int remote_eof; /* end of file received */ + uint32_t remote_maxpacket; + enum ssh_channel_state_e state; + int delayed_close; + int flags; + ssh_buffer stdout_buffer; + ssh_buffer stderr_buffer; + void *userarg; + int version; + int exit_status; + enum ssh_channel_request_state_e request_state; + ssh_channel_callbacks callbacks; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf); +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail); +SSH_PACKET_CALLBACK(ssh_packet_channel_success); +SSH_PACKET_CALLBACK(ssh_packet_channel_failure); +SSH_PACKET_CALLBACK(ssh_request_success); +SSH_PACKET_CALLBACK(ssh_request_denied); + +SSH_PACKET_CALLBACK(channel_rcv_change_window); +SSH_PACKET_CALLBACK(channel_rcv_eof); +SSH_PACKET_CALLBACK(channel_rcv_close); +SSH_PACKET_CALLBACK(channel_rcv_request); +SSH_PACKET_CALLBACK(channel_rcv_data); + +ssh_channel ssh_channel_new(ssh_session session); +int channel_default_bufferize(ssh_channel channel, void *data, int len, + int is_stderr); +int ssh_channel_flush(ssh_channel channel); +uint32_t ssh_channel_new_id(ssh_session session); +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id); +int channel_write_common(ssh_channel channel, const void *data, + uint32_t len, int is_stderr); +void ssh_channel_do_free(ssh_channel channel); +#ifdef WITH_SSH1 +SSH_PACKET_CALLBACK(ssh_packet_data1); +SSH_PACKET_CALLBACK(ssh_packet_close1); +SSH_PACKET_CALLBACK(ssh_packet_exist_status1); + +/* channels1.c */ +int channel_open_session1(ssh_channel channel); +int channel_request_pty_size1(ssh_channel channel, const char *terminal, + int cols, int rows); +int channel_change_pty_size1(ssh_channel channel, int cols, int rows); +int channel_request_shell1(ssh_channel channel); +int channel_request_exec1(ssh_channel channel, const char *cmd); +int channel_write1(ssh_channel channel, const void *data, int len); +ssh_channel ssh_get_channel1(ssh_session session); +#endif + +#endif /* CHANNELS_H_ */ diff --git a/libssh/include/libssh/crc32.h b/libssh/include/libssh/crc32.h new file mode 100644 index 00000000..07c0cafc --- /dev/null +++ b/libssh/include/libssh/crc32.h @@ -0,0 +1,28 @@ +/* + * crc32.c - simple CRC32 code + * + * This file is part of the SSH Library + * + * Copyright (c) 2005 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CRC32_H +#define _CRC32_H + +uint32_t ssh_crc32(const char *buf, uint32_t len); + +#endif /* _CRC32_H */ diff --git a/libssh/include/libssh/crypto.h b/libssh/include/libssh/crypto.h new file mode 100644 index 00000000..5376ca61 --- /dev/null +++ b/libssh/include/libssh/crypto.h @@ -0,0 +1,113 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * crypto.h is an include file for internal cryptographic structures of libssh + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT +#include +#endif +#include "libssh/wrapper.h" + +#ifdef cbc_encrypt +#undef cbc_encrypt +#endif +#ifdef cbc_decrypt +#undef cbc_decrypt +#endif + +#ifdef HAVE_OPENSSL_ECDH_H +#include +#endif +#include "libssh/ecdh.h" +#include "libssh/kex.h" + +enum ssh_key_exchange_e { + /* diffie-hellman-group1-sha1 */ + SSH_KEX_DH_GROUP1_SHA1=1, + /* diffie-hellman-group14-sha1 */ + SSH_KEX_DH_GROUP14_SHA1, + /* ecdh-sha2-nistp256 */ + SSH_KEX_ECDH_SHA2_NISTP256 +}; + +struct ssh_crypto_struct { + bignum e,f,x,k,y; +#ifdef HAVE_ECDH + EC_KEY *ecdh_privkey; + ssh_string ecdh_client_pubkey; + ssh_string ecdh_server_pubkey; +#endif + ssh_string dh_server_signature; /* information used by dh_handshake. */ + size_t digest_len; /* len of all the fields below */ + unsigned char *session_id; + unsigned char *secret_hash; /* Secret hash is same as session id until re-kex */ + unsigned char *encryptIV; + unsigned char *decryptIV; + unsigned char *decryptkey; + unsigned char *encryptkey; + unsigned char *encryptMAC; + unsigned char *decryptMAC; + unsigned char hmacbuf[EVP_MAX_MD_SIZE]; + struct ssh_cipher_struct *in_cipher, *out_cipher; /* the cipher structures/objects */ + ssh_string server_pubkey; + const char *server_pubkey_type; + int do_compress_out; /* idem */ + int do_compress_in; /* don't set them, set the option instead */ + int delayed_compress_in; /* Use of zlib@openssh.org */ + int delayed_compress_out; + void *compress_out_ctx; /* don't touch it */ + void *compress_in_ctx; /* really, don't */ + /* kex sent by server, client, and mutually elected methods */ + struct ssh_kex_struct server_kex; + struct ssh_kex_struct client_kex; + char *kex_methods[SSH_KEX_METHODS]; + enum ssh_key_exchange_e kex_type; + enum ssh_mac_e mac_type; /* Mac operations to use for key gen */ +}; + +struct ssh_cipher_struct { + const char *name; /* ssh name of the algorithm */ + unsigned int blocksize; /* blocksize of the algo */ + unsigned int keylen; /* length of the key structure */ +#ifdef HAVE_LIBGCRYPT + gcry_cipher_hd_t *key; +#elif defined HAVE_LIBCRYPTO + void *key; /* a key buffer allocated for the algo */ + void *IV; +#endif + unsigned int keysize; /* bytes of key used. != keylen */ + /* sets the new key for immediate use */ + int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + void (*cbc_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len); + void (*cbc_decrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len); +}; + +/* vim: set ts=2 sw=2 et cindent: */ +#endif /* _CRYPTO_H_ */ diff --git a/libssh/include/libssh/dh.h b/libssh/include/libssh/dh.h new file mode 100644 index 00000000..e1039e24 --- /dev/null +++ b/libssh/include/libssh/dh.h @@ -0,0 +1,55 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DH_H_ +#define DH_H_ + +#include "config.h" + +#include "libssh/crypto.h" + +void ssh_print_bignum(const char *which,bignum num); +int dh_generate_e(ssh_session session); +int dh_generate_f(ssh_session session); +int dh_generate_x(ssh_session session); +int dh_generate_y(ssh_session session); + +int ssh_crypto_init(void); +void ssh_crypto_finalize(void); + +ssh_string dh_get_e(ssh_session session); +ssh_string dh_get_f(ssh_session session); +int dh_import_f(ssh_session session,ssh_string f_string); +int dh_import_e(ssh_session session, ssh_string e_string); +void dh_import_pubkey(ssh_session session,ssh_string pubkey_string); +int dh_build_k(ssh_session session); +int ssh_client_dh_init(ssh_session session); +int ssh_client_dh_reply(ssh_session session, ssh_buffer packet); + +int make_sessionid(ssh_session session); +/* add data for the final cookie */ +int hashbufin_add_cookie(ssh_session session, unsigned char *cookie); +int hashbufout_add_cookie(ssh_session session); +int generate_session_keys(ssh_session session); +bignum make_string_bn(ssh_string string); +ssh_string make_bignum_string(bignum num); + + +#endif /* DH_H_ */ diff --git a/libssh/include/libssh/ecdh.h b/libssh/include/libssh/ecdh.h new file mode 100644 index 00000000..b35b2af7 --- /dev/null +++ b/libssh/include/libssh/ecdh.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "config.h" + +#ifdef HAVE_LIBCRYPTO +#ifdef HAVE_OPENSSL_ECDH_H + +#define HAVE_ECDH + +#endif /* HAVE_OPENSSL_ECDH_H */ +#endif /* HAVE_LIBCRYPTO */ + +int ssh_client_ecdh_init(ssh_session session); +int ssh_client_ecdh_reply(ssh_session session, ssh_buffer packet); + +#ifdef WITH_SERVER +int ssh_server_ecdh_init(ssh_session session, ssh_buffer packet); +#endif /* WITH_SERVER */ + +#endif /* ECDH_H_ */ diff --git a/libssh/include/libssh/kex.h b/libssh/include/libssh/kex.h new file mode 100644 index 00000000..37d4be8e --- /dev/null +++ b/libssh/include/libssh/kex.h @@ -0,0 +1,50 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEX_H_ +#define KEX_H_ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" + +#define SSH_KEX_METHODS 10 + +struct ssh_kex_struct { + unsigned char cookie[16]; + char *methods[SSH_KEX_METHODS]; +}; + +SSH_PACKET_CALLBACK(ssh_packet_kexinit); +#ifdef WITH_SSH1 +SSH_PACKET_CALLBACK(ssh_packet_publickey1); +#endif + +int ssh_send_kex(ssh_session session, int server_kex); +void ssh_list_kex(ssh_session session, struct ssh_kex_struct *kex); +int set_client_kex(ssh_session session); +int ssh_kex_select_methods(ssh_session session); +int verify_existing_algo(int algo, const char *name); +char **space_tokenize(const char *chain); +int ssh_get_kex1(ssh_session session); +char *ssh_find_matching(const char *in_d, const char *what_d); +const char *ssh_kex_get_supported_method(uint32_t algo); +const char *ssh_kex_get_description(uint32_t algo); + +#endif /* KEX_H_ */ diff --git a/libssh/include/libssh/keys.h b/libssh/include/libssh/keys.h new file mode 100644 index 00000000..6f08e070 --- /dev/null +++ b/libssh/include/libssh/keys.h @@ -0,0 +1,56 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEYS_H_ +#define KEYS_H_ + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/wrapper.h" + +struct ssh_public_key_struct { + int type; + const char *type_c; /* Don't free it ! it is static */ +#ifdef HAVE_LIBGCRYPT + gcry_sexp_t dsa_pub; + gcry_sexp_t rsa_pub; +#elif HAVE_LIBCRYPTO + DSA *dsa_pub; + RSA *rsa_pub; +#endif +}; + +struct ssh_private_key_struct { + int type; +#ifdef HAVE_LIBGCRYPT + gcry_sexp_t dsa_priv; + gcry_sexp_t rsa_priv; +#elif defined HAVE_LIBCRYPTO + DSA *dsa_priv; + RSA *rsa_priv; +#endif +}; + +const char *ssh_type_to_char(int type); +int ssh_type_from_name(const char *name); + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s); + +#endif /* KEYS_H_ */ diff --git a/libssh/include/libssh/legacy.h b/libssh/include/libssh/legacy.h new file mode 100644 index 00000000..771fe56f --- /dev/null +++ b/libssh/include/libssh/legacy.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Since libssh.h includes legacy.h, it's important that libssh.h is included + * first. we don't define LEGACY_H now because we want it to be defined when + * included from libssh.h + * All function calls declared in this header are deprecated and meant to be + * removed in future. + */ + +#ifndef LEGACY_H_ +#define LEGACY_H_ + +typedef struct ssh_private_key_struct* ssh_private_key; +typedef struct ssh_public_key_struct* ssh_public_key; + +LIBSSH_API int ssh_auth_list(ssh_session session); +LIBSSH_API int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey); +LIBSSH_API int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent_pubkey(ssh_session session, const char *username, + ssh_public_key publickey); +#endif +LIBSSH_API int ssh_userauth_autopubkey(ssh_session session, const char *passphrase); +LIBSSH_API int ssh_userauth_privatekey_file(ssh_session session, const char *username, + const char *filename, const char *passphrase); + +LIBSSH_API void buffer_free(ssh_buffer buffer); +LIBSSH_API void *buffer_get(ssh_buffer buffer); +LIBSSH_API uint32_t buffer_get_len(ssh_buffer buffer); +LIBSSH_API ssh_buffer buffer_new(void); + +LIBSSH_API ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API ssh_channel channel_forward_accept(ssh_session session, int timeout_ms); +LIBSSH_API int channel_close(ssh_channel channel); +LIBSSH_API int channel_forward_cancel(ssh_session session, const char *address, int port); +LIBSSH_API int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +LIBSSH_API void channel_free(ssh_channel channel); +LIBSSH_API int channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session channel_get_session(ssh_channel channel); +LIBSSH_API int channel_is_closed(ssh_channel channel); +LIBSSH_API int channel_is_eof(ssh_channel channel); +LIBSSH_API int channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel channel_new(ssh_session session); +LIBSSH_API int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int channel_open_session(ssh_channel channel); +LIBSSH_API int channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); + +LIBSSH_API int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr); + +LIBSSH_API int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int channel_request_pty(ssh_channel channel); +LIBSSH_API int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int channel_request_shell(ssh_channel channel); +LIBSSH_API int channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int channel_request_sftp(ssh_channel channel); +LIBSSH_API int channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int channel_send_eof(ssh_channel channel); +LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); + +LIBSSH_API void privatekey_free(ssh_private_key prv); +LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +LIBSSH_API void publickey_free(ssh_public_key key); +LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type); +LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, + ssh_string *publickey, int *type); +LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); + +LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); + +LIBSSH_API ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype); +LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); + +LIBSSH_API void string_burn(ssh_string str); +LIBSSH_API ssh_string string_copy(ssh_string str); +LIBSSH_API void *string_data(ssh_string str); +LIBSSH_API int string_fill(ssh_string str, const void *data, size_t len); +LIBSSH_API void string_free(ssh_string str); +LIBSSH_API ssh_string string_from_char(const char *what); +LIBSSH_API size_t string_len(ssh_string str); +LIBSSH_API ssh_string string_new(size_t size); +LIBSSH_API char *string_to_char(ssh_string str); + +#endif /* LEGACY_H_ */ diff --git a/libssh/include/libssh/libcrypto.h b/libssh/include/libssh/libcrypto.h new file mode 100644 index 00000000..54c78b16 --- /dev/null +++ b/libssh/include/libssh/libcrypto.h @@ -0,0 +1,86 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBCRYPTO_H_ +#define LIBCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBCRYPTO + +#include +#include +#include +#include +#include +#ifdef HAVE_OPENSSL_ECC +#include +#endif + +typedef SHA_CTX* SHACTX; +typedef SHA256_CTX* SHA256CTX; +typedef MD5_CTX* MD5CTX; +typedef HMAC_CTX* HMACCTX; + +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#ifdef MD5_DIGEST_LEN + #undef MD5_DIGEST_LEN +#endif +#define MD5_DIGEST_LEN MD5_DIGEST_LENGTH + +#ifdef HAVE_OPENSSL_ECC +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#endif + +#include +#include +#define OPENSSL_0_9_7b 0x0090702fL +#if (OPENSSL_VERSION_NUMBER <= OPENSSL_0_9_7b) +#define BROKEN_AES_CTR +#endif +typedef BIGNUM* bignum; +typedef BN_CTX* bignum_CTX; + +#define bignum_new() BN_new() +#define bignum_free(num) BN_clear_free(num) +#define bignum_set_word(bn,n) BN_set_word(bn,n) +#define bignum_bin2bn(bn,datalen,data) BN_bin2bn(bn,datalen,data) +#define bignum_bn2dec(num) BN_bn2dec(num) +#define bignum_dec2bn(bn,data) BN_dec2bn(data,bn) +#define bignum_bn2hex(num) BN_bn2hex(num) +#define bignum_rand(rnd, bits, top, bottom) BN_rand(rnd,bits,top,bottom) +#define bignum_ctx_new() BN_CTX_new() +#define bignum_ctx_free(num) BN_CTX_free(num) +#define bignum_mod_exp(dest,generator,exp,modulo,ctx) BN_mod_exp(dest,generator,exp,modulo,ctx) +#define bignum_num_bytes(num) BN_num_bytes(num) +#define bignum_num_bits(num) BN_num_bits(num) +#define bignum_is_bit_set(num,bit) BN_is_bit_set(num,bit) +#define bignum_bn2bin(num,ptr) BN_bn2bin(num,ptr) +#define bignum_cmp(num1,num2) BN_cmp(num1,num2) + +SHA256CTX sha256_init(void); +void sha256_update(SHA256CTX c, const void *data, unsigned long len); +void sha256_final(unsigned char *md, SHA256CTX c); + +struct ssh_cipher_struct *ssh_get_ciphertab(void); + +#endif /* HAVE_LIBCRYPTO */ + +#endif /* LIBCRYPTO_H_ */ diff --git a/libssh/include/libssh/libgcrypt.h b/libssh/include/libssh/libgcrypt.h new file mode 100644 index 00000000..54982063 --- /dev/null +++ b/libssh/include/libssh/libgcrypt.h @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBGCRYPT_H_ +#define LIBGCRYPT_H_ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +typedef gcry_md_hd_t SHACTX; +typedef gcry_md_hd_t MD5CTX; +typedef gcry_md_hd_t HMACCTX; +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA384_DIGEST_LENGTH 48 +#define SHA512_DIGEST_LENGTH 64 + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 36 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +typedef gcry_mpi_t bignum; + +/* missing gcrypt functions */ +int my_gcry_dec2bn(bignum *bn, const char *data); +char *my_gcry_bn2dec(bignum bn); + +#define bignum_new() gcry_mpi_new(0) +#define bignum_free(num) gcry_mpi_release(num) +#define bignum_set_word(bn,n) gcry_mpi_set_ui(bn,n) +#define bignum_bin2bn(bn,datalen,data) gcry_mpi_scan(data,GCRYMPI_FMT_USG,bn,datalen,NULL) +#define bignum_bn2dec(num) my_gcry_bn2dec(num) +#define bignum_dec2bn(num, data) my_gcry_dec2bn(data, num) +#define bignum_bn2hex(num,data) gcry_mpi_aprint(GCRYMPI_FMT_HEX,data,NULL,num) +#define bignum_hex2bn(num,datalen,data) gcry_mpi_scan(num,GCRYMPI_FMT_HEX,data,datalen,NULL) +#define bignum_rand(num,bits) gcry_mpi_randomize(num,bits,GCRY_STRONG_RANDOM),gcry_mpi_set_bit(num,bits-1),gcry_mpi_set_bit(num,0) +#define bignum_mod_exp(dest,generator,exp,modulo) gcry_mpi_powm(dest,generator,exp,modulo) +#define bignum_num_bits(num) gcry_mpi_get_nbits(num) +#define bignum_num_bytes(num) ((gcry_mpi_get_nbits(num)+7)/8) +#define bignum_is_bit_set(num,bit) gcry_mpi_test_bit(num,bit) +#define bignum_bn2bin(num,datalen,data) gcry_mpi_print(GCRYMPI_FMT_USG,data,datalen,NULL,num) +#define bignum_cmp(num1,num2) gcry_mpi_cmp(num1,num2) + +#endif /* HAVE_LIBGCRYPT */ + +struct ssh_cipher_struct *ssh_get_ciphertab(void); + +#endif /* LIBGCRYPT_H_ */ diff --git a/libssh/include/libssh/libssh.h b/libssh/include/libssh/libssh.h new file mode 100644 index 00000000..d9cc8478 --- /dev/null +++ b/libssh/include/libssh/libssh.h @@ -0,0 +1,596 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_H +#define _LIBSSH_H + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef LIBSSH_STATIC + #define LIBSSH_API + #else + #ifdef LIBSSH_EXPORTS + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllexport)) + #else + #define LIBSSH_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllimport)) + #else + #define LIBSSH_API __declspec(dllimport) + #endif + #endif + #endif +#else + #if __GNUC__ >= 4 && !defined(__OS2__) + #define LIBSSH_API __attribute__((visibility("default"))) + #else + #define LIBSSH_API + #endif +#endif + +#ifdef _MSC_VER + /* Visual Studio hasn't inttypes.h so it doesn't know uint32_t */ + typedef int int32_t; + typedef unsigned int uint32_t; + typedef unsigned short uint16_t; + typedef unsigned char uint8_t; + typedef unsigned long long uint64_t; + typedef int mode_t; +#else /* _MSC_VER */ + #include + #include +#endif /* _MSC_VER */ + +#ifdef _WIN32 + #include +#else /* _WIN32 */ + #include /* for fd_set * */ + #include +#endif /* _WIN32 */ + +#define SSH_STRINGIFY(s) SSH_TOSTRING(s) +#define SSH_TOSTRING(s) #s + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR 0 +#define LIBSSH_VERSION_MINOR 6 +#define LIBSSH_VERSION_MICRO 0 + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#ifdef __GNUC__ +#define SSH_DEPRECATED __attribute__ ((deprecated)) +#else +#define SSH_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct ssh_agent_struct* ssh_agent; +typedef struct ssh_buffer_struct* ssh_buffer; +typedef struct ssh_channel_struct* ssh_channel; +typedef struct ssh_message_struct* ssh_message; +typedef struct ssh_pcap_file_struct* ssh_pcap_file; +typedef struct ssh_key_struct* ssh_key; +typedef struct ssh_scp_struct* ssh_scp; +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_string_struct* ssh_string; +typedef struct ssh_event_struct* ssh_event; + +/* Socket type */ +#ifdef _WIN32 +#ifndef socket_t +typedef SOCKET socket_t; +#endif /* socket_t */ +#else /* _WIN32 */ +#ifndef socket_t +typedef int socket_t; +#endif +#endif /* _WIN32 */ + +#define SSH_INVALID_SOCKET ((socket_t) -1) + +/* the offsets of methods */ +enum ssh_kex_types_e { + SSH_KEX=0, + SSH_HOSTKEYS, + SSH_CRYPT_C_S, + SSH_CRYPT_S_C, + SSH_MAC_C_S, + SSH_MAC_S_C, + SSH_COMP_C_S, + SSH_COMP_S_C, + SSH_LANG_C_S, + SSH_LANG_S_C +}; + +#define SSH_CRYPT 2 +#define SSH_MAC 3 +#define SSH_COMP 4 +#define SSH_LANG 5 + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +/* auth flags */ +#define SSH_AUTH_METHOD_UNKNOWN 0 +#define SSH_AUTH_METHOD_NONE 0x0001 +#define SSH_AUTH_METHOD_PASSWORD 0x0002 +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004 +#define SSH_AUTH_METHOD_HOSTBASED 0x0008 +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010 + +/* messages */ +enum ssh_requests_e { + SSH_REQUEST_AUTH=1, + SSH_REQUEST_CHANNEL_OPEN, + SSH_REQUEST_CHANNEL, + SSH_REQUEST_SERVICE, + SSH_REQUEST_GLOBAL +}; + +enum ssh_channel_type_e { + SSH_CHANNEL_UNKNOWN=0, + SSH_CHANNEL_SESSION, + SSH_CHANNEL_DIRECT_TCPIP, + SSH_CHANNEL_FORWARDED_TCPIP, + SSH_CHANNEL_X11 +}; + +enum ssh_channel_requests_e { + SSH_CHANNEL_REQUEST_UNKNOWN=0, + SSH_CHANNEL_REQUEST_PTY, + SSH_CHANNEL_REQUEST_EXEC, + SSH_CHANNEL_REQUEST_SHELL, + SSH_CHANNEL_REQUEST_ENV, + SSH_CHANNEL_REQUEST_SUBSYSTEM, + SSH_CHANNEL_REQUEST_WINDOW_CHANGE, + SSH_CHANNEL_REQUEST_X11 +}; + +enum ssh_global_requests_e { + SSH_GLOBAL_REQUEST_UNKNOWN=0, + SSH_GLOBAL_REQUEST_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD, +}; + +enum ssh_publickey_state_e { + SSH_PUBLICKEY_STATE_ERROR=-1, + SSH_PUBLICKEY_STATE_NONE=0, + SSH_PUBLICKEY_STATE_VALID=1, + SSH_PUBLICKEY_STATE_WRONG=2 +}; + +/* status flags */ +#define SSH_CLOSED 0x01 +#define SSH_READ_PENDING 0x02 +#define SSH_CLOSED_ERROR 0x04 +#define SSH_WRITE_PENDING 0x08 + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +#ifndef MD5_DIGEST_LEN + #define MD5_DIGEST_LEN 16 +#endif +/* errors */ + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +/* some types for keys */ +enum ssh_keytypes_e{ + SSH_KEYTYPE_UNKNOWN=0, + SSH_KEYTYPE_DSS=1, + SSH_KEYTYPE_RSA, + SSH_KEYTYPE_RSA1, + SSH_KEYTYPE_ECDSA +}; + +enum ssh_keycmp_e { + SSH_KEY_CMP_PUBLIC = 0, + SSH_KEY_CMP_PRIVATE +}; + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +/** + * @addtogroup libssh_log + * + * @{ + */ + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only warnings + */ + SSH_LOG_WARNING, + /** High level protocol information + */ + SSH_LOG_PROTOCOL, + /** Lower level protocol infomations, packet level + */ + SSH_LOG_PACKET, + /** Every function path + */ + SSH_LOG_FUNCTIONS +}; +/** @} */ +#define SSH_LOG_RARE SSH_LOG_WARNING + +/** + * @name Logging levels + * + * @brief Debug levels for logging. + * @{ + */ + +/** No logging at all */ +#define SSH_LOG_NONE 0 +/** Show only warnings */ +#define SSH_LOG_WARN 1 +/** Get some information what's going on */ +#define SSH_LOG_INFO 2 +/** Get detailed debuging information **/ +#define SSH_LOG_DEBUG 3 +/** Get trace output, packet information, ... */ +#define SSH_LOG_TRACE 4 + +/** @} */ + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS +}; + +enum { + /** Code is going to write/create remote files */ + SSH_SCP_WRITE, + /** Code is going to read remote files */ + SSH_SCP_READ, + SSH_SCP_RECURSIVE=0x10 +}; + +enum ssh_scp_request_types { + /** A new directory is going to be pulled */ + SSH_SCP_REQUEST_NEWDIR=1, + /** A new file is going to be pulled */ + SSH_SCP_REQUEST_NEWFILE, + /** End of requests */ + SSH_SCP_REQUEST_EOF, + /** End of directory */ + SSH_SCP_REQUEST_ENDDIR, + /** Warning received */ + SSH_SCP_REQUEST_WARNING +}; + +LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); +LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API int ssh_channel_close(ssh_channel channel); +LIBSSH_API void ssh_channel_free(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); +LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); +LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel ssh_channel_new(ssh_session session); +LIBSSH_API int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_session(ssh_channel channel); +LIBSSH_API int ssh_channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr); +LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +LIBSSH_API int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); +LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); +LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel); +LIBSSH_API int ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len); +LIBSSH_API uint32_t ssh_channel_window_size(ssh_channel channel); + +LIBSSH_API char *ssh_basename (const char *path); +LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash); +LIBSSH_API int ssh_connect(ssh_session session); +LIBSSH_API const char *ssh_copyright(void); +LIBSSH_API void ssh_disconnect(ssh_session session); +LIBSSH_API char *ssh_dirname (const char *path); +LIBSSH_API int ssh_finalize(void); +LIBSSH_API ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms); +LIBSSH_API int ssh_forward_cancel(ssh_session session, const char *address, int port); +LIBSSH_API int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +LIBSSH_API void ssh_free(ssh_session session); +LIBSSH_API const char *ssh_get_disconnect_message(ssh_session session); +LIBSSH_API const char *ssh_get_error(void *error); +LIBSSH_API int ssh_get_error_code(void *error); +LIBSSH_API socket_t ssh_get_fd(ssh_session session); +LIBSSH_API char *ssh_get_hexa(const unsigned char *what, size_t len); +LIBSSH_API char *ssh_get_issue_banner(ssh_session session); +LIBSSH_API int ssh_get_openssh_version(ssh_session session); +LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); +LIBSSH_API int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash); +LIBSSH_API int ssh_get_random(void *where,int len,int strong); +LIBSSH_API int ssh_get_version(ssh_session session); +LIBSSH_API int ssh_get_status(ssh_session session); +LIBSSH_API int ssh_init(void); +LIBSSH_API int ssh_is_blocking(ssh_session session); +LIBSSH_API int ssh_is_connected(ssh_session session); +LIBSSH_API int ssh_is_server_known(ssh_session session); + +/* legacy */ +LIBSSH_API void ssh_log(ssh_session session, + int prioriry, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +LIBSSH_API ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_reply_success(ssh_message msg); +LIBSSH_API void ssh_message_free(ssh_message msg); +LIBSSH_API ssh_message ssh_message_get(ssh_session session); +LIBSSH_API int ssh_message_subtype(ssh_message msg); +LIBSSH_API int ssh_message_type(ssh_message msg); +LIBSSH_API int ssh_mkdir (const char *pathname, mode_t mode); +LIBSSH_API ssh_session ssh_new(void); + +LIBSSH_API int ssh_options_copy(ssh_session src, ssh_session *dest); +LIBSSH_API int ssh_options_getopt(ssh_session session, int *argcptr, char **argv); +LIBSSH_API int ssh_options_parse_config(ssh_session session, const char *filename); +LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value); +LIBSSH_API int ssh_options_get(ssh_session session, enum ssh_options_e type, + char **value); +LIBSSH_API int ssh_options_get_port(ssh_session session, unsigned int * port_target); +LIBSSH_API int ssh_pcap_file_close(ssh_pcap_file pcap); +LIBSSH_API void ssh_pcap_file_free(ssh_pcap_file pcap); +LIBSSH_API ssh_pcap_file ssh_pcap_file_new(void); +LIBSSH_API int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename); + +/** + * @brief SSH authentication callback. + * + * @param prompt Prompt to be displayed. + * @param buf Buffer to save the password. You should null-terminate it. + * @param len Length of the buffer. + * @param echo Enable or disable the echo of what you type. + * @param verify Should the password be verified? + * @param userdata Userdata to be passed to the callback function. Useful + * for GUI applications. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +LIBSSH_API ssh_key ssh_key_new(void); +LIBSSH_API void ssh_key_free (ssh_key key); +LIBSSH_API enum ssh_keytypes_e ssh_key_type(const ssh_key key); +LIBSSH_API const char *ssh_key_type_to_char(enum ssh_keytypes_e type); +LIBSSH_API enum ssh_keytypes_e ssh_key_type_from_name(const char *name); +LIBSSH_API int ssh_key_is_public(const ssh_key k); +LIBSSH_API int ssh_key_is_private(const ssh_key k); +LIBSSH_API int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_pubkey_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key); +LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename); + +LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); +LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +LIBSSH_API int ssh_scp_close(ssh_scp scp); +LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +LIBSSH_API void ssh_scp_free(ssh_scp scp); +LIBSSH_API int ssh_scp_init(ssh_scp scp); +LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); +LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout); +LIBSSH_API int ssh_service_request(ssh_session session, const char *service); +LIBSSH_API void ssh_set_blocking(ssh_session session, int blocking); +LIBSSH_API void ssh_set_fd_except(ssh_session session); +LIBSSH_API void ssh_set_fd_toread(ssh_session session); +LIBSSH_API void ssh_set_fd_towrite(ssh_session session); +LIBSSH_API void ssh_silent_disconnect(ssh_session session); +LIBSSH_API int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile); + +/* USERAUTH */ +LIBSSH_API int ssh_userauth_none(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey); +LIBSSH_API int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent(ssh_session session, + const char *username); +#endif +LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase); +LIBSSH_API int ssh_userauth_password(ssh_session session, + const char *username, + const char *password); + +LIBSSH_API int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods); +LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); +LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); +LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer); +LIBSSH_API const char *ssh_version(int req_version); +LIBSSH_API int ssh_write_knownhost(ssh_session session); + +LIBSSH_API void ssh_string_burn(ssh_string str); +LIBSSH_API ssh_string ssh_string_copy(ssh_string str); +LIBSSH_API void *ssh_string_data(ssh_string str); +LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); +LIBSSH_API void ssh_string_free(ssh_string str); +LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API size_t ssh_string_len(ssh_string str); +LIBSSH_API ssh_string ssh_string_new(size_t size); +LIBSSH_API const char *ssh_string_get_char(ssh_string str); +LIBSSH_API char *ssh_string_to_char(ssh_string str); +LIBSSH_API void ssh_string_free_char(char *s); + +LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, + int verify); + + +typedef int (*ssh_event_callback)(socket_t fd, int revents, void *userdata); + +LIBSSH_API ssh_event ssh_event_new(void); +LIBSSH_API int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata); +LIBSSH_API int ssh_event_add_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_dopoll(ssh_event event, int timeout); +LIBSSH_API int ssh_event_remove_fd(ssh_event event, socket_t fd); +LIBSSH_API int ssh_event_remove_session(ssh_event event, ssh_session session); +LIBSSH_API void ssh_event_free(ssh_event event); +LIBSSH_API const char* ssh_get_serverbanner(ssh_session session); + +#ifndef LIBSSH_LEGACY_0_4 +#include "libssh/legacy.h" +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBSSH_H */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/include/libssh/libsshpp.hpp b/libssh/include/libssh/libsshpp.hpp new file mode 100644 index 00000000..16e27dd8 --- /dev/null +++ b/libssh/include/libssh/libsshpp.hpp @@ -0,0 +1,596 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBSSHPP_HPP_ +#define LIBSSHPP_HPP_ + +/** + * @defgroup ssh_cpp The libssh C++ wrapper + * + * The C++ bindings for libssh are completely embedded in a single .hpp file, and + * this for two reasons: + * - C++ is hard to keep binary compatible, C is easy. We try to keep libssh C version + * as much as possible binary compatible between releases, while this would be hard for + * C++. If you compile your program with these headers, you will only link to the C version + * of libssh which will be kept ABI compatible. No need to recompile your C++ program + * each time a new binary-compatible version of libssh is out + * - Most of the functions in this file are really short and are probably worth the "inline" + * linking mode, which the compiler can decide to do in some case. There would be nearly no + * performance penalty of using the wrapper rather than native calls. + * + * Please visit the documentation of ssh::Session and ssh::Channel + * @see ssh::Session + * @see ssh::Channel + * + * If you wish not to use C++ exceptions, please define SSH_NO_CPP_EXCEPTIONS: + * @code + * #define SSH_NO_CPP_EXCEPTIONS + * #include + * @endcode + * All functions will then return SSH_ERROR in case of error. + * @{ + */ + +/* do not use deprecated functions */ +#define LIBSSH_LEGACY_0_4 + +#include +#include +#include +#include +#include + +namespace ssh { + +class Channel; +/** Some people do not like C++ exceptions. With this define, we give + * the choice to use or not exceptions. + * @brief if defined, disable C++ exceptions for libssh c++ wrapper + */ +#ifndef SSH_NO_CPP_EXCEPTIONS + +/** @brief This class describes a SSH Exception object. This object can be thrown + * by several SSH functions that interact with the network, and may fail because of + * socket, protocol or memory errors. + */ +class SshException{ +public: + SshException(ssh_session csession){ + code=ssh_get_error_code(csession); + description=std::string(ssh_get_error(csession)); + } + SshException(const SshException &e){ + code=e.code; + description=e.description; + } + /** @brief returns the Error code + * @returns SSH_FATAL Fatal error happened (not recoverable) + * @returns SSH_REQUEST_DENIED Request was denied by remote host + * @see ssh_get_error_code + */ + int getCode(){ + return code; + } + /** @brief returns the error message of the last exception + * @returns pointer to a c string containing the description of error + * @see ssh_get_error + */ + std::string getError(){ + return description; + } +private: + int code; + std::string description; +}; + +/** @internal + * @brief Macro to throw exception if there was an error + */ +#define ssh_throw(x) if((x)==SSH_ERROR) throw SshException(getCSession()) +#define ssh_throw_null(CSession,x) if((x)==NULL) throw SshException(CSession) +#define void_throwable void +#define return_throwable return + +#else + +/* No exception at all. All functions will return an error code instead + * of an exception + */ +#define ssh_throw(x) if((x)==SSH_ERROR) return SSH_ERROR +#define ssh_throw_null(CSession,x) if((x)==NULL) return NULL +#define void_throwable int +#define return_throwable return SSH_OK +#endif + +/** + * The ssh::Session class contains the state of a SSH connection. + */ +class Session { + friend class Channel; +public: + Session(){ + c_session=ssh_new(); + } + ~Session(){ + ssh_free(c_session); + c_session=NULL; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option cstring containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, const char *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option long integer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, long int option){ + ssh_throw(ssh_options_set(c_session,type,&option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option void pointer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, void *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief connects to the remote host + * @throws SshException on error + * @see ssh_connect + */ + void_throwable connect(){ + int ret=ssh_connect(c_session); + ssh_throw(ret); + return_throwable; + } + /** @brief Authenticates automatically using public key + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_autopubkey + */ + int userauthPublickeyAuto(void){ + int ret=ssh_userauth_publickey_auto(c_session, NULL, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the "none" method. Prefer using autopubkey if + * possible. + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_none + * @see Session::userauthAutoPubkey + */ + int userauthNone(){ + int ret=ssh_userauth_none(c_session,NULL); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the password method. + * @param[in] password password to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_password + */ + int userauthPassword(const char *password){ + int ret=ssh_userauth_password(c_session,NULL,password); + ssh_throw(ret); + return ret; + } + /** @brief Try to authenticate using the publickey method. + * @param[in] pubkey public key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS if the pubkey is accepted, + * @returns SSH_AUTH_DENIED if the pubkey is denied + * @see ssh_userauth_try_pubkey + */ + int userauthTryPublickey(ssh_key pubkey){ + int ret=ssh_userauth_try_publickey(c_session, NULL, pubkey); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the publickey method. + * @param[in] privkey private key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_pubkey + */ + int userauthPublickey(ssh_key privkey){ + int ret=ssh_userauth_publickey(c_session, NULL, privkey); + ssh_throw(ret); + return ret; + } + int userauthPrivatekeyFile(const char *filename, + const char *passphrase); + /** @brief Returns the available authentication methods from the server + * @throws SshException on error + * @returns Bitfield of available methods. + * @see ssh_userauth_list + */ + int getAuthList(){ + int ret=ssh_userauth_list(c_session, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Disconnects from the SSH server and closes connection + * @see ssh_disconnect + */ + void disconnect(){ + ssh_disconnect(c_session); + } + /** @brief Returns the disconnect message from the server, if any + * @returns pointer to the message, or NULL. Do not attempt to free + * the pointer. + */ + const char *getDisconnectMessage(){ + const char *msg=ssh_get_disconnect_message(c_session); + return msg; + } + /** @internal + * @brief gets error message + */ + const char *getError(){ + return ssh_get_error(c_session); + } + /** @internal + * @brief returns error code + */ + int getErrorCode(){ + return ssh_get_error_code(c_session); + } + /** @brief returns the file descriptor used for the communication + * @returns the file descriptor + * @warning if a proxycommand is used, this function will only return + * one of the two file descriptors being used + * @see ssh_get_fd + */ + socket_t getSocket(){ + return ssh_get_fd(c_session); + } + /** @brief gets the Issue banner from the ssh server + * @returns the issue banner. This is generally a MOTD from server + * @see ssh_get_issue_banner + */ + std::string getIssueBanner(){ + char *banner=ssh_get_issue_banner(c_session); + std::string ret= std::string(banner); + ::free(banner); + return ret; + } + /** @brief returns the OpenSSH version (server) if possible + * @returns openssh version code + * @see ssh_get_openssh_version + */ + int getOpensshVersion(){ + return ssh_get_openssh_version(c_session); + } + /** @brief returns the version of the SSH protocol being used + * @returns the SSH protocol version + * @see ssh_get_version + */ + int getVersion(){ + return ssh_get_version(c_session); + } + /** @brief verifies that the server is known + * @throws SshException on error + * @returns Integer value depending on the knowledge of the + * server key + * @see ssh_is_server_known + */ + int isServerKnown(){ + int ret=ssh_is_server_known(c_session); + ssh_throw(ret); + return ret; + } + void log(int priority, const char *format, ...){ + char buffer[1024]; + va_list va; + + va_start(va, format); + vsnprintf(buffer, sizeof(buffer), format, va); + va_end(va); + ssh_log(c_session,priority, "%s", buffer); + } + + /** @brief copies options from a session to another + * @throws SshException on error + * @see ssh_options_copy + */ + void_throwable optionsCopy(const Session &source){ + ssh_throw(ssh_options_copy(source.c_session,&c_session)); + return_throwable; + } + /** @brief parses a configuration file for options + * @throws SshException on error + * @param[in] file configuration file name + * @see ssh_options_parse_config + */ + void_throwable optionsParseConfig(const char *file){ + ssh_throw(ssh_options_parse_config(c_session,file)); + return_throwable; + } + /** @brief silently disconnect from remote host + * @see ssh_silent_disconnect + */ + void silentDisconnect(){ + ssh_silent_disconnect(c_session); + } + /** @brief Writes the known host file with current + * host key + * @throws SshException on error + * @see ssh_write_knownhost + */ + int writeKnownhost(){ + int ret = ssh_write_knownhost(c_session); + ssh_throw(ret); + return ret; + } + + /** @brief accept an incoming forward connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the forward connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_forward_accept + * @see Session::listenForward + */ + Channel *acceptForward(int timeout_ms); + /* acceptForward is implemented later in this file */ + + void_throwable cancelForward(const char *address, int port){ + int err=ssh_forward_cancel(c_session, address, port); + ssh_throw(err); + return_throwable; + } + + void_throwable listenForward(const char *address, int port, + int &boundport){ + int err=ssh_forward_listen(c_session, address, port, &boundport); + ssh_throw(err); + return_throwable; + } + +private: + ssh_session c_session; + ssh_session getCSession(){ + return c_session; + } + /* No copy constructor, no = operator */ + Session(const Session &); + Session& operator=(const Session &); +}; + +/** @brief the ssh::Channel class describes the state of an SSH + * channel. + * @see ssh_channel + */ +class Channel { + friend class Session; +public: + Channel(Session &session){ + channel=ssh_channel_new(session.getCSession()); + this->session=&session; + } + ~Channel(){ + ssh_channel_free(channel); + channel=NULL; + } + + /** @brief accept an incoming X11 connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the X11 connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_accept_x11 + * @see Channel::requestX11 + */ + Channel *acceptX11(int timeout_ms){ + ssh_channel x11chan = ssh_channel_accept_x11(channel,timeout_ms); + ssh_throw_null(getCSession(),x11chan); + Channel *newchan = new Channel(getSession(),x11chan); + return newchan; + } + /** @brief change the size of a pseudoterminal + * @param[in] cols number of columns + * @param[in] rows number of rows + * @throws SshException on error + * @see ssh_channel_change_pty_size + */ + void_throwable changePtySize(int cols, int rows){ + int err=ssh_channel_change_pty_size(channel,cols,rows); + ssh_throw(err); + return_throwable; + } + + /** @brief closes a channel + * @throws SshException on error + * @see ssh_channel_close + */ + void_throwable close(){ + ssh_throw(ssh_channel_close(channel)); + return_throwable; + } + + int getExitStatus(){ + return ssh_channel_get_exit_status(channel); + } + Session &getSession(){ + return *session; + } + /** @brief returns true if channel is in closed state + * @see ssh_channel_is_closed + */ + bool isClosed(){ + return ssh_channel_is_closed(channel) != 0; + } + /** @brief returns true if channel is in EOF state + * @see ssh_channel_is_eof + */ + bool isEof(){ + return ssh_channel_is_eof(channel) != 0; + } + /** @brief returns true if channel is in open state + * @see ssh_channel_is_open + */ + bool isOpen(){ + return ssh_channel_is_open(channel) != 0; + } + int openForward(const char *remotehost, int remoteport, + const char *sourcehost=NULL, int localport=0){ + int err=ssh_channel_open_forward(channel,remotehost,remoteport, + sourcehost, localport); + ssh_throw(err); + return err; + } + /* TODO: completely remove this ? */ + void_throwable openSession(){ + int err=ssh_channel_open_session(channel); + ssh_throw(err); + return_throwable; + } + int poll(bool is_stderr=false){ + int err=ssh_channel_poll(channel,is_stderr); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, bool is_stderr=false){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read(channel,dest,count,is_stderr); + ssh_throw(err); + return err; + } + int readNonblocking(void *dest, size_t count, bool is_stderr=false){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_nonblocking(channel,dest,count,is_stderr); + ssh_throw(err); + return err; + } + void_throwable requestEnv(const char *name, const char *value){ + int err=ssh_channel_request_env(channel,name,value); + ssh_throw(err); + return_throwable; + } + + void_throwable requestExec(const char *cmd){ + int err=ssh_channel_request_exec(channel,cmd); + ssh_throw(err); + return_throwable; + } + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0){ + int err; + if(term != NULL && cols != 0 && rows != 0) + err=ssh_channel_request_pty_size(channel,term,cols,rows); + else + err=ssh_channel_request_pty(channel); + ssh_throw(err); + return_throwable; + } + + void_throwable requestShell(){ + int err=ssh_channel_request_shell(channel); + ssh_throw(err); + return_throwable; + } + void_throwable requestSendSignal(const char *signum){ + int err=ssh_channel_request_send_signal(channel, signum); + ssh_throw(err); + return_throwable; + } + void_throwable requestSubsystem(const char *subsystem){ + int err=ssh_channel_request_subsystem(channel,subsystem); + ssh_throw(err); + return_throwable; + } + int requestX11(bool single_connection, + const char *protocol, const char *cookie, int screen_number){ + int err=ssh_channel_request_x11(channel,single_connection, + protocol, cookie, screen_number); + ssh_throw(err); + return err; + } + void_throwable sendEof(){ + int err=ssh_channel_send_eof(channel); + ssh_throw(err); + return_throwable; + } + /** @brief Writes on a channel + * @param data data to write. + * @param len number of bytes to write. + * @param is_stderr write should be done on the stderr channel (server only) + * @returns number of bytes written + * @throws SshException in case of error + * @see channel_write + * @see channel_write_stderr + */ + int write(const void *data, size_t len, bool is_stderr=false){ + int ret; + if(is_stderr){ + ret=ssh_channel_write_stderr(channel,data,len); + } else { + ret=ssh_channel_write(channel,data,len); + } + ssh_throw(ret); + return ret; + } +private: + ssh_session getCSession(){ + return session->getCSession(); + } + Channel (Session &session, ssh_channel c_channel){ + this->channel=c_channel; + this->session=&session; + } + Session *session; + ssh_channel channel; + /* No copy and no = operator */ + Channel(const Channel &); + Channel &operator=(const Channel &); +}; + + +/* This code cannot be put inline due to references to Channel */ +Channel *Session::acceptForward(int timeout_ms){ + ssh_channel forward = ssh_forward_accept(c_session, + timeout_ms); + ssh_throw_null(c_session,forward); + Channel *newchan = new Channel(*this,forward); + return newchan; + } + +} // namespace ssh + +/** @} */ +#endif /* LIBSSHPP_HPP_ */ diff --git a/libssh/include/libssh/messages.h b/libssh/include/libssh/messages.h new file mode 100644 index 00000000..f196c6f7 --- /dev/null +++ b/libssh/include/libssh/messages.h @@ -0,0 +1,104 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MESSAGES_H_ +#define MESSAGES_H_ + +#include "config.h" + +struct ssh_auth_request { + char *username; + int method; + char *password; + struct ssh_key_struct *pubkey; + char signature_state; + char kbdint_response; +}; + +struct ssh_channel_request_open { + int type; + uint32_t sender; + uint32_t window; + uint32_t packet_size; + char *originator; + uint16_t originator_port; + char *destination; + uint16_t destination_port; +}; + +struct ssh_service_request { + char *service; +}; + +struct ssh_global_request { + int type; + uint8_t want_reply; + char *bind_address; + uint16_t bind_port; +}; + +struct ssh_channel_request { + int type; + ssh_channel channel; + uint8_t want_reply; + /* pty-req type specifics */ + char *TERM; + uint32_t width; + uint32_t height; + uint32_t pxwidth; + uint32_t pxheight; + ssh_string modes; + + /* env type request */ + char *var_name; + char *var_value; + /* exec type request */ + char *command; + /* subsystem */ + char *subsystem; + + /* X11 */ + uint8_t x11_single_connection; + const char *x11_auth_protocol; + const char *x11_auth_cookie; + uint32_t x11_screen_number; +}; + +struct ssh_message_struct { + ssh_session session; + int type; + struct ssh_auth_request auth_request; + struct ssh_channel_request_open channel_request_open; + struct ssh_channel_request channel_request; + struct ssh_service_request service_request; + struct ssh_global_request global_request; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open); +SSH_PACKET_CALLBACK(ssh_packet_service_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_request); +SSH_PACKET_CALLBACK(ssh_packet_global_request); + +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply); +void ssh_message_queue(ssh_session session, ssh_message message); +ssh_message ssh_message_pop_head(ssh_session session); + +#endif /* MESSAGES_H_ */ diff --git a/libssh/include/libssh/misc.h b/libssh/include/libssh/misc.h new file mode 100644 index 00000000..1e69b069 --- /dev/null +++ b/libssh/include/libssh/misc.h @@ -0,0 +1,92 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MISC_H_ +#define MISC_H_ + +/* in misc.c */ +/* gets the user home dir. */ +char *ssh_get_user_home_dir(void); +char *ssh_get_local_username(void); +int ssh_file_readaccess_ok(const char *file); + +char *ssh_path_expand_tilde(const char *d); +char *ssh_path_expand_escape(ssh_session session, const char *s); +int ssh_analyze_banner(ssh_session session, int server, int *ssh1, int *ssh2); +int ssh_is_ipaddr_v4(const char *str); +int ssh_is_ipaddr(const char *str); + +#ifndef HAVE_NTOHLL +/* macro for byte ordering */ +uint64_t ntohll(uint64_t); +#endif + +#ifndef HAVE_HTONLL +#define htonll(x) ntohll((x)) +#endif + +/* list processing */ + +struct ssh_list { + struct ssh_iterator *root; + struct ssh_iterator *end; +}; + +struct ssh_iterator { + struct ssh_iterator *next; + const void *data; +}; + +struct ssh_timestamp { + long seconds; + long useconds; +}; + +struct ssh_list *ssh_list_new(void); +void ssh_list_free(struct ssh_list *list); +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list); +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value); +int ssh_list_append(struct ssh_list *list, const void *data); +int ssh_list_prepend(struct ssh_list *list, const void *data); +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator); +char *ssh_lowercase(const char* str); +char *ssh_hostport(const char *host, int port); + +const void *_ssh_list_pop_head(struct ssh_list *list); + +#define ssh_iterator_value(type, iterator)\ + ((type)((iterator)->data)) + +/** @brief fetch the head element of a list and remove it from list + * @param type type of the element to return + * @param list the ssh_list to use + * @return the first element of the list, or NULL if the list is empty + */ +#define ssh_list_pop_head(type, ssh_list)\ + ((type)_ssh_list_pop_head(ssh_list)) + +int ssh_make_milliseconds(long sec, long usec); +void ssh_timestamp_init(struct ssh_timestamp *ts); +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); + +int ssh_match_group(const char *group, const char *object); + +#endif /* MISC_H_ */ diff --git a/libssh/include/libssh/options.h b/libssh/include/libssh/options.h new file mode 100644 index 00000000..4078ad08 --- /dev/null +++ b/libssh/include/libssh/options.h @@ -0,0 +1,28 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 Andreas Schneider + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +int ssh_config_parse_file(ssh_session session, const char *filename); +int ssh_options_set_algo(ssh_session session, int algo, const char *list); +int ssh_options_apply(ssh_session session); + +#endif /* _OPTIONS_H */ diff --git a/libssh/include/libssh/packet.h b/libssh/include/libssh/packet.h new file mode 100644 index 00000000..513eaa81 --- /dev/null +++ b/libssh/include/libssh/packet.h @@ -0,0 +1,87 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PACKET_H_ +#define PACKET_H_ + +struct ssh_socket_struct; + +/* this structure should go someday */ +typedef struct packet_struct { + int valid; + uint32_t len; + uint8_t type; +} PACKET; + +/** different state of packet reading. */ +enum ssh_packet_state_e { + /** Packet not initialized, must read the size of packet */ + PACKET_STATE_INIT, + /** Size was read, waiting for the rest of data */ + PACKET_STATE_SIZEREAD, + /** Full packet was read and callbacks are being called. Future packets + * should wait for the end of the callback. */ + PACKET_STATE_PROCESSING +}; + +int packet_send(ssh_session session); + +#ifdef WITH_SSH1 +int packet_send1(ssh_session session) ; +void ssh_packet_set_default_callbacks1(ssh_session session); + +SSH_PACKET_CALLBACK(ssh_packet_disconnect1); +SSH_PACKET_CALLBACK(ssh_packet_smsg_success1); +SSH_PACKET_CALLBACK(ssh_packet_smsg_failure1); +int ssh_packet_socket_callback1(const void *data, size_t receivedlen, void *user); + +#endif + +SSH_PACKET_CALLBACK(ssh_packet_unimplemented); +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); +SSH_PACKET_CALLBACK(ssh_packet_dh_reply); +SSH_PACKET_CALLBACK(ssh_packet_newkeys); +SSH_PACKET_CALLBACK(ssh_packet_service_accept); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); +#endif + +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); +int ssh_packet_parse_type(ssh_session session); +//int packet_flush(ssh_session session, int enforce_blocking); + +int ssh_packet_socket_callback(const void *data, size_t len, void *user); +void ssh_packet_register_socket_callback(ssh_session session, struct ssh_socket_struct *s); +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_set_default_callbacks(ssh_session session); +void ssh_packet_process(ssh_session session, uint8_t type); + +/* PACKET CRYPT */ +uint32_t packet_decrypt_len(ssh_session session, char *crypted); +int packet_decrypt(ssh_session session, void *packet, unsigned int len); +unsigned char *packet_encrypt(ssh_session session, + void *packet, + unsigned int len); +int packet_hmac_verify(ssh_session session,ssh_buffer buffer, + unsigned char *mac); + +#endif /* PACKET_H_ */ diff --git a/libssh/include/libssh/pcap.h b/libssh/include/libssh/pcap.h new file mode 100644 index 00000000..ca57156b --- /dev/null +++ b/libssh/include/libssh/pcap.h @@ -0,0 +1,46 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PCAP_H_ +#define PCAP_H_ + +#include "config.h" +#include "libssh/libssh.h" + +#ifdef WITH_PCAP +typedef struct ssh_pcap_context_struct* ssh_pcap_context; + +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len); + +ssh_pcap_context ssh_pcap_context_new(ssh_session session); +void ssh_pcap_context_free(ssh_pcap_context ctx); + +enum ssh_pcap_direction{ + SSH_PCAP_DIR_IN, + SSH_PCAP_DIR_OUT +}; +void ssh_pcap_context_set_file(ssh_pcap_context, ssh_pcap_file); +int ssh_pcap_context_write(ssh_pcap_context,enum ssh_pcap_direction direction, void *data, + uint32_t len, uint32_t origlen); + + +#endif /* WITH_PCAP */ +#endif /* PCAP_H_ */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/include/libssh/pki.h b/libssh/include/libssh/pki.h new file mode 100644 index 00000000..d7fa5e57 --- /dev/null +++ b/libssh/include/libssh/pki.h @@ -0,0 +1,121 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_H_ +#define PKI_H_ + +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif + +#include "libssh/crypto.h" + +#define MAX_PUBKEY_SIZE 0x100000 /* 1M */ + +#define SSH_KEY_FLAG_EMPTY 0x0 +#define SSH_KEY_FLAG_PUBLIC 0x0001 +#define SSH_KEY_FLAG_PRIVATE 0x0002 + +struct ssh_key_struct { + enum ssh_keytypes_e type; + int flags; + const char *type_c; /* Don't free it ! it is static */ + int ecdsa_nid; +#ifdef HAVE_LIBGCRYPT + gcry_sexp_t dsa; + gcry_sexp_t rsa; + void *ecdsa; +#elif HAVE_LIBCRYPTO + DSA *dsa; + RSA *rsa; +#ifdef HAVE_OPENSSL_ECC + EC_KEY *ecdsa; +#else + void *ecdsa; +#endif /* HAVE_OPENSSL_EC_H */ +#endif + void *cert; +}; + +struct ssh_signature_struct { + enum ssh_keytypes_e type; +#ifdef HAVE_LIBGCRYPT + gcry_sexp_t dsa_sig; + gcry_sexp_t rsa_sig; + void *ecdsa_sig; +#elif defined HAVE_LIBCRYPTO + DSA_SIG *dsa_sig; + ssh_string rsa_sig; +# ifdef HAVE_OPENSSL_ECC + ECDSA_SIG *ecdsa_sig; +# else + void *ecdsa_sig; +# endif +#endif +}; + +typedef struct ssh_signature_struct *ssh_signature; + +/* SSH Key Functions */ +ssh_key ssh_key_dup(const ssh_key key); +void ssh_key_clean (ssh_key key); + +/* SSH Signature Functions */ +ssh_signature ssh_signature_new(void); +void ssh_signature_free(ssh_signature sign); + +int ssh_pki_export_signature_blob(const ssh_signature sign, + ssh_string *sign_blob); +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig); +int ssh_pki_signature_verify_blob(ssh_session session, + ssh_string sig_blob, + const ssh_key key, + unsigned char *digest, + size_t dlen); + +/* SSH Public Key Functions */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob); +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey); +int ssh_pki_export_pubkey_rsa1(const ssh_key key, + const char *host, + char *rsa1, + size_t rsa1_len); + +/* SSH Signing Functions */ +ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, + ssh_key privatekey); +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey); +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey); + +/* Temporary functions, to be removed after migration to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(ssh_key key); +ssh_private_key ssh_pki_convert_key_to_privatekey(ssh_key key); + +#endif /* PKI_H_ */ diff --git a/libssh/include/libssh/pki_priv.h b/libssh/include/libssh/pki_priv.h new file mode 100644 index 00000000..2c361e43 --- /dev/null +++ b/libssh/include/libssh/pki_priv.h @@ -0,0 +1,88 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_PRIV_H_ +#define PKI_PRIV_H_ + +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define DSA_HEADER_BEGIN "-----BEGIN DSA PRIVATE KEY-----" +#define DSA_HEADER_END "-----END DSA PRIVATE KEY-----" +#define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" + +#define ssh_pki_log(...) \ + _ssh_pki_log(__FUNCTION__, __VA_ARGS__) +void _ssh_pki_log(const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(2, 3); + +int pki_key_ecdsa_nid_from_name(const char *name); + +/* SSH Key Functions */ +ssh_key pki_key_dup(const ssh_key key, int demote); +int pki_key_generate_rsa(ssh_key key, int parameter); +int pki_key_generate_dss(ssh_key key, int parameter); +int pki_key_generate_ecdsa(ssh_key key, int parameter); +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +/* SSH Private Key Functions */ +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey); +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); + +/* SSH Public Key Functions */ +int pki_pubkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey); +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n); +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e); +ssh_string pki_publickey_to_blob(const ssh_key key); +int pki_export_pubkey_rsa1(const ssh_key key, + const char *host, + char *rsa1, + size_t rsa1_len); + +/* SSH Signature Functions */ +ssh_string pki_signature_to_blob(const ssh_signature sign); +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type); +int pki_signature_verify(ssh_session session, + const ssh_signature sig, + const ssh_key key, + const unsigned char *hash, + size_t hlen); + +/* SSH Signing Functions */ +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *hash, + size_t hlen); +ssh_signature pki_do_sign_sessionid(const ssh_key key, + const unsigned char *hash, + size_t hlen); +#endif /* PKI_PRIV_H_ */ diff --git a/libssh/include/libssh/poll.h b/libssh/include/libssh/poll.h new file mode 100644 index 00000000..bbc03a95 --- /dev/null +++ b/libssh/include/libssh/poll.h @@ -0,0 +1,159 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef POLL_H_ +#define POLL_H_ + +#include "config.h" + +#ifdef HAVE_POLL + +#include +typedef struct pollfd ssh_pollfd_t; + +#else /* HAVE_POLL */ + +/* poll emulation support */ + +typedef struct ssh_pollfd_struct { + socket_t fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +} ssh_pollfd_t; + +typedef unsigned long int nfds_t; + +#ifdef _WIN32 + +#ifndef POLLRDNORM +#define POLLRDNORM 0x0100 +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x0200 +#endif +#ifndef POLLIN +#define POLLIN (POLLRDNORM | POLLRDBAND) +#endif +#ifndef POLLPRI +#define POLLPRI 0x0400 +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM 0x0010 +#endif +#ifndef POLLOUT +#define POLLOUT (POLLWRNORM) +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x0020 +#endif + +#ifndef POLLERR +#define POLLERR 0x0001 +#endif +#ifndef POLLHUP +#define POLLHUP 0x0002 +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x0004 +#endif + +#else /* _WIN32 */ + +/* poll.c */ +#ifndef POLLIN +#define POLLIN 0x001 /* There is data to read. */ +#endif +#ifndef POLLPRI +#define POLLPRI 0x002 /* There is urgent data to read. */ +#endif +#ifndef POLLOUT +#define POLLOUT 0x004 /* Writing now will not block. */ +#endif + +#ifndef POLLERR +#define POLLERR 0x008 /* Error condition. */ +#endif +#ifndef POLLHUP +#define POLLHUP 0x010 /* Hung up. */ +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x020 /* Invalid polling request. */ +#endif + +#ifndef POLLRDNORM +#define POLLRDNORM 0x040 /* mapped to read fds_set */ +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x080 /* mapped to exception fds_set */ +#endif +#ifndef POLLWRNORM +#define POLLWRNORM 0x100 /* mapped to write fds_set */ +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x200 /* mapped to write fds_set */ +#endif + +#endif /* WIN32 */ +#endif /* HAVE_POLL */ + +void ssh_poll_init(void); +void ssh_poll_cleanup(void); +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout); +typedef struct ssh_poll_ctx_struct *ssh_poll_ctx; +typedef struct ssh_poll_handle_struct *ssh_poll_handle; + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 if you removed the poll object from + * its poll context. + */ +typedef int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, int revents, + void *userdata); + +struct ssh_socket_struct; + +ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, + void *userdata); +void ssh_poll_free(ssh_poll_handle p); +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p); +short ssh_poll_get_events(ssh_poll_handle p); +void ssh_poll_set_events(ssh_poll_handle p, short events); +void ssh_poll_add_events(ssh_poll_handle p, short events); +void ssh_poll_remove_events(ssh_poll_handle p, short events); +socket_t ssh_poll_get_fd(ssh_poll_handle p); +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd); +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata); +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size); +void ssh_poll_ctx_free(ssh_poll_ctx ctx); +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); + +#endif /* POLL_H_ */ diff --git a/libssh/include/libssh/priv.h b/libssh/include/libssh/priv.h new file mode 100644 index 00000000..912a1918 --- /dev/null +++ b/libssh/include/libssh/priv.h @@ -0,0 +1,247 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * priv.h file + * This include file contains everything you shouldn't deal with in + * user programs. Consider that anything in this file might change + * without notice; libssh.h file will keep backward compatibility + * on binary & source + */ + +#ifndef _LIBSSH_PRIV_H +#define _LIBSSH_PRIV_H + +#include "config.h" + +#ifdef _WIN32 + +/* Imitate define of inttypes.h */ +# ifndef PRIdS +# define PRIdS "Id" +# endif + +# ifndef PRIu64 +# if __WORDSIZE == 64 +# define PRIu64 "lu" +# else +# define PRIu64 "llu" +# endif /* __WORDSIZE */ +# endif /* PRIu64 */ + +#if !defined(HAVE_STRTOULL) +# if defined(HAVE___STRTOULL) +# define strtoull __strtoull +# elif defined(__hpux) && defined(__LP64__) +# define strtoull strtoul +# else +# error "no strtoull function found" +# endif +#endif /* !defined(HAVE_STRTOULL) */ + +# ifdef _MSC_VER +# include + +/* On Microsoft compilers define inline to __inline on all others use inline */ +# undef inline +# define inline __inline + +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# if !defined(HAVE_STRTOULL) +# define strtoull _strtoui64 +# endif +# define isblank(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n' || (ch) == '\r') + +# define usleep(X) Sleep(((X)+1000)/1000) + +# undef strtok_r +# define strtok_r strtok_s + +# if defined(HAVE__SNPRINTF_S) +# undef snprintf +# define snprintf(d, n, ...) _snprintf_s((d), (n), _TRUNCATE, __VA_ARGS__) +# else /* HAVE__SNPRINTF_S */ +# if defined(HAVE__SNPRINTF) +# undef snprintf +# define snprintf _snprintf +# else /* HAVE__SNPRINTF */ +# if !defined(HAVE_SNPRINTF) +# error "no snprintf compatible function found" +# endif /* HAVE_SNPRINTF */ +# endif /* HAVE__SNPRINTF */ +# endif /* HAVE__SNPRINTF_S */ + +# if defined(HAVE__VSNPRINTF_S) +# undef vsnprintf +# define vsnprintf(s, n, f, v) _vsnprintf_s((s), (n), _TRUNCATE, (f), (v)) +# else /* HAVE__VSNPRINTF_S */ +# if defined(HAVE__VSNPRINTF) +# undef vsnprintf +# define vsnprintf _vsnprintf +# else +# if !defined(HAVE_VSNPRINTF) +# error "No vsnprintf compatible function found" +# endif /* HAVE_VSNPRINTF */ +# endif /* HAVE__VSNPRINTF */ +# endif /* HAVE__VSNPRINTF_S */ + +# endif /* _MSC_VER */ + +int gettimeofday(struct timeval *__p, void *__t); + +#else /* _WIN32 */ + +#include +#define PRIdS "zd" + +#endif /* _WIN32 */ + +#include "libssh/libssh.h" +#include "libssh/callbacks.h" + +/* some constants */ +#define MAX_PACKET_LEN 262144 +#define ERROR_BUFFERLEN 1024 +#define CLIENTBANNER1 "SSH-1.5-libssh-" SSH_STRINGIFY(LIBSSH_VERSION) +#define CLIENTBANNER2 "SSH-2.0-libssh-" SSH_STRINGIFY(LIBSSH_VERSION) +#define KBDINT_MAX_PROMPT 256 /* more than openssh's :) */ + +#ifndef __FUNCTION__ +#if defined(__SUNPRO_C) +#define __FUNCTION__ __func__ +#endif +#endif + +#define enter_function() (void)session +#define leave_function() (void)session + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +/* forward declarations */ +struct ssh_common_struct; +struct ssh_kex_struct; + +int ssh_get_key_params(ssh_session session, ssh_key *privkey); + +/* LOGGING */ +#define SSH_LOG(session, priority, ...) \ + ssh_log_common(&session->common, priority, __FUNCTION__, __VA_ARGS__) +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(4, 5); +void ssh_log_function(int verbosity, + const char *function, + const char *buffer); + + +/* ERROR HANDLING */ + +/* error handling structure */ +struct error_struct { + int error_code; + char error_buffer[ERROR_BUFFERLEN]; +}; + +#define ssh_set_error(error, code, ...) \ + _ssh_set_error(error, code, __FUNCTION__, __VA_ARGS__) +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) PRINTF_ATTRIBUTE(4, 5); + +#define ssh_set_error_oom(error) \ + _ssh_set_error_oom(error, __FUNCTION__) +void _ssh_set_error_oom(void *error, const char *function); + +#define ssh_set_error_invalid(error) \ + _ssh_set_error_invalid(error, __FUNCTION__) +void _ssh_set_error_invalid(void *error, const char *function); + + + + + +/* client.c */ + +int ssh_send_banner(ssh_session session, int is_server); + +/* connect.c */ +socket_t ssh_connect_host(ssh_session session, const char *host,const char + *bind_addr, int port, long timeout, long usec); +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port); + +/* in base64.c */ +ssh_buffer base64_to_bin(const char *source); +unsigned char *bin_to_base64(const unsigned char *source, int len); + +/* gzip.c */ +int compress_buffer(ssh_session session,ssh_buffer buf); +int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); + +/* match.c */ +int match_hostname(const char *host, const char *pattern, unsigned int len); + + + + +/** Free memory space */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/** Overwrite a string with '\0' */ +#define BURN_STRING(x) do { if ((x) != NULL) memset((x), '\0', strlen((x))); __asm__ volatile ("" : : : "memory"); } while(0) + +/** Overwrite the buffer with '\0' */ +#define BURN_BUFFER(x, size) do { if ((x) != NULL) memset((x), '\0', (size))); __asm__ volatile ("") : : : "memory"; } while(0) + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +#endif /* _LIBSSH_PRIV_H */ +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/include/libssh/scp.h b/libssh/include/libssh/scp.h new file mode 100644 index 00000000..d356d89b --- /dev/null +++ b/libssh/include/libssh/scp.h @@ -0,0 +1,55 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SCP_H +#define _SCP_H + +enum ssh_scp_states { + SSH_SCP_NEW, //Data structure just created + SSH_SCP_WRITE_INITED, //Gave our intention to write + SSH_SCP_WRITE_WRITING,//File was opened and currently writing + SSH_SCP_READ_INITED, //Gave our intention to read + SSH_SCP_READ_REQUESTED, //We got a read request + SSH_SCP_READ_READING, //File is opened and reading + SSH_SCP_ERROR, //Something bad happened + SSH_SCP_TERMINATED //Transfer finished +}; + +struct ssh_scp_struct { + ssh_session session; + int mode; + int recursive; + ssh_channel channel; + char *location; + enum ssh_scp_states state; + uint64_t filelen; + uint64_t processed; + enum ssh_scp_request_types request_type; + char *request_name; + char *warning; + int request_mode; +}; + +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len); +int ssh_scp_integer_mode(const char *mode); +char *ssh_scp_string_mode(int mode); +int ssh_scp_response(ssh_scp scp, char **response); + +#endif diff --git a/libssh/include/libssh/server.h b/libssh/include/libssh/server.h new file mode 100644 index 00000000..6ed8002a --- /dev/null +++ b/libssh/include/libssh/server.h @@ -0,0 +1,393 @@ +/* Public include file for server support */ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_server The libssh server API + * + * @{ + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "libssh/libssh.h" +#define SERVERBANNER CLIENTBANNER + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_options_e { + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, + SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR +}; + +typedef struct ssh_bind_struct* ssh_bind; + +/* Callback functions */ + +/** + * @brief Incoming connection callback. This callback is called when a ssh_bind + * has a new incoming connection. + * @param sshbind Current sshbind session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_bind_incoming_connection_callback) (ssh_bind sshbind, + void *userdata); + +/** + * @brief These are the callbacks exported by the ssh_bind structure. + * + * They are called by the server module when events appear on the network. + */ +struct ssh_bind_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** A new connection is available. */ + ssh_bind_incoming_connection_callback incoming_connection; +}; +typedef struct ssh_bind_callbacks_struct *ssh_bind_callbacks; + +/** + * @brief Creates a new SSH server bind. + * + * @return A newly allocated ssh_bind session pointer. + */ +LIBSSH_API ssh_bind ssh_bind_new(void); + +/** + * @brief Set the options for the current SSH server bind. + * + * @param sshbind The ssh server bind to configure. + * + * @param type The option type to set. This could be one of the + * following: + * + * - SSH_BIND_OPTIONS_BINDADDR + * The ip address to bind (const char *). + * + * - SSH_BIND_OPTIONS_BINDPORT + * The port to bind (unsigned int). + * + * - SSH_BIND_OPTIONS_BINDPORT_STR + * The port to bind (const char *). + * + * - SSH_BIND_OPTIONS_HOSTKEY + * This specifies the file containing the private host key used + * by SSHv1. (const char *). + * + * - SSH_BIND_OPTIONS_DSAKEY + * This specifies the file containing the private host dsa key + * used by SSHv2. (const char *). + * + * - SSH_BIND_OPTIONS_RSAKEY + * This specifies the file containing the private host dsa key + * used by SSHv2. (const char *). + * + * - SSH_BIND_OPTIONS_BANNER + * That the server banner (version string) for SSH. + * (const char *). + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY + * Set the session logging verbosity (int).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_RARE: Rare conditions or warnings + * - SSH_LOG_ENTRY: API-accessible entrypoints + * - SSH_LOG_PACKET: Packet id and size + * - SSH_LOG_FUNCTIONS: Function entering and leaving + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR + * Set the session logging verbosity (const char *).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_RARE: Rare conditions or warnings + * - SSH_LOG_ENTRY: API-accessible entrypoints + * - SSH_LOG_PACKET: Packet id and size + * - SSH_LOG_FUNCTIONS: Function entering and leaving + * \n + * See the corresponding numbers in libssh.h. + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * type set. + * + * @returns SSH_OK on success, SSH_ERROR on invalid option or parameter. + */ +LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, const void *value); + +/** + * @brief Start listening to the socket. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); + +/** + * @brief Set the callback for this bind. + * + * @param[in] sshbind The bind to set the callback on. + * + * @param[in] callbacks An already set up ssh_bind_callbacks instance. + * + * @param[in] userdata A pointer to private data to pass to the callbacks. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_bind_set_callbacks(session, &cb); + * @endcode + */ +LIBSSH_API int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata); + +/** + * @brief Set the session to blocking/nonblocking mode. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @param blocking Zero for nonblocking mode. + */ +LIBSSH_API void ssh_bind_set_blocking(ssh_bind ssh_bind_o, int blocking); + +/** + * @brief Recover the file descriptor from the session. + * + * @param ssh_bind_o The ssh server bind to get the fd from. + * + * @return The file descriptor. + */ +LIBSSH_API socket_t ssh_bind_get_fd(ssh_bind ssh_bind_o); + +/** + * @brief Set the file descriptor for a session. + * + * @param ssh_bind_o The ssh server bind to set the fd. + * + * @param fd The file descriptssh_bind B + */ +LIBSSH_API void ssh_bind_set_fd(ssh_bind ssh_bind_o, socket_t fd); + +/** + * @brief Allow the file descriptor to accept new sessions. + * + * @param ssh_bind_o The ssh server bind to use. + */ +LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); + +/** + * @brief Accept an incoming ssh connection and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @see ssh_new + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); + +/** + * @brief Accept an incoming ssh connection on the given file descriptor + * and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @param fd A file descriptor of an already established TCP + * inbound connection + * @see ssh_new + * @see ssh_bind_accept + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, + socket_t fd); + +/** + * @brief Handles the key exchange and set up encryption + * + * @param session A connected ssh session + * @see ssh_bind_accept + * @return SSH_OK if the key exchange was successful + */ +LIBSSH_API int ssh_handle_key_exchange(ssh_session session); + +/** + * @brief Free a ssh servers bind. + * + * @param ssh_bind_o The ssh server bind to free. + */ +LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); + +/********************************************************** + * SERVER MESSAGING + **********************************************************/ + +/** + * @brief Reply with a standard reject message. + * + * Use this function if you don't know what to respond or if you want to reject + * a request. + * + * @param[in] msg The message to use for the reply. + * + * @return 0 on success, -1 on error. + * + * @see ssh_message_get() + */ +LIBSSH_API int ssh_message_reply_default(ssh_message msg); + +/** + * @brief Get the name of the authenticated user. + * + * @param[in] msg The message to get the username from. + * + * @return The username or NULL if an error occured. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); + +/** + * @brief Get the password of the authenticated user. + * + * @param[in] msg The message to get the password from. + * + * @return The username or NULL if an error occured. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); + +/** + * @brief Get the publickey of the authenticated user. + * + * If you need the key for later user you should duplicate it. + * + * @param[in] msg The message to get the public key from. + * + * @return The public key or NULL. + * + * @see ssh_key_dup() + * @see ssh_key_cmp() + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); + +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); +LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); +LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); +LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); +LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); + +LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); + +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + +LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); +LIBSSH_API const char *ssh_message_service_service(ssh_message msg); + +LIBSSH_API int ssh_message_global_request_reply_success(ssh_message msg, + uint16_t bound_port); + +LIBSSH_API void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data); +LIBSSH_API int ssh_execute_message_callbacks(ssh_session session); + +LIBSSH_API const char *ssh_message_channel_request_open_originator(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_originator_port(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_open_destination(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg); + +LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); + +LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); + +LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); +LIBSSH_API int ssh_message_global_request_port(ssh_message msg); + +LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port); + +LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, + int exit_status); +LIBSSH_API int ssh_channel_request_send_exit_signal(ssh_channel channel, + const char *signum, + int core, + const char *errmsg, + const char *lang); +LIBSSH_API int ssh_channel_write_stderr(ssh_channel channel, + const void *data, + uint32_t len); + +/* deprecated functions */ +SSH_DEPRECATED LIBSSH_API int ssh_accept(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_write_stderr(ssh_channel channel, + const void *data, uint32_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SERVER_H */ + +/** @} */ diff --git a/libssh/include/libssh/session.h b/libssh/include/libssh/session.h new file mode 100644 index 00000000..6edf9e51 --- /dev/null +++ b/libssh/include/libssh/session.h @@ -0,0 +1,197 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SESSION_H_ +#define SESSION_H_ +#include "libssh/priv.h" +#include "libssh/kex.h" +#include "libssh/packet.h" +#include "libssh/pcap.h" +#include "libssh/auth.h" +#include "libssh/channels.h" +#include "libssh/poll.h" + +/* These are the different states a SSH session can be into its life */ +enum ssh_session_state_e { + SSH_SESSION_STATE_NONE=0, + SSH_SESSION_STATE_CONNECTING, + SSH_SESSION_STATE_SOCKET_CONNECTED, + SSH_SESSION_STATE_BANNER_RECEIVED, + SSH_SESSION_STATE_INITIAL_KEX, + SSH_SESSION_STATE_KEXINIT_RECEIVED, + SSH_SESSION_STATE_DH, + SSH_SESSION_STATE_AUTHENTICATING, + SSH_SESSION_STATE_AUTHENTICATED, + SSH_SESSION_STATE_ERROR, + SSH_SESSION_STATE_DISCONNECTED +}; + +enum ssh_dh_state_e { + DH_STATE_INIT=0, + DH_STATE_INIT_SENT, + DH_STATE_NEWKEYS_SENT, + DH_STATE_FINISHED +}; + +enum ssh_pending_call_e { + SSH_PENDING_CALL_NONE = 0, + SSH_PENDING_CALL_CONNECT, + SSH_PENDING_CALL_AUTH_NONE, + SSH_PENDING_CALL_AUTH_PASSWORD, + SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, + SSH_PENDING_CALL_AUTH_PUBKEY, + SSH_PENDING_CALL_AUTH_AGENT, + SSH_PENDING_CALL_AUTH_KBDINT_INIT, + SSH_PENDING_CALL_AUTH_KBDINT_SEND +}; + +/* libssh calls may block an undefined amount of time */ +#define SSH_SESSION_FLAG_BLOCKING 1 + +/* Client successfully authenticated */ +#define SSH_SESSION_FLAG_AUTHENTICATED 2 + +/* codes to use with ssh_handle_packets*() */ +#define SSH_TIMEOUT_INFINITE -1 +#define SSH_TIMEOUT_USER -2 +#define SSH_TIMEOUT_NONBLOCKING 0 + +/* members that are common to ssh_session and ssh_bind */ +struct ssh_common_struct { + struct error_struct error; + ssh_callbacks callbacks; /* Callbacks to user functions */ + int log_verbosity; /* verbosity of the log functions */ + int log_indent; /* indentation level in enter_function logs */ +}; + +struct ssh_session_struct { + struct ssh_common_struct common; + struct ssh_socket_struct *socket; + char *serverbanner; + char *clientbanner; + int protoversion; + int server; + int client; + int openssh; + uint32_t send_seq; + uint32_t recv_seq; +/* status flags */ + int closed; + int closed_by_except; + + int connected; + /* !=0 when the user got a session handle */ + int alive; + /* two previous are deprecated */ + /* int auth_service_asked; */ + + /* session flags (SSH_SESSION_FLAG_*) */ + int flags; + + ssh_string banner; /* that's the issue banner from + the server */ + char *discon_msg; /* disconnect message from + the remote host */ + ssh_buffer in_buffer; + PACKET in_packet; + ssh_buffer out_buffer; + + /* the states are used by the nonblocking stuff to remember */ + /* where it was before being interrupted */ + enum ssh_pending_call_e pending_call_state; + enum ssh_session_state_e session_state; + int packet_state; + enum ssh_dh_state_e dh_handshake_state; + enum ssh_auth_service_state_e auth_service_state; + enum ssh_auth_state_e auth_state; + enum ssh_channel_request_state_e global_req_state; + struct ssh_agent_state_struct *agent_state; + struct ssh_auth_auto_state_struct *auth_auto_state; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; + struct ssh_crypto_struct *next_crypto; /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ + + struct ssh_list *channels; /* linked list of channels */ + int maxchannel; + int exec_channel_opened; /* version 1 only. more + info in channels1.c */ + ssh_agent agent; /* ssh agent */ + +/* keyb interactive data */ + struct ssh_kbdint_struct *kbdint; + int version; /* 1 or 2 */ + /* server host keys */ + struct { + ssh_key rsa_key; + ssh_key dsa_key; + ssh_key ecdsa_key; + + /* The type of host key wanted by client */ + enum ssh_keytypes_e hostkey; + } srv; + /* auths accepted by server */ + int auth_methods; + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ + int (*ssh_message_callback)( struct ssh_session_struct *session, ssh_message msg, void *userdata); + void *ssh_message_callback_data; + + void (*ssh_connection_callback)( struct ssh_session_struct *session); + struct ssh_packet_callbacks_struct default_packet_callbacks; + struct ssh_list *packet_callbacks; + struct ssh_socket_callbacks_struct socket_callbacks; + ssh_poll_ctx default_poll_ctx; + /* options */ +#ifdef WITH_PCAP + ssh_pcap_context pcap_ctx; /* pcap debugging context */ +#endif + struct { + struct ssh_list *identity; + char *username; + char *host; + char *bindaddr; /* bind the client to an ip addr */ + char *sshdir; + char *knownhosts; + char *wanted_methods[10]; + char *ProxyCommand; + unsigned long timeout; /* seconds */ + unsigned long timeout_usec; + unsigned int port; + socket_t fd; + int StrictHostKeyChecking; + int ssh2; + int ssh1; + char compressionlevel; + } opts; +}; + +/** @internal + * @brief a termination function evaluates the status of an object + * @param user[in] object to evaluate + * @returns 1 if the polling routine should terminate, 0 instead + */ +typedef int (*ssh_termination_function)(void *user); +int ssh_handle_packets(ssh_session session, int timeout); +int ssh_handle_packets_termination(ssh_session session, int timeout, + ssh_termination_function fct, void *user); +void ssh_socket_exception_callback(int code, int errno_code, void *user); + +#endif /* SESSION_H_ */ diff --git a/libssh/include/libssh/sftp.h b/libssh/include/libssh/sftp.h new file mode 100644 index 00000000..462e04c5 --- /dev/null +++ b/libssh/include/libssh/sftp.h @@ -0,0 +1,979 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_sftp The libssh SFTP API + * + * @brief SFTP handling functions + * + * SFTP commands are channeled by the ssh sftp subsystem. Every packet is + * sent/read using a sftp_packet type structure. Related to these packets, + * most of the server answers are messages having an ID and a message + * specific part. It is described by sftp_message when reading a message, + * the sftp system puts it into the queue, so the process having asked for + * it can fetch it, while continuing to read for other messages (it is + * unspecified in which order messages may be sent back to the client + * + * @{ + */ + +#ifndef SFTP_H +#define SFTP_H + +#include + +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef uid_t + typedef uint32_t uid_t; +#endif /* uid_t */ +#ifndef gid_t + typedef uint32_t gid_t; +#endif /* gid_t */ +#ifdef _MSC_VER +#ifndef ssize_t + typedef _W64 SSIZE_T ssize_t; +#endif /* ssize_t */ +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#define LIBSFTP_VERSION 3 + +typedef struct sftp_attributes_struct* sftp_attributes; +typedef struct sftp_client_message_struct* sftp_client_message; +typedef struct sftp_dir_struct* sftp_dir; +typedef struct sftp_ext_struct *sftp_ext; +typedef struct sftp_file_struct* sftp_file; +typedef struct sftp_message_struct* sftp_message; +typedef struct sftp_packet_struct* sftp_packet; +typedef struct sftp_request_queue_struct* sftp_request_queue; +typedef struct sftp_session_struct* sftp_session; +typedef struct sftp_status_message_struct* sftp_status_message; +typedef struct sftp_statvfs_struct* sftp_statvfs_t; + +struct sftp_session_struct { + ssh_session session; + ssh_channel channel; + int server_version; + int client_version; + int version; + sftp_request_queue queue; + uint32_t id_counter; + int errnum; + void **handles; + sftp_ext ext; +}; + +struct sftp_packet_struct { + sftp_session sftp; + uint8_t type; + ssh_buffer payload; +}; + +/* file handler */ +struct sftp_file_struct { + sftp_session sftp; + char *name; + uint64_t offset; + ssh_string handle; + int eof; + int nonblocking; +}; + +struct sftp_dir_struct { + sftp_session sftp; + char *name; + ssh_string handle; /* handle to directory */ + ssh_buffer buffer; /* contains raw attributes from server which haven't been parsed */ + uint32_t count; /* counts the number of following attributes structures into buffer */ + int eof; /* end of directory listing */ +}; + +struct sftp_message_struct { + sftp_session sftp; + uint8_t packet_type; + ssh_buffer payload; + uint32_t id; +}; + +/* this is a bunch of all data that could be into a message */ +struct sftp_client_message_struct { + sftp_session sftp; + uint8_t type; + uint32_t id; + char *filename; /* can be "path" */ + uint32_t flags; + sftp_attributes attr; + ssh_string handle; + uint64_t offset; + uint32_t len; + int attr_num; + ssh_buffer attrbuf; /* used by sftp_reply_attrs */ + ssh_string data; /* can be newpath of rename() */ +}; + +struct sftp_request_queue_struct { + sftp_request_queue next; + sftp_message message; +}; + +/* SSH_FXP_MESSAGE described into .7 page 26 */ +struct sftp_status_message_struct { + uint32_t id; + uint32_t status; + ssh_string error; + ssh_string lang; + char *errormsg; + char *langmsg; +}; + +struct sftp_attributes_struct { + char *name; + char *longname; /* ls -l output on openssh, not reliable else */ + uint32_t flags; + uint8_t type; + uint64_t size; + uint32_t uid; + uint32_t gid; + char *owner; /* set if openssh and version 4 */ + char *group; /* set if openssh and version 4 */ + uint32_t permissions; + uint64_t atime64; + uint32_t atime; + uint32_t atime_nseconds; + uint64_t createtime; + uint32_t createtime_nseconds; + uint64_t mtime64; + uint32_t mtime; + uint32_t mtime_nseconds; + ssh_string acl; + uint32_t extended_count; + ssh_string extended_type; + ssh_string extended_data; +}; + +/** + * @brief SFTP statvfs structure. + */ +struct sftp_statvfs_struct { + uint64_t f_bsize; /** file system block size */ + uint64_t f_frsize; /** fundamental fs block size */ + uint64_t f_blocks; /** number of blocks (unit f_frsize) */ + uint64_t f_bfree; /** free blocks in file system */ + uint64_t f_bavail; /** free blocks for non-root */ + uint64_t f_files; /** total file inodes */ + uint64_t f_ffree; /** free file inodes */ + uint64_t f_favail; /** free file inodes for to non-root */ + uint64_t f_fsid; /** file system id */ + uint64_t f_flag; /** bit mask of f_flag values */ + uint64_t f_namemax; /** maximum filename length */ +}; + +/** + * @brief Start a new sftp session. + * + * @param session The ssh session to use. + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + */ +LIBSSH_API sftp_session sftp_new(ssh_session session); + +/** + * @brief Close and deallocate a sftp session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_free(sftp_session sftp); + +/** + * @brief Initialize the sftp session with the server. + * + * @param sftp The sftp session to initialize. + * + * @return 0 on success, < 0 on error with ssh error set. + * + * @see sftp_new() + */ +LIBSSH_API int sftp_init(sftp_session sftp); + +/** + * @brief Get the last sftp error. + * + * Use this function to get the latest error set by a posix like sftp function. + * + * @param sftp The sftp session where the error is saved. + * + * @return The saved error (see server responses), < 0 if an error + * in the function occured. + * + * @see Server responses + */ +LIBSSH_API int sftp_get_error(sftp_session sftp); + +/** + * @brief Get the count of extensions provided by the server. + * + * @param sftp The sftp session to use. + * + * @return The count of extensions provided by the server, 0 on error or + * not available. + */ +LIBSSH_API unsigned int sftp_extensions_get_count(sftp_session sftp); + +/** + * @brief Get the name of the extension provided by the server. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension name you want. + * + * @return The name of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_name(sftp_session sftp, unsigned int indexn); + +/** + * @brief Get the data of the extension provided by the server. + * + * This is normally the version number of the extension. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension data you want. + * + * @return The data of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_data(sftp_session sftp, unsigned int indexn); + +/** + * @brief Check if the given extension is supported. + * + * @param sftp The sftp session to use. + * + * @param name The name of the extension. + * + * @param data The data of the extension. + * + * @return 1 if supported, 0 if not. + * + * Example: + * + * @code + * sftp_extension_supported(sftp, "statvfs@openssh.com", "2"); + * @endcode + */ +LIBSSH_API int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data); + +/** + * @brief Open a directory used to obtain directory entries. + * + * @param session The sftp session handle to open the directory. + * @param path The path of the directory to open. + * + * @return A sftp directory handle or NULL on error with ssh and + * sftp error set. + * + * @see sftp_readdir + * @see sftp_closedir + */ +LIBSSH_API sftp_dir sftp_opendir(sftp_session session, const char *path); + +/** + * @brief Get a single file attributes structure of a directory. + * + * @param session The sftp session handle to read the directory entry. + * @param dir The opened sftp directory handle to read from. + * + * @return A file attribute structure or NULL at the end of the + * directory. + * + * @see sftp_opendir() + * @see sftp_attribute_free() + * @see sftp_closedir() + */ +LIBSSH_API sftp_attributes sftp_readdir(sftp_session session, sftp_dir dir); + +/** + * @brief Tell if the directory has reached EOF (End Of File). + * + * @param dir The sftp directory handle. + * + * @return 1 if the directory is EOF, 0 if not. + * + * @see sftp_readdir() + */ +LIBSSH_API int sftp_dir_eof(sftp_dir dir); + +/** + * @brief Get information about a file or directory. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_stat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory. + * + * Identical to sftp_stat, but if the file or directory is a symbolic link, + * then the link itself is stated, not the file that it refers to. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_lstat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory from a file handle. + * + * @param file The sftp file handle to get the stat information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_fstat(sftp_file file); + +/** + * @brief Free a sftp attribute structure. + * + * @param file The sftp attribute structure to free. + */ +LIBSSH_API void sftp_attributes_free(sftp_attributes file); + +/** + * @brief Close a directory handle opened by sftp_opendir(). + * + * @param dir The sftp directory handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + */ +LIBSSH_API int sftp_closedir(sftp_dir dir); + +/** + * @brief Close an open file handle. + * + * @param file The open sftp file handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + * + * @see sftp_open() + */ +LIBSSH_API int sftp_close(sftp_file file); + +/** + * @brief Open a file on the server. + * + * @param session The sftp session handle. + * + * @param file The file to be opened. + * + * @param accesstype Is one of O_RDONLY, O_WRONLY or O_RDWR which request + * opening the file read-only,write-only or read/write. + * Acesss may also be bitwise-or'd with one or more of + * the following: + * O_CREAT - If the file does not exist it will be + * created. + * O_EXCL - When used with O_CREAT, if the file already + * exists it is an error and the open will fail. + * O_TRUNC - If the file already exists it will be + * truncated. + * + * @param mode Mode specifies the permissions to use if a new file is + * created. It is modified by the process's umask in + * the usual way: The permissions of the created file are + * (mode & ~umask) + * + * @return A sftp file handle, NULL on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_file sftp_open(sftp_session session, const char *file, int accesstype, + mode_t mode); + +/** + * @brief Make the sftp communication for this file handle non blocking. + * + * @param[in] handle The file handle to set non blocking. + */ +LIBSSH_API void sftp_file_set_nonblocking(sftp_file handle); + +/** + * @brief Make the sftp communication for this file handle blocking. + * + * @param[in] handle The file handle to set blocking. + */ +LIBSSH_API void sftp_file_set_blocking(sftp_file handle); + +/** + * @brief Read from a file using an opened sftp file handle. + * + * @param file The opened sftp file handle to be read from. + * + * @param buf Pointer to buffer to recieve read data. + * + * @param count Size of the buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); + +/** + * @brief Start an asynchronous read from a file using an opened sftp file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions: + * + * sftp_async_read_begin() and sftp_async_read(). + * + * The first step is to call sftp_async_read_begin(). This function returns a + * request identifier. The second step is to call sftp_async_read() using the + * returned identifier. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Size to read in bytes. + * + * @return An identifier corresponding to the sent request, < 0 on + * error. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_async_read_begin() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_async_read() is called. + * Not calling sftp_async_read() will lead to memory + * leaks. + * + * @see sftp_async_read() + * @see sftp_open() + */ +LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); + +/** + * @brief Wait for an asynchronous read to complete and save the data. + * + * @param file The opened sftp file handle to be read from. + * + * @param data Pointer to buffer to recieve read data. + * + * @param len Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_async_read_begin() call. + * + * @param id The identifier returned by the sftp_async_read_begin() + * function. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occured, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid identifier + * will never return. + * + * @see sftp_async_read_begin() + */ +LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_t id); + +/** + * @brief Write to a file using an opened sftp file handle. + * + * @param file Open sftp file handle to write to. + * + * @param buf Pointer to buffer to write data. + * + * @param count Size of buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_open() + * @see sftp_read() + * @see sftp_close() + */ +LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); + +/** + * @brief Seek to a specific location in a file. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek(sftp_file file, uint32_t new_offset); + +/** + * @brief Seek to a specific location in a file. This is the + * 64bit version. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek64(sftp_file file, uint64_t new_offset); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API unsigned long sftp_tell(sftp_file file); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API uint64_t sftp_tell64(sftp_file file); + +/** + * @brief Rewinds the position of the file pointer to the beginning of the + * file. + * + * @param file Open sftp file handle. + */ +LIBSSH_API void sftp_rewind(sftp_file file); + +/** + * @brief Unlink (delete) a file. + * + * @param sftp The sftp session handle. + * + * @param file The file to unlink/delete. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); + +/** + * @brief Remove a directoy. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to remove. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rmdir(sftp_session sftp, const char *directory); + +/** + * @brief Create a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to create. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode); + +/** + * @brief Rename or move a file or directory. + * + * @param sftp The sftp session handle. + * + * @param original The original url (source url) of file or directory to + * be moved. + * + * @param newname The new url (destination url) of the file or directory + * after the move. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char *newname); + +/** + * @brief Set file attributes on a file, directory or symbolic link. + * + * @param sftp The sftp session handle. + * + * @param file The file which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief Change the file owner and group + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param owner The new owner which should be set. + * + * @param group The new group which should be set. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group); + +/** + * @brief Change permissions of a file + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chmod(sftp_session sftp, const char *file, mode_t mode); + +/** + * @brief Change the last modification and access time of a file. + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param times A timeval structure which contains the desired access + * and modification time. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_utimes(sftp_session sftp, const char *file, const struct timeval *times); + +/** + * @brief Create a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param target Specifies the target of the symlink. + * + * @param dest Specifies the path name of the symlink to be created. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *dest); + +/** + * @brief Read the value of a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param path Specifies the path name of the symlink to be read. + * + * @return The target of the link, NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param sftp The sftp session handle. + * + * @param path The pathname of any file within the mounted file system. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param file An opened file. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_fstatvfs(sftp_file file); + +/** + * @brief Free the memory of an allocated statvfs. + * + * @param statvfs_o The statvfs to free. + */ +LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); + +/** + * @brief Canonicalize a sftp path. + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return The canonicalize path, NULL on error. + */ +LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); + +/** + * @brief Get the version of the SFTP protocol supported by the server + * + * @param sftp The sftp session handle. + * + * @return The server version. + */ +LIBSSH_API int sftp_server_version(sftp_session sftp); + +#ifdef WITH_SERVER +/** + * @brief Create a new sftp server session. + * + * @param session The ssh session to use. + * + * @param chan The ssh channel to use. + * + * @return A new sftp server session. + */ +LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); + +/** + * @brief Intialize the sftp server. + * + * @param sftp The sftp session to init. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_server_init(sftp_session sftp); +#endif /* WITH_SERVER */ + +/* this is not a public interface */ +#define SFTP_HANDLES 256 +sftp_packet sftp_packet_read(sftp_session sftp); +int sftp_packet_write(sftp_session sftp,uint8_t type, ssh_buffer payload); +void sftp_packet_free(sftp_packet packet); +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); +sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf,int expectname); +/* sftpserver.c */ + +sftp_client_message sftp_get_client_message(sftp_session sftp); +void sftp_client_message_free(sftp_client_message msg); +int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr); +int sftp_reply_handle(sftp_client_message msg, ssh_string handle); +ssh_string sftp_handle_alloc(sftp_session sftp, void *info); +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr); +void *sftp_handle(sftp_session sftp, ssh_string handle); +int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message); +int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr); +int sftp_reply_names(sftp_client_message msg); +int sftp_reply_data(sftp_client_message msg, const void *data, int len); +void sftp_handle_remove(sftp_session sftp, void *handle); + +/* SFTP commands and constants */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 + +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 + +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* attributes */ +/* sftp draft is completely braindead : version 3 and 4 have different flags for same constants */ +/* and even worst, version 4 has same flag for 2 different constants */ +/* follow up : i won't develop any sftp4 compliant library before having a clarification */ + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH_FILEXFER_ATTR_ACL 0x00000040 +#define SSH_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 + +/* types */ +#define SSH_FILEXFER_TYPE_REGULAR 1 +#define SSH_FILEXFER_TYPE_DIRECTORY 2 +#define SSH_FILEXFER_TYPE_SYMLINK 3 +#define SSH_FILEXFER_TYPE_SPECIAL 4 +#define SSH_FILEXFER_TYPE_UNKNOWN 5 + +/** + * @name Server responses + * + * @brief Responses returned by the sftp server. + * @{ + */ + +/** No error */ +#define SSH_FX_OK 0 +/** End-of-file encountered */ +#define SSH_FX_EOF 1 +/** File doesn't exist */ +#define SSH_FX_NO_SUCH_FILE 2 +/** Permission denied */ +#define SSH_FX_PERMISSION_DENIED 3 +/** Generic failure */ +#define SSH_FX_FAILURE 4 +/** Garbage received from server */ +#define SSH_FX_BAD_MESSAGE 5 +/** No connection has been set up */ +#define SSH_FX_NO_CONNECTION 6 +/** There was a connection, but we lost it */ +#define SSH_FX_CONNECTION_LOST 7 +/** Operation not supported by the server */ +#define SSH_FX_OP_UNSUPPORTED 8 +/** Invalid file handle */ +#define SSH_FX_INVALID_HANDLE 9 +/** No such file or directory path exists */ +#define SSH_FX_NO_SUCH_PATH 10 +/** An attempt to create an already existing file or directory has been made */ +#define SSH_FX_FILE_ALREADY_EXISTS 11 +/** We are trying to write on a write-protected filesystem */ +#define SSH_FX_WRITE_PROTECT 12 +/** No media in remote drive */ +#define SSH_FX_NO_MEDIA 13 + +/** @} */ + +/* file flags */ +#define SSH_FXF_READ 0x01 +#define SSH_FXF_WRITE 0x02 +#define SSH_FXF_APPEND 0x04 +#define SSH_FXF_CREAT 0x08 +#define SSH_FXF_TRUNC 0x10 +#define SSH_FXF_EXCL 0x20 +#define SSH_FXF_TEXT 0x40 + +/* rename flags */ +#define SSH_FXF_RENAME_OVERWRITE 0x00000001 +#define SSH_FXF_RENAME_ATOMIC 0x00000002 +#define SSH_FXF_RENAME_NATIVE 0x00000004 + +#define SFTP_OPEN SSH_FXP_OPEN +#define SFTP_CLOSE SSH_FXP_CLOSE +#define SFTP_READ SSH_FXP_READ +#define SFTP_WRITE SSH_FXP_WRITE +#define SFTP_LSTAT SSH_FXP_LSTAT +#define SFTP_FSTAT SSH_FXP_FSTAT +#define SFTP_SETSTAT SSH_FXP_SETSTAT +#define SFTP_FSETSTAT SSH_FXP_FSETSTAT +#define SFTP_OPENDIR SSH_FXP_OPENDIR +#define SFTP_READDIR SSH_FXP_READDIR +#define SFTP_REMOVE SSH_FXP_REMOVE +#define SFTP_MKDIR SSH_FXP_MKDIR +#define SFTP_RMDIR SSH_FXP_RMDIR +#define SFTP_REALPATH SSH_FXP_REALPATH +#define SFTP_STAT SSH_FXP_STAT +#define SFTP_RENAME SSH_FXP_RENAME +#define SFTP_READLINK SSH_FXP_READLINK +#define SFTP_SYMLINK SSH_FXP_SYMLINK + +/* openssh flags */ +#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ +#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ + +#ifdef __cplusplus +} ; +#endif + +#endif /* SFTP_H */ + +/** @} */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/include/libssh/socket.h b/libssh/include/libssh/socket.h new file mode 100644 index 00000000..e25bb0c9 --- /dev/null +++ b/libssh/include/libssh/socket.h @@ -0,0 +1,68 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SOCKET_H_ +#define SOCKET_H_ + +#include "libssh/callbacks.h" +struct ssh_poll_handle_struct; +/* socket.c */ + +struct ssh_socket_struct; +typedef struct ssh_socket_struct* ssh_socket; + +int ssh_socket_init(void); +void ssh_socket_cleanup(void); +ssh_socket ssh_socket_new(ssh_session session); +void ssh_socket_reset(ssh_socket s); +void ssh_socket_free(ssh_socket s); +void ssh_socket_set_fd(ssh_socket s, socket_t fd); +socket_t ssh_socket_get_fd_in(ssh_socket s); +#ifndef _WIN32 +int ssh_socket_unix(ssh_socket s, const char *path); +void ssh_execute_command(const char *command, socket_t in, socket_t out); +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command); +#endif +void ssh_socket_close(ssh_socket s); +int ssh_socket_write(ssh_socket s,const void *buffer, int len); +int ssh_socket_is_open(ssh_socket s); +int ssh_socket_fd_isset(ssh_socket s, fd_set *set); +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd); +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd); +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd); +int ssh_socket_nonblocking_flush(ssh_socket s); +void ssh_socket_set_write_wontblock(ssh_socket s); +void ssh_socket_set_read_wontblock(ssh_socket s); +void ssh_socket_set_except(ssh_socket s); +int ssh_socket_get_status(ssh_socket s); +int ssh_socket_buffered_write_bytes(ssh_socket s); +int ssh_socket_data_available(ssh_socket s); +int ssh_socket_data_writable(ssh_socket s); +void ssh_socket_set_nonblocking(socket_t fd); +void ssh_socket_set_blocking(socket_t fd); + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks); +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s); +struct ssh_poll_handle_struct * ssh_socket_get_poll_handle_in(ssh_socket s); +struct ssh_poll_handle_struct * ssh_socket_get_poll_handle_out(ssh_socket s); + +int ssh_socket_connect(ssh_socket s, const char *host, int port, const char *bind_addr); + +#endif /* SOCKET_H_ */ diff --git a/libssh/include/libssh/ssh1.h b/libssh/include/libssh/ssh1.h new file mode 100644 index 00000000..ce67f20b --- /dev/null +++ b/libssh/include/libssh/ssh1.h @@ -0,0 +1,82 @@ +#ifndef __SSH1_H +#define __SSH1_H + +#define SSH_MSG_NONE 0 /* no message */ +#define SSH_MSG_DISCONNECT 1 /* cause (string) */ +#define SSH_SMSG_PUBLIC_KEY 2 /* ck,msk,srvk,hostk */ +#define SSH_CMSG_SESSION_KEY 3 /* key (BIGNUM) */ +#define SSH_CMSG_USER 4 /* user (string) */ +#define SSH_CMSG_AUTH_RHOSTS 5 /* user (string) */ +#define SSH_CMSG_AUTH_RSA 6 /* modulus (BIGNUM) */ +#define SSH_SMSG_AUTH_RSA_CHALLENGE 7 /* int (BIGNUM) */ +#define SSH_CMSG_AUTH_RSA_RESPONSE 8 /* int (BIGNUM) */ +#define SSH_CMSG_AUTH_PASSWORD 9 /* pass (string) */ +#define SSH_CMSG_REQUEST_PTY 10 /* TERM, tty modes */ +#define SSH_CMSG_WINDOW_SIZE 11 /* row,col,xpix,ypix */ +#define SSH_CMSG_EXEC_SHELL 12 /* */ +#define SSH_CMSG_EXEC_CMD 13 /* cmd (string) */ +#define SSH_SMSG_SUCCESS 14 /* */ +#define SSH_SMSG_FAILURE 15 /* */ +#define SSH_CMSG_STDIN_DATA 16 /* data (string) */ +#define SSH_SMSG_STDOUT_DATA 17 /* data (string) */ +#define SSH_SMSG_STDERR_DATA 18 /* data (string) */ +#define SSH_CMSG_EOF 19 /* */ +#define SSH_SMSG_EXITSTATUS 20 /* status (int) */ +#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* channel (int) */ +#define SSH_MSG_CHANNEL_OPEN_FAILURE 22 /* channel (int) */ +#define SSH_MSG_CHANNEL_DATA 23 /* ch,data (int,str) */ +#define SSH_MSG_CHANNEL_CLOSE 24 /* channel (int) */ +#define SSH_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* channel (int) */ +/* SSH_CMSG_X11_REQUEST_FORWARDING 26 OBSOLETE */ +#define SSH_SMSG_X11_OPEN 27 /* channel (int) */ +#define SSH_CMSG_PORT_FORWARD_REQUEST 28 /* p,host,hp (i,s,i) */ +#define SSH_MSG_PORT_OPEN 29 /* ch,h,p (i,s,i) */ +#define SSH_CMSG_AGENT_REQUEST_FORWARDING 30 /* */ +#define SSH_SMSG_AGENT_OPEN 31 /* port (int) */ +#define SSH_MSG_IGNORE 32 /* string */ +#define SSH_CMSG_EXIT_CONFIRMATION 33 /* */ +#define SSH_CMSG_X11_REQUEST_FORWARDING 34 /* proto,data (s,s) */ +#define SSH_CMSG_AUTH_RHOSTS_RSA 35 /* user,mod (s,mpi) */ +#define SSH_MSG_DEBUG 36 /* string */ +#define SSH_CMSG_REQUEST_COMPRESSION 37 /* level 1-9 (int) */ +#define SSH_CMSG_MAX_PACKET_SIZE 38 /* size 4k-1024k (int) */ +#define SSH_CMSG_AUTH_TIS 39 /* we use this for s/key */ +#define SSH_SMSG_AUTH_TIS_CHALLENGE 40 /* challenge (string) */ +#define SSH_CMSG_AUTH_TIS_RESPONSE 41 /* response (string) */ +#define SSH_CMSG_AUTH_KERBEROS 42 /* (KTEXT) */ +#define SSH_SMSG_AUTH_KERBEROS_RESPONSE 43 /* (KTEXT) */ +#define SSH_CMSG_HAVE_KERBEROS_TGT 44 /* credentials (s) */ +#define SSH_CMSG_HAVE_AFS_TOKEN 65 /* token (s) */ + +/* protocol version 1.5 overloads some version 1.3 message types */ +#define SSH_MSG_CHANNEL_INPUT_EOF SSH_MSG_CHANNEL_CLOSE +#define SSH_MSG_CHANNEL_OUTPUT_CLOSE SSH_MSG_CHANNEL_CLOSE_CONFIRMATION + +/* + * Authentication methods. New types can be added, but old types should not + * be removed for compatibility. The maximum allowed value is 31. + */ +#define SSH_AUTH_RHOSTS 1 +#define SSH_AUTH_RSA 2 +#define SSH_AUTH_PASSWORD 3 +#define SSH_AUTH_RHOSTS_RSA 4 +#define SSH_AUTH_TIS 5 +#define SSH_AUTH_KERBEROS 6 +#define SSH_PASS_KERBEROS_TGT 7 + /* 8 to 15 are reserved */ +#define SSH_PASS_AFS_TOKEN 21 + +/* Protocol flags. These are bit masks. */ +#define SSH_PROTOFLAG_SCREEN_NUMBER 1 /* X11 forwarding includes screen */ +#define SSH_PROTOFLAG_HOST_IN_FWD_OPEN 2 /* forwarding opens contain host */ + +/* cipher flags. they are bit numbers */ +#define SSH_CIPHER_NONE 0 /* No encryption */ +#define SSH_CIPHER_IDEA 1 /* IDEA in CFB mode */ +#define SSH_CIPHER_DES 2 /* DES in CBC mode */ +#define SSH_CIPHER_3DES 3 /* Triple-DES in CBC mode */ +#define SSH_CIPHER_RC4 5 /* RC4 */ +#define SSH_CIPHER_BLOWFISH 6 + +#endif + diff --git a/libssh/include/libssh/ssh2.h b/libssh/include/libssh/ssh2.h new file mode 100644 index 00000000..f66dd2a9 --- /dev/null +++ b/libssh/include/libssh/ssh2.h @@ -0,0 +1,73 @@ +#ifndef __SSH2_H +#define __SSH2_H + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 +#define SSH2_MSG_ECMQV_INIT 30 +#define SSH2_MSG_ECMQV_REPLY 31 + +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 +#define SSH2_MSG_KEX_DH_GEX_INIT 32 +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 + +#endif diff --git a/libssh/include/libssh/string.h b/libssh/include/libssh/string.h new file mode 100644 index 00000000..8c7db1df --- /dev/null +++ b/libssh/include/libssh/string.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STRING_H_ +#define STRING_H_ +#include "libssh/priv.h" + +/* must be 32 bits number + immediately our data */ +#ifdef _MSC_VER +#pragma pack(1) +#endif +struct ssh_string_struct { + uint32_t size; + unsigned char data[1]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack() +#endif +; + +#endif /* STRING_H_ */ diff --git a/libssh/include/libssh/threads.h b/libssh/include/libssh/threads.h new file mode 100644 index 00000000..70072648 --- /dev/null +++ b/libssh/include/libssh/threads.h @@ -0,0 +1,31 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef THREADS_H_ +#define THREADS_H_ + +#include +#include + +int ssh_threads_init(void); +void ssh_threads_finalize(void); +const char *ssh_threads_get_type(void); + +#endif /* THREADS_H_ */ diff --git a/libssh/include/libssh/wrapper.h b/libssh/include/libssh/wrapper.h new file mode 100644 index 00000000..90c268d9 --- /dev/null +++ b/libssh/include/libssh/wrapper.h @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef WRAPPER_H_ +#define WRAPPER_H_ + +#include "config.h" +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" + +enum ssh_mac_e { + SSH_MAC_SHA1=1, + SSH_MAC_SHA256, + SSH_MAC_SHA384, + SSH_MAC_SHA512 +}; + +enum ssh_hmac_e { + SSH_HMAC_SHA1 = 1, + SSH_HMAC_MD5 +}; + +enum ssh_des_e { + SSH_3DES, + SSH_DES +}; + +typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; +MD5CTX md5_init(void); +void md5_update(MD5CTX c, const void *data, unsigned long len); +void md5_final(unsigned char *md,MD5CTX c); +SHACTX sha1_init(void); +void sha1_update(SHACTX c, const void *data, unsigned long len); +void sha1_final(unsigned char *md,SHACTX c); +void sha1(unsigned char *digest,int len,unsigned char *hash); +void sha256(unsigned char *digest, int len, unsigned char *hash); + +void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen); + +ssh_mac_ctx ssh_mac_ctx_init(enum ssh_mac_e type); +void ssh_mac_update(ssh_mac_ctx ctx, const void *data, unsigned long len); +void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx); + +HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); +void hmac_update(HMACCTX c, const void *data, unsigned long len); +void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); + +int crypt_set_algorithms(ssh_session session, enum ssh_des_e des_type); +int crypt_set_algorithms_server(ssh_session session); +struct ssh_crypto_struct *crypto_new(void); +void crypto_free(struct ssh_crypto_struct *crypto); + + +#endif /* WRAPPER_H_ */ diff --git a/libssh/libssh-build-tree-settings.cmake.in b/libssh/libssh-build-tree-settings.cmake.in new file mode 100644 index 00000000..16b406aa --- /dev/null +++ b/libssh/libssh-build-tree-settings.cmake.in @@ -0,0 +1 @@ +set(LIBSSH_INLUDE_DIR @PROJECT_SOURCE_DIR@/include) diff --git a/libssh/libssh-config-version.cmake.in b/libssh/libssh-config-version.cmake.in new file mode 100644 index 00000000..98f292c0 --- /dev/null +++ b/libssh/libssh-config-version.cmake.in @@ -0,0 +1,11 @@ +set(PACKAGE_VERSION @APPLICATION_VERSION@) + +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/libssh/libssh-config.cmake.in b/libssh/libssh-config.cmake.in new file mode 100644 index 00000000..1e287fae --- /dev/null +++ b/libssh/libssh-config.cmake.in @@ -0,0 +1,11 @@ +get_filename_component(LIBSSH_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) + +if (EXISTS "${LIBSSH_CMAKE_DIR}/CMakeCache.txt") + # In build tree + include(${LIBSSH_CMAKE_DIR}/libssh-build-tree-settings.cmake) +else() + set(LIBSSH_INCLUDE_DIR @INCLUDE_INSTALL_DIR@) +endif() + +set(LIBSSH_LIRBARY @LIB_INSTALL_DIR@/libssh.so) +set(LIBSSH_LIRBARIES @LIB_INSTALL_DIR@/libssh.so) diff --git a/libssh/libssh.pc.cmake b/libssh/libssh.pc.cmake new file mode 100644 index 00000000..3b1cb8dc --- /dev/null +++ b/libssh/libssh.pc.cmake @@ -0,0 +1,6 @@ +Name: ${APPLICATION_NAME} +Description: The SSH Library +Version: ${APPLICATION_VERSION} +Libs: -L${LIB_INSTALL_DIR} -lssh +Cflags: -I${INCLUDE_INSTALL_DIR} + diff --git a/libssh/libssh_threads.pc.cmake b/libssh/libssh_threads.pc.cmake new file mode 100644 index 00000000..5479c34f --- /dev/null +++ b/libssh/libssh_threads.pc.cmake @@ -0,0 +1,6 @@ +Name: ${APPLICATION_NAME}_threads +Description: The SSH Library Thread Extension +Version: ${APPLICATION_VERSION} +Libs: -L${LIB_INSTALL_DIR} -lssh_threads +Cflags: -I${INCLUDE_INSTALL_DIR} + diff --git a/libssh/src/CMakeLists.txt b/libssh/src/CMakeLists.txt new file mode 100644 index 00000000..1171c90d --- /dev/null +++ b/libssh/src/CMakeLists.txt @@ -0,0 +1,254 @@ +project(libssh-library C) + +set(LIBSSH_PUBLIC_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/include + CACHE INTERNAL "libssh public include directories" +) + +set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${CMAKE_BINARY_DIR} + ${OPENSSL_INCLUDE_DIRS} +) + +set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_REQUIRED_LIBRARIES} +) + +if (WIN32) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ws2_32 + ) +endif (WIN32) + +if (HAVE_LIBSOCKET) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + socket + ) +endif (HAVE_LIBSOCKET) + +if (OPENSSL_LIBRARIES) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${OPENSSL_LIBRARIES} + ) +endif (OPENSSL_LIBRARIES) + +if (GCRYPT_LIBRARY) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${GCRYPT_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${GCRYPT_LIBRARY} + ) +endif (GCRYPT_LIBRARY) + +if (WITH_ZLIB) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${ZLIB_LIBRARY} + ) +endif (WITH_ZLIB) + +set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + CACHE INTERNAL "libssh link libraries" +) + +set(LIBSSH_SHARED_LIBRARY + ssh_shared + CACHE INTERNAL "libssh shared library" +) + +if (WITH_STATIC_LIB) + set(LIBSSH_STATIC_LIBRARY + ssh_static + CACHE INTERNAL "libssh static library" + ) +endif (WITH_STATIC_LIB) + +set(libssh_SRCS + agent.c + auth.c + base64.c + buffer.c + callbacks.c + channels.c + client.c + config.c + connect.c + dh.c + ecdh.c + error.c + getpass.c + init.c + kex.c + known_hosts.c + legacy.c + libcrypto.c + log.c + match.c + messages.c + misc.c + options.c + packet.c + packet_cb.c + packet_crypt.c + pcap.c + pki.c + poll.c + session.c + scp.c + socket.c + string.c + threads.c + wrapper.c +) + +if (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + libgcrypt.c + gcrypt_missing.c + pki_gcrypt.c + ) +else (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + pki_crypto.c + ) +endif (WITH_GCRYPT) + +if (WITH_SFTP) + set(libssh_SRCS + ${libssh_SRCS} + sftp.c + ) + + if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + sftpserver.c + ) + endif (WITH_SERVER) +endif (WITH_SFTP) + +if (WITH_SSH1) + set(libssh_SRCS + ${libssh_SRCS} + auth1.c + channels1.c + crc32.c + kex1.c + packet1.c + ) +endif (WITH_SSH1) + +if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + server.c + bind.c + ) +endif (WITH_SERVER) + +if (WITH_ZLIB) + set(libssh_SRCS + ${libssh_SRCS} + gzip.c + ) +endif(WITH_ZLIB) + +include_directories( + ${LIBSSH_PUBLIC_INCLUDE_DIRS} + ${LIBSSH_PRIVATE_INCLUDE_DIRS} +) + +add_library(${LIBSSH_SHARED_LIBRARY} SHARED ${libssh_SRCS}) + +target_link_libraries(${LIBSSH_SHARED_LIBRARY} ${LIBSSH_LINK_LIBRARIES}) + +set_target_properties( + ${LIBSSH_SHARED_LIBRARY} + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh + DEFINE_SYMBOL + LIBSSH_EXPORTS +) + +if (WITH_VISIBILITY_HIDDEN) + set_target_properties(${LIBSSH_SHARED_LIBRARY} PROPERTIES COMPILE_FLAGS "-fvisibility=hidden") +endif (WITH_VISIBILITY_HIDDEN) + + +install( + TARGETS + ${LIBSSH_SHARED_LIBRARY} + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + COMPONENT libraries +) + +if (WITH_STATIC_LIB) + add_library(${LIBSSH_STATIC_LIBRARY} STATIC ${libssh_SRCS}) + + if (MSVC) + set(OUTPUT_SUFFIX static) + else (MSVC) + set(OUTPUT_SUFFIX ) + endif (MSVC) + set_target_properties( + ${LIBSSH_STATIC_LIBRARY} + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh + ARCHIVE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUFFIX} + ) + + if (WIN32) + set_target_properties( + ${LIBSSH_STATIC_LIBRARY} + PROPERTIES + COMPILE_FLAGS + "-DLIBSSH_STATIC" + ) + endif (WIN32) + + install( + TARGETS + ${LIBSSH_STATIC_LIBRARY} + DESTINATION + ${LIB_INSTALL_DIR}/${OUTPUT_SUFFIX} + COMPONENT + libraries + ) +endif (WITH_STATIC_LIB) + +if (CMAKE_HAVE_THREADS_LIBRARY) + add_subdirectory(threads) +endif (CMAKE_HAVE_THREADS_LIBRARY) diff --git a/libssh/src/agent.c b/libssh/src/agent.c new file mode 100644 index 00000000..95cf6a12 --- /dev/null +++ b/libssh/src/agent.c @@ -0,0 +1,516 @@ +/* + * agent.c - ssh agent functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* This file is based on authfd.c from OpenSSH */ + +/* + * How does the ssh-agent work? + * + * a) client sends a request to get a list of all keys + * the agent returns the count and all public keys + * b) iterate over them to check if the server likes one + * c) the client sends a sign request to the agent + * type, pubkey as blob, data to sign, flags + * the agent returns the signed data + */ + +#ifndef _WIN32 + +#include "config.h" + +#include +#include +#include +#include + +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/agent.h" +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/poll.h" +#include "libssh/pki.h" + +/* macro to check for "agent failure" message */ +#define agent_failed(x) \ + (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ + ((x) == SSH2_AGENT_FAILURE)) + +static uint32_t agent_get_u32(const void *vp) { + const uint8_t *p = (const uint8_t *)vp; + uint32_t v; + + v = (uint32_t)p[0] << 24; + v |= (uint32_t)p[1] << 16; + v |= (uint32_t)p[2] << 8; + v |= (uint32_t)p[3]; + + return v; +} + +static void agent_put_u32(void *vp, uint32_t v) { + uint8_t *p = (uint8_t *)vp; + + p[0] = (uint8_t)(v >> 24) & 0xff; + p[1] = (uint8_t)(v >> 16) & 0xff; + p[2] = (uint8_t)(v >> 8) & 0xff; + p[3] = (uint8_t)v & 0xff; +} + +static size_t atomicio(ssh_socket s, void *buf, size_t n, int do_read) { + char *b = buf; + size_t pos = 0; + ssize_t res; + ssh_pollfd_t pfd; + socket_t fd = ssh_socket_get_fd_in(s); + + pfd.fd = fd; + pfd.events = do_read ? POLLIN : POLLOUT; + + while (n > pos) { + if (do_read) { + res = read(fd, b + pos, n - pos); + } else { + res = write(fd, b + pos, n - pos); + } + switch (res) { + case -1: + if (errno == EINTR) { + continue; + } +#ifdef EWOULDBLOCK + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#else + if (errno == EAGAIN) { +#endif + (void) ssh_poll(&pfd, 1, -1); + continue; + } + return 0; + case 0: + /* read returns 0 on end-of-file */ + errno = do_read ? 0 : EPIPE; + return pos; + default: + pos += (size_t) res; + } + } + + return pos; +} + +ssh_agent agent_new(struct ssh_session_struct *session) { + ssh_agent agent = NULL; + + agent = malloc(sizeof(struct ssh_agent_struct)); + if (agent == NULL) { + return NULL; + } + ZERO_STRUCTP(agent); + + agent->count = 0; + agent->sock = ssh_socket_new(session); + if (agent->sock == NULL) { + SAFE_FREE(agent); + return NULL; + } + + return agent; +} + +void agent_close(struct ssh_agent_struct *agent) { + if (agent == NULL) { + return; + } + + if (getenv("SSH_AUTH_SOCK")) { + ssh_socket_close(agent->sock); + } +} + +void agent_free(ssh_agent agent) { + if (agent) { + if (agent->ident) { + ssh_buffer_free(agent->ident); + } + if (agent->sock) { + agent_close(agent); + ssh_socket_free(agent->sock); + } + SAFE_FREE(agent); + } +} + +static int agent_connect(ssh_session session) { + const char *auth_sock = NULL; + + if (session == NULL || session->agent == NULL) { + return -1; + } + + auth_sock = getenv("SSH_AUTH_SOCK"); + + if (auth_sock && *auth_sock) { + if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { + return -1; + } + return 0; + } + + return -1; +} + +#if 0 +static int agent_decode_reply(struct ssh_session_struct *session, int type) { + switch (type) { + case SSH_AGENT_FAILURE: + case SSH2_AGENT_FAILURE: + case SSH_COM_AGENT2_FAILURE: + ssh_log(session, SSH_LOG_RARE, "SSH_AGENT_FAILURE"); + return 0; + case SSH_AGENT_SUCCESS: + return 1; + default: + ssh_set_error(session, SSH_FATAL, + "Bad response from authentication agent: %d", type); + break; + } + + return -1; +} +#endif + +static int agent_talk(struct ssh_session_struct *session, + struct ssh_buffer_struct *request, struct ssh_buffer_struct *reply) { + uint32_t len = 0; + uint8_t payload[1024] = {0}; + + len = buffer_get_rest_len(request); + SSH_LOG(session, SSH_LOG_TRACE, "Request length: %u", len); + agent_put_u32(payload, len); + + /* send length and then the request packet */ + if (atomicio(session->agent->sock, payload, 4, 0) == 4) { + if (atomicio(session->agent->sock, buffer_get_rest(request), len, 0) + != len) { + SSH_LOG(session, SSH_LOG_WARN, "atomicio sending request failed: %s", + strerror(errno)); + return -1; + } + } else { + SSH_LOG(session, SSH_LOG_WARN, + "atomicio sending request length failed: %s", + strerror(errno)); + return -1; + } + + /* wait for response, read the length of the response packet */ + if (atomicio(session->agent->sock, payload, 4, 1) != 4) { + SSH_LOG(session, SSH_LOG_WARN, "atomicio read response length failed: %s", + strerror(errno)); + return -1; + } + + len = agent_get_u32(payload); + if (len > 256 * 1024) { + ssh_set_error(session, SSH_FATAL, + "Authentication response too long: %u", len); + return -1; + } + SSH_LOG(session, SSH_LOG_TRACE, "Response length: %u", len); + + while (len > 0) { + size_t n = len; + if (n > sizeof(payload)) { + n = sizeof(payload); + } + if (atomicio(session->agent->sock, payload, n, 1) != n) { + SSH_LOG(session, SSH_LOG_WARN, + "Error reading response from authentication socket."); + return -1; + } + if (buffer_add_data(reply, payload, n) < 0) { + SSH_LOG(session, SSH_LOG_WARN, "Not enough space"); + return -1; + } + len -= n; + } + + return 0; +} + +int ssh_agent_get_ident_count(struct ssh_session_struct *session) { + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + unsigned int type = 0; + unsigned int c1 = 0, c2 = 0; + uint8_t buf[4] = {0}; + + switch (session->version) { + case 1: + c1 = SSH_AGENTC_REQUEST_RSA_IDENTITIES; + c2 = SSH_AGENT_RSA_IDENTITIES_ANSWER; + break; + case 2: + c1 = SSH2_AGENTC_REQUEST_IDENTITIES; + c2 = SSH2_AGENT_IDENTITIES_ANSWER; + break; + default: + return 0; + } + + /* send message to the agent requesting the list of identities */ + request = ssh_buffer_new(); + if (buffer_add_u8(request, c1) < 0) { + ssh_set_error(session, SSH_FATAL, "Not enough space"); + return -1; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_buffer_free(request); + ssh_set_error(session, SSH_FATAL, "Not enough space"); + return -1; + } + + if (agent_talk(session, request, reply) < 0) { + ssh_buffer_free(request); + ssh_buffer_free(reply); + return 0; + } + ssh_buffer_free(request); + + /* get message type and verify the answer */ + buffer_get_u8(reply, (uint8_t *) &type); + SSH_LOG(session, SSH_LOG_WARN, + "Answer type: %d, expected answer: %d", + type, c2); + if (agent_failed(type)) { + return 0; + } else if (type != c2) { + ssh_set_error(session, SSH_FATAL, + "Bad authentication reply message type: %d", type); + return -1; + } + + buffer_get_u32(reply, (uint32_t *) buf); + session->agent->count = agent_get_u32(buf); + SSH_LOG(session, SSH_LOG_DEBUG, "Agent count: %d", + session->agent->count); + if (session->agent->count > 1024) { + ssh_set_error(session, SSH_FATAL, + "Too many identities in authentication reply: %d", + session->agent->count); + ssh_buffer_free(reply); + return -1; + } + + if (session->agent->ident) { + buffer_reinit(session->agent->ident); + } + session->agent->ident = reply; + + return session->agent->count; +} + +/* caller has to free commment */ +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment) { + if (ssh_agent_get_ident_count(session) > 0) { + return ssh_agent_get_next_ident(session, comment); + } + + return NULL; +} + +/* caller has to free commment */ +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment) { + struct ssh_key_struct *key; + struct ssh_string_struct *blob = NULL; + struct ssh_string_struct *tmp = NULL; + int rc; + + if (session->agent->count == 0) { + return NULL; + } + + switch(session->version) { + case 1: + return NULL; + case 2: + /* get the blob */ + blob = buffer_get_ssh_string(session->agent->ident); + if (blob == NULL) { + return NULL; + } + + /* get the comment */ + tmp = buffer_get_ssh_string(session->agent->ident); + if (tmp == NULL) { + ssh_string_free(blob); + + return NULL; + } + + if (comment) { + *comment = ssh_string_to_char(tmp); + } else { + ssh_string_free(blob); + ssh_string_free(tmp); + + return NULL; + } + ssh_string_free(tmp); + + /* get key from blob */ + rc = ssh_pki_import_pubkey_blob(blob, &key); + ssh_string_free(blob); + if (rc == SSH_ERROR) { + return NULL; + } + break; + default: + return NULL; + } + + return key; +} + +int agent_is_running(ssh_session session) { + if (session == NULL || session->agent == NULL) { + return 0; + } + + if (ssh_socket_is_open(session->agent->sock)) { + return 1; + } else { + if (agent_connect(session) < 0) { + return 0; + } else { + return 1; + } + } + + return 0; +} + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data) +{ + ssh_buffer request; + ssh_buffer reply; + ssh_string key_blob; + ssh_string sig_blob; + int type = SSH2_AGENT_FAILURE; + int flags = 0; + uint32_t dlen; + int rc; + + request = ssh_buffer_new(); + if (request == NULL) { + return NULL; + } + + /* create request */ + if (buffer_add_u8(request, SSH2_AGENTC_SIGN_REQUEST) < 0) { + ssh_buffer_free(request); + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &key_blob); + if (rc < 0) { + ssh_buffer_free(request); + return NULL; + } + + /* adds len + blob */ + rc = buffer_add_ssh_string(request, key_blob); + ssh_string_free(key_blob); + if (rc < 0) { + ssh_buffer_free(request); + return NULL; + } + + /* Add data */ + dlen = buffer_get_rest_len(data); + if (buffer_add_u32(request, htonl(dlen)) < 0) { + ssh_buffer_free(request); + return NULL; + } + if (buffer_add_data(request, buffer_get_rest(data), dlen) < 0) { + ssh_buffer_free(request); + return NULL; + } + + if (buffer_add_u32(request, htonl(flags)) < 0) { + ssh_buffer_free(request); + return NULL; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_buffer_free(request); + return NULL; + } + + /* send the request */ + if (agent_talk(session, request, reply) < 0) { + ssh_buffer_free(request); + ssh_buffer_free(reply); + return NULL; + } + ssh_buffer_free(request); + + /* check if reply is valid */ + if (buffer_get_u8(reply, (uint8_t *) &type) != sizeof(uint8_t)) { + ssh_buffer_free(reply); + return NULL; + } + + if (agent_failed(type)) { + SSH_LOG(session, SSH_LOG_WARN, "Agent reports failure in signing the key"); + ssh_buffer_free(reply); + return NULL; + } else if (type != SSH2_AGENT_SIGN_RESPONSE) { + ssh_set_error(session, SSH_FATAL, "Bad authentication response: %d", type); + ssh_buffer_free(reply); + return NULL; + } + + sig_blob = buffer_get_ssh_string(reply); + ssh_buffer_free(reply); + + return sig_blob; +} + +#endif /* _WIN32 */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/auth.c b/libssh/src/auth.c new file mode 100644 index 00000000..9d099a53 --- /dev/null +++ b/libssh/src/auth.c @@ -0,0 +1,2116 @@ +/* + * auth.c - Authentication with SSH protocols + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2011 by Aris Adamantiadis + * Copyright (c) 2008-2011 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. + */ + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/agent.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/keys.h" +#include "libssh/auth.h" +#include "libssh/pki.h" + +#include "libssh/legacy.h" + +/** + * @defgroup libssh_auth The SSH authentication functions. + * @ingroup libssh + * + * Functions to authenticate with a server. + * + * @{ + */ + +/** + * @internal + * + * @brief Ask access to the ssh-userauth service. + * + * @param[in] session The SSH session handle. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN on nonblocking mode, if calling that function + * again is necessary + */ +static int ssh_userauth_request_service(ssh_session session) { + int rc; + + rc = ssh_service_request(session, "ssh-userauth"); + if (rc != SSH_OK) { + SSH_LOG(session, SSH_LOG_WARN, + "Failed to request \"ssh-userauth\" service"); + } + + return rc; +} + +static int ssh_auth_response_termination(void *user){ + ssh_session session=(ssh_session)user; + switch(session->auth_state){ + case SSH_AUTH_STATE_NONE: + case SSH_AUTH_STATE_KBDINT_SENT: + return 0; + default: + return 1; + } +} + +/** + * @internal + * @brief Wait for a response of an authentication function. + * + * @param[in] session The SSH session. + * + * @returns SSH_AUTH_SUCCESS Authentication success, or pubkey accepted + * SSH_AUTH_PARTIAL Authentication succeeded but another mean + * of authentication is needed. + * SSH_AUTH_INFO Data for keyboard-interactive + * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again + * SSH_AUTH_ERROR Error during the process. + */ +static int ssh_userauth_get_response(ssh_session session) { + int rc = SSH_AUTH_ERROR; + + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_auth_response_termination, session); + if (rc == SSH_ERROR) { + leave_function(); + return SSH_AUTH_ERROR; + } + if (!ssh_auth_response_termination(session)){ + leave_function(); + return SSH_AUTH_AGAIN; + } + + switch(session->auth_state) { + case SSH_AUTH_STATE_ERROR: + rc = SSH_AUTH_ERROR; + break; + case SSH_AUTH_STATE_FAILED: + rc = SSH_AUTH_DENIED; + break; + case SSH_AUTH_STATE_INFO: + rc = SSH_AUTH_INFO; + break; + case SSH_AUTH_STATE_PARTIAL: + rc = SSH_AUTH_PARTIAL; + break; + case SSH_AUTH_STATE_PK_OK: + case SSH_AUTH_STATE_SUCCESS: + rc = SSH_AUTH_SUCCESS; + break; + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; + break; + } + + return rc; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_BANNER packet. + * + * This banner should be shown to user prior to authentication + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner){ + ssh_string banner; + (void)type; + (void)user; + enter_function(); + banner = buffer_get_ssh_string(packet); + if (banner == NULL) { + SSH_LOG(session, SSH_LOG_WARN, + "Invalid SSH_USERAUTH_BANNER packet"); + } else { + SSH_LOG(session, SSH_LOG_DEBUG, + "Received SSH_USERAUTH_BANNER packet"); + if(session->banner != NULL) + ssh_string_free(session->banner); + session->banner = banner; + } + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_FAILURE packet. + * + * This handles the complete or partial authentication failure. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure){ + char *auth_methods = NULL; + ssh_string auth; + uint8_t partial = 0; + (void) type; + (void) user; + enter_function(); + + auth = buffer_get_ssh_string(packet); + if (auth == NULL || buffer_get_u8(packet, &partial) != 1) { + ssh_set_error(session, SSH_FATAL, + "Invalid SSH_MSG_USERAUTH_FAILURE message"); + session->auth_state=SSH_AUTH_STATE_ERROR; + goto end; + } + + auth_methods = ssh_string_to_char(auth); + if (auth_methods == NULL) { + ssh_set_error_oom(session); + goto end; + } + + if (partial) { + session->auth_state=SSH_AUTH_STATE_PARTIAL; + SSH_LOG(session, SSH_LOG_INFO, + "Partial success. Authentication that can continue: %s", + auth_methods); + } else { + session->auth_state=SSH_AUTH_STATE_FAILED; + SSH_LOG(session, SSH_LOG_INFO, + "Access denied. Authentication that can continue: %s", + auth_methods); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Access denied. Authentication that can continue: %s", + auth_methods); + + session->auth_methods = 0; + } + if (strstr(auth_methods, "password") != NULL) { + session->auth_methods |= SSH_AUTH_METHOD_PASSWORD; + } + if (strstr(auth_methods, "keyboard-interactive") != NULL) { + session->auth_methods |= SSH_AUTH_METHOD_INTERACTIVE; + } + if (strstr(auth_methods, "publickey") != NULL) { + session->auth_methods |= SSH_AUTH_METHOD_PUBLICKEY; + } + if (strstr(auth_methods, "hostbased") != NULL) { + session->auth_methods |= SSH_AUTH_METHOD_HOSTBASED; + } + +end: + ssh_string_free(auth); + SAFE_FREE(auth_methods); + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_SUCCESS packet. + * + * It is also used to communicate the new to the upper levels. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_success){ + enter_function(); + (void)packet; + (void)type; + (void)user; + + SSH_LOG(session, SSH_LOG_DEBUG, "Authentication successful"); + SSH_LOG(session, SSH_LOG_TRACE, "Received SSH_USERAUTH_SUCCESS"); + + session->auth_state=SSH_AUTH_STATE_SUCCESS; + session->session_state=SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + if(session->current_crypto && session->current_crypto->delayed_compress_out){ + SSH_LOG(session, SSH_LOG_DEBUG, "Enabling delayed compression OUT"); + session->current_crypto->do_compress_out=1; + } + if(session->current_crypto && session->current_crypto->delayed_compress_in){ + SSH_LOG(session,SSH_LOG_DEBUG, "Enabling delayed compression IN"); + session->current_crypto->do_compress_in=1; + } + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_PK_OK or SSH_USERAUTH_INFO_REQUEST packet. + * + * Since the two types of packets share the same code, additional work is done + * to understand if we are in a public key or keyboard-interactive context. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok){ + int rc; + enter_function(); + + SSH_LOG(session, SSH_LOG_TRACE, "Received SSH_USERAUTH_PK_OK/INFO_REQUEST"); + + if(session->auth_state==SSH_AUTH_STATE_KBDINT_SENT){ + /* Assuming we are in keyboard-interactive context */ + SSH_LOG(session, SSH_LOG_TRACE, + "keyboard-interactive context, assuming SSH_USERAUTH_INFO_REQUEST"); + rc=ssh_packet_userauth_info_request(session,type,packet,user); + } else { + session->auth_state=SSH_AUTH_STATE_PK_OK; + SSH_LOG(session, SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK"); + rc=SSH_PACKET_USED; + } + leave_function(); + return rc; +} + +/** + * @brief Get available authentication methods from the server. + * + * This requires the function ssh_userauth_none() to be called before the + * methods are available. The server MAY return a list of methods that may + * continue. + * + * @param[in] session The SSH session. + * + * @param[in] username Deprecated, set to NULL. + * + * @returns A bitfield of the fllowing values: + * - SSH_AUTH_METHOD_PASSWORD + * - SSH_AUTH_METHOD_PUBLICKEY + * - SSH_AUTH_METHOD_HOSTBASED + * - SSH_AUTH_METHOD_INTERACTIVE + * + * @warning Other reserved flags may appear in future versions. + * @see ssh_userauth_none() + */ +int ssh_userauth_list(ssh_session session, const char *username) +{ + (void) username; /* unused */ + + if (session == NULL) { + return 0; + } + +#ifdef WITH_SSH1 + if(session->version == 1) { + return SSH_AUTH_METHOD_PASSWORD; + } +#endif + + return session->auth_methods; +} + +/** + * @brief Try to authenticate through the "none" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_optoins_set() only + * before you connect to the server. + */ +int ssh_userauth_none(ssh_session session, const char *username) { + ssh_string str; + int rc; + +#ifdef WITH_SSH1 + if (session->version == 1) { + return ssh_userauth1_none(session, username); + } +#endif + + switch(session->pending_call_state){ + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_NONE: + goto pending; + default: + ssh_set_error(session, SSH_FATAL, + "Wrong state during pending SSH call"); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + goto fail; + } + + /* username */ + if (username) { + str = ssh_string_from_char(username); + } else { + str = ssh_string_from_char(session->opts.username); + } + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* service */ + str = ssh_string_from_char("ssh-connection"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* method */ + str = ssh_string_from_char("none"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + session->auth_state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_NONE; + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @brief Try to authenticate with the given public key. + * + * To avoid unnecessary processing and user interaction, the following method + * is provided for querying whether authentication using the 'pubkey' would + * be possible. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] pubkey The public key to try. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_pubkey(). + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_optoins_set() only + * before you connect to the server. + */ +int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey) +{ + ssh_string str; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (pubkey == NULL || !ssh_key_is_public(pubkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid pubkey"); + return SSH_AUTH_ERROR; + } + +#ifdef WITH_SSH1 + if (session->version == 1) { + return SSH_AUTH_DENIED; + } +#endif + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state during pending SSH call"); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + goto fail; + } + + /* username */ + if (username) { + str = ssh_string_from_char(username); + } else { + str = ssh_string_from_char(session->opts.username); + } + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* service */ + str = ssh_string_from_char("ssh-connection"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* method */ + str = ssh_string_from_char("publickey"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* private key? */ + rc = buffer_add_u8(session->out_buffer, 0); + if (rc < 0) { + goto fail; + } + + /* algo */ + str = ssh_string_from_char(pubkey->type_c); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &str); + if (rc < 0) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + session->auth_state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @brief Authenticate with public/private key. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] privkey The private key for authentication. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_pubkey(). + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_optoins_set() only + * before you connect to the server. + */ +int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey) +{ + ssh_string str; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid private key"); + return SSH_AUTH_ERROR; + } + +#ifdef WITH_SSH1 + if (session->version == 1) { + return SSH_AUTH_DENIED; + } +#endif + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in ssh_userauth_try_pubkey"); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + goto fail; + } + + /* username */ + if (username) { + str = ssh_string_from_char(username); + } else { + str = ssh_string_from_char(session->opts.username); + } + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* service */ + str = ssh_string_from_char("ssh-connection"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* method */ + str = ssh_string_from_char("publickey"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* private key? */ + rc = buffer_add_u8(session->out_buffer, 1); + if (rc < 0) { + goto fail; + } + + /* algo */ + str = ssh_string_from_char(privkey->type_c); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(privkey, &str); + if (rc < 0) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* sign the buffer with the private key */ + str = ssh_pki_do_sign(session, session->out_buffer, privkey); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + session->auth_state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PUBKEY; + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +#ifndef _WIN32 +static int ssh_userauth_agent_publickey(ssh_session session, + const char *username, + ssh_key pubkey) +{ + ssh_string str; + int rc; + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in ssh_userauth_try_pubkey"); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + goto fail; + } + + /* username */ + if (username) { + str = ssh_string_from_char(username); + } else { + str = ssh_string_from_char(session->opts.username); + } + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* service */ + str = ssh_string_from_char("ssh-connection"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* method */ + str = ssh_string_from_char("publickey"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* private key? */ + rc = buffer_add_u8(session->out_buffer, 1); + if (rc < 0) { + goto fail; + } + + /* algo */ + str = ssh_string_from_char(pubkey->type_c); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &str); + if (rc < 0) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* sign the buffer with the private key */ + str = ssh_pki_do_sign_agent(session, session->out_buffer, pubkey); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + session->auth_state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_AGENT; + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +enum ssh_agent_state_e { + SSH_AGENT_STATE_NONE = 0, + SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_AUTH +}; + +struct ssh_agent_state_struct { + enum ssh_agent_state_e state; + ssh_key pubkey; + char *comment; +}; + + +/** + * @brief Try to do public key authentication with ssh agent. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_pubkey(). + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_optoins_set() only + * before you connect to the server. + */ +int ssh_userauth_agent(ssh_session session, + const char *username) { + int rc = SSH_AUTH_ERROR; + struct ssh_agent_state_struct *state; + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (!agent_is_running(session)) { + return SSH_AUTH_DENIED; + } + if (!session->agent_state){ + session->agent_state = malloc(sizeof(struct ssh_agent_state_struct)); + if (!session->agent_state){ + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + ZERO_STRUCTP(session->agent_state); + session->agent_state->state=SSH_AGENT_STATE_NONE; + } + state = session->agent_state; + if (state->pubkey == NULL) + state->pubkey = ssh_agent_get_first_ident(session, &state->comment); + while (state->pubkey != NULL) { + if(state->state == SSH_AGENT_STATE_NONE){ + SSH_LOG(session, SSH_LOG_DEBUG, + "Trying identity %s", state->comment); + } + if(state->state == SSH_AGENT_STATE_NONE || + state->state == SSH_AGENT_STATE_PUBKEY){ + rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_ERROR) { + ssh_string_free_char(state->comment); + ssh_key_free(state->pubkey); + SAFE_FREE(session->agent_state); + return rc; + } else if (rc == SSH_AUTH_AGAIN) { + state->state = SSH_AGENT_STATE_PUBKEY; + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(session, SSH_LOG_DEBUG, + "Public key of %s refused by server", state->comment); + ssh_string_free_char(state->comment); + ssh_key_free(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + + SSH_LOG(session, SSH_LOG_DEBUG, + "Public key of %s accepted by server", state->comment); + state->state = SSH_AGENT_STATE_AUTH; + } + if (state->state == SSH_AGENT_STATE_AUTH){ + rc = ssh_userauth_agent_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_AGAIN) + return rc; + ssh_string_free_char(state->comment); + ssh_key_free(state->pubkey); + if (rc == SSH_AUTH_ERROR) { + SAFE_FREE(session->agent_state); + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(session, SSH_LOG_INFO, + "Server accepted public key but refused the signature"); + state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + SAFE_FREE(session->agent_state); + return SSH_AUTH_SUCCESS; + } + } + + SAFE_FREE(session->agent_state); + return rc; +} +#endif + +enum ssh_auth_auto_state_e { + SSH_AUTH_AUTO_STATE_NONE=0, + SSH_AUTH_AUTO_STATE_PUBKEY, + SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED +}; + +struct ssh_auth_auto_state_struct { + enum ssh_auth_auto_state_e state; + struct ssh_iterator *it; + ssh_key privkey; + ssh_key pubkey; +}; + +/** + * @brief Tries to automatically authenticate with public key and "none" + * + * It may fail, for instance it doesn't ask for a password and uses a default + * asker for passphrases (in case the private key is encrypted). + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] passphrase Use this passphrase to unlock the privatekey. Use NULL + * if you don't want to use a passphrase or the user + * should be asked. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_pubkey(). + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_optoins_set() only + * before you connect to the server. + */ +int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase) +{ + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + struct ssh_auth_auto_state_struct *state; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + if (!session->auth_auto_state){ + session->auth_auto_state = + malloc(sizeof(struct ssh_auth_auto_state_struct)); + if (!session->auth_auto_state){ + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + ZERO_STRUCTP(session->auth_auto_state); + } + state = session->auth_auto_state; + if (state->state == SSH_AUTH_AUTO_STATE_NONE) { +#ifndef _WIN32 + /* Try authentication with ssh-agent first */ + rc = ssh_userauth_agent(session, username); + if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_SUCCESS) { + return rc; + } + if (rc == SSH_AUTH_AGAIN) + return rc; +#endif + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + } + if (state->it == NULL) { + state->it = ssh_list_get_iterator(session->opts.identity); + } + + while (state->it != NULL){ + const char *privkey_file = state->it->data; + char pubkey_file[1024] = {0}; + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY){ + SSH_LOG(session, SSH_LOG_DEBUG, + "Trying to authenticate with %s", privkey_file); + state->privkey = NULL; + state->pubkey = NULL; + snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + + rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); + SAFE_FREE(session->auth_auto_state); + return SSH_AUTH_ERROR; + } else if (rc == SSH_EOF) { + /* Read the private key and save the public key to file */ + rc = ssh_pki_import_privkey_file(privkey_file, + passphrase, + auth_fn, + auth_data, + &state->privkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it=state->it->next; + continue; + } else if (rc == SSH_EOF) { + /* If the file doesn't exist, continue */ + SSH_LOG(session, SSH_LOG_DEBUG, + "Private key %s doesn't exist.", + privkey_file); + state->it=state->it->next; + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); + if (rc == SSH_ERROR) { + ssh_key_free(state->privkey); + SAFE_FREE(session->auth_auto_state); + return SSH_AUTH_ERROR; + } + + rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file); + if (rc == SSH_ERROR) { + SSH_LOG(session, + SSH_LOG_WARN, + "Could not write public key to file: %s", + pubkey_file); + } + } + state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED){ + rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_ERROR) { + SSH_LOG(session, + SSH_LOG_WARN, + "Public key authentication error for %s", + privkey_file); + ssh_key_free(state->privkey); + ssh_key_free(state->pubkey); + SAFE_FREE(session->auth_auto_state); + return rc; + } else if (rc == SSH_AUTH_AGAIN){ + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(session, + SSH_LOG_DEBUG, + "Public key for %s refused by server", + privkey_file); + ssh_key_free(state->privkey); + state->privkey = NULL; + ssh_key_free(state->pubkey); + state->pubkey = NULL; + state->it=state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + state->state = SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED){ + /* Public key has been accepted by the server */ + if (state->privkey == NULL) { + rc = ssh_pki_import_privkey_file(privkey_file, + passphrase, + auth_fn, + auth_data, + &state->privkey); + if (rc == SSH_ERROR) { + ssh_key_free(state->pubkey); + state->pubkey=NULL; + ssh_set_error(session, + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it=state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } else if (rc == SSH_EOF) { + /* If the file doesn't exist, continue */ + ssh_key_free(state->pubkey); + state->pubkey=NULL; + SSH_LOG(session, + SSH_LOG_INFO, + "Private key %s doesn't exist.", + privkey_file); + state->it=state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } + + rc = ssh_userauth_publickey(session, username, state->privkey); + if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { + ssh_key_free(state->privkey); + ssh_key_free(state->pubkey); + SAFE_FREE(session->auth_auto_state); + } + if (rc == SSH_AUTH_ERROR) { + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + SSH_LOG(session, + SSH_LOG_INFO, + "Successfully authenticated using %s", + privkey_file); + return rc; + } else if (rc == SSH_AUTH_AGAIN){ + return rc; + } + + SSH_LOG(session, + SSH_LOG_WARN, + "The server accepted the public key but refused the signature"); + state->it=state->it->next; + state->state=SSH_AUTH_AUTO_STATE_PUBKEY; + /* continue */ + } + } + SSH_LOG(session, + SSH_LOG_INFO, + "Tried every public key, none matched"); + SAFE_FREE(session->auth_auto_state); + return SSH_AUTH_DENIED; +} + +/** + * @brief Try to authenticate by password. + * + * This authentication method is normally disabled on SSHv2 server. You should + * use keyboard-interactive mode. + * + * The 'password' value MUST be encoded UTF-8. It is up to the server how to + * interpret the password and validate it against the password database. + * However, if you read the password in some other encoding, you MUST convert + * the password to UTF-8. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] password The password to authenticate in UTF-8. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_optoins_set() only + * before you connect to the server. + * + * @see ssh_userauth_none() + * @see ssh_userauth_kbdint() + */ +int ssh_userauth_password(ssh_session session, + const char *username, + const char *password) { + ssh_string str; + int rc; + +#ifdef WITH_SSH1 + if (session->version == 1) { + rc = ssh_userauth1_password(session, username, password); + return rc; + } +#endif + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state during pending SSH call"); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + goto fail; + } + + /* username */ + if (username) { + str = ssh_string_from_char(username); + } else { + str = ssh_string_from_char(session->opts.username); + } + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* service */ + str = ssh_string_from_char("ssh-connection"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* method */ + str = ssh_string_from_char("password"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* FALSE */ + rc = buffer_add_u8(session->out_buffer, 0); + if (rc < 0) { + goto fail; + } + + /* password */ + str = ssh_string_from_char(password); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + session->auth_state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +#ifndef _WIN32 +/* LEGACY */ +int ssh_userauth_agent_pubkey(ssh_session session, + const char *username, + ssh_public_key publickey) +{ + ssh_key key; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + key->type = publickey->type; + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PUBLIC; + key->dsa = publickey->dsa_pub; + key->rsa = publickey->rsa_pub; + + rc = ssh_userauth_agent_publickey(session, username, key); + + key->dsa = NULL; + key->rsa = NULL; + ssh_key_free(key); + + return rc; +} +#endif /* _WIN32 */ + +ssh_kbdint ssh_kbdint_new(void) { + ssh_kbdint kbd; + + kbd = malloc(sizeof(struct ssh_kbdint_struct)); + if (kbd == NULL) { + return NULL; + } + ZERO_STRUCTP(kbd); + + return kbd; +} + + +void ssh_kbdint_free(ssh_kbdint kbd) { + int i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + BURN_STRING(kbd->prompts[i]); + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + if (kbd->answers) { + for (i = 0; i < n; i++) { + BURN_STRING(kbd->answers[i]); + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + SAFE_FREE(kbd); +} + +void ssh_kbdint_clean(ssh_kbdint kbd) { + int i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + BURN_STRING(kbd->prompts[i]); + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + + if (kbd->answers) { + for (i = 0; i < n; i++) { + BURN_STRING(kbd->answers[i]); + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + kbd->nprompts = 0; + kbd->nanswers = 0; +} + +/* + * This function sends the first packet as explained in RFC 3066 section 3.1. + */ +static int ssh_userauth_kbdint_init(ssh_session session, + const char *username, + const char *submethods) +{ + ssh_string str; + int rc; + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) + goto pending; + if (session->pending_call_state != SSH_PENDING_CALL_NONE){ + ssh_set_error_invalid(session); + return SSH_ERROR; + } + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) + return SSH_AUTH_AGAIN; + if (rc != SSH_OK) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + goto fail; + } + + /* username */ + if (username) { + str = ssh_string_from_char(username); + } else { + str = ssh_string_from_char(session->opts.username); + } + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* service */ + str = ssh_string_from_char("ssh-connection"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* method */ + str = ssh_string_from_char("keyboard-interactive"); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* lang string (ignore it) */ + str = ssh_string_from_char(""); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + /* submethods */ + if (submethods == NULL) { + submethods = ""; + } + + str = ssh_string_from_char(submethods); + if (str == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto fail; + } + + session->auth_state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_INIT; + + SSH_LOG(session, SSH_LOG_DEBUG, + "Sending keyboard-interactive init request"); + + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * + * @brief Send the current challenge response and wait for a reply from the + * server. + * + * @returns SSH_AUTH_INFO if more info is needed + * @returns SSH_AUTH_SUCCESS + * @returns SSH_AUTH_FAILURE + * @returns SSH_AUTH_PARTIAL + */ +static int ssh_userauth_kbdint_send(ssh_session session) +{ + ssh_string answer; + uint32_t i; + int rc; + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND) + goto pending; + if (session->pending_call_state != SSH_PENDING_CALL_NONE){ + ssh_set_error_invalid(session); + return SSH_ERROR; + } + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_INFO_RESPONSE); + if (rc < 0) { + goto fail; + } + + rc = buffer_add_u32(session->out_buffer, htonl(session->kbdint->nprompts)); + if (rc < 0) { + goto fail; + } + + for (i = 0; i < session->kbdint->nprompts; i++) { + if (session->kbdint->answers && session->kbdint->answers[i]) { + answer = ssh_string_from_char(session->kbdint->answers[i]); + } else { + answer = ssh_string_from_char(""); + } + if (answer == NULL) { + goto fail; + } + + rc = buffer_add_ssh_string(session->out_buffer, answer); + string_burn(answer); + string_free(answer); + if (rc < 0) { + goto fail; + } + } + + session->auth_state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_SEND; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + SSH_LOG(session, SSH_LOG_DEBUG, + "Sending keyboard-interactive response packet"); + + rc = packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * @brief handles a SSH_USERAUTH_INFO_REQUEST packet, as used in + * keyboard-interactive authentication, and changes the + * authentication state. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { + ssh_string name; /* name of the "asking" window showed to client */ + ssh_string instruction; + ssh_string tmp; + uint32_t nprompts; + uint32_t i; + (void)user; + (void)type; + enter_function(); + + name = buffer_get_ssh_string(packet); + instruction = buffer_get_ssh_string(packet); + tmp = buffer_get_ssh_string(packet); + buffer_get_u32(packet, &nprompts); + + /* We don't care about tmp */ + ssh_string_free(tmp); + + if (name == NULL || instruction == NULL) { + ssh_string_free(name); + ssh_string_free(instruction); + ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg"); + leave_function(); + return SSH_PACKET_USED; + } + + if (session->kbdint == NULL) { + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + ssh_string_free(name); + ssh_string_free(instruction); + + leave_function(); + return SSH_PACKET_USED; + } + } else { + ssh_kbdint_clean(session->kbdint); + } + + session->kbdint->name = ssh_string_to_char(name); + ssh_string_free(name); + if (session->kbdint->name == NULL) { + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + ssh_string_free(instruction); + leave_function(); + return SSH_PACKET_USED; + } + + session->kbdint->instruction = ssh_string_to_char(instruction); + ssh_string_free(instruction); + if (session->kbdint->instruction == NULL) { + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + + nprompts = ntohl(nprompts); + SSH_LOG(session, SSH_LOG_DEBUG, + "%d keyboard-interactive prompts", nprompts); + if (nprompts > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much prompts requested by the server: %u (0x%.4x)", + nprompts, nprompts); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + + session->kbdint->nprompts = nprompts; + session->kbdint->nanswers = nprompts; + session->kbdint->prompts = malloc(nprompts * sizeof(char *)); + if (session->kbdint->prompts == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + memset(session->kbdint->prompts, 0, nprompts * sizeof(char *)); + + session->kbdint->echo = malloc(nprompts); + if (session->kbdint->echo == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + memset(session->kbdint->echo, 0, nprompts); + + for (i = 0; i < nprompts; i++) { + tmp = buffer_get_ssh_string(packet); + buffer_get_u8(packet, &session->kbdint->echo[i]); + if (tmp == NULL) { + ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + session->kbdint->prompts[i] = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (session->kbdint->prompts[i] == NULL) { + ssh_set_error_oom(session); + session->kbdint->nprompts = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + leave_function(); + return SSH_PACKET_USED; + } + } + session->auth_state=SSH_AUTH_STATE_INFO; + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @brief Try to authenticate through the "keyboard-interactive" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] user The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot try + * two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_INFO: The server asked some questions. Use + * ssh_userauth_kbdint_getnprompts() and such.\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @see ssh_userauth_kbdint_getnprompts() + * @see ssh_userauth_kbdint_getname() + * @see ssh_userauth_kbdint_getinstruction() + * @see ssh_userauth_kbdint_getprompt() + * @see ssh_userauth_kbdint_setanswer() + */ +int ssh_userauth_kbdint(ssh_session session, const char *user, + const char *submethods) { + int rc = SSH_AUTH_ERROR; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + +#ifdef WITH_SSH1 + if (session->version == 1) { + return SSH_AUTH_DENIED; + } +#endif + if ((session->pending_call_state == SSH_PENDING_CALL_NONE && session->kbdint == NULL) || + session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) + rc = ssh_userauth_kbdint_init(session, user, submethods); + else if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND || + session->kbdint != NULL) { + /* + * If we are at this point, it is because session->kbdint exists. + * It means the user has set some information there we need to send + * the server and then we need to ack the status (new questions or ok + * pass in). + * It is possible that session->kbdint is NULL while we're waiting for + * a reply, hence the test for the pending call. + */ + rc = ssh_userauth_kbdint_send(session); + } else { + /* We are here because session->kbdint == NULL & state != NONE. + * This should not happen + */ + rc = SSH_AUTH_ERROR; + ssh_set_error(session,SSH_FATAL,"Invalid state in %s", __FUNCTION__); + } + return rc; +} + +/** + * @brief Get the number of prompts (questions) the server has given. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The number of prompts. + */ +int ssh_userauth_kbdint_getnprompts(ssh_session session) { + if(session==NULL) + return SSH_ERROR; + if(session->kbdint == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + return session->kbdint->nprompts; +} + +/** + * @brief Get the "name" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The name of the message block. Do not free it. + */ +const char *ssh_userauth_kbdint_getname(ssh_session session) { + if(session==NULL) + return NULL; + if(session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->name; +} + +/** + * @brief Get the "instruction" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The instruction of the message block. + */ + +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { + if(session==NULL) + return NULL; + if(session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->instruction; +} + +/** + * @brief Get a prompt from a message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @param[in] i The index number of the i'th prompt. + * + * @param[out] echo This is an optional variable. You can obtain a + * boolean if the user input should be echoed or + * hidden. For passwords it is usually hidden. + * + * @returns A pointer to the prompt. Do not free it. + * + * @code + * const char prompt; + * char echo; + * + * prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + * if (echo) ... + * @endcode + */ +const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, + char *echo) { + if(session==NULL) + return NULL; + if(session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + if (i > session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return NULL; + } + + if (echo) { + *echo = session->kbdint->echo[i]; + } + + return session->kbdint->prompts[i]; +} + +#ifdef WITH_SERVER +/** + * @brief Get the number of answers the client has given. + * + * @param[in] session The ssh session to use. + * + * @returns The number of answers. + */ +int ssh_userauth_kbdint_getnanswers(ssh_session session) { + if(session==NULL || session->kbdint == NULL) + return SSH_ERROR; + return session->kbdint->nanswers; +} + +/** + * @brief Get the answer for a question from a message block. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith answer. + * + * @return 0 on success, < 0 on error. + */ +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { + if(session==NULL || session->kbdint == NULL + || session->kbdint->answers == NULL) { + return NULL; + } + if (i >= session->kbdint->nanswers) { + return NULL; + } + + return session->kbdint->answers[i]; +} +#endif + +/** + * @brief Set the answer for a question from a message block. + * + * If you have called ssh_userauth_kbdint() and got SSH_AUTH_INFO, this + * function returns the questions from the server. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith prompt. + * + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @return 0 on success, < 0 on error. + */ +int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) { + if (session == NULL) + return -1; + if (answer == NULL || session->kbdint == NULL || + i >= session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->kbdint->answers == NULL) { + session->kbdint->answers = malloc(sizeof(char*) * session->kbdint->nprompts); + if (session->kbdint->answers == NULL) { + ssh_set_error_oom(session); + return -1; + } + memset(session->kbdint->answers, 0, sizeof(char *) * session->kbdint->nprompts); + } + + if (session->kbdint->answers[i]) { + BURN_STRING(session->kbdint->answers[i]); + SAFE_FREE(session->kbdint->answers[i]); + } + + session->kbdint->answers[i] = strdup(answer); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + return -1; + } + + return 0; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/auth1.c b/libssh/src/auth1.c new file mode 100644 index 00000000..0f3f096d --- /dev/null +++ b/libssh/src/auth1.c @@ -0,0 +1,235 @@ +/* + * auth1.c - authentication with SSH-1 protocol + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/priv.h" +#include "libssh/ssh1.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/string.h" + +#ifdef WITH_SSH1 + +static int ssh_auth_status_termination(void *s){ + ssh_session session=s; + if(session->auth_state != SSH_AUTH_STATE_NONE || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + return 0; +} + +static int wait_auth1_status(ssh_session session) { + enter_function(); + /* wait for a packet */ + if (ssh_handle_packets_termination(session,SSH_TIMEOUT_USER, + ssh_auth_status_termination, session) != SSH_OK){ + leave_function(); + return SSH_AUTH_ERROR; + } + ssh_log(session,SSH_LOG_PROTOCOL,"Auth state : %d",session->auth_state); + leave_function(); + switch(session->auth_state) { + case SSH_AUTH_STATE_SUCCESS: + return SSH_AUTH_SUCCESS; + case SSH_AUTH_STATE_FAILED: + return SSH_AUTH_DENIED; + default: + return SSH_AUTH_AGAIN; + } + return SSH_AUTH_ERROR; +} + +void ssh_auth1_handler(ssh_session session, uint8_t type){ + if(session->session_state != SSH_SESSION_STATE_AUTHENTICATING){ + ssh_set_error(session,SSH_FATAL,"SSH_SMSG_SUCCESS or FAILED received in wrong state"); + return; + } + if(type==SSH_SMSG_SUCCESS){ + session->auth_state=SSH_AUTH_STATE_SUCCESS; + session->session_state=SSH_SESSION_STATE_AUTHENTICATED; + } else if(type==SSH_SMSG_FAILURE) + session->auth_state=SSH_AUTH_STATE_FAILED; +} + +static int send_username(ssh_session session, const char *username) { + ssh_string user = NULL; + int rc; + /* returns SSH_AUTH_SUCCESS or SSH_AUTH_DENIED */ + if(session->auth_service_state == SSH_AUTH_SERVICE_USER_SENT) { + if(session->auth_state == SSH_AUTH_STATE_FAILED) + return SSH_AUTH_DENIED; + if(session->auth_state == SSH_AUTH_STATE_SUCCESS) + return SSH_AUTH_SUCCESS; + return SSH_AUTH_ERROR; + } + if (session->auth_service_state == SSH_AUTH_SERVICE_SENT) + goto pending; + if (!username) { + if(!(username = session->opts.username)) { + if (ssh_options_set(session, SSH_OPTIONS_USER, NULL) < 0) { + session->auth_service_state = SSH_AUTH_SERVICE_DENIED; + return SSH_ERROR; + } else { + username = session->opts.username; + } + } + } + user = ssh_string_from_char(username); + if (user == NULL) { + return SSH_AUTH_ERROR; + } + + if (buffer_add_u8(session->out_buffer, SSH_CMSG_USER) < 0) { + ssh_string_free(user); + return SSH_AUTH_ERROR; + } + if (buffer_add_ssh_string(session->out_buffer, user) < 0) { + ssh_string_free(user); + return SSH_AUTH_ERROR; + } + ssh_string_free(user); + session->auth_state=SSH_AUTH_STATE_NONE; + session->auth_service_state = SSH_AUTH_SERVICE_SENT; + if (packet_send(session) == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = wait_auth1_status(session); + switch (rc){ + case SSH_AUTH_SUCCESS: + session->auth_service_state=SSH_AUTH_SERVICE_USER_SENT; + session->auth_state=SSH_AUTH_STATE_SUCCESS; + ssh_set_error(session, SSH_NO_ERROR, "Authentication successful"); + return SSH_AUTH_SUCCESS; + case SSH_AUTH_DENIED: + session->auth_service_state=SSH_AUTH_SERVICE_USER_SENT; + ssh_set_error(session,SSH_REQUEST_DENIED,"Password authentication necessary for user %s",username); + return SSH_AUTH_DENIED; + case SSH_AUTH_AGAIN: + return SSH_AUTH_AGAIN; + default: + session->auth_service_state = SSH_AUTH_SERVICE_NONE; + session->auth_state=SSH_AUTH_STATE_ERROR; + return SSH_AUTH_ERROR; + } +} + +/* use the "none" authentication question */ +int ssh_userauth1_none(ssh_session session, const char *username){ + return send_username(session, username); +} + +/** \internal + * \todo implement ssh1 public key + */ +int ssh_userauth1_offer_pubkey(ssh_session session, const char *username, + int type, ssh_string pubkey) { + (void) session; + (void) username; + (void) type; + (void) pubkey; + enter_function(); + leave_function(); + return SSH_AUTH_DENIED; +} + +int ssh_userauth1_password(ssh_session session, const char *username, + const char *password) { + ssh_string pwd = NULL; + int rc; + enter_function(); + rc = send_username(session, username); + if (rc != SSH_AUTH_DENIED) { + leave_function(); + return rc; + } + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_PASSWORD) + goto pending; + /* we trick a bit here. A known flaw in SSH1 protocol is that it's + * easy to guess password sizes. + * not that sure ... + */ + + /* XXX fix me here ! */ + /* cisco IOS doesn't like when a password is followed by zeroes and random pad. */ + if(1 || strlen(password) >= 128) { + /* not risky to disclose the size of such a big password .. */ + pwd = ssh_string_from_char(password); + if (pwd == NULL) { + leave_function(); + return SSH_AUTH_ERROR; + } + } else { + char buf[128] = {0}; + /* fill the password string from random things. the strcpy + * ensure there is at least a nul byte after the password. + * most implementation won't see the garbage at end. + * why garbage ? because nul bytes will be compressed by + * gzip and disclose password len. + */ + pwd = ssh_string_new(sizeof(buf)); + if (pwd == NULL) { + leave_function(); + return SSH_AUTH_ERROR; + } + ssh_get_random(buf, sizeof(buf), 0); + strcpy(buf, password); + ssh_string_fill(pwd, buf, sizeof(buf)); + } + + if (buffer_add_u8(session->out_buffer, SSH_CMSG_AUTH_PASSWORD) < 0) { + ssh_string_burn(pwd); + ssh_string_free(pwd); + leave_function(); + return SSH_AUTH_ERROR; + } + if (buffer_add_ssh_string(session->out_buffer, pwd) < 0) { + ssh_string_burn(pwd); + ssh_string_free(pwd); + leave_function(); + return SSH_AUTH_ERROR; + } + + ssh_string_burn(pwd); + ssh_string_free(pwd); + session->auth_state=SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PASSWORD; + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return SSH_AUTH_ERROR; + } +pending: + rc = wait_auth1_status(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + leave_function(); + return rc; +} + +#endif /* WITH_SSH1 */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/base64.c b/libssh/src/base64.c new file mode 100644 index 00000000..ff7671c1 --- /dev/null +++ b/libssh/src/base64.c @@ -0,0 +1,287 @@ +/* + * base64.c - support for base64 alphabet system, described in RFC1521 + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* just the dirtiest part of code i ever made */ +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" + +static char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* Transformations */ +#define SET_A(n, i) do { (n) |= ((i) & 63) <<18; } while (0) +#define SET_B(n, i) do { (n) |= ((i) & 63) <<12; } while (0) +#define SET_C(n, i) do { (n) |= ((i) & 63) << 6; } while (0) +#define SET_D(n, i) do { (n) |= ((i) & 63); } while (0) + +#define GET_A(n) (unsigned char) (((n) & 0xff0000) >> 16) +#define GET_B(n) (unsigned char) (((n) & 0xff00) >> 8) +#define GET_C(n) (unsigned char) ((n) & 0xff) + +static int _base64_to_bin(unsigned char dest[3], const char *source, int num); +static int get_equals(char *string); + +/* First part: base64 to binary */ + +/** + * @internal + * + * @brief Translates a base64 string into a binary one. + * + * @returns A buffer containing the decoded string, NULL if something went + * wrong (e.g. incorrect char). + */ +ssh_buffer base64_to_bin(const char *source) { + ssh_buffer buffer = NULL; + unsigned char block[3]; + char *base64; + char *ptr; + size_t len; + int equals; + + base64 = strdup(source); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + /* Get the number of equals signs, which mirrors the padding */ + equals = get_equals(ptr); + if (equals > 2) { + SAFE_FREE(base64); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SAFE_FREE(base64); + return NULL; + } + + len = strlen(ptr); + while (len > 4) { + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + len -= 4; + ptr += 4; + } + + /* + * Depending on the number of bytes resting, there are 3 possibilities + * from the RFC. + */ + switch (len) { + /* + * (1) The final quantum of encoding input is an integral multiple of + * 24 bits. Here, the final unit of encoded output will be an integral + * multiple of 4 characters with no "=" padding + */ + case 4: + if (equals != 0) { + goto error; + } + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * (2) The final quantum of encoding input is exactly 8 bits; here, the + * final unit of encoded output will be two characters followed by + * two "=" padding characters. + */ + case 2: + if (equals != 2){ + goto error; + } + + if (_base64_to_bin(block, ptr, 1) < 0) { + goto error; + } + if (buffer_add_data(buffer, block, 1) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * The final quantum of encoding input is exactly 16 bits. Here, the final + * unit of encoded output will be three characters followed by one "=" + * padding character. + */ + case 3: + if (equals != 1) { + goto error; + } + if (_base64_to_bin(block, ptr, 2) < 0) { + goto error; + } + if (buffer_add_data(buffer,block,2) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + default: + /* 4,3,2 are the only padding size allowed */ + goto error; + } + +error: + SAFE_FREE(base64); + ssh_buffer_free(buffer); + return NULL; +} + +#define BLOCK(letter, n) do {ptr = strchr(alphabet, source[n]); \ + if(!ptr) return -1; \ + i = ptr - alphabet; \ + SET_##letter(*block, i); \ + } while(0) + +/* Returns 0 if ok, -1 if not (ie invalid char into the stuff) */ +static int to_block4(unsigned long *block, const char *source, int num) { + char *ptr; + unsigned int i; + + *block = 0; + if (num < 1) { + return 0; + } + + BLOCK(A, 0); /* 6 bit */ + BLOCK(B,1); /* 12 bit */ + + if (num < 2) { + return 0; + } + + BLOCK(C, 2); /* 18 bit */ + + if (num < 3) { + return 0; + } + + BLOCK(D, 3); /* 24 bit */ + + return 0; +} + +/* num = numbers of final bytes to be decoded */ +static int _base64_to_bin(unsigned char dest[3], const char *source, int num) { + unsigned long block; + + if (to_block4(&block, source, num) < 0) { + return -1; + } + dest[0] = GET_A(block); + dest[1] = GET_B(block); + dest[2] = GET_C(block); + + return 0; +} + +/* Count the number of "=" signs and replace them by zeroes */ +static int get_equals(char *string) { + char *ptr = string; + int num = 0; + + while ((ptr=strchr(ptr,'=')) != NULL) { + num++; + *ptr = '\0'; + ptr++; + } + + return num; +} + +/* thanks sysk for debugging my mess :) */ +#define BITS(n) ((1 << (n)) - 1) +static void _bin_to_base64(unsigned char *dest, const unsigned char source[3], + int len) { + switch (len) { + case 1: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[((source[0] & BITS(2)) << 4)]; + dest[2] = '='; + dest[3] = '='; + break; + case 2: + dest[0] = alphabet[source[0] >> 2]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[1] & BITS(4)) << 2]; + dest[3] = '='; + break; + case 3: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[ (source[2] >> 6) | (source[1] & BITS(4)) << 2]; + dest[3] = alphabet[source[2] & BITS(6)]; + break; + } +} + +/** + * @internal + * + * @brief Converts binary data to a base64 string. + * + * @returns the converted string + */ +unsigned char *bin_to_base64(const unsigned char *source, int len) { + unsigned char *base64; + unsigned char *ptr; + int flen = len + (3 - (len % 3)); /* round to upper 3 multiple */ + flen = (4 * flen) / 3 + 1; + + base64 = malloc(flen); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + while(len > 0){ + _bin_to_base64(ptr, source, len > 3 ? 3 : len); + ptr += 4; + source += 3; + len -= 3; + } + ptr[0] = '\0'; + + return base64; +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/bind.c b/libssh/src/bind.c new file mode 100644 index 00000000..add5a702 --- /dev/null +++ b/libssh/src/bind.c @@ -0,0 +1,466 @@ +/* + * bind.c : all ssh_bind functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/bind.h" +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/pki.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/session.h" + +/** + * @addtogroup libssh_server + * + * @{ + */ + + +#ifdef _WIN32 +#include +#include +#include + +/* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +#ifdef HAVE_WSPIAPI_H +# include +#endif + +#define SOCKOPT_TYPE_ARG4 char + +#else /* _WIN32 */ + +#include +#include +#include +#define SOCKOPT_TYPE_ARG4 int + +#endif /* _WIN32 */ + +static socket_t bind_socket(ssh_bind sshbind, const char *hostname, + int port) { + char port_c[6]; + struct addrinfo *ai; + struct addrinfo hints; + int opt = 1; + socket_t s; + int rc; + + ZERO_STRUCT(hints); + + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + snprintf(port_c, 6, "%d", port); + rc = getaddrinfo(hostname, port_c, &hints, &ai); + if (rc != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Resolving %s: %s", hostname, gai_strerror(rc)); + return -1; + } + + s = socket (ai->ai_family, + ai->ai_socktype, + ai->ai_protocol); + if (s == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, "%s", strerror(errno)); + freeaddrinfo (ai); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&opt, sizeof(opt)) < 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Setting socket options failed: %s", + strerror(errno)); + freeaddrinfo (ai); + close(s); + return -1; + } + + if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Binding to %s:%d: %s", + hostname, + port, + strerror(errno)); + freeaddrinfo (ai); + close(s); + return -1; + } + + freeaddrinfo (ai); + return s; +} + +ssh_bind ssh_bind_new(void) { + ssh_bind ptr; + + ptr = malloc(sizeof(struct ssh_bind_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + ptr->bindfd = SSH_INVALID_SOCKET; + ptr->bindport= 22; + ptr->common.log_verbosity = 0; + + return ptr; +} + +int ssh_bind_listen(ssh_bind sshbind) { + const char *host; + socket_t fd; + int rc; + + if (ssh_init() < 0) { + ssh_set_error(sshbind, SSH_FATAL, "ssh_init() failed"); + return -1; + } + + if (sshbind->ecdsakey == NULL && + sshbind->dsakey == NULL && + sshbind->rsakey == NULL) { + ssh_set_error(sshbind, SSH_FATAL, + "DSA or RSA host key file must be set before listen()"); + return SSH_ERROR; + } + +#ifdef HAVE_ECC + if (sshbind->ecdsakey) { + rc = ssh_pki_import_privkey_file(sshbind->ecdsakey, + NULL, + NULL, + NULL, + &sshbind->ecdsa); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ECDSA host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->ecdsa) != SSH_KEYTYPE_ECDSA) { + ssh_set_error(sshbind, SSH_FATAL, + "The ECDSA host key has the wrong type"); + ssh_key_free(sshbind->ecdsa); + return SSH_ERROR; + } + } +#endif + + if (sshbind->dsakey) { + rc = ssh_pki_import_privkey_file(sshbind->dsakey, + NULL, + NULL, + NULL, + &sshbind->dsa); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private DSA host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->dsa) != SSH_KEYTYPE_DSS) { + ssh_set_error(sshbind, SSH_FATAL, + "The DSA host key has the wrong type: %d", + ssh_key_type(sshbind->dsa)); + ssh_key_free(sshbind->dsa); + return SSH_ERROR; + } + } + + if (sshbind->rsakey) { + rc = ssh_pki_import_privkey_file(sshbind->rsakey, + NULL, + NULL, + NULL, + &sshbind->rsa); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private RSA host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->rsa) != SSH_KEYTYPE_RSA && + ssh_key_type(sshbind->rsa) != SSH_KEYTYPE_RSA1) { + ssh_set_error(sshbind, SSH_FATAL, + "The RSA host key has the wrong type"); + ssh_key_free(sshbind->rsa); + return SSH_ERROR; + } + } + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + host = sshbind->bindaddr; + if (host == NULL) { + host = "0.0.0.0"; + } + + fd = bind_socket(sshbind, host, sshbind->bindport); + if (fd == SSH_INVALID_SOCKET) { + ssh_key_free(sshbind->dsa); + ssh_key_free(sshbind->rsa); + return -1; + } + sshbind->bindfd = fd; + + if (listen(fd, 10) < 0) { + ssh_set_error(sshbind, SSH_FATAL, + "Listening to socket %d: %s", + fd, strerror(errno)); + close(fd); + ssh_key_free(sshbind->dsa); + ssh_key_free(sshbind->rsa); + return -1; + } + } else { + SSH_LOG(sshbind, SSH_LOG_INFO, "Using app-provided bind socket"); + } + return 0; +} + +int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata){ + if (sshbind == NULL) { + return SSH_ERROR; + } + if (callbacks == NULL) { + ssh_set_error_invalid(sshbind); + return SSH_ERROR; + } + if(callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)){ + ssh_set_error(sshbind,SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + } + sshbind->bind_callbacks = callbacks; + sshbind->bind_callbacks_userdata=userdata; + return 0; +} + +/** @internal + * @brief callback being called by poll when an event happens + * + */ +static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, + socket_t fd, int revents, void *user){ + ssh_bind sshbind=(ssh_bind)user; + (void)sshpoll; + (void)fd; + + if(revents & POLLIN){ + /* new incoming connection */ + if(ssh_callbacks_exists(sshbind->bind_callbacks,incoming_connection)){ + sshbind->bind_callbacks->incoming_connection(sshbind, + sshbind->bind_callbacks_userdata); + } + } + return 0; +} + +/** @internal + * @brief returns the current poll handle, or create it + * @param sshbind the ssh_bind object + * @returns a ssh_poll handle suitable for operation + */ +ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind){ + if(sshbind->poll) + return sshbind->poll; + sshbind->poll=ssh_poll_new(sshbind->bindfd,POLLIN, + ssh_bind_poll_callback,sshbind); + return sshbind->poll; +} + +void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) { + sshbind->blocking = blocking ? 1 : 0; +} + +socket_t ssh_bind_get_fd(ssh_bind sshbind) { + return sshbind->bindfd; +} + +void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) { + sshbind->bindfd = fd; +} + +void ssh_bind_fd_toaccept(ssh_bind sshbind) { + sshbind->toaccept = 1; +} + +void ssh_bind_free(ssh_bind sshbind){ + int i; + + if (sshbind == NULL) { + return; + } + + if (sshbind->bindfd >= 0) { +#ifdef _WIN32 + closesocket(sshbind->bindfd); +#else + close(sshbind->bindfd); +#endif + } + sshbind->bindfd = SSH_INVALID_SOCKET; + + /* options */ + SAFE_FREE(sshbind->banner); + SAFE_FREE(sshbind->dsakey); + SAFE_FREE(sshbind->rsakey); + SAFE_FREE(sshbind->dsa); + SAFE_FREE(sshbind->rsa); + SAFE_FREE(sshbind->bindaddr); + + for (i = 0; i < 10; i++) { + if (sshbind->wanted_methods[i]) { + SAFE_FREE(sshbind->wanted_methods[i]); + } + } + + SAFE_FREE(sshbind); +} + +int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ + int i; + + if (session == NULL){ + ssh_set_error(sshbind, SSH_FATAL,"session is null"); + return SSH_ERROR; + } + + session->server = 1; + session->version = 2; + + /* copy options */ + for (i = 0; i < 10; ++i) { + if (sshbind->wanted_methods[i]) { + session->opts.wanted_methods[i] = strdup(sshbind->wanted_methods[i]); + if (session->opts.wanted_methods[i] == NULL) { + return SSH_ERROR; + } + } + } + + if (sshbind->bindaddr == NULL) + session->opts.bindaddr = NULL; + else { + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = strdup(sshbind->bindaddr); + if (session->opts.bindaddr == NULL) { + return SSH_ERROR; + } + } + + session->common.log_verbosity = sshbind->common.log_verbosity; + + ssh_socket_free(session->socket); + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + /* perhaps it may be better to copy the error from session to sshbind */ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + ssh_socket_set_fd(session->socket, fd); + ssh_socket_get_poll_handle_out(session->socket); + +#ifdef HAVE_ECC + if (sshbind->ecdsa) { + session->srv.ecdsa_key = ssh_key_dup(sshbind->ecdsa); + if (session->srv.ecdsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } +#endif + if (sshbind->dsa) { + session->srv.dsa_key = ssh_key_dup(sshbind->dsa); + if (session->srv.dsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + if (sshbind->rsa) { + session->srv.rsa_key = ssh_key_dup(sshbind->rsa); + if (session->srv.rsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + return SSH_OK; +} + +int ssh_bind_accept(ssh_bind sshbind, ssh_session session) { + socket_t fd = SSH_INVALID_SOCKET; + int rc; + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Can't accept new clients on a not bound socket."); + return SSH_ERROR; + } + + if (session == NULL){ + ssh_set_error(sshbind, SSH_FATAL,"session is null"); + return SSH_ERROR; + } + + fd = accept(sshbind->bindfd, NULL, NULL); + if (fd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + strerror(errno)); + return SSH_ERROR; + } + rc = ssh_bind_accept_fd(sshbind, session, fd); + + if(rc == SSH_ERROR){ +#ifdef _WIN32 + closesocket(fd); +#else + close(fd); +#endif + if (session->socket) + ssh_socket_close(session->socket); + } + return rc; +} + + +/** + * @} + */ diff --git a/libssh/src/buffer.c b/libssh/src/buffer.c new file mode 100644 index 00000000..ca120868 --- /dev/null +++ b/libssh/src/buffer.c @@ -0,0 +1,626 @@ +/* + * buffer.c - buffer functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" + +/** + * @defgroup libssh_buffer The SSH buffer functions. + * @ingroup libssh + * + * Functions to handle SSH buffers. + * + * @{ + */ + + +#ifdef DEBUG_BUFFER +/** + * @internal + * + * @brief Check that preconditions and postconditions are valid. + * + * @param[in] buf The buffer to check. + */ +static void buffer_verify(ssh_buffer buf){ + int doabort=0; + if(buf->data == NULL) + return; + if(buf->used > buf->allocated){ + fprintf(stderr,"Buffer error : allocated %u, used %u\n",buf->allocated, buf->used); + doabort=1; + } + if(buf->pos > buf->used){ + fprintf(stderr,"Buffer error : position %u, used %u\n",buf->pos, buf->used); + doabort=1; + } + if(buf->pos > buf->allocated){ + fprintf(stderr,"Buffer error : position %u, allocated %u\n",buf->pos, buf->allocated); + doabort=1; + } + if(doabort) + abort(); +} + +#else +#define buffer_verify(x) +#endif + +/** + * @brief Create a new SSH buffer. + * + * @return A newly initialized SSH buffer, NULL on error. + */ +struct ssh_buffer_struct *ssh_buffer_new(void) { + struct ssh_buffer_struct *buf = malloc(sizeof(struct ssh_buffer_struct)); + + if (buf == NULL) { + return NULL; + } + memset(buf, 0, sizeof(struct ssh_buffer_struct)); + buffer_verify(buf); + return buf; +} + +/** + * @brief Deallocate a SSH buffer. + * + * \param[in] buffer The buffer to free. + */ +void ssh_buffer_free(struct ssh_buffer_struct *buffer) { + if (buffer == NULL) { + return; + } + buffer_verify(buffer); + + if (buffer->data) { + /* burn the data */ + memset(buffer->data, 0, buffer->allocated); + SAFE_FREE(buffer->data); + } + memset(buffer, 'X', sizeof(*buffer)); + SAFE_FREE(buffer); +} + +static int realloc_buffer(struct ssh_buffer_struct *buffer, size_t needed) { + size_t smallest = 1; + char *new; + + buffer_verify(buffer); + + /* Find the smallest power of two which is greater or equal to needed */ + while(smallest <= needed) { + if (smallest == 0) { + return -1; + } + smallest <<= 1; + } + needed = smallest; + new = realloc(buffer->data, needed); + if (new == NULL) { + return -1; + } + buffer->data = new; + buffer->allocated = needed; + buffer_verify(buffer); + return 0; +} + +/** @internal + * @brief shifts a buffer to remove unused data in the beginning + * @param buffer SSH buffer + */ +static void buffer_shift(ssh_buffer buffer){ + buffer_verify(buffer); + if(buffer->pos==0) + return; + memmove(buffer->data, buffer->data + buffer->pos, buffer->used - buffer->pos); + buffer->used -= buffer->pos; + buffer->pos=0; + buffer_verify(buffer); +} + +/** + * @internal + * + * @brief Reinitialize a SSH buffer. + * + * @param[in] buffer The buffer to reinitialize. + * + * @return 0 on success, < 0 on error. + */ +int buffer_reinit(struct ssh_buffer_struct *buffer) { + buffer_verify(buffer); + memset(buffer->data, 0, buffer->used); + buffer->used = 0; + buffer->pos = 0; + if(buffer->allocated > 127) { + if (realloc_buffer(buffer, 127) < 0) { + return -1; + } + } + buffer_verify(buffer); + return 0; +} + +/** + * @internal + * + * @brief Add data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data A pointer to the data to add. + * + * @param[in] len The length of the data to add. + * + * @return 0 on success, < 0 on error. + */ +int buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint32_t len) { + buffer_verify(buffer); + + if (buffer->used + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used + len)) { + if(buffer->pos > 0) + buffer_shift(buffer); + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return -1; + } + } + + memcpy(buffer->data+buffer->used, data, len); + buffer->used+=len; + buffer_verify(buffer); + return 0; +} + +/** + * @internal + * + * @brief Add a SSH string to the tail of a buffer. + * + * @param[in] buffer The buffer to add the string. + * + * @param[in] string The SSH String to add. + * + * @return 0 on success, < 0 on error. + */ +int buffer_add_ssh_string(struct ssh_buffer_struct *buffer, + struct ssh_string_struct *string) { + uint32_t len = 0; + + len = ssh_string_len(string); + if (buffer_add_data(buffer, string, len + sizeof(uint32_t)) < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 32 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 32 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int buffer_add_u32(struct ssh_buffer_struct *buffer,uint32_t data){ + if (buffer_add_data(buffer, &data, sizeof(data)) < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 16 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 16 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int buffer_add_u16(struct ssh_buffer_struct *buffer,uint16_t data){ + if (buffer_add_data(buffer, &data, sizeof(data)) < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 64 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 64 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int buffer_add_u64(struct ssh_buffer_struct *buffer, uint64_t data){ + if (buffer_add_data(buffer, &data, sizeof(data)) < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 8 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 8 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int buffer_add_u8(struct ssh_buffer_struct *buffer,uint8_t data){ + if (buffer_add_data(buffer, &data, sizeof(uint8_t)) < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add data at the head of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data The data to prepend. + * + * @param[in] len The length of data to prepend. + * + * @return 0 on success, -1 on error. + */ +int buffer_prepend_data(struct ssh_buffer_struct *buffer, const void *data, + uint32_t len) { + buffer_verify(buffer); + + if(len <= buffer->pos){ + /* It's possible to insert data between begin and pos */ + memcpy(buffer->data + (buffer->pos - len), data, len); + buffer->pos -= len; + buffer_verify(buffer); + return 0; + } + /* pos isn't high enough */ + if (buffer->used - buffer->pos + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used - buffer->pos + len)) { + if (realloc_buffer(buffer, buffer->used - buffer->pos + len) < 0) { + return -1; + } + } + memmove(buffer->data + len, buffer->data + buffer->pos, buffer->used - buffer->pos); + memcpy(buffer->data, data, len); + buffer->used += len - buffer->pos; + buffer->pos = 0; + buffer_verify(buffer); + return 0; +} + +/** + * @internal + * + * @brief Append data from a buffer to the tail of another buffer. + * + * @param[in] buffer The destination buffer. + * + * @param[in] source The source buffer to append. It doesn't take the + * position of the buffer into account. + * + * @return 0 on success, -1 on error. + */ +int buffer_add_buffer(struct ssh_buffer_struct *buffer, + struct ssh_buffer_struct *source) { + if (buffer_add_data(buffer, buffer_get_rest(source), buffer_get_rest_len(source)) < 0) { + return -1; + } + + return 0; +} + +/** + * @brief Get a pointer on the head of a buffer. + * + * @param[in] buffer The buffer to get the head pointer. + * + * @return A data pointer on the head. It doesn't take the position + * into account. + * + * @warning Don't expect data to be nul-terminated. + * + * @see buffer_get_rest() + * @see buffer_get_len() + */ +void *ssh_buffer_get_begin(struct ssh_buffer_struct *buffer){ + return buffer->data; +} + +/** + * @internal + * + * @brief Get a pointer to the head of a buffer at the current position. + * + * @param[in] buffer The buffer to get the head pointer. + * + * @return A pointer to the data from current position. + * + * @see buffer_get_rest_len() + * @see buffer_get() + */ +void *buffer_get_rest(struct ssh_buffer_struct *buffer){ + return buffer->data + buffer->pos; +} + +/** + * @brief Get the length of the buffer, not counting position. + * + * @param[in] buffer The buffer to get the length from. + * + * @return The length of the buffer. + * + * @see buffer_get() + */ +uint32_t ssh_buffer_get_len(struct ssh_buffer_struct *buffer){ + return buffer->used; +} + +/** + * @internal + * + * @brief Get the length of the buffer from the current position. + * + * @param[in] buffer The buffer to get the length from. + * + * @return The length of the buffer. + * + * @see buffer_get_rest() + */ +uint32_t buffer_get_rest_len(struct ssh_buffer_struct *buffer){ + buffer_verify(buffer); + return buffer->used - buffer->pos; +} + +/** + * @internal + * + * @brief Advance the position in the buffer. + * + * This has effect to "eat" bytes at head of the buffer. + * + * @param[in] buffer The buffer to advance the position. + * + * @param[in] len The number of bytes to eat. + * + * @return The new size of the buffer. + */ +uint32_t buffer_pass_bytes(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->pos + len < len || buffer->used < buffer->pos + len) { + return 0; + } + + buffer->pos+=len; + /* if the buffer is empty after having passed the whole bytes into it, we can clean it */ + if(buffer->pos==buffer->used){ + buffer->pos=0; + buffer->used=0; + } + buffer_verify(buffer); + return len; +} + +/** + * @internal + * + * @brief Cut the end of the buffer. + * + * @param[in] buffer The buffer to cut. + * + * @param[in] len The number of bytes to remove from the tail. + * + * @return The new size of the buffer. + */ +uint32_t buffer_pass_bytes_end(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->used < len) { + return 0; + } + + buffer->used-=len; + buffer_verify(buffer); + return len; +} + +/** + * @internal + * + * @brief Get the remaining data out of the buffer and adjust the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data The data buffer where to store the data. + * + * @param[in] len The length to read from the buffer. + * + * @returns 0 if there is not enough data in buffer, len otherwise. + */ +uint32_t buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint32_t len){ + /* + * Check for a integer overflow first, then check if not enough data is in + * the buffer. + */ + if (buffer->pos + len < len || buffer->pos + len > buffer->used) { + return 0; + } + memcpy(data,buffer->data+buffer->pos,len); + buffer->pos+=len; + return len; /* no yet support for partial reads (is it really needed ?? ) */ +} + +/** + * @internal + * + * @brief Get a 8 bits unsigned int out of the buffer and adjusts the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint8_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 1 otherwise. + */ +int buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ + return buffer_get_data(buffer,data,sizeof(uint8_t)); +} + +/** \internal + * \brief gets a 32 bits unsigned int out of the buffer. Adjusts the read pointer. + * \param buffer Buffer to read + * \param data pointer to a uint32_t where to store the data + * \returns 0 if there is not enough data in buffer + * \returns 4 otherwise. + */ +int buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ + return buffer_get_data(buffer,data,sizeof(uint32_t)); +} +/** + * @internal + * + * @brief Get a 64 bits unsigned int out of the buffer and adjusts the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint64_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 8 otherwise. + */ +int buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ + return buffer_get_data(buffer,data,sizeof(uint64_t)); +} + +/** + * @internal + * + * @brief Get a SSH String out of the buffer and adjusts the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @returns The SSH String, NULL on error. + */ +struct ssh_string_struct *buffer_get_ssh_string(struct ssh_buffer_struct *buffer) { + uint32_t stringlen; + uint32_t hostlen; + struct ssh_string_struct *str = NULL; + + if (buffer_get_u32(buffer, &stringlen) == 0) { + return NULL; + } + hostlen = ntohl(stringlen); + /* verify if there is enough space in buffer to get it */ + if (buffer->pos + hostlen < hostlen || buffer->pos + hostlen > buffer->used) { + return NULL; /* it is indeed */ + } + str = ssh_string_new(hostlen); + if (str == NULL) { + return NULL; + } + if (buffer_get_data(buffer, ssh_string_data(str), hostlen) != hostlen) { + /* should never happen */ + SAFE_FREE(str); + return NULL; + } + + return str; +} + +/** + * @internal + * + * @brief Get a mpint out of the buffer and adjusts the read pointer. + * + * @note This function is SSH-1 only. + * + * @param[in] buffer The buffer to read. + * + * @returns The SSH String containing the mpint, NULL on error. + */ +struct ssh_string_struct *buffer_get_mpint(struct ssh_buffer_struct *buffer) { + uint16_t bits; + uint32_t len; + struct ssh_string_struct *str = NULL; + + if (buffer_get_data(buffer, &bits, sizeof(uint16_t)) != sizeof(uint16_t)) { + return NULL; + } + bits = ntohs(bits); + len = (bits + 7) / 8; + if (buffer->pos + len < len || buffer->pos + len > buffer->used) { + return NULL; + } + str = ssh_string_new(len); + if (str == NULL) { + return NULL; + } + if (buffer_get_data(buffer, ssh_string_data(str), len) != len) { + SAFE_FREE(str); + return NULL; + } + return str; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/callbacks.c b/libssh/src/callbacks.c new file mode 100644 index 00000000..5a61180d --- /dev/null +++ b/libssh/src/callbacks.c @@ -0,0 +1,61 @@ +/* + * callbacks.c - callback functions + * + * This file is part of the SSH Library + * + * Copyright (c) 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. + */ + +#include "config.h" + +#include "libssh/callbacks.h" +#include "libssh/session.h" + +int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) { + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + enter_function(); + if(cb->size <= 0 || cb->size > 1024 * sizeof(void *)){ + ssh_set_error(session,SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + leave_function(); + return SSH_ERROR; + } + session->common.callbacks = cb; + leave_function(); + return 0; +} + +int ssh_set_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) { + ssh_session session = NULL; + if (channel == NULL || cb == NULL) { + return SSH_ERROR; + } + session = channel->session; + enter_function(); + if(cb->size <= 0 || cb->size > 1024 * sizeof(void *)){ + ssh_set_error(session,SSH_FATAL, + "Invalid channel callback passed in (badly initialized)"); + leave_function(); + return SSH_ERROR; + } + channel->callbacks = cb; + leave_function(); + return 0; +} diff --git a/libssh/src/channels.c b/libssh/src/channels.c new file mode 100644 index 00000000..6e9b2eb0 --- /dev/null +++ b/libssh/src/channels.c @@ -0,0 +1,3461 @@ +/* + * channels.c - SSH channel functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/messages.h" +#if WITH_SERVER +#include "libssh/server.h" +#endif + +#define WINDOWBASE 1280000 +#define WINDOWLIMIT (WINDOWBASE/2) + +/* + * All implementations MUST be able to process packets with an + * uncompressed payload length of 32768 bytes or less and a total packet + * size of 35000 bytes or less. + */ +#define CHANNEL_MAX_PACKET 32768 +#define CHANNEL_INITIAL_WINDOW 64000 + +/** + * @defgroup libssh_channel The SSH channel functions + * @ingroup libssh + * + * Functions that manage a SSH channel. + * + * @{ + */ + +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); + +/** + * @brief Allocate a new channel. + * + * @param[in] session The ssh session to use. + * + * @return A pointer to a newly allocated channel, NULL on error. + */ +ssh_channel ssh_channel_new(ssh_session session) { + ssh_channel channel = NULL; + + if(session == NULL) { + return NULL; + } + + channel = malloc(sizeof(struct ssh_channel_struct)); + if (channel == NULL) { + ssh_set_error_oom(session); + return NULL; + } + memset(channel,0,sizeof(struct ssh_channel_struct)); + + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(channel); + return NULL; + } + + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + ssh_buffer_free(channel->stdout_buffer); + SAFE_FREE(channel); + return NULL; + } + + channel->session = session; + channel->version = session->version; + channel->exit_status = -1; + channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; + + if(session->channels == NULL) { + session->channels = ssh_list_new(); + } + ssh_list_prepend(session->channels, channel); + return channel; +} + +/** + * @internal + * + * @brief Create a new channel identifier. + * + * @param[in] session The SSH session to use. + * + * @return The new channel identifier. + */ +uint32_t ssh_channel_new_id(ssh_session session) { + return ++(session->maxchannel); +} + +/** + * @internal + * + * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet. + * + * Constructs the channel object. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ + uint32_t channelid=0; + uint32_t tmp; + ssh_channel channel; + (void)type; + (void)user; + enter_function(); + ssh_log(session,SSH_LOG_PACKET,"Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + + buffer_get_u32(packet, &channelid); + channelid=ntohl(channelid); + channel=ssh_channel_from_local(session,channelid); + if(channel==NULL){ + ssh_set_error(session, SSH_FATAL, + "Unknown channel id %lu", + (long unsigned int) channelid); + /* TODO: Set error marking in channel object */ + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u32(packet, &tmp); + channel->remote_channel = ntohl(tmp); + + buffer_get_u32(packet, &tmp); + channel->remote_window = ntohl(tmp); + + buffer_get_u32(packet,&tmp); + channel->remote_maxpacket=ntohl(tmp); + + ssh_log(session, SSH_LOG_PROTOCOL, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + channel->local_channel, + channel->remote_channel); + ssh_log(session, SSH_LOG_PROTOCOL, + "Remote window : %lu, maxpacket : %lu", + (long unsigned int) channel->remote_window, + (long unsigned int) channel->remote_maxpacket); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->flags = channel->flags & ~SSH_CHANNEL_FLAG_NOT_BOUND; + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ + + ssh_channel channel; + ssh_string error_s; + char *error = NULL; + uint32_t code; + (void)user; + (void)type; + channel=channel_from_msg(session,packet); + if(channel==NULL){ + ssh_log(session,SSH_LOG_RARE,"Invalid channel in packet"); + return SSH_PACKET_USED; + } + buffer_get_u32(packet, &code); + + error_s = buffer_get_ssh_string(packet); + if(error_s != NULL) + error = ssh_string_to_char(error_s); + ssh_string_free(error_s); + if (error == NULL) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel opening failure: channel %u error (%lu) %s", + channel->local_channel, + (long unsigned int) ntohl(code), + error); + SAFE_FREE(error); + channel->state=SSH_CHANNEL_STATE_OPEN_DENIED; + return SSH_PACKET_USED; +} + +static int ssh_channel_open_termination(void *c){ + ssh_channel channel = (ssh_channel) c; + if (channel->state != SSH_CHANNEL_STATE_OPENING || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and + * wait for the reply. + * + * @param[in] channel The current channel. + * + * @param[in] type_c A C string describing the kind of channel (e.g. "exec"). + * + * @param[in] window The receiving window of the channel. The window is the + * maximum size of data that can stay in buffers and + * network. + * + * @param[in] maxpacket The maximum packet size allowed (like MTU). + * + * @param[in] payload The buffer containing additional payload for the query. + */ +static int channel_open(ssh_channel channel, const char *type_c, int window, + int maxpacket, ssh_buffer payload) { + ssh_session session = channel->session; + ssh_string type = NULL; + int err=SSH_ERROR; + + enter_function(); + switch(channel->state){ + case SSH_CHANNEL_STATE_NOT_OPEN: + break; + case SSH_CHANNEL_STATE_OPENING: + goto pending; + case SSH_CHANNEL_STATE_OPEN: + case SSH_CHANNEL_STATE_CLOSED: + case SSH_CHANNEL_STATE_OPEN_DENIED: + goto end; + default: + ssh_set_error(session,SSH_FATAL,"Bad state in channel_open: %d",channel->state); + } + channel->local_channel = ssh_channel_new_id(session); + channel->local_maxpacket = maxpacket; + channel->local_window = window; + + ssh_log(session, SSH_LOG_PROTOCOL, + "Creating a channel %d with %d window and %d max packet", + channel->local_channel, window, maxpacket); + + type = ssh_string_from_char(type_c); + if (type == NULL) { + ssh_set_error_oom(session); + leave_function(); + return err; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN) < 0 || + buffer_add_ssh_string(session->out_buffer,type) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->local_channel)) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->local_window)) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->local_maxpacket)) < 0) { + ssh_set_error_oom(session); + ssh_string_free(type); + leave_function(); + return err; + } + + ssh_string_free(type); + + if (payload != NULL) { + if (buffer_add_buffer(session->out_buffer, payload) < 0) { + ssh_set_error_oom(session); + leave_function(); + return err; + } + } + channel->state = SSH_CHANNEL_STATE_OPENING; + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return err; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d", + type_c, channel->local_channel); +pending: + /* wait until channel is opened by server */ + err = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, ssh_channel_open_termination, channel); + if (err != SSH_OK || session->session_state == SSH_SESSION_STATE_ERROR) + err = SSH_ERROR; +end: + if(channel->state == SSH_CHANNEL_STATE_OPEN) + err=SSH_OK; + leave_function(); + return err; +} + +/* return channel with corresponding local id, or NULL if not found */ +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { + struct ssh_iterator *it; + ssh_channel channel; + + for (it = ssh_list_get_iterator(session->channels); it != NULL ; it=it->next) { + channel = ssh_iterator_value(ssh_channel, it); + if (channel == NULL) { + continue; + } + if (channel->local_channel == id) { + return channel; + } + } + + return NULL; +} + +/** + * @internal + * @brief grows the local window and send a packet to the other party + * @param session SSH session + * @param channel SSH channel + * @param minimumsize The minimum acceptable size for the new window. + */ +static int grow_window(ssh_session session, ssh_channel channel, int minimumsize) { + uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; + + enter_function(); +#ifdef WITH_SSH1 + if (session->version == 1){ + channel->remote_window = new_window; + leave_function(); + return SSH_OK; + } +#endif + if(new_window <= channel->local_window){ + ssh_log(session,SSH_LOG_PROTOCOL, + "growing window (channel %d:%d) to %d bytes : not needed (%d bytes)", + channel->local_channel, channel->remote_channel, new_window, + channel->local_window); + leave_function(); + return SSH_OK; + } + /* WINDOW_ADJUST packet needs a relative increment rather than an absolute + * value, so we give here the missing bytes needed to reach new_window + */ + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_WINDOW_ADJUST) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0 || + buffer_add_u32(session->out_buffer, htonl(new_window - channel->local_window)) < 0) { + ssh_set_error_oom(session); + goto error; + } + + if (packet_send(session) == SSH_ERROR) { + goto error; + } + + ssh_log(session, SSH_LOG_PROTOCOL, + "growing window (channel %d:%d) to %d bytes", + channel->local_channel, + channel->remote_channel, + new_window); + + channel->local_window = new_window; + + leave_function(); + return SSH_OK; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Parse a channel-related packet to resolve it to a ssh_channel. + * + * This works on SSH1 sessions too. + * + * @param[in] session The current SSH session. + * + * @param[in] packet The buffer to parse packet from. The read pointer will + * be moved after the call. + * + * @returns The related ssh_channel, or NULL if the channel is + * unknown or the packet is invalid. + */ +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { + ssh_channel channel; + uint32_t chan; +#ifdef WITH_SSH1 + /* With SSH1, the channel is always the first one */ + if(session->version==1) + return ssh_get_channel1(session); +#endif + if (buffer_get_u32(packet, &chan) != sizeof(uint32_t)) { + ssh_set_error(session, SSH_FATAL, + "Getting channel from message: short read"); + return NULL; + } + + channel = ssh_channel_from_local(session, ntohl(chan)); + if (channel == NULL) { + ssh_set_error(session, SSH_FATAL, + "Server specified invalid channel %lu", + (long unsigned int) ntohl(chan)); + } + + return channel; +} + +SSH_PACKET_CALLBACK(channel_rcv_change_window) { + ssh_channel channel; + uint32_t bytes; + int rc; + (void)user; + (void)type; + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + } + + rc = buffer_get_u32(packet, &bytes); + if (channel == NULL || rc != sizeof(uint32_t)) { + ssh_log(session, SSH_LOG_PACKET, + "Error getting a window adjust message: invalid packet"); + leave_function(); + return SSH_PACKET_USED; + } + + bytes = ntohl(bytes); + ssh_log(session, SSH_LOG_PROTOCOL, + "Adding %d bytes to channel (%d:%d) (from %d bytes)", + bytes, + channel->local_channel, + channel->remote_channel, + channel->remote_window); + + channel->remote_window += bytes; + + leave_function(); + return SSH_PACKET_USED; +} + +/* is_stderr is set to 1 if the data are extended, ie stderr */ +SSH_PACKET_CALLBACK(channel_rcv_data){ + ssh_channel channel; + ssh_string str; + ssh_buffer buf; + size_t len; + int is_stderr; + int rest; + (void)user; + enter_function(); + if(type==SSH2_MSG_CHANNEL_DATA) + is_stderr=0; + else + is_stderr=1; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, + "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + if (is_stderr) { + uint32_t ignore; + /* uint32 data type code. we can ignore it */ + buffer_get_u32(packet, &ignore); + } + + str = buffer_get_ssh_string(packet); + if (str == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid data packet!"); + leave_function(); + return SSH_PACKET_USED; + } + len = ssh_string_len(str); + + ssh_log(session, SSH_LOG_PROTOCOL, + "Channel receiving %" PRIdS " bytes data in %d (local win=%d remote win=%d)", + len, + is_stderr, + channel->local_window, + channel->remote_window); + + /* What shall we do in this case? Let's accept it anyway */ + if (len > channel->local_window) { + ssh_log(session, SSH_LOG_RARE, + "Data packet too big for our window(%" PRIdS " vs %d)", + len, + channel->local_window); + } + + if (channel_default_bufferize(channel, ssh_string_data(str), len, + is_stderr) < 0) { + ssh_string_free(str); + leave_function(); + return SSH_PACKET_USED; + } + + if (len <= channel->local_window) { + channel->local_window -= len; + } else { + channel->local_window = 0; /* buggy remote */ + } + + ssh_log(session, SSH_LOG_PROTOCOL, + "Channel windows are now (local win=%d remote win=%d)", + channel->local_window, + channel->remote_window); + + ssh_string_free(str); + + if(ssh_callbacks_exists(channel->callbacks, channel_data_function)) { + if(is_stderr) { + buf = channel->stderr_buffer; + } else { + buf = channel->stdout_buffer; + } + rest = channel->callbacks->channel_data_function(channel->session, + channel, + buffer_get_rest(buf), + buffer_get_rest_len(buf), + is_stderr, + channel->callbacks->userdata); + if(rest > 0) { + buffer_pass_bytes(buf, rest); + } + if (channel->local_window + buffer_get_rest_len(buf) < WINDOWLIMIT) { + if (grow_window(session, channel, 0) < 0) { + leave_function(); + return -1; + } + } + } + + leave_function(); + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_eof) { + ssh_channel channel; + (void)user; + (void)type; + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received eof on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + /* channel->remote_window = 0; */ + channel->remote_eof = 1; + + if(ssh_callbacks_exists(channel->callbacks, channel_eof_function)) { + channel->callbacks->channel_eof_function(channel->session, + channel, + channel->callbacks->userdata); + } + + leave_function(); + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_close) { + ssh_channel channel; + (void)user; + (void)type; + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received close on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if ((channel->stdout_buffer && + buffer_get_rest_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + buffer_get_rest_len(channel->stderr_buffer) > 0)) { + channel->delayed_close = 1; + } else { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } + if (channel->remote_eof == 0) { + ssh_log(session, SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + channel->remote_eof = 1; + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + + if(ssh_callbacks_exists(channel->callbacks, channel_close_function)) { + channel->callbacks->channel_close_function(channel->session, + channel, + channel->callbacks->userdata); + } + channel->flags &= SSH_CHANNEL_FLAG_CLOSED_REMOTE; + if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) + ssh_channel_do_free(channel); + leave_function(); + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_request) { + ssh_channel channel; + ssh_string request_s; + char *request; + uint8_t status; + int rc; + (void)user; + (void)type; + + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + request_s = buffer_get_ssh_string(packet); + if (request_s == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + request = ssh_string_to_char(request_s); + ssh_string_free(request_s); + if (request == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u8(packet, (uint8_t *) &status); + + if (strcmp(request,"exit-status") == 0) { + uint32_t exit_status = 0; + + SAFE_FREE(request); + buffer_get_u32(packet, &exit_status); + channel->exit_status = ntohl(exit_status); + ssh_log(session, SSH_LOG_PACKET, "received exit-status %d", channel->exit_status); + + if(ssh_callbacks_exists(channel->callbacks, channel_exit_status_function)) { + channel->callbacks->channel_exit_status_function(channel->session, + channel, + channel->exit_status, + channel->callbacks->userdata); + } + + leave_function(); + return SSH_PACKET_USED; + } + + if (strcmp(request,"signal") == 0) { + ssh_string signal_str; + char *sig; + + SAFE_FREE(request); + ssh_log(session, SSH_LOG_PACKET, "received signal"); + + signal_str = buffer_get_ssh_string(packet); + if (signal_str == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + sig = ssh_string_to_char(signal_str); + ssh_string_free(signal_str); + if (sig == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + + ssh_log(session, SSH_LOG_PACKET, + "Remote connection sent a signal SIG %s", sig); + if(ssh_callbacks_exists(channel->callbacks, channel_signal_function)) { + channel->callbacks->channel_signal_function(channel->session, + channel, + sig, + channel->callbacks->userdata); + } + SAFE_FREE(sig); + + leave_function(); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + ssh_string tmp; + char *sig; + char *errmsg = NULL; + char *lang = NULL; + uint8_t i; + + SAFE_FREE(request); + + tmp = buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + sig = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (sig == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u8(packet, &i); + if (i == 0) { + core = ""; + } + + tmp = buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + SAFE_FREE(sig); + leave_function(); + return SSH_PACKET_USED; + } + + errmsg = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (errmsg == NULL) { + SAFE_FREE(sig); + leave_function(); + return SSH_PACKET_USED; + } + + tmp = buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + SAFE_FREE(errmsg); + SAFE_FREE(sig); + leave_function(); + return SSH_PACKET_USED; + } + + lang = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (lang == NULL) { + SAFE_FREE(errmsg); + SAFE_FREE(sig); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", sig, core); + if(ssh_callbacks_exists(channel->callbacks, channel_exit_signal_function)) { + channel->callbacks->channel_exit_signal_function(channel->session, + channel, + sig, i, errmsg, lang, + channel->callbacks->userdata); + } + + SAFE_FREE(lang); + SAFE_FREE(errmsg); + SAFE_FREE(sig); + + leave_function(); + return SSH_PACKET_USED; + } + if(strcmp(request,"keepalive@openssh.com")==0){ + SAFE_FREE(request); + ssh_log(session, SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive"); + rc = buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_FAILURE); + if (rc < 0) { + return SSH_PACKET_USED; + } + rc = buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)); + if (rc < 0) { + return SSH_PACKET_USED; + } + packet_send(session); + leave_function(); + return SSH_PACKET_USED; + } + + /* If we are here, that means we have a request that is not in the understood + * client requests. That means we need to create a ssh message to be passed + * to the user code handling ssh messages + */ + ssh_message_handle_channel_request(session,channel,packet,request,status); + + SAFE_FREE(request); + + leave_function(); + return SSH_PACKET_USED; +} + +/* + * When data has been received from the ssh server, it can be applied to the + * known user function, with help of the callback, or inserted here + * + * FIXME is the window changed? + */ +int channel_default_bufferize(ssh_channel channel, void *data, int len, + int is_stderr) { + ssh_session session; + + if(channel == NULL) { + return -1; + } + + session = channel->session; + + if(data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + ssh_log(session, SSH_LOG_RARE, + "placing %d bytes into channel buffer (stderr=%d)", len, is_stderr); + if (is_stderr == 0) { + /* stdout */ + if (channel->stdout_buffer == NULL) { + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (buffer_add_data(channel->stdout_buffer, data, len) < 0) { + ssh_set_error_oom(session); + ssh_buffer_free(channel->stdout_buffer); + channel->stdout_buffer = NULL; + return -1; + } + } else { + /* stderr */ + if (channel->stderr_buffer == NULL) { + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (buffer_add_data(channel->stderr_buffer, data, len) < 0) { + ssh_set_error_oom(session); + ssh_buffer_free(channel->stderr_buffer); + channel->stderr_buffer = NULL; + return -1; + } + } + + return 0; +} + +/** + * @brief Open a session channel (suited for a shell, not TCP forwarding). + * + * @param[in] channel An allocated channel. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @see channel_open_forward() + * @see channel_request_env() + * @see channel_request_shell() + * @see channel_request_exec() + */ +int ssh_channel_open_session(ssh_channel channel) { + if(channel == NULL) { + return SSH_ERROR; + } + +#ifdef WITH_SSH1 + if (channel->session->version == 1) { + return channel_open_session1(channel); + } +#endif + + return channel_open(channel, + "session", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + NULL); +} + +/** + * @brief Open a TCP/IP forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not automatically + * forward the content of a socket to the channel. You still have to + * use channel_read and channel_write for this. + */ +int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) { + ssh_session session; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + + session = channel->session; + + if(remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + enter_function(); + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + str = ssh_string_from_char(remotehost); + if (str == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(remoteport)) < 0) { + ssh_set_error_oom(session); + goto error; + } + + ssh_string_free(str); + str = ssh_string_from_char(sourcehost); + if (str == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(localport)) < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-tcpip", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + ssh_buffer_free(payload); + ssh_string_free(str); + + leave_function(); + return rc; +} + + +/** + * @brief Close and free a channel. + * + * @param[in] channel The channel to free. + * + * @warning Any data unread on this channel will be lost. + */ +void ssh_channel_free(ssh_channel channel) { + ssh_session session; + + if (channel == NULL) { + return; + } + + session = channel->session; + enter_function(); + + if (session->alive && channel->state == SSH_CHANNEL_STATE_OPEN) { + ssh_channel_close(channel); + } + channel->flags &= SSH_CHANNEL_FLAG_FREED_LOCAL; + + /* The idea behind the flags is the following : it is well possible + * that a client closes a channel that stills exists on the server side. + * We definitively close the channel when we receive a close message *and* + * the user closed it. + */ + if((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) + || (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)){ + ssh_channel_do_free(channel); + } + leave_function(); +} + +/** + * @internal + * @brief Effectively free a channel, without caring about flags + */ + +void ssh_channel_do_free(ssh_channel channel){ + struct ssh_iterator *it; + ssh_session session = channel->session; + it = ssh_list_find(session->channels, channel); + if(it != NULL){ + ssh_list_remove(session->channels, it); + } + ssh_buffer_free(channel->stdout_buffer); + ssh_buffer_free(channel->stderr_buffer); + + /* debug trick to catch use after frees */ + memset(channel, 'X', sizeof(struct ssh_channel_struct)); + SAFE_FREE(channel); +} + +/** + * @brief Send an end of file on the channel. + * + * This doesn't close the channel. You may still read from it but not write. + * + * @param[in] channel The channel to send the eof to. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @see channel_close() + * @see channel_free() + */ +int ssh_channel_send_eof(ssh_channel channel){ + ssh_session session; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + + session = channel->session; + enter_function(); + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_EOF) < 0) { + ssh_set_error_oom(session); + goto error; + } + if (buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)) < 0) { + ssh_set_error_oom(session); + goto error; + } + rc = packet_send(session); + ssh_log(session, SSH_LOG_PACKET, + "Sent a EOF on client channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + channel->local_eof = 1; + + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return rc; +} + +/** + * @brief Close a channel. + * + * This sends an end of file and then closes the channel. You won't be able + * to recover any data the server was going to send or was in buffers. + * + * @param[in] channel The channel to close. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @see channel_free() + * @see channel_eof() + */ +int ssh_channel_close(ssh_channel channel){ + ssh_session session; + int rc = 0; + + if(channel == NULL) { + return SSH_ERROR; + } + + session = channel->session; + enter_function(); + + if (channel->local_eof == 0) { + rc = ssh_channel_send_eof(channel); + } + + if (rc != SSH_OK) { + leave_function(); + return rc; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_CLOSE) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = packet_send(session); + ssh_log(session, SSH_LOG_PACKET, + "Sent a close on client channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if(rc == SSH_OK) { + channel->state=SSH_CHANNEL_STATE_CLOSED; + } + + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return rc; +} + +/* this termination function waits for a window growing condition */ +static int ssh_channel_waitwindow_termination(void *c){ + ssh_channel channel = (ssh_channel) c; + if (channel->remote_window > 0 || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/* This termination function waits until the session is not in blocked status + * anymore, e.g. because of a key re-exchange. + */ +static int ssh_waitsession_unblocked(void *s){ + ssh_session session = (ssh_session)s; + switch (session->session_state){ + case SSH_SESSION_STATE_DH: + case SSH_SESSION_STATE_INITIAL_KEX: + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + return 0; + default: + return 1; + } +} +/** + * @internal + * @brief Flushes a channel (and its session) until the output buffer + * is empty, or timeout elapsed. + * @param channel SSH channel + * @returns SSH_OK On success, + * SSH_ERROR on error + * SSH_AGAIN Timeout elapsed (or in nonblocking mode) + */ +int ssh_channel_flush(ssh_channel channel){ + return ssh_blocking_flush(channel->session, SSH_TIMEOUT_USER); +} + +int channel_write_common(ssh_channel channel, const void *data, + uint32_t len, int is_stderr) { + ssh_session session; + uint32_t origlen = len; + size_t effectivelen; + size_t maxpacketlen; + int rc; + + if(channel == NULL) { + return -1; + } + session = channel->session; + if(data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (len > INT_MAX) { + ssh_log(session, SSH_LOG_PROTOCOL, + "Length (%u) is bigger than INT_MAX", len); + return SSH_ERROR; + } + + enter_function(); + + /* + * Handle the max packet len from remote side, be nice + * 10 bytes for the headers + */ + maxpacketlen = channel->remote_maxpacket - 10; + + if (channel->local_eof) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Can't write to channel %d:%d after EOF was sent", + channel->local_channel, + channel->remote_channel); + leave_function(); + return -1; + } + + if (channel->state != SSH_CHANNEL_STATE_OPEN || channel->delayed_close != 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); + leave_function(); + return -1; + } + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + leave_function(); + return SSH_ERROR; + } +#ifdef WITH_SSH1 + if (channel->version == 1) { + rc = channel_write1(channel, data, len); + leave_function(); + return rc; + } +#endif + if (ssh_waitsession_unblocked(session) == 0){ + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_waitsession_unblocked, session); + if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session)) + goto out; + } + while (len > 0) { + if (channel->remote_window < len) { + ssh_log(session, SSH_LOG_PROTOCOL, + "Remote window is %d bytes. going to write %d bytes", + channel->remote_window, + len); + /* What happens when the channel window is zero? */ + if(channel->remote_window == 0) { + /* nothing can be written */ + ssh_log(session, SSH_LOG_PROTOCOL, + "Wait for a growing window message..."); + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_channel_waitwindow_termination,channel); + if (rc == SSH_ERROR || !ssh_channel_waitwindow_termination(channel)) + goto out; + continue; + } + effectivelen = len > channel->remote_window ? channel->remote_window : len; + } else { + effectivelen = len; + } + effectivelen = effectivelen > maxpacketlen ? maxpacketlen : effectivelen; + if (buffer_add_u8(session->out_buffer, is_stderr ? + SSH2_MSG_CHANNEL_EXTENDED_DATA : SSH2_MSG_CHANNEL_DATA) < 0 || + buffer_add_u32(session->out_buffer, + htonl(channel->remote_channel)) < 0) { + ssh_set_error_oom(session); + goto error; + } + /* stderr message has an extra field */ + if (is_stderr && + buffer_add_u32(session->out_buffer, htonl(SSH2_EXTENDED_DATA_STDERR)) < 0) { + ssh_set_error_oom(session); + goto error; + } + /* append payload data */ + if (buffer_add_u32(session->out_buffer, htonl(effectivelen)) < 0 || + buffer_add_data(session->out_buffer, data, effectivelen) < 0) { + ssh_set_error_oom(session); + goto error; + } + + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return SSH_ERROR; + } + + ssh_log(session, SSH_LOG_RARE, + "channel_write wrote %ld bytes", (long int) effectivelen); + + channel->remote_window -= effectivelen; + len -= effectivelen; + data = ((uint8_t*)data + effectivelen); + } + /* it's a good idea to flush the socket now */ + rc = ssh_channel_flush(channel); + if(rc == SSH_ERROR) + goto error; +out: + leave_function(); + return (int)(origlen - len); + +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return SSH_ERROR; +} + +uint32_t ssh_channel_window_size(ssh_channel channel) { + return channel->remote_window; +} + +/** + * @brief Blocking write on a channel. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @see channel_read() + */ +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { + return channel_write_common(channel, data, len, 0); +} + +/** + * @brief Check if the channel is open or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is closed, nonzero otherwise. + * + * @see channel_is_closed() + */ +int ssh_channel_is_open(ssh_channel channel) { + if(channel == NULL) { + return 0; + } + return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); +} + +/** + * @brief Check if the channel is closed or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is opened, nonzero otherwise. + * + * @see channel_is_open() + */ +int ssh_channel_is_closed(ssh_channel channel) { + if(channel == NULL) { + return SSH_ERROR; + } + return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); +} + +/** + * @brief Check if remote has sent an EOF. + * + * @param[in] channel The channel to check. + * + * @return 0 if there is no EOF, nonzero otherwise. + */ +int ssh_channel_is_eof(ssh_channel channel) { + if(channel == NULL) { + return SSH_ERROR; + } + if ((channel->stdout_buffer && + buffer_get_rest_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + buffer_get_rest_len(channel->stderr_buffer) > 0)) { + return 0; + } + + return (channel->remote_eof != 0); +} + +/** + * @brief Put the channel into blocking or nonblocking mode. + * + * @param[in] channel The channel to use. + * + * @param[in] blocking A boolean for blocking or nonblocking. + * + * @warning A side-effect of this is to put the whole session + * in non-blocking mode. + * @see ssh_set_blocking() + */ +void ssh_channel_set_blocking(ssh_channel channel, int blocking) { + if(channel == NULL) { + return; + } + ssh_set_blocking(channel->session,blocking); +} + +/** + * @internal + * + * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state. + * + * This works on SSH1 sessions too. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_success){ + ssh_channel channel; + (void)type; + (void)user; + enter_function(); + channel=channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_CHANNEL_SUCCESS on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_CHANNEL_SUCCESS received in incorrect state %d", + channel->request_state); + } else { + channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state. + * + * This works on SSH1 sessions too. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ + ssh_channel channel; + (void)type; + (void)user; + enter_function(); + channel=channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_CHANNEL_FAILURE on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_CHANNEL_FAILURE received in incorrect state %d", + channel->request_state); + } else { + channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + leave_function(); + return SSH_PACKET_USED; +} + +static int ssh_channel_request_termination(void *c){ + ssh_channel channel = (ssh_channel)c; + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +static int channel_request(ssh_channel channel, const char *request, + ssh_buffer buffer, int reply) { + ssh_session session = channel->session; + ssh_string req = NULL; + int rc = SSH_ERROR; + + enter_function(); + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + req = ssh_string_from_char(request); + if (req == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_REQUEST) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0 || + buffer_add_ssh_string(session->out_buffer, req) < 0 || + buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) { + ssh_set_error_oom(session); + ssh_string_free(req); + goto error; + } + ssh_string_free(req); + + if (buffer != NULL) { + if (buffer_add_data(session->out_buffer, buffer_get_rest(buffer), + buffer_get_rest_len(buffer)) < 0) { + ssh_set_error_oom(session); + goto error; + } + } + channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return rc; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_REQUEST %s", request); + if (reply == 0) { + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + leave_function(); + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session,SSH_TIMEOUT_USER, ssh_channel_request_termination, channel); + if(session->session_state == SSH_SESSION_STATE_ERROR || rc == SSH_ERROR) { + channel->request_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + /* we received something */ + switch (channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + ssh_log(session, SSH_LOG_PROTOCOL, + "Channel request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + rc = SSH_AGAIN; + leave_function(); + return rc; + case SSH_CHANNEL_REQ_STATE_NONE: + /* Never reached */ + ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()"); + rc=SSH_ERROR; + break; + } + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return rc; +} + +/** + * @brief Request a pty with a specific type and size. + * + * @param[in] channel The channel to sent the request. + * + * @param[in] terminal The terminal type ("vt100, xterm,..."). + * + * @param[in] col The number of columns. + * + * @param[in] row The number of rows. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) { + ssh_session session; + ssh_string term = NULL; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if(terminal == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + enter_function(); +#ifdef WITH_SSH1 + if (channel->version==1) { + rc = channel_request_pty_size1(channel,terminal, col, row); + leave_function(); + return rc; + } +#endif + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + term = ssh_string_from_char(terminal); + if (term == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(buffer, term) < 0 || + buffer_add_u32(buffer, htonl(col)) < 0 || + buffer_add_u32(buffer, htonl(row)) < 0 || + buffer_add_u32(buffer, 0) < 0 || + buffer_add_u32(buffer, 0) < 0 || + buffer_add_u32(buffer, htonl(1)) < 0 || /* Add a 0byte string */ + buffer_add_u8(buffer, 0) < 0) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_request(channel, "pty-req", buffer, 1); +error: + ssh_buffer_free(buffer); + ssh_string_free(term); + + leave_function(); + return rc; +} + +/** + * @brief Request a PTY. + * + * @param[in] channel The channel to send the request. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @see channel_request_pty_size() + */ +int ssh_channel_request_pty(ssh_channel channel) { + return ssh_channel_request_pty_size(channel, "xterm", 80, 24); +} + +/** + * @brief Change the size of the terminal associated to a channel. + * + * @param[in] channel The channel to change the size. + * + * @param[in] cols The new number of columns. + * + * @param[in] rows The new number of rows. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @warning Do not call it from a signal handler if you are not sure any other + * libssh function using the same channel/session is running at same + * time (not 100% threadsafe). + */ +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) { + ssh_session session = channel->session; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + enter_function(); + +#ifdef WITH_SSH1 + if (channel->version == 1) { + rc = channel_change_pty_size1(channel,cols,rows); + leave_function(); + return rc; + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_u32(buffer, htonl(cols)) < 0 || + buffer_add_u32(buffer, htonl(rows)) < 0 || + buffer_add_u32(buffer, 0) < 0 || + buffer_add_u32(buffer, 0) < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_request(channel, "window-change", buffer, 0); +error: + ssh_buffer_free(buffer); + + leave_function(); + return rc; +} + +/** + * @brief Request a shell. + * + * @param[in] channel The channel to send the request. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_shell(ssh_channel channel) { + if(channel == NULL) { + return SSH_ERROR; + } +#ifdef WITH_SSH1 + if (channel->version == 1) { + return channel_request_shell1(channel); + } +#endif + return channel_request(channel, "shell", NULL, 1); +} + +/** + * @brief Request a subsystem (for example "sftp"). + * + * @param[in] channel The channel to send the request. + * + * @param[in] subsys The subsystem to request (for example "sftp"). + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @warning You normally don't have to call it for sftp, see sftp_new(). + */ +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) { + ssh_buffer buffer = NULL; + ssh_string subsystem = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(subsys == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + subsystem = ssh_string_from_char(subsys); + if (subsystem == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_ssh_string(buffer, subsystem) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "subsystem", buffer, 1); +error: + ssh_buffer_free(buffer); + ssh_string_free(subsystem); + + return rc; +} + +int ssh_channel_request_sftp( ssh_channel channel){ + if(channel == NULL) { + return SSH_ERROR; + } + return ssh_channel_request_subsystem(channel, "sftp"); +} + +static ssh_string generate_cookie(void) { + static const char *hex = "0123456789abcdef"; + char s[36]; + unsigned char rnd[16]; + int i; + + ssh_get_random(rnd,sizeof(rnd),0); + for (i = 0; i < 16; i++) { + s[i*2] = hex[rnd[i] & 0x0f]; + s[i*2+1] = hex[rnd[i] >> 4]; + } + s[32] = '\0'; + return ssh_string_from_char(s); +} + +/** + * @brief Sends the "x11-req" channel request over an existing session channel. + * + * This will enable redirecting the display of the remote X11 applications to + * local X server over an secure tunnel. + * + * @param[in] channel An existing session channel where the remote X11 + * applications are going to be executed. + * + * @param[in] single_connection A boolean to mark only one X11 app will be + * redirected. + * + * @param[in] protocol A x11 authentication protocol. Pass NULL to use the + * default value MIT-MAGIC-COOKIE-1. + * + * @param[in] cookie A x11 authentication cookie. Pass NULL to generate + * a random cookie. + * + * @param[in] screen_number The screen number. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number) { + ssh_buffer buffer = NULL; + ssh_string p = NULL; + ssh_string c = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + p = ssh_string_from_char(protocol ? protocol : "MIT-MAGIC-COOKIE-1"); + if (p == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (cookie) { + c = ssh_string_from_char(cookie); + } else { + c = generate_cookie(); + } + if (c == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_u8(buffer, single_connection == 0 ? 0 : 1) < 0 || + buffer_add_ssh_string(buffer, p) < 0 || + buffer_add_ssh_string(buffer, c) < 0 || + buffer_add_u32(buffer, htonl(screen_number)) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "x11-req", buffer, 1); + +error: + ssh_buffer_free(buffer); + ssh_string_free(p); + ssh_string_free(c); + return rc; +} + +static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, + int timeout_ms) { +#ifndef _WIN32 + static const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 50000000 /* 50ms */ + }; +#endif + ssh_message msg = NULL; + ssh_channel channel = NULL; + struct ssh_iterator *iterator; + int t; + + for (t = timeout_ms; t >= 0; t -= 50) + { + ssh_handle_packets(session, 50); + + if (session->ssh_message_list) { + iterator = ssh_list_get_iterator(session->ssh_message_list); + while (iterator) { + msg = (ssh_message)iterator->data; + if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(msg) == channeltype) { + ssh_list_remove(session->ssh_message_list, iterator); + channel = ssh_message_channel_request_open_reply_accept(msg); + ssh_message_free(msg); + return channel; + } + iterator = iterator->next; + } + } + if(t>0){ +#ifdef _WIN32 + Sleep(50); /* 50ms */ +#else + nanosleep(&ts, NULL); +#endif + } + } + + ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server"); + return NULL; +} + +/** + * @brief Accept an X11 forwarding channel. + * + * @param[in] channel An x11-enabled session channel. + * + * @param[in] timeout_ms Timeout in milliseconds. + * + * @return A newly created channel, or NULL if no X11 request from + * the server. + */ +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) { + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms); +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_success){ + (void)type; + (void)user; + (void)packet; + enter_function(); + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_REQUEST_SUCCESS"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_denied){ + (void)type; + (void)user; + (void)packet; + enter_function(); + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_REQUEST_FAILURE"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + + leave_function(); + return SSH_PACKET_USED; + +} + +static int ssh_global_request_termination(void *s){ + ssh_session session = (ssh_session) s; + if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING || + session->session_state != SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Send a global request (needed for forward listening) and wait for the + * result. + * + * @param[in] session The SSH session handle. + * + * @param[in] request The type of request (defined in RFC). + * + * @param[in] buffer Additional data to put in packet. + * + * @param[in] reply Set if you expect a reply from server. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + */ +static int global_request(ssh_session session, const char *request, + ssh_buffer buffer, int reply) { + ssh_string req = NULL; + int rc = SSH_ERROR; + + enter_function(); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + req = ssh_string_from_char(request); + if (req == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_GLOBAL_REQUEST) < 0 || + buffer_add_ssh_string(session->out_buffer, req) < 0 || + buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) { + ssh_set_error_oom(session); + goto error; + } + ssh_string_free(req); + req=NULL; + + if (buffer != NULL) { + if (buffer_add_data(session->out_buffer, buffer_get_rest(buffer), + buffer_get_rest_len(buffer)) < 0) { + ssh_set_error_oom(session); + goto error; + } + } + session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return rc; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent a SSH_MSG_GLOBAL_REQUEST %s", request); + if (reply == 0) { + session->global_req_state=SSH_CHANNEL_REQ_STATE_NONE; + leave_function(); + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_global_request_termination, session); + if(rc==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){ + session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + switch(session->global_req_state){ + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + ssh_log(session, SSH_LOG_PROTOCOL, "Global request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_log(session, SSH_LOG_PACKET, + "Global request %s failed", request); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Global request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_NONE: + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + rc=SSH_AGAIN; + break; + } + + leave_function(); + return rc; +error: + ssh_string_free(req); + leave_function(); + return rc; +} + +/** + * @brief Sends the "tcpip-forward" global request to ask the server to begin + * listening for inbound connections. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The address to bind to on the server. Pass NULL to bind + * to all available addresses on all protocol families + * supported by the server. + * + * @param[in] port The port to bind to on the server. Pass 0 to ask the + * server to allocate the next available unprivileged port + * number + * + * @param[in] bound_port The pointer to get actual bound port. Pass NULL to + * ignore. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + **/ +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) { + ssh_buffer buffer = NULL; + ssh_string addr = NULL; + int rc = SSH_ERROR; + uint32_t tmp; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + addr = ssh_string_from_char(address ? address : ""); + if (addr == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(buffer, addr) < 0 || + buffer_add_u32(buffer, htonl(port)) < 0) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = global_request(session, "tcpip-forward", buffer, 1); + + /* TODO: FIXME no guarantee the last packet we received contains + * that info */ + if (rc == SSH_OK && port == 0 && bound_port) { + buffer_get_u32(session->in_buffer, &tmp); + *bound_port = ntohl(tmp); + } + +error: + ssh_buffer_free(buffer); + ssh_string_free(addr); + return rc; +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel. + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + */ +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms); +} + +/** + * @brief Sends the "cancel-tcpip-forward" global request to ask the server to + * cancel the tcpip-forward request. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The bound address on the server. + * + * @param[in] port The bound port on the server. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + */ +int ssh_forward_cancel(ssh_session session, const char *address, int port) { + ssh_buffer buffer = NULL; + ssh_string addr = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + addr = ssh_string_from_char(address ? address : ""); + if (addr == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(buffer, addr) < 0 || + buffer_add_u32(buffer, htonl(port)) < 0) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = global_request(session, "cancel-tcpip-forward", buffer, 1); + +error: + ssh_buffer_free(buffer); + ssh_string_free(addr); + return rc; +} + +/** + * @brief Set environment variables. + * + * @param[in] channel The channel to set the environment variables. + * + * @param[in] name The name of the variable. + * + * @param[in] value The value to set. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * @warning Some environment variables may be refused by security reasons. + */ +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) { + ssh_buffer buffer = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(name == NULL || value == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + str = ssh_string_from_char(name); + if (str == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_ssh_string(buffer, str) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + ssh_string_free(str); + str = ssh_string_from_char(value); + if (str == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_ssh_string(buffer, str) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "env", buffer,1); +error: + ssh_buffer_free(buffer); + ssh_string_free(str); + + return rc; +} + +/** + * @brief Run a shell command without an interactive shell. + * + * This is similar to 'sh -c command'. + * + * @param[in] channel The channel to execute the command. + * + * @param[in] cmd The command to execute + * (e.g. "ls ~/ -al | grep -i reports"). + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * @code + * rc = channel_request_exec(channel, "ps aux"); + * if (rc > 0) { + * return -1; + * } + * + * while ((rc = channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + * if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) { + * return -1; + * } + * } + * @endcode + * + * @see channel_request_shell() + */ +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { + ssh_buffer buffer = NULL; + ssh_string command = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(cmd == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + +#ifdef WITH_SSH1 + if (channel->version == 1) { + return channel_request_exec1(channel, cmd); + } +#endif + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + command = ssh_string_from_char(cmd); + if (command == NULL) { + goto error; + ssh_set_error_oom(channel->session); + } + + if (buffer_add_ssh_string(buffer, command) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "exec", buffer, 1); +error: + ssh_buffer_free(buffer); + ssh_string_free(command); + return rc; +} + + +/** + * @brief Send a signal to remote process (as described in RFC 4254, section 6.9). + * + * Sends a signal 'sig' to the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * Only SSH-v2 is supported (I'm not sure about SSH-v1). + * + * OpenSSH doesn't support signals yet, see: + * https://bugzilla.mindrot.org/show_bug.cgi?id=1424 + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * \n\n + * SIGABRT -> ABRT \n + * SIGALRM -> ALRM \n + * SIGFPE -> FPE \n + * SIGHUP -> HUP \n + * SIGILL -> ILL \n + * SIGINT -> INT \n + * SIGKILL -> KILL \n + * SIGPIPE -> PIPE \n + * SIGQUIT -> QUIT \n + * SIGSEGV -> SEGV \n + * SIGTERM -> TERM \n + * SIGUSR1 -> USR1 \n + * SIGUSR2 -> USR2 \n + * + * @return SSH_OK on success, SSH_ERROR if an error occurred + * (including attempts to send signal via SSH-v1 session). + */ +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { + ssh_buffer buffer = NULL; + ssh_string encoded_signal = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(sig == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + +#ifdef WITH_SSH1 + if (channel->version == 1) { + return SSH_ERROR; // TODO: Add support for SSH-v1 if possible. + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + encoded_signal = ssh_string_from_char(sig); + if (encoded_signal == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_ssh_string(buffer, encoded_signal) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "signal", buffer, 0); +error: + ssh_buffer_free(buffer); + ssh_string_free(encoded_signal); + return rc; +} + + +/** + * @brief Read data from a channel into a buffer. + * + * @param[in] channel The channel to read from. + * + * @param[in] buffer The buffer which will get the data. + * + * @param[in] count The count of bytes to be read. If it is bigger than 0, + * the exact size will be read, else (bytes=0) it will + * return once anything is available. + * + * @param is_stderr A boolean value to mark reading from the stderr stream. + * + * @return The number of bytes read, 0 on end of file or SSH_ERROR + * on error. + * @deprecated Please use ssh_channel_read instead + * @warning This function doesn't work in nonblocking/timeout mode + * @see ssh_channel_read + */ +int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr) { + ssh_session session; + char buffer_tmp[8192]; + int r; + uint32_t total=0; + + if(channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if(buffer == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + enter_function(); + buffer_reinit(buffer); + if(count==0){ + do { + r=ssh_channel_poll(channel, is_stderr); + if(r < 0){ + leave_function(); + return r; + } + if(r > 0){ + r=ssh_channel_read(channel, buffer_tmp, r, is_stderr); + if(r < 0){ + leave_function(); + return r; + } + if(buffer_add_data(buffer,buffer_tmp,r) < 0){ + ssh_set_error_oom(session); + r = SSH_ERROR; + } + leave_function(); + return r; + } + if(ssh_channel_is_eof(channel)){ + leave_function(); + return 0; + } + ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); + } while (r == 0); + } + while(total < count){ + r=ssh_channel_read(channel, buffer_tmp, sizeof(buffer_tmp), is_stderr); + if(r<0){ + leave_function(); + return r; + } + if(r==0){ + leave_function(); + return total; + } + if(buffer_add_data(buffer,buffer_tmp,r) < 0){ + ssh_set_error_oom(session); + leave_function(); + return SSH_ERROR; + } + total += r; + } + leave_function(); + return total; +} + +struct ssh_channel_read_termination_struct { + ssh_channel channel; + uint32_t count; + ssh_buffer buffer; +}; + +static int ssh_channel_read_termination(void *s){ + struct ssh_channel_read_termination_struct *ctx = s; + if (buffer_get_rest_len(ctx->buffer) >= ctx->count || + ctx->channel->remote_eof || + ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/* TODO FIXME Fix the delayed close thing */ +/* TODO FIXME Fix the blocking behaviours */ + +/** + * @brief Reads data from a channel. + * + * @param[in] channel The channel to read from. + * + * @param[in] dest The destination buffer which will get the data. + * + * @param[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @return The number of bytes read, 0 on end of file or SSH_ERROR + * on error. Can return 0 if nothing is available in nonblocking + * mode. + * + * @warning This function may return less than count bytes of data, and won't + * block until count bytes have been read. + * @warning The read function using a buffer has been renamed to + * channel_read_buffer(). + */ +int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) { + ssh_session session; + ssh_buffer stdbuf; + uint32_t len; + struct ssh_channel_read_termination_struct ctx; + int rc; + + if(channel == NULL) { + return SSH_ERROR; + } + if(dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + enter_function(); + + if (count == 0) { + leave_function(); + return 0; + } + + if (is_stderr) { + stdbuf=channel->stderr_buffer; + } + + /* + * We may have problem if the window is too small to accept as much data + * as asked + */ + ssh_log(session, SSH_LOG_PROTOCOL, + "Read (%d) buffered : %d bytes. Window: %d", + count, + buffer_get_rest_len(stdbuf), + channel->local_window); + + if (count > buffer_get_rest_len(stdbuf) + channel->local_window) { + if (grow_window(session, channel, count - buffer_get_rest_len(stdbuf)) < 0) { + leave_function(); + return -1; + } + } + + /* block reading until all bytes are read + * and ignore the trivial case count=0 + */ + ctx.channel = channel; + ctx.buffer = stdbuf; + ctx.count = count; + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_channel_read_termination, &ctx); + if (rc == SSH_ERROR){ + leave_function(); + return rc; + } + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + leave_function(); + return SSH_ERROR; + } + if (channel->remote_eof && buffer_get_rest_len(stdbuf) == 0) { + leave_function(); + return 0; + } + len = buffer_get_rest_len(stdbuf); + /* Read count bytes if len is greater, everything otherwise */ + len = (len > count ? count : len); + memcpy(dest, buffer_get_rest(stdbuf), len); + buffer_pass_bytes(stdbuf,len); + /* Authorize some buffering while userapp is busy */ + if (channel->local_window < WINDOWLIMIT) { + if (grow_window(session, channel, 0) < 0) { + leave_function(); + return -1; + } + } + + leave_function(); + return len; +} + +/** + * @brief Do a nonblocking read on the channel. + * + * A nonblocking read on the specified channel. it will return <= count bytes of + * data read atomically. + * + * @param[in] channel The channel to read from. + * + * @param[in] dest A pointer to a destination buffer. + * + * @param[in] count The count of bytes of data to be read. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes read, 0 if nothing is available or + * SSH_ERROR on error. + * + * @warning Don't forget to check for EOF as it would return 0 here. + * + * @see channel_is_eof() + */ +int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr) { + ssh_session session; + int to_read; + int rc; + int blocking; + + if(channel == NULL) { + return SSH_ERROR; + } + if(dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + enter_function(); + + to_read = ssh_channel_poll(channel, is_stderr); + + if (to_read <= 0) { + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + leave_function(); + return SSH_ERROR; + } + leave_function(); + return to_read; /* may be an error code */ + } + + if (to_read > (int)count) { + to_read = (int)count; + } + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + rc = ssh_channel_read(channel, dest, to_read, is_stderr); + ssh_set_blocking(session,blocking); + leave_function(); + return rc; +} + +/** + * @brief Polls a channel for data to read. + * + * @param[in] channel The channel to poll. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, 0 if nothing + * is available or SSH_ERROR on error. + * + * @warning When the channel is in EOF state, the function returns SSH_EOF. + * + * @see channel_is_eof() + */ +int ssh_channel_poll(ssh_channel channel, int is_stderr){ + ssh_session session; + ssh_buffer stdbuf; + + if(channel == NULL) { + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + enter_function(); + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + if (buffer_get_rest_len(stdbuf) == 0 && channel->remote_eof == 0) { + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + leave_function(); + return SSH_ERROR; + } + if (ssh_handle_packets(channel->session, SSH_TIMEOUT_NONBLOCKING)==SSH_ERROR) { + leave_function(); + return SSH_ERROR; + } + } + + if (buffer_get_rest_len(stdbuf) > 0){ + leave_function(); + return buffer_get_rest_len(stdbuf); + } + + if (channel->remote_eof) { + leave_function(); + return SSH_EOF; + } + + leave_function(); + return buffer_get_rest_len(stdbuf); +} + +/** + * @brief Polls a channel for data to read, waiting for a certain timeout. + * + * @param[in] channel The channel to poll. + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, + * 0 if nothing is available (timeout elapsed), + * SSH_EOF on end of file, + * SSH_ERROR on error. + * + * @warning When the channel is in EOF state, the function returns SSH_EOF. + * + * @see channel_is_eof() + */ +int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr){ + ssh_session session; + ssh_buffer stdbuf; + struct ssh_channel_read_termination_struct ctx; + int rc; + + if(channel == NULL) { + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + enter_function(); + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + ctx.buffer = stdbuf; + ctx.channel = channel; + ctx.count = 1; + rc = ssh_handle_packets_termination(channel->session, timeout, + ssh_channel_read_termination, &ctx); + if(rc ==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){ + rc = SSH_ERROR; + goto end; + } + rc = buffer_get_rest_len(stdbuf); + if(rc > 0) + goto end; + if (channel->remote_eof) + rc = SSH_EOF; +end: + leave_function(); + return rc; +} + +/** + * @brief Recover the session in which belongs a channel. + * + * @param[in] channel The channel to recover the session from. + * + * @return The session pointer. + */ +ssh_session ssh_channel_get_session(ssh_channel channel) { + if(channel == NULL) { + return NULL; + } + + return channel->session; +} + +static int ssh_channel_exit_status_termination(void *c){ + ssh_channel channel = c; + if(channel->exit_status != -1 || + /* When a channel is closed, no exit status message can + * come anymore */ + (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Get the exit status of the channel (error code from the executed + * instruction). + * + * @param[in] channel The channel to get the status from. + * + * @returns The exit status, -1 if no exit status has been returned + * (yet). + * @warning This function may block until a timeout (or never) + * if the other side is not willing to close the channel. + */ +int ssh_channel_get_exit_status(ssh_channel channel) { + int rc; + if(channel == NULL) { + return SSH_ERROR; + } + rc = ssh_handle_packets_termination(channel->session, SSH_TIMEOUT_USER, + ssh_channel_exit_status_termination, channel); + if (rc == SSH_ERROR || channel->session->session_state == + SSH_SESSION_STATE_ERROR) + return SSH_ERROR; + return channel->exit_status; +} + +/* + * This function acts as a meta select. + * + * First, channels are analyzed to seek potential can-write or can-read ones, + * then if no channel has been elected, it goes in a loop with the posix + * select(2). + * This is made in two parts: protocol select and network select. The protocol + * select does not use the network functions at all + */ +static int channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, ssh_channel *wout, ssh_channel *eout) { + ssh_channel chan; + int i; + int j = 0; + + for (i = 0; rchans[i] != NULL; i++) { + chan = rchans[i]; + + while (ssh_channel_is_open(chan) && ssh_socket_data_available(chan->session->socket)) { + ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING); + } + + if ((chan->stdout_buffer && buffer_get_rest_len(chan->stdout_buffer) > 0) || + (chan->stderr_buffer && buffer_get_rest_len(chan->stderr_buffer) > 0) || + chan->remote_eof) { + rout[j] = chan; + j++; + } + } + rout[j] = NULL; + + j = 0; + for(i = 0; wchans[i] != NULL; i++) { + chan = wchans[i]; + /* It's not our business to seek if the file descriptor is writable */ + if (ssh_socket_data_writable(chan->session->socket) && + ssh_channel_is_open(chan) && (chan->remote_window > 0)) { + wout[j] = chan; + j++; + } + } + wout[j] = NULL; + + j = 0; + for (i = 0; echans[i] != NULL; i++) { + chan = echans[i]; + + if (!ssh_socket_is_open(chan->session->socket) || ssh_channel_is_closed(chan)) { + eout[j] = chan; + j++; + } + } + eout[j] = NULL; + + return 0; +} + +/* Just count number of pointers in the array */ +static int count_ptrs(ssh_channel *ptrs) { + int c; + for (c = 0; ptrs[c] != NULL; c++) + ; + + return c; +} + +/** + * @brief Act like the standard select(2) on channels. + * + * The list of pointers are then actualized and will only contain pointers to + * channels that are respectively readable, writable or have an exception to + * trap. + * + * @param[in] readchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] writechans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] exceptchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] timeout Timeout as defined by select(2). + * + * @return SSH_OK on a successful operation, SSH_EINTR if the + * select(2) syscall was interrupted, then relaunch the + * function. + */ +int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, + ssh_channel *exceptchans, struct timeval * timeout) { + ssh_channel *rchans, *wchans, *echans; + ssh_channel dummy = NULL; + ssh_event event = NULL; + int rc; + int i; + int tm, tm_base; + int firstround=1; + struct ssh_timestamp ts; + + if (timeout != NULL) + tm_base = timeout->tv_sec * 1000 + timeout->tv_usec/1000; + else + tm_base = SSH_TIMEOUT_INFINITE; + ssh_timestamp_init(&ts); + tm = tm_base; + /* don't allow NULL pointers */ + if (readchans == NULL) { + readchans = &dummy; + } + + if (writechans == NULL) { + writechans = &dummy; + } + + if (exceptchans == NULL) { + exceptchans = &dummy; + } + + if (readchans[0] == NULL && writechans[0] == NULL && exceptchans[0] == NULL) { + /* No channel to poll?? Go away! */ + return 0; + } + + /* Prepare the outgoing temporary arrays */ + rchans = malloc(sizeof(ssh_channel ) * (count_ptrs(readchans) + 1)); + if (rchans == NULL) { + return SSH_ERROR; + } + + wchans = malloc(sizeof(ssh_channel ) * (count_ptrs(writechans) + 1)); + if (wchans == NULL) { + SAFE_FREE(rchans); + return SSH_ERROR; + } + + echans = malloc(sizeof(ssh_channel ) * (count_ptrs(exceptchans) + 1)); + if (echans == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + return SSH_ERROR; + } + + /* + * First, try without doing network stuff then, use the ssh_poll + * infrastructure to poll on all sessions. + */ + do { + channel_protocol_select(readchans, writechans, exceptchans, + rchans, wchans, echans); + if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { + /* At least one channel has an event */ + break; + } + /* Add all channels' sessions right into an event object */ + if (event == NULL) { + event = ssh_event_new(); + if (event == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + + return SSH_ERROR; + } + for (i = 0; readchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(readchans[i]->session); + ssh_event_add_session(event, readchans[i]->session); + } + for (i = 0; writechans[i] != NULL; i++) { + ssh_poll_get_default_ctx(writechans[i]->session); + ssh_event_add_session(event, writechans[i]->session); + } + for (i = 0; exceptchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(exceptchans[i]->session); + ssh_event_add_session(event, exceptchans[i]->session); + } + } + /* Get out if the timeout has elapsed */ + if (!firstround && ssh_timeout_elapsed(&ts, tm_base)){ + break; + } + /* Here we go */ + rc = ssh_event_dopoll(event,tm); + if (rc != SSH_OK){ + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + ssh_event_free(event); + return rc; + } + tm = ssh_timeout_update(&ts, tm_base); + firstround=0; + } while(1); + + memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel )); + memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel )); + memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel )); + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + if(event) + ssh_event_free(event); + return 0; +} + +#if WITH_SERVER +/** + * @brief Blocking write on a channel stderr. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @see channel_read() + */ +int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { + return channel_write_common(channel, data, len, 1); +} + +/** + * @brief Open a TCP/IP reverse forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The source host (your local computer). It's optional + * and for logging purpose. + * + * @param[in] localport The source port (your local computer). It's optional + * and for logging purpose. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not automatically + * forward the content of a socket to the channel. You still have to + * use channel_read and channel_write for this. + */ +int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) { + ssh_session session; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + + session = channel->session; + + enter_function(); + if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + str = ssh_string_from_char(remotehost); + if (str == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(remoteport)) < 0) { + ssh_set_error_oom(session); + goto error; + } + + ssh_string_free(str); + str = ssh_string_from_char(sourcehost); + if (str == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(localport)) < 0) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "forwarded-tcpip", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + ssh_buffer_free(payload); + ssh_string_free(str); + + leave_function(); + return rc; +} + +/** + * @brief Open a X11 channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] orig_addr The source host (the local server). + * + * @param[in] orig_port The source port (the local server). + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * @warning This function does not bind the local port and does not automatically + * forward the content of a socket to the channel. You still have to + * use channel_read and channel_write for this. + */ +int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port) { + ssh_session session; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(orig_addr == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + session = channel->session; + + enter_function(); + + if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + str = ssh_string_from_char(orig_addr); + if (str == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(orig_port)) < 0) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "x11", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + ssh_buffer_free(payload); + ssh_string_free(str); + + leave_function(); + return rc; +} + +/** + * @brief Send the exit status to the remote process (as described in RFC 4254, section 6.10). + * + * Sends the exit status to the remote process. + * Only SSH-v2 is supported (I'm not sure about SSH-v1). + * + * @param[in] channel The channel to send exit status. + * + * @param[in] sig The exit status to send + * + * @return SSH_OK on success, SSH_ERROR if an error occurred + * (including attempts to send exit status via SSH-v1 session). + */ +int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + +#ifdef WITH_SSH1 + if (channel->version == 1) { + return SSH_ERROR; // TODO: Add support for SSH-v1 if possible. + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_u32(buffer, ntohl(exit_status)) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-status", buffer, 0); +error: + ssh_buffer_free(buffer); + return rc; +} + +/** + * @brief Send an exit signal to remote process (as described in RFC 4254, section 6.10). + * + * Sends a signal 'sig' to the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * Only SSH-v2 is supported (I'm not sure about SSH-v1). + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * (e.g. "TERM" or "KILL"). + * @param[in] core A boolean to tell if a core was dumped + * @param[in] errmsg A CRLF explanation text about the error condition + * @param[in] lang The language used in the message (format: RFC 3066) + * + * @return SSH_OK on success, SSH_ERROR if an error occurred + * (including attempts to send signal via SSH-v1 session). + */ +int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, + int core, const char *errmsg, const char *lang) { + ssh_buffer buffer = NULL; + ssh_string tmp = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(sig == NULL || errmsg == NULL || lang == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } +#ifdef WITH_SSH1 + if (channel->version == 1) { + return SSH_ERROR; // TODO: Add support for SSH-v1 if possible. + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + tmp = ssh_string_from_char(sig); + if (tmp == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + if (buffer_add_ssh_string(buffer, tmp) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (buffer_add_u8(buffer, core?1:0) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + ssh_string_free(tmp); + tmp = ssh_string_from_char(errmsg); + if (tmp == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + if (buffer_add_ssh_string(buffer, tmp) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + ssh_string_free(tmp); + tmp = ssh_string_from_char(lang); + if (tmp == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + if (buffer_add_ssh_string(buffer, tmp) < 0) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "signal", buffer, 0); +error: + ssh_buffer_free(buffer); + if(tmp) + ssh_string_free(tmp); + return rc; +} + +#endif + +/* @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/channels1.c b/libssh/src/channels1.c new file mode 100644 index 00000000..5d0158d2 --- /dev/null +++ b/libssh/src/channels1.c @@ -0,0 +1,385 @@ +/* + * channels1.c - Support for SSH-1 type channels + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 +#include +#include +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh1.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" + +#ifdef WITH_SSH1 + +/* + * This is a big hack. In fact, SSH1 doesn't make a clever use of channels. + * The whole packets concerning shells are sent outside of a channel. + * Thus, an inside limitation of this behavior is that you can't only + * request one shell. + * The question is still how they managed to embed two "channel" into one + * protocol. + */ + +int channel_open_session1(ssh_channel chan) { + ssh_session session; + + if (chan == NULL) { + return -1; + } + session = chan->session; + + /* + * We guess we are requesting an *exec* channel. It can only have one exec + * channel. So we abort with an error if we need more than one. + */ + if (session->exec_channel_opened) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "SSH1 supports only one execution channel. " + "One has already been opened"); + return -1; + } + session->exec_channel_opened = 1; + chan->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED; + chan->state = SSH_CHANNEL_STATE_OPEN; + chan->local_maxpacket = 32000; + chan->local_window = 64000; + ssh_log(session, SSH_LOG_PACKET, "Opened a SSH1 channel session"); + + return 0; +} + +/* 10 SSH_CMSG_REQUEST_PTY + * + * string TERM environment variable value (e.g. vt100) + * 32-bit int terminal height, rows (e.g., 24) + * 32-bit int terminal width, columns (e.g., 80) + * 32-bit int terminal width, pixels (0 if no graphics) (e.g., 480) + * 32-bit int terminal height, pixels (0 if no graphics) (e.g., 640) + * n bytes tty modes encoded in binary + * Some day, someone should have a look at that nasty tty encoded. It's + * much simplier under ssh2. I just hope the defaults values are ok ... + */ + +int channel_request_pty_size1(ssh_channel channel, const char *terminal, int col, + int row) { + ssh_session session; + ssh_string str = NULL; + + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Wrong request state"); + return SSH_ERROR; + } + str = ssh_string_from_char(terminal); + if (str == NULL) { + ssh_set_error_oom(session); + return -1; + } + + if (buffer_add_u8(session->out_buffer, SSH_CMSG_REQUEST_PTY) < 0 || + buffer_add_ssh_string(session->out_buffer, str) < 0) { + ssh_string_free(str); + return -1; + } + ssh_string_free(str); + + if (buffer_add_u32(session->out_buffer, ntohl(row)) < 0 || + buffer_add_u32(session->out_buffer, ntohl(col)) < 0 || + buffer_add_u32(session->out_buffer, 0) < 0 || /* x */ + buffer_add_u32(session->out_buffer, 0) < 0 || /* y */ + buffer_add_u8(session->out_buffer, 0) < 0) { /* tty things */ + return -1; + } + + ssh_log(session, SSH_LOG_FUNCTIONS, "Opening a ssh1 pty"); + channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (packet_send(session) == SSH_ERROR) { + return -1; + } + + while (channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING) { + ssh_handle_packets(session, SSH_TIMEOUT_INFINITE); + } + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_PENDING: + case SSH_CHANNEL_REQ_STATE_NONE: + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + return SSH_ERROR; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + ssh_log(session, SSH_LOG_RARE, "PTY: Success"); + return SSH_OK; + case SSH_CHANNEL_REQ_STATE_DENIED: + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + ssh_set_error(session, SSH_REQUEST_DENIED, + "Server denied PTY allocation"); + ssh_log(session, SSH_LOG_RARE, "PTY: denied\n"); + return SSH_ERROR; + } + // Not reached + return SSH_ERROR; +} + +int channel_change_pty_size1(ssh_channel channel, int cols, int rows) { + ssh_session session; + + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Wrong request state"); + return SSH_ERROR; + } + if (buffer_add_u8(session->out_buffer, SSH_CMSG_WINDOW_SIZE) < 0 || + buffer_add_u32(session->out_buffer, ntohl(rows)) < 0 || + buffer_add_u32(session->out_buffer, ntohl(cols)) < 0 || + buffer_add_u32(session->out_buffer, 0) < 0 || + buffer_add_u32(session->out_buffer, 0) < 0) { + return SSH_ERROR; + } + channel->request_state=SSH_CHANNEL_REQ_STATE_PENDING; + if (packet_send(session) == SSH_ERROR) { + return SSH_ERROR; + } + + ssh_log(session, SSH_LOG_PROTOCOL, "Change pty size send"); + while(channel->request_state==SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_handle_packets(session, SSH_TIMEOUT_INFINITE); + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_PENDING: + case SSH_CHANNEL_REQ_STATE_NONE: + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + return SSH_ERROR; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + ssh_log(session, SSH_LOG_PROTOCOL, "pty size changed"); + return SSH_OK; + case SSH_CHANNEL_REQ_STATE_DENIED: + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + ssh_log(session, SSH_LOG_RARE, "pty size change denied"); + ssh_set_error(session, SSH_REQUEST_DENIED, "pty size change denied"); + return SSH_ERROR; + } + // Not reached + return SSH_ERROR; + +} + +int channel_request_shell1(ssh_channel channel) { + ssh_session session; + + if (channel == NULL) { + return -1; + } + session = channel->session; + + if (buffer_add_u8(session->out_buffer,SSH_CMSG_EXEC_SHELL) < 0) { + return -1; + } + + if (packet_send(session) == SSH_ERROR) { + return -1; + } + + ssh_log(session, SSH_LOG_RARE, "Launched a shell"); + + return 0; +} + +int channel_request_exec1(ssh_channel channel, const char *cmd) { + ssh_session session; + ssh_string command = NULL; + + if (channel == NULL) { + return -1; + } + session = channel->session; + + command = ssh_string_from_char(cmd); + if (command == NULL) { + return -1; + } + + if (buffer_add_u8(session->out_buffer, SSH_CMSG_EXEC_CMD) < 0 || + buffer_add_ssh_string(session->out_buffer, command) < 0) { + ssh_string_free(command); + return -1; + } + ssh_string_free(command); + + if(packet_send(session) == SSH_ERROR) { + return -1; + } + + ssh_log(session, SSH_LOG_RARE, "Executing %s ...", cmd); + + return 0; +} + +SSH_PACKET_CALLBACK(ssh_packet_data1){ + ssh_channel channel = ssh_get_channel1(session); + ssh_string str = NULL; + int is_stderr=(type==SSH_SMSG_STDOUT_DATA ? 0 : 1); + (void)user; + + if (channel == NULL) { + return SSH_PACKET_NOT_USED; + } + + str = buffer_get_ssh_string(packet); + if (str == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "Invalid data packet !\n"); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PROTOCOL, + "Adding %" PRIdS " bytes data in %d", + ssh_string_len(str), is_stderr); + + if (channel_default_bufferize(channel, ssh_string_data(str), ssh_string_len(str), + is_stderr) < 0) { + ssh_string_free(str); + return SSH_PACKET_USED; + } + ssh_string_free(str); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_close1){ + ssh_channel channel = ssh_get_channel1(session); + uint32_t status; + (void)type; + (void)user; + + if (channel == NULL) { + return SSH_PACKET_NOT_USED; + } + + buffer_get_u32(packet, &status); + /* + * It's much more than a channel closing. spec says it's the last + * message sent by server (strange) + */ + + /* actually status is lost somewhere */ + channel->state = SSH_CHANNEL_STATE_CLOSED; + channel->remote_eof = 1; + + buffer_add_u8(session->out_buffer, SSH_CMSG_EXIT_CONFIRMATION); + packet_send(session); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_exist_status1){ + ssh_channel channel = ssh_get_channel1(session); + uint32_t status; + (void)type; + (void)user; + + if (channel == NULL) { + return SSH_PACKET_NOT_USED; + } + + buffer_get_u32(packet, &status); + channel->state = SSH_CHANNEL_STATE_CLOSED; + channel->remote_eof = 1; + channel->exit_status = ntohl(status); + + return SSH_PACKET_USED; +} + + +int channel_write1(ssh_channel channel, const void *data, int len) { + ssh_session session; + int origlen = len; + int effectivelen; + const unsigned char *ptr=data; + + if (channel == NULL) { + return -1; + } + session = channel->session; + + while (len > 0) { + if (buffer_add_u8(session->out_buffer, SSH_CMSG_STDIN_DATA) < 0) { + return -1; + } + + effectivelen = len > 32000 ? 32000 : len; + + if (buffer_add_u32(session->out_buffer, htonl(effectivelen)) < 0 || + buffer_add_data(session->out_buffer, ptr, effectivelen) < 0) { + return -1; + } + + ptr += effectivelen; + len -= effectivelen; + + if (packet_send(session) == SSH_ERROR) { + return -1; + } + ssh_handle_packets(session, SSH_TIMEOUT_NONBLOCKING); + } + if (ssh_blocking_flush(session,SSH_TIMEOUT_USER) == SSH_ERROR) + return -1; + return origlen; +} + +ssh_channel ssh_get_channel1(ssh_session session){ + struct ssh_iterator *it; + + if (session == NULL) { + return NULL; + } + + /* With SSH1, the channel is always the first one */ + if(session->channels != NULL){ + it = ssh_list_get_iterator(session->channels); + if(it) + return ssh_iterator_value(ssh_channel, it); + } + return NULL; +} +#endif /* WITH_SSH1 */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/client.c b/libssh/src/client.c new file mode 100644 index 00000000..ac1b83db --- /dev/null +++ b/libssh/src/client.c @@ -0,0 +1,696 @@ +/* + * client.c - SSH client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/options.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/dh.h" +#include "libssh/ecdh.h" +#include "libssh/threads.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/kex.h" + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @internal + * @brief Callback to be called when the socket is connected or had a + * connection error. Changes the state of the session and updates the error + * message. + * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR + * @param user is a pointer to session + */ +static void socket_callback_connected(int code, int errno_code, void *user){ + ssh_session session=(ssh_session)user; + enter_function(); + if(session->session_state != SSH_SESSION_STATE_CONNECTING){ + ssh_set_error(session,SSH_FATAL, "Wrong state in socket_callback_connected : %d", + session->session_state); + leave_function(); + return; + } + ssh_log(session,SSH_LOG_RARE,"Socket connection callback: %d (%d)",code, errno_code); + if(code == SSH_SOCKET_CONNECTED_OK) + session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; + else { + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"%s",strerror(errno_code)); + } + session->ssh_connection_callback(session); + leave_function(); +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +static int callback_receive_banner(const void *data, size_t len, void *user) { + char *buffer = (char *)data; + ssh_session session=(ssh_session) user; + char *str = NULL; + size_t i; + int ret=0; + enter_function(); + if(session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED){ + ssh_set_error(session,SSH_FATAL,"Wrong state in callback_receive_banner : %d",session->session_state); + leave_function(); + return SSH_ERROR; + } + for(i=0;ipcap_ctx && buffer[i] == '\n'){ + ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_IN,buffer,i+1,i+1); + } +#endif + if(buffer[i]=='\r') + buffer[i]='\0'; + if(buffer[i]=='\n'){ + buffer[i]='\0'; + str=strdup(buffer); + /* number of bytes read */ + ret=i+1; + session->serverbanner=str; + session->session_state=SSH_SESSION_STATE_BANNER_RECEIVED; + ssh_log(session,SSH_LOG_PACKET,"Received banner: %s",str); + session->ssh_connection_callback(session); + leave_function(); + return ret; + } + if(i>127){ + /* Too big banner */ + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"Receiving banner: too large banner"); + leave_function(); + return 0; + } + } + leave_function(); + return ret; +} + +/** @internal + * @brief Sends a SSH banner to the server. + * + * @param session The SSH session to use. + * + * @param server Send client or server banner. + * + * @return 0 on success, < 0 on error. + */ +int ssh_send_banner(ssh_session session, int server) { + const char *banner = NULL; + char buffer[128] = {0}; + int err=SSH_ERROR; + + enter_function(); + + banner = session->version == 1 ? CLIENTBANNER1 : CLIENTBANNER2; + + if (server) { + session->serverbanner = strdup(banner); + if (session->serverbanner == NULL) { + goto end; + } + } else { + session->clientbanner = strdup(banner); + if (session->clientbanner == NULL) { + goto end; + } + } + + snprintf(buffer, 128, "%s\n", banner); + + if (ssh_socket_write(session->socket, buffer, strlen(buffer)) == SSH_ERROR) { + goto end; + } +#ifdef WITH_PCAP + if(session->pcap_ctx) + ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT,buffer,strlen(buffer),strlen(buffer)); +#endif + err=SSH_OK; +end: + leave_function(); + return err; +} + +/** @internal + * @brief launches the DH handshake state machine + * @param session session handle + * @returns SSH_OK or SSH_ERROR + * @warning this function returning is no proof that DH handshake is + * completed + */ +static int dh_handshake(ssh_session session) { + + int rc = SSH_AGAIN; + + enter_function(); + + switch (session->dh_handshake_state) { + case DH_STATE_INIT: + switch(session->next_crypto->kex_type){ + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + rc = ssh_client_dh_init(session); + break; +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + rc = ssh_client_ecdh_init(session); + break; +#endif + default: + rc=SSH_ERROR; + goto error; + } + + if (rc == SSH_ERROR) { + goto error; + } + + session->dh_handshake_state = DH_STATE_INIT_SENT; + case DH_STATE_INIT_SENT: + /* wait until ssh_packet_dh_reply is called */ + break; + case DH_STATE_NEWKEYS_SENT: + /* wait until ssh_packet_newkeys is called */ + break; + case DH_STATE_FINISHED: + leave_function(); + return SSH_OK; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state in dh_handshake(): %d", + session->dh_handshake_state); + leave_function(); + return SSH_ERROR; + } +error: + leave_function(); + return rc; +} + +static int ssh_service_request_termination(void *s){ + ssh_session session = (ssh_session)s; + if(session->session_state == SSH_SESSION_STATE_ERROR || + session->auth_service_state != SSH_AUTH_SERVICE_SENT) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Request a service from the SSH server. + * + * Service requests are for example: ssh-userauth, ssh-connection, etc. + * + * @param session The session to use to ask for a service request. + * @param service The service request. + * + * @return SSH_OK on success + * @return SSH_ERROR on error + * @return SSH_AGAIN No response received yet + * @bug actually only works with ssh-userauth + */ +int ssh_service_request(ssh_session session, const char *service) { + ssh_string service_s = NULL; + int rc=SSH_ERROR; + enter_function(); + if(session->auth_service_state != SSH_AUTH_SERVICE_NONE) + goto pending; + if (buffer_add_u8(session->out_buffer, SSH2_MSG_SERVICE_REQUEST) < 0) { + goto error; + } + service_s = ssh_string_from_char(service); + if (service_s == NULL) { + goto error; + } + + if (buffer_add_ssh_string(session->out_buffer,service_s) < 0) { + ssh_string_free(service_s); + goto error; + } + ssh_string_free(service_s); + session->auth_service_state=SSH_AUTH_SERVICE_SENT; + if (packet_send(session) == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, + "Sending SSH2_MSG_SERVICE_REQUEST failed."); + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent SSH_MSG_SERVICE_REQUEST (service %s)", service); +pending: + rc=ssh_handle_packets_termination(session,SSH_TIMEOUT_USER, + ssh_service_request_termination, session); + if(rc == SSH_ERROR) + goto error; + switch(session->auth_service_state){ + case SSH_AUTH_SERVICE_DENIED: + ssh_set_error(session,SSH_FATAL,"ssh_auth_service request denied"); + break; + case SSH_AUTH_SERVICE_ACCEPTED: + rc=SSH_OK; + break; + case SSH_AUTH_SERVICE_SENT: + rc=SSH_AGAIN; + break; + case SSH_AUTH_SERVICE_NONE: + case SSH_AUTH_SERVICE_USER_SENT: + /* Invalid state, SSH1 specific */ + rc=SSH_ERROR; + break; + } +error: + leave_function(); + return rc; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +static void ssh_client_connection_callback(ssh_session session){ + int ssh1,ssh2; + enter_function(); + switch(session->session_state){ + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + case SSH_SESSION_STATE_SOCKET_CONNECTED: + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->serverbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + ssh_log(session, SSH_LOG_RARE, + "SSH server banner: %s", session->serverbanner); + + /* Here we analyze the different protocols the server allows. */ + if (ssh_analyze_banner(session, 0, &ssh1, &ssh2) < 0) { + goto error; + } + /* Here we decide which version of the protocol to use. */ + if (ssh2 && session->opts.ssh2) { + session->version = 2; +#ifdef WITH_SSH1 + } else if(ssh1 && session->opts.ssh1) { + session->version = 1; +#endif + } else if(ssh1 && !session->opts.ssh1){ +#ifdef WITH_SSH1 + ssh_set_error(session, SSH_FATAL, + "SSH-1 protocol not available (configure session to allow SSH-1)"); + goto error; +#else + ssh_set_error(session, SSH_FATAL, + "SSH-1 protocol not available (libssh compiled without SSH-1 support)"); + goto error; +#endif + } else { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->serverbanner); + goto error; + } + /* from now, the packet layer is handling incoming packets */ + if(session->version==2) + session->socket_callbacks.data=ssh_packet_socket_callback; +#ifdef WITH_SSH1 + else + session->socket_callbacks.data=ssh_packet_socket_callback1; +#endif + ssh_packet_set_default_callbacks(session); + session->session_state=SSH_SESSION_STATE_INITIAL_KEX; + ssh_send_banner(session, 0); + set_status(session, 0.5f); + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ +#ifdef WITH_SSH1 + if(session->version==1){ + if (ssh_get_kex1(session) < 0) + goto error; + set_status(session,0.6f); + session->connected = 1; + break; + } +#endif + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session,0.6f); + ssh_list_kex(session, &session->next_crypto->server_kex); + if (set_client_kex(session) < 0) { + goto error; + } + if (ssh_kex_select_methods(session) == SSH_ERROR) + goto error; + if (ssh_send_kex(session, 0) < 0) { + goto error; + } + set_status(session,0.8f); + session->session_state=SSH_SESSION_STATE_DH; + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + case SSH_SESSION_STATE_DH: + if(session->dh_handshake_state==DH_STATE_FINISHED){ + set_status(session,1.0f); + session->connected = 1; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + else + session->session_state=SSH_SESSION_STATE_AUTHENTICATING; + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); + } + leave_function(); + return; + error: + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state=SSH_SESSION_STATE_ERROR; + leave_function(); +} + +/** @internal + * @brief describe under which conditions the ssh_connect function may stop + */ +static int ssh_connect_termination(void *user){ + ssh_session session = (ssh_session)user; + switch(session->session_state){ + case SSH_SESSION_STATE_ERROR: + case SSH_SESSION_STATE_AUTHENTICATING: + case SSH_SESSION_STATE_DISCONNECTED: + return 1; + default: + return 0; + } +} + +/** + * @brief Connect to the ssh server. + * + * @param[in] session The ssh session to connect. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + * + * @see ssh_new() + * @see ssh_disconnect() + */ +int ssh_connect(ssh_session session) { + int ret; + + if (session == NULL) { + return SSH_ERROR; + } + + enter_function(); + switch(session->pending_call_state){ + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_CONNECT: + goto pending; + default: + ssh_set_error(session,SSH_FATAL,"Bad call during pending SSH call in ssh_connect"); + leave_function(); + return SSH_ERROR; + } + session->alive = 0; + session->client = 1; + + if (ssh_init() < 0) { + leave_function(); + return SSH_ERROR; + } + if (session->opts.fd == SSH_INVALID_SOCKET && + session->opts.host == NULL && + session->opts.ProxyCommand == NULL) { + ssh_set_error(session, SSH_FATAL, "Hostname required"); + leave_function(); + return SSH_ERROR; + } + + ret = ssh_options_apply(session); + if (ret < 0) { + ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); + leave_function(); + return SSH_ERROR; + } + ssh_log(session,SSH_LOG_RARE,"libssh %s, using threading %s", ssh_copyright(), ssh_threads_get_type()); + session->ssh_connection_callback = ssh_client_connection_callback; + session->session_state=SSH_SESSION_STATE_CONNECTING; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); + session->socket_callbacks.connected=socket_callback_connected; + session->socket_callbacks.data=callback_receive_banner; + session->socket_callbacks.exception=ssh_socket_exception_callback; + session->socket_callbacks.userdata=session; + if (session->opts.fd != SSH_INVALID_SOCKET) { + ssh_socket_set_fd(session->socket, session->opts.fd); + ret=SSH_OK; +#ifndef _WIN32 + } else if (session->opts.ProxyCommand != NULL){ + ret = ssh_socket_connect_proxycommand(session->socket, + session->opts.ProxyCommand); +#endif + } else { + ret=ssh_socket_connect(session->socket, + session->opts.host, + session->opts.port, + session->opts.bindaddr); + } + if (ret == SSH_ERROR) { + leave_function(); + return SSH_ERROR; + } + + set_status(session, 0.2f); + + session->alive = 1; + ssh_log(session,SSH_LOG_PROTOCOL,"Socket connecting, now waiting for the callbacks to work"); +pending: + session->pending_call_state=SSH_PENDING_CALL_CONNECT; + if(ssh_is_blocking(session)) { + int timeout = (session->opts.timeout * 1000) + + (session->opts.timeout_usec / 1000); + if (timeout == 0) { + timeout = 10 * 1000; + } + ssh_log(session,SSH_LOG_PACKET,"ssh_connect: Actual timeout : %d", timeout); + ret = ssh_handle_packets_termination(session, timeout, ssh_connect_termination, session); + if (ret == SSH_ERROR || !ssh_connect_termination(session)) { + ssh_set_error(session, SSH_FATAL, + "Timeout connecting to %s", session->opts.host); + session->session_state = SSH_SESSION_STATE_ERROR; + } + } + else { + ret = ssh_handle_packets_termination(session, + SSH_TIMEOUT_NONBLOCKING, + ssh_connect_termination, + session); + if (ret == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + } + ssh_log(session,SSH_LOG_PACKET,"ssh_connect: Actual state : %d",session->session_state); + if(!ssh_is_blocking(session) && !ssh_connect_termination(session)){ + leave_function(); + return SSH_AGAIN; + } + leave_function(); + session->pending_call_state=SSH_PENDING_CALL_NONE; + if(session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) + return SSH_ERROR; + return SSH_OK; +} + +/** + * @brief Get the issue banner from the server. + * + * This is the banner showing a disclaimer to users who log in, + * typically their right or the fact that they will be monitored. + * + * @param[in] session The SSH session to use. + * + * @return A newly allocated string with the banner, NULL on error. + */ +char *ssh_get_issue_banner(ssh_session session) { + if (session == NULL || session->banner == NULL) { + return NULL; + } + + return ssh_string_to_char(session->banner); +} + +/** + * @brief Get the version of the OpenSSH server, if it is not an OpenSSH server + * then 0 will be returned. + * + * You can use the SSH_VERSION_INT macro to compare version numbers. + * + * @param[in] session The SSH session to use. + * + * @return The version number if available, 0 otherwise. + */ +int ssh_get_openssh_version(ssh_session session) { + if (session == NULL) { + return 0; + } + + return session->openssh; +} + +/** + * @brief Disconnect from a session (client or server). + * The session can then be reused to open a new session. + * + * @param[in] session The SSH session to use. + */ +void ssh_disconnect(ssh_session session) { + ssh_string str = NULL; + struct ssh_iterator *it; + + if (session == NULL) { + return; + } + + enter_function(); + + if (ssh_socket_is_open(session->socket)) { + if (buffer_add_u8(session->out_buffer, SSH2_MSG_DISCONNECT) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer, + htonl(SSH2_DISCONNECT_BY_APPLICATION)) < 0) { + goto error; + } + + str = ssh_string_from_char("Bye Bye"); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(session->out_buffer,str) < 0) { + ssh_string_free(str); + goto error; + } + ssh_string_free(str); + + packet_send(session); + ssh_socket_close(session->socket); + } +error: + session->alive = 0; + if(session->socket){ + ssh_socket_reset(session->socket); + } + session->opts.fd = SSH_INVALID_SOCKET; + session->session_state=SSH_SESSION_STATE_DISCONNECTED; + + while ((it=ssh_list_get_iterator(session->channels)) != NULL) { + ssh_channel_free(ssh_iterator_value(ssh_channel,it)); + ssh_list_remove(session->channels, it); + } + if(session->current_crypto){ + crypto_free(session->current_crypto); + session->current_crypto=NULL; + } + if(session->in_buffer) + buffer_reinit(session->in_buffer); + if(session->out_buffer) + buffer_reinit(session->out_buffer); + if(session->in_hashbuf) + buffer_reinit(session->in_hashbuf); + if(session->out_hashbuf) + buffer_reinit(session->out_hashbuf); + session->auth_methods = 0; + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + + if(session->ssh_message_list){ + ssh_message msg; + while((msg=ssh_list_pop_head(ssh_message ,session->ssh_message_list)) + != NULL){ + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + session->ssh_message_list=NULL; + } + + if (session->packet_callbacks){ + ssh_list_free(session->packet_callbacks); + session->packet_callbacks=NULL; + } + + leave_function(); +} + +const char *ssh_copyright(void) { + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2010 Aris Adamantiadis " + "(aris@0xbadc0de.be) Distributed under the LGPL, please refer to COPYING " + "file for information about your rights"; +} +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/config.c b/libssh/src/config.c new file mode 100644 index 00000000..632a50bb --- /dev/null +++ b/libssh/src/config.c @@ -0,0 +1,366 @@ +/* + * config.c - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 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. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/options.h" + +enum ssh_config_opcode_e { + SOC_UNSUPPORTED = -1, + SOC_HOST, + SOC_HOSTNAME, + SOC_PORT, + SOC_USERNAME, + SOC_IDENTITY, + SOC_CIPHERS, + SOC_COMPRESSION, + SOC_TIMEOUT, + SOC_PROTOCOL, + SOC_STRICTHOSTKEYCHECK, + SOC_KNOWNHOSTS, + SOC_PROXYCOMMAND +}; + +struct ssh_config_keyword_table_s { + const char *name; + enum ssh_config_opcode_e opcode; +}; + +static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { + { "host", SOC_HOST }, + { "hostname", SOC_HOSTNAME }, + { "port", SOC_PORT }, + { "user", SOC_USERNAME }, + { "identityfile", SOC_IDENTITY }, + { "ciphers", SOC_CIPHERS }, + { "compression", SOC_COMPRESSION }, + { "connecttimeout", SOC_TIMEOUT }, + { "protocol", SOC_PROTOCOL }, + { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK }, + { "userknownhostsfile", SOC_KNOWNHOSTS }, + { "proxycommand", SOC_PROXYCOMMAND }, + { NULL, SOC_UNSUPPORTED } +}; + +static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) { + return ssh_config_keyword_table[i].opcode; + } + } + + return SOC_UNSUPPORTED; +} + +static char *ssh_config_get_cmd(char **str) { + register char *c; + char *r; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + if (*c == '\"') { + for (r = ++c; *c; c++) { + if (*c == '\"') { + *c = '\0'; + goto out; + } + } + } + + for (r = c; *c; c++) { + if (*c == '\n') { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +static char *ssh_config_get_token(char **str) { + register char *c; + char *r; + + c = ssh_config_get_cmd(str); + + for (r = c; *c; c++) { + if (isblank(*c)) { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +static int ssh_config_get_int(char **str, int notfound) { + char *p, *endp; + int i; + + p = ssh_config_get_token(str); + if (p && *p) { + i = strtol(p, &endp, 10); + if (p == endp) { + return notfound; + } + return i; + } + + return notfound; +} + +static const char *ssh_config_get_str_tok(char **str, const char *def) { + char *p; + + p = ssh_config_get_token(str); + if (p && *p) { + return p; + } + + return def; +} + +static int ssh_config_get_yesno(char **str, int notfound) { + const char *p; + + p = ssh_config_get_str_tok(str, NULL); + if (p == NULL) { + return notfound; + } + + if (strncasecmp(p, "yes", 3) == 0) { + return 1; + } else if (strncasecmp(p, "no", 2) == 0) { + return 0; + } + + return notfound; +} + +static int ssh_config_parse_line(ssh_session session, const char *line, + unsigned int count, int *parsing) { + enum ssh_config_opcode_e opcode; + const char *p; + char *s, *x; + char *keyword; + char *lowerhost; + size_t len; + int i; + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(session); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_config_get_opcode(keyword); + + switch (opcode) { + case SOC_HOST: + *parsing = 0; + lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL; + for (p = ssh_config_get_str_tok(&s, NULL); p && *p; + p = ssh_config_get_str_tok(&s, NULL)) { + if (match_hostname(lowerhost, p, strlen(p))) { + *parsing = 1; + } + } + SAFE_FREE(lowerhost); + break; + case SOC_HOSTNAME: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_HOST, p); + } + break; + case SOC_PORT: + if (session->opts.port == 22) { + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, p); + } + } + break; + case SOC_USERNAME: + if (session->opts.username == NULL) { + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_USER, p); + } + } + break; + case SOC_IDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); + } + break; + case SOC_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); + } + break; + case SOC_COMPRESSION: + i = ssh_config_get_yesno(&s, -1); + if (i >= 0 && *parsing) { + if (i) { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); + } else { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + } + } + break; + case SOC_PROTOCOL: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + char *a, *b; + b = strdup(p); + if (b == NULL) { + SAFE_FREE(x); + ssh_set_error_oom(session); + return -1; + } + i = 0; + ssh_options_set(session, SSH_OPTIONS_SSH1, &i); + ssh_options_set(session, SSH_OPTIONS_SSH2, &i); + + for (a = strtok(b, ","); a; a = strtok(NULL, ",")) { + switch (atoi(a)) { + case 1: + i = 1; + ssh_options_set(session, SSH_OPTIONS_SSH1, &i); + break; + case 2: + i = 1; + ssh_options_set(session, SSH_OPTIONS_SSH2, &i); + break; + default: + break; + } + } + SAFE_FREE(b); + } + break; + case SOC_TIMEOUT: + i = ssh_config_get_int(&s, -1); + if (i >= 0 && *parsing) { + ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &i); + } + break; + case SOC_STRICTHOSTKEYCHECK: + i = ssh_config_get_yesno(&s, -1); + if (i >= 0 && *parsing) { + ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); + } + break; + case SOC_KNOWNHOSTS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); + } + break; + case SOC_PROXYCOMMAND: + p = ssh_config_get_cmd(&s); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); + } + break; + case SOC_UNSUPPORTED: + ssh_log(session, SSH_LOG_RARE, "Unsupported option: %s, line: %d\n", + keyword, count); + break; + default: + ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d\n", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return 0; +} + +/* ssh_config_parse_file */ +int ssh_config_parse_file(ssh_session session, const char *filename) { + char line[1024] = {0}; + unsigned int count = 0; + FILE *f; + int parsing; + + if ((f = fopen(filename, "r")) == NULL) { + return 0; + } + + ssh_log(session, SSH_LOG_RARE, "Reading configuration data from %s", filename); + + parsing = 1; + while (fgets(line, sizeof(line), f)) { + count++; + if (ssh_config_parse_line(session, line, count, &parsing) < 0) { + fclose(f); + return -1; + } + } + + fclose(f); + return 0; +} diff --git a/libssh/src/connect.c b/libssh/src/connect.c new file mode 100644 index 00000000..ae55b140 --- /dev/null +++ b/libssh/src/connect.c @@ -0,0 +1,484 @@ +/* + * connect.c - handles connections to ssh servers + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#ifdef _WIN32 +/* + * Only use Windows API functions available on Windows 2000 SP4 or later. + * The available constants are in . + * http://msdn.microsoft.com/en-us/library/aa383745.aspx + * http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx + */ +#undef _WIN32_WINNT +#ifdef HAVE_WSPIAPI_H +#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */ +#else +#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */ +#endif + +#if _MSC_VER >= 1400 +#include +#undef close +#define close _close +#endif /* _MSC_VER */ +#include +#include + +/* is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. */ +#ifdef HAVE_WSPIAPI_H +#include +#endif + +#else /* _WIN32 */ + +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/poll.h" + +#ifndef HAVE_GETADDRINFO +#error "Your system must have getaddrinfo()" +#endif + +#ifdef _WIN32 +#ifndef gai_strerror +char WSAAPI *gai_strerrorA(int code) { + static char buf[256]; + + snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code); + + return buf; +} +#endif /* gai_strerror */ +#endif /* _WIN32 */ + +static int ssh_connect_socket_close(socket_t s){ +#ifdef _WIN32 + return closesocket(s); +#else + return close(s); +#endif +} + + +static int getai(ssh_session session, const char *host, int port, struct addrinfo **ai) { + const char *service = NULL; + struct addrinfo hints; + char s_port[10]; + + ZERO_STRUCT(hints); + + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) { + hints.ai_flags = AI_PASSIVE; + } else { + snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port); + service = s_port; +#ifdef AI_NUMERICSERV + hints.ai_flags=AI_NUMERICSERV; +#endif + } + + if (ssh_is_ipaddr(host)) { + /* this is an IP address */ + ssh_log(session,SSH_LOG_PACKET,"host %s matches an IP address",host); + hints.ai_flags |= AI_NUMERICHOST; + } + + return getaddrinfo(host, service, &hints, ai); +} + +static int ssh_connect_ai_timeout(ssh_session session, const char *host, + int port, struct addrinfo *ai, long timeout, long usec, socket_t s) { + int timeout_ms; + ssh_pollfd_t fds; + int rc = 0; + socklen_t len = sizeof(rc); + + enter_function(); + + /* I know we're losing some precision. But it's not like poll-like family + * type of mechanisms are precise up to the microsecond. + */ + timeout_ms=timeout * 1000 + usec / 1000; + + ssh_socket_set_nonblocking(s); + + ssh_log(session, SSH_LOG_RARE, "Trying to connect to host: %s:%d with " + "timeout %d ms", host, port, timeout_ms); + + /* The return value is checked later */ + connect(s, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + + fds.fd=s; + fds.revents=0; + fds.events=POLLOUT; +#ifdef _WIN32 + fds.events |= POLLWRNORM; +#endif + rc = ssh_poll(&fds,1,timeout_ms); + + if (rc == 0) { + /* timeout */ + ssh_set_error(session, SSH_FATAL, + "Timeout while connecting to %s:%d", host, port); + ssh_connect_socket_close(s); + leave_function(); + return -1; + } + + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "poll error: %s", strerror(errno)); + ssh_connect_socket_close(s); + leave_function(); + return -1; + } + rc = 0; + + /* Get connect(2) return code. Zero means no error */ + getsockopt(s, SOL_SOCKET, SO_ERROR,(char *) &rc, &len); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Connect to %s:%d failed: %s", host, port, strerror(rc)); + ssh_connect_socket_close(s); + leave_function(); + return -1; + } + + /* s is connected ? */ + ssh_log(session, SSH_LOG_PACKET, "Socket connected with timeout\n"); + ssh_socket_set_blocking(s); + + leave_function(); + return s; +} + +/** + * @internal + * + * @brief Connect to an IPv4 or IPv6 host specified by its IP address or + * hostname. + * + * @returns A file descriptor, < 0 on error. + */ +socket_t ssh_connect_host(ssh_session session, const char *host, + const char *bind_addr, int port, long timeout, long usec) { + socket_t s = -1; + int rc; + struct addrinfo *ai; + struct addrinfo *itr; + + enter_function(); + + rc = getai(session,host, port, &ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve hostname %s (%s)", host, gai_strerror(rc)); + leave_function(); + return -1; + } + + for (itr = ai; itr != NULL; itr = itr->ai_next){ + /* create socket */ + s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); + if (s < 0) { + ssh_set_error(session, SSH_FATAL, + "Socket create failed: %s", strerror(errno)); + continue; + } + + if (bind_addr) { + struct addrinfo *bind_ai; + struct addrinfo *bind_itr; + + ssh_log(session, SSH_LOG_PACKET, "Resolving %s\n", bind_addr); + + rc = getai(session,bind_addr, 0, &bind_ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve bind address %s (%s)", + bind_addr, + gai_strerror(rc)); + freeaddrinfo(ai); + close(s); + leave_function(); + return -1; + } + + for (bind_itr = bind_ai; bind_itr != NULL; bind_itr = bind_itr->ai_next) { + if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { + ssh_set_error(session, SSH_FATAL, + "Binding local address: %s", strerror(errno)); + continue; + } else { + break; + } + } + freeaddrinfo(bind_ai); + + /* Cannot bind to any local addresses */ + if (bind_itr == NULL) { + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + if (timeout || usec) { + socket_t ret = ssh_connect_ai_timeout(session, host, port, itr, + timeout, usec, s); + leave_function(); + return ret; + } + + if (connect(s, itr->ai_addr, itr->ai_addrlen) < 0) { + ssh_set_error(session, SSH_FATAL, "Connect failed: %s", strerror(errno)); + ssh_connect_socket_close(s); + s = -1; + leave_function(); + continue; + } else { + /* We are connected */ + break; + } + } + + freeaddrinfo(ai); + leave_function(); + + return s; +} + +/** + * @internal + * + * @brief Launches a nonblocking connect to an IPv4 or IPv6 host + * specified by its IP address or hostname. + * + * @returns A file descriptor, < 0 on error. + * @warning very ugly !!! + */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port) { + socket_t s = -1; + int rc; + struct addrinfo *ai; + struct addrinfo *itr; + + enter_function(); + + rc = getai(session,host, port, &ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve hostname %s (%s)", host, gai_strerror(rc)); + leave_function(); + return -1; + } + + for (itr = ai; itr != NULL; itr = itr->ai_next){ + /* create socket */ + s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); + if (s < 0) { + ssh_set_error(session, SSH_FATAL, + "Socket create failed: %s", strerror(errno)); + continue; + } + + if (bind_addr) { + struct addrinfo *bind_ai; + struct addrinfo *bind_itr; + + ssh_log(session, SSH_LOG_PACKET, "Resolving %s\n", bind_addr); + + rc = getai(session,bind_addr, 0, &bind_ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve bind address %s (%s)", + bind_addr, + gai_strerror(rc)); + close(s); + s=-1; + break; + } + + for (bind_itr = bind_ai; bind_itr != NULL; bind_itr = bind_itr->ai_next) { + if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { + ssh_set_error(session, SSH_FATAL, + "Binding local address: %s", strerror(errno)); + continue; + } else { + break; + } + } + freeaddrinfo(bind_ai); + + /* Cannot bind to any local addresses */ + if (bind_itr == NULL) { + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + ssh_socket_set_nonblocking(s); + + connect(s, itr->ai_addr, itr->ai_addrlen); + break; + } + + freeaddrinfo(ai); + leave_function(); + + return s; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int ssh_select_cb (socket_t fd, int revents, void *userdata){ + fd_set *set = (fd_set *)userdata; + if(revents & POLLIN) + FD_SET(fd, set); + return 0; +} + +/** + * @brief A wrapper for the select syscall + * + * This functions acts more or less like the select(2) syscall.\n + * There is no support for writing or exceptions.\n + * + * @param[in] channels Arrays of channels pointers terminated by a NULL. + * It is never rewritten. + * + * @param[out] outchannels Arrays of same size that "channels", there is no need + * to initialize it. + * + * @param[in] maxfd Maximum +1 file descriptor from readfds. + * + * @param[in] readfds A fd_set of file descriptors to be select'ed for + * reading. + * + * @param[in] timeout A timeout for the select. + * + * @return SSH_OK on success, + * SSH_ERROR on error, + * SSH_EINTR if it was interrupted. In that case, + * just restart it. + * + * @warning libssh is not reentrant here. That means that if a signal is caught + * during the processing of this function, you cannot call libssh + * functions on sessions that are busy with ssh_select(). + * + * @see select(2) + */ +int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout) { + int i,j; + int rc; + int base_tm, tm; + struct ssh_timestamp ts; + ssh_event event = ssh_event_new(); + int firstround=1; + + base_tm = tm=timeout->tv_sec * 1000 + timeout->tv_usec/1000; + for (i=0 ; channels[i] != NULL; ++i){ + ssh_event_add_session(event, channels[i]->session); + } + for (i=0; i 0) { + ret = crc_table[(ret ^ *buf) & 0xff] ^ (ret >> 8); + --len; + ++buf; + } + + return ret; +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/dh.c b/libssh/src/dh.c new file mode 100644 index 00000000..e4f2062b --- /dev/null +++ b/libssh/src/dh.c @@ -0,0 +1,1136 @@ +/* + * dh.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * Let us resume the dh protocol. + * Each side computes a private prime number, x at client side, y at server + * side. + * g and n are two numbers common to every ssh software. + * client's public key (e) is calculated by doing: + * e = g^x mod p + * client sends e to the server. + * the server computes his own public key, f + * f = g^y mod p + * it sends it to the client + * the common key K is calculated by the client by doing + * k = f^x mod p + * the server does the same with the client public key e + * k' = e^y mod p + * if everything went correctly, k and k' are equal + */ + +#include "config.h" +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" + +/* todo: remove it */ +#include "libssh/string.h" +#ifdef HAVE_LIBCRYPTO +#include +#include +#include +#endif + +static unsigned char p_group1_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define P_GROUP1_LEN 128 /* Size in bytes of the p number */ + + +static unsigned char p_group14_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP14_LEN 256 /* Size in bytes of the p number for group 14 */ + +static unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */ +static bignum g; +static bignum p_group1; +static bignum p_group14; +static int ssh_crypto_initialized; + +static bignum select_p(enum ssh_key_exchange_e type) { + return type == SSH_KEX_DH_GROUP14_SHA1 ? p_group14 : p_group1; +} + +int ssh_get_random(void *where, int len, int strong){ + +#ifdef HAVE_LIBGCRYPT + /* variable not used in gcrypt */ + (void) strong; + /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ + gcry_randomize(where,len,GCRY_STRONG_RANDOM); + + return 1; +#elif defined HAVE_LIBCRYPTO + if (strong) { + return RAND_bytes(where,len); + } else { + return RAND_pseudo_bytes(where,len); + } +#endif + + /* never reached */ + return 1; +} + + +/* + * This inits the values g and p which are used for DH key agreement + * FIXME: Make the function thread safe by adding a semaphore or mutex. + */ +int ssh_crypto_init(void) { + if (ssh_crypto_initialized == 0) { +#ifdef HAVE_LIBGCRYPT + gcry_check_version(NULL); + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P,0)) { + gcry_control(GCRYCTL_INIT_SECMEM, 4096); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED,0); + } +#endif + + g = bignum_new(); + if (g == NULL) { + return -1; + } + bignum_set_word(g,g_int); + +#ifdef HAVE_LIBGCRYPT + bignum_bin2bn(p_group1_value, P_GROUP1_LEN, &p_group1); + if (p_group1 == NULL) { + bignum_free(g); + g = NULL; + return -1; + } + bignum_bin2bn(p_group14_value, P_GROUP14_LEN, &p_group14); + if (p_group1 == NULL) { + bignum_free(g); + bignum_free(p_group1); + g = NULL; + p_group1 = NULL; + return -1; + } + +#elif defined HAVE_LIBCRYPTO + p_group1 = bignum_new(); + if (p_group1 == NULL) { + bignum_free(g); + g = NULL; + return -1; + } + bignum_bin2bn(p_group1_value, P_GROUP1_LEN, p_group1); + + p_group14 = bignum_new(); + if (p_group14 == NULL) { + bignum_free(g); + bignum_free(p_group1); + g = NULL; + p_group1 = NULL; + return -1; + } + bignum_bin2bn(p_group14_value, P_GROUP14_LEN, p_group14); + + OpenSSL_add_all_algorithms(); + +#endif + + ssh_crypto_initialized = 1; + } + + return 0; +} + +void ssh_crypto_finalize(void) { + if (ssh_crypto_initialized) { + bignum_free(g); + g = NULL; + bignum_free(p_group1); + p_group1 = NULL; + bignum_free(p_group14); + p_group14 = NULL; +#ifdef HAVE_LIBGCRYPT + gcry_control(GCRYCTL_TERM_SECMEM); +#elif defined HAVE_LIBCRYPTO + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); +#endif + ssh_crypto_initialized=0; + } +} + +/* prints the bignum on stderr */ +void ssh_print_bignum(const char *which, bignum num) { +#ifdef HAVE_LIBGCRYPT + unsigned char *hex = NULL; + bignum_bn2hex(num, &hex); +#elif defined HAVE_LIBCRYPTO + char *hex = NULL; + hex = bignum_bn2hex(num); +#endif + fprintf(stderr, "%s value: ", which); + fprintf(stderr, "%s\n", (hex == NULL) ? "(null)" : (char *) hex); + SAFE_FREE(hex); +} + +/** + * @brief Convert a buffer into a colon separated hex string. + * The caller has to free the memory. + * + * @param what What should be converted to a hex string. + * + * @param len Length of the buffer to convert. + * + * @return The hex string or NULL on error. + * + * @see ssh_string_free_char() + */ +char *ssh_get_hexa(const unsigned char *what, size_t len) { + const char h[] = "0123456789abcdef"; + char *hexa; + size_t i; + size_t hlen = len * 3; + + if (len > (UINT_MAX - 1) / 3) { + return NULL; + } + + hexa = malloc(hlen + 1); + if (hexa == NULL) { + return NULL; + } + + for (i = 0; i < len; i++) { + hexa[i * 3] = h[(what[i] >> 4) & 0xF]; + hexa[i * 3 + 1] = h[what[i] & 0xF]; + hexa[i * 3 + 2] = ':'; + } + hexa[hlen - 1] = '\0'; + + return hexa; +} + +/** + * @brief Print a buffer as colon separated hex string. + * + * @param descr Description printed in front of the hex string. + * + * @param what What should be converted to a hex string. + * + * @param len Length of the buffer to convert. + */ +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { + char *hexa = ssh_get_hexa(what, len); + + if (hexa == NULL) { + return; + } + printf("%s: %s\n", descr, hexa); + + free(hexa); +} + +int dh_generate_x(ssh_session session) { + session->next_crypto->x = bignum_new(); + if (session->next_crypto->x == NULL) { + return -1; + } + +#ifdef HAVE_LIBGCRYPT + bignum_rand(session->next_crypto->x, 128); +#elif defined HAVE_LIBCRYPTO + bignum_rand(session->next_crypto->x, 128, 0, -1); +#endif + + /* not harder than this */ +#ifdef DEBUG_CRYPTO + ssh_print_bignum("x", session->next_crypto->x); +#endif + + return 0; +} + +/* used by server */ +int dh_generate_y(ssh_session session) { + session->next_crypto->y = bignum_new(); + if (session->next_crypto->y == NULL) { + return -1; + } + +#ifdef HAVE_LIBGCRYPT + bignum_rand(session->next_crypto->y, 128); +#elif defined HAVE_LIBCRYPTO + bignum_rand(session->next_crypto->y, 128, 0, -1); +#endif + + /* not harder than this */ +#ifdef DEBUG_CRYPTO + ssh_print_bignum("y", session->next_crypto->y); +#endif + + return 0; +} + +/* used by server */ +int dh_generate_e(ssh_session session) { +#ifdef HAVE_LIBCRYPTO + bignum_CTX ctx = bignum_ctx_new(); + if (ctx == NULL) { + return -1; + } +#endif + + session->next_crypto->e = bignum_new(); + if (session->next_crypto->e == NULL) { +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + return -1; + } + +#ifdef HAVE_LIBGCRYPT + bignum_mod_exp(session->next_crypto->e, g, session->next_crypto->x, + select_p(session->next_crypto->kex_type)); +#elif defined HAVE_LIBCRYPTO + bignum_mod_exp(session->next_crypto->e, g, session->next_crypto->x, + select_p(session->next_crypto->kex_type), ctx); +#endif + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("e", session->next_crypto->e); +#endif + +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + + return 0; +} + +int dh_generate_f(ssh_session session) { +#ifdef HAVE_LIBCRYPTO + bignum_CTX ctx = bignum_ctx_new(); + if (ctx == NULL) { + return -1; + } +#endif + + session->next_crypto->f = bignum_new(); + if (session->next_crypto->f == NULL) { +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + return -1; + } + +#ifdef HAVE_LIBGCRYPT + bignum_mod_exp(session->next_crypto->f, g, session->next_crypto->y, + select_p(session->next_crypto->kex_type)); +#elif defined HAVE_LIBCRYPTO + bignum_mod_exp(session->next_crypto->f, g, session->next_crypto->y, + select_p(session->next_crypto->kex_type), ctx); +#endif + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("f", session->next_crypto->f); +#endif + +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + + return 0; +} + +ssh_string make_bignum_string(bignum num) { + ssh_string ptr = NULL; + int pad = 0; + unsigned int len = bignum_num_bytes(num); + unsigned int bits = bignum_num_bits(num); + + if (len == 0) { + return NULL; + } + + /* If the first bit is set we have a negative number */ + if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) { + pad++; + } + +#ifdef DEBUG_CRYPTO + fprintf(stderr, "%d bits, %d bytes, %d padding\n", bits, len, pad); +#endif /* DEBUG_CRYPTO */ + + ptr = ssh_string_new(len + pad); + if (ptr == NULL) { + return NULL; + } + + /* We have a negative number so we need a leading zero */ + if (pad) { + ptr->data[0] = 0; + } + +#ifdef HAVE_LIBGCRYPT + bignum_bn2bin(num, len, ptr->data + pad); +#elif HAVE_LIBCRYPTO + bignum_bn2bin(num, ptr->data + pad); +#endif + + return ptr; +} + +bignum make_string_bn(ssh_string string){ + bignum bn = NULL; + unsigned int len = ssh_string_len(string); + +#ifdef DEBUG_CRYPTO + fprintf(stderr, "Importing a %d bits, %d bytes object ...\n", + len * 8, len); +#endif /* DEBUG_CRYPTO */ + +#ifdef HAVE_LIBGCRYPT + bignum_bin2bn(string->data, len, &bn); +#elif defined HAVE_LIBCRYPTO + bn = bignum_bin2bn(string->data, len, NULL); +#endif + + return bn; +} + +ssh_string dh_get_e(ssh_session session) { + return make_bignum_string(session->next_crypto->e); +} + +/* used by server */ +ssh_string dh_get_f(ssh_session session) { + return make_bignum_string(session->next_crypto->f); +} + +void dh_import_pubkey(ssh_session session, ssh_string pubkey_string) { + session->next_crypto->server_pubkey = pubkey_string; +} + +int dh_import_f(ssh_session session, ssh_string f_string) { + session->next_crypto->f = make_string_bn(f_string); + if (session->next_crypto->f == NULL) { + return -1; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("f",session->next_crypto->f); +#endif + + return 0; +} + +/* used by the server implementation */ +int dh_import_e(ssh_session session, ssh_string e_string) { + session->next_crypto->e = make_string_bn(e_string); + if (session->next_crypto->e == NULL) { + return -1; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("e",session->next_crypto->e); +#endif + + return 0; +} + +int dh_build_k(ssh_session session) { +#ifdef HAVE_LIBCRYPTO + bignum_CTX ctx = bignum_ctx_new(); + if (ctx == NULL) { + return -1; + } +#endif + + session->next_crypto->k = bignum_new(); + if (session->next_crypto->k == NULL) { +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + return -1; + } + + /* the server and clients don't use the same numbers */ +#ifdef HAVE_LIBGCRYPT + if(session->client) { + bignum_mod_exp(session->next_crypto->k, session->next_crypto->f, + session->next_crypto->x, select_p(session->next_crypto->kex_type)); + } else { + bignum_mod_exp(session->next_crypto->k, session->next_crypto->e, + session->next_crypto->y, select_p(session->next_crypto->kex_type)); + } +#elif defined HAVE_LIBCRYPTO + if (session->client) { + bignum_mod_exp(session->next_crypto->k, session->next_crypto->f, + session->next_crypto->x, select_p(session->next_crypto->kex_type), ctx); + } else { + bignum_mod_exp(session->next_crypto->k, session->next_crypto->e, + session->next_crypto->y, select_p(session->next_crypto->kex_type), ctx); + } +#endif + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Session server cookie", + session->next_crypto->server_kex.cookie, 16); + ssh_print_hexa("Session client cookie", + session->next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", session->next_crypto->k); +#endif + +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + + return 0; +} + +/** @internal + * @brief Starts diffie-hellman-group1 key exchange + */ +int ssh_client_dh_init(ssh_session session){ + ssh_string e = NULL; + int rc; + enter_function(); + if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_INIT) < 0) { + goto error; + } + + if (dh_generate_x(session) < 0) { + goto error; + } + if (dh_generate_e(session) < 0) { + goto error; + } + + e = dh_get_e(session); + if (e == NULL) { + goto error; + } + + if (buffer_add_ssh_string(session->out_buffer, e) < 0) { + goto error; + } + ssh_string_burn(e); + ssh_string_free(e); + e=NULL; + + rc = packet_send(session); + return rc; + error: + if(e != NULL){ + ssh_string_burn(e); + ssh_string_free(e); + } + + leave_function(); + return SSH_ERROR; +} + +int ssh_client_dh_reply(ssh_session session, ssh_buffer packet){ + ssh_string f; + ssh_string pubkey = NULL; + ssh_string signature = NULL; + int rc; + pubkey = buffer_get_ssh_string(packet); + if (pubkey == NULL){ + ssh_set_error(session,SSH_FATAL, "No public key in packet"); + goto error; + } + dh_import_pubkey(session, pubkey); + + f = buffer_get_ssh_string(packet); + if (f == NULL) { + ssh_set_error(session,SSH_FATAL, "No F number in packet"); + goto error; + } + rc = dh_import_f(session, f); + ssh_string_burn(f); + ssh_string_free(f); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot import f number"); + goto error; + } + + signature = buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature=NULL; /* ownership changed */ + if (dh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc=packet_send(session); + ssh_log(session, SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); + return rc; +error: + return SSH_ERROR; +} + + +/* +static void sha_add(ssh_string str,SHACTX ctx){ + sha1_update(ctx,str,string_len(str)+4); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("partial hashed sessionid",str,string_len(str)+4); +#endif +} +*/ + +int make_sessionid(ssh_session session) { + ssh_string num = NULL; + ssh_string str = NULL; + ssh_buffer server_hash = NULL; + ssh_buffer client_hash = NULL; + ssh_buffer buf = NULL; + uint32_t len; + int rc = SSH_ERROR; + + enter_function(); + + buf = ssh_buffer_new(); + if (buf == NULL) { + return rc; + } + + str = ssh_string_from_char(session->clientbanner); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buf, str) < 0) { + goto error; + } + ssh_string_free(str); + + str = ssh_string_from_char(session->serverbanner); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buf, str) < 0) { + goto error; + } + + if (session->client) { + server_hash = session->in_hashbuf; + client_hash = session->out_hashbuf; + } else { + server_hash = session->out_hashbuf; + client_hash = session->in_hashbuf; + } + + if (buffer_add_u32(server_hash, 0) < 0) { + goto error; + } + if (buffer_add_u8(server_hash, 0) < 0) { + goto error; + } + if (buffer_add_u32(client_hash, 0) < 0) { + goto error; + } + if (buffer_add_u8(client_hash, 0) < 0) { + goto error; + } + + len = ntohl(buffer_get_rest_len(client_hash)); + if (buffer_add_u32(buf,len) < 0) { + goto error; + } + if (buffer_add_data(buf, buffer_get_rest(client_hash), + buffer_get_rest_len(client_hash)) < 0) { + goto error; + } + + len = ntohl(buffer_get_rest_len(server_hash)); + if (buffer_add_u32(buf, len) < 0) { + goto error; + } + if (buffer_add_data(buf, buffer_get_rest(server_hash), + buffer_get_rest_len(server_hash)) < 0) { + goto error; + } + + len = ssh_string_len(session->next_crypto->server_pubkey) + 4; + if (buffer_add_data(buf, session->next_crypto->server_pubkey, len) < 0) { + goto error; + } + if(session->next_crypto->kex_type == SSH_KEX_DH_GROUP1_SHA1 || + session->next_crypto->kex_type == SSH_KEX_DH_GROUP14_SHA1) { + + num = make_bignum_string(session->next_crypto->e); + if (num == NULL) { + goto error; + } + + len = ssh_string_len(num) + 4; + if (buffer_add_data(buf, num, len) < 0) { + goto error; + } + + ssh_string_free(num); + num = make_bignum_string(session->next_crypto->f); + if (num == NULL) { + goto error; + } + + len = ssh_string_len(num) + 4; + if (buffer_add_data(buf, num, len) < 0) { + goto error; + } + + ssh_string_free(num); +#ifdef HAVE_ECDH + } else if (session->next_crypto->kex_type == SSH_KEX_ECDH_SHA2_NISTP256){ + if(session->next_crypto->ecdh_client_pubkey == NULL || + session->next_crypto->ecdh_server_pubkey == NULL){ + ssh_log(session,SSH_LOG_WARNING,"ECDH parameted missing"); + goto error; + } + buffer_add_ssh_string(buf,session->next_crypto->ecdh_client_pubkey); + buffer_add_ssh_string(buf,session->next_crypto->ecdh_server_pubkey); +#endif + } + num = make_bignum_string(session->next_crypto->k); + if (num == NULL) { + goto error; + } + + len = ssh_string_len(num) + 4; + if (buffer_add_data(buf, num, len) < 0) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("hash buffer", ssh_buffer_get_begin(buf), ssh_buffer_get_len(buf)); +#endif + + switch(session->next_crypto->kex_type){ + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + session->next_crypto->digest_len = SHA_DIGEST_LENGTH; + session->next_crypto->mac_type = SSH_MAC_SHA1; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if(session->next_crypto->secret_hash == NULL){ + ssh_set_error_oom(session); + goto error; + } + sha1(buffer_get_rest(buf), buffer_get_rest_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_ECDH_SHA2_NISTP256: + session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; + session->next_crypto->mac_type = SSH_MAC_SHA256; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if(session->next_crypto->secret_hash == NULL){ + ssh_set_error_oom(session); + goto error; + } + sha256(buffer_get_rest(buf), buffer_get_rest_len(buf), + session->next_crypto->secret_hash); + break; + } + /* During the first kex, secret hash and session ID are equal. However, after + * a key re-exchange, a new secret hash is calculated. This hash will not replace + * but complement existing session id. + */ + if (!session->next_crypto->session_id){ + session->next_crypto->session_id = malloc(session->next_crypto->digest_len); + if (session->next_crypto->session_id == NULL){ + ssh_set_error_oom(session); + goto error; + } + memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, + session->next_crypto->digest_len); + } +#ifdef DEBUG_CRYPTO + printf("Session hash: "); + ssh_print_hexa("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); + ssh_print_hexa("session id", session->next_crypto->session_id, session->next_crypto->digest_len); +#endif + + rc = SSH_OK; +error: + ssh_buffer_free(buf); + ssh_buffer_free(client_hash); + ssh_buffer_free(server_hash); + + session->in_hashbuf = NULL; + session->out_hashbuf = NULL; + + ssh_string_free(str); + ssh_string_free(num); + + leave_function(); + + return rc; +} + +int hashbufout_add_cookie(ssh_session session) { + session->out_hashbuf = ssh_buffer_new(); + if (session->out_hashbuf == NULL) { + return -1; + } + + if (buffer_add_u8(session->out_hashbuf, 20) < 0) { + buffer_reinit(session->out_hashbuf); + return -1; + } + + if (session->server) { + if (buffer_add_data(session->out_hashbuf, + session->next_crypto->server_kex.cookie, 16) < 0) { + buffer_reinit(session->out_hashbuf); + return -1; + } + } else { + if (buffer_add_data(session->out_hashbuf, + session->next_crypto->client_kex.cookie, 16) < 0) { + buffer_reinit(session->out_hashbuf); + return -1; + } + } + + return 0; +} + +int hashbufin_add_cookie(ssh_session session, unsigned char *cookie) { + session->in_hashbuf = ssh_buffer_new(); + if (session->in_hashbuf == NULL) { + return -1; + } + + if (buffer_add_u8(session->in_hashbuf, 20) < 0) { + buffer_reinit(session->in_hashbuf); + return -1; + } + if (buffer_add_data(session->in_hashbuf,cookie, 16) < 0) { + buffer_reinit(session->in_hashbuf); + return -1; + } + + return 0; +} + +static int generate_one_key(ssh_string k, + struct ssh_crypto_struct *crypto, unsigned char *output, char letter) { + ssh_mac_ctx ctx; + ctx=ssh_mac_ctx_init(crypto->mac_type); + + if (ctx == NULL) { + return -1; + } + + ssh_mac_update(ctx, k, ssh_string_len(k) + 4); + ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + ssh_mac_update(ctx, &letter, 1); + ssh_mac_update(ctx, crypto->session_id, crypto->digest_len); + ssh_mac_final(output, ctx); + + return 0; +} + +int generate_session_keys(ssh_session session) { + ssh_string k_string = NULL; + ssh_mac_ctx ctx = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + int rc = -1; + + enter_function(); + + k_string = make_bignum_string(crypto->k); + if (k_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + + crypto->encryptIV = malloc(crypto->digest_len); + crypto->decryptIV = malloc(crypto->digest_len); + crypto->encryptkey = malloc(crypto->digest_len); + crypto->decryptkey = malloc(crypto->digest_len); + crypto->encryptMAC = malloc(crypto->digest_len); + crypto->decryptMAC = malloc(crypto->digest_len); + if(crypto->encryptIV == NULL || crypto->decryptIV == NULL || + crypto->encryptkey == NULL || crypto->decryptkey == NULL || + crypto->encryptMAC == NULL || crypto->decryptMAC == NULL){ + ssh_set_error_oom(session); + goto error; + } + + /* IV */ + if (session->client) { + if (generate_one_key(k_string, crypto, crypto->encryptIV, 'A') < 0) { + goto error; + } + if (generate_one_key(k_string, crypto, crypto->decryptIV, 'B') < 0) { + goto error; + } + } else { + if (generate_one_key(k_string, crypto, crypto->decryptIV, 'A') < 0) { + goto error; + } + if (generate_one_key(k_string, crypto, crypto->encryptIV, 'B') < 0) { + goto error; + } + } + if (session->client) { + if (generate_one_key(k_string, crypto, crypto->encryptkey, 'C') < 0) { + goto error; + } + if (generate_one_key(k_string, crypto, crypto->decryptkey, 'D') < 0) { + goto error; + } + } else { + if (generate_one_key(k_string, crypto, crypto->decryptkey, 'C') < 0) { + goto error; + } + if (generate_one_key(k_string, crypto, crypto->encryptkey, 'D') < 0) { + goto error; + } + } + + /* some ciphers need more than DIGEST_LEN bytes of input key */ + if (crypto->out_cipher->keysize > crypto->digest_len * 8) { + crypto->encryptkey = realloc(crypto->encryptkey, crypto->digest_len * 2); + if(crypto->encryptkey == NULL) + goto error; + ctx = ssh_mac_ctx_init(crypto->mac_type); + if (ctx == NULL) { + goto error; + } + ssh_mac_update(ctx, k_string, ssh_string_len(k_string) + 4); + ssh_mac_update(ctx, crypto->session_id, + crypto->digest_len); + ssh_mac_update(ctx, crypto->encryptkey, crypto->digest_len); + ssh_mac_final(crypto->encryptkey + crypto->digest_len, ctx); + } + + if (crypto->in_cipher->keysize > crypto->digest_len * 8) { + crypto->decryptkey = realloc(crypto->decryptkey, crypto->digest_len *2); + if(crypto->decryptkey == NULL) + goto error; + ctx = ssh_mac_ctx_init(crypto->mac_type); + ssh_mac_update(ctx, k_string, ssh_string_len(k_string) + 4); + ssh_mac_update(ctx, crypto->session_id, + crypto->digest_len); + ssh_mac_update(ctx, crypto->decryptkey, crypto->digest_len); + ssh_mac_final(crypto->decryptkey + crypto->digest_len, ctx); + } + if(session->client) { + if (generate_one_key(k_string, crypto, crypto->encryptMAC, 'E') < 0) { + goto error; + } + if (generate_one_key(k_string, crypto, crypto->decryptMAC, 'F') < 0) { + goto error; + } + } else { + if (generate_one_key(k_string, crypto, crypto->decryptMAC, 'E') < 0) { + goto error; + } + if (generate_one_key(k_string, crypto, crypto->encryptMAC, 'F') < 0) { + goto error; + } + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Encrypt IV", crypto->encryptIV, SHA_DIGEST_LEN); + ssh_print_hexa("Decrypt IV", crypto->decryptIV, SHA_DIGEST_LEN); + ssh_print_hexa("Encryption key", crypto->encryptkey, + crypto->out_cipher->keysize); + ssh_print_hexa("Decryption key", crypto->decryptkey, + crypto->in_cipher->keysize); + ssh_print_hexa("Encryption MAC", crypto->encryptMAC, SHA_DIGEST_LEN); + ssh_print_hexa("Decryption MAC", crypto->decryptMAC, 20); +#endif + + rc = 0; +error: + ssh_string_free(k_string); + leave_function(); + + return rc; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @brief Allocates a buffer with the MD5 hash of the server public key. + * + * This function allows you to get a MD5 hash of the public key. You can then + * print this hash in a human-readable form to the user so that he is able to + * verify it. Use ssh_get_hexa() or ssh_print_hexa() to display it. + * + * @param[in] session The SSH session to use. + * + * @param[in] hash The buffer to allocate. + * + * @return The bytes allocated or < 0 on error. + * + * @warning It is very important that you verify at some moment that the hash + * matches a known server. If you don't do it, cryptography wont help + * you at making things secure + * + * @see ssh_is_server_known() + * @see ssh_get_hexa() + * @see ssh_print_hexa() + */ +int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) { + ssh_string pubkey; + MD5CTX ctx; + unsigned char *h; + + if (session == NULL || hash == NULL) { + return SSH_ERROR; + } + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL){ + ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); + return SSH_ERROR; + } + + h = malloc(sizeof(unsigned char) * MD5_DIGEST_LEN); + if (h == NULL) { + return SSH_ERROR; + } + + ctx = md5_init(); + if (ctx == NULL) { + SAFE_FREE(h); + return SSH_ERROR; + } + + pubkey = session->current_crypto->server_pubkey; + + md5_update(ctx, ssh_string_data(pubkey), ssh_string_len(pubkey)); + md5_final(h, ctx); + + *hash = h; + + return MD5_DIGEST_LEN; +} + +/** + * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. + * + * This is required under Microsoft platform as this library might use a + * different C library than your software, hence a different heap. + * + * @param[in] hash The buffer to deallocate. + * + * @see ssh_get_pubkey_hash() + */ +void ssh_clean_pubkey_hash(unsigned char **hash) { + SAFE_FREE(*hash); + *hash = NULL; +} + +/** + * @brief Get the server public key from a session. + * + * @param[in] session The session to get the key from. + * + * @param[out] key A pointer to store the allocated key. You need to free + * the key. + * + * @return SSH_OK on success, SSH_ERROR on errror. + * + * @see ssh_key_free() + */ +int ssh_get_publickey(ssh_session session, ssh_key *key) +{ + if (session==NULL || + session->current_crypto ==NULL || + session->current_crypto->server_pubkey == NULL) { + return SSH_ERROR; + } + + return ssh_pki_import_pubkey_blob(session->current_crypto->server_pubkey, + key); +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/ecdh.c b/libssh/src/ecdh.c new file mode 100644 index 00000000..bc0b03ce --- /dev/null +++ b/libssh/src/ecdh.c @@ -0,0 +1,278 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" + +#ifdef HAVE_ECDH +#include + +#define NISTP256 NID_X9_62_prime256v1 +#define NISTP384 NID_secp384r1 +#define NISTP521 NID_secp521r1 + +/** @internal + * @brief Starts ecdh-sha2-nistp256 key exchange + */ +int ssh_client_ecdh_init(ssh_session session){ + EC_KEY *key=NULL; + const EC_GROUP *group; + const EC_POINT *pubkey; + ssh_string client_pubkey; + int len; + int rc; + bignum_CTX ctx=BN_CTX_new(); + enter_function(); + if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT) < 0) { + goto error; + } + key = EC_KEY_new_by_curve_name(NISTP256); + group = EC_KEY_get0_group(key); + EC_KEY_generate_key(key); + pubkey=EC_KEY_get0_public_key(key); + len = EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, + NULL,0,ctx); + client_pubkey=ssh_string_new(len); + + EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(client_pubkey),len,ctx); + buffer_add_ssh_string(session->out_buffer,client_pubkey); + BN_CTX_free(ctx); + session->next_crypto->ecdh_privkey = key; + session->next_crypto->ecdh_client_pubkey = client_pubkey; + rc = packet_send(session); + leave_function(); + return rc; +error: + leave_function(); + return SSH_ERROR; +} + +static void ecdh_import_pubkey(ssh_session session, ssh_string pubkey_string) { + session->next_crypto->server_pubkey = pubkey_string; +} + +static int ecdh_build_k(ssh_session session) { + const EC_GROUP *group = EC_KEY_get0_group(session->next_crypto->ecdh_privkey); + EC_POINT *pubkey; + void *buffer; + int len = (EC_GROUP_get_degree(group) + 7) / 8; + bignum_CTX ctx = bignum_ctx_new(); + if (ctx == NULL) { + return -1; + } + + session->next_crypto->k = bignum_new(); + if (session->next_crypto->k == NULL) { + bignum_ctx_free(ctx); + return -1; + } + + pubkey = EC_POINT_new(group); + if (pubkey == NULL) { + bignum_ctx_free(ctx); + return -1; + } + + if (session->server) + EC_POINT_oct2point(group,pubkey,ssh_string_data(session->next_crypto->ecdh_client_pubkey), + ssh_string_len(session->next_crypto->ecdh_client_pubkey),ctx); + else + EC_POINT_oct2point(group,pubkey,ssh_string_data(session->next_crypto->ecdh_server_pubkey), + ssh_string_len(session->next_crypto->ecdh_server_pubkey),ctx); + buffer = malloc(len); + ECDH_compute_key(buffer,len,pubkey,session->next_crypto->ecdh_privkey,NULL); + EC_POINT_free(pubkey); + BN_bin2bn(buffer,len,session->next_crypto->k); + free(buffer); + EC_KEY_free(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey=NULL; +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Session server cookie", + session->next_crypto->server_kex.cookie, 16); + ssh_print_hexa("Session client cookie", + session->next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", session->next_crypto->k); +#endif + +#ifdef HAVE_LIBCRYPTO + bignum_ctx_free(ctx); +#endif + + return 0; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +int ssh_client_ecdh_reply(ssh_session session, ssh_buffer packet){ + ssh_string q_s_string = NULL; + ssh_string pubkey = NULL; + ssh_string signature = NULL; + int rc; + pubkey = buffer_get_ssh_string(packet); + if (pubkey == NULL){ + ssh_set_error(session,SSH_FATAL, "No public key in packet"); + goto error; + } + ecdh_import_pubkey(session, pubkey); + + q_s_string = buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session,SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_server_pubkey = q_s_string; + signature = buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature=NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ecdh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc=packet_send(session); + ssh_log(session, SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); + return rc; +error: + return SSH_ERROR; +} + +#ifdef WITH_SERVER + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ + +int ssh_server_ecdh_init(ssh_session session, ssh_buffer packet){ + /* ECDH keys */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + EC_KEY *ecdh_key=NULL; + const EC_GROUP *group; + const EC_POINT *ecdh_pubkey; + bignum_CTX ctx; + /* SSH host keys (rsa,dsa,ecdsa) */ + ssh_key privkey; + ssh_string sig_blob = NULL; + int len; + int rc; + + enter_function(); + + /* Extract the client pubkey from the init packet */ + + q_c_string = buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session,SSH_FATAL, "No Q_C ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_client_pubkey = q_c_string; + + /* Build server's keypair */ + + ctx = BN_CTX_new(); + ecdh_key = EC_KEY_new_by_curve_name(NISTP256); + group = EC_KEY_get0_group(ecdh_key); + EC_KEY_generate_key(ecdh_key); + ecdh_pubkey=EC_KEY_get0_public_key(ecdh_key); + len = EC_POINT_point2oct(group,ecdh_pubkey,POINT_CONVERSION_UNCOMPRESSED, + NULL,0,ctx); + q_s_string=ssh_string_new(len); + + EC_POINT_point2oct(group,ecdh_pubkey,POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(q_s_string),len,ctx); + + BN_CTX_free(ctx); + session->next_crypto->ecdh_privkey = ecdh_key; + session->next_crypto->ecdh_server_pubkey = q_s_string; + + buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_REPLY); + /* build k and session_id */ + if (ecdh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + if (ssh_get_key_params(session, &privkey) == SSH_ERROR) + goto error; + if (make_sessionid(session) != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + /* add host's public key */ + buffer_add_ssh_string(session->out_buffer, session->next_crypto->server_pubkey); + /* add ecdh public key */ + buffer_add_ssh_string(session->out_buffer,q_s_string); + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + buffer_add_ssh_string(session->out_buffer, sig_blob); + ssh_string_free(sig_blob); + /* Free private keys as they should not be readable after this point */ + if (session->srv.rsa_key) { + ssh_key_free(session->srv.rsa_key); + session->srv.rsa_key = NULL; + } + if (session->srv.dsa_key) { + ssh_key_free(session->srv.dsa_key); + session->srv.dsa_key = NULL; + } + + ssh_log(session,SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + rc = packet_send(session); + if (rc == SSH_ERROR) + goto error; + + /* Send the MSG_NEWKEYS */ + if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; + rc=packet_send(session); + ssh_log(session, SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); + return rc; + error: + return SSH_ERROR; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/libssh/src/error.c b/libssh/src/error.c new file mode 100644 index 00000000..f4a2af00 --- /dev/null +++ b/libssh/src/error.c @@ -0,0 +1,139 @@ +/* + * error.c - functions for ssh error handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include "libssh/priv.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_error The SSH error functions. + * @ingroup libssh + * + * Functions for error handling. + * + * @{ + */ + +/** + * @internal + * + * @brief Registers an error with a description. + * + * @param error The place to store the error. + * + * @param code The class of error. + * + * @param descr The description, which can be a format string. + * + * @param ... The arguments for the format string. + */ +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) +{ + struct ssh_common_struct *err = error; + va_list va; + + va_start(va, descr); + vsnprintf(err->error.error_buffer, ERROR_BUFFERLEN, descr, va); + va_end(va); + + err->error.error_code = code; + ssh_log_common(err, + SSH_LOG_WARN, + function, + "Error: %s", + err->error.error_buffer); +} + +/** + * @internal + * + * @brief Registers an out of memory error + * + * @param error The place to store the error. + * + */ +void _ssh_set_error_oom(void *error, const char *function) +{ + struct error_struct *err = error; + + snprintf(err->error_buffer, sizeof(err->error_buffer), + "%s: Out of memory", function); + err->error_code = SSH_FATAL; +} + +/** + * @internal + * + * @brief Registers an invalid argument error + * + * @param error The place to store the error. + * + * @param function The function the error happened in. + * + */ +void _ssh_set_error_invalid(void *error, const char *function) +{ + _ssh_set_error(error, SSH_FATAL, function, + "Invalid argument in %s", function); +} + +/** + * @brief Retrieve the error text message from the last error. + * + * @param error The SSH session pointer. + * + * @return A static string describing the error. + */ +const char *ssh_get_error(void *error) { + struct error_struct *err = error; + + return err->error_buffer; +} + +/** + * @brief Retrieve the error code from the last error. + * + * @param error The SSH session pointer. + * + * \return SSH_NO_ERROR No error occurred\n + * SSH_REQUEST_DENIED The last request was denied but situation is + * recoverable\n + * SSH_FATAL A fatal error occurred. This could be an unexpected + * disconnection\n + * + * Other error codes are internal but can be considered same than + * SSH_FATAL. + */ +int ssh_get_error_code(void *error) { + struct error_struct *err = error; + + return err->error_code; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/gcrypt_missing.c b/libssh/src/gcrypt_missing.c new file mode 100644 index 00000000..b21e5f30 --- /dev/null +++ b/libssh/src/gcrypt_missing.c @@ -0,0 +1,101 @@ +/* + * gcrypt_missing.c - routines that are in OpenSSL but not in libgcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2006 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include + +#include "libssh/priv.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_LIBGCRYPT +int my_gcry_dec2bn(bignum *bn, const char *data) { + int count; + + *bn = bignum_new(); + if (*bn == NULL) { + return 0; + } + gcry_mpi_set_ui(*bn, 0); + for (count = 0; data[count]; count++) { + gcry_mpi_mul_ui(*bn, *bn, 10); + gcry_mpi_add_ui(*bn, *bn, data[count] - '0'); + } + + return count; +} + +char *my_gcry_bn2dec(bignum bn) { + bignum bndup, num, ten; + char *ret; + int count, count2; + int size, rsize; + char decnum; + + size = gcry_mpi_get_nbits(bn) * 3; + rsize = size / 10 + size / 1000 + 2; + + ret = malloc(rsize + 1); + if (ret == NULL) { + return NULL; + } + + if (!gcry_mpi_cmp_ui(bn, 0)) { + strcpy(ret, "0"); + } else { + ten = bignum_new(); + if (ten == NULL) { + SAFE_FREE(ret); + return NULL; + } + + num = bignum_new(); + if (num == NULL) { + SAFE_FREE(ret); + bignum_free(ten); + return NULL; + } + + for (bndup = gcry_mpi_copy(bn), bignum_set_word(ten, 10), count = rsize; + count; count--) { + gcry_mpi_div(bndup, num, bndup, ten, 0); + for (decnum = 0, count2 = gcry_mpi_get_nbits(num); count2; + decnum *= 2, decnum += (gcry_mpi_test_bit(num, count2 - 1) ? 1 : 0), + count2--) + ; + ret[count - 1] = decnum + '0'; + } + for (count = 0; count < rsize && ret[count] == '0'; count++) + ; + for (count2 = 0; count2 < rsize - count; ++count2) { + ret[count2] = ret[count2 + count]; + } + ret[count2] = 0; + bignum_free(num); + bignum_free(bndup); + bignum_free(ten); + } + + return ret; +} + +#endif +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/getpass.c b/libssh/src/getpass.c new file mode 100644 index 00000000..cc6d33ca --- /dev/null +++ b/libssh/src/getpass.c @@ -0,0 +1,282 @@ +/* + * getpass.c - platform independent getpass function. + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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. + */ + +#include +#include +#include + +#include + +/** + * @internal + * + * @brief Get the password from the console. + * + * @param[in] prompt The prompt to display. + * + * @param[in] buf The buffer to fill. + * + * @param[in] len The length of the buffer. + * + * @param[in] verify Should the password be verified? + * + * @return 1 on success, 0 on error. + */ +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) { + char *tmp; + char *ptr = NULL; + int ok = 0; + + tmp = malloc(len); + if (tmp == NULL) { + return 0; + } + memset(tmp,'\0',len); + + /* read the password */ + while (!ok) { + if (buf[0] != '\0') { + fprintf(stdout, "%s[%s] ", prompt, buf); + } else { + fprintf(stdout, "%s", prompt); + } + fflush(stdout); + if (fgets(tmp, len, stdin) == NULL) { + free(tmp); + return 0; + } + + if ((ptr = strchr(tmp, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + + if (*tmp) { + strncpy(buf, tmp, len); + } + + if (verify) { + char *key_string; + + key_string = malloc(len); + if (key_string == NULL) { + break; + } + memset(key_string, '\0', len); + + fprintf(stdout, "\nVerifying, please re-enter. %s", prompt); + fflush(stdout); + if (! fgets(key_string, len, stdin)) { + memset(key_string, '\0', len); + SAFE_FREE(key_string); + clearerr(stdin); + continue; + } + if ((ptr = strchr(key_string, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + if (strcmp(buf, key_string)) { + printf("\n\07\07Mismatch - try again\n"); + memset(key_string, '\0', len); + SAFE_FREE(key_string); + fflush(stdout); + continue; + } + memset(key_string, '\0', len); + SAFE_FREE(key_string); + } + ok = 1; + } + memset(tmp, '\0', len); + free(tmp); + + return ok; +} + +#ifdef _WIN32 +#include + +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) { + HANDLE h; + DWORD mode = 0; + int ok; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + /* get stdin and mode */ + h = GetStdHandle(STD_INPUT_HANDLE); + if (!GetConsoleMode(h, &mode)) { + return -1; + } + + /* disable echo */ + if (!echo) { + if (!SetConsoleMode(h, mode & ~ENABLE_ECHO_INPUT)) { + return -1; + } + } + + ok = ssh_gets(prompt, buf, len, verify); + + /* reset echo */ + SetConsoleMode(h, mode); + + if (!ok) { + memset (buf, '\0', len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#else + +#include +#include +#include + +/** + * @ingroup libssh_misc + * + * @brief Get a password from the console. + * + * You should make sure that the buffer is an empty string! + * + * You can also use this function to ask for a username. Then you can fill the + * buffer with the username and it is shows to the users. If the users just + * presses enter the buffer will be untouched. + * + * @code + * char username[128]; + * + * snprintf(username, sizeof(username), "john"); + * + * ssh_getpass("Username:", username, sizeof(username), 1, 0); + * @endcode + * + * The prompt will look like this: + * + * Username: [john] + * + * If you press enter then john is used as the username, or you can type it in + * to change it. + * + * @param[in] prompt The prompt to show to ask for the password. + * + * @param[out] buf The buffer the password should be stored. It NEEDS to be + * empty or filled out. + * + * @param[in] len The length of the buffer. + * + * @param[in] echo Should we echo what you type. + * + * @param[in] verify Should we ask for the password twice. + * + * @return 0 on success, -1 on error. + */ +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) { + struct termios attr; + struct termios old_attr; + int ok = 0; + int fd = -1; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + if (isatty(STDIN_FILENO)) { + ZERO_STRUCT(attr); + ZERO_STRUCT(old_attr); + + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + + /* save terminal attributes */ + memcpy(&old_attr, &attr, sizeof(attr)); + if((fd = fcntl(0, F_GETFL, 0)) < 0) { + perror("fcntl"); + return -1; + } + + /* disable echo */ + if (!echo) { + attr.c_lflag &= ~(ECHO); + } + + /* write attributes to terminal */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr) < 0) { + perror("tcsetattr"); + return -1; + } + } + + /* disable nonblocking I/O */ + if (fd & O_NDELAY) { + fcntl(0, F_SETFL, fd & ~O_NDELAY); + } + + ok = ssh_gets(prompt, buf, len, verify); + + if (isatty(STDIN_FILENO)) { + /* reset terminal */ + tcsetattr(STDIN_FILENO, TCSANOW, &old_attr); + } + + /* close fd */ + if (fd & O_NDELAY) { + fcntl(0, F_SETFL, fd); + } + + if (!ok) { + memset (buf, '\0', len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#endif + +/* vim: set ts=4 sw=4 et cindent syntax=c.doxygen: */ diff --git a/libssh/src/gzip.c b/libssh/src/gzip.c new file mode 100644 index 00000000..339217d4 --- /dev/null +++ b/libssh/src/gzip.c @@ -0,0 +1,221 @@ +/* + * gzip.c - hooks for compression of packets + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/session.h" + +#define BLOCKSIZE 4092 + +static z_stream *initcompress(ssh_session session, int level) { + z_stream *stream = NULL; + int status; + + stream = malloc(sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } + memset(stream, 0, sizeof(z_stream)); + + status = deflateInit(stream, level); + if (status != Z_OK) { + SAFE_FREE(stream); + ssh_set_error(session, SSH_FATAL, + "status %d inititalising zlib deflate", status); + return NULL; + } + + return stream; +} + +static ssh_buffer gzip_compress(ssh_session session,ssh_buffer source,int level){ + z_stream *zout = session->current_crypto->compress_out_ctx; + void *in_ptr = buffer_get_rest(source); + unsigned long in_size = buffer_get_rest_len(source); + ssh_buffer dest = NULL; + unsigned char out_buf[BLOCKSIZE] = {0}; + unsigned long len; + int status; + + if(zout == NULL) { + zout = session->current_crypto->compress_out_ctx = initcompress(session, level); + if (zout == NULL) { + return NULL; + } + } + + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; + } + + zout->next_out = out_buf; + zout->next_in = in_ptr; + zout->avail_in = in_size; + do { + zout->avail_out = BLOCKSIZE; + status = deflate(zout, Z_PARTIAL_FLUSH); + if (status != Z_OK) { + ssh_buffer_free(dest); + ssh_set_error(session, SSH_FATAL, + "status %d deflating zlib packet", status); + return NULL; + } + len = BLOCKSIZE - zout->avail_out; + if (buffer_add_data(dest, out_buf, len) < 0) { + ssh_buffer_free(dest); + return NULL; + } + zout->next_out = out_buf; + } while (zout->avail_out == 0); + + return dest; +} + +int compress_buffer(ssh_session session, ssh_buffer buf) { + ssh_buffer dest = NULL; + + dest = gzip_compress(session, buf, session->opts.compressionlevel); + if (dest == NULL) { + return -1; + } + + if (buffer_reinit(buf) < 0) { + ssh_buffer_free(dest); + return -1; + } + + if (buffer_add_data(buf, buffer_get_rest(dest), buffer_get_rest_len(dest)) < 0) { + ssh_buffer_free(dest); + return -1; + } + + ssh_buffer_free(dest); + return 0; +} + +/* decompression */ + +static z_stream *initdecompress(ssh_session session) { + z_stream *stream = NULL; + int status; + + stream = malloc(sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } + memset(stream,0,sizeof(z_stream)); + + status = inflateInit(stream); + if (status != Z_OK) { + SAFE_FREE(stream); + ssh_set_error(session, SSH_FATAL, + "Status = %d initiating inflate context!", status); + return NULL; + } + + return stream; +} + +static ssh_buffer gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) { + z_stream *zin = session->current_crypto->compress_in_ctx; + void *in_ptr = buffer_get_rest(source); + unsigned long in_size = buffer_get_rest_len(source); + unsigned char out_buf[BLOCKSIZE] = {0}; + ssh_buffer dest = NULL; + unsigned long len; + int status; + + if (zin == NULL) { + zin = session->current_crypto->compress_in_ctx = initdecompress(session); + if (zin == NULL) { + return NULL; + } + } + + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; + } + + zin->next_out = out_buf; + zin->next_in = in_ptr; + zin->avail_in = in_size; + + do { + zin->avail_out = BLOCKSIZE; + status = inflate(zin, Z_PARTIAL_FLUSH); + if (status != Z_OK && status != Z_BUF_ERROR) { + ssh_set_error(session, SSH_FATAL, + "status %d inflating zlib packet", status); + ssh_buffer_free(dest); + return NULL; + } + + len = BLOCKSIZE - zin->avail_out; + if (buffer_add_data(dest,out_buf,len) < 0) { + ssh_buffer_free(dest); + return NULL; + } + if (buffer_get_rest_len(dest) > maxlen){ + /* Size of packet exceeded, avoid a denial of service attack */ + ssh_buffer_free(dest); + return NULL; + } + zin->next_out = out_buf; + } while (zin->avail_out == 0); + + return dest; +} + +int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen){ + ssh_buffer dest = NULL; + + dest = gzip_decompress(session,buf, maxlen); + if (dest == NULL) { + return -1; + } + + if (buffer_reinit(buf) < 0) { + ssh_buffer_free(dest); + return -1; + } + + if (buffer_add_data(buf, buffer_get_rest(dest), buffer_get_rest_len(dest)) < 0) { + ssh_buffer_free(dest); + return -1; + } + + ssh_buffer_free(dest); + return 0; +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/init.c b/libssh/src/init.c new file mode 100644 index 00000000..241b8618 --- /dev/null +++ b/libssh/src/init.c @@ -0,0 +1,85 @@ +/* + * init.c - initialization and finalization of the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/dh.h" +#include "libssh/poll.h" +#include "libssh/threads.h" + +#ifdef _WIN32 +#include +#endif + +/** + * @defgroup libssh The libssh API + * + * The libssh library is implementing the SSH protocols and some of its + * extensions. This group of functions is mostly used to implment a SSH client. + * Some function are needed to implement a SSH server too. + * + * @{ + */ + +/** + * @brief Initialize global cryptographic data structures. + * + * This function should only be called once, at the beginning of the program, in + * the main thread. It may be omitted if your program is not multithreaded. + * + * @returns 0 on success, -1 if an error occured. + */ +int ssh_init(void) { + if(ssh_threads_init()) + return -1; + if(ssh_crypto_init()) + return -1; + if(ssh_socket_init()) + return -1; + return 0; +} + + +/** + * @brief Finalize and cleanup all libssh and cryptographic data structures. + * + * This function should only be called once, at the end of the program! + * + * @returns 0 on succes, -1 if an error occured. + * + @returns 0 otherwise + */ +int ssh_finalize(void) { + ssh_crypto_finalize(); + ssh_socket_cleanup(); + /* It is important to finalize threading after CRYPTO because + * it still depends on it */ + ssh_threads_finalize(); + + return 0; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/kex.c b/libssh/src/kex.c new file mode 100644 index 00000000..e900a91a --- /dev/null +++ b/libssh/src/kex.c @@ -0,0 +1,501 @@ +/* + * kex.c - key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/dh.h" +#include "libssh/kex.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" +#include "libssh/string.h" + +#ifdef HAVE_LIBGCRYPT +# define BLOWFISH "blowfish-cbc," +# define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc," +# define DES "3des-cbc,des-cbc-ssh1" +#elif defined(HAVE_LIBCRYPTO) +# ifdef HAVE_OPENSSL_BLOWFISH_H +# define BLOWFISH "blowfish-cbc," +# else +# define BLOWFISH "" +# endif +# ifdef HAVE_OPENSSL_AES_H +# ifdef BROKEN_AES_CTR +# define AES "aes256-cbc,aes192-cbc,aes128-cbc," +# else +# define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc," +# endif /* BROKEN_AES_CTR */ +# else +# define AES "" +# endif +# define DES "3des-cbc,des-cbc-ssh1" +#endif + +#ifdef WITH_ZLIB +#define ZLIB "none,zlib,zlib@openssh.com" +#else +#define ZLIB "none" +#endif + +#ifdef HAVE_ECDH +#define KEY_EXCHANGE "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" +#define HOSTKEYS "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss" +#else +#define KEY_EXCHANGE "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" +#define HOSTKEYS "ssh-rsa,ssh-dss" +#endif + +#define KEX_METHODS_SIZE 10 + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *default_methods[] = { + KEY_EXCHANGE, + HOSTKEYS, + AES BLOWFISH DES, + AES BLOWFISH DES, + "hmac-sha1", + "hmac-sha1", + "none", + "none", + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *supported_methods[] = { + KEY_EXCHANGE, + HOSTKEYS, + AES BLOWFISH DES, + AES BLOWFISH DES, + "hmac-sha1", + "hmac-sha1", + ZLIB, + ZLIB, + "", + "", + NULL +}; + +/* descriptions of the key exchange packet */ +static const char *ssh_kex_descriptions[] = { + "kex algos", + "server host key algo", + "encryption client->server", + "encryption server->client", + "mac algo client->server", + "mac algo server->client", + "compression algo client->server", + "compression algo server->client", + "languages client->server", + "languages server->client", + NULL +}; + +/* tokenize will return a token of strings delimited by ",". the first element has to be freed */ +static char **tokenize(const char *chain){ + char **tokens; + int n=1; + int i=0; + char *tmp; + char *ptr; + + tmp = strdup(chain); + if (tmp == NULL) { + return NULL; + } + ptr = tmp; + while(*ptr){ + if(*ptr==','){ + n++; + *ptr=0; + } + ptr++; + } + /* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */ + tokens=malloc(sizeof(char *) * (n+1) ); /* +1 for the null */ + if (tokens == NULL) { + SAFE_FREE(tmp); + return NULL; + } + ptr=tmp; + for(i=0;i= KEX_METHODS_SIZE) { + return NULL; + } + + return supported_methods[algo]; +} + +const char *ssh_kex_get_description(uint32_t algo) { + if (algo >= KEX_METHODS_SIZE) { + return NULL; + } + + return ssh_kex_descriptions[algo]; +} + +/* find_matching gets 2 parameters : a list of available objects (available_d), separated by colons,*/ +/* and a list of preferred objects (preferred_d) */ +/* it will return a strduped pointer on the first preferred object found in the available objects list */ + +char *ssh_find_matching(const char *available_d, const char *preferred_d){ + char ** tok_available, **tok_preferred; + int i_avail, i_pref; + char *ret; + + if ((available_d == NULL) || (preferred_d == NULL)) { + return NULL; /* don't deal with null args */ + } + + tok_available = tokenize(available_d); + if (tok_available == NULL) { + return NULL; + } + + tok_preferred = tokenize(preferred_d); + if (tok_preferred == NULL) { + SAFE_FREE(tok_available[0]); + SAFE_FREE(tok_available); + return NULL; + } + + for(i_pref=0; tok_preferred[i_pref] ; ++i_pref){ + for(i_avail=0; tok_available[i_avail]; ++i_avail){ + if(strcmp(tok_available[i_avail],tok_preferred[i_pref]) == 0){ + /* match */ + ret=strdup(tok_available[i_avail]); + /* free the tokens */ + SAFE_FREE(tok_available[0]); + SAFE_FREE(tok_preferred[0]); + SAFE_FREE(tok_available); + SAFE_FREE(tok_preferred); + return ret; + } + } + } + SAFE_FREE(tok_available[0]); + SAFE_FREE(tok_preferred[0]); + SAFE_FREE(tok_available); + SAFE_FREE(tok_preferred); + return NULL; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexinit){ + int server_kex=session->server; + ssh_string str = NULL; + char *strings[KEX_METHODS_SIZE]; + int i; + + enter_function(); + (void)type; + (void)user; + memset(strings, 0, sizeof(strings)); + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED){ + ssh_log(session,SSH_LOG_WARNING, "Other side initiating key re-exchange"); + } else if(session->session_state != SSH_SESSION_STATE_INITIAL_KEX){ + ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); + goto error; + } + if (server_kex) { + if (buffer_get_data(packet,session->next_crypto->client_kex.cookie,16) != 16) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + if (hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie) < 0) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + } else { + if (buffer_get_data(packet,session->next_crypto->server_kex.cookie,16) != 16) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + if (hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie) < 0) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + } + + for (i = 0; i < KEX_METHODS_SIZE; i++) { + str = buffer_get_ssh_string(packet); + if (str == NULL) { + break; + } + + if (buffer_add_ssh_string(session->in_hashbuf, str) < 0) { + ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); + goto error; + } + + strings[i] = ssh_string_to_char(str); + if (strings[i] == NULL) { + ssh_set_error_oom(session); + goto error; + } + ssh_string_free(str); + str = NULL; + } + + /* copy the server kex info into an array of strings */ + if (server_kex) { + for (i = 0; i < SSH_KEX_METHODS; i++) { + session->next_crypto->client_kex.methods[i] = strings[i]; + } + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + session->next_crypto->server_kex.methods[i] = strings[i]; + } + } + + leave_function(); + session->session_state=SSH_SESSION_STATE_KEXINIT_RECEIVED; + session->dh_handshake_state=DH_STATE_INIT; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; +error: + ssh_string_free(str); + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(strings[i]); + } + + session->session_state = SSH_SESSION_STATE_ERROR; + leave_function(); + return SSH_PACKET_USED; +} + +void ssh_list_kex(ssh_session session, struct ssh_kex_struct *kex) { + int i = 0; + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("session cookie", kex->cookie, 16); +#endif + + for(i = 0; i < SSH_KEX_METHODS; i++) { + if (kex->methods[i] == NULL) { + continue; + } + ssh_log(session, SSH_LOG_FUNCTIONS, "%s: %s", + ssh_kex_descriptions[i], kex->methods[i]); + } +} + +/** + * @brief sets the key exchange parameters to be sent to the server, + * in function of the options and available methods. + */ +int set_client_kex(ssh_session session){ + struct ssh_kex_struct *client= &session->next_crypto->client_kex; + const char *wanted; + int i; + + ssh_get_random(client->cookie, 16, 0); + + memset(client->methods, 0, KEX_METHODS_SIZE * sizeof(char **)); + for (i = 0; i < KEX_METHODS_SIZE; i++) { + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) + wanted = default_methods[i]; + client->methods[i] = strdup(wanted); + } + + return SSH_OK; +} + +/** @brief Select the different methods on basis of client's and + * server's kex messages, and watches out if a match is possible. + */ +int ssh_kex_select_methods (ssh_session session){ + struct ssh_kex_struct *server = &session->next_crypto->server_kex; + struct ssh_kex_struct *client = &session->next_crypto->client_kex; + int rc = SSH_ERROR; + int i; + + enter_function(); + + for (i = 0; i < KEX_METHODS_SIZE; i++) { + session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); + if(session->next_crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S){ + ssh_set_error(session,SSH_FATAL,"kex error : no match for method %s: server [%s], client [%s]", + ssh_kex_descriptions[i],server->methods[i],client->methods[i]); + goto error; + } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { + /* we can safely do that for languages */ + session->next_crypto->kex_methods[i] = strdup(""); + } + } + if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ + session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; + } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ + session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; + } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ + session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; + } + rc = SSH_OK; +error: + leave_function(); + return rc; +} + + +/* this function only sends the predefined set of kex methods */ +int ssh_send_kex(ssh_session session, int server_kex) { + struct ssh_kex_struct *kex = (server_kex ? &session->next_crypto->server_kex : + &session->next_crypto->client_kex); + ssh_string str = NULL; + int i; + + enter_function(); + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXINIT) < 0) { + goto error; + } + if (buffer_add_data(session->out_buffer, kex->cookie, 16) < 0) { + goto error; + } + + if (hashbufout_add_cookie(session) < 0) { + goto error; + } + + ssh_list_kex(session, kex); + + for (i = 0; i < KEX_METHODS_SIZE; i++) { + str = ssh_string_from_char(kex->methods[i]); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(session->out_hashbuf, str) < 0) { + goto error; + } + if (buffer_add_ssh_string(session->out_buffer, str) < 0) { + goto error; + } + ssh_string_free(str); + } + + if (buffer_add_u8(session->out_buffer, 0) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer, 0) < 0) { + goto error; + } + + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return -1; + } + + leave_function(); + return 0; +error: + buffer_reinit(session->out_buffer); + buffer_reinit(session->out_hashbuf); + ssh_string_free(str); + + leave_function(); + return -1; +} + +/* returns 1 if at least one of the name algos is in the default algorithms table */ +int verify_existing_algo(int algo, const char *name){ + char *ptr; + if(algo>9 || algo <0) + return -1; + ptr=ssh_find_matching(supported_methods[algo],name); + if(ptr){ + free(ptr); + return 1; + } + return 0; +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/kex1.c b/libssh/src/kex1.c new file mode 100644 index 00000000..b11cf007 --- /dev/null +++ b/libssh/src/kex1.c @@ -0,0 +1,496 @@ +/* + * kex.c - key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifndef _WIN32 +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/kex.h" +#include "libssh/keys.h" +#include "libssh/session.h" +#include "libssh/ssh1.h" +#include "libssh/wrapper.h" + +/* SSHv1 functions */ + +/* makes a STRING contating 3 strings : ssh-rsa1,e and n */ +/* this is a public key in openssh's format */ +static ssh_string make_rsa1_string(ssh_string e, ssh_string n){ + ssh_buffer buffer = NULL; + ssh_string rsa = NULL; + ssh_string ret = NULL; + + buffer = ssh_buffer_new(); + rsa = ssh_string_from_char("ssh-rsa1"); + + if (buffer_add_ssh_string(buffer, rsa) < 0) { + goto error; + } + if (buffer_add_ssh_string(buffer, e) < 0) { + goto error; + } + if (buffer_add_ssh_string(buffer, n) < 0) { + goto error; + } + + ret = ssh_string_new(ssh_buffer_get_len(buffer)); + if (ret == NULL) { + goto error; + } + + ssh_string_fill(ret, ssh_buffer_get_begin(buffer), ssh_buffer_get_len(buffer)); +error: + ssh_buffer_free(buffer); + ssh_string_free(rsa); + + return ret; +} + +static int build_session_id1(ssh_session session, ssh_string servern, + ssh_string hostn) { + MD5CTX md5 = NULL; + + md5 = md5_init(); + if (md5 == NULL) { + return -1; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("host modulus",ssh_string_data(hostn),ssh_string_len(hostn)); + ssh_print_hexa("server modulus",ssh_string_data(servern),ssh_string_len(servern)); +#endif + md5_update(md5,ssh_string_data(hostn),ssh_string_len(hostn)); + md5_update(md5,ssh_string_data(servern),ssh_string_len(servern)); + md5_update(md5,session->next_crypto->server_kex.cookie,8); + if(session->next_crypto->session_id != NULL) + SAFE_FREE(session->next_crypto->session_id); + session->next_crypto->session_id = malloc(MD5_DIGEST_LEN); + if(session->next_crypto->session_id == NULL){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + md5_final(session->next_crypto->session_id,md5); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("session_id",session->next_crypto->session_id,MD5_DIGEST_LEN); +#endif + + return 0; +} + +/* returns 1 if the modulus of k1 is < than the one of k2 */ +static int modulus_smaller(ssh_public_key k1, ssh_public_key k2){ + bignum n1; + bignum n2; + int res; +#ifdef HAVE_LIBGCRYPT + gcry_sexp_t sexp; + sexp=gcry_sexp_find_token(k1->rsa_pub,"n",0); + n1=gcry_sexp_nth_mpi(sexp,1,GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + sexp=gcry_sexp_find_token(k2->rsa_pub,"n",0); + n2=gcry_sexp_nth_mpi(sexp,1,GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); +#elif defined HAVE_LIBCRYPTO + n1=k1->rsa_pub->n; + n2=k2->rsa_pub->n; +#endif + if(bignum_cmp(n1,n2)<0) + res=1; + else + res=0; +#ifdef HAVE_LIBGCRYPT + bignum_free(n1); + bignum_free(n2); +#endif + return res; + +} + +static ssh_string ssh_encrypt_rsa1(ssh_session session, + ssh_string data, + ssh_public_key key) { + ssh_string str = NULL; + size_t len = ssh_string_len(data); + size_t size = 0; +#ifdef HAVE_LIBGCRYPT + const char *tmp = NULL; + gcry_sexp_t ret_sexp; + gcry_sexp_t data_sexp; + + if (gcry_sexp_build(&data_sexp, NULL, "(data(flags pkcs1)(value %b))", + len, ssh_string_data(data))) { + ssh_set_error(session, SSH_FATAL, "RSA1 encrypt: libgcrypt error"); + return NULL; + } + if (gcry_pk_encrypt(&ret_sexp, data_sexp, key->rsa_pub)) { + gcry_sexp_release(data_sexp); + ssh_set_error(session, SSH_FATAL, "RSA1 encrypt: libgcrypt error"); + return NULL; + } + + gcry_sexp_release(data_sexp); + + data_sexp = gcry_sexp_find_token(ret_sexp, "a", 0); + if (data_sexp == NULL) { + ssh_set_error(session, SSH_FATAL, "RSA1 encrypt: libgcrypt error"); + gcry_sexp_release(ret_sexp); + return NULL; + } + tmp = gcry_sexp_nth_data(data_sexp, 1, &size); + if (*tmp == 0) { + size--; + tmp++; + } + + str = ssh_string_new(size); + if (str == NULL) { + ssh_set_error(session, SSH_FATAL, "Not enough space"); + gcry_sexp_release(data_sexp); + gcry_sexp_release(ret_sexp); + return NULL; + } + ssh_string_fill(str, tmp, size); + + gcry_sexp_release(data_sexp); + gcry_sexp_release(ret_sexp); +#elif defined HAVE_LIBCRYPTO + size = RSA_size(key->rsa_pub); + + str = ssh_string_new(size); + if (str == NULL) { + ssh_set_error(session, SSH_FATAL, "Not enough space"); + return NULL; + } + + if (RSA_public_encrypt(len, ssh_string_data(data), ssh_string_data(str), key->rsa_pub, + RSA_PKCS1_PADDING) < 0) { + ssh_string_free(str); + return NULL; + } +#endif + + return str; +} + +#define ABS(A) ( (A)<0 ? -(A):(A) ) +static ssh_string encrypt_session_key(ssh_session session, ssh_public_key srvkey, + ssh_public_key hostkey, int slen, int hlen) { + unsigned char buffer[32] = {0}; + int i; + ssh_string data1 = NULL; + ssh_string data2 = NULL; + if(session->next_crypto->encryptkey != NULL) + SAFE_FREE(session->next_crypto->encryptkey); + if(session->next_crypto->decryptkey != NULL) + SAFE_FREE(session->next_crypto->decryptkey); + if(session->next_crypto->encryptIV != NULL) + SAFE_FREE(session->next_crypto->encryptIV); + if(session->next_crypto->decryptIV != NULL) + SAFE_FREE(session->next_crypto->decryptIV); + session->next_crypto->encryptkey = malloc(32); + session->next_crypto->decryptkey = malloc(32); + session->next_crypto->encryptIV = malloc(32); + session->next_crypto->decryptIV = malloc(32); + if(session->next_crypto->encryptkey == NULL || + session->next_crypto->decryptkey == NULL || + session->next_crypto->encryptIV == NULL || + session->next_crypto->decryptIV == NULL){ + ssh_set_error_oom(session); + return NULL; + } + /* first, generate a session key */ + ssh_get_random(session->next_crypto->encryptkey, 32, 1); + memcpy(buffer, session->next_crypto->encryptkey, 32); + memcpy(session->next_crypto->decryptkey, session->next_crypto->encryptkey, 32); + memset(session->next_crypto->encryptIV, 0, 32); + memset(session->next_crypto->decryptIV, 0, 32); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("session key",buffer,32); +#endif + + /* xor session key with session_id */ + for (i = 0; i < 16; i++) { + buffer[i] ^= session->next_crypto->session_id[i]; + } + data1 = ssh_string_new(32); + if (data1 == NULL) { + return NULL; + } + ssh_string_fill(data1, buffer, 32); + if (ABS(hlen - slen) < 128){ + ssh_log(session, SSH_LOG_FUNCTIONS, + "Difference between server modulus and host modulus is only %d. " + "It's illegal and may not work", + ABS(hlen - slen)); + } + + if (modulus_smaller(srvkey, hostkey)) { + data2 = ssh_encrypt_rsa1(session, data1, srvkey); + ssh_string_free(data1); + data1 = NULL; + if (data2 == NULL) { + return NULL; + } + data1 = ssh_encrypt_rsa1(session, data2, hostkey); + ssh_string_free(data2); + if (data1 == NULL) { + return NULL; + } + } else { + data2 = ssh_encrypt_rsa1(session, data1, hostkey); + ssh_string_free(data1); + data1 = NULL; + if (data2 == NULL) { + return NULL; + } + data1 = ssh_encrypt_rsa1(session, data2, srvkey); + ssh_string_free(data2); + if (data1 == NULL) { + return NULL; + } + } + + return data1; +} + +/* 2 SSH_SMSG_PUBLIC_KEY + * + * 8 bytes anti_spoofing_cookie + * 32-bit int server_key_bits + * mp-int server_key_public_exponent + * mp-int server_key_public_modulus + * 32-bit int host_key_bits + * mp-int host_key_public_exponent + * mp-int host_key_public_modulus + * 32-bit int protocol_flags + * 32-bit int supported_ciphers_mask + * 32-bit int supported_authentications_mask + */ +/** + * @brief Wait for a SSH_SMSG_PUBLIC_KEY and does the key exchange + */ +SSH_PACKET_CALLBACK(ssh_packet_publickey1){ + ssh_string server_exp = NULL; + ssh_string server_mod = NULL; + ssh_string host_exp = NULL; + ssh_string host_mod = NULL; + ssh_string serverkey = NULL; + ssh_string hostkey = NULL; + ssh_public_key srv = NULL; + ssh_public_key host = NULL; + uint32_t server_bits; + uint32_t host_bits; + uint32_t protocol_flags; + uint32_t supported_ciphers_mask; + uint32_t supported_authentications_mask; + ssh_string enc_session = NULL; + uint16_t bits; + int ko; + uint32_t support_3DES = 0; + uint32_t support_DES = 0; + enter_function(); + (void)type; + (void)user; + ssh_log(session, SSH_LOG_PROTOCOL, "Got a SSH_SMSG_PUBLIC_KEY"); + if(session->session_state != SSH_SESSION_STATE_INITIAL_KEX){ + ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); + goto error; + } + if (buffer_get_data(packet, session->next_crypto->server_kex.cookie, 8) != 8) { + ssh_set_error(session, SSH_FATAL, "Can't get cookie in buffer"); + goto error; + } + + buffer_get_u32(packet, &server_bits); + server_exp = buffer_get_mpint(packet); + if (server_exp == NULL) { + goto error; + } + server_mod = buffer_get_mpint(packet); + if (server_mod == NULL) { + goto error; + } + buffer_get_u32(packet, &host_bits); + host_exp = buffer_get_mpint(packet); + if (host_exp == NULL) { + goto error; + } + host_mod = buffer_get_mpint(packet); + if (host_mod == NULL) { + goto error; + } + buffer_get_u32(packet, &protocol_flags); + buffer_get_u32(packet, &supported_ciphers_mask); + ko = buffer_get_u32(packet, &supported_authentications_mask); + + if ((ko != sizeof(uint32_t)) || !host_mod || !host_exp + || !server_mod || !server_exp) { + ssh_log(session, SSH_LOG_RARE, "Invalid SSH_SMSG_PUBLIC_KEY packet"); + ssh_set_error(session, SSH_FATAL, "Invalid SSH_SMSG_PUBLIC_KEY packet"); + goto error; + } + + server_bits = ntohl(server_bits); + host_bits = ntohl(host_bits); + protocol_flags = ntohl(protocol_flags); + supported_ciphers_mask = ntohl(supported_ciphers_mask); + supported_authentications_mask = ntohl(supported_authentications_mask); + ssh_log(session, SSH_LOG_PROTOCOL, + "Server bits: %d; Host bits: %d; Protocol flags: %.8lx; " + "Cipher mask: %.8lx; Auth mask: %.8lx", + server_bits, + host_bits, + (unsigned long int) protocol_flags, + (unsigned long int) supported_ciphers_mask, + (unsigned long int) supported_authentications_mask); + + serverkey = make_rsa1_string(server_exp, server_mod); + if (serverkey == NULL) { + goto error; + } + hostkey = make_rsa1_string(host_exp,host_mod); + if (serverkey == NULL) { + goto error; + } + if (build_session_id1(session, server_mod, host_mod) < 0) { + goto error; + } + + srv = publickey_from_string(session, serverkey); + if (srv == NULL) { + goto error; + } + host = publickey_from_string(session, hostkey); + if (host == NULL) { + goto error; + } + + session->next_crypto->server_pubkey = ssh_string_copy(hostkey); + if (session->next_crypto->server_pubkey == NULL) { + goto error; + } + session->next_crypto->server_pubkey_type = "ssh-rsa1"; + + /* now, we must choose an encryption algo */ + /* hardcode 3des */ + // + support_3DES = (supported_ciphers_mask & (1<out_buffer, SSH_CMSG_SESSION_KEY) < 0) { + goto error; + } + if (buffer_add_u8(session->out_buffer, support_3DES ? SSH_CIPHER_3DES : SSH_CIPHER_DES) < 0) { + goto error; + } + if (buffer_add_data(session->out_buffer, session->next_crypto->server_kex.cookie, 8) < 0) { + goto error; + } + + enc_session = encrypt_session_key(session, srv, host, server_bits, host_bits); + if (enc_session == NULL) { + goto error; + } + + bits = ssh_string_len(enc_session) * 8 - 7; + ssh_log(session, SSH_LOG_PROTOCOL, "%d bits, %" PRIdS " bytes encrypted session", + bits, ssh_string_len(enc_session)); + bits = htons(bits); + /* the encrypted mpint */ + if (buffer_add_data(session->out_buffer, &bits, sizeof(uint16_t)) < 0) { + goto error; + } + if (buffer_add_data(session->out_buffer, ssh_string_data(enc_session), + ssh_string_len(enc_session)) < 0) { + goto error; + } + /* the protocol flags */ + if (buffer_add_u32(session->out_buffer, 0) < 0) { + goto error; + } + session->session_state=SSH_SESSION_STATE_KEXINIT_RECEIVED; + if (packet_send(session) == SSH_ERROR) { + goto error; + } + + /* we can set encryption */ + if(crypt_set_algorithms(session, support_3DES ? SSH_3DES : SSH_DES)){ + goto error; + } + + session->current_crypto = session->next_crypto; + session->next_crypto = NULL; + goto end; +error: + session->session_state=SSH_SESSION_STATE_ERROR; +end: + + ssh_string_free(host_mod); + ssh_string_free(host_exp); + ssh_string_free(server_mod); + ssh_string_free(server_exp); + ssh_string_free(serverkey); + ssh_string_free(hostkey); + ssh_string_free(enc_session); + + publickey_free(srv); + publickey_free(host); + + leave_function(); + return SSH_PACKET_USED; +} + +int ssh_get_kex1(ssh_session session) { + int ret=SSH_ERROR; + enter_function(); + ssh_log(session, SSH_LOG_PROTOCOL, "Waiting for a SSH_SMSG_PUBLIC_KEY"); + /* Here the callback is called */ + while(session->session_state==SSH_SESSION_STATE_INITIAL_KEX){ + ssh_handle_packets(session, SSH_TIMEOUT_USER); + } + if(session->session_state==SSH_SESSION_STATE_ERROR) + goto error; + ssh_log(session, SSH_LOG_PROTOCOL, "Waiting for a SSH_SMSG_SUCCESS"); + /* Waiting for SSH_SMSG_SUCCESS */ + while(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){ + ssh_handle_packets(session, SSH_TIMEOUT_USER); + } + if(session->session_state==SSH_SESSION_STATE_ERROR) + goto error; + ssh_log(session, SSH_LOG_PROTOCOL, "received SSH_SMSG_SUCCESS\n"); + ret=SSH_OK; +error: + leave_function(); + return ret; +} diff --git a/libssh/src/known_hosts.c b/libssh/src/known_hosts.c new file mode 100644 index 00000000..2d281e72 --- /dev/null +++ b/libssh/src/known_hosts.c @@ -0,0 +1,666 @@ +/* + * keyfiles.c - private and public key handling for authentication. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/options.h" + +/*todo: remove this include */ +#include "libssh/string.h" + +#ifdef HAVE_LIBGCRYPT +#include +#elif defined HAVE_LIBCRYPTO +#include +#include +#include +#include +#endif /* HAVE_LIBCRYPTO */ + +#ifndef _WIN32 +# include +# include +#endif + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int alldigits(const char *s) { + while (*s) { + if (isdigit(*s)) { + s++; + } else { + return 0; + } + } + + return 1; +} + +/** + * @internal + * + * @brief Free a token array. + */ +static void tokens_free(char **tokens) { + if (tokens == NULL) { + return; + } + + SAFE_FREE(tokens[0]); + /* It's not needed to free other pointers because tokens generated by + * space_tokenize fit all in one malloc + */ + SAFE_FREE(tokens); +} + +/** + * @internal + * + * @brief Return one line of known host file. + * + * This will return a token array containing (host|ip), keytype and key. + * + * @param[out] file A pointer to the known host file. Could be pointing to + * NULL at start. + * + * @param[in] filename The file name of the known host file. + * + * @param[out] found_type A pointer to a string to be set with the found key + * type. + * + * @returns The found_type type of key (ie "dsa","ssh-rsa1"). Don't + * free that value. NULL if no match was found or the file + * was not found. + */ +static char **ssh_get_knownhost_line(ssh_session session, FILE **file, + const char *filename, const char **found_type) { + char buffer[4096] = {0}; + char *ptr; + char **tokens; + + enter_function(); + + if(*file == NULL){ + *file = fopen(filename,"r"); + if (*file == NULL) { + leave_function(); + return NULL; + } + } + + while (fgets(buffer, sizeof(buffer), *file)) { + ptr = strchr(buffer, '\n'); + if (ptr) { + *ptr = '\0'; + } + + ptr = strchr(buffer,'\r'); + if (ptr) { + *ptr = '\0'; + } + + if (!buffer[0] || buffer[0] == '#') { + continue; /* skip empty lines */ + } + + tokens = space_tokenize(buffer); + if (tokens == NULL) { + fclose(*file); + *file = NULL; + leave_function(); + return NULL; + } + + if(!tokens[0] || !tokens[1] || !tokens[2]) { + /* it should have at least 3 tokens */ + tokens_free(tokens); + continue; + } + + *found_type = tokens[1]; + if (tokens[3]) { + /* openssh rsa1 format has 4 tokens on the line. Recognize it + by the fact that everything is all digits */ + if (tokens[4]) { + /* that's never valid */ + tokens_free(tokens); + continue; + } + if (alldigits(tokens[1]) && alldigits(tokens[2]) && alldigits(tokens[3])) { + *found_type = "ssh-rsa1"; + } else { + /* 3 tokens only, not four */ + tokens_free(tokens); + continue; + } + } + leave_function(); + return tokens; + } + + fclose(*file); + *file = NULL; + + /* we did not find anything, end of file*/ + leave_function(); + return NULL; +} + +/** + * @brief Check the public key in the known host line matches the public key of + * the currently connected server. + * + * @param[in] session The SSH session to use. + * + * @param[in] tokens A list of tokens in the known_hosts line. + * + * @returns 1 if the key matches, 0 if the key doesn't match and -1 + * on error. + */ +static int check_public_key(ssh_session session, char **tokens) { + ssh_string pubkey = session->current_crypto->server_pubkey; + ssh_buffer pubkey_buffer; + char *pubkey_64; + + /* ok we found some public key in known hosts file. now un-base64it */ + if (alldigits(tokens[1])) { + /* openssh rsa1 format */ + bignum tmpbn; + ssh_string tmpstring; + unsigned int len; + int i; + + pubkey_buffer = ssh_buffer_new(); + if (pubkey_buffer == NULL) { + return -1; + } + + tmpstring = ssh_string_from_char("ssh-rsa1"); + if (tmpstring == NULL) { + ssh_buffer_free(pubkey_buffer); + return -1; + } + + if (buffer_add_ssh_string(pubkey_buffer, tmpstring) < 0) { + ssh_buffer_free(pubkey_buffer); + ssh_string_free(tmpstring); + return -1; + } + ssh_string_free(tmpstring); + + for (i = 2; i < 4; i++) { /* e, then n */ + tmpbn = NULL; + bignum_dec2bn(tokens[i], &tmpbn); + if (tmpbn == NULL) { + ssh_buffer_free(pubkey_buffer); + return -1; + } + /* for some reason, make_bignum_string does not work + because of the padding which it does --kv */ + /* tmpstring = make_bignum_string(tmpbn); */ + /* do it manually instead */ + len = bignum_num_bytes(tmpbn); + tmpstring = malloc(4 + len); + if (tmpstring == NULL) { + ssh_buffer_free(pubkey_buffer); + bignum_free(tmpbn); + return -1; + } + /* TODO: fix the hardcoding */ + tmpstring->size = htonl(len); +#ifdef HAVE_LIBGCRYPT + bignum_bn2bin(tmpbn, len, string_data(tmpstring)); +#elif defined HAVE_LIBCRYPTO + bignum_bn2bin(tmpbn, string_data(tmpstring)); +#endif + bignum_free(tmpbn); + if (buffer_add_ssh_string(pubkey_buffer, tmpstring) < 0) { + ssh_buffer_free(pubkey_buffer); + ssh_string_free(tmpstring); + bignum_free(tmpbn); + return -1; + } + ssh_string_free(tmpstring); + } + } else { + /* ssh-dss or ssh-rsa */ + pubkey_64 = tokens[2]; + pubkey_buffer = base64_to_bin(pubkey_64); + } + + if (pubkey_buffer == NULL) { + ssh_set_error(session, SSH_FATAL, + "Verifying that server is a known host: base64 error"); + return -1; + } + + if (buffer_get_rest_len(pubkey_buffer) != ssh_string_len(pubkey)) { + ssh_buffer_free(pubkey_buffer); + return 0; + } + + /* now test that they are identical */ + if (memcmp(buffer_get_rest(pubkey_buffer), ssh_string_data(pubkey), + buffer_get_rest_len(pubkey_buffer)) != 0) { + ssh_buffer_free(pubkey_buffer); + return 0; + } + + ssh_buffer_free(pubkey_buffer); + return 1; +} + +/** + * @brief Check if a hostname matches a openssh-style hashed known host. + * + * @param[in] host The host to check. + * + * @param[in] hashed The hashed value. + * + * @returns 1 if it matches, 0 otherwise. + */ +static int match_hashed_host(ssh_session session, const char *host, + const char *sourcehash) { + /* Openssh hash structure : + * |1|base64 encoded salt|base64 encoded hash + * hash is produced that way : + * hash := HMAC_SHA1(key=salt,data=host) + */ + unsigned char buffer[256] = {0}; + ssh_buffer salt; + ssh_buffer hash; + HMACCTX mac; + char *source; + char *b64hash; + int match; + unsigned int size; + + enter_function(); + + if (strncmp(sourcehash, "|1|", 3) != 0) { + leave_function(); + return 0; + } + + source = strdup(sourcehash + 3); + if (source == NULL) { + leave_function(); + return 0; + } + + b64hash = strchr(source, '|'); + if (b64hash == NULL) { + /* Invalid hash */ + SAFE_FREE(source); + leave_function(); + return 0; + } + + *b64hash = '\0'; + b64hash++; + + salt = base64_to_bin(source); + if (salt == NULL) { + SAFE_FREE(source); + leave_function(); + return 0; + } + + hash = base64_to_bin(b64hash); + SAFE_FREE(source); + if (hash == NULL) { + ssh_buffer_free(salt); + leave_function(); + return 0; + } + + mac = hmac_init(buffer_get_rest(salt), buffer_get_rest_len(salt), SSH_HMAC_SHA1); + if (mac == NULL) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + leave_function(); + return 0; + } + size = sizeof(buffer); + hmac_update(mac, host, strlen(host)); + hmac_final(mac, buffer, &size); + + if (size == buffer_get_rest_len(hash) && + memcmp(buffer, buffer_get_rest(hash), size) == 0) { + match = 1; + } else { + match = 0; + } + + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + ssh_log(session, SSH_LOG_PACKET, + "Matching a hashed host: %s match=%d", host, match); + + leave_function(); + return match; +} + +/* How it's working : + * 1- we open the known host file and bitch if it doesn't exist + * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK: + * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK, + * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER) + * - there's no match : no change + */ + +/** + * @brief Check if the server is known. + * + * Checks the user's known host file for a previous connection to the + * current server. + * + * @param[in] session The SSH session to use. + * + * @returns SSH_SERVER_KNOWN_OK: The server is known and has not changed.\n + * SSH_SERVER_KNOWN_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_SERVER_FOUND_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_SERVER_NOT_KNOWN: The server is unknown. User should + * confirm the MD5 is correct.\n + * SSH_SERVER_FILE_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_SERVER_ERROR: Some error happened. + * + * @see ssh_get_pubkey_hash() + * + * @bug There is no current way to remove or modify an entry into the known + * host table. + */ +int ssh_is_server_known(ssh_session session) { + FILE *file = NULL; + char **tokens; + char *host; + char *hostport; + const char *type; + int match; + int ret = SSH_SERVER_NOT_KNOWN; + + enter_function(); + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + leave_function(); + return SSH_SERVER_FILE_NOT_FOUND; + } + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't verify host in known hosts if the hostname isn't known"); + leave_function(); + return SSH_SERVER_ERROR; + } + + if (session->current_crypto == NULL){ + ssh_set_error(session, SSH_FATAL, + "ssh_is_host_known called without cryptographic context"); + leave_function(); + return SSH_SERVER_ERROR; + } + host = ssh_lowercase(session->opts.host); + hostport = ssh_hostport(host, session->opts.port); + if (host == NULL || hostport == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(host); + SAFE_FREE(hostport); + leave_function(); + return SSH_SERVER_ERROR; + } + + do { + tokens = ssh_get_knownhost_line(session, + &file, + session->opts.knownhosts, + &type); + + /* End of file, return the current state */ + if (tokens == NULL) { + break; + } + match = match_hashed_host(session, host, tokens[0]); + if (match == 0){ + match = match_hostname(hostport, tokens[0], strlen(tokens[0])); + } + if (match == 0) { + match = match_hostname(host, tokens[0], strlen(tokens[0])); + } + if (match == 0) { + match = match_hashed_host(session, hostport, tokens[0]); + } + if (match) { + /* We got a match. Now check the key type */ + if (strcmp(session->current_crypto->server_pubkey_type, type) != 0) { + ssh_log(session, + SSH_LOG_PACKET, + "ssh_is_server_known: server type [%s] doesn't match the " + "type [%s] in known_hosts file", + session->current_crypto->server_pubkey_type, + type); + /* Different type. We don't override the known_changed error which is + * more important */ + if (ret != SSH_SERVER_KNOWN_CHANGED) + ret = SSH_SERVER_FOUND_OTHER; + tokens_free(tokens); + continue; + } + /* so we know the key type is good. We may get a good key or a bad key. */ + match = check_public_key(session, tokens); + tokens_free(tokens); + + if (match < 0) { + ret = SSH_SERVER_ERROR; + break; + } else if (match == 1) { + ret = SSH_SERVER_KNOWN_OK; + break; + } else if(match == 0) { + /* We override the status with the wrong key state */ + ret = SSH_SERVER_KNOWN_CHANGED; + } + } else { + tokens_free(tokens); + } + } while (1); + + if ((ret == SSH_SERVER_NOT_KNOWN) && + (session->opts.StrictHostKeyChecking == 0)) { + ssh_write_knownhost(session); + ret = SSH_SERVER_KNOWN_OK; + } + + SAFE_FREE(host); + SAFE_FREE(hostport); + if (file != NULL) { + fclose(file); + } + + /* Return the current state at end of file */ + leave_function(); + return ret; +} + +/** + * @brief Write the current server as known in the known hosts file. + * + * This will create the known hosts file if it does not exist. You generaly use + * it when ssh_is_server_known() answered SSH_SERVER_NOT_KNOWN. + * + * @param[in] session The ssh session to use. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_write_knownhost(ssh_session session) { + ssh_key key; + ssh_string pubkey_s; + char *b64_key; + char buffer[4096] = {0}; + FILE *file; + char *dir; + char *host; + char *hostport; + int rc; + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't write host in known hosts if the hostname isn't known"); + return SSH_ERROR; + } + + host = ssh_lowercase(session->opts.host); + /* If using a nonstandard port, save the host in the [host]:port format */ + if(session->opts.port != 22) { + hostport = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (hostport == NULL) { + return SSH_ERROR; + } + host = hostport; + hostport = NULL; + } + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + SAFE_FREE(host); + return SSH_ERROR; + } + } + + if (session->current_crypto==NULL) { + ssh_set_error(session, SSH_FATAL, "No current crypto context"); + SAFE_FREE(host); + return SSH_ERROR; + } + + pubkey_s = session->current_crypto->server_pubkey; + if (pubkey_s == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return SSH_ERROR; + } + + /* Check if ~/.ssh exists and create it if not */ + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + SAFE_FREE(host); + return SSH_ERROR; + } + + if (!ssh_file_readaccess_ok(dir)) { + if (ssh_mkdir(dir, 0700) < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory.", dir); + SAFE_FREE(dir); + SAFE_FREE(host); + return SSH_ERROR; + } + } + SAFE_FREE(dir); + + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s for appending: %s", + session->opts.knownhosts, strerror(errno)); + SAFE_FREE(host); + return SSH_ERROR; + } + + rc = ssh_pki_import_pubkey_blob(pubkey_s, &key); + if (rc < 0) { + fclose(file); + SAFE_FREE(host); + return -1; + } + + if (strcmp(session->current_crypto->server_pubkey_type, "ssh-rsa1") == 0) { + /* openssh uses a different format for ssh-rsa1 keys. + Be compatible --kv */ + rc = ssh_pki_export_pubkey_rsa1(key, host, buffer, sizeof(buffer)); + ssh_key_free(key); + SAFE_FREE(host); + if (rc < 0) { + fclose(file); + return -1; + } + } else { + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + if (rc < 0) { + ssh_key_free(key); + fclose(file); + SAFE_FREE(host); + return -1; + } + + snprintf(buffer, sizeof(buffer), + "%s %s %s\n", + host, + key->type_c, + b64_key); + + ssh_key_free(key); + SAFE_FREE(host); + SAFE_FREE(b64_key); + } + + if (fwrite(buffer, strlen(buffer), 1, file) != 1 || ferror(file)) { + fclose(file); + return -1; + } + + fclose(file); + return 0; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/legacy.c b/libssh/src/legacy.c new file mode 100644 index 00000000..6ad4fdc2 --- /dev/null +++ b/libssh/src/legacy.c @@ -0,0 +1,731 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** functions in that file are wrappers to the newly named functions. All + * of them are depreciated, but these wrapper will avoid breaking backward + * compatibility + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include "libssh/pki_priv.h" +#include +#include +#include "libssh/options.h" + +/* AUTH FUNCTIONS */ +int ssh_auth_list(ssh_session session) { + return ssh_userauth_list(session, NULL); +} + +int ssh_userauth_offer_pubkey(ssh_session session, const char *username, + int type, ssh_string publickey) +{ + ssh_key key; + int rc; + + (void) type; /* unused */ + + rc = ssh_pki_import_pubkey_blob(publickey, &key); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Failed to convert public key"); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_try_publickey(session, username, key); + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_pubkey(ssh_session session, + const char *username, + ssh_string publickey, + ssh_private_key privatekey) +{ + ssh_key key; + int rc; + + (void) publickey; /* unused */ + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + key->type = privatekey->type; + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PRIVATE|SSH_KEY_FLAG_PUBLIC; + key->dsa = privatekey->dsa_priv; + key->rsa = privatekey->rsa_priv; + + rc = ssh_userauth_publickey(session, username, key); + key->dsa = NULL; + key->rsa = NULL; + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) { + return ssh_userauth_publickey_auto(session, NULL, passphrase); +} + +int ssh_userauth_privatekey_file(ssh_session session, + const char *username, + const char *filename, + const char *passphrase) { + char *pubkeyfile = NULL; + ssh_string pubkey = NULL; + ssh_private_key privkey = NULL; + int type = 0; + int rc = SSH_AUTH_ERROR; + size_t klen = strlen(filename) + 4 + 1; + + enter_function(); + + pubkeyfile = malloc(klen); + if (pubkeyfile == NULL) { + ssh_set_error_oom(session); + leave_function(); + return SSH_AUTH_ERROR; + } + snprintf(pubkeyfile, klen, "%s.pub", filename); + + pubkey = publickey_from_file(session, pubkeyfile, &type); + if (pubkey == NULL) { + ssh_log(session, SSH_LOG_RARE, "Public key file %s not found. Trying to generate it.", pubkeyfile); + /* auto-detect the key type with type=0 */ + privkey = privatekey_from_file(session, filename, 0, passphrase); + } else { + ssh_log(session, SSH_LOG_RARE, "Public key file %s loaded.", pubkeyfile); + privkey = privatekey_from_file(session, filename, type, passphrase); + } + if (privkey == NULL) { + goto error; + } + /* ssh_userauth_pubkey is responsible for taking care of null-pubkey */ + rc = ssh_userauth_pubkey(session, username, pubkey, privkey); + privatekey_free(privkey); + +error: + SAFE_FREE(pubkeyfile); + ssh_string_free(pubkey); + + leave_function(); + return rc; +} + +/* BUFFER FUNCTIONS */ + +void buffer_free(ssh_buffer buffer){ + ssh_buffer_free(buffer); +} +void *buffer_get(ssh_buffer buffer){ + return ssh_buffer_get_begin(buffer); +} +uint32_t buffer_get_len(ssh_buffer buffer){ + return ssh_buffer_get_len(buffer); +} +ssh_buffer buffer_new(void){ + return ssh_buffer_new(); +} + +ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms){ + return ssh_channel_accept_x11(channel, timeout_ms); +} + +int channel_change_pty_size(ssh_channel channel,int cols,int rows){ + return ssh_channel_change_pty_size(channel,cols,rows); +} + +ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ + return ssh_forward_accept(session,timeout_ms); +} + +int channel_close(ssh_channel channel){ + return ssh_channel_close(channel); +} + +int channel_forward_cancel(ssh_session session, const char *address, int port){ + return ssh_forward_cancel(session, address, port); +} + +int channel_forward_listen(ssh_session session, const char *address, + int port, int *bound_port){ + return ssh_forward_listen(session, address, port, bound_port); +} + +void channel_free(ssh_channel channel){ + ssh_channel_free(channel); +} + +int channel_get_exit_status(ssh_channel channel){ + return ssh_channel_get_exit_status(channel); +} + +ssh_session channel_get_session(ssh_channel channel){ + return ssh_channel_get_session(channel); +} + +int channel_is_closed(ssh_channel channel){ + return ssh_channel_is_closed(channel); +} + +int channel_is_eof(ssh_channel channel){ + return ssh_channel_is_eof(channel); +} + +int channel_is_open(ssh_channel channel){ + return ssh_channel_is_open(channel); +} + +ssh_channel channel_new(ssh_session session){ + return ssh_channel_new(session); +} + +int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport){ + return ssh_channel_open_forward(channel, remotehost, remoteport, + sourcehost,localport); +} + +int channel_open_session(ssh_channel channel){ + return ssh_channel_open_session(channel); +} + +int channel_poll(ssh_channel channel, int is_stderr){ + return ssh_channel_poll(channel, is_stderr); +} + +int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr){ + return ssh_channel_read(channel, dest, count, is_stderr); +} + +/* + * This function will completely be depreciated. The old implementation was not + * renamed. + * int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + * int is_stderr); + */ + +int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr){ + return ssh_channel_read_nonblocking(channel, dest, count, is_stderr); +} + +int channel_request_env(ssh_channel channel, const char *name, const char *value){ + return ssh_channel_request_env(channel, name, value); +} + +int channel_request_exec(ssh_channel channel, const char *cmd){ + return ssh_channel_request_exec(channel, cmd); +} + +int channel_request_pty(ssh_channel channel){ + return ssh_channel_request_pty(channel); +} + +int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows){ + return ssh_channel_request_pty_size(channel, term, cols, rows); +} + +int channel_request_shell(ssh_channel channel){ + return ssh_channel_request_shell(channel); +} + +int channel_request_send_signal(ssh_channel channel, const char *signum){ + return ssh_channel_request_send_signal(channel, signum); +} + +int channel_request_sftp(ssh_channel channel){ + return ssh_channel_request_sftp(channel); +} + +int channel_request_subsystem(ssh_channel channel, const char *subsystem){ + return ssh_channel_request_subsystem(channel, subsystem); +} + +int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number){ + return ssh_channel_request_x11(channel, single_connection, protocol, cookie, + screen_number); +} + +int channel_send_eof(ssh_channel channel){ + return ssh_channel_send_eof(channel); +} + +int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout){ + return ssh_channel_select(readchans, writechans, exceptchans, timeout); +} + +void channel_set_blocking(ssh_channel channel, int blocking){ + ssh_channel_set_blocking(channel, blocking); +} + +int channel_write(ssh_channel channel, const void *data, uint32_t len){ + return ssh_channel_write(channel, data, len); +} + +/* + * These functions have to be wrapped around the pki.c functions. + +void privatekey_free(ssh_private_key prv); +ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +ssh_string publickey_to_string(ssh_public_key key); + * + */ + +void string_burn(ssh_string str){ + ssh_string_burn(str); +} + +ssh_string string_copy(ssh_string str){ + return ssh_string_copy(str); +} + +void *string_data(ssh_string str){ + return ssh_string_data(str); +} + +int string_fill(ssh_string str, const void *data, size_t len){ + return ssh_string_fill(str,data,len); +} + +void string_free(ssh_string str){ + ssh_string_free(str); +} + +ssh_string string_from_char(const char *what){ + return ssh_string_from_char(what); +} + +size_t string_len(ssh_string str){ + return ssh_string_len(str); +} + +ssh_string string_new(size_t size){ + return ssh_string_new(size); +} + +char *string_to_char(ssh_string str){ + return ssh_string_to_char(str); +} + +/* OLD PKI FUNCTIONS */ + +void publickey_free(ssh_public_key key) { + if (key == NULL) { + return; + } + + switch(key->type) { + case SSH_KEYTYPE_DSS: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(key->dsa_pub); +#elif HAVE_LIBCRYPTO + DSA_free(key->dsa_pub); +#endif + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(key->rsa_pub); +#elif defined HAVE_LIBCRYPTO + RSA_free(key->rsa_pub); +#endif + break; + default: + break; + } + SAFE_FREE(key); +} + +ssh_public_key publickey_from_privatekey(ssh_private_key prv) { + struct ssh_public_key_struct *p; + ssh_key privkey; + ssh_key pubkey; + int rc; + + privkey = ssh_key_new(); + if (privkey == NULL) { + return NULL; + } + + privkey->type = prv->type; + privkey->type_c = ssh_key_type_to_char(privkey->type); + privkey->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + privkey->dsa = prv->dsa_priv; + privkey->rsa = prv->rsa_priv; + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + privkey->dsa = NULL; + privkey->rsa = NULL; + ssh_key_free(privkey); + if (rc < 0) { + return NULL; + } + + p = ssh_pki_convert_key_to_publickey(pubkey); + ssh_key_free(pubkey); + + return p; +} + +ssh_private_key privatekey_from_file(ssh_session session, + const char *filename, + int type, + const char *passphrase) { + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + ssh_private_key privkey; + ssh_key key; + int rc; + + (void) type; /* unused */ + + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + + + rc = ssh_pki_import_privkey_file(filename, + passphrase, + auth_fn, + auth_data, + &key); + if (rc == SSH_ERROR) { + return NULL; + } + + privkey = malloc(sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; + privkey->dsa_priv = key->dsa; + privkey->rsa_priv = key->rsa; + + key->dsa = NULL; + key->rsa = NULL; + + ssh_key_free(key); + + return privkey; +} + +enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey){ + if (privatekey==NULL) + return SSH_KEYTYPE_UNKNOWN; + return privatekey->type; +} + +void privatekey_free(ssh_private_key prv) { + if (prv == NULL) { + return; + } + +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(prv->dsa_priv); + gcry_sexp_release(prv->rsa_priv); +#elif defined HAVE_LIBCRYPTO + DSA_free(prv->dsa_priv); + RSA_free(prv->rsa_priv); +#endif + memset(prv, 0, sizeof(struct ssh_private_key_struct)); + SAFE_FREE(prv); +} + +ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type) { + ssh_key key; + ssh_string key_str = NULL; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_file(filename, &key); + if (rc < 0) { + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(key, &key_str); + if (rc < 0) { + ssh_key_free(key); + return NULL; + } + + if (type) { + *type = key->type; + } + ssh_key_free(key); + + return key_str; +} + +const char *ssh_type_to_char(int type) { + return ssh_key_type_to_char(type); +} + +int ssh_type_from_name(const char *name) { + return ssh_key_type_from_name(name); +} + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { + struct ssh_public_key_struct *pubkey; + ssh_key key; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_blob(pubkey_s, &key); + if (rc < 0) { + return NULL; + } + + pubkey = malloc(sizeof(struct ssh_public_key_struct)); + if (pubkey == NULL) { + ssh_key_free(key); + return NULL; + } + + pubkey->type = key->type; + pubkey->type_c = key->type_c; + + pubkey->dsa_pub = key->dsa; + key->dsa = NULL; + pubkey->rsa_pub = key->rsa; + key->rsa = NULL; + + ssh_key_free(key); + + return pubkey; +} + +ssh_string publickey_to_string(ssh_public_key pubkey) { + ssh_key key; + ssh_string key_blob; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return NULL; + } + + key->type = pubkey->type; + key->type_c = pubkey->type_c; + + key->dsa = pubkey->dsa_pub; + key->rsa = pubkey->rsa_pub; + + rc = ssh_pki_export_pubkey_blob(key, &key_blob); + if (rc < 0) { + key_blob = NULL; + } + + key->dsa = NULL; + key->rsa = NULL; + ssh_key_free(key); + + return key_blob; +} + +int ssh_publickey_to_file(ssh_session session, + const char *file, + ssh_string pubkey, + int type) +{ + FILE *fp; + char *user; + char buffer[1024]; + char host[256]; + unsigned char *pubkey_64; + size_t len; + int rc; + if(session==NULL) + return SSH_ERROR; + if(file==NULL || pubkey==NULL){ + ssh_set_error(session, SSH_FATAL, "Invalid parameters"); + return SSH_ERROR; + } + pubkey_64 = bin_to_base64(string_data(pubkey), ssh_string_len(pubkey)); + if (pubkey_64 == NULL) { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + rc = gethostname(host, sizeof(host)); + if (rc < 0) { + SAFE_FREE(user); + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + snprintf(buffer, sizeof(buffer), "%s %s %s@%s\n", + ssh_type_to_char(type), + pubkey_64, + user, + host); + + SAFE_FREE(pubkey_64); + SAFE_FREE(user); + + ssh_log(session, SSH_LOG_RARE, "Trying to write public key file: %s", file); + ssh_log(session, SSH_LOG_PACKET, "public key file content: %s", buffer); + + fp = fopen(file, "w+"); + if (fp == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Error opening %s: %s", file, strerror(errno)); + return SSH_ERROR; + } + + len = strlen(buffer); + if (fwrite(buffer, len, 1, fp) != 1 || ferror(fp)) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Unable to write to %s", file); + fclose(fp); + unlink(file); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +int ssh_try_publickey_from_file(ssh_session session, + const char *keyfile, + ssh_string *publickey, + int *type) { + char *pubkey_file; + size_t len; + ssh_string pubkey_string; + int pubkey_type; + + if (session == NULL || keyfile == NULL || publickey == NULL || type == NULL) { + return -1; + } + + if (session->opts.sshdir == NULL) { + if (ssh_options_apply(session) < 0) { + return -1; + } + } + + ssh_log(session, SSH_LOG_PACKET, "Trying to open privatekey %s", keyfile); + if (!ssh_file_readaccess_ok(keyfile)) { + ssh_log(session, SSH_LOG_PACKET, "Failed to open privatekey %s", keyfile); + return -1; + } + + len = strlen(keyfile) + 5; + pubkey_file = malloc(len); + if (pubkey_file == NULL) { + return -1; + } + snprintf(pubkey_file, len, "%s.pub", keyfile); + + ssh_log(session, SSH_LOG_PACKET, "Trying to open publickey %s", + pubkey_file); + if (!ssh_file_readaccess_ok(pubkey_file)) { + ssh_log(session, SSH_LOG_PACKET, "Failed to open publickey %s", + pubkey_file); + SAFE_FREE(pubkey_file); + return 1; + } + + ssh_log(session, SSH_LOG_PACKET, "Success opening public and private key"); + + /* + * We are sure both the private and public key file is readable. We return + * the public as a string, and the private filename as an argument + */ + pubkey_string = publickey_from_file(session, pubkey_file, &pubkey_type); + if (pubkey_string == NULL) { + ssh_log(session, SSH_LOG_PACKET, + "Wasn't able to open public key file %s: %s", + pubkey_file, + ssh_get_error(session)); + SAFE_FREE(pubkey_file); + return -1; + } + + SAFE_FREE(pubkey_file); + + *publickey = pubkey_string; + *type = pubkey_type; + + return 0; +} + +ssh_string ssh_get_pubkey(ssh_session session){ + if(session==NULL || session->current_crypto ==NULL || + session->current_crypto->server_pubkey==NULL) + return NULL; + else + return ssh_string_copy(session->current_crypto->server_pubkey); +} + +/**************************************************************************** + * SERVER SUPPORT + ****************************************************************************/ + +#ifdef WITH_SERVER +int ssh_accept(ssh_session session) { + return ssh_handle_key_exchange(session); +} + +int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { + return ssh_channel_write(channel, data, len); +} + +/** @deprecated + * @brief Interface previously exported by error. + */ +ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype){ + (void) packettype; + ssh_set_error(session, SSH_FATAL, "ssh_message_retrieve: obsolete libssh call"); + return NULL; +} + +#endif /* WITH_SERVER */ diff --git a/libssh/src/libcrypto.c b/libssh/src/libcrypto.c new file mode 100644 index 00000000..44b0fb36 --- /dev/null +++ b/libssh/src/libcrypto.c @@ -0,0 +1,600 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/libcrypto.h" + +#ifdef HAVE_LIBCRYPTO + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_OPENSSL_AES_H +#define HAS_AES +#include +#endif +#ifdef HAVE_OPENSSL_BLOWFISH_H +#define HAS_BLOWFISH +#include +#endif +#ifdef HAVE_OPENSSL_DES_H +#define HAS_DES +#include +#endif + +#if (OPENSSL_VERSION_NUMBER<0x00907000L) +#define OLD_CRYPTO +#endif + +#include "libssh/crypto.h" + +struct ssh_mac_ctx_struct { + enum ssh_mac_e mac_type; + union { + SHACTX sha1_ctx; + SHA256CTX sha256_ctx; + } ctx; +}; + +static int alloc_key(struct ssh_cipher_struct *cipher) { + cipher->key = malloc(cipher->keylen); + if (cipher->key == NULL) { + return -1; + } + + return 0; +} + +SHACTX sha1_init(void) { + SHACTX c = malloc(sizeof(*c)); + if (c == NULL) { + return NULL; + } + SHA1_Init(c); + + return c; +} + +void sha1_update(SHACTX c, const void *data, unsigned long len) { + SHA1_Update(c,data,len); +} + +void sha1_final(unsigned char *md, SHACTX c) { + SHA1_Final(md, c); + SAFE_FREE(c); +} + +void sha1(unsigned char *digest, int len, unsigned char *hash) { + SHA1(digest, len, hash); +} + +#ifdef HAVE_OPENSSL_ECC +static const EVP_MD *nid_to_evpmd(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return EVP_sha256(); + case NID_secp384r1: + return EVP_sha384(); + case NID_secp521r1: + return EVP_sha512(); + default: + return NULL; + } + + return NULL; +} + +void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen) +{ + const EVP_MD *evp_md = nid_to_evpmd(nid); + EVP_MD_CTX md; + + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, digest, len); + EVP_DigestFinal(&md, hash, hlen); +} +#endif + +SHA256CTX sha256_init(void){ + SHA256CTX c = malloc(sizeof(*c)); + if (c == NULL) { + return NULL; + } + SHA256_Init(c); + + return c; +} + +void sha256_update(SHA256CTX c, const void *data, unsigned long len){ + SHA256_Update(c,data,len); +} + +void sha256_final(unsigned char *md, SHA256CTX c) { + SHA256_Final(md, c); + SAFE_FREE(c); +} + +void sha256(unsigned char *digest, int len, unsigned char *hash) { + SHA256(digest, len, hash); +} + +MD5CTX md5_init(void) { + MD5CTX c = malloc(sizeof(*c)); + if (c == NULL) { + return NULL; + } + + MD5_Init(c); + + return c; +} + +void md5_update(MD5CTX c, const void *data, unsigned long len) { + MD5_Update(c, data, len); +} + +void md5_final(unsigned char *md, MD5CTX c) { + MD5_Final(md,c); + SAFE_FREE(c); +} + +ssh_mac_ctx ssh_mac_ctx_init(enum ssh_mac_e type){ + ssh_mac_ctx ctx=malloc(sizeof(struct ssh_mac_ctx_struct)); + ctx->mac_type=type; + switch(type){ + case SSH_MAC_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); + return ctx; + case SSH_MAC_SHA256: + ctx->ctx.sha256_ctx = sha256_init(); + return ctx; + case SSH_MAC_SHA384: + case SSH_MAC_SHA512: + default: + SAFE_FREE(ctx); + return NULL; + } +} + +void ssh_mac_update(ssh_mac_ctx ctx, const void *data, unsigned long len) { + switch(ctx->mac_type){ + case SSH_MAC_SHA1: + sha1_update(ctx->ctx.sha1_ctx, data, len); + break; + case SSH_MAC_SHA256: + sha256_update(ctx->ctx.sha256_ctx, data, len); + break; + case SSH_MAC_SHA384: + case SSH_MAC_SHA512: + default: + break; + } +} + +void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) { + switch(ctx->mac_type){ + case SSH_MAC_SHA1: + sha1_final(md,ctx->ctx.sha1_ctx); + break; + case SSH_MAC_SHA256: + sha256_final(md,ctx->ctx.sha256_ctx); + break; + case SSH_MAC_SHA384: + case SSH_MAC_SHA512: + default: + break; + } + SAFE_FREE(ctx); +} + +HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { + HMACCTX ctx = NULL; + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } + +#ifndef OLD_CRYPTO + HMAC_CTX_init(ctx); // openssl 0.9.7 requires it. +#endif + + switch(type) { + case SSH_HMAC_SHA1: + HMAC_Init(ctx, key, len, EVP_sha1()); + break; + case SSH_HMAC_MD5: + HMAC_Init(ctx, key, len, EVP_md5()); + break; + default: + SAFE_FREE(ctx); + ctx = NULL; + } + + return ctx; +} + +void hmac_update(HMACCTX ctx, const void *data, unsigned long len) { + HMAC_Update(ctx, data, len); +} + +void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) { + HMAC_Final(ctx,hashmacbuf,len); + +#ifndef OLD_CRYPTO + HMAC_CTX_cleanup(ctx); +#else + HMAC_cleanup(ctx); +#endif + + SAFE_FREE(ctx); +} + +#ifdef HAS_BLOWFISH +/* the wrapper functions for blowfish */ +static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + BF_set_key(cipher->key, 16, key); + } + cipher->IV = IV; + return 0; +} + +static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + BF_cbc_encrypt(in, out, len, cipher->key, cipher->IV, BF_ENCRYPT); +} + +static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + BF_cbc_encrypt(in, out, len, cipher->key, cipher->IV, BF_DECRYPT); +} +#endif /* HAS_BLOWFISH */ + +#ifdef HAS_AES +static int aes_set_encrypt_key(struct ssh_cipher_struct *cipher, void *key, + void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (AES_set_encrypt_key(key,cipher->keysize,cipher->key) < 0) { + SAFE_FREE(cipher->key); + return -1; + } + } + cipher->IV=IV; + return 0; +} +static int aes_set_decrypt_key(struct ssh_cipher_struct *cipher, void *key, + void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (AES_set_decrypt_key(key,cipher->keysize,cipher->key) < 0) { + SAFE_FREE(cipher->key); + return -1; + } + } + cipher->IV=IV; + return 0; +} + +static void aes_encrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len) { + AES_cbc_encrypt(in, out, len, cipher->key, cipher->IV, AES_ENCRYPT); +} + +static void aes_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len) { + AES_cbc_encrypt(in, out, len, cipher->key, cipher->IV, AES_DECRYPT); +} + +#ifndef BROKEN_AES_CTR +/* OpenSSL until 0.9.7c has a broken AES_ctr128_encrypt implementation which + * increments the counter from 2^64 instead of 1. It's better not to use it + */ + +/** @internal + * @brief encrypts/decrypts data with stream cipher AES_ctr128. 128 bits is actually + * the size of the CTR counter and incidentally the blocksize, but not the keysize. + * @param len[in] must be a multiple of AES128 block size. + */ +static void aes_ctr128_encrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len) { + unsigned char tmp_buffer[128/8]; + unsigned int num=0; + /* Some things are special with ctr128 : + * In this case, tmp_buffer is not being used, because it is used to store temporary data + * when an encryption is made on lengths that are not multiple of blocksize. + * Same for num, which is being used to store the current offset in blocksize in CTR + * function. + */ + AES_ctr128_encrypt(in, out, len, cipher->key, cipher->IV, tmp_buffer, &num); +} +#endif /* BROKEN_AES_CTR */ +#endif /* HAS_AES */ + +#ifdef HAS_DES +static int des3_set_key(struct ssh_cipher_struct *cipher, void *key,void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + + DES_set_odd_parity(key); + DES_set_odd_parity((void*)((uint8_t*)key + 8)); + DES_set_odd_parity((void*)((uint8_t*)key + 16)); + DES_set_key_unchecked(key, cipher->key); + DES_set_key_unchecked((void*)((uint8_t*)key + 8), (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule))); + DES_set_key_unchecked((void*)((uint8_t*)key + 16), (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule))); + } + cipher->IV=IV; + return 0; +} + +static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + DES_ede3_cbc_encrypt(in, out, len, cipher->key, + (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)), + (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)), + cipher->IV, 1); +} + +static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + DES_ede3_cbc_encrypt(in, out, len, cipher->key, + (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)), + (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)), + cipher->IV, 0); +} + +static void des3_1_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Encrypt IV before", cipher->IV, 24); +#endif + DES_ncbc_encrypt(in, out, len, cipher->key, cipher->IV, 1); + DES_ncbc_encrypt(out, in, len, (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)), + (void*)((uint8_t*)cipher->IV + 8), 0); + DES_ncbc_encrypt(in, out, len, (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)), + (void*)((uint8_t*)cipher->IV + 16), 1); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Encrypt IV after", cipher->IV, 24); +#endif +} + +static void des3_1_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Decrypt IV before", cipher->IV, 24); +#endif + + DES_ncbc_encrypt(in, out, len, (void*)((uint8_t*)cipher->key + 2 * sizeof(DES_key_schedule)), + cipher->IV, 0); + DES_ncbc_encrypt(out, in, len, (void*)((uint8_t*)cipher->key + sizeof(DES_key_schedule)), + (void*)((uint8_t*)cipher->IV + 8), 1); + DES_ncbc_encrypt(in, out, len, cipher->key, (void*)((uint8_t*)cipher->IV + 16), 0); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Decrypt IV after", cipher->IV, 24); +#endif +} + +static int des1_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if(!cipher->key){ + if (alloc_key(cipher) < 0) { + return -1; + } + DES_set_odd_parity(key); + DES_set_key_unchecked(key,cipher->key); + } + cipher->IV=IV; + return 0; +} + +static void des1_1_encrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len){ + + DES_ncbc_encrypt(in, out, len, cipher->key, cipher->IV, 1); +} + +static void des1_1_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len){ + + DES_ncbc_encrypt(in,out,len, cipher->key, cipher->IV, 0); +} + +#endif /* HAS_DES */ + +/* + * The table of supported ciphers + * + * WARNING: If you modify ssh_cipher_struct, you must make sure the order is + * correct! + */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAS_BLOWFISH + { + "blowfish-cbc", + 8, + sizeof (BF_KEY), + NULL, + NULL, + 128, + blowfish_set_key, + blowfish_set_key, + blowfish_encrypt, + blowfish_decrypt + }, +#endif /* HAS_BLOWFISH */ +#ifdef HAS_AES +#ifndef BROKEN_AES_CTR + { + "aes128-ctr", + 16, + sizeof(AES_KEY), + NULL, + NULL, + 128, + aes_set_encrypt_key, + aes_set_encrypt_key, + aes_ctr128_encrypt, + aes_ctr128_encrypt + }, + { + "aes192-ctr", + 16, + sizeof(AES_KEY), + NULL, + NULL, + 192, + aes_set_encrypt_key, + aes_set_encrypt_key, + aes_ctr128_encrypt, + aes_ctr128_encrypt + }, + { + "aes256-ctr", + 16, + sizeof(AES_KEY), + NULL, + NULL, + 256, + aes_set_encrypt_key, + aes_set_encrypt_key, + aes_ctr128_encrypt, + aes_ctr128_encrypt + }, +#endif /* BROKEN_AES_CTR */ + { + "aes128-cbc", + 16, + sizeof(AES_KEY), + NULL, + NULL, + 128, + aes_set_encrypt_key, + aes_set_decrypt_key, + aes_encrypt, + aes_decrypt + }, + { + "aes192-cbc", + 16, + sizeof(AES_KEY), + NULL, + NULL, + 192, + aes_set_encrypt_key, + aes_set_decrypt_key, + aes_encrypt, + aes_decrypt + }, + { + "aes256-cbc", + 16, + sizeof(AES_KEY), + NULL, + NULL, + 256, + aes_set_encrypt_key, + aes_set_decrypt_key, + aes_encrypt, + aes_decrypt + }, +#endif /* HAS_AES */ +#ifdef HAS_DES + { + "3des-cbc", + 8, + sizeof(DES_key_schedule) * 3, + NULL, + NULL, + 192, + des3_set_key, + des3_set_key, + des3_encrypt, + des3_decrypt + }, + { + "3des-cbc-ssh1", + 8, + sizeof(DES_key_schedule) * 3, + NULL, + NULL, + 192, + des3_set_key, + des3_set_key, + des3_1_encrypt, + des3_1_decrypt + }, + { + "des-cbc-ssh1", + 8, + sizeof(DES_key_schedule), + NULL, + NULL, + 64, + des1_set_key, + des1_set_key, + des1_1_encrypt, + des1_1_decrypt + }, +#endif /* HAS_DES */ + { + NULL, + 0, + 0, + NULL, + NULL, + 0, + NULL, + NULL, + NULL, + NULL + } +}; + + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +#endif /* LIBCRYPTO */ + diff --git a/libssh/src/libgcrypt.c b/libssh/src/libgcrypt.c new file mode 100644 index 00000000..899bccdf --- /dev/null +++ b/libssh/src/libgcrypt.c @@ -0,0 +1,526 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#ifdef HAVE_LIBGCRYPT +#include + +struct ssh_mac_ctx_struct { + enum ssh_mac_e mac_type; + gcry_md_hd_t ctx; +}; + +static int alloc_key(struct ssh_cipher_struct *cipher) { + cipher->key = malloc(cipher->keylen); + if (cipher->key == NULL) { + return -1; + } + + return 0; +} + +SHACTX sha1_init(void) { + SHACTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA1, 0); + + return ctx; +} + +void sha1_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); +} + +void sha1_final(unsigned char *md, SHACTX c) { + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); + gcry_md_close(c); +} + +void sha1(unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); +} + +void sha256(unsigned char *digest, int len, unsigned char *hash){ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); +} + +MD5CTX md5_init(void) { + MD5CTX c = NULL; + gcry_md_open(&c, GCRY_MD_MD5, 0); + + return c; +} + +void md5_update(MD5CTX c, const void *data, unsigned long len) { + gcry_md_write(c,data,len); +} + +void md5_final(unsigned char *md, MD5CTX c) { + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); + gcry_md_close(c); +} + +ssh_mac_ctx ssh_mac_ctx_init(enum ssh_mac_e type){ + ssh_mac_ctx ctx=malloc(sizeof(struct ssh_mac_ctx_struct)); + ctx->mac_type=type; + switch(type){ + case SSH_MAC_SHA1: + gcry_md_open(&ctx->ctx, GCRY_MD_SHA1, 0); + break; + case SSH_MAC_SHA256: + gcry_md_open(&ctx->ctx, GCRY_MD_SHA256, 0); + break; + case SSH_MAC_SHA384: + gcry_md_open(&ctx->ctx, GCRY_MD_SHA384, 0); + break; + case SSH_MAC_SHA512: + gcry_md_open(&ctx->ctx, GCRY_MD_SHA512, 0); + break; + default: + SAFE_FREE(ctx); + return NULL; + } + return ctx; +} + +void ssh_mac_update(ssh_mac_ctx ctx, const void *data, unsigned long len) { + gcry_md_write(ctx->ctx,data,len); +} + +void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) { + size_t len; + switch(ctx->mac_type){ + case SSH_MAC_SHA1: + len=SHA_DIGEST_LEN; + break; + case SSH_MAC_SHA256: + len=SHA256_DIGEST_LENGTH; + break; + case SSH_MAC_SHA384: + len=SHA384_DIGEST_LENGTH; + break; + case SSH_MAC_SHA512: + len=SHA512_DIGEST_LENGTH; + break; + } + gcry_md_final(ctx->ctx); + memcpy(md, gcry_md_read(ctx->ctx, 0), len); + gcry_md_close(ctx->ctx); + SAFE_FREE(ctx); +} + +HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { + HMACCTX c = NULL; + + switch(type) { + case SSH_HMAC_SHA1: + gcry_md_open(&c, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_MD5: + gcry_md_open(&c, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC); + break; + default: + c = NULL; + } + + gcry_md_setkey(c, key, len); + + return c; +} + +void hmac_update(HMACCTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); +} + +void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) { + *len = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + memcpy(hashmacbuf, gcry_md_read(c, 0), *len); + gcry_md_close(c); +} + +/* the wrapper functions for blowfish */ +static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_BLOWFISH, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 16)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + int mode=GCRY_CIPHER_MODE_CBC; + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if(strstr(cipher->name,"-ctr")) + mode=GCRY_CIPHER_MODE_CTR; + switch (cipher->keysize) { + case 128: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 192: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES192, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 256: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES256, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + } + if (gcry_cipher_setkey(cipher->key[0], key, cipher->keysize / 8)) { + SAFE_FREE(cipher->key); + return -1; + } + if(mode == GCRY_CIPHER_MODE_CBC){ + if (gcry_cipher_setiv(cipher->key[0], IV, 16)) { + + SAFE_FREE(cipher->key); + return -1; + } + } else { + if(gcry_cipher_setctr(cipher->key[0],IV,16)){ + SAFE_FREE(cipher->key); + return -1; + } + } + } + + return 0; +} + +static void aes_encrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void aes_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static int des1_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if(!cipher->key){ + if (alloc_key(cipher) < 0) { + return -1; + } + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + } + return 0; +} + +static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_3DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 24)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + + +static void des1_1_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void des1_1_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static int des3_1_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + + if (gcry_cipher_open(&cipher->key[1], GCRY_CIPHER_DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[1], (unsigned char *)key + 8, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[1], (unsigned char *)IV + 8, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + + if (gcry_cipher_open(&cipher->key[2], GCRY_CIPHER_DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[2], (unsigned char *)key + 16, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[2], (unsigned char *)IV + 16, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void des3_1_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); + gcry_cipher_decrypt(cipher->key[1], in, len, out, len); + gcry_cipher_encrypt(cipher->key[2], out, len, in, len); +} + +static void des3_1_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long len) { + gcry_cipher_decrypt(cipher->key[2], out, len, in, len); + gcry_cipher_encrypt(cipher->key[1], in, len, out, len); + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +/* the table of supported ciphers */ +static struct ssh_cipher_struct ssh_ciphertab[] = { + { + .name = "blowfish-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = blowfish_set_key, + .set_decrypt_key = blowfish_set_key, + .cbc_encrypt = blowfish_encrypt, + .cbc_decrypt = blowfish_decrypt + }, + { + .name = "aes128-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .cbc_encrypt = aes_encrypt, + .cbc_decrypt = aes_encrypt + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .cbc_encrypt = aes_encrypt, + .cbc_decrypt = aes_encrypt + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .cbc_encrypt = aes_encrypt, + .cbc_decrypt = aes_encrypt + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .cbc_encrypt = aes_encrypt, + .cbc_decrypt = aes_decrypt + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .cbc_encrypt = aes_encrypt, + .cbc_decrypt = aes_decrypt + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .cbc_encrypt = aes_encrypt, + .cbc_decrypt = aes_decrypt + }, + { + .name = "3des-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = des3_set_key, + .set_decrypt_key = des3_set_key, + .cbc_encrypt = des3_encrypt, + .cbc_decrypt = des3_decrypt + }, + { + .name = "3des-cbc-ssh1", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t) * 3, + .key = NULL, + .keysize = 192, + .set_encrypt_key = des3_1_set_key, + .set_decrypt_key = des3_1_set_key, + .cbc_encrypt = des3_1_encrypt, + .cbc_decrypt = des3_1_decrypt + }, + { + .name = "des-cbc-ssh1", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 64, + .set_encrypt_key = des1_set_key, + .set_decrypt_key = des1_set_key, + .cbc_encrypt = des1_1_encrypt, + .cbc_decrypt = des1_1_decrypt + }, + { + .name = NULL, + .blocksize = 0, + .keylen = 0, + .key = NULL, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .cbc_encrypt = NULL, + .cbc_decrypt = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +#endif diff --git a/libssh/src/log.c b/libssh/src/log.c new file mode 100644 index 00000000..687afabc --- /dev/null +++ b/libssh/src/log.c @@ -0,0 +1,152 @@ +/* + * log.c - logging and debugging functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif +#include + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_log The SSH logging functions. + * @ingroup libssh + * + * Logging functions for debugging and problem resolving. + * + * @{ + */ + +static int current_timestring(int hires, char *buf, size_t len) +{ + char tbuf[64]; + struct timeval tv; + struct tm *tm; + time_t t; + + gettimeofday(&tv, NULL); + t = (time_t) tv.tv_sec; + + tm = localtime(&t); + if (tm == NULL) { + return -1; + } + + if (hires) { + strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + snprintf(buf, len, "%s.%06ld", tbuf, tv.tv_usec); + } else { + strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + snprintf(buf, len, "%s", tbuf); + } + + return 0; +} + +void ssh_log_function(int verbosity, + const char *function, + const char *buffer) +{ + char date[64] = {0}; + int rc; + + rc = current_timestring(1, date, sizeof(date)); + if (rc == 0) { + fprintf(stderr, "[%s, %d] %s", date, verbosity, function); + } else { + fprintf(stderr, "[%d] %s", verbosity, function); + } + fprintf(stderr, " %s\n", buffer); +} + +/** @internal + * @brief do the actual work of logging an event + */ + +static void do_ssh_log(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *buffer) { + if (common->callbacks && common->callbacks->log_function) { + char buf[1024]; + + snprintf(buf, sizeof(buf), "%s: %s", function, buffer); + + common->callbacks->log_function((ssh_session)common, + verbosity, + buf, + common->callbacks->userdata); + return; + } + + ssh_log_function(verbosity, function, buffer); +} + +/* legacy function */ +void ssh_log(ssh_session session, + int verbosity, + const char *format, ...) +{ + char buffer[1024]; + va_list va; + + if (verbosity <= session->common.log_verbosity) { + va_start(va, format); + vsnprintf(buffer, sizeof(buffer), format, va); + va_end(va); + do_ssh_log(&session->common, verbosity, "", buffer); + } +} + +/** @internal + * @brief log a SSH event with a common pointer + * @param common The SSH/bind session. + * @param verbosity The verbosity of the event. + * @param format The format string of the log entry. + */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) +{ + char buffer[1024]; + va_list va; + + if (verbosity <= common->log_verbosity) { + va_start(va, format); + vsnprintf(buffer, sizeof(buffer), format, va); + va_end(va); + do_ssh_log(common, verbosity, function, buffer); + } +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/match.c b/libssh/src/match.c new file mode 100644 index 00000000..53620bdd --- /dev/null +++ b/libssh/src/match.c @@ -0,0 +1,189 @@ +/* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Simple pattern matching, with '*' and '?' as wildcards. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "libssh/priv.h" + +/* + * Returns true if the given string matches the pattern (which may contain ? + * and * as wildcards), and zero if it does not match. + */ +static int match_pattern(const char *s, const char *pattern) { + if (s == NULL || pattern == NULL) { + return 0; + } + + for (;;) { + /* If at end of pattern, accept if also at end of string. */ + if (*pattern == '\0') { + return (*s == '\0'); + } + + if (*pattern == '*') { + /* Skip the asterisk. */ + pattern++; + + /* If at end of pattern, accept immediately. */ + if (!*pattern) + return 1; + + /* If next character in pattern is known, optimize. */ + if (*pattern != '?' && *pattern != '*') { + /* + * Look instances of the next character in + * pattern, and try to match starting from + * those. + */ + for (; *s; s++) + if (*s == *pattern && match_pattern(s + 1, pattern + 1)) { + return 1; + } + /* Failed. */ + return 0; + } + /* + * Move ahead one character at a time and try to + * match at each position. + */ + for (; *s; s++) { + if (match_pattern(s, pattern)) { + return 1; + } + } + /* Failed. */ + return 0; + } + /* + * There must be at least one more character in the string. + * If we are at the end, fail. + */ + if (!*s) { + return 0; + } + + /* Check if the next character of the string is acceptable. */ + if (*pattern != '?' && *pattern != *s) { + return 0; + } + + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } + + /* NOTREACHED */ +} + +/* + * Tries to match the string against the comma-separated sequence of subpatterns + * (each possibly preceded by ! to indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is + * no match at all. + */ +static int match_pattern_list(const char *string, const char *pattern, + unsigned int len, int dolower) { + char sub[1024]; + int negated; + int got_positive; + unsigned int i, subi; + + got_positive = 0; + for (i = 0; i < len;) { + /* Check if the subpattern is negated. */ + if (pattern[i] == '!') { + negated = 1; + i++; + } else { + negated = 0; + } + + /* + * Extract the subpattern up to a comma or end. Convert the + * subpattern to lowercase. + */ + for (subi = 0; + i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; + subi++, i++) { + sub[subi] = dolower && isupper(pattern[i]) ? + (char)tolower(pattern[i]) : pattern[i]; + } + + /* If subpattern too long, return failure (no match). */ + if (subi >= sizeof(sub) - 1) { + return 0; + } + + /* If the subpattern was terminated by a comma, skip the comma. */ + if (i < len && pattern[i] == ',') { + i++; + } + + /* Null-terminate the subpattern. */ + sub[subi] = '\0'; + + /* Try to match the subpattern against the string. */ + if (match_pattern(string, sub)) { + if (negated) { + return -1; /* Negative */ + } else { + got_positive = 1; /* Positive */ + } + } + } + + /* + * Return success if got a positive match. If there was a negative + * match, we have already returned -1 and never get here. + */ + return got_positive; +} + +/* + * Tries to match the host name (which must be in all lowercase) against the + * comma-separated sequence of subpatterns (each possibly preceded by ! to + * indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there + * is no match at all. + */ +int match_hostname(const char *host, const char *pattern, unsigned int len) { + return match_pattern_list(host, pattern, len, 1); +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/messages.c b/libssh/src/messages.c new file mode 100644 index 00000000..1ef8a745 --- /dev/null +++ b/libssh/src/messages.c @@ -0,0 +1,1319 @@ +/* + * messages.c - message parsing for client and server + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/dh.h" +#include "libssh/messages.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#endif + +/** + * @defgroup libssh_messages The SSH message functions + * @ingroup libssh + * + * This file contains the message parsing utilities for client and server + * programs using libssh. + * + * On the server the the main loop of the program will call + * ssh_message_get(session) to get messages as they come. They are not 1-1 with + * the protocol messages. Then, the user will know what kind of a message it is + * and use the appropriate functions to handle it (or use the default handlers + * if you don't know what to do). + * + * @{ + */ + +static ssh_message ssh_message_new(ssh_session session){ + ssh_message msg = malloc(sizeof(struct ssh_message_struct)); + if (msg == NULL) { + return NULL; + } + ZERO_STRUCTP(msg); + msg->session = session; + return msg; +} + +#ifndef WITH_SERVER + +/* Reduced version of the reply default that only reply with + * SSH_MSG_UNIMPLEMENTED + */ +static int ssh_message_reply_default(ssh_message msg) { + ssh_log(msg->session, SSH_LOG_FUNCTIONS, "Reporting unknown packet"); + + if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_UNIMPLEMENTED) < 0) + goto error; + if (buffer_add_u32(msg->session->out_buffer, + htonl(msg->session->recv_seq-1)) < 0) + goto error; + return packet_send(msg->session); + error: + return SSH_ERROR; +} + +#endif + +static int ssh_execute_message_callback(ssh_session session, ssh_message msg) { + int ret; + if(session->ssh_message_callback != NULL) { + ret = session->ssh_message_callback(session, msg, + session->ssh_message_callback_data); + if(ret == 1) { + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) { + return ret; + } + } else { + ssh_message_free(msg); + } + } else { + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) { + return ret; + } + } + return SSH_OK; +} + + +/** + * @internal + * + * @brief Add a message to the current queue of messages to be parsed. + * + * @param[in] session The SSH session to add the message. + * + * @param[in] message The message to add to the queue. + */ +void ssh_message_queue(ssh_session session, ssh_message message){ + if(message) { + if(session->ssh_message_list == NULL) { + if(session->ssh_message_callback != NULL) { + ssh_execute_message_callback(session, message); + return; + } + session->ssh_message_list = ssh_list_new(); + } + ssh_list_append(session->ssh_message_list, message); + } +} + +/** + * @internal + * + * @brief Pop a message from the message list and dequeue it. + * + * @param[in] session The SSH session to pop the message. + * + * @returns The head message or NULL if it doesn't exist. + */ +ssh_message ssh_message_pop_head(ssh_session session){ + ssh_message msg=NULL; + struct ssh_iterator *i; + if(session->ssh_message_list == NULL) + return NULL; + i=ssh_list_get_iterator(session->ssh_message_list); + if(i != NULL){ + msg=ssh_iterator_value(ssh_message,i); + ssh_list_remove(session->ssh_message_list,i); + } + return msg; +} + +/* Returns 1 if there is a message available */ +static int ssh_message_termination(void *s){ + ssh_session session = s; + struct ssh_iterator *it; + if(session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + it = ssh_list_get_iterator(session->ssh_message_list); + if(!it) + return 0; + else + return 1; +} +/** + * @brief Retrieve a SSH message from a SSH session. + * + * @param[in] session The SSH session to get the message. + * + * @returns The SSH message received, NULL in case of error, or timeout + * elapsed. + * + * @warning This function blocks until a message has been received. Betterset up + * a callback if this behavior is unwanted. + */ +ssh_message ssh_message_get(ssh_session session) { + ssh_message msg = NULL; + int rc; + enter_function(); + + msg=ssh_message_pop_head(session); + if(msg) { + leave_function(); + return msg; + } + if(session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + } + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_message_termination, session); + if(rc || session->session_state == SSH_SESSION_STATE_ERROR) + return NULL; + msg=ssh_list_pop_head(ssh_message, session->ssh_message_list); + leave_function(); + return msg; +} + +/** + * @brief Get the type of the message. + * + * @param[in] msg The message to get the type from. + * + * @return The message type or -1 on error. + */ +int ssh_message_type(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->type; +} + +/** + * @brief Get the subtype of the message. + * + * @param[in] msg The message to get the subtype from. + * + * @return The message type or -1 on error. + */ +int ssh_message_subtype(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return msg->auth_request.method; + case SSH_REQUEST_CHANNEL_OPEN: + return msg->channel_request_open.type; + case SSH_REQUEST_CHANNEL: + return msg->channel_request.type; + case SSH_REQUEST_GLOBAL: + return msg->global_request.type; + } + + return -1; +} + +/** + * @brief Free a SSH message. + * + * @param[in] msg The message to release the memory. + */ +void ssh_message_free(ssh_message msg){ + if (msg == NULL) { + return; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + SAFE_FREE(msg->auth_request.username); + if (msg->auth_request.password) { + memset(msg->auth_request.password, 0, + strlen(msg->auth_request.password)); + SAFE_FREE(msg->auth_request.password); + } + ssh_key_free(msg->auth_request.pubkey); + break; + case SSH_REQUEST_CHANNEL_OPEN: + SAFE_FREE(msg->channel_request_open.originator); + SAFE_FREE(msg->channel_request_open.destination); + break; + case SSH_REQUEST_CHANNEL: + SAFE_FREE(msg->channel_request.TERM); + SAFE_FREE(msg->channel_request.modes); + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + SAFE_FREE(msg->channel_request.command); + SAFE_FREE(msg->channel_request.subsystem); + break; + case SSH_REQUEST_SERVICE: + SAFE_FREE(msg->service_request.service); + break; + case SSH_REQUEST_GLOBAL: + SAFE_FREE(msg->global_request.bind_address); + break; + } + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +SSH_PACKET_CALLBACK(ssh_packet_service_request){ + ssh_string service = NULL; + char *service_c = NULL; + ssh_message msg=NULL; + + enter_function(); + (void)type; + (void)user; + service = buffer_get_ssh_string(packet); + if (service == NULL) { + ssh_set_error(session, SSH_FATAL, "Invalid SSH_MSG_SERVICE_REQUEST packet"); + goto error; + } + + service_c = ssh_string_to_char(service); + if (service_c == NULL) { + goto error; + } + ssh_log(session, SSH_LOG_PACKET, + "Received a SERVICE_REQUEST for service %s", service_c); + msg=ssh_message_new(session); + if(!msg){ + SAFE_FREE(service_c); + goto error; + } + msg->type=SSH_REQUEST_SERVICE; + msg->service_request.service=service_c; +error: + ssh_string_free(service); + if(msg != NULL) + ssh_message_queue(session,msg); + leave_function(); + return SSH_PACKET_USED; +} + + +/* + * This function concats in a buffer the values needed to do a signature + * verification. + */ +static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, + ssh_message msg, + const char *service) +{ + struct ssh_crypto_struct *crypto = + session->current_crypto ? session->current_crypto : + session->next_crypto; + ssh_buffer buffer; + ssh_string str; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + /* Add session id */ + str = ssh_string_new(crypto->digest_len); + if (str == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + ssh_string_fill(str, crypto->session_id, crypto->digest_len); + + rc = buffer_add_ssh_string(buffer, str); + string_free(str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Add the type */ + rc = buffer_add_u8(buffer, SSH2_MSG_USERAUTH_REQUEST); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Add the username */ + str = ssh_string_from_char(msg->auth_request.username); + if (str == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + rc = buffer_add_ssh_string(buffer, str); + string_free(str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Add the service name */ + str = ssh_string_from_char(service); + if (str == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + rc = buffer_add_ssh_string(buffer, str); + string_free(str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Add the method (publickey) */ + str = ssh_string_from_char("publickey"); + if (str == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + rc = buffer_add_ssh_string(buffer, str); + string_free(str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Has been signed (TRUE) */ + rc = buffer_add_u8(buffer, 1); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Add the public key algorithm */ + str = ssh_string_from_char(msg->auth_request.pubkey->type_c); + if (str == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + rc = buffer_add_ssh_string(buffer, str); + string_free(str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + /* Add the publickey as blob */ + rc = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + rc = buffer_add_ssh_string(buffer, str); + string_free(str); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + return buffer; +} + +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a + * SSH Message + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ + ssh_string str; + ssh_message msg = NULL; + char *service = NULL; + char *method = NULL; + uint32_t method_size = 0; + + enter_function(); + + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_AUTH; + + str = buffer_get_ssh_string(packet); + if (str == NULL) { + goto error; + } + msg->auth_request.username = ssh_string_to_char(str); + ssh_string_free(str); + if (msg->auth_request.username == NULL) { + goto error; + } + + str = buffer_get_ssh_string(packet); + if (str == NULL) { + goto error; + } + service = ssh_string_to_char(str); + ssh_string_free(str); + if (service == NULL) { + goto error; + } + + str = buffer_get_ssh_string(packet); + if (str == NULL) { + goto error; + } + method = ssh_string_to_char(str); + method_size = ssh_string_len(str); + ssh_string_free(str); + if (method == NULL) { + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, + "Auth request for service %s, method %s for user '%s'", + service, method, + msg->auth_request.username); + + + if (strncmp(method, "none", method_size) == 0) { + msg->auth_request.method = SSH_AUTH_METHOD_NONE; + goto end; + } + + if (strncmp(method, "password", method_size) == 0) { + ssh_string pass = NULL; + uint8_t tmp; + + msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; + buffer_get_u8(packet, &tmp); + pass = buffer_get_ssh_string(packet); + if (pass == NULL) { + goto error; + } + msg->auth_request.password = ssh_string_to_char(pass); + ssh_string_burn(pass); + ssh_string_free(pass); + pass = NULL; + if (msg->auth_request.password == NULL) { + goto error; + } + goto end; + } + + if (strncmp(method, "keyboard-interactive", method_size) == 0) { + ssh_string lang = NULL; + ssh_string submethods = NULL; + + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + lang = buffer_get_ssh_string(packet); + if (lang == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "The language tag is deprecated and SHOULD be the empty string." + */ + ssh_string_free(lang); + + submethods = buffer_get_ssh_string(packet); + if (submethods == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "One possible implementation strategy of the submethods field on the + * server is that, unless the user may use multiple different + * submethods, the server ignores this field." + */ + ssh_string_free(submethods); + + goto end; + } + + if (strncmp(method, "publickey", method_size) == 0) { + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + uint8_t has_sign; + int rc; + + msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; + SAFE_FREE(method); + buffer_get_u8(packet, &has_sign); + algo = buffer_get_ssh_string(packet); + if (algo == NULL) { + goto error; + } + pubkey_blob = buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_string_free(algo); + algo = NULL; + goto error; + } + ssh_string_free(algo); + algo = NULL; + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey); + ssh_string_free(pubkey_blob); + pubkey_blob = NULL; + if (rc < 0) { + goto error; + } + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + // has a valid signature ? + if(has_sign) { + ssh_string sig_blob = NULL; + ssh_buffer digest = NULL; + + sig_blob = buffer_get_ssh_string(packet); + if(sig_blob == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid signature packet from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + goto error; + } + + digest = ssh_msg_userauth_build_digest(session, msg, service); + if (digest == NULL) { + ssh_string_free(sig_blob); + ssh_log(session, SSH_LOG_PACKET, "Failed to get digest"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_pki_signature_verify_blob(session, + sig_blob, + msg->auth_request.pubkey, + buffer_get_rest(digest), + buffer_get_rest_len(digest)); + ssh_string_free(sig_blob); + ssh_buffer_free(digest); + if (rc < 0) { + ssh_log(session, + SSH_LOG_PACKET, + "Received an invalid signature from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, "Valid signature received"); + + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; + } + goto end; + } + + msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(method); + goto end; +error: + SAFE_FREE(service); + SAFE_FREE(method); + + ssh_message_free(msg); + + leave_function(); + return SSH_PACKET_USED; +end: + SAFE_FREE(service); + SAFE_FREE(method); + + ssh_message_queue(session,msg); + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_INFO_RESPONSE packet and queue a + * SSH Message + */ +#ifndef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + (void)session; + (void)type; + (void)packet; + (void)user; + return SSH_PACKET_USED; +} +#else +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + uint32_t nanswers; + uint32_t i; + ssh_string tmp; + + ssh_message msg = NULL; + + enter_function(); + + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* HACK: we forge a message to be able to handle it in the + * same switch() as other auth methods */ + msg->type = SSH_REQUEST_AUTH; + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + msg->auth_request.kbdint_response = 1; +#if 0 // should we wipe the username ? + msg->auth_request.username = NULL; +#endif + + buffer_get_u32(packet, &nanswers); + + if (session->kbdint == NULL) { + ssh_log(session, SSH_LOG_PROTOCOL, "Warning: Got a keyboard-interactive " + "response but it seems we didn't send the request."); + + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + + goto error; + } + } + + nanswers = ntohl(nanswers); + ssh_log(session,SSH_LOG_PACKET,"kbdint: %d answers",nanswers); + if (nanswers > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much answers received from client: %u (0x%.4x)", + nanswers, nanswers); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + if(nanswers != session->kbdint->nprompts) { + /* warn but let the application handle this case */ + ssh_log(session, SSH_LOG_PROTOCOL, "Warning: Number of prompts and answers" + " mismatch: p=%u a=%u", session->kbdint->nprompts, nanswers); + } + session->kbdint->nanswers = nanswers; + session->kbdint->answers = malloc(nanswers * sizeof(char *)); + if (session->kbdint->answers == NULL) { + session->kbdint->nanswers = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + memset(session->kbdint->answers, 0, nanswers * sizeof(char *)); + + for (i = 0; i < nanswers; i++) { + tmp = buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_set_error(session, SSH_FATAL, "Short INFO_RESPONSE packet"); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + session->kbdint->answers[i] = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + } + + ssh_message_queue(session,msg); + leave_function(); + return SSH_PACKET_USED; + +error: + ssh_message_free(msg); + + leave_function(); + return SSH_PACKET_USED; +} +#endif + +SSH_PACKET_CALLBACK(ssh_packet_channel_open){ + ssh_message msg = NULL; + ssh_string type_s = NULL, originator = NULL, destination = NULL; + char *type_c = NULL; + uint32_t sender, window, packet_size, originator_port, destination_port; + + enter_function(); + (void)type; + (void)user; + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + msg->type = SSH_REQUEST_CHANNEL_OPEN; + + type_s = buffer_get_ssh_string(packet); + if (type_s == NULL) { + ssh_set_error_oom(session); + goto error; + } + type_c = ssh_string_to_char(type_s); + if (type_c == NULL) { + ssh_set_error_oom(session); + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, + "Clients wants to open a %s channel", type_c); + ssh_string_free(type_s); + type_s=NULL; + + buffer_get_u32(packet, &sender); + buffer_get_u32(packet, &window); + buffer_get_u32(packet, &packet_size); + + msg->channel_request_open.sender = ntohl(sender); + msg->channel_request_open.window = ntohl(window); + msg->channel_request_open.packet_size = ntohl(packet_size); + + if (strcmp(type_c,"session") == 0) { + msg->channel_request_open.type = SSH_CHANNEL_SESSION; + SAFE_FREE(type_c); + goto end; + } + + if (strcmp(type_c,"direct-tcpip") == 0) { + destination = buffer_get_ssh_string(packet); + if (destination == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->channel_request_open.destination = ssh_string_to_char(destination); + if (msg->channel_request_open.destination == NULL) { + ssh_set_error_oom(session); + ssh_string_free(destination); + goto error; + } + ssh_string_free(destination); + + buffer_get_u32(packet, &destination_port); + msg->channel_request_open.destination_port = (uint16_t) ntohl(destination_port); + + originator = buffer_get_ssh_string(packet); + if (originator == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->channel_request_open.originator = ssh_string_to_char(originator); + if (msg->channel_request_open.originator == NULL) { + ssh_set_error_oom(session); + ssh_string_free(originator); + goto error; + } + ssh_string_free(originator); + + buffer_get_u32(packet, &originator_port); + msg->channel_request_open.originator_port = (uint16_t) ntohl(originator_port); + + msg->channel_request_open.type = SSH_CHANNEL_DIRECT_TCPIP; + goto end; + } + + if (strcmp(type_c,"forwarded-tcpip") == 0) { + destination = buffer_get_ssh_string(packet); + if (destination == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->channel_request_open.destination = ssh_string_to_char(destination); + if (msg->channel_request_open.destination == NULL) { + ssh_set_error_oom(session); + ssh_string_free(destination); + goto error; + } + ssh_string_free(destination); + + buffer_get_u32(packet, &destination_port); + msg->channel_request_open.destination_port = (uint16_t) ntohl(destination_port); + + originator = buffer_get_ssh_string(packet); + if (originator == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->channel_request_open.originator = ssh_string_to_char(originator); + if (msg->channel_request_open.originator == NULL) { + ssh_set_error_oom(session); + ssh_string_free(originator); + goto error; + } + ssh_string_free(originator); + + buffer_get_u32(packet, &originator_port); + msg->channel_request_open.originator_port = (uint16_t) ntohl(originator_port); + + msg->channel_request_open.type = SSH_CHANNEL_FORWARDED_TCPIP; + goto end; + } + + if (strcmp(type_c,"x11") == 0) { + originator = buffer_get_ssh_string(packet); + if (originator == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->channel_request_open.originator = ssh_string_to_char(originator); + if (msg->channel_request_open.originator == NULL) { + ssh_set_error_oom(session); + ssh_string_free(originator); + goto error; + } + ssh_string_free(originator); + + buffer_get_u32(packet, &originator_port); + msg->channel_request_open.originator_port = (uint16_t) ntohl(originator_port); + + msg->channel_request_open.type = SSH_CHANNEL_X11; + goto end; + } + + msg->channel_request_open.type = SSH_CHANNEL_UNKNOWN; + goto end; + +error: + ssh_message_free(msg); + msg=NULL; +end: + if(type_s != NULL) + ssh_string_free(type_s); + SAFE_FREE(type_c); + if(msg != NULL) + ssh_message_queue(session,msg); + leave_function(); + return SSH_PACKET_USED; +} + +/* TODO: make this function accept a ssh_channel */ +ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { + ssh_session session; + ssh_channel chan = NULL; + + enter_function(); + + if (msg == NULL) { + leave_function(); + return NULL; + } + + session = msg->session; + + chan = ssh_channel_new(session); + if (chan == NULL) { + leave_function(); + return NULL; + } + + chan->local_channel = ssh_channel_new_id(session); + chan->local_maxpacket = 35000; + chan->local_window = 32000; + chan->remote_channel = msg->channel_request_open.sender; + chan->remote_maxpacket = msg->channel_request_open.packet_size; + chan->remote_window = msg->channel_request_open.window; + chan->state = SSH_CHANNEL_STATE_OPEN; + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer, htonl(chan->remote_channel)) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer, htonl(chan->local_channel)) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer, htonl(chan->local_window)) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer, htonl(chan->local_maxpacket)) < 0) { + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, + "Accepting a channel request_open for chan %d", chan->remote_channel); + + if (packet_send(session) == SSH_ERROR) { + goto error; + } + + leave_function(); + return chan; +error: + ssh_channel_free(chan); + + leave_function(); + return NULL; +} + +/** + * @internal + * + * @brief This function parses the last end of a channel request packet. + * + * This is normally converted to a SSH message and placed in the queue. + * + * @param[in] session The SSH session. + * + * @param[in] channel The channel the request is made on. + * + * @param[in] packet The rest of the packet to be parsed. + * + * @param[in] request The type of request. + * + * @param[in] want_reply The want_reply field from the request. + * + * @returns SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply) { + ssh_message msg = NULL; + enter_function(); + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)", + request, channel->local_channel, channel->remote_channel, want_reply); + + msg->type = SSH_REQUEST_CHANNEL; + msg->channel_request.channel = channel; + msg->channel_request.want_reply = want_reply; + + if (strcmp(request, "pty-req") == 0) { + ssh_string term = NULL; + char *term_c = NULL; + term = buffer_get_ssh_string(packet); + if (term == NULL) { + ssh_set_error_oom(session); + goto error; + } + term_c = ssh_string_to_char(term); + if (term_c == NULL) { + ssh_set_error_oom(session); + ssh_string_free(term); + goto error; + } + ssh_string_free(term); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; + msg->channel_request.TERM = term_c; + + buffer_get_u32(packet, &msg->channel_request.width); + buffer_get_u32(packet, &msg->channel_request.height); + buffer_get_u32(packet, &msg->channel_request.pxwidth); + buffer_get_u32(packet, &msg->channel_request.pxheight); + + msg->channel_request.width = ntohl(msg->channel_request.width); + msg->channel_request.height = ntohl(msg->channel_request.height); + msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth); + msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight); + msg->channel_request.modes = buffer_get_ssh_string(packet); + if (msg->channel_request.modes == NULL) { + SAFE_FREE(term_c); + goto error; + } + goto end; + } + + if (strcmp(request, "window-change") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; + + buffer_get_u32(packet, &msg->channel_request.width); + buffer_get_u32(packet, &msg->channel_request.height); + buffer_get_u32(packet, &msg->channel_request.pxwidth); + buffer_get_u32(packet, &msg->channel_request.pxheight); + + msg->channel_request.width = ntohl(msg->channel_request.width); + msg->channel_request.height = ntohl(msg->channel_request.height); + msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth); + msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight); + + goto end; + } + + if (strcmp(request, "subsystem") == 0) { + ssh_string subsys = NULL; + char *subsys_c = NULL; + subsys = buffer_get_ssh_string(packet); + if (subsys == NULL) { + ssh_set_error_oom(session); + goto error; + } + subsys_c = ssh_string_to_char(subsys); + if (subsys_c == NULL) { + ssh_set_error_oom(session); + ssh_string_free(subsys); + goto error; + } + ssh_string_free(subsys); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; + msg->channel_request.subsystem = subsys_c; + + goto end; + } + + if (strcmp(request, "shell") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; + goto end; + } + + if (strcmp(request, "exec") == 0) { + ssh_string cmd = NULL; + cmd = buffer_get_ssh_string(packet); + if (cmd == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; + msg->channel_request.command = ssh_string_to_char(cmd); + ssh_string_free(cmd); + if (msg->channel_request.command == NULL) { + goto error; + } + goto end; + } + + if (strcmp(request, "env") == 0) { + ssh_string name = NULL; + ssh_string value = NULL; + name = buffer_get_ssh_string(packet); + if (name == NULL) { + ssh_set_error_oom(session); + goto error; + } + value = buffer_get_ssh_string(packet); + if (value == NULL) { + ssh_set_error_oom(session); + ssh_string_free(name); + goto error; + } + + msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; + msg->channel_request.var_name = ssh_string_to_char(name); + msg->channel_request.var_value = ssh_string_to_char(value); + if (msg->channel_request.var_name == NULL || + msg->channel_request.var_value == NULL) { + ssh_string_free(name); + ssh_string_free(value); + goto error; + } + ssh_string_free(name); + ssh_string_free(value); + + goto end; + } + + if (strcmp(request, "x11-req") == 0) { + ssh_string auth_protocol = NULL; + ssh_string auth_cookie = NULL; + + buffer_get_u8(packet, &msg->channel_request.x11_single_connection); + + auth_protocol = buffer_get_ssh_string(packet); + if (auth_protocol == NULL) { + ssh_set_error_oom(session); + goto error; + } + auth_cookie = buffer_get_ssh_string(packet); + if (auth_cookie == NULL) { + ssh_set_error_oom(session); + ssh_string_free(auth_protocol); + goto error; + } + + msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; + msg->channel_request.x11_auth_protocol = ssh_string_to_char(auth_protocol); + msg->channel_request.x11_auth_cookie = ssh_string_to_char(auth_cookie); + if (msg->channel_request.x11_auth_protocol == NULL || + msg->channel_request.x11_auth_cookie == NULL) { + ssh_string_free(auth_protocol); + ssh_string_free(auth_cookie); + goto error; + } + ssh_string_free(auth_protocol); + ssh_string_free(auth_cookie); + + buffer_get_u32(packet, &msg->channel_request.x11_screen_number); + + goto end; + } + + msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; +end: + ssh_message_queue(session,msg); + leave_function(); + return SSH_OK; +error: + ssh_message_free(msg); + + leave_function(); + return SSH_ERROR; +} + +int ssh_message_channel_request_reply_success(ssh_message msg) { + uint32_t channel; + + if (msg == NULL) { + return SSH_ERROR; + } + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + ssh_log(msg->session, SSH_LOG_PACKET, + "Sending a channel_request success to channel %d", channel); + + if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_CHANNEL_SUCCESS) < 0) { + return SSH_ERROR; + } + if (buffer_add_u32(msg->session->out_buffer, htonl(channel)) < 0) { + return SSH_ERROR; + } + + return packet_send(msg->session); + } + + ssh_log(msg->session, SSH_LOG_PACKET, + "The client doesn't want to know the request succeeded"); + + return SSH_OK; +} + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_global_request){ + ssh_message msg = NULL; + ssh_string request_s=NULL; + char *request=NULL; + ssh_string bind_addr_s=NULL; + char *bind_addr=NULL; + uint32_t bind_port; + uint8_t want_reply; + int rc = SSH_PACKET_USED; + (void)user; + (void)type; + (void)packet; + + request_s = buffer_get_ssh_string(packet); + if (request_s != NULL) { + request = ssh_string_to_char(request_s); + ssh_string_free(request_s); + } + + buffer_get_u8(packet, &want_reply); + + ssh_log(session,SSH_LOG_PROTOCOL,"Received SSH_MSG_GLOBAL_REQUEST packet"); + + msg = ssh_message_new(session); + if (msg == NULL) { + return SSH_PACKET_NOT_USED; + } + msg->type = SSH_REQUEST_GLOBAL; + + if (request && strcmp(request, "tcpip-forward") == 0) { + bind_addr_s = buffer_get_ssh_string(packet); + if (bind_addr_s != NULL) { + bind_addr = ssh_string_to_char(bind_addr_s); + ssh_string_free(bind_addr_s); + } + + buffer_get_u32(packet, &bind_port); + bind_port = ntohl(bind_port); + + msg->global_request.type = SSH_GLOBAL_REQUEST_TCPIP_FORWARD; + msg->global_request.want_reply = want_reply; + msg->global_request.bind_address = bind_addr; + msg->global_request.bind_port = bind_port; + + ssh_log(session, SSH_LOG_PROTOCOL, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, bind_addr, bind_port); + + if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { + ssh_log(session, SSH_LOG_PROTOCOL, "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, bind_addr, bind_port); + session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); + } else { + ssh_message_reply_default(msg); + } + } else if (request && strcmp(request, "cancel-tcpip-forward") == 0) { + bind_addr_s = buffer_get_ssh_string(packet); + if (bind_addr_s != NULL) { + bind_addr = ssh_string_to_char(bind_addr_s); + ssh_string_free(bind_addr_s); + } + buffer_get_u32(packet, &bind_port); + bind_port = ntohl(bind_port); + + msg->global_request.type = SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD; + msg->global_request.want_reply = want_reply; + msg->global_request.bind_address = bind_addr; + msg->global_request.bind_port = bind_port; + + ssh_log(session, SSH_LOG_PROTOCOL, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, bind_addr, bind_port); + + if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { + session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); + } else { + ssh_message_reply_default(msg); + } + } else { + ssh_log(session, SSH_LOG_PROTOCOL, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s %d", request, want_reply); + rc = SSH_PACKET_NOT_USED; + } + + SAFE_FREE(msg); + SAFE_FREE(request); + SAFE_FREE(bind_addr); + + return rc; +} + +#endif /* WITH_SERVER */ + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/misc.c b/libssh/src/misc.c new file mode 100644 index 00000000..99f60b48 --- /dev/null +++ b/libssh/src/misc.c @@ -0,0 +1,1036 @@ +/* + * misc.c - useful client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2008-2009 by Andreas Schneider + * + * 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" + +#ifndef _WIN32 +/* This is needed for a standard getpwuid_r on opensolaris */ +#define _POSIX_PTHREAD_SEMANTICS +#include +#include +#include +#include +#include + +#ifndef HAVE_CLOCK_GETTIME +#include +#endif /* HAVE_CLOCK_GETTIME */ +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#ifndef _WIN32_IE +# define _WIN32_IE 0x0501 // SHGetSpecialFolderPath +#endif + +#include // Must be the first to include +#include +#include +#include + +#if _MSC_VER >= 1400 +# include +#endif /* _MSC_VER */ + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#ifdef HAVE_LIBGCRYPT +#define GCRYPT_STRING "/gnutls" +#else +#define GCRYPT_STRING "" +#endif + +#ifdef HAVE_LIBCRYPTO +#define CRYPTO_STRING "/openssl" +#else +#define CRYPTO_STRING "" +#endif + +#ifdef WITH_ZLIB +#define ZLIB_STRING "/zlib" +#else +#define ZLIB_STRING "" +#endif + +/** + * @defgroup libssh_misc The SSH helper functions. + * @ingroup libssh + * + * Different helper functions used in the SSH Library. + * + * @{ + */ + +#ifdef _WIN32 +char *ssh_get_user_home_dir(void) { + char tmp[MAX_PATH] = {0}; + char *szPath = NULL; + + if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { + szPath = malloc(strlen(tmp) + 1); + if (szPath == NULL) { + return NULL; + } + + strcpy(szPath, tmp); + return szPath; + } + + return NULL; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) { + if (_access(file, 4) < 0) { + return 0; + } + + return 1; +} + +#define SSH_USEC_IN_SEC 1000000LL +#define SSH_SECONDS_SINCE_1601 11644473600LL + +int gettimeofday(struct timeval *__p, void *__t) { + union { + unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } now; + + GetSystemTimeAsFileTime (&now.ft); + __p->tv_usec = (long) ((now.ns100 / 10LL) % SSH_USEC_IN_SEC); + __p->tv_sec = (long)(((now.ns100 / 10LL ) / SSH_USEC_IN_SEC) - SSH_SECONDS_SINCE_1601); + + return (0); +} + +char *ssh_get_local_username(void) { + DWORD size = 0; + char *user; + + /* get the size */ + GetUserName(NULL, &size); + + user = (char *) malloc(size); + if (user == NULL) { + return NULL; + } + + if (GetUserName(user, &size)) { + return user; + } + + return NULL; +} + +int ssh_is_ipaddr_v4(const char *str) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); + int rc = SOCKET_ERROR; + + /* WSAStringToAddressA thinks that 0.0.0 is a valid IP */ + if (strlen(str) < 7) { + return 0; + } + + rc = WSAStringToAddressA((LPSTR) str, + AF_INET, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) { + int rc = SOCKET_ERROR; + + if (strchr(str, ':')) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); + + /* TODO link-local (IP:v6:addr%ifname). */ + rc = WSAStringToAddressA((LPSTR) str, + AF_INET6, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + return 1; + } + } + + return ssh_is_ipaddr_v4(str); +} +#else /* _WIN32 */ + +#ifndef NSS_BUFLEN_PASSWD +#define NSS_BUFLEN_PASSWD 4096 +#endif /* NSS_BUFLEN_PASSWD */ + +char *ssh_get_user_home_dir(void) { + char *szPath = NULL; + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0) { + szPath = getenv("HOME"); + if (szPath == NULL) { + return NULL; + } + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "%s", getenv("HOME")); + + return strdup(buf); + } + + szPath = strdup(pwd.pw_dir); + + return szPath; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) { + if (access(file, R_OK) < 0) { + return 0; + } + + return 1; +} + +char *ssh_get_local_username(void) { + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + char *name; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0) { + return NULL; + } + + name = strdup(pwd.pw_name); + + if (name == NULL) { + return NULL; + } + + return name; +} + +int ssh_is_ipaddr_v4(const char *str) { + int rc = -1; + struct in_addr dest; + + rc = inet_pton(AF_INET, str, &dest); + if (rc > 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) { + int rc = -1; + + if (strchr(str, ':')) { + struct in6_addr dest6; + + /* TODO link-local (IP:v6:addr%ifname). */ + rc = inet_pton(AF_INET6, str, &dest6); + if (rc > 0) { + return 1; + } + } + + return ssh_is_ipaddr_v4(str); +} + +#endif /* _WIN32 */ + +#ifndef HAVE_NTOHLL +uint64_t ntohll(uint64_t a) { +#ifdef WORDS_BIGENDIAN + return a; +#else /* WORDS_BIGENDIAN */ + return (((uint64_t)(a) << 56) | \ + (((uint64_t)(a) << 40) & 0xff000000000000ULL) | \ + (((uint64_t)(a) << 24) & 0xff0000000000ULL) | \ + (((uint64_t)(a) << 8) & 0xff00000000ULL) | \ + (((uint64_t)(a) >> 8) & 0xff000000ULL) | \ + (((uint64_t)(a) >> 24) & 0xff0000ULL) | \ + (((uint64_t)(a) >> 40) & 0xff00ULL) | \ + ((uint64_t)(a) >> 56)); +#endif /* WORDS_BIGENDIAN */ +} +#endif /* HAVE_NTOHLL */ + +char *ssh_lowercase(const char* str) { + char *new, *p; + + if (str == NULL) { + return NULL; + } + + new = strdup(str); + if (new == NULL) { + return NULL; + } + + for (p = new; *p; p++) { + *p = tolower(*p); + } + + return new; +} + +char *ssh_hostport(const char *host, int port){ + char *dest; + size_t len; + if(host==NULL) + return NULL; + /* 3 for []:, 5 for 65536 and 1 for nul */ + len=strlen(host) + 3 + 5 + 1; + dest=malloc(len); + if(dest==NULL) + return NULL; + snprintf(dest,len,"[%s]:%d",host,port); + return dest; +} + +/** + * @brief Check if libssh is the required version or get the version + * string. + * + * @param[in] req_version The version required. + * + * @return If the version of libssh is newer than the version + * required it will return a version string. + * NULL if the version is older. + * + * Example: + * + * @code + * if (ssh_version(SSH_VERSION_INT(0,2,1)) == NULL) { + * fprintf(stderr, "libssh version is too old!\n"); + * exit(1); + * } + * + * if (debug) { + * printf("libssh %s\n", ssh_version(0)); + * } + * @endcode + */ +const char *ssh_version(int req_version) { + if (req_version <= LIBSSH_VERSION_INT) { + return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING + ZLIB_STRING; + } + + return NULL; +} + +struct ssh_list *ssh_list_new(void) { + struct ssh_list *ret=malloc(sizeof(struct ssh_list)); + if(!ret) + return NULL; + ret->root=ret->end=NULL; + return ret; +} + +void ssh_list_free(struct ssh_list *list){ + struct ssh_iterator *ptr,*next; + if(!list) + return; + ptr=list->root; + while(ptr){ + next=ptr->next; + SAFE_FREE(ptr); + ptr=next; + } + SAFE_FREE(list); +} + +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list){ + if(!list) + return NULL; + return list->root; +} + +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ + struct ssh_iterator *it; + for(it = ssh_list_get_iterator(list); it != NULL ;it=it->next) + if(it->data==value) + return it; + return NULL; +} + +static struct ssh_iterator *ssh_iterator_new(const void *data){ + struct ssh_iterator *iterator=malloc(sizeof(struct ssh_iterator)); + if(!iterator) + return NULL; + iterator->next=NULL; + iterator->data=data; + return iterator; +} + +int ssh_list_append(struct ssh_list *list,const void *data){ + struct ssh_iterator *iterator=ssh_iterator_new(data); + if(!iterator) + return SSH_ERROR; + if(!list->end){ + /* list is empty */ + list->root=list->end=iterator; + } else { + /* put it on end of list */ + list->end->next=iterator; + list->end=iterator; + } + return SSH_OK; +} + +int ssh_list_prepend(struct ssh_list *list, const void *data){ + struct ssh_iterator *it = ssh_iterator_new(data); + + if (it == NULL) { + return SSH_ERROR; + } + + if (list->end == NULL) { + /* list is empty */ + list->root = list->end = it; + } else { + /* set as new root */ + it->next = list->root; + list->root = it; + } + + return SSH_OK; +} + +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ + struct ssh_iterator *ptr,*prev; + prev=NULL; + ptr=list->root; + while(ptr && ptr != iterator){ + prev=ptr; + ptr=ptr->next; + } + if(!ptr){ + /* we did not find the element */ + return; + } + /* unlink it */ + if(prev) + prev->next=ptr->next; + /* if iterator was the head */ + if(list->root == iterator) + list->root=iterator->next; + /* if iterator was the tail */ + if(list->end == iterator) + list->end = prev; + SAFE_FREE(iterator); +} + +/** + * @internal + * + * @brief Removes the top element of the list and returns the data value + * attached to it. + * + * @param[in[ list The ssh_list to remove the element. + * + * @returns A pointer to the element being stored in head, or NULL + * if the list is empty. + */ +const void *_ssh_list_pop_head(struct ssh_list *list){ + struct ssh_iterator *iterator=list->root; + const void *data; + if(!list->root) + return NULL; + data=iterator->data; + list->root=iterator->next; + if(list->end==iterator) + list->end=NULL; + SAFE_FREE(iterator); + return data; +} + +/** + * @brief Parse directory component. + * + * dirname breaks a null-terminated pathname string into a directory component. + * In the usual case, ssh_dirname() returns the string up to, but not including, + * the final '/'. Trailing '/' characters are not counted as part of the + * pathname. The caller must free the memory. + * + * @param[in] path The path to parse. + * + * @return The dirname of path or NULL if we can't allocate memory. + * If path does not contain a slash, c_dirname() returns + * the string ".". If path is the string "/", it returns + * the string "/". If path is NULL or an empty string, + * "." is returned. + */ +char *ssh_dirname (const char *path) { + char *new = NULL; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + /* goto next slash */ + while(len > 0 && path[len - 1] != '/') --len; + + if (len == 0) { + return strdup("."); + } else if (len == 1) { + return strdup("/"); + } + + /* Remove slashes again */ + while(len > 0 && path[len - 1] == '/') --len; + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, path, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief basename - parse filename component. + * + * basename breaks a null-terminated pathname string into a filename component. + * ssh_basename() returns the component following the final '/'. Trailing '/' + * characters are not counted as part of the pathname. + * + * @param[in] path The path to parse. + * + * @return The filename of path or NULL if we can't allocate + * memory. If path is a the string "/", basename returns + * the string "/". If path is NULL or an empty string, + * "." is returned. + */ +char *ssh_basename (const char *path) { + char *new = NULL; + const char *s; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + while(len > 0 && path[len - 1] != '/') --len; + + if (len > 0) { + s = path + len; + len = strlen(s); + + while(len > 0 && s[len - 1] == '/') --len; + } else { + return strdup(path); + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, s, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Attempts to create a directory with the given pathname. + * + * This is the portable version of mkdir, mode is ignored on Windows systems. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + */ +int ssh_mkdir(const char *pathname, mode_t mode) { + int r; + +#ifdef _WIN32 + r = _mkdir(pathname); +#else + r = mkdir(pathname, mode); +#endif + + return r; +} + +/** + * @brief Expand a directory starting with a tilde '~' + * + * @param[in] d The directory to expand. + * + * @return The expanded directory, NULL on error. + */ +char *ssh_path_expand_tilde(const char *d) { + char *h = NULL, *r; + const char *p; + size_t ld; + size_t lh = 0; + + if (d[0] != '~') { + return strdup(d); + } + d++; + + /* handle ~user/path */ + p = strchr(d, '/'); + if (p != NULL && p > d) { +#ifdef _WIN32 + return strdup(d); +#else + struct passwd *pw; + size_t s = p - d; + char u[128]; + + if (s >= sizeof(u)) { + return NULL; + } + memcpy(u, d, s); + u[s] = '\0'; + pw = getpwnam(u); + if (pw == NULL) { + return NULL; + } + ld = strlen(p); + h = strdup(pw->pw_dir); +#endif + } else { + ld = strlen(d); + p = (char *) d; + h = ssh_get_user_home_dir(); + } + if (h == NULL) { + return NULL; + } + lh = strlen(h); + + r = malloc(ld + lh + 1); + if (r == NULL) { + SAFE_FREE(h); + return NULL; + } + + if (lh > 0) { + memcpy(r, h, lh); + } + SAFE_FREE(h); + memcpy(r + lh, p, ld + 1); + + return r; +} + +char *ssh_path_expand_escape(ssh_session session, const char *s) { +#define MAX_BUF_SIZE 4096 + char host[NI_MAXHOST]; + char buf[MAX_BUF_SIZE]; + char *r, *x = NULL; + const char *p; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case 'd': + x = strdup(session->opts.sshdir); + break; + case 'u': + x = ssh_get_local_username(); + break; + case 'l': + if (gethostname(host, sizeof(host) == 0)) { + x = strdup(host); + } + break; + case 'h': + x = strdup(session->opts.host); + break; + case 'r': + x = strdup(session->opts.username); + break; + case 'p': + if (session->opts.port < 65536) { + char tmp[6]; + + snprintf(tmp, sizeof(tmp), "%u", session->opts.port); + x = strdup(tmp); + } + break; + default: + ssh_set_error(session, SSH_FATAL, + "Wrong escape sequence detected"); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(session); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, + "String too long"); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, sizeof(buf) - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + return strdup(buf); +#undef MAX_BUF_SIZE +} + +/** + * @internal + * + * @brief Analyze the SSH banner to find out if we have a SSHv1 or SSHv2 + * server. + * + * @param session The session to analyze the banner from. + * @param server 0 means we are a client, 1 a server. + * @param ssh1 The variable which is set if it is a SSHv1 server. + * @param ssh2 The variable which is set if it is a SSHv2 server. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_get_banner() + */ +int ssh_analyze_banner(ssh_session session, int server, int *ssh1, int *ssh2) { + const char *banner; + const char *openssh; + + if (server) { + banner = session->clientbanner; + } else { + banner = session->serverbanner; + } + + if (banner == NULL) { + ssh_set_error(session, SSH_FATAL, "Invalid banner"); + return -1; + } + + /* + * Typical banners e.g. are: + * + * SSH-1.5-openSSH_5.4 + * SSH-1.99-openSSH_3.0 + * + * SSH-2.0-something + * 012345678901234567890 + */ + if (strlen(banner) < 6 || + strncmp(banner, "SSH-", 4) != 0) { + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + ssh_log(session, SSH_LOG_RARE, "Analyzing banner: %s", banner); + + switch(banner[4]) { + case '1': + *ssh1 = 1; + if (strlen(banner) > 6) { + if (banner[6] == '9') { + *ssh2 = 1; + } else { + *ssh2 = 0; + } + } + break; + case '2': + *ssh1 = 0; + *ssh2 = 1; + break; + default: + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + openssh = strstr(banner, "OpenSSH"); + if (openssh != NULL) { + int major, minor; + + /* + * The banner is typical: + * OpenSSH_5.4 + * 012345678901234567890 + */ + if (strlen(openssh) > 9) { + major = strtol(openssh + 8, (char **) NULL, 10); + minor = strtol(openssh + 10, (char **) NULL, 10); + session->openssh = SSH_VERSION_INT(major, minor, 0); + ssh_log(session, SSH_LOG_RARE, + "We are talking to an OpenSSH client version: %d.%d (%x)", + major, minor, session->openssh); + } + } + + + return 0; +} + +/* try the Monotonic clock if possible for perfs reasons */ +#ifdef _POSIX_MONOTONIC_CLOCK +#define CLOCK CLOCK_MONOTONIC +#else +#define CLOCK CLOCK_REALTIME +#endif + +/** + * @internal + * @brief initializes a timestamp to the current time + * @param[out] ts pointer to an allocated ssh_timestamp structure + */ +void ssh_timestamp_init(struct ssh_timestamp *ts){ +#ifdef HAVE_CLOCK_GETTIME + struct timespec tp; + clock_gettime(CLOCK, &tp); + ts->useconds = tp.tv_nsec / 1000; +#else + struct timeval tp; + gettimeofday(&tp, NULL); + ts->useconds = tp.tv_usec; +#endif + ts->seconds = tp.tv_sec; +} + +#undef CLOCK + +/** + * @internal + * @brief gets the time difference between two timestamps in ms + * @param[in] old older value + * @param[in] new newer value + * @returns difference in milliseconds + */ + +static int ssh_timestamp_difference(struct ssh_timestamp *old, + struct ssh_timestamp *new){ + long seconds, usecs, msecs; + seconds = new->seconds - old->seconds; + usecs = new->useconds - old->useconds; + if (usecs < 0){ + seconds--; + usecs += 1000000; + } + msecs = seconds * 1000 + usecs/1000; + return msecs; +} + +/** + * @internal + * @brief turn seconds and microseconds pair (as provided by user-set options) + * into millisecond value + * @param[in] sec number of seconds + * @param[in] usec number of microseconds + * @returns milliseconds, or 10000 if user supplied values are equal to zero + */ +int ssh_make_milliseconds(long sec, long usec) { + int res = usec ? (usec / 1000) : 0; + res += (sec * 1000); + if (res == 0) { + res = 10 * 1000; /* use a reasonable default value in case + * SSH_OPTIONS_TIMEOUT is not set in options. */ + } + return res; +} + +/** + * @internal + * @brief Checks if a timeout is elapsed, in function of a previous + * timestamp and an assigned timeout + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns 1 if timeout is elapsed + * 0 otherwise + */ +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { + struct ssh_timestamp now; + + switch(timeout) { + case -2: /* + * -2 means user-defined timeout as available in + * session->timeout, session->timeout_usec. + */ + fprintf(stderr, "ssh_timeout_elapsed called with -2. this needs to " + "be fixed. please set a breakpoint on %s:%d and " + "fix the caller\n", __FILE__, __LINE__); + case -1: /* -1 means infinite timeout */ + return 0; + case 0: /* 0 means no timeout */ + return 1; + default: + break; + } + + ssh_timestamp_init(&now); + + return (ssh_timestamp_difference(ts,&now) >= timeout); +} + +/** + * @brief updates a timeout value so it reflects the remaining time + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. + */ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout){ + struct ssh_timestamp now; + int ms, ret; + if (timeout <= 0) { + return timeout; + } + ssh_timestamp_init(&now); + ms = ssh_timestamp_difference(ts,&now); + if(ms < 0) + ms = 0; + ret = timeout - ms; + return ret >= 0 ? ret: 0; +} + + +int ssh_match_group(const char *group, const char *object) +{ + const char *a; + const char *z; + + z = group; + do { + a = strchr(z, ','); + if (a == NULL) { + if (strcmp(z, object) == 0) { + return 1; + } + return 0; + } else { + if (strncmp(z, object, a - z) == 0) { + return 1; + } + } + z = a + 1; + } while(1); + + /* not reached */ + return 0; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/options.c b/libssh/src/options.c new file mode 100644 index 00000000..931cb31e --- /dev/null +++ b/libssh/src/options.c @@ -0,0 +1,1424 @@ +/* + * options.c - handle pre-connection options + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 +#ifndef _WIN32 +#include +#else +#include +#endif +#include +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/bind.h" +#endif + +/** + * @addtogroup libssh_session + * @{ + */ + +/** + * @brief Duplicate the options of a session structure. + * + * If you make several sessions with the same options this is useful. You + * cannot use twice the same option structure in ssh_session_connect. + * + * @param src The session to use to copy the options. + * + * @param dest A pointer to store the allocated session with duplicated + * options. You have to free the memory. + * + * @returns 0 on sucess, -1 on error with errno set. + * + * @see ssh_session_connect() + */ +int ssh_options_copy(ssh_session src, ssh_session *dest) { + ssh_session new; + int i; + + if (src == NULL || dest == NULL) { + return -1; + } + + new = ssh_new(); + if (new == NULL) { + return -1; + } + + if (src->opts.username) { + new->opts.username = strdup(src->opts.username); + if (new->opts.username == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.host) { + new->opts.host = strdup(src->opts.host); + if (new->opts.host == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.identity) { + struct ssh_iterator *it; + + new->opts.identity = ssh_list_new(); + if (new->opts.identity == NULL) { + ssh_free(new); + return -1; + } + + it = ssh_list_get_iterator(src->opts.identity); + while (it) { + char *id; + int rc; + + id = strdup((char *) it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(new->opts.identity, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + } + + if (src->opts.sshdir) { + new->opts.sshdir = strdup(src->opts.sshdir); + if (new->opts.sshdir == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.knownhosts) { + new->opts.knownhosts = strdup(src->opts.knownhosts); + if (new->opts.knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + for (i = 0; i < 10; ++i) { + if (src->opts.wanted_methods[i]) { + new->opts.wanted_methods[i] = strdup(src->opts.wanted_methods[i]); + if (new->opts.wanted_methods[i] == NULL) { + ssh_free(new); + return -1; + } + } + } + + if (src->opts.ProxyCommand) { + new->opts.ProxyCommand = strdup(src->opts.ProxyCommand); + if (new->opts.ProxyCommand == NULL) { + ssh_free(new); + return -1; + } + } + new->opts.fd = src->opts.fd; + new->opts.port = src->opts.port; + new->opts.timeout = src->opts.timeout; + new->opts.timeout_usec = src->opts.timeout_usec; + new->opts.ssh2 = src->opts.ssh2; + new->opts.ssh1 = src->opts.ssh1; + new->opts.compressionlevel = src->opts.compressionlevel; + new->common.log_verbosity = src->common.log_verbosity; + new->common.callbacks = src->common.callbacks; + + *dest = new; + + return 0; +} + +int ssh_options_set_algo(ssh_session session, int algo, + const char *list) { + if (!verify_existing_algo(algo, list)) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Setting method: no algorithm for method \"%s\" (%s)\n", + ssh_kex_get_description(algo), list); + return -1; + } + + SAFE_FREE(session->opts.wanted_methods[algo]); + session->opts.wanted_methods[algo] = strdup(list); + if (session->opts.wanted_methods[algo] == NULL) { + ssh_set_error_oom(session); + return -1; + } + + return 0; +} + +/** + * @brief This function can set all possible ssh options. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to set. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to (const char *). + * + * - SSH_OPTIONS_PORT: + * The port to connect to (unsigned int). + * + * - SSH_OPTIONS_PORT_STR: + * The port to connect to (const char *). + * + * - SSH_OPTIONS_FD: + * The file descriptor to use (socket_t).\n + * \n + * If you wish to open the socket yourself for a reason + * or another, set the file descriptor. Don't forget to + * set the hostname as the hostname is used as a key in + * the known_host mechanism. + * + * - SSH_OPTIONS_BINDADDR: + * The address to bind the client to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n + * If the value is NULL, the username is set to the + * default username. + * + * - SSH_OPTIONS_SSH_DIR: + * Set the ssh directory (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default ssh directory.\n + * \n + * The ssh directory is used for files like known_hosts + * and identity (private and public key). It may include + * "%s" which will be replaced by the user home + * directory. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Set the known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default known hosts file, normally + * ~/.ssh/known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. It may include "%s" which will be + * replaced by the user home directory. + * + * - SSH_OPTIONS_IDENTITY: + * Set the identity file name (const char *,format string).\n + * \n + * By default identity, id_dsa and id_rsa are checked.\n + * \n + * The identity file used authenticate with public key. + * It may include "%s" which will be replaced by the + * user home directory. + * + * - SSH_OPTIONS_TIMEOUT: + * Set a timeout for the connection in seconds (long). + * + * - SSH_OPTIONS_TIMEOUT_USEC: + * Set a timeout for the connection in micro seconds + * (long). + * + * - SSH_OPTIONS_SSH1: + * Allow or deny the connection to SSH1 servers + * (int, 0 is false). + * + * - SSH_OPTIONS_SSH2: + * Allow or deny the connection to SSH2 servers + * (int, 0 is false). + * + * - SSH_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_RARE: Rare conditions or warnings + * - SSH_LOG_ENTRY: API-accessible entrypoints + * - SSH_LOG_PACKET: Packet id and size + * - SSH_LOG_FUNCTIONS: Function entering and leaving + * + * - SSH_OPTIONS_LOG_VERBOSITY_STR: + * Set the session logging verbosity (const char *).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_RARE: Rare conditions or warnings + * - SSH_LOG_ENTRY: API-accessible entrypoints + * - SSH_LOG_PACKET: Packet id and size + * - SSH_LOG_FUNCTIONS: Function entering and leaving + * \n + * See the corresponding numbers in libssh.h. + * + * - SSH_OPTIONS_AUTH_CALLBACK: + * Set a callback to use your own authentication function + * (function pointer). + * + * - SSH_OPTIONS_AUTH_USERDATA: + * Set the user data passed to the authentication + * function (generic pointer). + * + * - SSH_OPTIONS_LOG_CALLBACK: + * Set a callback to use your own logging function + * (function pointer). + * + * - SSH_OPTIONS_LOG_USERDATA: + * Set the user data passed to the logging function + * (generic pointer). + * + * - SSH_OPTIONS_STATUS_CALLBACK: + * Set a callback to show connection status in realtime + * (function pointer).\n + * \n + * @code + * fn(void *arg, float status) + * @endcode + * \n + * During ssh_connect(), libssh will call the callback + * with status from 0.0 to 1.0. + * + * - SSH_OPTIONS_STATUS_ARG: + * Set the status argument which should be passed to the + * status callback (generic pointer). + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Set the symmetric cipher client to server (const char *, + * comma-separated list). + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Set the symmetric cipher server to client (const char *, + * comma-separated list). + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Set the key exchange method to be used (const char *, + * comma-separated list). ex: + * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + * + * - SSH_OPTIONS_HOSTKEYS: + * Set the preferred server host key types (const char *, + * comma-separated list). ex: + * "ssh-rsa,ssh-dsa,ecdh-sha2-nistp256" + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Set the compression to use for client to server + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Set the compression to use for server to client + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION: + * Set the compression to use for both directions + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_LEVEL: + * Set the compression level to use for zlib functions. (int, + * value from 1 to 9, 9 being the most efficient but slower). + * + * - SSH_OPTIONS_STRICTHOSTKEYCHECK: + * Set the parameter StrictHostKeyChecking to avoid + * asking about a fingerprint (int, 0 = false). + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Set the command to be executed in order to connect to + * server (const char *). + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * type set. + * + * @return 0 on success, < 0 on error. + */ +int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value) { + const char *v; + char *p, *q; + long int i; + int rc; + + if (session == NULL) { + return -1; + } + + switch (type) { + case SSH_OPTIONS_HOST: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + p = strchr(q, '@'); + + SAFE_FREE(session->opts.host); + + if (p) { + *p = '\0'; + session->opts.host = strdup(p + 1); + if (session->opts.host == NULL) { + SAFE_FREE(q); + ssh_set_error_oom(session); + return -1; + } + + SAFE_FREE(session->opts.username); + session->opts.username = strdup(q); + SAFE_FREE(q); + if (session->opts.username == NULL) { + ssh_set_error_oom(session); + return -1; + } + } else { + session->opts.host = q; + } + } + break; + case SSH_OPTIONS_PORT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = *x & 0xffff; + } + break; + case SSH_OPTIONS_PORT_STR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SAFE_FREE(q); + } + SAFE_FREE(q); + if (i <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = i & 0xffff; + } + break; + case SSH_OPTIONS_FD: + if (value == NULL) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } else { + socket_t *x = (socket_t *) value; + if (*x < 0) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } + + session->opts.fd = *x & 0xffff; + } + break; + case SSH_OPTIONS_BINDADDR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + + q = strdup(v); + if (q == NULL) { + return -1; + } + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = q; + break; + case SSH_OPTIONS_USER: + v = value; + SAFE_FREE(session->opts.username); + if (v == NULL) { + q = ssh_get_local_username(); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.username = q; + } else if (v[0] == '\0') { + ssh_set_error_oom(session); + return -1; + } else { /* username provided */ + session->opts.username = strdup(value); + if (session->opts.username == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_SSH_DIR: + v = value; + SAFE_FREE(session->opts.sshdir); + if (v == NULL) { + session->opts.sshdir = ssh_path_expand_tilde("~/.ssh"); + if (session->opts.sshdir == NULL) { + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_oom(session); + return -1; + } else { + session->opts.sshdir = ssh_path_expand_tilde(v); + if (session->opts.sshdir == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITY: + case SSH_OPTIONS_ADD_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + rc = ssh_list_prepend(session->opts.identity, q); + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.knownhosts); + if (v == NULL) { + session->opts.knownhosts = ssh_path_expand_escape(session, + "%d/known_hosts"); + if (session->opts.knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.knownhosts = strdup(v); + if (session->opts.knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_TIMEOUT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout = *x & 0xffffffff; + } + break; + case SSH_OPTIONS_TIMEOUT_USEC: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout_usec = *x & 0xffffffff; + } + break; + case SSH_OPTIONS_SSH1: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.ssh1 = *x; + } + break; + case SSH_OPTIONS_SSH2: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.ssh2 = *x & 0xffff; + } + break; + case SSH_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = *x & 0xffff; + } + break; + case SSH_OPTIONS_LOG_VERBOSITY_STR: + v = value; + if (v == NULL || v[0] == '\0') { + session->common.log_verbosity = 0; + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SAFE_FREE(q); + } + SAFE_FREE(q); + if (i < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = i & 0xffff; + } + break; + case SSH_OPTIONS_CIPHERS_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_CRYPT_C_S, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_CIPHERS_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_CRYPT_S_C, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_KEX, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_HOSTKEYS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_HOSTKEYS, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (strcasecmp(value,"yes")==0){ + if(ssh_options_set_algo(session,SSH_COMP_C_S,"zlib@openssh.com,zlib") < 0) + return -1; + } else if (strcasecmp(value,"no")==0){ + if(ssh_options_set_algo(session,SSH_COMP_C_S,"none") < 0) + return -1; + } else { + if (ssh_options_set_algo(session, SSH_COMP_C_S, v) < 0) + return -1; + } + } + break; + case SSH_OPTIONS_COMPRESSION_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (strcasecmp(value,"yes")==0){ + if(ssh_options_set_algo(session,SSH_COMP_S_C,"zlib@openssh.com,zlib") < 0) + return -1; + } else if (strcasecmp(value,"no")==0){ + if(ssh_options_set_algo(session,SSH_COMP_S_C,"none") < 0) + return -1; + } else { + if (ssh_options_set_algo(session, SSH_COMP_S_C, v) < 0) + return -1; + } + } + break; + case SSH_OPTIONS_COMPRESSION: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_C_S, v) < 0) + return -1; + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_S_C, v) < 0) + return -1; + break; + case SSH_OPTIONS_COMPRESSION_LEVEL: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < 1 || *x > 9) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.compressionlevel = *x & 0xff; + } + break; + case SSH_OPTIONS_STRICTHOSTKEYCHECK: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + + session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; + } + session->opts.StrictHostKeyChecking = *(int*)value; + break; + case SSH_OPTIONS_PROXYCOMMAND: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.ProxyCommand); + q = strdup(v); + if (q == NULL) { + return -1; + } + session->opts.ProxyCommand = q; + } + break; + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return -1; + break; + } + + return 0; +} + +/** + * @brief This function can get ssh the ssh port. It must only be used on + * a valid ssh session. This function is useful when the session + * options have been automatically inferred from the environment + * or configuration files and one + * + * @param session An allocated SSH session structure. + * + * @param port_target An unsigned integer into which the + * port will be set from the ssh session. + * + * @return 0 on success, < 0 on error. + * + */ +int ssh_options_get_port(ssh_session session, unsigned int* port_target) { + if (session == NULL) { + return -1; + } + if (!session->opts.port) { + ssh_set_error_invalid(session); + return -1; + } + *port_target = session->opts.port; + return 0; +} + +/** + * @brief This function can get ssh options, it does not support all options provided for + * ssh options set, but mostly those which a user-space program may care about having + * trusted the ssh driver to infer these values from underlaying configuration files. + * It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive + * the port then please use ssh_options_get_port() which returns an unsigned int. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to get. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n when not explicitly set this will be inferred from the + * ~/.ssh/config file. + * + * - SSH_OPTIONS_IDENTITY: + * Set the identity file name (const char *,format string).\n + * \n + * By default identity, id_dsa and id_rsa are checked.\n + * \n + * The identity file used authenticate with public key. + * It may include "%s" which will be replaced by the + * user home directory. + * + * @param value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) +{ + char* src = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + switch(type) + { + case SSH_OPTIONS_HOST: { + src = session->opts.host; + break; + } + case SSH_OPTIONS_USER: { + src = session->opts.username; + break; + } + case SSH_OPTIONS_IDENTITY: { + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + return SSH_ERROR; + } + src = ssh_iterator_value(char *, it); + break; + } + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return SSH_ERROR; + break; + } + if (src == NULL) { + return SSH_ERROR; + } + *value = strdup(src); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Parse command line arguments. + * + * This is a helper for your application to generate the appropriate + * options from the command line arguments.\n + * The argv array and argc value are changed so that the parsed + * arguments wont appear anymore in them.\n + * The single arguments (without switches) are not parsed. thus, + * myssh -l user localhost\n + * The command wont set the hostname value of options to localhost. + * + * @param session The session to configure. + * + * @param argcptr The pointer to the argument count. + * + * @param argv The arguments list pointer. + * + * @returns 0 on success, < 0 on error. + * + * @see ssh_session_new() + */ +int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) { + char *user = NULL; + char *cipher = NULL; + char *identity = NULL; + char *port = NULL; + char **save = NULL, **tmp; + int i = 0; + int argc = *argcptr; + int debuglevel = 0; + int usersa = 0; + int usedss = 0; + int compress = 0; + int cont = 1; + int current = 0; +#ifdef WITH_SSH1 + int ssh1 = 1; +#else + int ssh1 = 0; +#endif + int ssh2 = 1; +#ifdef _MSC_VER + /* Not supported with a Microsoft compiler */ + return -1; +#else + int saveoptind = optind; /* need to save 'em */ + int saveopterr = opterr; + + opterr = 0; /* shut up getopt */ + while(cont && ((i = getopt(argc, argv, "c:i:Cl:p:vb:rd12")) != -1)) { + switch(i) { + case 'l': + user = optarg; + break; + case 'p': + port = optarg; + break; + case 'v': + debuglevel++; + break; + case 'r': + usersa++; + break; + case 'd': + usedss++; + break; + case 'c': + cipher = optarg; + break; + case 'i': + identity = optarg; + break; + case 'C': + compress++; + break; + case '2': + ssh2 = 1; + ssh1 = 0; + break; + case '1': + ssh2 = 0; + ssh1 = 1; + break; + default: + { + char opt[3]="- "; + opt[1] = optopt; + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = strdup(opt); + if (save[current] == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + current++; + if (optarg) { + save[current++] = argv[optind + 1]; + } + } + } /* switch */ + } /* while */ + opterr = saveopterr; + while (optind < argc) { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = argv[optind]; + current++; + optind++; + } + + if (usersa && usedss) { + ssh_set_error(session, SSH_FATAL, "Either RSA or DSS must be chosen"); + cont = 0; + } + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &debuglevel); + + optind = saveoptind; + + if(!cont) { + SAFE_FREE(save); + return -1; + } + + /* first recopy the save vector into the original's */ + for (i = 0; i < current; i++) { + /* don't erase argv[0] */ + argv[ i + 1] = save[i]; + } + argv[current + 1] = NULL; + *argcptr = current + 1; + SAFE_FREE(save); + + /* set a new option struct */ + if (compress) { + if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes") < 0) { + cont = 0; + } + } + + if (cont && cipher) { + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) < 0) { + cont = 0; + } + if (cont && ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher) < 0) { + cont = 0; + } + } + + if (cont && user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + cont = 0; + } + } + + if (cont && identity) { + if (ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity) < 0) { + cont = 0; + } + } + + ssh_options_set(session, SSH_OPTIONS_PORT_STR, port); + + ssh_options_set(session, SSH_OPTIONS_SSH1, &ssh1); + ssh_options_set(session, SSH_OPTIONS_SSH2, &ssh2); + + if (!cont) { + return SSH_ERROR; + } + + return SSH_OK; +#endif +} + +/** + * @brief Parse the ssh config file. + * + * This should be the last call of all options, it may overwrite options which + * are already set. It requires that the host name is already set with + * ssh_options_set_host(). + * + * @param session SSH session handle + * + * @param filename The options file to use, if NULL the default + * ~/.ssh/config will be used. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_options_set_host() + */ +int ssh_options_parse_config(ssh_session session, const char *filename) { + char *expanded_filename; + int r; + + if (session == NULL) { + return -1; + } + if (session->opts.host == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->opts.sshdir == NULL) { + r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (r < 0) { + ssh_set_error_oom(session); + return -1; + } + } + + /* set default filename */ + if (filename == NULL) { + expanded_filename = ssh_path_expand_escape(session, "%d/config"); + } else { + expanded_filename = ssh_path_expand_escape(session, filename); + } + if (expanded_filename == NULL) { + return -1; + } + + r = ssh_config_parse_file(session, expanded_filename); + if (r < 0) { + goto out; + } + if (filename == NULL) { + r = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); + } + +out: + free(expanded_filename); + return r; +} + +int ssh_options_apply(ssh_session session) { + struct ssh_iterator *it; + char *tmp; + int rc; + + if (session->opts.sshdir == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (rc < 0) { + return -1; + } + } + + if (session->opts.username == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + if (rc < 0) { + return -1; + } + } + + if (session->opts.knownhosts == NULL) { + tmp = ssh_path_expand_escape(session, "%d/known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, session->opts.knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.knownhosts); + session->opts.knownhosts = tmp; + + if (session->opts.ProxyCommand != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.ProxyCommand); + if (tmp == NULL) { + return -1; + } + free(session->opts.ProxyCommand); + session->opts.ProxyCommand = tmp; + } + + for (it = ssh_list_get_iterator(session->opts.identity); + it != NULL; + it = it->next) { + char *id = (char *) it->data; + tmp = ssh_path_expand_escape(session, id); + if (tmp == NULL) { + return -1; + } + free(id); + it->data = tmp; + } + + return 0; +} + +/** @} */ + +#ifdef WITH_SERVER +/** + * @addtogroup libssh_server + * @{ + */ +static int ssh_bind_options_set_algo(ssh_bind sshbind, int algo, + const char *list) { + if (!verify_existing_algo(algo, list)) { + ssh_set_error(sshbind, SSH_REQUEST_DENIED, + "Setting method: no algorithm for method \"%s\" (%s)\n", + ssh_kex_get_description(algo), list); + return -1; + } + + SAFE_FREE(sshbind->wanted_methods[algo]); + sshbind->wanted_methods[algo] = strdup(list); + if (sshbind->wanted_methods[algo] == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + + return 0; +} + +/** + * @brief This function can set all possible ssh bind options. + * + * @param session An allocated ssh option structure. + * + * @param type The option type to set. This could be one of the + * following: + * + * SSH_BIND_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (integer). + * + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * SSH_LOG_NOLOG: No logging + * SSH_LOG_RARE: Rare conditions or warnings + * SSH_LOG_ENTRY: API-accessible entrypoints + * SSH_LOG_PACKET: Packet id and size + * SSH_LOG_FUNCTIONS: Function entering and leaving + * + * SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + * Set the session logging verbosity (integer). + * + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * SSH_LOG_NOLOG: No logging + * SSH_LOG_RARE: Rare conditions or warnings + * SSH_LOG_ENTRY: API-accessible entrypoints + * SSH_LOG_PACKET: Packet id and size + * SSH_LOG_FUNCTIONS: Function entering and leaving + * + * SSH_BIND_OPTIONS_BINDADDR: + * Set the bind address. + * + * SSH_BIND_OPTIONS_BINDPORT: + * Set the bind port, default is 22. + * + * SSH_BIND_OPTIONS_HOSTKEY: + * Set the server public key type: ssh-rsa or ssh-dss + * (string). + * + * SSH_BIND_OPTIONS_DSAKEY: + * Set the path to the dsa ssh host key (string). + * + * SSH_BIND_OPTIONS_RSAKEY: + * Set the path to the ssh host rsa key (string). + * + * SSH_BIND_OPTIONS_BANNER: + * Set the server banner sent to clients (string). + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * type set. + * + * @return 0 on success, < 0 on error. + */ +int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, + const void *value) { + char *p, *q; + int i; + + if (sshbind == NULL) { + return -1; + } + + switch (type) { + case SSH_BIND_OPTIONS_HOSTKEY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + if (ssh_bind_options_set_algo(sshbind, SSH_HOSTKEYS, value) < 0) + return -1; + } + break; + case SSH_BIND_OPTIONS_BINDADDR: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->bindaddr); + sshbind->bindaddr = strdup(value); + if (sshbind->bindaddr == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_BINDPORT: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *) value; + sshbind->bindport = *x & 0xffff; + } + break; + case SSH_BIND_OPTIONS_BINDPORT_STR: + if (value == NULL) { + sshbind->bindport = 22 & 0xffff; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SAFE_FREE(q); + } + SAFE_FREE(q); + + sshbind->bindport = i & 0xffff; + } + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *) value; + sshbind->common.log_verbosity = *x & 0xffff; + } + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + if (value == NULL) { + sshbind->common.log_verbosity = 0; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SAFE_FREE(q); + } + SAFE_FREE(q); + + sshbind->common.log_verbosity = i & 0xffff; + } + break; + case SSH_BIND_OPTIONS_DSAKEY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->dsakey); + sshbind->dsakey = strdup(value); + if (sshbind->dsakey == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_RSAKEY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->rsakey); + sshbind->rsakey = strdup(value); + if (sshbind->rsakey == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_BANNER: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->banner); + sshbind->banner = strdup(value); + if (sshbind->banner == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + default: + ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return -1; + break; + } + + return 0; +} +#endif + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/packet.c b/libssh/src/packet.c new file mode 100644 index 00000000..2256f113 --- /dev/null +++ b/libssh/src/packet.c @@ -0,0 +1,531 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/misc.h" +#include "libssh/session.h" +#include "libssh/messages.h" +#include "libssh/pcap.h" +#include "libssh/kex.h" +#include "libssh/auth.h" + +#define MACSIZE SHA_DIGEST_LEN + +static ssh_packet_callback default_packet_handlers[]= { + ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1 + ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2 + ssh_packet_unimplemented, // SSH2_MSG_UNIMPLEMENTED 3 + ssh_packet_ignore_callback, // SSH2_MSG_DEBUG 4 + ssh_packet_service_request, // SSH2_MSG_SERVICE_REQUEST 5 + ssh_packet_service_accept, // SSH2_MSG_SERVICE_ACCEPT 6 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, // 7-19 + ssh_packet_kexinit, // SSH2_MSG_KEXINIT 20 + ssh_packet_newkeys, // SSH2_MSG_NEWKEYS 21 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 22-29 +#if WITH_SERVER + ssh_packet_kexdh_init, // SSH2_MSG_KEXDH_INIT 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#else + NULL, +#endif + ssh_packet_dh_reply, // SSH2_MSG_KEXDH_REPLY 31 + // SSH2_MSG_KEX_DH_GEX_GROUP 31 + NULL, // SSH2_MSG_KEX_DH_GEX_INIT 32 + NULL, // SSH2_MSG_KEX_DH_GEX_REPLY 33 + NULL, // SSH2_MSG_KEX_DH_GEX_REQUEST 34 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 35-49 + ssh_packet_userauth_request, // SSH2_MSG_USERAUTH_REQUEST 50 + ssh_packet_userauth_failure, // SSH2_MSG_USERAUTH_FAILURE 51 + ssh_packet_userauth_success, // SSH2_MSG_USERAUTH_SUCCESS 52 + ssh_packet_userauth_banner, // SSH2_MSG_USERAUTH_BANNER 53 + NULL,NULL,NULL,NULL,NULL,NULL, // 54-59 + ssh_packet_userauth_pk_ok, // SSH2_MSG_USERAUTH_PK_OK 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST 60 + ssh_packet_userauth_info_response, // SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, // 62-79 +#ifdef WITH_SERVER + ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 +#else /* WITH_SERVER */ + NULL, +#endif /* WITH_SERVER */ + ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81 + ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82 + NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89 + ssh_packet_channel_open, // SSH2_MSG_CHANNEL_OPEN 90 + ssh_packet_channel_open_conf, // SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 + ssh_packet_channel_open_fail, // SSH2_MSG_CHANNEL_OPEN_FAILURE 92 + channel_rcv_change_window, // SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 + channel_rcv_data, // SSH2_MSG_CHANNEL_DATA 94 + channel_rcv_data, // SSH2_MSG_CHANNEL_EXTENDED_DATA 95 + channel_rcv_eof, // SSH2_MSG_CHANNEL_EOF 96 + channel_rcv_close, // SSH2_MSG_CHANNEL_CLOSE 97 + channel_rcv_request, // SSH2_MSG_CHANNEL_REQUEST 98 + ssh_packet_channel_success, // SSH2_MSG_CHANNEL_SUCCESS 99 + ssh_packet_channel_failure, // SSH2_MSG_CHANNEL_FAILURE 100 +}; + +/* in nonblocking mode, socket_read will read as much as it can, and return */ +/* SSH_OK if it has read at least len bytes, otherwise, SSH_AGAIN. */ +/* in blocking mode, it will read at least len bytes and will block until it's ok. */ + +/** @internal + * @handles a data received event. It then calls the handlers for the different packet types + * or and exception handler callback. + * @param user pointer to current ssh_session + * @param data pointer to the data received + * @len length of data received. It might not be enough for a complete packet + * @returns number of bytes read and processed. + */ +int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user){ + ssh_session session=(ssh_session) user; + unsigned int blocksize = (session->current_crypto ? + session->current_crypto->in_cipher->blocksize : 8); + int current_macsize = session->current_crypto ? MACSIZE : 0; + unsigned char mac[30] = {0}; + char buffer[16] = {0}; + const void *packet = NULL; + int to_be_read; + int rc; + uint32_t len, compsize, payloadsize; + uint8_t padding; + size_t processed=0; /* number of byte processed from the callback */ + + if (data == NULL) { + goto error; + } + + enter_function(); + if (session->session_state == SSH_SESSION_STATE_ERROR) + goto error; + switch(session->packet_state) { + case PACKET_STATE_INIT: + if(receivedlen < blocksize){ + /* We didn't receive enough data to read at least one block size, give up */ + leave_function(); + return 0; + } + memset(&session->in_packet, 0, sizeof(PACKET)); + + if (session->in_buffer) { + if (buffer_reinit(session->in_buffer) < 0) { + goto error; + } + } else { + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto error; + } + } + + memcpy(buffer,data,blocksize); + processed += blocksize; + len = packet_decrypt_len(session, buffer); + + if (buffer_add_data(session->in_buffer, buffer, blocksize) < 0) { + goto error; + } + + if(len > MAX_PACKET_LEN) { + ssh_set_error(session, SSH_FATAL, + "read_packet(): Packet len too high(%u %.4x)", len, len); + goto error; + } + + to_be_read = len - blocksize + sizeof(uint32_t); + if (to_be_read < 0) { + /* remote sshd sends invalid sizes? */ + ssh_set_error(session, SSH_FATAL, + "given numbers of bytes left to be read < 0 (%d)!", to_be_read); + goto error; + } + + /* saves the status of the current operations */ + session->in_packet.len = len; + session->packet_state = PACKET_STATE_SIZEREAD; + case PACKET_STATE_SIZEREAD: + len = session->in_packet.len; + to_be_read = len - blocksize + sizeof(uint32_t) + current_macsize; + /* if to_be_read is zero, the whole packet was blocksize bytes. */ + if (to_be_read != 0) { + if(receivedlen - processed < (unsigned int)to_be_read){ + /* give up, not enough data in buffer */ + ssh_log(session,SSH_LOG_PACKET,"packet: partial packet (read len) [len=%d]",len); + return processed; + } + + packet = ((unsigned char *)data) + processed; +// ssh_socket_read(session->socket,packet,to_be_read-current_macsize); + + if (buffer_add_data(session->in_buffer, packet, + to_be_read - current_macsize) < 0) { + goto error; + } + processed += to_be_read - current_macsize; + } + + if (session->current_crypto) { + /* + * decrypt the rest of the packet (blocksize bytes already + * have been decrypted) + */ + if (packet_decrypt(session, + ((uint8_t*)buffer_get_rest(session->in_buffer) + blocksize), + buffer_get_rest_len(session->in_buffer) - blocksize) < 0) { + ssh_set_error(session, SSH_FATAL, "Decrypt error"); + goto error; + } + /* copy the last part from the incoming buffer */ + memcpy(mac,(unsigned char *)packet + to_be_read - current_macsize, MACSIZE); + + if (packet_hmac_verify(session, session->in_buffer, mac) < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + processed += current_macsize; + } + + /* skip the size field which has been processed before */ + buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); + + if (buffer_get_u8(session->in_buffer, &padding) == 0) { + ssh_set_error(session, SSH_FATAL, "Packet too short to read padding"); + goto error; + } + + if (padding > buffer_get_rest_len(session->in_buffer)) { + ssh_set_error(session, SSH_FATAL, + "Invalid padding: %d (%d left)", + padding, + buffer_get_rest_len(session->in_buffer)); + goto error; + } + buffer_pass_bytes_end(session->in_buffer, padding); + compsize = buffer_get_rest_len(session->in_buffer); + +#ifdef WITH_ZLIB + if (session->current_crypto + && session->current_crypto->do_compress_in + && buffer_get_rest_len(session->in_buffer)) { + if (decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN) < 0) { + goto error; + } + } +#endif /* WITH_ZLIB */ + payloadsize=buffer_get_rest_len(session->in_buffer); + session->recv_seq++; + /* We don't want to rewrite a new packet while still executing the packet callbacks */ + session->packet_state = PACKET_STATE_PROCESSING; + ssh_packet_parse_type(session); + ssh_log(session,SSH_LOG_PACKET, + "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", + session->in_packet.type, len, padding, compsize, payloadsize); + /* execute callbacks */ + ssh_packet_process(session, session->in_packet.type); + session->packet_state = PACKET_STATE_INIT; + if(processed < receivedlen){ + /* Handle a potential packet left in socket buffer */ + ssh_log(session,SSH_LOG_PACKET,"Processing %" PRIdS " bytes left in socket buffer", + receivedlen-processed); + rc = ssh_packet_socket_callback(((unsigned char *)data) + processed, + receivedlen - processed,user); + processed += rc; + } + leave_function(); + return processed; + case PACKET_STATE_PROCESSING: + ssh_log(session, SSH_LOG_RARE, "Nested packet processing. Delaying."); + return 0; + } + + ssh_set_error(session, SSH_FATAL, + "Invalid state into packet_read2(): %d", + session->packet_state); + +error: + session->session_state= SSH_SESSION_STATE_ERROR; + leave_function(); + return processed; +} + +void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s){ + session->socket_callbacks.data=ssh_packet_socket_callback; + session->socket_callbacks.connected=NULL; + session->socket_callbacks.controlflow=NULL; + session->socket_callbacks.exception=NULL; + session->socket_callbacks.userdata=session; + ssh_socket_set_callbacks(s,&session->socket_callbacks); +} + +/** @internal + * @brief sets the callbacks for the packet layer + */ +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ + if(session->packet_callbacks == NULL){ + session->packet_callbacks = ssh_list_new(); + } + ssh_list_append(session->packet_callbacks, callbacks); +} + +/** @internal + * @brief sets the default packet handlers + */ +void ssh_packet_set_default_callbacks(ssh_session session){ +#ifdef WITH_SSH1 + if(session->version==1){ + ssh_packet_set_default_callbacks1(session); + return; + } +#endif + session->default_packet_callbacks.start=1; + session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback); + session->default_packet_callbacks.user=session; + session->default_packet_callbacks.callbacks=default_packet_handlers; + ssh_packet_set_callbacks(session, &session->default_packet_callbacks); +} + +/** @internal + * @brief dispatch the call of packet handlers callbacks for a received packet + * @param type type of packet + */ +void ssh_packet_process(ssh_session session, uint8_t type){ + struct ssh_iterator *i; + int r=SSH_PACKET_NOT_USED; + ssh_packet_callbacks cb; + enter_function(); + ssh_log(session,SSH_LOG_PACKET, "Dispatching handler for packet type %d",type); + if(session->packet_callbacks == NULL){ + ssh_log(session,SSH_LOG_RARE,"Packet callback is not initialized !"); + goto error; + } + i=ssh_list_get_iterator(session->packet_callbacks); + while(i != NULL){ + cb=ssh_iterator_value(ssh_packet_callbacks,i); + i=i->next; + if(!cb) + continue; + if(cb->start > type) + continue; + if(cb->start + cb->n_callbacks <= type) + continue; + if(cb->callbacks[type - cb->start]==NULL) + continue; + r=cb->callbacks[type - cb->start](session,type,session->in_buffer,cb->user); + if(r==SSH_PACKET_USED) + break; + } + if(r==SSH_PACKET_NOT_USED){ + ssh_log(session,SSH_LOG_RARE,"Couldn't do anything with packet type %d",type); + ssh_packet_send_unimplemented(session, session->recv_seq-1); + } +error: + leave_function(); +} + +/** @internal + * @brief sends a SSH_MSG_UNIMPLEMENTED answer to an unhandled packet + * @param session the SSH session + * @param seqnum the sequence number of the unknown packet + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum){ + int r; + enter_function(); + r = buffer_add_u8(session->out_buffer, SSH2_MSG_UNIMPLEMENTED); + if (r < 0) { + return SSH_ERROR; + } + r = buffer_add_u32(session->out_buffer, htonl(seqnum)); + if (r < 0) { + return SSH_ERROR; + } + r = packet_send(session); + leave_function(); + return r; +} + +/** @internal + * @brief handles a SSH_MSG_UNIMPLEMENTED packet + */ +SSH_PACKET_CALLBACK(ssh_packet_unimplemented){ + uint32_t seq; + (void)type; + (void)user; + buffer_get_u32(packet,&seq); + seq=ntohl(seq); + ssh_log(session,SSH_LOG_RARE, + "Received SSH_MSG_UNIMPLEMENTED (sequence number %d)",seq); + return SSH_PACKET_USED; +} + +/** @internal + * @parse the "Type" header field of a packet and updates the session + */ +int ssh_packet_parse_type(ssh_session session) { + enter_function(); + + memset(&session->in_packet, 0, sizeof(PACKET)); + if(session->in_buffer == NULL) { + leave_function(); + return SSH_ERROR; + } + + if(buffer_get_u8(session->in_buffer, &session->in_packet.type) == 0) { + ssh_set_error(session, SSH_FATAL, "Packet too short to read type"); + leave_function(); + return SSH_ERROR; + } + + session->in_packet.valid = 1; + + leave_function(); + return SSH_OK; +} + +/* + * This function places the outgoing packet buffer into an outgoing + * socket buffer + */ +static int ssh_packet_write(ssh_session session) { + int rc = SSH_ERROR; + + enter_function(); + + rc=ssh_socket_write(session->socket, + buffer_get_rest(session->out_buffer), + buffer_get_rest_len(session->out_buffer)); + leave_function(); + return rc; +} + +static int packet_send2(ssh_session session) { + unsigned int blocksize = (session->current_crypto ? + session->current_crypto->out_cipher->blocksize : 8); + uint32_t currentlen = buffer_get_rest_len(session->out_buffer); + unsigned char *hmac = NULL; + char padstring[32] = {0}; + int rc = SSH_ERROR; + uint32_t finallen,payloadsize,compsize; + uint8_t padding; + + enter_function(); + + payloadsize = currentlen; +#ifdef WITH_ZLIB + if (session->current_crypto + && session->current_crypto->do_compress_out + && buffer_get_rest_len(session->out_buffer)) { + if (compress_buffer(session,session->out_buffer) < 0) { + goto error; + } + currentlen = buffer_get_rest_len(session->out_buffer); + } +#endif /* WITH_ZLIB */ + compsize = currentlen; + padding = (blocksize - ((currentlen +5) % blocksize)); + if(padding < 4) { + padding += blocksize; + } + + if (session->current_crypto) { + ssh_get_random(padstring, padding, 0); + } else { + memset(padstring,0,padding); + } + + finallen = htonl(currentlen + padding + 1); + + if (buffer_prepend_data(session->out_buffer, &padding, sizeof(uint8_t)) < 0) { + goto error; + } + if (buffer_prepend_data(session->out_buffer, &finallen, sizeof(uint32_t)) < 0) { + goto error; + } + if (buffer_add_data(session->out_buffer, padstring, padding) < 0) { + goto error; + } +#ifdef WITH_PCAP + if(session->pcap_ctx){ + ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT, + buffer_get_rest(session->out_buffer),buffer_get_rest_len(session->out_buffer) + ,buffer_get_rest_len(session->out_buffer)); + } +#endif + hmac = packet_encrypt(session, buffer_get_rest(session->out_buffer), + buffer_get_rest_len(session->out_buffer)); + if (hmac) { + if (buffer_add_data(session->out_buffer, hmac, 20) < 0) { + goto error; + } + } + + rc = ssh_packet_write(session); + session->send_seq++; + + ssh_log(session,SSH_LOG_PACKET, + "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]", + ntohl(finallen), padding, compsize, payloadsize); + if (buffer_reinit(session->out_buffer) < 0) { + rc = SSH_ERROR; + } +error: + leave_function(); + return rc; /* SSH_OK, AGAIN or ERROR */ +} + + +int packet_send(ssh_session session) { +#ifdef WITH_SSH1 + if (session->version == 1) { + return packet_send1(session); + } +#endif + return packet_send2(session); +} + + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/packet1.c b/libssh/src/packet1.c new file mode 100644 index 00000000..5c8b81e5 --- /dev/null +++ b/libssh/src/packet1.c @@ -0,0 +1,370 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#ifndef _WIN32 +#include +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/ssh1.h" +#include "libssh/crc32.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/kex.h" +#include "libssh/crypto.h" + +#ifdef WITH_SSH1 + +static ssh_packet_callback default_packet_handlers1[]= { + NULL, //SSH_MSG_NONE 0 + ssh_packet_disconnect1, //SSH_MSG_DISCONNECT 1 + ssh_packet_publickey1, //SSH_SMSG_PUBLIC_KEY 2 + NULL, //SSH_CMSG_SESSION_KEY 3 + NULL, //SSH_CMSG_USER 4 + NULL, //SSH_CMSG_AUTH_RHOSTS 5 + NULL, //SSH_CMSG_AUTH_RSA 6 + NULL, //SSH_SMSG_AUTH_RSA_CHALLENGE 7 + NULL, //SSH_CMSG_AUTH_RSA_RESPONSE 8 + NULL, //SSH_CMSG_AUTH_PASSWORD 9 + NULL, //SSH_CMSG_REQUEST_PTY 10 + NULL, //SSH_CMSG_WINDOW_SIZE 11 + NULL, //SSH_CMSG_EXEC_SHELL 12 + NULL, //SSH_CMSG_EXEC_CMD 13 + ssh_packet_smsg_success1, //SSH_SMSG_SUCCESS 14 + ssh_packet_smsg_failure1, //SSH_SMSG_FAILURE 15 + NULL, //SSH_CMSG_STDIN_DATA 16 + ssh_packet_data1, //SSH_SMSG_STDOUT_DATA 17 + ssh_packet_data1, //SSH_SMSG_STDERR_DATA 18 + NULL, //SSH_CMSG_EOF 19 + ssh_packet_exist_status1, //SSH_SMSG_EXITSTATUS 20 + NULL, //SSH_MSG_CHANNEL_OPEN_CONFIRMATION 21 + NULL, //SSH_MSG_CHANNEL_OPEN_FAILURE 22 + NULL, //SSH_MSG_CHANNEL_DATA 23 + ssh_packet_close1, //SSH_MSG_CHANNEL_CLOSE 24 + NULL, //SSH_MSG_CHANNEL_CLOSE_CONFIRMATION 25 + NULL, //SSH_CMSG_X11_REQUEST_FORWARDING 26 + NULL, //SSH_SMSG_X11_OPEN 27 + NULL, //SSH_CMSG_PORT_FORWARD_REQUEST 28 + NULL, //SSH_MSG_PORT_OPEN 29 + NULL, //SSH_CMSG_AGENT_REQUEST_FORWARDING 30 + NULL, //SSH_SMSG_AGENT_OPEN 31 + ssh_packet_ignore_callback, //SSH_MSG_IGNORE 32 + NULL, //SSH_CMSG_EXIT_CONFIRMATION 33 + NULL, //SSH_CMSG_X11_REQUEST_FORWARDING 34 + NULL, //SSH_CMSG_AUTH_RHOSTS_RSA 35 + ssh_packet_ignore_callback, //SSH_MSG_DEBUG 36 +}; + +/** @internal + * @brief sets the default packet handlers + */ +void ssh_packet_set_default_callbacks1(ssh_session session){ + session->default_packet_callbacks.start=0; + session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers1)/sizeof(ssh_packet_callback); + session->default_packet_callbacks.user=session; + session->default_packet_callbacks.callbacks=default_packet_handlers1; + ssh_packet_set_callbacks(session, &session->default_packet_callbacks); +} + + +/** @internal + * @handles a data received event. It then calls the handlers for the different packet types + * or and exception handler callback. Adapted for SSH-1 packets. + * @param user pointer to current ssh_session + * @param data pointer to the data received + * @len length of data received. It might not be enough for a complete packet + * @returns number of bytes read and processed. + */ + +int ssh_packet_socket_callback1(const void *data, size_t receivedlen, void *user) { + void *packet = NULL; + int to_be_read; + size_t processed=0; + uint32_t padding; + uint32_t crc; + uint32_t len; + ssh_session session=(ssh_session)user; + enter_function(); + + switch (session->packet_state){ + case PACKET_STATE_INIT: + memset(&session->in_packet, 0, sizeof(PACKET)); + + if (session->in_buffer) { + if (buffer_reinit(session->in_buffer) < 0) { + goto error; + } + } else { + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto error; + } + } + /* must have at least enough bytes for size */ + if(receivedlen < sizeof(uint32_t)){ + leave_function(); + return 0; + } + memcpy(&len,data,sizeof(uint32_t)); + processed += sizeof(uint32_t); + + /* len is not encrypted */ + len = ntohl(len); + if (len > MAX_PACKET_LEN) { + ssh_set_error(session, SSH_FATAL, + "read_packet(): Packet len too high (%u %.8x)", len, len); + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, "Reading a %d bytes packet", len); + + session->in_packet.len = len; + session->packet_state = PACKET_STATE_SIZEREAD; + case PACKET_STATE_SIZEREAD: + len = session->in_packet.len; + /* SSH-1 has a fixed padding lenght */ + padding = 8 - (len % 8); + to_be_read = len + padding; + if(to_be_read + processed > receivedlen){ + /* wait for rest of packet */ + leave_function(); + return processed; + } + /* it is _not_ possible that to_be_read be < 8. */ + packet = (char *)data + processed; + + if (buffer_add_data(session->in_buffer,packet,to_be_read) < 0) { + SAFE_FREE(packet); + goto error; + } + processed += to_be_read; +#ifdef DEBUG_CRYPTO + ssh_print_hexa("read packet:", ssh_buffer_get_begin(session->in_buffer), + ssh_buffer_get_len(session->in_buffer)); +#endif + if (session->current_crypto) { + /* + * We decrypt everything, missing the lenght part (which was + * previously read, unencrypted, and is not part of the buffer + */ + if (packet_decrypt(session, + ssh_buffer_get_begin(session->in_buffer), + ssh_buffer_get_len(session->in_buffer)) < 0) { + ssh_set_error(session, SSH_FATAL, "Packet decrypt error"); + goto error; + } + } +#ifdef DEBUG_CRYPTO + ssh_print_hexa("read packet decrypted:", ssh_buffer_get_begin(session->in_buffer), + ssh_buffer_get_len(session->in_buffer)); +#endif + ssh_log(session, SSH_LOG_PACKET, "%d bytes padding", padding); + if(((len + padding) != buffer_get_rest_len(session->in_buffer)) || + ((len + padding) < sizeof(uint32_t))) { + ssh_log(session, SSH_LOG_RARE, "no crc32 in packet"); + ssh_set_error(session, SSH_FATAL, "no crc32 in packet"); + goto error; + } + + memcpy(&crc, + (unsigned char *)buffer_get_rest(session->in_buffer) + (len+padding) - sizeof(uint32_t), + sizeof(uint32_t)); + buffer_pass_bytes_end(session->in_buffer, sizeof(uint32_t)); + crc = ntohl(crc); + if (ssh_crc32(buffer_get_rest(session->in_buffer), + (len + padding) - sizeof(uint32_t)) != crc) { +#ifdef DEBUG_CRYPTO + ssh_print_hexa("crc32 on",buffer_get_rest(session->in_buffer), + len + padding - sizeof(uint32_t)); +#endif + ssh_log(session, SSH_LOG_RARE, "Invalid crc32"); + ssh_set_error(session, SSH_FATAL, + "Invalid crc32: expected %.8x, got %.8x", + crc, + ssh_crc32(buffer_get_rest(session->in_buffer), + len + padding - sizeof(uint32_t))); + goto error; + } + /* pass the padding */ + buffer_pass_bytes(session->in_buffer, padding); + ssh_log(session, SSH_LOG_PACKET, "The packet is valid"); + +/* TODO FIXME +#ifdef WITH_ZLIB + if(session->current_crypto && session->current_crypto->do_compress_in){ + decompress_buffer(session,session->in_buffer); + } +#endif +*/ + session->recv_seq++; + /* We don't want to rewrite a new packet while still executing the packet callbacks */ + session->packet_state = PACKET_STATE_PROCESSING; + ssh_packet_parse_type(session); + /* execute callbacks */ + ssh_packet_process(session, session->in_packet.type); + session->packet_state = PACKET_STATE_INIT; + if(processed < receivedlen){ + int rc; + /* Handle a potential packet left in socket buffer */ + ssh_log(session,SSH_LOG_PACKET,"Processing %" PRIdS " bytes left in socket buffer", + receivedlen-processed); + rc = ssh_packet_socket_callback1((char *)data + processed, + receivedlen - processed,user); + processed += rc; + } + leave_function(); + return processed; + case PACKET_STATE_PROCESSING: + ssh_log(session, SSH_LOG_RARE, "Nested packet processing. Delaying."); + return 0; + } + +error: + session->session_state=SSH_SESSION_STATE_ERROR; + leave_function(); + return processed; +} + + +int packet_send1(ssh_session session) { + unsigned int blocksize = (session->current_crypto ? + session->current_crypto->out_cipher->blocksize : 8); + uint32_t currentlen = ssh_buffer_get_len(session->out_buffer) + sizeof(uint32_t); + char padstring[32] = {0}; + int rc = SSH_ERROR; + uint32_t finallen; + uint32_t crc; + uint8_t padding; + + enter_function(); + ssh_log(session,SSH_LOG_PACKET,"Sending a %d bytes long packet",currentlen); + +/* TODO FIXME +#ifdef WITH_ZLIB + if (session->current_crypto && session->current_crypto->do_compress_out) { + if (compress_buffer(session, session->out_buffer) < 0) { + goto error; + } + currentlen = buffer_get_len(session->out_buffer); + } +#endif +*/ + padding = blocksize - (currentlen % blocksize); + if (session->current_crypto) { + ssh_get_random(padstring, padding, 0); + } else { + memset(padstring, 0, padding); + } + + finallen = htonl(currentlen); + ssh_log(session, SSH_LOG_PACKET, + "%d bytes after comp + %d padding bytes = %d bytes packet", + currentlen, padding, ntohl(finallen)); + + if (buffer_prepend_data(session->out_buffer, &padstring, padding) < 0) { + goto error; + } + if (buffer_prepend_data(session->out_buffer, &finallen, sizeof(uint32_t)) < 0) { + goto error; + } + + crc = ssh_crc32((char *)ssh_buffer_get_begin(session->out_buffer) + sizeof(uint32_t), + ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t)); + + if (buffer_add_u32(session->out_buffer, ntohl(crc)) < 0) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Clear packet", ssh_buffer_get_begin(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); +#endif + + packet_encrypt(session, (unsigned char *)ssh_buffer_get_begin(session->out_buffer) + sizeof(uint32_t), + ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t)); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("encrypted packet",ssh_buffer_get_begin(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); +#endif + rc=ssh_socket_write(session->socket, ssh_buffer_get_begin(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + if(rc== SSH_ERROR) { + goto error; + } + + session->send_seq++; + + if (buffer_reinit(session->out_buffer) < 0) { + rc = SSH_ERROR; + } +error: + leave_function(); + return rc; /* SSH_OK, AGAIN or ERROR */ +} + +SSH_PACKET_CALLBACK(ssh_packet_disconnect1){ + (void)packet; + (void)user; + (void)type; + ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT"); + ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_DISCONNECT"); + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state=SSH_SESSION_STATE_DISCONNECTED; + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_smsg_success1){ + if(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){ + session->session_state=SSH_SESSION_STATE_AUTHENTICATING; + return SSH_PACKET_USED; + } else if(session->session_state==SSH_SESSION_STATE_AUTHENTICATING){ + ssh_auth1_handler(session,type); + return SSH_PACKET_USED; + } else { + return ssh_packet_channel_success(session,type,packet,user); + } +} + +SSH_PACKET_CALLBACK(ssh_packet_smsg_failure1){ + if(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){ + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"Key exchange failed: received SSH_SMSG_FAILURE"); + return SSH_PACKET_USED; + } else if(session->session_state==SSH_SESSION_STATE_AUTHENTICATING){ + ssh_auth1_handler(session,type); + return SSH_PACKET_USED; + } else { + return ssh_packet_channel_failure(session,type,packet,user); + } +} + + +#endif /* WITH_SSH1 */ + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/packet_cb.c b/libssh/src/packet_cb.c new file mode 100644 index 00000000..41d0985c --- /dev/null +++ b/libssh/src/packet_cb.c @@ -0,0 +1,244 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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 "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" + +/** + * @internal + * + * @brief Handle a SSH_DISCONNECT packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ + uint32_t code; + char *error=NULL; + ssh_string error_s; + (void)user; + (void)type; + buffer_get_u32(packet, &code); + error_s = buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + ssh_string_free(error_s); + } + ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %d:%s",code, + error != NULL ? error : "no error"); + ssh_set_error(session, SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %d:%s",code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state= SSH_SESSION_STATE_ERROR; + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){ + (void)user; + (void)type; + (void)packet; + ssh_log(session,SSH_LOG_PROTOCOL,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_dh_reply){ + int rc; + (void)type; + (void)user; + ssh_log(session,SSH_LOG_PROTOCOL,"Received SSH_KEXDH_REPLY"); + if(session->session_state!= SSH_SESSION_STATE_DH && + session->dh_handshake_state != DH_STATE_INIT_SENT){ + ssh_set_error(session,SSH_FATAL,"ssh_packet_dh_reply called in wrong state : %d:%d", + session->session_state,session->dh_handshake_state); + goto error; + } + switch(session->next_crypto->kex_type){ + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + rc=ssh_client_dh_reply(session, packet); + break; +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + rc = ssh_client_ecdh_reply(session, packet); + break; +#endif + default: + ssh_set_error(session,SSH_FATAL,"Wrong kex type in ssh_packet_dh_reply"); + goto error; + } + if(rc==SSH_OK) { + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; + } +error: + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_newkeys){ + ssh_string sig_blob = NULL; + int rc; + (void)packet; + (void)user; + (void)type; + ssh_log(session, SSH_LOG_PROTOCOL, "Received SSH_MSG_NEWKEYS"); + if(session->session_state!= SSH_SESSION_STATE_DH && + session->dh_handshake_state != DH_STATE_NEWKEYS_SENT){ + ssh_set_error(session,SSH_FATAL,"ssh_packet_newkeys called in wrong state : %d:%d", + session->session_state,session->dh_handshake_state); + goto error; + } + if(session->server){ + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { + ssh_key key; + /* client */ + rc = make_sessionid(session); + if (rc != SSH_OK) { + goto error; + } + + /* + * Set the cryptographic functions for the next crypto + * (it is needed for generate_session_keys for key lengths) + */ + if (crypt_set_algorithms(session, SSH_3DES) /* knows nothing about DES*/ ) { + goto error; + } + + if (generate_session_keys(session) < 0) { + goto error; + } + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + rc = ssh_pki_import_pubkey_blob(session->next_crypto->server_pubkey, &key); + if (rc < 0) { + return SSH_ERROR; + } + + /* check if public key from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + if(!ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + key->type_c)) { + ssh_set_error(session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + key->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + ssh_key_free(key); + return -1; + } + } + + rc = ssh_pki_signature_verify_blob(session, + sig_blob, + key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + /* Set the server public key type for known host checking */ + session->next_crypto->server_pubkey_type = key->type_c; + + ssh_key_free(key); + ssh_string_burn(sig_blob); + ssh_string_free(sig_blob); + sig_blob = NULL; + if (rc == SSH_ERROR) { + goto error; + } + ssh_log(session,SSH_LOG_PROTOCOL,"Signature verified and valid"); + + /* + * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and + * current_crypto + */ + if (session->current_crypto) { + crypto_free(session->current_crypto); + session->current_crypto=NULL; + } + + /* FIXME later, include a function to change keys */ + session->current_crypto = session->next_crypto; + + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + goto error; + } + session->next_crypto->session_id = malloc(session->current_crypto->digest_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + goto error; + } + memcpy(session->next_crypto->session_id, session->current_crypto->session_id, + session->current_crypto->digest_len); + } + session->dh_handshake_state = DH_STATE_FINISHED; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; +error: + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH_SERVICE_ACCEPT packet + * + */ +SSH_PACKET_CALLBACK(ssh_packet_service_accept){ + (void)packet; + (void)type; + (void)user; + enter_function(); + session->auth_service_state=SSH_AUTH_SERVICE_ACCEPTED; + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_MSG_SERVICE_ACCEPT"); + leave_function(); + return SSH_PACKET_USED; +} diff --git a/libssh/src/packet_crypt.c b/libssh/src/packet_crypt.c new file mode 100644 index 00000000..50b81893 --- /dev/null +++ b/libssh/src/packet_crypt.c @@ -0,0 +1,186 @@ +/* + * crypt.c - blowfish-cbc code + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#ifdef OPENSSL_CRYPTO +#include +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" + +uint32_t packet_decrypt_len(ssh_session session, char *crypted){ + uint32_t decrypted; + + if (session->current_crypto) { + if (packet_decrypt(session, crypted, + session->current_crypto->in_cipher->blocksize) < 0) { + return 0; + } + } + memcpy(&decrypted,crypted,sizeof(decrypted)); + return ntohl(decrypted); +} + +int packet_decrypt(ssh_session session, void *data,uint32_t len) { + struct ssh_cipher_struct *crypto = session->current_crypto->in_cipher; + char *out = NULL; + if(len % session->current_crypto->in_cipher->blocksize != 0){ + ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set on at least one blocksize (received %d)",len); + return SSH_ERROR; + } + out = malloc(len); + if (out == NULL) { + return -1; + } + + if (crypto->set_decrypt_key(crypto, session->current_crypto->decryptkey, + session->current_crypto->decryptIV) < 0) { + SAFE_FREE(out); + return -1; + } + crypto->cbc_decrypt(crypto,data,out,len); + + memcpy(data,out,len); + memset(out,0,len); + + SAFE_FREE(out); + return 0; +} + +unsigned char *packet_encrypt(ssh_session session, void *data, uint32_t len) { + struct ssh_cipher_struct *crypto = NULL; + HMACCTX ctx = NULL; + char *out = NULL; + unsigned int finallen; + uint32_t seq; + + if (!session->current_crypto) { + return NULL; /* nothing to do here */ + } + if(len % session->current_crypto->in_cipher->blocksize != 0){ + ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set on at least one blocksize (received %d)",len); + return NULL; + } + out = malloc(len); + if (out == NULL) { + return NULL; + } + + seq = ntohl(session->send_seq); + crypto = session->current_crypto->out_cipher; + + if (crypto->set_encrypt_key(crypto, session->current_crypto->encryptkey, + session->current_crypto->encryptIV) < 0) { + SAFE_FREE(out); + return NULL; + } + + if (session->version == 2) { + ctx = hmac_init(session->current_crypto->encryptMAC,20,SSH_HMAC_SHA1); + if (ctx == NULL) { + SAFE_FREE(out); + return NULL; + } + hmac_update(ctx,(unsigned char *)&seq,sizeof(uint32_t)); + hmac_update(ctx,data,len); + hmac_final(ctx,session->current_crypto->hmacbuf,&finallen); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("mac: ",data,len); + if (finallen != 20) { + printf("Final len is %d\n",finallen); + } + ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, 20); +#endif + } + + crypto->cbc_encrypt(crypto, data, out, len); + + memcpy(data, out, len); + memset(out, 0, len); + SAFE_FREE(out); + + if (session->version == 2) { + return session->current_crypto->hmacbuf; + } + + return NULL; +} + +/** + * @internal + * + * @brief Verify the hmac of a packet + * + * @param session The session to use. + * @param buffer The buffer to verify the hmac from. + * @param mac The mac to compare with the hmac. + * + * @return 0 if hmac and mac are equal, < 0 if not or an error + * occurred. + */ +int packet_hmac_verify(ssh_session session, ssh_buffer buffer, + unsigned char *mac) { + unsigned char hmacbuf[EVP_MAX_MD_SIZE] = {0}; + HMACCTX ctx; + unsigned int len; + uint32_t seq; + + ctx = hmac_init(session->current_crypto->decryptMAC, 20, SSH_HMAC_SHA1); + if (ctx == NULL) { + return -1; + } + + seq = htonl(session->recv_seq); + + hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t)); + hmac_update(ctx, buffer_get_rest(buffer), buffer_get_rest_len(buffer)); + hmac_final(ctx, hmacbuf, &len); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("received mac",mac,len); + ssh_print_hexa("Computed mac",hmacbuf,len); + ssh_print_hexa("seq",(unsigned char *)&seq,sizeof(uint32_t)); +#endif + if (memcmp(mac, hmacbuf, len) == 0) { + return 0; + } + + return -1; +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/pcap.c b/libssh/src/pcap.c new file mode 100644 index 00000000..6e688962 --- /dev/null +++ b/libssh/src/pcap.c @@ -0,0 +1,554 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* pcap.c */ +#include "config.h" +#ifdef WITH_PCAP + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif +#include +#include + +#include "libssh/libssh.h" +#include "libssh/pcap.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" + +/** + * @internal + * + * @defgroup libssh_pcap The libssh pcap functions + * @ingroup libssh + * + * The pcap file generation + * + * + * @{ + */ + +/* The header of a pcap file is the following. We are not going to make it + * very complicated. + * Just for information. + */ +struct pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_VERSION_MAJOR 2 +#define PCAP_VERSION_MINOR 4 + +#define DLT_RAW 12 /* raw IP */ + +/* TCP flags */ +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + +/* The header of a pcap packet. + * Just for information. + */ +struct pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/** @private + * @brief a pcap context expresses the state of a pcap dump + * in a SSH session only. Multiple pcap contexts may be used into + * a single pcap file. + */ + +struct ssh_pcap_context_struct { + ssh_session session; + ssh_pcap_file file; + int connected; + /* All of these information are useful to generate + * the dummy IP and TCP packets + */ + uint32_t ipsource; + uint32_t ipdest; + uint16_t portsource; + uint16_t portdest; + uint32_t outsequence; + uint32_t insequence; +}; + +/** @private + * @brief a pcap file expresses the state of a pcap file which may + * contain several streams. + */ +struct ssh_pcap_file_struct { + FILE *output; + uint16_t ipsequence; +}; + +/** + * @brief create a new ssh_pcap_file object + */ +ssh_pcap_file ssh_pcap_file_new(void) { + struct ssh_pcap_file_struct *pcap; + + pcap = (struct ssh_pcap_file_struct *) malloc(sizeof(struct ssh_pcap_file_struct)); + if (pcap == NULL) { + return NULL; + } + ZERO_STRUCTP(pcap); + + return pcap; +} + +/** @internal + * @brief writes a packet on file + */ +static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet){ + int err; + uint32_t len; + if(pcap == NULL || pcap->output==NULL) + return SSH_ERROR; + len=buffer_get_rest_len(packet); + err=fwrite(buffer_get_rest(packet),len,1,pcap->output); + if(err<0) + return SSH_ERROR; + else + return SSH_OK; +} + +/** @internal + * @brief prepends a packet with the pcap header and writes packet + * on file + */ +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len){ + ssh_buffer header=ssh_buffer_new(); + struct timeval now; + int err; + if(header == NULL) + return SSH_ERROR; + gettimeofday(&now,NULL); + err = buffer_add_u32(header,htonl(now.tv_sec)); + if (err < 0) { + goto error; + } + err = buffer_add_u32(header,htonl(now.tv_usec)); + if (err < 0) { + goto error; + } + err = buffer_add_u32(header,htonl(buffer_get_rest_len(packet))); + if (err < 0) { + goto error; + } + err = buffer_add_u32(header,htonl(original_len)); + if (err < 0) { + goto error; + } + err = buffer_add_buffer(header,packet); + if (err < 0) { + goto error; + } + err=ssh_pcap_file_write(pcap,header); +error: + ssh_buffer_free(header); + return err; +} + +/** + * @brief opens a new pcap file and create header + */ +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ + ssh_buffer header; + int err; + if(pcap == NULL) + return SSH_ERROR; + if(pcap->output){ + fclose(pcap->output); + pcap->output=NULL; + } + pcap->output=fopen(filename,"wb"); + if(pcap->output==NULL) + return SSH_ERROR; + header=ssh_buffer_new(); + if(header==NULL) + return SSH_ERROR; + err = buffer_add_u32(header,htonl(PCAP_MAGIC)); + if (err < 0) { + goto error; + } + err = buffer_add_u16(header,htons(PCAP_VERSION_MAJOR)); + if (err < 0) { + goto error; + } + err = buffer_add_u16(header,htons(PCAP_VERSION_MINOR)); + if (err < 0) { + goto error; + } + /* currently hardcode GMT to 0 */ + err = buffer_add_u32(header,htonl(0)); + if (err < 0) { + goto error; + } + /* accuracy */ + err = buffer_add_u32(header,htonl(0)); + if (err < 0) { + goto error; + } + /* size of the biggest packet */ + err = buffer_add_u32(header,htonl(MAX_PACKET_LEN)); + if (err < 0) { + goto error; + } + /* we will write sort-of IP */ + err = buffer_add_u32(header,htonl(DLT_RAW)); + if (err < 0) { + goto error; + } + err=ssh_pcap_file_write(pcap,header); +error: + ssh_buffer_free(header); + return err; +} + +int ssh_pcap_file_close(ssh_pcap_file pcap){ + int err; + if(pcap ==NULL || pcap->output==NULL) + return SSH_ERROR; + err=fclose(pcap->output); + pcap->output=NULL; + if(err != 0) + return SSH_ERROR; + else + return SSH_OK; +} + +void ssh_pcap_file_free(ssh_pcap_file pcap){ + ssh_pcap_file_close(pcap); + SAFE_FREE(pcap); +} + + +/** @internal + * @brief allocates a new ssh_pcap_context object + */ + +ssh_pcap_context ssh_pcap_context_new(ssh_session session){ + ssh_pcap_context ctx = (struct ssh_pcap_context_struct *) malloc(sizeof(struct ssh_pcap_context_struct)); + if(ctx==NULL){ + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(ctx); + ctx->session=session; + return ctx; +} + +void ssh_pcap_context_free(ssh_pcap_context ctx){ + SAFE_FREE(ctx); +} + +void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap){ + ctx->file=pcap; +} + +/** @internal + * @brief sets the IP and port parameters in the connection + */ +static int ssh_pcap_context_connect(ssh_pcap_context ctx){ + ssh_session session=ctx->session; + struct sockaddr_in local, remote; + socket_t fd; + socklen_t len; + if(session==NULL) + return SSH_ERROR; + if(session->socket==NULL) + return SSH_ERROR; + fd=ssh_socket_get_fd_in(session->socket); + /* TODO: adapt for windows */ + if(fd<0) + return SSH_ERROR; + len=sizeof(local); + if(getsockname(fd,(struct sockaddr *)&local,&len)<0){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Getting local IP address: %s",strerror(errno)); + return SSH_ERROR; + } + len=sizeof(remote); + if(getpeername(fd,(struct sockaddr *)&remote,&len)<0){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Getting remote IP address: %s",strerror(errno)); + return SSH_ERROR; + } + if(local.sin_family != AF_INET){ + ssh_set_error(session,SSH_REQUEST_DENIED,"Only IPv4 supported for pcap logging"); + return SSH_ERROR; + } + memcpy(&ctx->ipsource,&local.sin_addr,sizeof(ctx->ipsource)); + memcpy(&ctx->ipdest,&remote.sin_addr,sizeof(ctx->ipdest)); + memcpy(&ctx->portsource,&local.sin_port,sizeof(ctx->portsource)); + memcpy(&ctx->portdest,&remote.sin_port,sizeof(ctx->portdest)); + + ctx->connected=1; + return SSH_OK; +} + +#define IPHDR_LEN 20 +#define TCPHDR_LEN 20 +#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN) +/** @internal + * @brief write a SSH packet as a TCP over IP in a pcap file + * @param ctx open pcap context + * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received + * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted + * @param data pointer to the data to write + * @param len data to write in the pcap file. May be smaller than origlen. + * @param origlen number of bytes of complete data. + * @returns SSH_OK write is successful + * @returns SSH_ERROR an error happened. + */ +int ssh_pcap_context_write(ssh_pcap_context ctx,enum ssh_pcap_direction direction + , void *data, uint32_t len, uint32_t origlen){ + ssh_buffer ip; + int err; + if(ctx==NULL || ctx->file ==NULL) + return SSH_ERROR; + if(ctx->connected==0) + if(ssh_pcap_context_connect(ctx)==SSH_ERROR) + return SSH_ERROR; + ip=ssh_buffer_new(); + if(ip==NULL){ + ssh_set_error_oom(ctx->session); + return SSH_ERROR; + } + /* build an IP packet */ + /* V4, 20 bytes */ + err = buffer_add_u8(ip,4 << 4 | 5); + if (err < 0) { + goto error; + } + /* tos */ + err = buffer_add_u8(ip,0); + if (err < 0) { + goto error; + } + /* total len */ + err = buffer_add_u16(ip,htons(origlen + TCPIPHDR_LEN)); + if (err < 0) { + goto error; + } + /* IP id number */ + err = buffer_add_u16(ip,htons(ctx->file->ipsequence)); + if (err < 0) { + goto error; + } + ctx->file->ipsequence++; + /* fragment offset */ + err = buffer_add_u16(ip,htons(0)); + if (err < 0) { + goto error; + } + /* TTL */ + err = buffer_add_u8(ip,64); + if (err < 0) { + goto error; + } + /* protocol TCP=6 */ + err = buffer_add_u8(ip,6); + if (err < 0) { + goto error; + } + /* checksum */ + err = buffer_add_u16(ip,0); + if (err < 0) { + goto error; + } + if(direction==SSH_PCAP_DIR_OUT){ + err = buffer_add_u32(ip,ctx->ipsource); + if (err < 0) { + goto error; + } + err = buffer_add_u32(ip,ctx->ipdest); + if (err < 0) { + goto error; + } + } else { + err = buffer_add_u32(ip,ctx->ipdest); + if (err < 0) { + goto error; + } + err = buffer_add_u32(ip,ctx->ipsource); + if (err < 0) { + goto error; + } + } + /* TCP */ + if(direction==SSH_PCAP_DIR_OUT){ + err = buffer_add_u16(ip,ctx->portsource); + if (err < 0) { + goto error; + } + err = buffer_add_u16(ip,ctx->portdest); + if (err < 0) { + goto error; + } + } else { + err = buffer_add_u16(ip,ctx->portdest); + if (err < 0) { + goto error; + } + err = buffer_add_u16(ip,ctx->portsource); + if (err < 0) { + goto error; + } + } + /* sequence number */ + if(direction==SSH_PCAP_DIR_OUT){ + err = buffer_add_u32(ip,ntohl(ctx->outsequence)); + if (err < 0) { + goto error; + } + ctx->outsequence+=origlen; + } else { + err = buffer_add_u32(ip,ntohl(ctx->insequence)); + if (err < 0) { + goto error; + } + ctx->insequence+=origlen; + } + /* ack number */ + if(direction==SSH_PCAP_DIR_OUT){ + err = buffer_add_u32(ip,ntohl(ctx->insequence)); + if (err < 0) { + goto error; + } + } else { + err = buffer_add_u32(ip,ntohl(ctx->outsequence)); + if (err < 0) { + goto error; + } + } + /* header len = 20 = 5 * 32 bits, at offset 4*/ + err = buffer_add_u8(ip,5 << 4); + if (err < 0) { + goto error; + } + /* flags */ + err = buffer_add_u8(ip,TH_PUSH | TH_ACK); + if (err < 0) { + goto error; + } + /* window */ + err = buffer_add_u16(ip,htons(65535)); + if (err < 0) { + goto error; + } + /* checksum */ + err = buffer_add_u16(ip,htons(0)); + if (err < 0) { + goto error; + } + /* urgent data ptr */ + err = buffer_add_u16(ip,0); + if (err < 0) { + goto error; + } + /* actual data */ + err = buffer_add_data(ip,data,len); + if (err < 0) { + goto error; + } + err=ssh_pcap_file_write_packet(ctx->file,ip,origlen + TCPIPHDR_LEN); +error: + ssh_buffer_free(ip); + return err; +} + +/** @brief sets the pcap file used to trace the session + * @param current session + * @param pcap an handler to a pcap file. A pcap file may be used in several + * sessions. + * @returns SSH_ERROR in case of error, SSH_OK otherwise. + */ +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){ + ssh_pcap_context ctx=ssh_pcap_context_new(session); + if(ctx==NULL){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + ctx->file=pcap; + if(session->pcap_ctx) + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx=ctx; + return SSH_OK; +} + + +#else /* WITH_PCAP */ + +/* Simple stub returning errors when no pcap compiled in */ + +#include "libssh/libssh.h" +#include "libssh/priv.h" + +int ssh_pcap_file_close(ssh_pcap_file pcap){ + (void) pcap; + return SSH_ERROR; +} + +void ssh_pcap_file_free(ssh_pcap_file pcap){ + (void) pcap; +} + +ssh_pcap_file ssh_pcap_file_new(void){ + return NULL; +} +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ + (void) pcap; + (void) filename; + return SSH_ERROR; +} + +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile){ + (void) pcapfile; + ssh_set_error(session,SSH_REQUEST_DENIED,"Pcap support not compiled in"); + return SSH_ERROR; +} + +#endif + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/pki.c b/libssh/src/pki.c new file mode 100644 index 00000000..87d7e765 --- /dev/null +++ b/libssh/src/pki.c @@ -0,0 +1,1410 @@ +/* + * known_hosts.c + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2011-2012 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. + */ + +/** + * @defgroup libssh_pki The SSH Public Key Infrastructure + * @ingroup libssh + * + * Functions for the creation, importation and manipulation of public and + * private keys in the context of the SSH protocol + * + * @{ + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# if _MSC_VER >= 1400 +# include +# undef open +# define open _open +# undef close +# define close _close +# undef read +# define read _read +# undef unlink +# define unlink _unlink +# endif /* _MSC_VER */ +#endif + +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/keys.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/agent.h" + +void _ssh_pki_log(const char *function, const char *format, ...) +{ +#ifdef DEBUG_CRYPTO + char buffer[1024]; + va_list va; + + va_start(va, format); + vsnprintf(buffer, sizeof(buffer), format, va); + va_end(va); + + ssh_log_function(SSH_LOG_DEBUG, function, buffer); +#else + (void) function; + (void) format; +#endif + return; +} + +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey) { + if (strncmp(privkey, DSA_HEADER_BEGIN, strlen(DSA_HEADER_BEGIN)) == 0) { + return SSH_KEYTYPE_DSS; + } + + if (strncmp(privkey, RSA_HEADER_BEGIN, strlen(RSA_HEADER_BEGIN)) == 0) { + return SSH_KEYTYPE_RSA; + } + + if (strncmp(privkey, ECDSA_HEADER_BEGIN, strlen(ECDSA_HEADER_BEGIN)) == 0) { + return SSH_KEYTYPE_ECDSA; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief creates a new empty SSH key + * @returns an empty ssh_key handle, or NULL on error. + */ +ssh_key ssh_key_new (void) { + ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + return ptr; +} + +ssh_key ssh_key_dup(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + + return pki_key_dup(key, 0); +} + +/** + * @brief clean up the key and deallocate all existing keys + * @param[in] key ssh_key to clean + */ +void ssh_key_clean (ssh_key key){ + if(key == NULL) + return; +#ifdef HAVE_LIBGCRYPT + if(key->dsa) gcry_sexp_release(key->dsa); + if(key->rsa) gcry_sexp_release(key->rsa); + if(key->ecdsa) gcry_sexp_release(key->ecdsa); +#elif defined HAVE_LIBCRYPTO + if(key->dsa) DSA_free(key->dsa); + if(key->rsa) RSA_free(key->rsa); +#ifdef HAVE_OPENSSL_ECC + if(key->ecdsa) EC_KEY_free(key->ecdsa); +#endif /* HAVE_OPENSSL_ECC */ +#endif + key->flags=SSH_KEY_FLAG_EMPTY; + key->type=SSH_KEYTYPE_UNKNOWN; + key->ecdsa_nid = 0; + key->type_c=NULL; + key->dsa = NULL; + key->rsa = NULL; + key->ecdsa = NULL; +} + +/** + * @brief deallocate a SSH key + * @param[in] key ssh_key handle to free + */ +void ssh_key_free (ssh_key key){ + if(key){ + ssh_key_clean(key); + SAFE_FREE(key); + } +} + +/** + * @brief returns the type of a ssh key + * @param[in] key the ssh_key handle + * @returns one of SSH_KEYTYPE_RSA,SSH_KEYTYPE_DSS,SSH_KEYTYPE_RSA1 + * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown + */ +enum ssh_keytypes_e ssh_key_type(const ssh_key key){ + if (key == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + return key->type; +} + +/** + * @brief Convert a key type to a string. + * + * @param[in] type The type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { + switch (type) { + case SSH_KEYTYPE_DSS: + return "ssh-dss"; + case SSH_KEYTYPE_RSA: + return "ssh-rsa"; + case SSH_KEYTYPE_RSA1: + return "ssh-rsa1"; + case SSH_KEYTYPE_ECDSA: + return "ssh-ecdsa"; + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + /* We should never reach this */ + return NULL; +} + +/** + * @brief Convert a ssh key name to a ssh key type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key type. + */ +enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { + if (name == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + + if (strcmp(name, "rsa1") == 0) { + return SSH_KEYTYPE_RSA1; + } else if (strcmp(name, "rsa") == 0) { + return SSH_KEYTYPE_RSA; + } else if (strcmp(name, "dsa") == 0) { + return SSH_KEYTYPE_DSS; + } else if (strcmp(name, "ssh-rsa1") == 0) { + return SSH_KEYTYPE_RSA1; + } else if (strcmp(name, "ssh-rsa") == 0) { + return SSH_KEYTYPE_RSA; + } else if (strcmp(name, "ssh-dss") == 0) { + return SSH_KEYTYPE_DSS; + } else if (strcmp(name, "ssh-ecdsa") == 0 + || strcmp(name, "ecdsa") == 0 + || strcmp(name, "ecdsa-sha2-nistp256") == 0 + || strcmp(name, "ecdsa-sha2-nistp384") == 0 + || strcmp(name, "ecdsa-sha2-nistp521") == 0) { + return SSH_KEYTYPE_ECDSA; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief Check if the key has/is a public key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a public key, 0 if not. + */ +int ssh_key_is_public(const ssh_key k) { + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PUBLIC); +} + +/** + * @brief Check if the key is a private key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a private key, 0 if not. + */ +int ssh_key_is_private(const ssh_key k) { + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PRIVATE); +} + +/** + * @brief Compare keys if they are equal. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + if (k1 == NULL || k2 == NULL) { + return 1; + } + + if (k1->type != k2->type) { + ssh_pki_log("key types don't macth!"); + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (!ssh_key_is_private(k1) || + !ssh_key_is_private(k2)) { + return 1; + } + } + + return pki_key_compare(k1, k2, what); +} + +ssh_signature ssh_signature_new(void) +{ + struct ssh_signature_struct *sig; + + sig = malloc(sizeof(struct ssh_signature_struct)); + if (sig == NULL) { + return NULL; + } + ZERO_STRUCTP(sig); + + return sig; +} + +void ssh_signature_free(ssh_signature sig) +{ + if (sig == NULL) { + return; + } + + switch(sig->type) { + case SSH_KEYTYPE_DSS: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(sig->dsa_sig); +#elif defined HAVE_LIBCRYPTO + DSA_SIG_free(sig->dsa_sig); +#endif + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(sig->rsa_sig); +#elif defined HAVE_LIBCRYPTO + SAFE_FREE(sig->rsa_sig); +#endif + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + break; + } + + SAFE_FREE(sig); +} + +/** + * @brief import a base64 formated key from a memory c-string + * + * @param[in] b64_key The c-string holding the base64 encoded key + * + * @param[in] passphrase The passphrase to decrypt the key, or NULL + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] pkey A pointer where the key can be stored. You need + * to free the memory. + * + * @return SSH_ERROR in case of error, SSH_OK otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) +{ + ssh_key key; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + if (b64_key == NULL || !*b64_key) { + return SSH_ERROR; + } + + ssh_pki_log("Trying to decode privkey passphrase=%s", + passphrase ? "true" : "false"); + + key = pki_private_key_from_base64(b64_key, passphrase, auth_fn, auth_data); + if (key == NULL) { + return SSH_ERROR; + } + + *pkey = key; + + return SSH_OK; +} + +/** + * @brief Import a key from a file. + * + * @param[in] filename The filename of the the private key. + * + * @param[in] passphrase The passphrase to decrypt the private key. Set to NULL + * if none is needed or it is unknown. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] pkey A pointer to store the ssh_key. You need to free the + * key. + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + **/ +int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) { + struct stat sb; + char *key_buf; + ssh_key key; + FILE *file; + off_t size; + int rc; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + rc = stat(filename, &sb); + if (rc < 0) { + ssh_pki_log("Error getting stat of %s: %s", + filename, strerror(errno)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + + return SSH_ERROR; + } + + file = fopen(filename, "rb"); + if (file == NULL) { + ssh_pki_log("Error opening %s: %s", + filename, strerror(errno)); + return SSH_EOF; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + ssh_pki_log("Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + ssh_pki_log("Error reading %s: %s", + filename, strerror(errno)); + return SSH_ERROR; + } + key_buf[size] = 0; + + key = pki_private_key_from_base64(key_buf, passphrase, auth_fn, auth_data); + SAFE_FREE(key_buf); + if (key == NULL) { + return SSH_ERROR; + } + + *pkey = key; + return SSH_OK; +} + +/* temporary function to migrate seemlessly to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { + ssh_public_key pub; + ssh_key tmp; + + if(key == NULL) { + return NULL; + } + + tmp = ssh_key_dup(key); + if (tmp == NULL) { + return NULL; + } + + pub = malloc(sizeof(struct ssh_public_key_struct)); + if (pub == NULL) { + ssh_key_free(tmp); + return NULL; + } + ZERO_STRUCTP(pub); + + pub->type = tmp->type; + pub->type_c = tmp->type_c; + + pub->dsa_pub = tmp->dsa; + tmp->dsa = NULL; + pub->rsa_pub = tmp->rsa; + tmp->rsa = NULL; + + ssh_key_free(tmp); + + return pub; +} + +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) { + ssh_private_key privkey; + + privkey = malloc(sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; + privkey->dsa_priv = key->dsa; + privkey->rsa_priv = key->rsa; + + return privkey; +} + +static int pki_import_pubkey_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) { + ssh_key key; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC; + + switch (type) { + case SSH_KEYTYPE_DSS: + { + ssh_string p; + ssh_string q; + ssh_string g; + ssh_string pubkey; + + p = buffer_get_ssh_string(buffer); + if (p == NULL) { + goto fail; + } + q = buffer_get_ssh_string(buffer); + if (q == NULL) { + ssh_string_burn(p); + ssh_string_free(p); + + goto fail; + } + g = buffer_get_ssh_string(buffer); + if (g == NULL) { + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(q); + ssh_string_free(q); + + goto fail; + } + pubkey = buffer_get_ssh_string(buffer); + if (pubkey == NULL) { + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(g); + ssh_string_free(g); + + goto fail; + } + + rc = pki_pubkey_build_dss(key, p, q, g, pubkey); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("p", ssh_string_data(p), ssh_string_len(p)); + ssh_print_hexa("q", ssh_string_data(q), ssh_string_len(q)); + ssh_print_hexa("g", ssh_string_data(g), ssh_string_len(g)); +#endif + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(g); + ssh_string_free(g); + ssh_string_burn(pubkey); + ssh_string_free(pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + { + ssh_string e; + ssh_string n; + + e = buffer_get_ssh_string(buffer); + if (e == NULL) { + goto fail; + } + n = buffer_get_ssh_string(buffer); + if (n == NULL) { + ssh_string_burn(e); + ssh_string_free(e); + + goto fail; + } + + rc = pki_pubkey_build_rsa(key, e, n); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("e", ssh_string_data(e), ssh_string_len(e)); + ssh_print_hexa("n", ssh_string_data(n), ssh_string_len(n)); +#endif + ssh_string_burn(e); + ssh_string_free(e); + ssh_string_burn(n); + ssh_string_free(n); + if (rc == SSH_ERROR) { + goto fail; + } + } + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_ECC + { + ssh_string e; + ssh_string i; + int nid; + + i = buffer_get_ssh_string(buffer); + if (i == NULL) { + goto fail; + } + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + ssh_string_free(i); + if (nid == -1) { + goto fail; + } + + + e = buffer_get_ssh_string(buffer); + if (e == NULL) { + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, e); + ssh_string_burn(e); + ssh_string_free(e); + if (rc < 0) { + goto fail; + } + } + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + ssh_pki_log("Unknown public key protocol %d", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +/** + * @brief Import a base64 formated public key from a memory c-string. + * + * @param[in] b64_key The base64 key to format. + * + * @param[in] type The type of the key to format. + * + * @param[out] pkey A pointer where the key can be stored. You need + * to free the memory. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey) { + ssh_buffer buffer; + ssh_string type_s; + int rc; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = base64_to_bin(b64_key); + if (buffer == NULL) { + return SSH_ERROR; + } + + type_s = buffer_get_ssh_string(buffer); + if (type_s == NULL) { + ssh_buffer_free(buffer); + return SSH_ERROR; + } + ssh_string_free(type_s); + + rc = pki_import_pubkey_buffer(buffer, type, pkey); + ssh_buffer_free(buffer); + + return rc; +} + +/** + * @internal + * + * @brief Import a public key from a ssh string. + * + * @param[in] key_blob The key blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the key can be stored. You need + * to free the memory. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey) { + ssh_buffer buffer; + ssh_string type_s = NULL; + enum ssh_keytypes_e type; + int rc; + + if (key_blob == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_pki_log("Out of memory!"); + return SSH_ERROR; + } + + rc = buffer_add_data(buffer, ssh_string_data(key_blob), + ssh_string_len(key_blob)); + if (rc < 0) { + ssh_pki_log("Out of memory!"); + goto fail; + } + + type_s = buffer_get_ssh_string(buffer); + if (type_s == NULL) { + ssh_pki_log("Out of memory!"); + goto fail; + } + + type = ssh_key_type_from_name(ssh_string_get_char(type_s)); + if (type == SSH_KEYTYPE_UNKNOWN) { + ssh_pki_log("Unknown key type found!"); + goto fail; + } + ssh_string_free(type_s); + + rc = pki_import_pubkey_buffer(buffer, type, pkey); + + ssh_buffer_free(buffer); + + return rc; +fail: + ssh_buffer_free(buffer); + ssh_string_free(type_s); + + return SSH_ERROR; +} + +/** + * @brief Import a public key from the given filename. + * + * @param[in] filename The path to the public key. + * + * @param[out] pkey A pointer to store the public key. You need to free the + * memory. + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) +{ + enum ssh_keytypes_e type; + struct stat sb; + char *key_buf, *p; + const char *q; + FILE *file; + off_t size; + int rc; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + rc = stat(filename, &sb); + if (rc < 0) { + ssh_pki_log("Error gettint stat of %s: %s", + filename, strerror(errno)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + return SSH_ERROR; + } + + if (sb.st_size > MAX_PUBKEY_SIZE) { + return SSH_ERROR; + } + + file = fopen(filename, "r"); + if (file == NULL) { + ssh_pki_log("Error opening %s: %s", + filename, strerror(errno)); + return SSH_EOF; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + ssh_pki_log("Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + ssh_pki_log("Error reading %s: %s", + filename, strerror(errno)); + return SSH_ERROR; + } + key_buf[size] = '\0'; + + q = p = key_buf; + while (!isspace((int)*p)) p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + if (type == SSH_KEYTYPE_UNKNOWN) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } + q = ++p; + while (!isspace((int)*p)) p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, pkey); + SAFE_FREE(key_buf); + + return rc; +} + +/** + * @brief Generates a keypair. + * @param[in] type Type of key to create + * @param[in] parameter Parameter to the creation of key: + * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * dsa : length of the key in bits (e.g. 1024, 2048, 3072) + * ecdsa : bits of the key (e.g. 256, 384, 512) + * @param[out] pkey A pointer to store the private key. You need to free the + * memory. + * @return SSH_OK on success, SSH_ERROR on error. + * @warning Generating a key pair may take some time. + */ + +int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, + ssh_key *pkey){ + int rc; + ssh_key key = ssh_key_new(); + + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + switch(type){ + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = pki_key_generate_rsa(key, parameter); + if(rc == SSH_ERROR) + goto error; + break; + case SSH_KEYTYPE_DSS: + rc = pki_key_generate_dss(key, parameter); + if(rc == SSH_ERROR) + goto error; + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_ECC + rc = pki_key_generate_ecdsa(key, parameter); + if(rc == SSH_ERROR) + goto error; + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + goto error; + } + + *pkey = key; + return SSH_OK; +error: + ssh_key_free(key); + return SSH_ERROR; +} + +/** + * @brief Create a public key from a private key. + * + * @param[in] privkey The private key to get the public key from. + * + * @param[out] pkey A pointer to store the newly allocated public key. You + * NEED to free the key. + * + * @return A public key, NULL on error. + * + * @see ssh_key_free() + */ +int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey) +{ + ssh_key pubkey; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + pubkey = pki_key_dup(privkey, 1); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *pkey = pubkey; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a public key. + * + * The "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key + * Algorithms" for any of the supported protocol 2 key types. + * + * @param[in] key A public or private key to create the public ssh_string + * from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * NEED to free it. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_publickey_to_blob(key); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @brief Convert a public key to a base64 hased key. + * + * @param[in] key The key to hash + * + * @param[out] b64_key A pointer to store the base64 hased key. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key) +{ + ssh_string key_blob; + unsigned char *b64; + + if (key == NULL || b64_key == NULL) { + return SSH_ERROR; + } + + key_blob = pki_publickey_to_blob(key); + if (key_blob == NULL) { + return SSH_ERROR; + } + + b64 = bin_to_base64(ssh_string_data(key_blob), ssh_string_len(key_blob)); + ssh_string_free(key_blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = (char *)b64; + + return SSH_OK; +} + +int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename) +{ + char key_buf[4096]; + char host[256]; + char *b64_key; + char *user; + FILE *fp; + int rc; + + if (key == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + return SSH_ERROR; + } + + rc = gethostname(host, sizeof(host)); + if (rc < 0) { + free(user); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + if (rc < 0) { + free(user); + return SSH_ERROR; + } + + rc = snprintf(key_buf, sizeof(key_buf), + "%s %s %s@%s\n", + key->type_c, + b64_key, + user, + host); + free(user); + free(b64_key); + if (rc < 0) { + return SSH_ERROR; + } + + fp = fopen(filename, "w+"); + if (fp == NULL) { + return SSH_ERROR; + } + rc = fwrite(key_buf, strlen(key_buf), 1, fp); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +int ssh_pki_export_pubkey_rsa1(const ssh_key key, + const char *host, + char *rsa1, + size_t rsa1_len) +{ + return pki_export_pubkey_rsa1(key, host, rsa1, rsa1_len); +} + +int ssh_pki_export_signature_blob(const ssh_signature sig, + ssh_string *sig_blob) +{ + ssh_buffer buf = NULL; + ssh_string str; + int rc; + + if (sig == NULL || sig_blob == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + str = ssh_string_from_char(ssh_key_type_to_char(sig->type)); + if (str == NULL) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + rc = buffer_add_ssh_string(buf, str); + ssh_string_free(str); + if (rc < 0) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + str = pki_signature_to_blob(sig); + if (str == NULL) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + rc = buffer_add_ssh_string(buf, str); + ssh_string_free(str); + if (rc < 0) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + str = ssh_string_new(buffer_get_rest_len(buf)); + if (str == NULL) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + ssh_string_fill(str, buffer_get_rest(buf), buffer_get_rest_len(buf)); + ssh_buffer_free(buf); + + *sig_blob = str; + + return SSH_OK; +} + +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig) +{ + ssh_signature sig; + enum ssh_keytypes_e type; + ssh_string str; + ssh_buffer buf; + int rc; + + if (sig_blob == NULL || psig == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + rc = buffer_add_data(buf, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + str = buffer_get_ssh_string(buf); + if (str == NULL) { + ssh_buffer_free(buf); + return SSH_ERROR; + } + + type = ssh_key_type_from_name(ssh_string_get_char(str)); + ssh_string_free(str); + + str = buffer_get_ssh_string(buf); + ssh_buffer_free(buf); + if (str == NULL) { + return SSH_ERROR; + } + + sig = pki_signature_from_blob(pubkey, str, type); + ssh_string_free(str); + if (sig == NULL) { + return SSH_ERROR; + } + + *psig = sig; + return SSH_OK; +} + +int ssh_pki_signature_verify_blob(ssh_session session, + ssh_string sig_blob, + const ssh_key key, + unsigned char *digest, + size_t dlen) +{ + ssh_signature sig; + int rc; + + rc = ssh_pki_import_signature_blob(sig_blob, key, &sig); + if (rc < 0) { + return SSH_ERROR; + } + + ssh_log(session, + SSH_LOG_FUNCTIONS, + "Going to verify a %s type signature", + key->type_c); + + + if (key->type == SSH_KEYTYPE_ECDSA) { +#if HAVE_ECC + unsigned char ehash[EVP_DIGEST_LEN] = {0}; + uint32_t elen; + + evp(key->ecdsa_nid, digest, dlen, ehash, &elen); + + rc = pki_signature_verify(session, + sig, + key, + ehash, + elen); +#endif + } else { + unsigned char hash[SHA_DIGEST_LEN] = {0}; + + sha1(digest, dlen, hash); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Hash to be verified with dsa", hash, SHA_DIGEST_LEN); +#endif + + rc = pki_signature_verify(session, + sig, + key, + hash, + SHA_DIGEST_LEN); + } + + ssh_signature_free(sig); + + return rc; +} + +/* + * This function signs the session id (known as H) as a string then + * the content of sigbuf */ +ssh_string ssh_pki_do_sign(ssh_session session, + ssh_buffer sigbuf, + const ssh_key privkey) { + struct ssh_crypto_struct *crypto = + session->current_crypto ? session->current_crypto : + session->next_crypto; + unsigned char hash[SHA_DIGEST_LEN] = {0}; + ssh_signature sig; + ssh_string sig_blob; + ssh_string session_id; + SHACTX ctx; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return NULL; + } + + session_id = ssh_string_new(crypto->digest_len); + if (session_id == NULL) { + return NULL; + } + ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + + ctx = sha1_init(); + if (ctx == NULL) { + ssh_string_free(session_id); + return NULL; + } + + sha1_update(ctx, session_id, ssh_string_len(session_id) + 4); + ssh_string_free(session_id); + + sha1_update(ctx, buffer_get_rest(sigbuf), buffer_get_rest_len(sigbuf)); + sha1_final(hash, ctx); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Hash being signed", hash, SHA_DIGEST_LEN); +#endif + + sig = pki_do_sign(privkey, hash, SHA_DIGEST_LEN); + if (sig == NULL) { + return NULL; + } + + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + ssh_signature_free(sig); + if (rc < 0) { + return NULL; + } + + return sig_blob; +} + +#ifndef _WIN32 +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey) { + struct ssh_crypto_struct *crypto; + ssh_string session_id; + ssh_string sig_blob; + ssh_buffer sig_buf; + int rc; + + if (session->current_crypto) { + crypto = session->current_crypto; + } else { + crypto = session->next_crypto; + } + + /* prepend session identifier */ + session_id = ssh_string_new(crypto->digest_len); + if (session_id == NULL) { + return NULL; + } + ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + + sig_buf = ssh_buffer_new(); + if (sig_buf == NULL) { + ssh_string_free(session_id); + return NULL; + } + + rc = buffer_add_ssh_string(sig_buf, session_id); + if (rc < 0) { + ssh_string_free(session_id); + ssh_buffer_free(sig_buf); + return NULL; + } + ssh_string_free(session_id); + + /* append out buffer */ + if (buffer_add_buffer(sig_buf, buf) < 0) { + ssh_buffer_free(sig_buf); + return NULL; + } + + /* create signature */ + sig_blob = ssh_agent_sign_data(session, pubkey, sig_buf); + + ssh_buffer_free(sig_buf); + + return sig_blob; +} +#endif /* _WIN32 */ + +#ifdef WITH_SERVER +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey) +{ + struct ssh_crypto_struct *crypto; + unsigned char hash[SHA_DIGEST_LEN] = {0}; + ssh_signature sig; + ssh_string sig_blob; + SHACTX ctx; + int rc; + + if (session == NULL || privkey == NULL || !ssh_key_is_private(privkey)) { + return NULL; + } + crypto = session->current_crypto ? session->current_crypto : + session->next_crypto; + + ctx = sha1_init(); + if (ctx == NULL) { + return NULL; + } + if (crypto->session_id == NULL){ + ssh_set_error(session,SSH_FATAL,"Missing session_id"); + return NULL; + } + sha1_update(ctx, crypto->session_id, crypto->digest_len); + sha1_final(hash, ctx); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Hash being signed", hash, SHA_DIGEST_LEN); +#endif + + sig = pki_do_sign_sessionid(privkey, hash, SHA_DIGEST_LEN); + if (sig == NULL) { + return NULL; + } + + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + ssh_signature_free(sig); + if (rc < 0) { + return NULL; + } + + return sig_blob; +} +#endif /* WITH_SERVER */ + +/** + * @} + */ diff --git a/libssh/src/pki_crypto.c b/libssh/src/pki_crypto.c new file mode 100644 index 00000000..0ec05d33 --- /dev/null +++ b/libssh/src/pki_crypto.c @@ -0,0 +1,1403 @@ +/* + * pki_crypto.c - PKI infrastructure using OpenSSL + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2012 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. + */ + +#ifndef _PKI_CRYPTO_H +#define _PKI_CRYPTO_H + +#include +#include +#include +#include + +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif + + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/dh.h" + +struct pem_get_password_struct { + ssh_auth_callback fn; + void *data; +}; + +static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { + struct pem_get_password_struct *pgp = userdata; + + (void) rwflag; /* unused */ + + if (buf == NULL) { + return 0; + } + + memset(buf, '\0', size); + if (pgp) { + int rc; + + rc = pgp->fn("Passphrase for private key:", + buf, size, 0, 0, + pgp->data); + if (rc == 0) { + return strlen(buf); + } + } + + return 0; +} + +#ifdef HAVE_OPENSSL_ECC +static int pki_key_ecdsa_to_nid(EC_KEY *k) +{ + const EC_GROUP *g = EC_KEY_get0_group(k); + int nid; + + nid = EC_GROUP_get_curve_name(g); + if (nid) { + return nid; + } + + return -1; +} + +static const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "ecdsa-sha2-nistp256"; + case NID_secp384r1: + return "ecdsa-sha2-nistp384"; + case NID_secp521r1: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "nistp256"; + case NID_secp384r1: + return "nistp384"; + case NID_secp521r1: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(name, "nistp384") == 0) { + return NID_secp384r1; + } else if (strcmp(name, "nistp521") == 0) { + return NID_secp521r1; + } + + return -1; +} + +static ssh_string make_ecpoint_string(const EC_GROUP *g, + const EC_POINT *p) +{ + ssh_string s; + size_t len; + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + NULL, + 0, + NULL); + if (len == 0) { + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(s), + ssh_string_len(s), + NULL); + if (len != ssh_string_len(s)) { + ssh_string_free(s); + return NULL; + } + + return s; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + EC_POINT *p; + const EC_GROUP *g; + int ok; + + key->ecdsa_nid = nid; + key->type_c = pki_key_ecdsa_nid_to_name(nid); + + key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (key->ecdsa == NULL) { + return -1; + } + + g = EC_KEY_get0_group(key->ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + return -1; + } + + ok = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (!ok) { + EC_POINT_free(p); + return -1; + } + + ok = EC_KEY_set_public_key(key->ecdsa, p); + if (!ok) { + EC_POINT_free(p); + } + + return 0; +} +#endif + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new; + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + switch (key->type) { + case SSH_KEYTYPE_DSS: + new->dsa = DSA_new(); + if (new->dsa == NULL) { + goto fail; + } + + /* + * p = public prime number + * q = public 160-bit subprime, q | p-1 + * g = public generator of subgroup + * pub_key = public key y = g^x + * priv_key = private key x + */ + new->dsa->p = BN_dup(key->dsa->p); + if (new->dsa->p == NULL) { + goto fail; + } + + new->dsa->q = BN_dup(key->dsa->q); + if (new->dsa->q == NULL) { + goto fail; + } + + new->dsa->g = BN_dup(key->dsa->g); + if (new->dsa->g == NULL) { + goto fail; + } + + new->dsa->pub_key = BN_dup(key->dsa->pub_key); + if (new->dsa->pub_key == NULL) { + goto fail; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + new->dsa->priv_key = BN_dup(key->dsa->priv_key); + if (new->dsa->priv_key == NULL) { + goto fail; + } + } + + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + new->rsa = RSA_new(); + if (new->rsa == NULL) { + goto fail; + } + + /* + * n = public modulus + * e = public exponent + * d = private exponent + * p = secret prime factor + * q = secret prime factor + * dmp1 = d mod (p-1) + * dmq1 = d mod (q-1) + * iqmp = q^-1 mod p + */ + new->rsa->n = BN_dup(key->rsa->n); + if (new->rsa->n == NULL) { + goto fail; + } + + new->rsa->e = BN_dup(key->rsa->e); + if (new->rsa->e == NULL) { + goto fail; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + new->rsa->d = BN_dup(key->rsa->d); + if (new->rsa->d == NULL) { + goto fail; + } + + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the + * RSA operations are much faster when these values are available. + */ + if (key->rsa->p != NULL) { + new->rsa->p = BN_dup(key->rsa->p); + if (new->rsa->p == NULL) { + goto fail; + } + } + + if (key->rsa->q != NULL) { + new->rsa->q = BN_dup(key->rsa->q); + if (new->rsa->q == NULL) { + goto fail; + } + } + + if (key->rsa->dmp1 != NULL) { + new->rsa->dmp1 = BN_dup(key->rsa->dmp1); + if (new->rsa->dmp1 == NULL) { + goto fail; + } + } + + if (key->rsa->dmq1 != NULL) { + new->rsa->dmq1 = BN_dup(key->rsa->dmq1); + if (new->rsa->dmq1 == NULL) { + goto fail; + } + } + + if (key->rsa->iqmp != NULL) { + new->rsa->iqmp = BN_dup(key->rsa->iqmp); + if (new->rsa->iqmp == NULL) { + goto fail; + } + } + } + + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + /* privkey -> pubkey */ + if (demote && ssh_key_is_private(key)) { + const EC_POINT *p; + int ok; + + new->ecdsa_nid = key->ecdsa_nid; + + new->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (new->ecdsa == NULL) { + goto fail; + } + + p = EC_KEY_get0_public_key(key->ecdsa); + if (p == NULL) { + goto fail; + } + + ok = EC_KEY_set_public_key(new->ecdsa, p); + if (!ok) { + goto fail; + } + } else { + new->ecdsa = EC_KEY_dup(key->ecdsa); + } + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + ssh_key_free(new); + return NULL; + } + + return new; +fail: + ssh_key_free(new); + return NULL; +} + +int pki_key_generate_rsa(ssh_key key, int parameter){ + key->rsa = RSA_generate_key(parameter, 65537, NULL, NULL); + if(key->rsa == NULL) + return SSH_ERROR; + return SSH_OK; +} + +int pki_key_generate_dss(ssh_key key, int parameter){ + int rc; + key->dsa = DSA_generate_parameters(parameter, NULL, 0, NULL, NULL, + NULL, NULL); + if(key->dsa == NULL){ + return SSH_ERROR; + } + rc = DSA_generate_key(key->dsa); + if (rc != 1){ + DSA_free(key->dsa); + key->dsa=NULL; + return SSH_ERROR; + } + return SSH_OK; +} + +#ifdef HAVE_OPENSSL_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) { + int nid; + int ok; + + switch (parameter) { + case 384: + nid = NID_secp384r1; + case 512: + nid = NID_secp521r1; + case 256: + default: + nid = NID_X9_62_prime256v1; + } + + key->ecdsa_nid = nid; + key->type = SSH_KEYTYPE_ECDSA; + key->type_c = pki_key_ecdsa_nid_to_name(nid); + + key->ecdsa = EC_KEY_new_by_curve_name(nid); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + ok = EC_KEY_generate_key(key->ecdsa); + if (!ok) { + EC_KEY_free(key->ecdsa); + return SSH_ERROR; + } + + EC_KEY_set_asn1_flag(key->ecdsa, OPENSSL_EC_NAMED_CURVE); + + return SSH_OK; +} +#endif + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + switch (k1->type) { + case SSH_KEYTYPE_DSS: + if (DSA_size(k1->dsa) != DSA_size(k2->dsa)) { + return 1; + } + if (bignum_cmp(k1->dsa->p, k2->dsa->p) != 0) { + return 1; + } + if (bignum_cmp(k1->dsa->q, k2->dsa->q) != 0) { + return 1; + } + if (bignum_cmp(k1->dsa->g, k2->dsa->g) != 0) { + return 1; + } + if (bignum_cmp(k1->dsa->pub_key, k2->dsa->pub_key) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (bignum_cmp(k1->dsa->priv_key, k2->dsa->priv_key) != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + if (RSA_size(k1->rsa) != RSA_size(k2->rsa)) { + return 1; + } + if (bignum_cmp(k1->rsa->e, k2->rsa->e) != 0) { + return 1; + } + if (bignum_cmp(k1->rsa->n, k2->rsa->n) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (bignum_cmp(k1->rsa->p, k2->rsa->p) != 0) { + return 1; + } + + if (bignum_cmp(k1->rsa->q, k2->rsa->q) != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + { + const EC_POINT *p1 = EC_KEY_get0_public_key(k1->ecdsa); + const EC_POINT *p2 = EC_KEY_get0_public_key(k2->ecdsa); + const EC_GROUP *g1 = EC_KEY_get0_group(k1->ecdsa); + const EC_GROUP *g2 = EC_KEY_get0_group(k2->ecdsa); + + if (p1 == NULL || p2 == NULL) { + return 1; + } + + if (EC_GROUP_cmp(g1, g2, NULL) != 0) { + return 1; + } + + if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (bignum_cmp(EC_KEY_get0_private_key(k1->ecdsa), + EC_KEY_get0_private_key(k2->ecdsa))) { + return 1; + } + } + + break; + } +#endif + case SSH_KEYTYPE_UNKNOWN: + return 1; + } + + return 0; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) { + BIO *mem = NULL; + DSA *dsa = NULL; + RSA *rsa = NULL; + ssh_key key; + enum ssh_keytypes_e type; +#ifdef HAVE_OPENSSL_ECC + EC_KEY *ecdsa = NULL; +#else + void *ecdsa = NULL; +#endif + + /* needed for openssl initialization */ + if (ssh_init() < 0) { + return NULL; + } + + type = pki_privatekey_type_from_string(b64_key); + if (type == SSH_KEYTYPE_UNKNOWN) { + ssh_pki_log("Unknown or invalid private key."); + return NULL; + } + + mem = BIO_new_mem_buf((void*)b64_key, -1); + + switch (type) { + case SSH_KEYTYPE_DSS: + if (passphrase == NULL) { + if (auth_fn) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + dsa = PEM_read_bio_DSAPrivateKey(mem, NULL, pem_get_password, &pgp); + } else { + /* openssl uses its own callback to get the passphrase here */ + dsa = PEM_read_bio_DSAPrivateKey(mem, NULL, NULL, NULL); + } + } else { + dsa = PEM_read_bio_DSAPrivateKey(mem, NULL, NULL, (void *) passphrase); + } + + BIO_free(mem); + + if (dsa == NULL) { + ssh_pki_log("Parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + if (passphrase == NULL) { + if (auth_fn) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + rsa = PEM_read_bio_RSAPrivateKey(mem, NULL, pem_get_password, &pgp); + } else { + /* openssl uses its own callback to get the passphrase here */ + rsa = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL); + } + } else { + rsa = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, (void *) passphrase); + } + + BIO_free(mem); + + if (rsa == NULL) { + ssh_pki_log("Parsing private key: %s", + ERR_error_string(ERR_get_error(),NULL)); + return NULL; + } + + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + if (passphrase == NULL) { + if (auth_fn) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + ecdsa = PEM_read_bio_ECPrivateKey(mem, NULL, pem_get_password, &pgp); + } else { + /* openssl uses its own callback to get the passphrase here */ + ecdsa = PEM_read_bio_ECPrivateKey(mem, NULL, NULL, NULL); + } + } else { + ecdsa = PEM_read_bio_ECPrivateKey(mem, NULL, NULL, (void *) passphrase); + } + + BIO_free(mem); + + if (ecdsa == NULL) { + ssh_pki_log("Parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + BIO_free(mem); + ssh_pki_log("Unkown or invalid private key type %d", type); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + key->dsa = dsa; + key->rsa = rsa; + key->ecdsa = ecdsa; +#ifdef HAVE_OPENSSL_ECC + if (key->type == SSH_KEYTYPE_ECDSA) { + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + key->type_c = pki_key_ecdsa_nid_to_name(key->ecdsa_nid); + } +#endif + + return key; +fail: + ssh_key_free(key); + DSA_free(dsa); + RSA_free(rsa); +#ifdef HAVE_OPENSSL_ECC + EC_KEY_free(ecdsa); +#endif + + return NULL; +} + +int pki_pubkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey) { + key->dsa = DSA_new(); + if (key->dsa == NULL) { + return SSH_ERROR; + } + + key->dsa->p = make_string_bn(p); + key->dsa->q = make_string_bn(q); + key->dsa->g = make_string_bn(g); + key->dsa->pub_key = make_string_bn(pubkey); + if (key->dsa->p == NULL || + key->dsa->q == NULL || + key->dsa->g == NULL || + key->dsa->pub_key == NULL) { + DSA_free(key->dsa); + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n) { + key->rsa = RSA_new(); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + key->rsa->e = make_string_bn(e); + key->rsa->n = make_string_bn(n); + if (key->rsa->e == NULL || + key->rsa->n == NULL) { + RSA_free(key->rsa); + return SSH_ERROR; + } + + return SSH_OK; +} + +ssh_string pki_publickey_to_blob(const ssh_key key) +{ + ssh_buffer buffer; + ssh_string type_s; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + + rc = buffer_add_ssh_string(buffer, type_s); + ssh_string_free(type_s); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_DSS: + p = make_bignum_string(key->dsa->p); + if (p == NULL) { + goto fail; + } + + q = make_bignum_string(key->dsa->q); + if (q == NULL) { + goto fail; + } + + g = make_bignum_string(key->dsa->g); + if (g == NULL) { + goto fail; + } + + n = make_bignum_string(key->dsa->pub_key); + if (n == NULL) { + goto fail; + } + + if (buffer_add_ssh_string(buffer, p) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, q) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, g) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, n) < 0) { + goto fail; + } + + ssh_string_burn(p); + ssh_string_free(p); + p = NULL; + ssh_string_burn(g); + ssh_string_free(g); + g = NULL; + ssh_string_burn(q); + ssh_string_free(q); + q = NULL; + ssh_string_burn(n); + ssh_string_free(n); + n = NULL; + + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + e = make_bignum_string(key->rsa->e); + if (e == NULL) { + goto fail; + } + + n = make_bignum_string(key->rsa->n); + if (n == NULL) { + goto fail; + } + + if (buffer_add_ssh_string(buffer, e) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, n) < 0) { + goto fail; + } + + ssh_string_burn(e); + ssh_string_free(e); + e = NULL; + ssh_string_burn(n); + ssh_string_free(n); + n = NULL; + + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + rc = buffer_reinit(buffer); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_name(key->ecdsa_nid)); + if (type_s == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + + rc = buffer_add_ssh_string(buffer, type_s); + ssh_string_free(type_s); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + + rc = buffer_add_ssh_string(buffer, type_s); + ssh_string_free(type_s); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + e = make_ecpoint_string(EC_KEY_get0_group(key->ecdsa), + EC_KEY_get0_public_key(key->ecdsa)); + if (e == NULL) { + return NULL; + } + + rc = buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + ssh_string_free(e); + e = NULL; + + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + goto fail; + } + + str = ssh_string_new(buffer_get_rest_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, buffer_get_rest(buffer), buffer_get_rest_len(buffer)); + if (rc < 0) { + goto fail; + } + ssh_buffer_free(buffer); + + return str; +fail: + ssh_buffer_free(buffer); + ssh_string_burn(str); + ssh_string_free(str); + ssh_string_burn(e); + ssh_string_free(e); + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(g); + ssh_string_free(g); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(n); + ssh_string_free(n); + + return NULL; +} + +int pki_export_pubkey_rsa1(const ssh_key key, + const char *host, + char *rsa1, + size_t rsa1_len) +{ + char *e; + char *n; + int rsa_size = RSA_size(key->rsa); + + e = bignum_bn2dec(key->rsa->e); + if (e == NULL) { + return SSH_ERROR; + } + + n = bignum_bn2dec(key->rsa->n); + if (n == NULL) { + OPENSSL_free(e); + return SSH_ERROR; + } + + snprintf(rsa1, rsa1_len, + "%s %d %s %s\n", + host, rsa_size << 3, e, n); + OPENSSL_free(e); + OPENSSL_free(n); + + return SSH_OK; +} + +/** + * @internal + * + * @brief Compute a digital signature. + * + * @param[in] digest The message digest. + * + * @param[in] dlen The length of the digest. + * + * @param[in] privkey The private rsa key to use for signing. + * + * @return A newly allocated rsa sig blob or NULL on error. + */ +static ssh_string _RSA_do_sign(const unsigned char *digest, + int dlen, + RSA *privkey) +{ + ssh_string sig_blob; + unsigned char *sig; + unsigned int slen; + int ok; + + sig = malloc(RSA_size(privkey)); + if (sig == NULL) { + return NULL; + } + + ok = RSA_sign(NID_sha1, digest, dlen, sig, &slen, privkey); + if (!ok) { + SAFE_FREE(sig); + return NULL; + } + + sig_blob = ssh_string_new(slen); + if (sig_blob == NULL) { + SAFE_FREE(sig); + return NULL; + } + + ssh_string_fill(sig_blob, sig, slen); + memset(sig, 'd', slen); + SAFE_FREE(sig); + + return sig_blob; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + char buffer[40] = {0}; + ssh_string sig_blob = NULL; + ssh_string r; + ssh_string s; + + switch(sig->type) { + case SSH_KEYTYPE_DSS: + r = make_bignum_string(sig->dsa_sig->r); + if (r == NULL) { + return NULL; + } + s = make_bignum_string(sig->dsa_sig->s); + if (s == NULL) { + ssh_string_free(r); + return NULL; + } + + memcpy(buffer, + ((char *)ssh_string_data(r)) + ssh_string_len(r) - 20, + 20); + memcpy(buffer + 20, + ((char *)ssh_string_data(s)) + ssh_string_len(s) - 20, + 20); + + ssh_string_free(r); + ssh_string_free(s); + + sig_blob = ssh_string_new(40); + if (sig_blob == NULL) { + return NULL; + } + + ssh_string_fill(sig_blob, buffer, 40); + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sig_blob = ssh_string_copy(sig->rsa_sig); + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + r = make_bignum_string(sig->ecdsa_sig->r); + if (r == NULL) { + return NULL; + } + s = make_bignum_string(sig->ecdsa_sig->s); + if (s == NULL) { + ssh_string_free(r); + return NULL; + } + + memcpy(buffer, + ((char *)ssh_string_data(r)) + ssh_string_len(r) - 20, + 20); + memcpy(buffer + 20, + ((char *)ssh_string_data(s)) + ssh_string_len(s) - 20, + 20); + + ssh_string_free(r); + ssh_string_free(s); + + sig_blob = ssh_string_new(40); + if (sig_blob == NULL) { + return NULL; + } + + ssh_string_fill(sig_blob, buffer, 40); + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + ssh_pki_log("Unknown signature key type: %d", sig->type); + return NULL; + } + + return sig_blob; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type) +{ + ssh_signature sig; + ssh_string r; + ssh_string s; + size_t len; + size_t rsalen; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + + len = ssh_string_len(sig_blob); + + switch(type) { + case SSH_KEYTYPE_DSS: + /* 40 is the dual signature blob len. */ + if (len != 40) { + ssh_pki_log("Signature has wrong size: %lu", + (unsigned long)len); + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("r", ssh_string_data(sig_blob), 20); + ssh_print_hexa("s", (unsigned char *)ssh_string_data(sig_blob) + 20, 20); +#endif + + sig->dsa_sig = DSA_SIG_new(); + if (sig->dsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + + r = ssh_string_new(20); + if (r == NULL) { + ssh_signature_free(sig); + return NULL; + } + ssh_string_fill(r, ssh_string_data(sig_blob), 20); + + sig->dsa_sig->r = make_string_bn(r); + ssh_string_free(r); + if (sig->dsa_sig->r == NULL) { + ssh_signature_free(sig); + return NULL; + } + + s = ssh_string_new(20); + if (s == NULL) { + ssh_signature_free(sig); + return NULL; + } + ssh_string_fill(s, (char *)ssh_string_data(sig_blob) + 20, 20); + + sig->dsa_sig->s = make_string_bn(s); + ssh_string_free(s); + if (sig->dsa_sig->s == NULL) { + ssh_signature_free(sig); + return NULL; + } + + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rsalen = RSA_size(pubkey->rsa); + + if (len > rsalen) { + ssh_pki_log("Signature is to big size: %lu", + (unsigned long)len); + ssh_signature_free(sig); + return NULL; + } + + if (len < rsalen) { + ssh_pki_log("RSA signature len %lu < %lu", + (unsigned long)len, (unsigned long)rsalen); + } + +#ifdef DEBUG_CRYPTO + ssh_pki_log("RSA signature len: %lu", (unsigned long)len); + ssh_print_hexa("RSA signature", ssh_string_data(sig_blob), len); +#endif + sig->rsa_sig = ssh_string_copy(sig_blob); + if (sig->rsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + sig->ecdsa_sig = ECDSA_SIG_new(); + if (sig->ecdsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + + { /* build ecdsa siganature */ + ssh_buffer b; + uint32_t rlen; + int rc; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + + rc = buffer_add_data(b, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + ssh_buffer_free(b); + ssh_signature_free(sig); + return NULL; + } + + r = buffer_get_ssh_string(b); + if (r == NULL) { + ssh_buffer_free(b); + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("r", ssh_string_data(r), ssh_string_len(r)); +#endif + + sig->ecdsa_sig->r = make_string_bn(r); + ssh_string_burn(r); + ssh_string_free(r); + if (sig->ecdsa_sig->r == NULL) { + ssh_buffer_free(b); + ssh_signature_free(sig); + return NULL; + } + + s = buffer_get_ssh_string(b); + rlen = buffer_get_rest_len(b); + ssh_buffer_free(b); + if (s == NULL) { + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + sig->ecdsa_sig->s = make_string_bn(s); + ssh_string_burn(s); + ssh_string_free(s); + if (sig->ecdsa_sig->s == NULL) { + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + ssh_pki_log("Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_signature_free(sig); + return NULL; + } + } + + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + ssh_pki_log("Unknown signature type"); + ssh_signature_free(sig); + return NULL; + } + + return sig; +} + +int pki_signature_verify(ssh_session session, + const ssh_signature sig, + const ssh_key key, + const unsigned char *hash, + size_t hlen) +{ + int rc; + + switch(key->type) { + case SSH_KEYTYPE_DSS: + rc = DSA_do_verify(hash, + hlen, + sig->dsa_sig, + key->dsa); + if (rc <= 0) { + ssh_set_error(session, + SSH_FATAL, + "DSA error: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = RSA_verify(NID_sha1, + hash, + hlen, + ssh_string_data(sig->rsa_sig), + ssh_string_len(sig->rsa_sig), + key->rsa); + if (rc <= 0) { + ssh_set_error(session, + SSH_FATAL, + "RSA error: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + rc = ECDSA_do_verify(hash, + hlen, + sig->ecdsa_sig, + key->ecdsa); + if (rc <= 0) { + ssh_set_error(session, + SSH_FATAL, + "ECDSA error: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + ssh_set_error(session, SSH_FATAL, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *hash, + size_t hlen) { + ssh_signature sig; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = privkey->type; + + switch(privkey->type) { + case SSH_KEYTYPE_DSS: + sig->dsa_sig = DSA_do_sign(hash, hlen, privkey->dsa); + if (sig->dsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("r", sig->dsa_sig->r); + ssh_print_bignum("s", sig->dsa_sig->s); +#endif + + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sig->rsa_sig = _RSA_do_sign(hash, hlen, privkey->rsa); + if (sig->rsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + sig->dsa_sig = NULL; + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + sig->ecdsa_sig = ECDSA_do_sign(hash, hlen, privkey->ecdsa); + if (sig->ecdsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + +# ifdef DEBUG_CRYPTO + ssh_print_bignum("r", sig->ecdsa_sig->r); + ssh_print_bignum("s", sig->ecdsa_sig->s); +# endif /* DEBUG_CRYPTO */ + + break; +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_UNKNOWN: + ssh_signature_free(sig); + return NULL; + } + + return sig; +} + +#ifdef WITH_SERVER +ssh_signature pki_do_sign_sessionid(const ssh_key key, + const unsigned char *hash, + size_t hlen) +{ + ssh_signature sig; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + sig->type = key->type; + + switch(key->type) { + case SSH_KEYTYPE_DSS: + sig->dsa_sig = DSA_do_sign(hash, hlen, key->dsa); + if (sig->dsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sig->rsa_sig = _RSA_do_sign(hash, hlen, key->rsa); + if (sig->rsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA: +#ifdef HAVE_OPENSSL_ECC + sig->ecdsa_sig = ECDSA_do_sign(hash, hlen, key->ecdsa); + if (sig->ecdsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + ssh_signature_free(sig); + return NULL; + } + + return sig; +} +#endif /* WITH_SERVER */ + +#endif /* _PKI_CRYPTO_H */ diff --git a/libssh/src/pki_gcrypt.c b/libssh/src/pki_gcrypt.c new file mode 100644 index 00000000..a44ed73a --- /dev/null +++ b/libssh/src/pki_gcrypt.c @@ -0,0 +1,1699 @@ +/* + * pki_gcrypt.c private and public key handling using gcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 Aris Adamantiadis + * Copyright (c) 2009-2011 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. + */ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" + +#define MAXLINESIZE 80 +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define DSA_HEADER_BEGIN "-----BEGIN DSA PRIVATE KEY-----" +#define DSA_HEADER_END "-----END DSA PRIVATE KEY-----" + +#define MAX_KEY_SIZE 32 +#define MAX_PASSPHRASE_SIZE 1024 +#define ASN1_INTEGER 2 +#define ASN1_SEQUENCE 48 +#define PKCS5_SALT_LEN 8 + +static int load_iv(const char *header, unsigned char *iv, int iv_len) { + int i; + int j; + int k; + + memset(iv, 0, iv_len); + for (i = 0; i < iv_len; i++) { + if ((header[2*i] >= '0') && (header[2*i] <= '9')) + j = header[2*i] - '0'; + else if ((header[2*i] >= 'A') && (header[2*i] <= 'F')) + j = header[2*i] - 'A' + 10; + else if ((header[2*i] >= 'a') && (header[2*i] <= 'f')) + j = header[2*i] - 'a' + 10; + else + return -1; + if ((header[2*i+1] >= '0') && (header[2*i+1] <= '9')) + k = header[2*i+1] - '0'; + else if ((header[2*i+1] >= 'A') && (header[2*i+1] <= 'F')) + k = header[2*i+1] - 'A' + 10; + else if ((header[2*i+1] >= 'a') && (header[2*i+1] <= 'f')) + k = header[2*i+1] - 'a' + 10; + else + return -1; + iv[i] = (j << 4) + k; + } + return 0; +} + +static uint32_t char_to_u32(unsigned char *data, uint32_t size) { + uint32_t ret; + uint32_t i; + + for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++]) + ; + return ret; +} + +static uint32_t asn1_get_len(ssh_buffer buffer) { + uint32_t len; + unsigned char tmp[4]; + + if (buffer_get_data(buffer,tmp,1) == 0) { + return 0; + } + + if (tmp[0] > 127) { + len = tmp[0] & 127; + if (len > 4) { + return 0; /* Length doesn't fit in u32. Can this really happen? */ + } + if (buffer_get_data(buffer,tmp,len) == 0) { + return 0; + } + len = char_to_u32(tmp, len); + } else { + len = char_to_u32(tmp, 1); + } + + return len; +} + +static ssh_string asn1_get_int(ssh_buffer buffer) { + ssh_string str; + unsigned char type; + uint32_t size; + + if (buffer_get_data(buffer, &type, 1) == 0 || type != ASN1_INTEGER) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + if (buffer_get_data(buffer, ssh_string_data(str), size) == 0) { + ssh_string_free(str); + return NULL; + } + + return str; +} + +static int asn1_check_sequence(ssh_buffer buffer) { + unsigned char *j = NULL; + unsigned char tmp; + int i; + uint32_t size; + uint32_t padding; + + if (buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { + return 0; + } + + size = asn1_get_len(buffer); + if ((padding = ssh_buffer_get_len(buffer) - buffer->pos - size) > 0) { + for (i = ssh_buffer_get_len(buffer) - buffer->pos - size, + j = (unsigned char*)ssh_buffer_get_begin(buffer) + size + buffer->pos; + i; + i--, j++) + { + if (*j != padding) { /* padding is allowed */ + return 0; /* but nothing else */ + } + } + } + + return 1; +} + +static int passphrase_to_key(char *data, unsigned int datalen, + unsigned char *salt, unsigned char *key, unsigned int keylen) { + MD5CTX md; + unsigned char digest[MD5_DIGEST_LEN] = {0}; + unsigned int i; + unsigned int j; + unsigned int md_not_empty; + + for (j = 0, md_not_empty = 0; j < keylen; ) { + md = md5_init(); + if (md == NULL) { + return -1; + } + + if (md_not_empty) { + md5_update(md, digest, MD5_DIGEST_LEN); + } else { + md_not_empty = 1; + } + + md5_update(md, data, datalen); + if (salt) { + md5_update(md, salt, PKCS5_SALT_LEN); + } + md5_final(digest, md); + + for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) { + if (key) { + key[j] = digest[i]; + } + } + } + + return 0; +} + +static int privatekey_decrypt(int algo, int mode, unsigned int key_len, + unsigned char *iv, unsigned int iv_len, + ssh_buffer data, ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + char passphrase[MAX_PASSPHRASE_SIZE] = {0}; + unsigned char key[MAX_KEY_SIZE] = {0}; + unsigned char *tmp = NULL; + gcry_cipher_hd_t cipher; + int rc = -1; + + if (!algo) { + return -1; + } + + if (cb) { + rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); + if (rc < 0) { + return -1; + } + } else if (cb == NULL && userdata != NULL) { + snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *) userdata); + } + + if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < 0) { + return -1; + } + + if (gcry_cipher_open(&cipher, algo, mode, 0) + || gcry_cipher_setkey(cipher, key, key_len) + || gcry_cipher_setiv(cipher, iv, iv_len) + || (tmp = malloc(ssh_buffer_get_len(data) * sizeof (char))) == NULL + || gcry_cipher_decrypt(cipher, tmp, ssh_buffer_get_len(data), + ssh_buffer_get_begin(data), ssh_buffer_get_len(data))) { + gcry_cipher_close(cipher); + return -1; + } + + memcpy(ssh_buffer_get_begin(data), tmp, ssh_buffer_get_len(data)); + + SAFE_FREE(tmp); + gcry_cipher_close(cipher); + + return 0; +} + +static int privatekey_dek_header(const char *header, unsigned int header_len, + int *algo, int *mode, unsigned int *key_len, unsigned char **iv, + unsigned int *iv_len) { + unsigned int iv_pos; + + if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12)) + { + *algo = GCRY_CIPHER_3DES; + iv_pos = 13; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 8; + } + else if (header_len > 8 && !strncmp("DES-CBC", header, 7)) + { + *algo = GCRY_CIPHER_DES; + iv_pos = 8; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 8; + *iv_len = 8; + } + else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11)) + { + *algo = GCRY_CIPHER_AES128; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 16; + *iv_len = 16; + } + else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11)) + { + *algo = GCRY_CIPHER_AES192; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 16; + } + else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11)) + { + *algo = GCRY_CIPHER_AES256; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 32; + *iv_len = 16; + } else { + return -1; + } + + *iv = malloc(*iv_len); + if (*iv == NULL) { + return -1; + } + + return load_iv(header + iv_pos, *iv, *iv_len); +} + +#define get_next_line(p, len) { \ + while(p[len] == '\n' || p[len] == '\r') /* skip empty lines */ \ + len++; \ + if(p[len] == '\0') /* EOL */ \ + len = -1; \ + else /* calculate length */ \ + for(p += len, len = 0; p[len] && p[len] != '\n' \ + && p[len] != '\r'; len++); \ + } + +static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, + ssh_auth_callback cb, void *userdata, const char *desc) { + ssh_buffer buffer = NULL; + ssh_buffer out = NULL; + const char *p; + unsigned char *iv = NULL; + const char *header_begin; + const char *header_end; + unsigned int header_begin_size; + unsigned int header_end_size; + unsigned int key_len = 0; + unsigned int iv_len = 0; + int algo = 0; + int mode = 0; + int len; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + switch(type) { + case SSH_KEYTYPE_DSS: + header_begin = DSA_HEADER_BEGIN; + header_end = DSA_HEADER_END; + break; + case SSH_KEYTYPE_RSA: + header_begin = RSA_HEADER_BEGIN; + header_end = RSA_HEADER_END; + break; + default: + ssh_buffer_free(buffer); + return NULL; + } + + header_begin_size = strlen(header_begin); + header_end_size = strlen(header_end); + + p = pkey; + len = 0; + get_next_line(p, len); + + while(len > 0 && strncmp(p, header_begin, header_begin_size)) { + /* skip line */ + get_next_line(p, len); + } + if(len < 0) { + /* no header found */ + return NULL; + } + /* skip header line */ + get_next_line(p, len); + + if (len > 11 && strncmp("Proc-Type: 4,ENCRYPTED", p, 11) == 0) { + /* skip line */ + get_next_line(p, len); + + if (len > 10 && strncmp("DEK-Info: ", p, 10) == 0) { + p += 10; + len = 0; + get_next_line(p, len); + if (privatekey_dek_header(p, len, &algo, &mode, &key_len, + &iv, &iv_len) < 0) { + ssh_buffer_free(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + ssh_buffer_free(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + if(len > 0) { + if (buffer_add_data(buffer, p, len) < 0) { + ssh_buffer_free(buffer); + SAFE_FREE(iv); + return NULL; + } + } + } + + get_next_line(p, len); + while(len > 0 && strncmp(p, header_end, header_end_size) != 0) { + if (buffer_add_data(buffer, p, len) < 0) { + ssh_buffer_free(buffer); + SAFE_FREE(iv); + return NULL; + } + get_next_line(p, len); + } + + if (len == -1 || strncmp(p, header_end, header_end_size) != 0) { + ssh_buffer_free(buffer); + SAFE_FREE(iv); + return NULL; + } + + if (buffer_add_data(buffer, "\0", 1) < 0) { + ssh_buffer_free(buffer); + SAFE_FREE(iv); + return NULL; + } + + out = base64_to_bin(ssh_buffer_get_begin(buffer)); + ssh_buffer_free(buffer); + if (out == NULL) { + SAFE_FREE(iv); + return NULL; + } + + if (algo) { + if (privatekey_decrypt(algo, mode, key_len, iv, iv_len, out, + cb, userdata, desc) < 0) { + ssh_buffer_free(out); + SAFE_FREE(iv); + return NULL; + } + } + SAFE_FREE(iv); + + return out; +} + +static int b64decode_rsa_privatekey(const char *pkey, gcry_sexp_t *r, + ssh_auth_callback cb, void *userdata, const char *desc) { + const unsigned char *data; + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string unused1 = NULL; + ssh_string unused2 = NULL; + ssh_string u = NULL; + ssh_string v = NULL; + ssh_buffer buffer = NULL; + int rc = 1; + + buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, cb, userdata, desc); + if (buffer == NULL) { + return 0; + } + + if (!asn1_check_sequence(buffer)) { + ssh_buffer_free(buffer); + return 0; + } + + v = asn1_get_int(buffer); + if (v == NULL) { + ssh_buffer_free(buffer); + return 0; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 0) { + ssh_buffer_free(buffer); + return 0; + } + + n = asn1_get_int(buffer); + e = asn1_get_int(buffer); + d = asn1_get_int(buffer); + q = asn1_get_int(buffer); + p = asn1_get_int(buffer); + unused1 = asn1_get_int(buffer); + unused2 = asn1_get_int(buffer); + u = asn1_get_int(buffer); + + ssh_buffer_free(buffer); + + if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || + unused1 == NULL || unused2 == NULL|| u == NULL) { + rc = 0; + goto error; + } + + if (gcry_sexp_build(r, NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e), ssh_string_data(e), + ssh_string_len(d), ssh_string_data(d), + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(u), ssh_string_data(u))) { + rc = 0; + } + +error: + ssh_string_free(n); + ssh_string_free(e); + ssh_string_free(d); + ssh_string_free(p); + ssh_string_free(q); + ssh_string_free(unused1); + ssh_string_free(unused2); + ssh_string_free(u); + ssh_string_free(v); + + return rc; +} + +static int b64decode_dsa_privatekey(const char *pkey, gcry_sexp_t *r, ssh_auth_callback cb, + void *userdata, const char *desc) { + const unsigned char *data; + ssh_buffer buffer = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string g = NULL; + ssh_string y = NULL; + ssh_string x = NULL; + ssh_string v = NULL; + int rc = 1; + + buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_DSS, cb, userdata, desc); + if (buffer == NULL) { + return 0; + } + + if (!asn1_check_sequence(buffer)) { + ssh_buffer_free(buffer); + return 0; + } + + v = asn1_get_int(buffer); + if (v == NULL) { + ssh_buffer_free(buffer); + return 0; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 0) { + ssh_buffer_free(buffer); + return 0; + } + + p = asn1_get_int(buffer); + q = asn1_get_int(buffer); + g = asn1_get_int(buffer); + y = asn1_get_int(buffer); + x = asn1_get_int(buffer); + ssh_buffer_free(buffer); + + if (p == NULL || q == NULL || g == NULL || y == NULL || x == NULL) { + rc = 0; + goto error; + } + + if (gcry_sexp_build(r, NULL, + "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))", + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(g), ssh_string_data(g), + ssh_string_len(y), ssh_string_data(y), + ssh_string_len(x), ssh_string_data(x))) { + rc = 0; + } + +error: + ssh_string_free(p); + ssh_string_free(q); + ssh_string_free(g); + ssh_string_free(y); + ssh_string_free(x); + ssh_string_free(v); + + return rc; +} + +#ifdef HAVE_GCRYPT_ECC +int pki_key_ecdsa_nid_from_name(const char *name) +{ + return -1; +} +#endif + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + gcry_sexp_t dsa = NULL; + gcry_sexp_t rsa = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type; + int valid; + + /* needed for gcrypt initialization */ + if (ssh_init() < 0) { + return NULL; + } + + type = pki_privatekey_type_from_string(b64_key); + if (type == SSH_KEYTYPE_UNKNOWN) { + ssh_pki_log("Unknown or invalid private key."); + return NULL; + } + + switch (type) { + case SSH_KEYTYPE_DSS: + if (passphrase == NULL) { + if (auth_fn) { + valid = b64decode_dsa_privatekey(b64_key, &dsa, auth_fn, + auth_data, "Passphrase for private key:"); + } else { + valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, NULL, + NULL); + } + } else { + valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, (void *) + passphrase, NULL); + } + + if (!valid) { + ssh_pki_log("Parsing private key"); + goto fail; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + if (passphrase == NULL) { + if (auth_fn) { + valid = b64decode_rsa_privatekey(b64_key, &rsa, auth_fn, + auth_data, "Passphrase for private key:"); + } else { + valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, + NULL); + } + } else { + valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, + (void *)passphrase, NULL); + } + + if (!valid) { + ssh_pki_log("Parsing private key"); + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + ssh_pki_log("Unkown or invalid private key type %d", type); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + key->dsa = dsa; + key->rsa = rsa; + + return key; +fail: + ssh_key_free(key); + gcry_sexp_release(dsa); + gcry_sexp_release(rsa); + + return NULL; +} + +int pki_pubkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey) { + gcry_sexp_build(&key->dsa, NULL, + "(public-key(dsa(p %b)(q %b)(g %b)(y %b)))", + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(g), ssh_string_data(g), + ssh_string_len(pubkey), ssh_string_data(pubkey)); + if (key->dsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n) { + gcry_sexp_build(&key->rsa, NULL, + "(public-key(rsa(n %b)(e %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e),ssh_string_data(e)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#ifdef HAVE_GCRYPT_ECC +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + return -1; +} +#endif + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new; + gcry_sexp_t sexp; + gcry_error_t err; + const char *tmp = NULL; + size_t size; + + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string g = NULL; + ssh_string y = NULL; + ssh_string x = NULL; + + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string d = NULL; + ssh_string u = NULL; + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + switch(key->type) { + case SSH_KEYTYPE_DSS: + sexp = gcry_sexp_find_token(key->dsa, "p", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + p = ssh_string_new(size); + if (p == NULL) { + goto fail; + } + ssh_string_fill(p, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->dsa, "q", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + q = ssh_string_new(size); + if (q == NULL) { + goto fail; + } + ssh_string_fill(q, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->dsa, "g", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + g = ssh_string_new(size); + if (g == NULL) { + goto fail; + } + ssh_string_fill(g, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->dsa, "y", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + y = ssh_string_new(size); + if (y == NULL) { + goto fail; + } + ssh_string_fill(y, (char *)tmp, size); + gcry_sexp_release(sexp); + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + sexp = gcry_sexp_find_token(key->dsa, "x", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + x = ssh_string_new(size); + if (x == NULL) { + goto fail; + } + ssh_string_fill(x, (char *)tmp, size); + gcry_sexp_release(sexp); + + err = gcry_sexp_build(&new->dsa, NULL, + "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))", + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(g), ssh_string_data(g), + ssh_string_len(y), ssh_string_data(y), + ssh_string_len(x), ssh_string_data(x)); + } else { + err = gcry_sexp_build(&new->dsa, NULL, + "(public-key(dsa(p %b)(q %b)(g %b)(y %b)))", + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(g), ssh_string_data(g), + ssh_string_len(y), ssh_string_data(y)); + } + if (err) { + goto fail; + } + + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(g); + ssh_string_free(g); + ssh_string_burn(y); + ssh_string_free(y); + ssh_string_burn(x); + ssh_string_free(x); + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sexp = gcry_sexp_find_token(key->rsa, "e", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + e = ssh_string_new(size); + if (e == NULL) { + goto fail; + } + ssh_string_fill(e, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->rsa, "n", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + n = ssh_string_new(size); + if (n == NULL) { + goto fail; + } + ssh_string_fill(n, (char *)tmp, size); + gcry_sexp_release(sexp); + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + sexp = gcry_sexp_find_token(key->rsa, "d", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + d = ssh_string_new(size); + if (e == NULL) { + goto fail; + } + ssh_string_fill(d, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->rsa, "p", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + p = ssh_string_new(size); + if (p == NULL) { + goto fail; + } + ssh_string_fill(p, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->rsa, "q", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + q = ssh_string_new(size); + if (q == NULL) { + goto fail; + } + ssh_string_fill(q, (char *)tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->rsa, "u", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + u = ssh_string_new(size); + if (u == NULL) { + goto fail; + } + ssh_string_fill(u, (char *)tmp, size); + gcry_sexp_release(sexp); + + err = gcry_sexp_build(&new->rsa, NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e), ssh_string_data(e), + ssh_string_len(d), ssh_string_data(d), + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(u), ssh_string_data(u)); + } else { + err = gcry_sexp_build(&new->rsa, NULL, + "(public-key(rsa(n %b)(e %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e), ssh_string_data(e)); + } + + if (err) { + goto fail; + } + + ssh_string_burn(e); + ssh_string_free(e); + ssh_string_burn(n); + ssh_string_free(n); + ssh_string_burn(d); + ssh_string_free(d); + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(u); + ssh_string_free(u); + + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + ssh_key_free(new); + return NULL; + } + + return new; +fail: + gcry_sexp_release(sexp); + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(g); + ssh_string_free(g); + ssh_string_burn(y); + ssh_string_free(y); + ssh_string_burn(x); + ssh_string_free(x); + + ssh_string_burn(e); + ssh_string_free(e); + ssh_string_burn(n); + ssh_string_free(n); + ssh_string_burn(u); + ssh_string_free(u); + + ssh_key_free(new); + + return NULL; +} + +static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int type){ + gcry_sexp_t parms; + int rc; + rc = gcry_sexp_build(&parms, + NULL, + "(genkey(%s(nbits %d)(transient-key)))", + type_s, + parameter); + if (rc != 0) + return SSH_ERROR; + if(type == SSH_KEYTYPE_RSA) + rc = gcry_pk_genkey(&key->rsa, parms); + else + rc = gcry_pk_genkey(&key->dsa, parms); + gcry_sexp_release(parms); + if (rc != 0) + return SSH_ERROR; + return SSH_OK; +} + +int pki_key_generate_rsa(ssh_key key, int parameter){ + return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); +} +int pki_key_generate_dss(ssh_key key, int parameter){ + return pki_key_generate(key, parameter, "dsa", SSH_KEYTYPE_DSS); +} + +#ifdef HAVE_GCRYPT_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) { + return -1; +} +#endif + +static int _bignum_cmp(const gcry_sexp_t s1, + const gcry_sexp_t s2, + const char *what) +{ + gcry_sexp_t sexp; + bignum b1; + bignum b2; + + sexp = gcry_sexp_find_token(s1, what, 0); + if (sexp == NULL) { + return 1; + } + b1 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b1 == NULL) { + return 1; + } + + sexp = gcry_sexp_find_token(s2, what, 0); + if (sexp == NULL) { + return 1; + } + b2 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b2 == NULL) { + return 1; + } + + if (bignum_cmp(b1, b2) != 0) { + return 1; + } + + return 0; +} + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + switch (k1->type) { + case SSH_KEYTYPE_DSS: + if (_bignum_cmp(k1->dsa, k2->dsa, "p") != 0) { + return 1; + } + + if (_bignum_cmp(k1->dsa, k2->dsa, "q") != 0) { + return 1; + } + + if (_bignum_cmp(k1->dsa, k2->dsa, "g") != 0) { + return 1; + } + + if (_bignum_cmp(k1->dsa, k2->dsa, "y") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->dsa, k2->dsa, "x") != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "n") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->rsa, k2->rsa, "d") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "p") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "q") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "u") != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + return 1; + } + + return 0; +} + +ssh_string pki_publickey_to_blob(const ssh_key key) +{ + ssh_buffer buffer; + ssh_string type_s; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + const char *tmp = NULL; + size_t size; + gcry_sexp_t sexp; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + ssh_buffer_free(buffer); + return NULL; + } + + rc = buffer_add_ssh_string(buffer, type_s); + string_free(type_s); + if (rc < 0) { + ssh_buffer_free(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_DSS: + sexp = gcry_sexp_find_token(key->dsa, "p", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + p = ssh_string_new(size); + if (p == NULL) { + goto fail; + } + ssh_string_fill(p, (char *) tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->dsa, "q", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + q = ssh_string_new(size); + if (q == NULL) { + goto fail; + } + ssh_string_fill(q, (char *) tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->dsa, "g", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + g = ssh_string_new(size); + if (g == NULL) { + goto fail; + } + ssh_string_fill(g, (char *) tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->dsa, "y", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + n = ssh_string_new(size); + if (n == NULL) { + goto fail; + } + ssh_string_fill(n, (char *) tmp, size); + + if (buffer_add_ssh_string(buffer, p) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, q) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, g) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, n) < 0) { + goto fail; + } + + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(g); + ssh_string_free(g); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(n); + ssh_string_free(n); + + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sexp = gcry_sexp_find_token(key->rsa, "e", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + e = ssh_string_new(size); + if (e == NULL) { + goto fail; + } + ssh_string_fill(e, (char *) tmp, size); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(key->rsa, "n", 0); + if (sexp == NULL) { + goto fail; + } + tmp = gcry_sexp_nth_data(sexp, 1, &size); + n = ssh_string_new(size); + if (n == NULL) { + goto fail; + } + ssh_string_fill(n, (char *) tmp, size); + gcry_sexp_release(sexp); + + if (buffer_add_ssh_string(buffer, e) < 0) { + goto fail; + } + if (buffer_add_ssh_string(buffer, n) < 0) { + goto fail; + } + + ssh_string_burn(e); + ssh_string_free(e); + ssh_string_burn(n); + ssh_string_free(n); + + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + goto fail; + } + + str = ssh_string_new(buffer_get_rest_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, buffer_get_rest(buffer), buffer_get_rest_len(buffer)); + if (rc < 0) { + goto fail; + } + ssh_buffer_free(buffer); + + return str; +fail: + ssh_buffer_free(buffer); + ssh_string_burn(str); + ssh_string_free(str); + ssh_string_burn(e); + ssh_string_free(e); + ssh_string_burn(p); + ssh_string_free(p); + ssh_string_burn(g); + ssh_string_free(g); + ssh_string_burn(q); + ssh_string_free(q); + ssh_string_burn(n); + ssh_string_free(n); + + return NULL; +} + +int pki_export_pubkey_rsa1(const ssh_key key, + const char *host, + char *rsa1, + size_t rsa1_len) +{ + gcry_sexp_t sexp; + int rsa_size; + bignum b; + char *e, *n; + + sexp = gcry_sexp_find_token(key->rsa, "e", 0); + if (sexp == NULL) { + return SSH_ERROR; + } + b = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b == NULL) { + return SSH_ERROR; + } + e = bignum_bn2dec(b); + + sexp = gcry_sexp_find_token(key->rsa, "n", 0); + if (sexp == NULL) { + SAFE_FREE(e); + return SSH_ERROR; + } + b = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b == NULL) { + SAFE_FREE(e); + return SSH_ERROR; + } + n = bignum_bn2dec(b); + + rsa_size = (gcry_pk_get_nbits(key->rsa) + 7) / 8; + + snprintf(rsa1, rsa1_len, + "%s %d %s %s\n", + host, rsa_size << 3, e, n); + SAFE_FREE(e); + SAFE_FREE(n); + + return SSH_OK; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + char buffer[40] = {0}; + const char *r = NULL; + const char *s = NULL; + gcry_sexp_t sexp; + size_t size = 0; + ssh_string sig_blob = NULL; + + switch(sig->type) { + case SSH_KEYTYPE_DSS: + sexp = gcry_sexp_find_token(sig->dsa_sig, "r", 0); + if (sexp == NULL) { + return NULL; + } + r = gcry_sexp_nth_data(sexp, 1, &size); + /* libgcrypt put 0 when first bit is set */ + if (*r == 0) { + size--; + r++; + } + memcpy(buffer, r + size - 20, 20); + gcry_sexp_release(sexp); + + sexp = gcry_sexp_find_token(sig->dsa_sig, "s", 0); + if (sexp == NULL) { + return NULL; + } + s = gcry_sexp_nth_data(sexp,1,&size); + if (*s == 0) { + size--; + s++; + } + memcpy(buffer+ 20, s + size - 20, 20); + gcry_sexp_release(sexp); + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); + if (sexp == NULL) { + return NULL; + } + s = gcry_sexp_nth_data(sexp, 1, &size); + if (*s == 0) { + size--; + s++; + } + + sig_blob = ssh_string_new(size); + if (sig_blob == NULL) { + return NULL; + } + ssh_string_fill(sig_blob, discard_const_p(char, s), size); + + gcry_sexp_release(sexp); + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + ssh_pki_log("Unknown signature key type: %d", sig->type); + return NULL; + break; + } + + return sig_blob; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type) +{ + ssh_signature sig; + gcry_error_t err; + size_t len; + size_t rsalen; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + + len = ssh_string_len(sig_blob); + + switch(type) { + case SSH_KEYTYPE_DSS: + /* 40 is the dual signature blob len. */ + if (len != 40) { + ssh_pki_log("Signature has wrong size: %lu", + (unsigned long)len); + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_pki_log("DSA signature len: %lu", (unsigned long)len); + ssh_print_hexa("DSA signature", ssh_string_data(sig_blob), len); +#endif + + err = gcry_sexp_build(&sig->dsa_sig, + NULL, + "(sig-val(dsa(r %b)(s %b)))", + 20, + ssh_string_data(sig_blob), + 20, + (unsigned char *)ssh_string_data(sig_blob) + 20); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; + + if (len > rsalen) { + ssh_pki_log("Signature is to big size: %lu", + (unsigned long)len); + ssh_signature_free(sig); + return NULL; + } + + if (len < rsalen) { + ssh_pki_log("RSA signature len %lu < %lu", + (unsigned long)len, (unsigned long)rsalen); + } + +#ifdef DEBUG_CRYPTO + ssh_pki_log("RSA signature len: %lu", (unsigned long)len); + ssh_print_hexa("RSA signature", ssh_string_data(sig_blob), len); +#endif + + err = gcry_sexp_build(&sig->rsa_sig, + NULL, + "(sig-val(rsa(s %b)))", + ssh_string_len(sig_blob), + ssh_string_data(sig_blob)); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + ssh_pki_log("Unknown signature type"); + return NULL; + } + + return sig; +} + +int pki_signature_verify(ssh_session session, + const ssh_signature sig, + const ssh_key key, + const unsigned char *hash, + size_t hlen) +{ + unsigned char ghash[hlen + 1]; + gcry_sexp_t sexp; + gcry_error_t err; + + switch(key->type) { + case SSH_KEYTYPE_DSS: + /* That is to mark the number as positive */ + if(hash[0] >= 0x80) { + memcpy(ghash + 1, hash, hlen); + ghash[0] = 0; + hash = ghash; + hlen += 1; + } + + err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); + if (err) { + ssh_set_error(session, + SSH_FATAL, + "DSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(sig->dsa_sig, sexp, key->dsa); + gcry_sexp_release(sexp); + if (err) { + ssh_set_error(session, SSH_FATAL, "Invalid DSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + ssh_set_error(session, + SSH_FATAL, + "DSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash sha1 %b))", + hlen, hash); + if (err) { + ssh_set_error(session, + SSH_FATAL, + "RSA hash error: %s", + gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(sig->rsa_sig, sexp, key->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_set_error(session, SSH_FATAL, "Invalid RSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + ssh_set_error(session, + SSH_FATAL, + "RSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + ssh_set_error(session, SSH_FATAL, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *hash, + size_t hlen) { + unsigned char ghash[hlen + 1]; + ssh_signature sig; + gcry_sexp_t sexp; + gcry_error_t err; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + sig->type = privkey->type; + + switch (privkey->type) { + case SSH_KEYTYPE_DSS: + /* That is to mark the number as positive */ + if(hash[0] >= 0x80) { + memcpy(ghash + 1, hash, hlen); + ghash[0] = 0; + hash = ghash; + hlen += 1; + } + + err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->dsa_sig, sexp, privkey->dsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash sha1 %b))", + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->rsa_sig, sexp, privkey->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + ssh_signature_free(sig); + return NULL; + } + + return sig; +} + +#ifdef WITH_SERVER +ssh_signature pki_do_sign_sessionid(const ssh_key key, + const unsigned char *hash, + size_t hlen) +{ + unsigned char ghash[hlen + 1]; + ssh_signature sig; + gcry_sexp_t sexp; + gcry_error_t err; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + sig->type = key->type; + + switch(key->type) { + case SSH_KEYTYPE_DSS: + /* That is to mark the number as positive */ + if(hash[0] >= 0x80) { + memcpy(ghash + 1, hash, hlen); + ghash[0] = 0; + hash = ghash; + hlen += 1; + } + + err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + err = gcry_pk_sign(&sig->dsa_sig, sexp, key->dsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash sha1 %b))", + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + err = gcry_pk_sign(&sig->rsa_sig, sexp, key->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return sig; +} +#endif /* WITH_SERVER */ + +#endif /* HAVE_LIBGCRYPT */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/poll.c b/libssh/src/poll.c new file mode 100644 index 00000000..932c4918 --- /dev/null +++ b/libssh/src/poll.c @@ -0,0 +1,926 @@ +/* + * poll.c - poll wrapper + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2010 by Andreas Schneider + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009 Aleksandar Kanchev + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + * + * vim: ts=2 sw=2 et cindent + */ + +#include "config.h" + +#include +#include + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/poll.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/misc.h" +#endif + + +#ifndef SSH_POLL_CTX_CHUNK +#define SSH_POLL_CTX_CHUNK 5 +#endif + +/** + * @defgroup libssh_poll The SSH poll functions. + * @ingroup libssh + * + * Add a generic way to handle sockets asynchronously. + * + * It's based on poll objects, each of which store a socket, its events and a + * callback, which gets called whenever an event is set. The poll objects are + * attached to a poll context, which should be allocated on per thread basis. + * + * Polling the poll context will poll all the attached poll objects and call + * their callbacks (handlers) if any of the socket events are set. This should + * be done within the main loop of an application. + * + * @{ + */ + +struct ssh_poll_handle_struct { + ssh_poll_ctx ctx; + union { + socket_t fd; + size_t idx; + } x; + short events; + ssh_poll_callback cb; + void *cb_data; +}; + +struct ssh_poll_ctx_struct { + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + size_t polls_allocated; + size_t polls_used; + size_t chunk_size; +}; + +#ifdef HAVE_POLL +#include + +void ssh_poll_init(void) { + return; +} + +void ssh_poll_cleanup(void) { + return; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { + return poll((struct pollfd *) fds, nfds, timeout); +} + +#else /* HAVE_POLL */ + +typedef int (*poll_fn)(ssh_pollfd_t *, nfds_t, int); +static poll_fn ssh_poll_emu; + +#include + +#ifdef _WIN32 +#ifndef STRICT +#define STRICT +#endif /* STRICT */ + +#include +#include +#include +#else /* _WIN32 */ +#include +#include +#include +#include +#endif /* _WIN32 */ + + +/* + * This is a poll(2)-emulation using select for systems not providing a native + * poll implementation. + * + * Keep in mind that select is terribly inefficient. The interface is simply not + * meant to be used with maximum descriptor value greater, say, 32 or so. With + * a value as high as 1024 on Linux you'll pay dearly in every single call. + * poll() will be orders of magnitude faster. + */ +static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { + fd_set readfds, writefds, exceptfds; + struct timeval tv, *ptv; + socket_t max_fd; + int rc; + nfds_t i; + + if (fds == NULL) { + errno = EFAULT; + return -1; + } + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + FD_ZERO (&exceptfds); + + /* compute fd_sets and find largest descriptor */ + for (rc = -1, max_fd = 0, i = 0; i < nfds; i++) { + if (fds[i].fd == SSH_INVALID_SOCKET) { + continue; + } +#ifndef _WIN32 + if (fds[i].fd >= FD_SETSIZE) { + rc = -1; + break; + } +#endif + + if (fds[i].events & (POLLIN | POLLRDNORM)) { + FD_SET (fds[i].fd, &readfds); + } + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { + FD_SET (fds[i].fd, &writefds); + } + if (fds[i].events & (POLLPRI | POLLRDBAND)) { + FD_SET (fds[i].fd, &exceptfds); + } + if (fds[i].fd > max_fd && + (fds[i].events & (POLLIN | POLLOUT | POLLPRI | + POLLRDNORM | POLLRDBAND | + POLLWRNORM | POLLWRBAND))) { + max_fd = fds[i].fd; + rc = 0; + } + } + + if (max_fd == SSH_INVALID_SOCKET || rc == -1) { + errno = EINVAL; + return -1; + } + + if (timeout < 0) { + ptv = NULL; + } else { + ptv = &tv; + if (timeout == 0) { + tv.tv_sec = 0; + tv.tv_usec = 0; + } else { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + } + } + + rc = select (max_fd + 1, &readfds, &writefds, &exceptfds, ptv); + if (rc < 0) { + return -1; + } + + for (rc = 0, i = 0; i < nfds; i++) + if (fds[i].fd >= 0) { + fds[i].revents = 0; + + if (FD_ISSET(fds[i].fd, &readfds)) { + int save_errno = errno; + char data[64] = {0}; + int ret; + + /* support for POLLHUP */ + ret = recv(fds[i].fd, data, 64, MSG_PEEK); +#ifdef _WIN32 + if ((ret == -1) && + (errno == WSAESHUTDOWN || errno == WSAECONNRESET || + errno == WSAECONNABORTED || errno == WSAENETRESET)) { +#else + if ((ret == -1) && + (errno == ESHUTDOWN || errno == ECONNRESET || + errno == ECONNABORTED || errno == ENETRESET)) { +#endif + fds[i].revents |= POLLHUP; + } else { + fds[i].revents |= fds[i].events & (POLLIN | POLLRDNORM); + } + + errno = save_errno; + } + if (FD_ISSET(fds[i].fd, &writefds)) { + fds[i].revents |= fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + + if (FD_ISSET(fds[i].fd, &exceptfds)) { + fds[i].revents |= fds[i].events & (POLLPRI | POLLRDBAND); + } + + if (fds[i].revents & ~POLLHUP) { + rc++; + } + } else { + fds[i].revents = POLLNVAL; + } + + return rc; +} + +void ssh_poll_init(void) { + ssh_poll_emu = bsd_poll; +} + +void ssh_poll_cleanup(void) { + ssh_poll_emu = bsd_poll; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { + return (ssh_poll_emu)(fds, nfds, timeout); +} + +#endif /* HAVE_POLL */ + +/** + * @brief Allocate a new poll object, which could be used within a poll context. + * + * @param fd Socket that will be polled. + * @param events Poll events that will be monitored for the socket. i.e. + * POLLIN, POLLPRI, POLLOUT + * @param cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, + * int revents, void *userdata); + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + * + * @return A new poll object, NULL on error + */ + +ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, + void *userdata) { + ssh_poll_handle p; + + p = malloc(sizeof(struct ssh_poll_handle_struct)); + if (p == NULL) { + return NULL; + } + ZERO_STRUCTP(p); + + p->x.fd = fd; + p->events = events; + p->cb = cb; + p->cb_data = userdata; + + return p; +} + + +/** + * @brief Free a poll object. + * + * @param p Pointer to an already allocated poll object. + */ + +void ssh_poll_free(ssh_poll_handle p) { + if(p->ctx != NULL){ + ssh_poll_ctx_remove(p->ctx,p); + p->ctx=NULL; + } + SAFE_FREE(p); +} + +/** + * @brief Get the poll context of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll context or NULL if the poll object isn't attached. + */ +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { + return p->ctx; +} + +/** + * @brief Get the events of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll events. + */ +short ssh_poll_get_events(ssh_poll_handle p) { + return p->events; +} + +/** + * @brief Set the events of a poll object. The events will also be propagated + * to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_set_events(ssh_poll_handle p, short events) { + p->events = events; + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].events = events; + } +} + +/** + * @brief Set the file descriptor of a poll object. The FD will also be propagated + * to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param fd New file descriptor. + */ +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].fd = fd; + } else { + p->x.fd = fd; + } +} + +/** + * @brief Add extra events to a poll object. Duplicates are ignored. + * The events will also be propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_add_events(ssh_poll_handle p, short events) { + ssh_poll_set_events(p, ssh_poll_get_events(p) | events); +} + +/** + * @brief Remove events from a poll object. Non-existent are ignored. + * The events will also be propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_remove_events(ssh_poll_handle p, short events) { + ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); +} + +/** + * @brief Get the raw socket of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Raw socket. + */ + +socket_t ssh_poll_get_fd(ssh_poll_handle p) { + if (p->ctx != NULL) { + return p->ctx->pollfds[p->x.idx].fd; + } + + return p->x.fd; +} +/** + * @brief Set the callback of a poll object. + * + * @param p Pointer to an already allocated poll object. + * @param cb Function to be called if any of the events are set. + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + */ +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) { + if (cb != NULL) { + p->cb = cb; + p->cb_data = userdata; + } +} + +/** + * @brief Create a new poll context. It could be associated with many poll object + * which are going to be polled at the same time as the poll context. You + * would need a single poll context per thread. + * + * @param chunk_size The size of the memory chunk that will be allocated, when + * more memory is needed. This is for efficiency reasons, + * i.e. don't allocate memory for each new poll object, but + * for the next 5. Set it to 0 if you want to use the + * library's default value. + */ +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { + ssh_poll_ctx ctx; + + ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + ZERO_STRUCTP(ctx); + + if (chunk_size == 0) { + chunk_size = SSH_POLL_CTX_CHUNK; + } + + ctx->chunk_size = chunk_size; + + return ctx; +} + +/** + * @brief Free a poll context. + * + * @param ctx Pointer to an already allocated poll context. + */ +void ssh_poll_ctx_free(ssh_poll_ctx ctx) { + if (ctx->polls_allocated > 0) { + while (ctx->polls_used > 0){ + ssh_poll_handle p = ctx->pollptrs[0]; + ssh_poll_ctx_remove(ctx, p); + } + + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } + + SAFE_FREE(ctx); +} + +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + + pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * new_size); + if (pollptrs == NULL) { + return -1; + } + + pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size); + if (pollfds == NULL) { + ctx->pollptrs = realloc(pollptrs, sizeof(ssh_poll_handle) * ctx->polls_allocated); + return -1; + } + + ctx->pollptrs = pollptrs; + ctx->pollfds = pollfds; + ctx->polls_allocated = new_size; + + return 0; +} + +/** + * @brief Add a poll object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { + socket_t fd; + + if (p->ctx != NULL) { + /* already attached to a context */ + return -1; + } + + if (ctx->polls_used == ctx->polls_allocated && + ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) { + return -1; + } + + fd = p->x.fd; + p->x.idx = ctx->polls_used++; + ctx->pollptrs[p->x.idx] = p; + ctx->pollfds[p->x.idx].fd = fd; + ctx->pollfds[p->x.idx].events = p->events; + ctx->pollfds[p->x.idx].revents = 0; + p->ctx = ctx; + + return 0; +} + +/** + * @brief Add a socket object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param s A SSH socket handle + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) { + ssh_poll_handle p_in, p_out; + int ret; + p_in=ssh_socket_get_poll_handle_in(s); + if(p_in==NULL) + return -1; + ret = ssh_poll_ctx_add(ctx,p_in); + if(ret != 0) + return ret; + p_out=ssh_socket_get_poll_handle_out(s); + if(p_in != p_out) + ret = ssh_poll_ctx_add(ctx,p_out); + return ret; +} + + +/** + * @brief Remove a poll object from a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + */ +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { + size_t i; + + i = p->x.idx; + p->x.fd = ctx->pollfds[i].fd; + p->ctx = NULL; + + ctx->polls_used--; + + /* fill the empty poll slot with the last one */ + if (ctx->polls_used > 0 && ctx->polls_used != i) { + ctx->pollfds[i] = ctx->pollfds[ctx->polls_used]; + ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used]; + ctx->pollptrs[i]->x.idx = i; + } + + /* this will always leave at least chunk_size polls allocated */ + if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) { + ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size); + } +} + +/** + * @brief Poll all the sockets associated through a poll object with a + * poll context. If any of the events are set after the poll, the + * call back function of the socket will be called. + * This function should be called once within the programs main loop. + * + * @param ctx Pointer to an already allocated poll context. + * @param timeout An upper limit on the time for which ssh_poll_ctx() will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK No error. + * SSH_ERROR Error happened during the poll. + * SSH_AGAIN Timeout occured + */ + +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) { + int rc; + int i, used; + ssh_poll_handle p; + socket_t fd; + int revents; + + if (!ctx->polls_used) + return SSH_ERROR; + + rc = ssh_poll(ctx->pollfds, ctx->polls_used, timeout); + if(rc < 0) + return SSH_ERROR; + if (rc == 0) + return SSH_AGAIN; + used = ctx->polls_used; + for (i = 0; i < used && rc > 0; ) { + if (!ctx->pollfds[i].revents) { + i++; + } else { + int ret; + + p = ctx->pollptrs[i]; + fd = ctx->pollfds[i].fd; + revents = ctx->pollfds[i].revents; + + if (p->cb && (ret = p->cb(p, fd, revents, p->cb_data)) < 0) { + if (ret == -2) { + return -1; + } + /* the poll was removed, reload the used counter and start again */ + used = ctx->polls_used; + i=0; + } else { + ctx->pollfds[i].revents = 0; + i++; + } + + rc--; + } + } + + return rc; +} + +/** + * @internal + * @brief gets the default poll structure for the current session, + * when used in blocking mode. + * @param session SSH session + * @returns the default ssh_poll_ctx + */ +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session){ + if(session->default_poll_ctx != NULL) + return session->default_poll_ctx; + /* 2 is enough for the default one */ + session->default_poll_ctx = ssh_poll_ctx_new(2); + return session->default_poll_ctx; +} + +/* public event API */ + +struct ssh_event_fd_wrapper { + ssh_event_callback cb; + void * userdata; +}; + +struct ssh_event_struct { + ssh_poll_ctx ctx; +#ifdef WITH_SERVER + struct ssh_list *sessions; +#endif +}; + +/** + * @brief Create a new event context. It could be associated with many + * ssh_session objects and socket fd which are going to be polled at the + * same time as the event context. You would need a single event context + * per thread. + * + * @return The ssh_event object on success, NULL on failure. + */ +ssh_event ssh_event_new(void) { + ssh_event event; + + event = malloc(sizeof(struct ssh_event_struct)); + if (event == NULL) { + return NULL; + } + ZERO_STRUCTP(event); + + event->ctx = ssh_poll_ctx_new(2); + if(event->ctx == NULL) { + free(event); + return NULL; + } + +#ifdef WITH_SERVER + event->sessions = ssh_list_new(); + if(event->sessions == NULL) { + ssh_poll_ctx_free(event->ctx); + free(event); + return NULL; + } +#endif + + return event; +} + +static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, + void *userdata) { + struct ssh_event_fd_wrapper *pw = (struct ssh_event_fd_wrapper *)userdata; + + (void)p; + if(pw->cb != NULL) { + return pw->cb(fd, revents, pw->userdata); + } + return 0; +} + +/** + * @brief Add a fd to the event and assign it a callback, + * when used in blocking mode. + * @param event The ssh_event + * @param fd Socket that will be polled. + * @param events Poll events that will be monitored for the socket. i.e. + * POLLIN, POLLPRI, POLLOUT + * @param cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_event_callback)(socket_t fd, int revents, + * void *userdata); + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata) { + ssh_poll_handle p; + struct ssh_event_fd_wrapper *pw; + + if(event == NULL || event->ctx == NULL || cb == NULL + || fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + pw = malloc(sizeof(struct ssh_event_fd_wrapper)); + if(pw == NULL) { + return SSH_ERROR; + } + + pw->cb = cb; + pw->userdata = userdata; + + /* pw is freed by ssh_event_remove_fd */ + p = ssh_poll_new(fd, events, ssh_event_fd_wrapper_callback, pw); + if(p == NULL) { + free(pw); + return SSH_ERROR; + } + + if(ssh_poll_ctx_add(event->ctx, p) < 0) { + free(pw); + ssh_poll_free(p); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief remove the poll handle from session and assign them to a event, + * when used in blocking mode. + * + * @param event The ssh_event object + * @param session The session to add to the event. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_session(ssh_event event, ssh_session session) { + unsigned int i; + ssh_poll_handle p; +#ifdef WITH_SERVER + struct ssh_iterator *iterator; +#endif + + if(event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + if(session->default_poll_ctx == NULL) { + return SSH_ERROR; + } + for(i = 0; i < session->default_poll_ctx->polls_used; i++) { + p = session->default_poll_ctx->pollptrs[i]; + ssh_poll_ctx_remove(session->default_poll_ctx, p); + ssh_poll_ctx_add(event->ctx, p); + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while(iterator != NULL) { + if((ssh_session)iterator->data == session) { + /* allow only one instance of this session */ + return SSH_OK; + } + iterator = iterator->next; + } + if(ssh_list_append(event->sessions, session) == SSH_ERROR) { + return SSH_ERROR; + } +#endif + return SSH_OK; +} + +/** + * @brief Poll all the sockets and sessions associated through an event object. + * If any of the events are set after the poll, the + * call back functions of the sessions or sockets will be called. + * This function should be called once within the programs main loop. + * + * @param event The ssh_event object to poll. + * @param timeout An upper limit on the time for which the poll will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK No error. + * SSH_ERROR Error happened during the poll. + */ +int ssh_event_dopoll(ssh_event event, int timeout) { + int rc; + + if(event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + rc = ssh_poll_ctx_dopoll(event->ctx, timeout); + return rc; +} + +/** + * @brief Remove a socket fd from an event context. + * + * @param event The ssh_event object. + * @param fd The fd to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_fd(ssh_event event, socket_t fd) { + register size_t i, used; + int rc = SSH_ERROR; + + if(event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + if(fd == event->ctx->pollfds[i].fd) { + ssh_poll_handle p = event->ctx->pollptrs[i]; + struct ssh_event_fd_wrapper *pw = p->cb_data; + + ssh_poll_ctx_remove(event->ctx, p); + free(pw); + ssh_poll_free(p); + rc = SSH_OK; + /* restart the loop */ + used = event->ctx->polls_used; + i = 0; + } + } + + return rc; +} + +/** + * @brief Remove a session object from an event context. + * + * @param event The ssh_event object. + * @param session The session to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_session(ssh_event event, ssh_session session) { + ssh_poll_handle p; + register size_t i, used; + int rc = SSH_ERROR; + socket_t session_fd; +#ifdef WITH_SERVER + struct ssh_iterator *iterator; +#endif + + if(event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + + session_fd = ssh_get_fd(session); + used = event->ctx->polls_used; + for(i = 0; i < used; i++) { + if(session_fd == event->ctx->pollfds[i].fd) { + p = event->ctx->pollptrs[i]; + ssh_poll_ctx_remove(event->ctx, p); + ssh_poll_ctx_add(session->default_poll_ctx, p); + rc = SSH_OK; + } + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while(iterator != NULL) { + if((ssh_session)iterator->data == session) { + ssh_list_remove(event->sessions, iterator); + /* there should be only one instance of this session */ + break; + } + iterator = iterator->next; + } +#endif + + return rc; +} + +/** + * @brief Free an event context. + * + * @param event The ssh_event object to free. + * Note: you have to manually remove sessions and socket + * fds before freeing the event object. + * + */ +void ssh_event_free(ssh_event event) { + if(event == NULL) { + return; + } + if(event->ctx != NULL) { + ssh_poll_ctx_free(event->ctx); + } +#ifdef WITH_SERVER + if(event->sessions != NULL) { + ssh_list_free(event->sessions); + } +#endif + free(event); +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/scp.c b/libssh/src/scp.c new file mode 100644 index 00000000..df4f5753 --- /dev/null +++ b/libssh/src/scp.c @@ -0,0 +1,809 @@ +/* + * scp - SSH scp wrapper functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/scp.h" + +/** + * @defgroup libssh_scp The SSH scp functions + * @ingroup libssh + * + * SCP protocol over SSH functions + * + * @{ + */ + +/** + * @brief Create a new scp session. + * + * @param[in] session The SSH session to use. + * + * @param[in] mode One of SSH_SCP_WRITE or SSH_SCP_READ, depending if you + * need to drop files remotely or read them. + * It is not possible to combine read and write. + * SSH_SCP_RECURSIVE Flag can be or'ed to this to indicate + * that you're going to use recursion. Browsing through + * directories is not possible without this. + * + * @param[in] location The directory in which write or read will be done. Any + * push or pull will be relative to this place. + * This can also be a pattern of files to download (read). + * + * @returns A ssh_scp handle, NULL if the creation was impossible. + */ +ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location){ + ssh_scp scp=malloc(sizeof(struct ssh_scp_struct)); + if(scp == NULL){ + ssh_set_error(session,SSH_FATAL,"Error allocating memory for ssh_scp"); + return NULL; + } + ZERO_STRUCTP(scp); + if((mode&~SSH_SCP_RECURSIVE) != SSH_SCP_WRITE && (mode &~SSH_SCP_RECURSIVE) != SSH_SCP_READ){ + ssh_set_error(session,SSH_FATAL,"Invalid mode %d for ssh_scp_new()",mode); + ssh_scp_free(scp); + return NULL; + } + scp->location=strdup(location); + if (scp->location == NULL) { + ssh_set_error(session,SSH_FATAL,"Error allocating memory for ssh_scp"); + ssh_scp_free(scp); + return NULL; + } + scp->session=session; + scp->mode=mode & ~SSH_SCP_RECURSIVE; + scp->recursive = (mode & SSH_SCP_RECURSIVE) != 0; + scp->channel=NULL; + scp->state=SSH_SCP_NEW; + return scp; +} + +int ssh_scp_init(ssh_scp scp){ + int r; + char execbuffer[1024]; + uint8_t code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_NEW){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_init called under invalid state"); + return SSH_ERROR; + } + ssh_log(scp->session,SSH_LOG_PROTOCOL,"Initializing scp session %s %son location '%s'", + scp->mode==SSH_SCP_WRITE?"write":"read", + scp->recursive?"recursive ":"", + scp->location); + scp->channel=ssh_channel_new(scp->session); + if(scp->channel == NULL){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r= ssh_channel_open_session(scp->channel); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(scp->mode == SSH_SCP_WRITE) + snprintf(execbuffer,sizeof(execbuffer),"scp -t %s %s", + scp->recursive ? "-r":"", scp->location); + else + snprintf(execbuffer,sizeof(execbuffer),"scp -f %s %s", + scp->recursive ? "-r":"", scp->location); + if(ssh_channel_request_exec(scp->channel,execbuffer) == SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(scp->mode == SSH_SCP_WRITE){ + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + } else { + ssh_channel_write(scp->channel,"",1); + } + if(scp->mode == SSH_SCP_WRITE) + scp->state=SSH_SCP_WRITE_INITED; + else + scp->state=SSH_SCP_READ_INITED; + return SSH_OK; +} + +int ssh_scp_close(ssh_scp scp){ + char buffer[128]; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->channel != NULL){ + if(ssh_channel_send_eof(scp->channel) == SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + /* avoid situations where data are buffered and + * not yet stored on disk. This can happen if the close is sent + * before we got the EOF back + */ + while(!ssh_channel_is_eof(scp->channel)){ + err=ssh_channel_read(scp->channel,buffer,sizeof(buffer),0); + if(err==SSH_ERROR || err==0) + break; + } + if(ssh_channel_close(scp->channel) == SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + ssh_channel_free(scp->channel); + scp->channel=NULL; + } + scp->state=SSH_SCP_NEW; + return SSH_OK; +} + +void ssh_scp_free(ssh_scp scp){ + if(scp==NULL) + return; + if(scp->state != SSH_SCP_NEW) + ssh_scp_close(scp); + if(scp->channel) + ssh_channel_free(scp->channel); + SAFE_FREE(scp->location); + SAFE_FREE(scp->request_name); + SAFE_FREE(scp->warning); + SAFE_FREE(scp); +} + +/** + * @brief Create a directory in a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] dirname The name of the directory being created. + * + * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. + * + * @returns SSH_OK if the directory has been created, SSH_ERROR if + * an error occured. + * + * @see ssh_scp_leave_directory() + */ +int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode){ + char buffer[1024]; + int r; + uint8_t code; + char *dir; + char *perms; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_push_directory called under invalid state"); + return SSH_ERROR; + } + dir=ssh_basename(dirname); + perms=ssh_scp_string_mode(mode); + snprintf(buffer, sizeof(buffer), "D%s 0 %s\n", perms, dir); + SAFE_FREE(dir); + SAFE_FREE(perms); + r=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Leave a directory. + * + * @returns SSH_OK if the directory has been left,SSH_ERROR if an + * error occured. + * + * @see ssh_scp_push_directory() + */ + int ssh_scp_leave_directory(ssh_scp scp){ + char buffer[]="E\n"; + int r; + uint8_t code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_leave_directory called under invalid state"); + return SSH_ERROR; + } + r=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit size. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occured. + * + * @see ssh_scp_push_file() + */ +int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int mode){ + char buffer[1024]; + int r; + uint8_t code; + char *file; + char *perms; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_push_file called under invalid state"); + return SSH_ERROR; + } + file=ssh_basename(filename); + perms=ssh_scp_string_mode(mode); + ssh_log(scp->session,SSH_LOG_PROTOCOL,"SCP pushing file %s, size %" PRIu64 " with permissions '%s'",file,size,perms); + snprintf(buffer, sizeof(buffer), "C%s %" PRIu64 " %s\n", perms, size, file); + SAFE_FREE(file); + SAFE_FREE(perms); + r=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + scp->filelen = size; + scp->processed = 0; + scp->state=SSH_SCP_WRITE_WRITING; + return SSH_OK; +} + +/** + * @brief Initialize the sending of a file to a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occured. + */ +int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode){ + return ssh_scp_push_file64(scp, filename, (uint64_t) size, mode); +} + +/** + * @internal + * + * @brief Wait for a response of the scp server. + * + * @param[in] scp The scp handle. + * + * @param[out] response A pointer where the response message must be copied if + * any. This pointer must then be free'd. + * + * @returns The return code, SSH_ERROR a error occured. + */ +int ssh_scp_response(ssh_scp scp, char **response){ + unsigned char code; + int r; + char msg[128]; + if(scp==NULL) + return SSH_ERROR; + r=ssh_channel_read(scp->channel,&code,1,0); + if(r == SSH_ERROR) + return SSH_ERROR; + if(code == 0) + return 0; + if(code > 2){ + ssh_set_error(scp->session,SSH_FATAL, "SCP: invalid status code %ud received", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_scp_read_string(scp,msg,sizeof(msg)); + if(r==SSH_ERROR) + return r; + /* Warning */ + if(code == 1){ + ssh_set_error(scp->session,SSH_REQUEST_DENIED, "SCP: Warning: status code 1 received: %s", msg); + ssh_log(scp->session,SSH_LOG_RARE,"SCP: Warning: status code 1 received: %s", msg); + if(response) + *response=strdup(msg); + return 1; + } + if(code == 2){ + ssh_set_error(scp->session,SSH_FATAL, "SCP: Error: status code 2 received: %s", msg); + if(response) + *response=strdup(msg); + return 2; + } + /* Not reached */ + return SSH_ERROR; +} + +/** + * @brief Write into a remote scp file. + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The buffer to write. + * + * @param[in] len The number of bytes to write. + * + * @returns SSH_OK if the write was successful, SSH_ERROR an error + * occured while writing. + */ +int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len){ + int w; + int r; + uint8_t code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_WRITING){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_write called under invalid state"); + return SSH_ERROR; + } + if(scp->processed + len > scp->filelen) + len = (size_t) (scp->filelen - scp->processed); + /* hack to avoid waiting for window change */ + r = ssh_channel_poll(scp->channel, 0); + if (r == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + w=ssh_channel_write(scp->channel,buffer,len); + if(w != SSH_ERROR) + scp->processed += w; + else { + scp->state=SSH_SCP_ERROR; + //return=channel_get_exit_status(scp->channel); + return SSH_ERROR; + } + /* Far end sometimes send a status message, which we need to read + * and handle */ + r = ssh_channel_poll(scp->channel,0); + if(r > 0){ + r = ssh_channel_read(scp->channel, &code, 1, 0); + if(r == SSH_ERROR){ + return SSH_ERROR; + } + if(code == 1 || code == 2){ + ssh_set_error(scp->session,SSH_REQUEST_DENIED, "SCP: Error: status code %i received", code); + return SSH_ERROR; + } + } + /* Check if we arrived at end of file */ + if(scp->processed == scp->filelen) { + code = 0; + w = ssh_channel_write(scp->channel, &code, 1); + if(w == SSH_ERROR){ + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + scp->processed=scp->filelen=0; + scp->state=SSH_SCP_WRITE_INITED; + } + return SSH_OK; +} + +/** + * @brief Read a string on a channel, terminated by '\n' + * + * @param[in] scp The scp handle. + * + * @param[out] buffer A pointer to a buffer to place the string. + * + * @param[in] len The size of the buffer in bytes. If the string is bigger + * than len-1, only len-1 bytes are read and the string is + * null-terminated. + * + * @returns SSH_OK if the string was read, SSH_ERROR if an error + * occured while reading. + */ +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len){ + size_t r=0; + int err=SSH_OK; + if(scp==NULL) + return SSH_ERROR; + while(rchannel,&buffer[r],1,0); + if(err==SSH_ERROR){ + break; + } + if(err==0){ + ssh_set_error(scp->session,SSH_FATAL,"End of file while reading string"); + err=SSH_ERROR; + break; + } + r++; + if(buffer[r-1] == '\n') + break; + } + buffer[r]=0; + return err; +} + +/** + * @brief Wait for a scp request (file, directory). + * + * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending + * a file + * SSH_SCP_REQUEST_NEWDIR: The other side is sending + * a directory + * SSH_SCP_REQUEST_ENDDIR: The other side has + * finished with the current + * directory + * SSH_SCP_REQUEST_WARNING: The other side sent us a warning + * SSH_SCP_REQUEST_EOF: The other side finished sending us + * files and data. + * SSH_ERROR: Some error happened + * + * @see ssh_scp_read() + * @see ssh_scp_deny_request() + * @see ssh_scp_accept_request() + * @see ssh_scp_request_get_warning() + */ +int ssh_scp_pull_request(ssh_scp scp){ + char buffer[4096] = {0}; + char *mode=NULL; + char *p,*tmp; + uint64_t size; + char *name=NULL; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_READ_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_pull_request called under invalid state"); + return SSH_ERROR; + } + err=ssh_scp_read_string(scp,buffer,sizeof(buffer)); + if(err==SSH_ERROR){ + if(ssh_channel_is_eof(scp->channel)){ + scp->state=SSH_SCP_TERMINATED; + return SSH_SCP_REQUEST_EOF; + } + return err; + } + p=strchr(buffer,'\n'); + if(p!=NULL) + *p='\0'; + ssh_log(scp->session,SSH_LOG_PROTOCOL,"Received SCP request: '%s'",buffer); + switch(buffer[0]){ + case 'C': + /* File */ + case 'D': + /* Directory */ + p=strchr(buffer,' '); + if(p==NULL) + goto error; + *p='\0'; + p++; + //mode=strdup(&buffer[1]); + scp->request_mode=ssh_scp_integer_mode(&buffer[1]); + tmp=p; + p=strchr(p,' '); + if(p==NULL) + goto error; + *p=0; + size = strtoull(tmp,NULL,10); + p++; + name=strdup(p); + SAFE_FREE(scp->request_name); + scp->request_name=name; + if(buffer[0]=='C'){ + scp->filelen=size; + scp->request_type=SSH_SCP_REQUEST_NEWFILE; + } else { + scp->filelen='0'; + scp->request_type=SSH_SCP_REQUEST_NEWDIR; + } + scp->state=SSH_SCP_READ_REQUESTED; + scp->processed = 0; + return scp->request_type; + break; + case 'E': + scp->request_type=SSH_SCP_REQUEST_ENDDIR; + ssh_channel_write(scp->channel,"",1); + return scp->request_type; + case 0x1: + ssh_set_error(scp->session,SSH_REQUEST_DENIED,"SCP: Warning: %s",&buffer[1]); + scp->request_type=SSH_SCP_REQUEST_WARNING; + SAFE_FREE(scp->warning); + scp->warning=strdup(&buffer[1]); + return scp->request_type; + case 0x2: + ssh_set_error(scp->session,SSH_FATAL,"SCP: Error: %s",&buffer[1]); + return SSH_ERROR; + case 'T': + /* Timestamp */ + default: + ssh_set_error(scp->session,SSH_FATAL,"Unhandled message: (%d)%s",buffer[0],buffer); + return SSH_ERROR; + } + + /* a parsing error occured */ + error: + SAFE_FREE(name); + SAFE_FREE(mode); + ssh_set_error(scp->session,SSH_FATAL,"Parsing error while parsing message: %s",buffer); + return SSH_ERROR; +} + +/** + * @brief Deny the transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * @param[in] reason A nul-terminated string with a human-readable + * explanation of the deny. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if the sending + * the message failed, or sending it in a bad state. + */ +int ssh_scp_deny_request(ssh_scp scp, const char *reason){ + char buffer[4096]; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_READ_REQUESTED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + snprintf(buffer,sizeof(buffer),"%c%s\n",2,reason); + err=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(err==SSH_ERROR) { + return SSH_ERROR; + } + else { + scp->state=SSH_SCP_READ_INITED; + return SSH_OK; + } +} + +/** + * @brief Accepts transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if sending the + * message failed, or sending it in a bad state. + */ +int ssh_scp_accept_request(ssh_scp scp){ + char buffer[]={0x00}; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_READ_REQUESTED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + err=ssh_channel_write(scp->channel,buffer,1); + if(err==SSH_ERROR) { + return SSH_ERROR; + } + if(scp->request_type==SSH_SCP_REQUEST_NEWFILE) + scp->state=SSH_SCP_READ_READING; + else + scp->state=SSH_SCP_READ_INITED; + return SSH_OK; +} + +/** @brief Read from a remote scp file + * @param[in] scp The scp handle. + * + * @param[in] buffer The destination buffer. + * + * @param[in] size The size of the buffer. + * + * @returns The nNumber of bytes read, SSH_ERROR if an error occured + * while reading. + */ +int ssh_scp_read(ssh_scp scp, void *buffer, size_t size){ + int r; + int code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state == SSH_SCP_READ_REQUESTED && scp->request_type == SSH_SCP_REQUEST_NEWFILE){ + r=ssh_scp_accept_request(scp); + if(r==SSH_ERROR) + return r; + } + if(scp->state != SSH_SCP_READ_READING){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_read called under invalid state"); + return SSH_ERROR; + } + if(scp->processed + size > scp->filelen) + size = (size_t) (scp->filelen - scp->processed); + if(size > 65536) + size=65536; /* avoid too large reads */ + r=ssh_channel_read(scp->channel,buffer,size,0); + if(r != SSH_ERROR) + scp->processed += r; + else { + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + /* Check if we arrived at end of file */ + if(scp->processed == scp->filelen) { + scp->processed=scp->filelen=0; + ssh_channel_write(scp->channel,"",1); + code=ssh_scp_response(scp,NULL); + if(code == 0){ + scp->state=SSH_SCP_READ_INITED; + return r; + } + if(code==1){ + scp->state=SSH_SCP_READ_INITED; + return SSH_ERROR; + } + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + return r; +} + +/** + * @brief Get the name of the directory or file being pushed from the other + * party. + * + * @returns The file name, NULL on error. The string should not be + * freed. + */ +const char *ssh_scp_request_get_filename(ssh_scp scp){ + if(scp==NULL) + return NULL; + return scp->request_name; +} + +/** + * @brief Get the permissions of the directory or file being pushed from the + * other party. + * + * @returns The UNIX permission, e.g 0644, -1 on error. + */ +int ssh_scp_request_get_permissions(ssh_scp scp){ + if(scp==NULL) + return -1; + return scp->request_mode; +} + +/** @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + * @warning The real size may not fit in a 32 bits field and may + * be truncated. + * @see ssh_scp_request_get_size64() + */ +size_t ssh_scp_request_get_size(ssh_scp scp){ + if(scp==NULL) + return 0; + return scp->filelen; +} + +/** @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + */ +uint64_t ssh_scp_request_get_size64(ssh_scp scp){ + if(scp==NULL) + return 0; + return scp->filelen; +} + +/** + * @brief Convert a scp text mode to an integer. + * + * @param[in] mode The mode to convert, e.g. "0644". + * + * @returns An integer value, e.g. 420 for "0644". + */ +int ssh_scp_integer_mode(const char *mode){ + int value=strtoul(mode,NULL,8) & 0xffff; + return value; +} + +/** + * @brief Convert a unix mode into a scp string. + * + * @param[in] mode The mode to convert, e.g. 420 or 0644. + * + * @returns A pointer to a malloc'ed string containing the scp mode, + * e.g. "0644". + */ +char *ssh_scp_string_mode(int mode){ + char buffer[16]; + snprintf(buffer,sizeof(buffer),"%.4o",mode); + return strdup(buffer); +} + +/** + * @brief Get the warning string from a scp handle. + * + * @param[in] scp The scp handle. + * + * @returns A warning string, or NULL on error. The string should + * not be freed. + */ +const char *ssh_scp_request_get_warning(ssh_scp scp){ + if(scp==NULL) + return NULL; + return scp->warning; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/server.c b/libssh/src/server.c new file mode 100644 index 00000000..db8f8152 --- /dev/null +++ b/libssh/src/server.c @@ -0,0 +1,1205 @@ +/* + * server.c - functions for creating a SSH server + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include + + /* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +# ifdef HAVE_WSPIAPI_H +# include +# endif +#else +# include +#endif + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/kex.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/dh.h" +#include "libssh/messages.h" +#include "libssh/options.h" + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +static int dh_handshake_server(ssh_session session); + + +/** + * @addtogroup libssh_server + * + * @{ + */ + +/** @internal + * This functions sets the Key Exchange protocols to be accepted + * by the server. They depend on + * -What the user asked (via options) + * -What is available (keys) + * It should then accept the intersection of what the user asked + * and what is available, and return an error if nothing matches + */ + +static int server_set_kex(ssh_session session) { + struct ssh_kex_struct *server = &session->next_crypto->server_kex; + int i, j, rc; + const char *wanted; + char hostkeys[64] = {0}; + enum ssh_keytypes_e keytype; + size_t len; + + ZERO_STRUCTP(server); + ssh_get_random(server->cookie, 16, 0); + +#ifdef HAVE_ECC + if (session->srv.ecdsa_key != NULL) { + snprintf(hostkeys, sizeof(hostkeys), + "%s", session->srv.ecdsa_key->type_c); + } +#endif + if (session->srv.dsa_key != NULL) { + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.dsa_key); + + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); + } + if (session->srv.rsa_key != NULL) { + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.rsa_key); + + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); + } + + if (strlen(hostkeys) == 0) { + return -1; + } + + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + hostkeys[0] == ',' ? hostkeys + 1 : hostkeys); + if (rc < 0) { + return -1; + } + + for (i = 0; i < 10; i++) { + if ((wanted = session->opts.wanted_methods[i]) == NULL) { + wanted = ssh_kex_get_supported_method(i); + } + server->methods[i] = strdup(wanted); + if (server->methods[i] == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + } + + return 0; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXDH_INIT packet and complete + * key exchange + **/ +static int ssh_server_kexdh_init(ssh_session session, ssh_buffer packet){ + ssh_string e; + e = buffer_get_ssh_string(packet); + if (e == NULL) { + ssh_set_error(session, SSH_FATAL, "No e number in client request"); + return -1; + } + if (dh_import_e(session, e) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot import e number"); + session->session_state=SSH_SESSION_STATE_ERROR; + } else { + session->dh_handshake_state=DH_STATE_INIT_SENT; + dh_handshake_server(session); + } + ssh_string_free(e); + return SSH_OK; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ + int rc; + (void)type; + (void)user; + enter_function(); + ssh_log(session,SSH_LOG_PACKET,"Received SSH_MSG_KEXDH_INIT"); + if(session->dh_handshake_state != DH_STATE_INIT){ + ssh_log(session,SSH_LOG_RARE,"Invalid state for SSH_MSG_KEXDH_INIT"); + goto error; + } + switch(session->next_crypto->kex_type){ + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + rc=ssh_server_kexdh_init(session, packet); + break; + #ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + rc = ssh_server_ecdh_init(session, packet); + break; + #endif + default: + ssh_set_error(session,SSH_FATAL,"Wrong kex type in ssh_packet_kexdh_init"); + rc = SSH_ERROR; + } + if (rc == SSH_ERROR) + session->session_state = SSH_SESSION_STATE_ERROR; + error: + leave_function(); + return SSH_PACKET_USED; +} + +int ssh_get_key_params(ssh_session session, ssh_key *privkey){ + ssh_key pubkey; + ssh_string pubkey_blob; + int rc; + + switch(session->srv.hostkey) { + case SSH_KEYTYPE_DSS: + *privkey = session->srv.dsa_key; + break; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + *privkey = session->srv.rsa_key; + break; + case SSH_KEYTYPE_ECDSA: + *privkey = session->srv.ecdsa_key; + break; + case SSH_KEYTYPE_UNKNOWN: + *privkey = NULL; + } + + rc = ssh_pki_export_privkey_to_pubkey(*privkey, &pubkey); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Could not get the public key from the private key"); + + return -1; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc < 0) { + ssh_set_error_oom(session); + return -1; + } + + dh_import_pubkey(session, pubkey_blob); + return SSH_OK; +} + +static int dh_handshake_server(ssh_session session) { + ssh_key privkey; + //ssh_string pubkey_blob = NULL; + ssh_string sig_blob; + ssh_string f; + + if (dh_generate_y(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Could not create y number"); + return -1; + } + if (dh_generate_f(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Could not create f number"); + return -1; + } + + f = dh_get_f(session); + if (f == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not get the f number"); + return -1; + } + + if (ssh_get_key_params(session,&privkey) != SSH_OK){ + ssh_string_free(f); + return -1; + } + + if (dh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Could not import the public key"); + ssh_string_free(f); + return -1; + } + + if (make_sessionid(session) != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + ssh_string_free(f); + return -1; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + ssh_string_free(f); + return -1; + } + + /* Free private keys as they should not be readable after this point */ + if (session->srv.rsa_key) { + ssh_key_free(session->srv.rsa_key); + session->srv.rsa_key = NULL; + } + if (session->srv.dsa_key) { + ssh_key_free(session->srv.dsa_key); + session->srv.dsa_key = NULL; + } +#ifdef HAVE_ECC + if (session->srv.ecdsa_key) { + ssh_key_free(session->srv.ecdsa_key); + session->srv.ecdsa_key = NULL; + } +#endif + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_REPLY) < 0 || + buffer_add_ssh_string(session->out_buffer, + session->next_crypto->server_pubkey) < 0 || + buffer_add_ssh_string(session->out_buffer, f) < 0 || + buffer_add_ssh_string(session->out_buffer, sig_blob) < 0) { + ssh_set_error(session, SSH_FATAL, "Not enough space"); + buffer_reinit(session->out_buffer); + ssh_string_free(f); + ssh_string_free(sig_blob); + return -1; + } + ssh_string_free(f); + ssh_string_free(sig_blob); + if (packet_send(session) == SSH_ERROR) { + return -1; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + buffer_reinit(session->out_buffer); + return -1; + } + + if (packet_send(session) == SSH_ERROR) { + return -1; + } + ssh_log(session, SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; + + return 0; +} + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +static void ssh_server_connection_callback(ssh_session session){ + int ssh1,ssh2; + enter_function(); + switch(session->session_state){ + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + case SSH_SESSION_STATE_SOCKET_CONNECTED: + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->clientbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + ssh_log(session, SSH_LOG_RARE, + "SSH client banner: %s", session->clientbanner); + + /* Here we analyze the different protocols the server allows. */ + if (ssh_analyze_banner(session, 1, &ssh1, &ssh2) < 0) { + goto error; + } + /* Here we decide which version of the protocol to use. */ + if (ssh2 && session->opts.ssh2) { + session->version = 2; + } else if (ssh1 && session->opts.ssh1) { + session->version = 1; + } else if (ssh1 && !session->opts.ssh1) { +#ifdef WITH_SSH1 + ssh_set_error(session, SSH_FATAL, + "SSH-1 protocol not available (configure session to allow SSH-1)"); + goto error; +#else + ssh_set_error(session, SSH_FATAL, + "SSH-1 protocol not available (libssh compiled without SSH-1 support)"); + goto error; +#endif + } else { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->clientbanner); + goto error; + } + /* from now, the packet layer is handling incoming packets */ + if(session->version==2) + session->socket_callbacks.data=ssh_packet_socket_callback; +#ifdef WITH_SSH1 + else + session->socket_callbacks.data=ssh_packet_socket_callback1; +#endif + ssh_packet_set_default_callbacks(session); + set_status(session, 0.5f); + session->session_state=SSH_SESSION_STATE_INITIAL_KEX; + if (ssh_send_kex(session, 1) < 0) { + goto error; + } + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ +#ifdef WITH_SSH1 + if(session->version==1){ + if (ssh_get_kex1(session) < 0) + goto error; + set_status(session,0.6f); + session->connected = 1; + break; + } +#endif + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session,0.6f); + ssh_list_kex(session, &session->next_crypto->client_kex); // log client kex + if (ssh_kex_select_methods(session) < 0) { + goto error; + } + if (crypt_set_algorithms_server(session) == SSH_ERROR) + goto error; + set_status(session,0.8f); + session->session_state=SSH_SESSION_STATE_DH; + break; + case SSH_SESSION_STATE_DH: + if(session->dh_handshake_state==DH_STATE_FINISHED){ + if (generate_session_keys(session) < 0) { + goto error; + } + + /* + * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and + * current_crypto + */ + if (session->current_crypto) { + crypto_free(session->current_crypto); + } + + /* FIXME TODO later, include a function to change keys */ + session->current_crypto = session->next_crypto; + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + goto error; + } + set_status(session,1.0f); + session->connected = 1; + session->session_state=SSH_SESSION_STATE_AUTHENTICATING; + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); + } + leave_function(); + return; + error: + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state=SSH_SESSION_STATE_ERROR; + leave_function(); +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +static int callback_receive_banner(const void *data, size_t len, void *user) { + char *buffer = (char *) data; + ssh_session session = (ssh_session) user; + char *str = NULL; + size_t i; + int ret=0; + + enter_function(); + + for (i = 0; i < len; i++) { +#ifdef WITH_PCAP + if(session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer, + i + 1, + i + 1); + } +#endif + if (buffer[i] == '\r') { + buffer[i]='\0'; + } + + if (buffer[i] == '\n') { + buffer[i]='\0'; + + str = strdup(buffer); + /* number of bytes read */ + ret = i + 1; + session->clientbanner = str; + session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; + ssh_log(session, SSH_LOG_PACKET, "Received banner: %s", str); + session->ssh_connection_callback(session); + + leave_function(); + return ret; + } + + if(i > 127) { + /* Too big banner */ + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); + + leave_function(); + return 0; + } + } + + leave_function(); + return ret; +} + +/* returns 0 until the key exchange is not finished */ +static int ssh_server_kex_termination(void *s){ + ssh_session session = s; + if (session->session_state != SSH_SESSION_STATE_ERROR && + session->session_state != SSH_SESSION_STATE_AUTHENTICATING && + session->session_state != SSH_SESSION_STATE_DISCONNECTED) + return 0; + else + return 1; +} + +/* Do the banner and key exchange */ +int ssh_handle_key_exchange(ssh_session session) { + int rc; + if (session->session_state != SSH_SESSION_STATE_NONE) + goto pending; + rc = ssh_send_banner(session, 1); + if (rc < 0) { + return SSH_ERROR; + } + + session->alive = 1; + + session->ssh_connection_callback = ssh_server_connection_callback; + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); + session->socket_callbacks.data=callback_receive_banner; + session->socket_callbacks.exception=ssh_socket_exception_callback; + session->socket_callbacks.userdata=session; + + rc = server_set_kex(session); + if (rc < 0) { + return SSH_ERROR; + } + pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_server_kex_termination,session); + ssh_log(session,SSH_LOG_PACKET, "ssh_handle_key_exchange: Actual state : %d", + session->session_state); + if (rc != SSH_OK) + return rc; + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/* messages */ + +static int ssh_message_auth_reply_default(ssh_message msg,int partial) { + ssh_session session = msg->session; + char methods_c[128] = {0}; + ssh_string methods = NULL; + int rc = SSH_ERROR; + + enter_function(); + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_FAILURE) < 0) { + return rc; + } + + if (session->auth_methods == 0) { + session->auth_methods = SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD; + } + if (session->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + strncat(methods_c, "publickey,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + strncat(methods_c, "keyboard-interactive,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth_methods & SSH_AUTH_METHOD_PASSWORD) { + strncat(methods_c, "password,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth_methods & SSH_AUTH_METHOD_HOSTBASED) { + strncat(methods_c, "hostbased,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + + if (methods_c[0] == '\0' || methods_c[strlen(methods_c)-1] != ',') { + return SSH_ERROR; + } + + /* Strip the comma. */ + methods_c[strlen(methods_c) - 1] = '\0'; // strip the comma. We are sure there is at + + ssh_log(session, SSH_LOG_PACKET, + "Sending a auth failure. methods that can continue: %s", methods_c); + + methods = ssh_string_from_char(methods_c); + if (methods == NULL) { + goto error; + } + + if (buffer_add_ssh_string(msg->session->out_buffer, methods) < 0) { + goto error; + } + + if (partial) { + if (buffer_add_u8(session->out_buffer, 1) < 0) { + goto error; + } + } else { + if (buffer_add_u8(session->out_buffer, 0) < 0) { + goto error; + } + } + + rc = packet_send(msg->session); +error: + ssh_string_free(methods); + + leave_function(); + return rc; +} + +static int ssh_message_channel_request_open_reply_default(ssh_message msg) { + ssh_log(msg->session, SSH_LOG_FUNCTIONS, "Refusing a channel"); + + if (buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_CHANNEL_OPEN_FAILURE) < 0) { + goto error; + } + if (buffer_add_u32(msg->session->out_buffer, + htonl(msg->channel_request_open.sender)) < 0) { + goto error; + } + if (buffer_add_u32(msg->session->out_buffer, + htonl(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED)) < 0) { + goto error; + } + /* reason is an empty string */ + if (buffer_add_u32(msg->session->out_buffer, 0) < 0) { + goto error; + } + /* language too */ + if (buffer_add_u32(msg->session->out_buffer, 0) < 0) { + goto error; + } + + return packet_send(msg->session); +error: + return SSH_ERROR; +} + +static int ssh_message_channel_request_reply_default(ssh_message msg) { + uint32_t channel; + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + ssh_log(msg->session, SSH_LOG_PACKET, + "Sending a default channel_request denied to channel %d", channel); + + if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_CHANNEL_FAILURE) < 0) { + return SSH_ERROR; + } + if (buffer_add_u32(msg->session->out_buffer, htonl(channel)) < 0) { + return SSH_ERROR; + } + + return packet_send(msg->session); + } + + ssh_log(msg->session, SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +} + +static int ssh_message_service_request_reply_default(ssh_message msg) { + /* The only return code accepted by specifications are success or disconnect */ + return ssh_message_service_reply_success(msg); +} + +int ssh_message_service_reply_success(ssh_message msg) { + struct ssh_string_struct *service; + ssh_session session; + + if (msg == NULL) { + return SSH_ERROR; + } + session = msg->session; + + ssh_log(session, SSH_LOG_PACKET, + "Sending a SERVICE_ACCEPT for service %s", msg->service_request.service); + if (buffer_add_u8(session->out_buffer, SSH2_MSG_SERVICE_ACCEPT) < 0) { + return -1; + } + service=ssh_string_from_char(msg->service_request.service); + if (service == NULL) { + return -1; + } + + if (buffer_add_ssh_string(session->out_buffer, service) < 0) { + ssh_string_free(service); + return -1; + } + ssh_string_free(service); + return packet_send(msg->session); +} + +int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { + ssh_log(msg->session, SSH_LOG_FUNCTIONS, "Accepting a global request"); + + if (msg->global_request.want_reply) { + if (buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_SUCCESS) < 0) { + goto error; + } + + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + && msg->global_request.bind_port == 0) { + if (buffer_add_u32(msg->session->out_buffer, htonl(bound_port)) < 0) { + goto error; + } + } + + return packet_send(msg->session); + } + + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + && msg->global_request.bind_port == 0) { + ssh_log(msg->session, SSH_LOG_PACKET, + "The client doesn't want to know the remote port!"); + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +static int ssh_message_global_request_reply_default(ssh_message msg) { + ssh_log(msg->session, SSH_LOG_FUNCTIONS, "Refusing a global request"); + + if (msg->global_request.want_reply) { + if (buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_FAILURE) < 0) { + goto error; + } + return packet_send(msg->session); + } + ssh_log(msg->session, SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +error: + return SSH_ERROR; +} + +int ssh_message_reply_default(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return ssh_message_auth_reply_default(msg, 0); + case SSH_REQUEST_CHANNEL_OPEN: + return ssh_message_channel_request_open_reply_default(msg); + case SSH_REQUEST_CHANNEL: + return ssh_message_channel_request_reply_default(msg); + case SSH_REQUEST_SERVICE: + return ssh_message_service_request_reply_default(msg); + case SSH_REQUEST_GLOBAL: + return ssh_message_global_request_reply_default(msg); + default: + ssh_log(msg->session, SSH_LOG_PACKET, + "Don't know what to default reply to %d type", + msg->type); + break; + } + + return -1; +} + +const char *ssh_message_service_service(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + return msg->service_request.service; +} + +const char *ssh_message_auth_user(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.username; +} + +const char *ssh_message_auth_password(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.password; +} + +ssh_key ssh_message_auth_pubkey(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.pubkey; +} + +/* Get the publickey of an auth request */ +ssh_public_key ssh_message_auth_publickey(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return ssh_pki_convert_key_to_publickey(msg->auth_request.pubkey); +} + +enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ + if (msg == NULL) { + return -1; + } + return msg->auth_request.signature_state; +} + +int ssh_message_auth_kbdint_is_response(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->auth_request.kbdint_response != 0; +} + +int ssh_message_auth_set_methods(ssh_message msg, int methods) { + if (msg == NULL || msg->session == NULL) { + return -1; + } + + msg->session->auth_methods = methods; + + return 0; +} + +int ssh_message_auth_interactive_request(ssh_message msg, const char *name, + const char *instruction, unsigned int num_prompts, + const char **prompts, char *echo) { + int r; + unsigned int i = 0; + ssh_string tmp = NULL; + + if(name == NULL || instruction == NULL) { + return SSH_ERROR; + } + if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { + return SSH_ERROR; + } + + if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_USERAUTH_INFO_REQUEST) < 0) { + return SSH_ERROR; + } + + /* name */ + tmp = ssh_string_from_char(name); + if (tmp == NULL) { + return SSH_ERROR; + } + + r = buffer_add_ssh_string(msg->session->out_buffer, tmp); + ssh_string_free(tmp); + if (r < 0) { + return SSH_ERROR; + } + + /* instruction */ + tmp = ssh_string_from_char(instruction); + if (tmp == NULL) { + return SSH_ERROR; + } + + r = buffer_add_ssh_string(msg->session->out_buffer, tmp); + ssh_string_free(tmp); + if (r < 0) { + return SSH_ERROR; + } + + /* language tag */ + tmp = ssh_string_from_char(""); + if (tmp == NULL) { + return SSH_ERROR; + } + + r = buffer_add_ssh_string(msg->session->out_buffer, tmp); + ssh_string_free(tmp); + if (r < 0) { + return SSH_ERROR; + } + + /* num prompts */ + if (buffer_add_u32(msg->session->out_buffer, ntohl(num_prompts)) < 0) { + return SSH_ERROR; + } + + for(i = 0; i < num_prompts; i++) { + /* prompt[i] */ + tmp = ssh_string_from_char(prompts[i]); + if (tmp == NULL) { + return SSH_ERROR; + } + + r = buffer_add_ssh_string(msg->session->out_buffer, tmp); + ssh_string_free(tmp); + if (r < 0) { + goto error; + } + + /* echo[i] */ + if (buffer_add_u8(msg->session->out_buffer, echo[i]) < 0) { + return SSH_ERROR; + } + } + + r = packet_send(msg->session); + + /* fill in the kbdint structure */ + if (msg->session->kbdint == NULL) { + ssh_log(msg->session, SSH_LOG_PROTOCOL, "Warning: Got a " + "keyboard-interactive response but it " + "seems we didn't send the request."); + + msg->session->kbdint = ssh_kbdint_new(); + if (msg->session->kbdint == NULL) { + ssh_set_error_oom(msg->session); + + return SSH_ERROR; + } + } else { + ssh_kbdint_clean(msg->session->kbdint); + } + + msg->session->kbdint->name = strdup(name); + if(msg->session->kbdint->name == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + msg->session->kbdint->instruction = strdup(instruction); + if(msg->session->kbdint->instruction == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + + msg->session->kbdint->nprompts = num_prompts; + if(num_prompts > 0) { + msg->session->kbdint->prompts = malloc(num_prompts * sizeof(char *)); + if (msg->session->kbdint->prompts == NULL) { + msg->session->kbdint->nprompts = 0; + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + msg->session->kbdint->echo = malloc(num_prompts * sizeof(char)); + if (msg->session->kbdint->echo == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + for (i = 0; i < num_prompts; i++) { + msg->session->kbdint->echo[i] = echo[i]; + msg->session->kbdint->prompts[i] = strdup(prompts[i]); + if (msg->session->kbdint->prompts[i] == NULL) { + ssh_set_error_oom(msg->session); + msg->session->kbdint->nprompts = i; + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + } + } else { + msg->session->kbdint->prompts = NULL; + msg->session->kbdint->echo = NULL; + } + + return r; +error: + if(tmp) ssh_string_free(tmp); + return SSH_ERROR; +} + +int ssh_message_auth_reply_success(ssh_message msg, int partial) { + int r; + + if (msg == NULL) { + return SSH_ERROR; + } + + if (partial) { + return ssh_message_auth_reply_default(msg, partial); + } + + if (buffer_add_u8(msg->session->out_buffer,SSH2_MSG_USERAUTH_SUCCESS) < 0) { + return SSH_ERROR; + } + + r = packet_send(msg->session); + if(msg->session->current_crypto && msg->session->current_crypto->delayed_compress_out){ + ssh_log(msg->session,SSH_LOG_PROTOCOL,"Enabling delayed compression OUT"); + msg->session->current_crypto->do_compress_out=1; + } + if(msg->session->current_crypto && msg->session->current_crypto->delayed_compress_in){ + ssh_log(msg->session,SSH_LOG_PROTOCOL,"Enabling delayed compression IN"); + msg->session->current_crypto->do_compress_in=1; + } + return r; +} + +/* Answer OK to a pubkey auth request */ +int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) { + if (msg == NULL) { + return SSH_ERROR; + } + + if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_USERAUTH_PK_OK) < 0 || + buffer_add_ssh_string(msg->session->out_buffer, algo) < 0 || + buffer_add_ssh_string(msg->session->out_buffer, pubkey) < 0) { + return SSH_ERROR; + } + + return packet_send(msg->session); +} + +int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) { + ssh_string algo; + ssh_string pubkey_blob = NULL; + int ret; + + algo = ssh_string_from_char(msg->auth_request.pubkey->type_c); + if (algo == NULL) { + return SSH_ERROR; + } + + ret = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &pubkey_blob); + if (ret < 0) { + ssh_string_free(algo); + return SSH_ERROR; + } + + ret = ssh_message_auth_reply_pk_ok(msg, algo, pubkey_blob); + + ssh_string_free(algo); + ssh_string_free(pubkey_blob); + + return ret; +} + + +const char *ssh_message_channel_request_open_originator(ssh_message msg){ + return msg->channel_request_open.originator; +} + +int ssh_message_channel_request_open_originator_port(ssh_message msg){ + return msg->channel_request_open.originator_port; +} + +const char *ssh_message_channel_request_open_destination(ssh_message msg){ + return msg->channel_request_open.destination; +} + +int ssh_message_channel_request_open_destination_port(ssh_message msg){ + return msg->channel_request_open.destination_port; +} + +ssh_channel ssh_message_channel_request_channel(ssh_message msg){ + return msg->channel_request.channel; +} + +const char *ssh_message_channel_request_pty_term(ssh_message msg){ + return msg->channel_request.TERM; +} + +int ssh_message_channel_request_pty_width(ssh_message msg){ + return msg->channel_request.width; +} + +int ssh_message_channel_request_pty_height(ssh_message msg){ + return msg->channel_request.height; +} + +int ssh_message_channel_request_pty_pxwidth(ssh_message msg){ + return msg->channel_request.pxwidth; +} + +int ssh_message_channel_request_pty_pxheight(ssh_message msg){ + return msg->channel_request.pxheight; +} + +const char *ssh_message_channel_request_env_name(ssh_message msg){ + return msg->channel_request.var_name; +} + +const char *ssh_message_channel_request_env_value(ssh_message msg){ + return msg->channel_request.var_value; +} + +const char *ssh_message_channel_request_command(ssh_message msg){ + return msg->channel_request.command; +} + +const char *ssh_message_channel_request_subsystem(ssh_message msg){ + return msg->channel_request.subsystem; +} + +int ssh_message_channel_request_x11_single_connection(ssh_message msg){ + return msg->channel_request.x11_single_connection ? 1 : 0; +} + +const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg){ + return msg->channel_request.x11_auth_protocol; +} + +const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg){ + return msg->channel_request.x11_auth_cookie; +} + +int ssh_message_channel_request_x11_screen_number(ssh_message msg){ + return msg->channel_request.x11_screen_number; +} + +const char *ssh_message_global_request_address(ssh_message msg){ + return msg->global_request.bind_address; +} + +int ssh_message_global_request_port(ssh_message msg){ + return msg->global_request.bind_port; +} + +/** @brief defines the ssh_message callback + * @param session the current ssh session + * @param[in] ssh_bind_message_callback a function pointer to a callback taking the + * current ssh session and received message as parameters. the function returns + * 0 if the message has been parsed and treated successfully, 1 otherwise (libssh + * must take care of the response). + * @param[in] data void pointer to be passed to callback functions + */ +void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data) { + session->ssh_message_callback = ssh_bind_message_callback; + session->ssh_message_callback_data = data; +} + +int ssh_execute_message_callbacks(ssh_session session){ + ssh_message msg=NULL; + int ret; + ssh_handle_packets(session, SSH_TIMEOUT_NONBLOCKING); + if(!session->ssh_message_list) + return SSH_OK; + if(session->ssh_message_callback){ + while((msg=ssh_message_pop_head(session)) != NULL) { + ret=session->ssh_message_callback(session,msg, + session->ssh_message_callback_data); + if(ret==1){ + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } else { + ssh_message_free(msg); + } + } + } else { + while((msg=ssh_message_pop_head(session)) != NULL) { + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } + } + return SSH_OK; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/session.c b/libssh/src/session.c new file mode 100644 index 00000000..4e713948 --- /dev/null +++ b/libssh/src/session.c @@ -0,0 +1,737 @@ +/* + * session.c - non-networking functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/server.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/agent.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" + +#define FIRST_CHANNEL 42 // why not ? it helps to find bugs. + +/** + * @defgroup libssh_session The SSH session functions. + * @ingroup libssh + * + * Functions that manage a session. + * + * @{ + */ + +/** + * @brief Create a new ssh session. + * + * @returns A new ssh_session pointer, NULL on error. + */ +ssh_session ssh_new(void) { + ssh_session session; + char *id = 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->common.log_indent = 0; + 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 = 22; + 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_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); + ssh_key_free(session->srv.rsa_key); + + 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); + } + + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->banner); + + SAFE_FREE(session->opts.bindaddr); + 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); + + for (i = 0; i < 10; i++) { + if (session->opts.wanted_methods[i]) { + SAFE_FREE(session->opts.wanted_methods[i]); + } + } + + /* burn connection, it could hang sensitive datas */ + ZERO_STRUCTP(session); + SAFE_FREE(session); +} + +/** + * @brief get the server banner + * @param[in] session The SSH session + */ +const char* ssh_get_serverbanner(ssh_session session) { + if(!session) { + return NULL; + } + return session->serverbanner; +} + +/** + * @brief Disconnect impolitely from a remote host by closing the socket. + * + * Suitable if you forked and want to destroy this session. + * + * @param[in] session The SSH session to disconnect. + */ +void ssh_silent_disconnect(ssh_session session) { + enter_function(); + + if (session == NULL) { + return; + } + + ssh_socket_close(session->socket); + session->alive = 0; + ssh_disconnect(session); + leave_function(); +} + +/** + * @brief Set the session in blocking/nonblocking mode. + * + * @param[in] session The ssh session to change. + * + * @param[in] blocking Zero for nonblocking mode. + * + * \bug nonblocking code is in development and won't work as expected + */ +void ssh_set_blocking(ssh_session session, int blocking) { + if (session == NULL) { + return; + } + session->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) + return SSH_ERROR; + enter_function(); + + rc = ssh_handle_packets_termination(session, timeout, + ssh_flush_termination, session); + if (rc == SSH_ERROR) + goto end; + if (!ssh_flush_termination(session)) + rc = SSH_AGAIN; +end: + leave_function(); + 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; + } + enter_function(); + + spoll_in = ssh_socket_get_poll_handle_in(session->socket); + spoll_out = ssh_socket_get_poll_handle_out(session->socket); + if (session->server) { + 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; + } + + leave_function(); + 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. + * 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){ + int ret = SSH_OK; + struct ssh_timestamp ts; + 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; + } + 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)) + 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->closed) { + r |= SSH_CLOSED; + } + if (socketstate & SSH_READ_PENDING) { + r |= SSH_READ_PENDING; + } + if (socketstate & SSH_WRITE_PENDING) { + r |= SSH_WRITE_PENDING; + } + if (session->closed && (socketstate & SSH_CLOSED_ERROR)) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @brief Get the disconnect message from the server. + * + * @param[in] session The ssh session to use. + * + * @return The message sent by the server along with the + * disconnect, or NULL in which case the reason of the + * disconnect may be found with ssh_get_error. + * + * @see ssh_get_error() + */ +const char *ssh_get_disconnect_message(ssh_session session) { + if (session == NULL) { + return NULL; + } + + if (!session->closed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection not closed yet"); + } else if(session->closed_by_except) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection closed by socket error"); + } else if(!session->discon_msg) { + ssh_set_error(session, SSH_FATAL, + "Connection correctly closed but no disconnect message"); + } else { + return session->discon_msg; + } + + return NULL; +} + +/** + * @brief Get the protocol version of the session. + * + * @param session The ssh session to use. + * + * @return 1 or 2, for ssh1 or ssh2, < 0 on error. + */ +int ssh_get_version(ssh_session session) { + if (session == NULL) { + return -1; + } + + return session->version; +} + +/** + * @internal + * @brief Callback to be called when the socket received an exception code. + * @param user is a pointer to session + */ +void ssh_socket_exception_callback(int code, int errno_code, void *user){ + ssh_session session=(ssh_session)user; + enter_function(); + ssh_log(session,SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code); + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"Socket error: %s",strerror(errno_code)); + session->ssh_connection_callback(session); + leave_function(); +} + +/** + * @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) { + ssh_string str; + + if (ssh_socket_is_open(session->socket)) { + if (buffer_add_u8(session->out_buffer, SSH2_MSG_IGNORE) < 0) { + goto error; + } + + str = ssh_string_from_char(data); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(session->out_buffer, str) < 0) { + ssh_string_free(str); + goto error; + } + + packet_send(session); + ssh_handle_packets(session, 0); + + ssh_string_free(str); + } + + return SSH_OK; + +error: + 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) { + ssh_string str; + int rc; + + if (ssh_socket_is_open(session->socket)) { + if (buffer_add_u8(session->out_buffer, SSH2_MSG_DEBUG) < 0) { + goto error; + } + + if (buffer_add_u8(session->out_buffer, always_display) < 0) { + goto error; + } + + str = ssh_string_from_char(message); + if (str == NULL) { + goto error; + } + + rc = buffer_add_ssh_string(session->out_buffer, str); + ssh_string_free(str); + if (rc < 0) { + goto error; + } + + /* Empty language tag */ + if (buffer_add_u32(session->out_buffer, 0) < 0) { + goto error; + } + + packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/sftp.c b/libssh/src/sftp.c new file mode 100644 index 00000000..ee86107b --- /dev/null +++ b/libssh/src/sftp.c @@ -0,0 +1,3199 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#else +#define S_IFSOCK 0140000 +#define S_IFLNK 0120000 + +#ifdef _MSC_VER +#define S_IFBLK 0060000 +#define S_IFIFO 0010000 +#endif +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/sftp.h" +#include "libssh/buffer.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" + +#ifdef WITH_SFTP + +struct sftp_ext_struct { + unsigned int count; + char **name; + char **data; +}; + +/* functions */ +static int sftp_enqueue(sftp_session session, sftp_message msg); +static void sftp_message_free(sftp_message msg); +static void sftp_set_error(sftp_session sftp, int errnum); +static void status_msg_free(sftp_status_message status); + +static sftp_ext sftp_ext_new(void) { + sftp_ext ext; + + ext = malloc(sizeof(struct sftp_ext_struct)); + if (ext == NULL) { + return NULL; + } + ZERO_STRUCTP(ext); + + return ext; +} + +static void sftp_ext_free(sftp_ext ext) { + unsigned int i; + + if (ext == NULL) { + return; + } + + if (ext->count) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->name[i]); + SAFE_FREE(ext->data[i]); + } + SAFE_FREE(ext->name); + SAFE_FREE(ext->data); + } + + SAFE_FREE(ext); +} + +sftp_session sftp_new(ssh_session session){ + sftp_session sftp; + + if (session == NULL) { + return NULL; + } + enter_function(); + + sftp = malloc(sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + leave_function(); + return NULL; + } + ZERO_STRUCTP(sftp); + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(sftp); + leave_function(); + return NULL; + } + + sftp->session = session; + sftp->channel = ssh_channel_new(session); + if (sftp->channel == NULL) { + SAFE_FREE(sftp); + leave_function(); + return NULL; + } + + if (ssh_channel_open_session(sftp->channel)) { + ssh_channel_free(sftp->channel); + SAFE_FREE(sftp); + leave_function(); + return NULL; + } + + if (ssh_channel_request_sftp(sftp->channel)) { + sftp_free(sftp); + leave_function(); + return NULL; + } + + leave_function(); + return sftp; +} + +#ifdef WITH_SERVER +sftp_session sftp_server_new(ssh_session session, ssh_channel chan){ + sftp_session sftp = NULL; + + sftp = malloc(sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(sftp); + + sftp->session = session; + sftp->channel = chan; + + return sftp; +} + +int sftp_server_init(sftp_session sftp){ + ssh_session session = sftp->session; + sftp_packet packet = NULL; + ssh_buffer reply = NULL; + uint32_t version; + + 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(session, SSH_LOG_PACKET, "Received SSH_FXP_INIT"); + + buffer_get_u32(packet->payload, &version); + version = ntohl(version); + ssh_log(session, SSH_LOG_PACKET, "Client version: %d", version); + sftp->client_version = version; + + sftp_packet_free(packet); + + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_set_error_oom(session); + return -1; + } + + if (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(session, 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 (buffer_prepend_data(payload, &type, sizeof(uint8_t)) < 0) { + ssh_set_error_oom(sftp->session); + return -1; + } + + size = htonl(buffer_get_rest_len(payload)); + if (buffer_prepend_data(payload, &size, sizeof(uint32_t)) < 0) { + ssh_set_error_oom(sftp->session); + return -1; + } + + size = ssh_channel_write(sftp->channel, buffer_get_rest(payload), + buffer_get_rest_len(payload)); + if (size < 0) { + return -1; + } else if((uint32_t) size != buffer_get_rest_len(payload)) { + ssh_log(sftp->session, SSH_LOG_PACKET, + "Had to write %d bytes, wrote only %d", + buffer_get_rest_len(payload), + size); + } + + return size; +} + +sftp_packet sftp_packet_read(sftp_session sftp) { + unsigned char buffer[4096]; + sftp_packet packet = NULL; + uint32_t size; + int r; + + packet = malloc(sizeof(struct sftp_packet_struct)); + if (packet == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + packet->sftp = sftp; + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(packet); + return NULL; + } + + r=ssh_channel_read(sftp->channel, buffer, 4, 0); + if (r < 0) { + ssh_buffer_free(packet->payload); + SAFE_FREE(packet); + return NULL; + } + buffer_add_data(packet->payload,buffer, r); + if (buffer_get_u32(packet->payload, &size) != sizeof(uint32_t)) { + ssh_set_error(sftp->session, SSH_FATAL, "Short sftp packet!"); + ssh_buffer_free(packet->payload); + SAFE_FREE(packet); + return NULL; + } + + size = ntohl(size); + r=ssh_channel_read(sftp->channel, buffer, 1, 0); + if (r <= 0) { + /* TODO: check if there are cases where an error needs to be set here */ + ssh_buffer_free(packet->payload); + SAFE_FREE(packet); + return NULL; + } + buffer_add_data(packet->payload, buffer, r); + buffer_get_u8(packet->payload, &packet->type); + size=size-1; + while (size>0){ + 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 */ + ssh_buffer_free(packet->payload); + SAFE_FREE(packet); + return NULL; + } + if(buffer_add_data(packet->payload,buffer,r)==SSH_ERROR){ + ssh_buffer_free(packet->payload); + SAFE_FREE(packet); + ssh_set_error_oom(sftp->session); + return NULL; + } + size -= r; + } + + return packet; +} + +static void sftp_set_error(sftp_session sftp, int errnum) { + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +/* Get the last sftp error */ +int sftp_get_error(sftp_session sftp) { + if (sftp == NULL) { + return -1; + } + + return sftp->errnum; +} + +static sftp_message sftp_message_new(sftp_session sftp){ + sftp_message msg = NULL; + + msg = malloc(sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + ZERO_STRUCTP(msg); + + msg->payload = ssh_buffer_new(); + if (msg->payload == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(msg); + return NULL; + } + msg->sftp = sftp; + + 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; + + 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; + } + + if (buffer_get_u32(packet->payload, &msg->id) != sizeof(uint32_t)) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + return NULL; + } + + ssh_log(packet->sftp->session, SSH_LOG_PACKET, + "Packet with id %d type %d", + msg->id, + msg->packet_type); + + if (buffer_add_data(msg->payload, buffer_get_rest(packet->payload), + buffer_get_rest_len(packet->payload)) < 0) { + ssh_set_error_oom(sftp->session); + sftp_message_free(msg); + 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; + ssh_string ext_name_s = NULL; + ssh_string ext_data_s = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version = htonl(LIBSFTP_VERSION); + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + if (buffer_add_u32(buffer, version) < 0) { + 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? */ + buffer_get_u32(packet->payload, &version); + version = ntohl(version); + ssh_log(sftp->session, SSH_LOG_RARE, + "SFTP server version %d", + version); + + ext_name_s = buffer_get_ssh_string(packet->payload); + while (ext_name_s != NULL) { + int count = sftp->ext->count; + char **tmp; + + ext_data_s = buffer_get_ssh_string(packet->payload); + if (ext_data_s == NULL) { + ssh_string_free(ext_name_s); + break; + } + + ext_name = ssh_string_to_char(ext_name_s); + ext_data = ssh_string_to_char(ext_data_s); + if (ext_name == NULL || ext_data == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + ssh_string_free(ext_name_s); + ssh_string_free(ext_data_s); + return -1; + } + ssh_log(sftp->session, SSH_LOG_RARE, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); + + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + ssh_string_free(ext_name_s); + ssh_string_free(ext_data_s); + return -1; + } + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; + + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + ssh_string_free(ext_name_s); + ssh_string_free(ext_data_s); + return -1; + } + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; + + sftp->ext->count = count; + + ssh_string_free(ext_name_s); + ssh_string_free(ext_data_s); + + ext_name_s = buffer_get_ssh_string(packet->payload); + } + + sftp_packet_free(packet); + + sftp->version = sftp->server_version = version; + + + 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 = malloc(sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + return NULL; + } + ZERO_STRUCTP(queue); + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) { + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int sftp_enqueue(sftp_session sftp, sftp_message msg) { + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + ssh_log(sftp->session, SSH_LOG_PACKET, + "Queued msg type %d id %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls of a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if(queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + ssh_log(sftp->session, SSH_LOG_PACKET, + "Dequeued msg id %d type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +/* + * Assigns a new SFTP ID for new requests and assures there is no collision + * between them. + * Returns a new ID ready to use in a request + */ +static inline uint32_t sftp_get_new_id(sftp_session session) { + return ++session->id_counter; +} + +static sftp_status_message parse_status_msg(sftp_message msg){ + sftp_status_message status; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + return NULL; + } + + status = malloc(sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + return NULL; + } + ZERO_STRUCTP(status); + + status->id = msg->id; + if (buffer_get_u32(msg->payload,&status->status) != 4){ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + return NULL; + } + status->error = buffer_get_ssh_string(msg->payload); + status->lang = buffer_get_ssh_string(msg->payload); + if(status->error == NULL || status->lang == NULL){ + if(msg->sftp->version >=3){ + /* These are mandatory from version 3 */ + ssh_string_free(status->error); + /* status->lang never get allocated if something failed */ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + return NULL; + } + } + + status->status = ntohl(status->status); + if(status->error) + status->errormsg = ssh_string_to_char(status->error); + else + status->errormsg = strdup("No error message in packet"); + if(status->lang) + status->langmsg = ssh_string_to_char(status->lang); + else + status->langmsg = strdup(""); + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + status_msg_free(status); + return NULL; + } + + return status; +} + +static void status_msg_free(sftp_status_message status){ + if (status == NULL) { + return; + } + + ssh_string_free(status->error); + ssh_string_free(status->lang); + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +static sftp_file parse_handle_msg(sftp_message msg){ + sftp_file file; + + if(msg->packet_type != SSH_FXP_HANDLE) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_handle message passed in!"); + return NULL; + } + + file = malloc(sizeof(struct sftp_file_struct)); + if (file == NULL) { + ssh_set_error_oom(msg->sftp->session); + return NULL; + } + ZERO_STRUCTP(file); + + file->handle = buffer_get_ssh_string(msg->payload); + if (file->handle == NULL) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_HANDLE message"); + SAFE_FREE(file); + return NULL; + } + + file->sftp = msg->sftp; + file->offset = 0; + file->eof = 0; + + return file; +} + +/* Open a directory */ +sftp_dir sftp_opendir(sftp_session sftp, const char *path){ + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_dir dir = NULL; + sftp_status_message status; + ssh_string path_s; + ssh_buffer payload; + uint32_t id; + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + path_s = ssh_string_from_char(path); + if (path_s == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(payload, id) < 0 || + buffer_add_ssh_string(payload, path_s) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + ssh_string_free(path_s); + return NULL; + } + ssh_string_free(path_s); + + if (sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload) < 0) { + ssh_buffer_free(payload); + return NULL; + } + ssh_buffer_free(payload); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + case SSH_FXP_HANDLE: + file = parse_handle_msg(msg); + sftp_message_free(msg); + if (file != NULL) { + dir = malloc(sizeof(struct sftp_dir_struct)); + if (dir == NULL) { + ssh_set_error_oom(sftp->session); + free(file); + return NULL; + } + ZERO_STRUCTP(dir); + + dir->sftp = sftp; + dir->name = strdup(path); + if (dir->name == NULL) { + SAFE_FREE(dir); + SAFE_FREE(file); + return NULL; + } + dir->handle = file->handle; + SAFE_FREE(file); + } + return dir; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during opendir!", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we need it in future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, + int expectnames) { + sftp_attributes attr; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = malloc(sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + ZERO_STRUCTP(attr); + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + string_free(owner); + if (attr->owner == NULL) { + break; + } + + group = buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + string_free(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & S_IFMT) { + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + } + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + } + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + } + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while(attr->extended_count && + (attr->extended_type = buffer_get_ssh_string(buf)) && + (attr->extended_data = buffer_get_ssh_string(buf))){ + attr->extended_count--; + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + ssh_string_free(attr->acl); + ssh_string_free(attr->extended_type); + ssh_string_free(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char *sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) { + const char *p, *q; + size_t len, field = 0; + char *x; + + p = longname; + /* Find the beginning of the field which is specified by sftp_longanme_field_e. */ + while(field != longname_field) { + if(isspace(*p)) { + field++; + p++; + while(*p && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + q = p; + while (! isspace(*q)) { + q++; + } + + /* There is no strndup on windows */ + len = q - p + 1; + x = malloc(len); + if (x == NULL) { + return NULL; + } + + snprintf(x, len, "%s", p); + + return x; +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, + int expectname) { + ssh_string longname; + ssh_string name; + sftp_attributes attr; + uint32_t flags = 0; + int ok = 0; + + attr = malloc(sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + ZERO_STRUCTP(attr); + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (expectname) { + name = buffer_get_ssh_string(buf); + if (name == NULL) { + break; + } + attr->name = ssh_string_to_char(name); + ssh_string_free(name); + if (attr->name == NULL) { + break; + } + + ssh_log(sftp->session, SSH_LOG_RARE, "Name: %s", attr->name); + + longname = buffer_get_ssh_string(buf); + if (longname == NULL) { + break; + } + attr->longname = ssh_string_to_char(longname); + ssh_string_free(longname); + if (attr->longname == NULL) { + break; + } + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + break; + } + + attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + break; + } + } + } + + if (buffer_get_u32(buf, &flags) != sizeof(uint32_t)) { + break; + } + flags = ntohl(flags); + attr->flags = flags; + ssh_log(sftp->session, SSH_LOG_RARE, + "Flags: %.8lx\n", (long unsigned int) flags); + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if(buffer_get_u64(buf, &attr->size) != sizeof(uint64_t)) { + break; + } + attr->size = ntohll(attr->size); + ssh_log(sftp->session, SSH_LOG_RARE, + "Size: %llu\n", + (long long unsigned int) attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + if (buffer_get_u32(buf, &attr->uid) != sizeof(uint32_t)) { + break; + } + if (buffer_get_u32(buf, &attr->gid) != sizeof(uint32_t)) { + break; + } + attr->uid = ntohl(attr->uid); + attr->gid = ntohl(attr->gid); + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (buffer_get_u32(buf, &attr->permissions) != sizeof(uint32_t)) { + break; + } + attr->permissions = ntohl(attr->permissions); + + switch (attr->permissions & S_IFMT) { + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + if (buffer_get_u32(buf, &attr->atime) != sizeof(uint32_t)) { + break; + } + attr->atime = ntohl(attr->atime); + if (buffer_get_u32(buf, &attr->mtime) != sizeof(uint32_t)) { + break; + } + attr->mtime = ntohl(attr->mtime); + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (buffer_get_u32(buf, &attr->extended_count) != sizeof(uint32_t)) { + break; + } + + attr->extended_count = ntohl(attr->extended_count); + while (attr->extended_count && + (attr->extended_type = buffer_get_ssh_string(buf)) + && (attr->extended_data = buffer_get_ssh_string(buf))) { + attr->extended_count--; + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (!ok) { + /* break issued somewhere */ + ssh_string_free(attr->extended_type); + ssh_string_free(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + /* everything went smoothly */ + return attr; +} + +/* FIXME is this really needed as a public function? */ +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) { + uint32_t flags = (attr ? attr->flags : 0); + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + if (buffer_add_u32(buffer, htonl(flags)) < 0) { + return -1; + } + + if (attr) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (buffer_add_u64(buffer, htonll(attr->size)) < 0) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + if (buffer_add_u32(buffer,htonl(attr->uid)) < 0 || + buffer_add_u32(buffer,htonl(attr->gid)) < 0) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (buffer_add_u32(buffer, htonl(attr->permissions)) < 0) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + if (buffer_add_u32(buffer, htonl(attr->atime)) < 0 || + buffer_add_u32(buffer, htonl(attr->mtime)) < 0) { + return -1; + } + } + } + + return 0; +} + + +sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf, + int expectname) { + switch(session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", session->server_version); + return NULL; + } + + return NULL; +} + +/* Get the version of the SFTP protocol supported by the server */ +int sftp_server_version(sftp_session sftp) { + return sftp->server_version; +} + +/* Get a single file attributes structure of a directory. */ +sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) { + sftp_message msg = NULL; + sftp_status_message status; + sftp_attributes attr; + ssh_buffer payload; + uint32_t id; + + if (dir->buffer == NULL) { + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(payload, id) < 0 || + buffer_add_ssh_string(payload, dir->handle) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(payload); + return NULL; + } + + if (sftp_packet_write(sftp, SSH_FXP_READDIR, payload) < 0) { + ssh_buffer_free(payload); + return NULL; + } + ssh_buffer_free(payload); + + ssh_log(sftp->session, SSH_LOG_PACKET, + "Sent a ssh_fxp_readdir with id %d", id); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + switch (msg->packet_type){ + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + dir->eof = 1; + status_msg_free(status); + return NULL; + default: + break; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Unknown error status: %d", status->status); + status_msg_free(status); + + return NULL; + case SSH_FXP_NAME: + buffer_get_u32(msg->payload, &dir->count); + dir->count = ntohl(dir->count); + dir->buffer = msg->payload; + msg->payload = NULL; + sftp_message_free(msg); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Unsupported message back %d", msg->packet_type); + sftp_message_free(msg); + + return NULL; + } + } + + /* now dir->buffer contains a buffer and dir->count != 0 */ + if (dir->count == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Count of files sent by the server is zero, which is invalid, or " + "libsftp bug"); + return NULL; + } + + ssh_log(sftp->session, SSH_LOG_RARE, "Count is %d", dir->count); + + attr = sftp_parse_attr(sftp, dir->buffer, 1); + if (attr == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + + dir->count--; + if (dir->count == 0) { + ssh_buffer_free(dir->buffer); + dir->buffer = NULL; + } + + return attr; +} + +/* Tell if the directory has reached EOF (End Of File). */ +int sftp_dir_eof(sftp_dir dir) { + return dir->eof; +} + +/* Free a SFTP_ATTRIBUTE handle */ +void sftp_attributes_free(sftp_attributes file){ + if (file == NULL) { + return; + } + + ssh_string_free(file->acl); + ssh_string_free(file->extended_data); + ssh_string_free(file->extended_type); + + SAFE_FREE(file->name); + SAFE_FREE(file->longname); + SAFE_FREE(file->group); + SAFE_FREE(file->owner); + + SAFE_FREE(file); +} + +static int sftp_handle_close(sftp_session sftp, ssh_string handle) { + sftp_status_message status; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, handle) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_CLOSE ,buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(sftp,id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if(status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during sftp_handle_close!", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Close an open file handle. */ +int sftp_close(sftp_file file){ + int err = SSH_NO_ERROR; + + SAFE_FREE(file->name); + if (file->handle){ + err = sftp_handle_close(file->sftp,file->handle); + ssh_string_free(file->handle); + } + /* FIXME: check server response and implement errno */ + SAFE_FREE(file); + + return err; +} + +/* Close an open directory. */ +int sftp_closedir(sftp_dir dir){ + int err = SSH_NO_ERROR; + + SAFE_FREE(dir->name); + if (dir->handle) { + err = sftp_handle_close(dir->sftp, dir->handle); + ssh_string_free(dir->handle); + } + /* FIXME: check server response and implement errno */ + ssh_buffer_free(dir->buffer); + SAFE_FREE(dir); + + return err; +} + +/* Open a file on the server. */ +sftp_file sftp_open(sftp_session sftp, const char *file, int flags, + mode_t mode) { + sftp_message msg = NULL; + sftp_status_message status; + struct sftp_attributes_struct attr; + sftp_file handle; + ssh_string filename; + ssh_buffer buffer; + uint32_t sftp_flags = 0; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + filename = ssh_string_from_char(file); + if (filename == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + if (flags == O_RDONLY) + sftp_flags |= SSH_FXF_READ; /* if any of the other flag is set, + READ should not be set initialy */ + if (flags & O_WRONLY) + sftp_flags |= SSH_FXF_WRITE; + if (flags & O_RDWR) + sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ); + if (flags & O_CREAT) + sftp_flags |= SSH_FXF_CREAT; + if (flags & O_TRUNC) + sftp_flags |= SSH_FXF_TRUNC; + if (flags & O_EXCL) + sftp_flags |= SSH_FXF_EXCL; + ssh_log(sftp->session,SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags); + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, filename) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(filename); + return NULL; + } + ssh_string_free(filename); + + if (buffer_add_u32(buffer, htonl(sftp_flags)) < 0 || + buffer_add_attributes(buffer, &attr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_OPEN, buffer) < 0) { + ssh_buffer_free(buffer); + return NULL; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + sftp_message_free(msg); + return handle; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during open!", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +void sftp_file_set_nonblocking(sftp_file handle){ + handle->nonblocking=1; +} +void sftp_file_set_blocking(sftp_file handle){ + handle->nonblocking=0; +} + +/* Read from a file using an opened sftp file handle. */ +ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { + sftp_session sftp = handle->sftp; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring; + ssh_buffer buffer; + int id; + + if (handle->eof) { + return 0; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + id = sftp_get_new_id(handle->sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, handle->handle) < 0 || + buffer_add_u64(buffer, htonll(handle->offset)) < 0 || + buffer_add_u32(buffer,htonl(count)) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (handle->nonblocking) { + if (ssh_channel_poll(handle->sftp->channel, 0) == 0) { + /* we cannot block */ + return 0; + } + } + if (sftp_read_and_dispatch(handle->sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(handle->sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + handle->eof = 1; + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session,SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + case SSH_FXP_DATA: + datastring = buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return -1; + } + + if (ssh_string_len(datastring) > count) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%" PRIdS " and asked for %" PRIdS, + ssh_string_len(datastring), count); + ssh_string_free(datastring); + return -1; + } + count = ssh_string_len(datastring); + handle->offset += count; + memcpy(buf, ssh_string_data(datastring), count); + ssh_string_free(datastring); + return count; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_message_free(msg); + return -1; + } + + return -1; /* not reached */ +} + +/* Start an asynchronous read from a file using an opened sftp file handle. */ +int sftp_async_read_begin(sftp_file file, uint32_t len){ + sftp_session sftp = file->sftp; + ssh_buffer buffer; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, file->handle) < 0 || + buffer_add_u64(buffer, htonll(file->offset)) < 0 || + buffer_add_u32(buffer, htonl(len)) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) { + ssh_buffer_free(buffer); + return -1; + } + ssh_buffer_free(buffer); + + file->offset += len; /* assume we'll read len bytes */ + + 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 = 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_string datastring; + ssh_buffer buffer; + uint32_t id; + int len; + int packetlen; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + datastring = ssh_string_new(count); + if (datastring == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + ssh_string_fill(datastring, buf, count); + + id = sftp_get_new_id(file->sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, file->handle) < 0 || + buffer_add_u64(buffer, htonll(file->offset)) < 0 || + buffer_add_ssh_string(buffer, datastring) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(datastring); + return -1; + } + ssh_string_free(datastring); + packetlen=buffer_get_rest_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(sftp->session, SSH_LOG_PACKET, + "Could not write as much data as expected"); + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(file->sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(file->sftp, id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + file->offset += count; + status_msg_free(status); + return count; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + file->offset += count; + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", msg->packet_type); + sftp_message_free(msg); + return -1; + } + + return -1; /* not reached */ +} + +/* Seek to a specific location in a file. */ +int sftp_seek(sftp_file file, uint32_t new_offset) { + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + 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_string filename; + ssh_buffer buffer; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + filename = ssh_string_from_char(file); + if (filename == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, filename) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(filename); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(filename); + return -1; + } + ssh_string_free(filename); + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp)) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_STATUS) { + /* by specification, this command's only supposed to return SSH_FXP_STATUS */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session,SSH_FATAL, + "Received message %d when attempting to remove file", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* code written by Nick */ +int sftp_rmdir(sftp_session sftp, const char *directory) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string filename; + ssh_buffer buffer; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + filename = ssh_string_from_char(directory); + if (filename == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, filename) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(filename); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(filename); + return -1; + } + ssh_buffer_free(buffer); + ssh_string_free(filename); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to remove directory", + msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Code written by Nick */ +int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_attributes errno_attr = NULL; + struct sftp_attributes_struct attr; + ssh_buffer buffer; + ssh_string path; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + path = ssh_string_from_char(directory); + if (path == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, path) < 0 || + buffer_add_attributes(buffer, &attr) < 0 || + sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(path); + 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; + ssh_string oldpath; + ssh_string newpath; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + oldpath = ssh_string_from_char(original); + if (oldpath == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + newpath = ssh_string_from_char(newname); + if (newpath == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(oldpath); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, oldpath) < 0 || + buffer_add_ssh_string(buffer, newpath) < 0 || + /* POSIX rename atomically replaces newpath, we should do the same + * only available on >=v4 */ + sftp->version>=4 ? (buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE) < 0):0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(oldpath); + ssh_string_free(newpath); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(oldpath); + ssh_string_free(newpath); + return -1; + } + ssh_buffer_free(buffer); + ssh_string_free(oldpath); + ssh_string_free(newpath); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, if it didn't, + * then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Code written by Nick */ +/* Set file attributes on a file, directory or symbolic link. */ +int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) { + uint32_t id; + ssh_buffer buffer; + ssh_string path; + sftp_message msg = NULL; + sftp_status_message status = NULL; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + path = ssh_string_from_char(file); + if (path == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, path) < 0 || + buffer_add_attributes(buffer, attr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(path); + return -1; + } + ssh_buffer_free(buffer); + ssh_string_free(path); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +/* Change the file owner and group */ +int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.uid = owner; + attr.gid = group; + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change permissions of a file */ +int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change the last modification and access time of a file. */ +int sftp_utimes(sftp_session sftp, const char *file, + const struct timeval *times) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.atime = times[0].tv_sec; + attr.atime_nseconds = times[0].tv_usec; + + attr.mtime = times[1].tv_sec; + attr.mtime_nseconds = times[1].tv_usec; + + attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES; + + return sftp_setstat(sftp, file, &attr); +} + +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string target_s; + ssh_string dest_s; + ssh_buffer buffer; + uint32_t id; + + if (sftp == NULL) + return -1; + if (target == NULL || dest == NULL) { + ssh_set_error_invalid(sftp->session); + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + target_s = ssh_string_from_char(target); + if (target_s == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return -1; + } + + dest_s = ssh_string_from_char(dest); + if (dest_s == NULL) { + ssh_set_error_oom(sftp->session); + ssh_string_free(target_s); + ssh_buffer_free(buffer); + return -1; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(dest_s); + ssh_string_free(target_s); + return -1; + } + if (ssh_get_openssh_version(sftp->session)) { + /* TODO check for version number if they ever fix it. */ + if (buffer_add_ssh_string(buffer, target_s) < 0 || + buffer_add_ssh_string(buffer, dest_s) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(dest_s); + ssh_string_free(target_s); + return -1; + } + } else { + if (buffer_add_ssh_string(buffer, dest_s) < 0 || + buffer_add_ssh_string(buffer, target_s) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(dest_s); + ssh_string_free(target_s); + return -1; + } + } + + if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(dest_s); + ssh_string_free(target_s); + return -1; + } + ssh_buffer_free(buffer); + ssh_string_free(dest_s); + ssh_string_free(target_s); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return -1; +} + +char *sftp_readlink(sftp_session sftp, const char *path) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string path_s = NULL; + ssh_string link_s = NULL; + ssh_buffer buffer; + char *lnk; + uint32_t ignored; + uint32_t id; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); + return NULL; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + path_s = ssh_string_from_char(path); + if (path_s == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, path_s) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(path_s); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_READLINK, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(path_s); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(path_s); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + /* we don't care about "count" */ + buffer_get_u32(msg->payload, &ignored); + /* we only care about the file name string */ + link_s = buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (link_s == NULL) { + /* TODO: what error to set here? */ + return NULL; + } + lnk = ssh_string_to_char(link_s); + ssh_string_free(link_s); + + return lnk; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { + sftp_statvfs_t statvfs; + uint64_t tmp; + int ok = 0; + + statvfs = malloc(sizeof(struct sftp_statvfs_struct)); + if (statvfs == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + ZERO_STRUCTP(statvfs); + + /* try .. catch */ + do { + /* file system block size */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_bsize = ntohll(tmp); + + /* fundamental fs block size */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_frsize = ntohll(tmp); + + /* number of blocks (unit f_frsize) */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_blocks = ntohll(tmp); + + /* free blocks in file system */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_bfree = ntohll(tmp); + + /* free blocks for non-root */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_bavail = ntohll(tmp); + + /* total file inodes */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_files = ntohll(tmp); + + /* free file inodes */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_ffree = ntohll(tmp); + + /* free file inodes for to non-root */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_favail = ntohll(tmp); + + /* file system id */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_fsid = ntohll(tmp); + + /* bit mask of f_flag values */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_flag = ntohll(tmp); + + /* maximum filename length */ + if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) { + break; + } + statvfs->f_namemax = ntohll(tmp); + + ok = 1; + } while(0); + + if (!ok) { + SAFE_FREE(statvfs); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); + return NULL; + } + + return statvfs; +} + +sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string pathstr; + ssh_string ext; + ssh_buffer buffer; + uint32_t id; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ext = ssh_string_from_char("statvfs@openssh.com"); + if (ext == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + pathstr = ssh_string_from_char(path); + if (pathstr == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, ext) < 0 || + buffer_add_ssh_string(buffer, pathstr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(ext); + ssh_string_free(pathstr); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to get statvfs", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +sftp_statvfs_t sftp_fstatvfs(sftp_file file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_session sftp; + ssh_string ext; + ssh_buffer buffer; + uint32_t id; + + if (file == NULL) { + return NULL; + } + sftp = file->sftp; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ext = ssh_string_from_char("fstatvfs@openssh.com"); + if (ext == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, ext) < 0 || + buffer_add_ssh_string(buffer, file->handle) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(ext); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(ext); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +void sftp_statvfs_free(sftp_statvfs_t statvfs) { + if (statvfs == NULL) { + return; + } + + SAFE_FREE(statvfs); +} + +/* another code written by Nick */ +char *sftp_canonicalize_path(sftp_session sftp, const char *path) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string name = NULL; + ssh_string pathstr; + ssh_buffer buffer; + char *cname; + uint32_t ignored; + uint32_t id; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + pathstr = ssh_string_from_char(path); + if (pathstr == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, pathstr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + if (sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + /* we don't care about "count" */ + buffer_get_u32(msg->payload, &ignored); + /* we only care about the file name string */ + name = buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (name == NULL) { + /* TODO: error message? */ + return NULL; + } + cname = ssh_string_to_char(name); + ssh_string_free(name); + if (cname == NULL) { + ssh_set_error_oom(sftp->session); + } + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +static sftp_attributes sftp_xstat(sftp_session sftp, const char *path, + int param) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_string pathstr; + ssh_buffer buffer; + uint32_t id; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + pathstr = ssh_string_from_char(path); + if (pathstr == NULL) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + + id = sftp_get_new_id(sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, pathstr) < 0) { + ssh_set_error_oom(sftp->session); + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + if (sftp_packet_write(sftp, param, buffer) < 0) { + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + return NULL; + } + ssh_buffer_free(buffer); + ssh_string_free(pathstr); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_ATTRS) { + 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; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(file->sftp->session); + return NULL; + } + + id = sftp_get_new_id(file->sftp); + if (buffer_add_u32(buffer, id) < 0 || + buffer_add_ssh_string(buffer, file->handle) < 0) { + ssh_set_error_oom(file->sftp->session); + ssh_buffer_free(buffer); + return NULL; + } + if (sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer) < 0) { + ssh_buffer_free(buffer); + return NULL; + } + ssh_buffer_free(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(file->sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(file->sftp, id); + } + + if (msg->packet_type == SSH_FXP_ATTRS){ + return sftp_parse_attr(file->sftp, msg->payload, 0); + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + } + ssh_set_error(file->sftp->session, SSH_FATAL, + "Received msg %d during fstat()", msg->packet_type); + sftp_message_free(msg); + + return NULL; +} + +#endif /* WITH_SFTP */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/sftpserver.c b/libssh/src/sftpserver.c new file mode 100644 index 00000000..3ae93a99 --- /dev/null +++ b/libssh/src/sftpserver.c @@ -0,0 +1,518 @@ +/* + * sftpserver.c - server based function for the sftp protocol + * + * This file is part of the SSH Library + * + * Copyright (c) 2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/sftp.h" +#include "libssh/ssh2.h" +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" + +sftp_client_message sftp_get_client_message(sftp_session sftp) { + ssh_session session = sftp->session; + sftp_packet packet; + sftp_client_message msg; + ssh_buffer payload; + ssh_string tmp; + + msg = malloc(sizeof (struct sftp_client_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(msg); + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + + payload = packet->payload; + msg->type = packet->type; + msg->sftp = sftp; + + buffer_get_u32(payload, &msg->id); + + switch(msg->type) { + case SSH_FXP_CLOSE: + case SSH_FXP_READDIR: + msg->handle = buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_READ: + msg->handle = buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + buffer_get_u64(payload, &msg->offset); + buffer_get_u32(payload, &msg->len); + break; + case SSH_FXP_WRITE: + msg->handle = buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + buffer_get_u64(payload, &msg->offset); + msg->data = buffer_get_ssh_string(payload); + if (msg->data == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_REMOVE: + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_READLINK: + case SSH_FXP_REALPATH: + tmp = buffer_get_ssh_string(payload); + if (tmp == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->filename = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (msg->filename == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_RENAME: + case SSH_FXP_SYMLINK: + tmp = buffer_get_ssh_string(payload); + if (tmp == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->filename = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (msg->filename == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->data = buffer_get_ssh_string(payload); + if (msg->data == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_MKDIR: + case SSH_FXP_SETSTAT: + tmp = buffer_get_ssh_string(payload); + if (tmp == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->filename=ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (msg->filename == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_FSETSTAT: + msg->handle = buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_LSTAT: + case SSH_FXP_STAT: + tmp = buffer_get_ssh_string(payload); + if (tmp == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->filename = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (msg->filename == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + if(sftp->version > 3) { + buffer_get_u32(payload,&msg->flags); + } + break; + case SSH_FXP_OPEN: + tmp=buffer_get_ssh_string(payload); + if (tmp == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + msg->filename = ssh_string_to_char(tmp); + ssh_string_free(tmp); + if (msg->filename == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + buffer_get_u32(payload,&msg->flags); + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + break; + case SSH_FXP_FSTAT: + msg->handle = buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + ssh_set_error_oom(session); + sftp_client_message_free(msg); + return NULL; + } + buffer_get_u32(payload, &msg->flags); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received unhandled sftp message %d\n", msg->type); + sftp_client_message_free(msg); + return NULL; + } + + msg->flags = ntohl(msg->flags); + msg->offset = ntohll(msg->offset); + msg->len = ntohl(msg->len); + sftp_packet_free(packet); + + return msg; +} + +void sftp_client_message_free(sftp_client_message msg) { + if (msg == NULL) { + return; + } + + SAFE_FREE(msg->filename); + ssh_string_free(msg->data); + ssh_string_free(msg->handle); + sftp_attributes_free(msg->attr); + + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr) { + ssh_buffer out; + ssh_string file; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + file = ssh_string_from_char(name); + if (file == NULL) { + ssh_buffer_free(out); + return -1; + } + + if (buffer_add_u32(out, msg->id) < 0 || + buffer_add_u32(out, htonl(1)) < 0 || + buffer_add_ssh_string(out, file) < 0 || + buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + ssh_buffer_free(out); + ssh_string_free(file); + return -1; + } + ssh_buffer_free(out); + ssh_string_free(file); + + return 0; +} + +int sftp_reply_handle(sftp_client_message msg, ssh_string handle){ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + if (buffer_add_u32(out, msg->id) < 0 || + buffer_add_ssh_string(out, handle) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + ssh_buffer_free(out); + return -1; + } + ssh_buffer_free(out); + + return 0; +} + +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) { + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + if (buffer_add_u32(out, msg->id) < 0 || + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + ssh_buffer_free(out); + return -1; + } + ssh_buffer_free(out); + + return 0; +} + +int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr) { + ssh_string name; + + name = ssh_string_from_char(file); + if (name == NULL) { + return -1; + } + + if (msg->attrbuf == NULL) { + msg->attrbuf = ssh_buffer_new(); + if (msg->attrbuf == NULL) { + ssh_string_free(name); + return -1; + } + } + + if (buffer_add_ssh_string(msg->attrbuf, name) < 0) { + ssh_string_free(name); + return -1; + } + + ssh_string_free(name); + name = ssh_string_from_char(longname); + if (name == NULL) { + return -1; + } + if (buffer_add_ssh_string(msg->attrbuf,name) < 0 || + buffer_add_attributes(msg->attrbuf,attr) < 0) { + ssh_string_free(name); + return -1; + } + ssh_string_free(name); + msg->attr_num++; + + return 0; +} + +int sftp_reply_names(sftp_client_message msg) { + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + ssh_buffer_free(msg->attrbuf); + return -1; + } + + if (buffer_add_u32(out, msg->id) < 0 || + buffer_add_u32(out, htonl(msg->attr_num)) < 0 || + buffer_add_data(out, buffer_get_rest(msg->attrbuf), + buffer_get_rest_len(msg->attrbuf)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + ssh_buffer_free(out); + ssh_buffer_free(msg->attrbuf); + return -1; + } + + ssh_buffer_free(out); + ssh_buffer_free(msg->attrbuf); + + msg->attr_num = 0; + msg->attrbuf = NULL; + + return 0; +} + +int sftp_reply_status(sftp_client_message msg, uint32_t status, + const char *message) { + ssh_buffer out; + ssh_string s; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + s = ssh_string_from_char(message ? message : ""); + if (s == NULL) { + ssh_buffer_free(out); + return -1; + } + + if (buffer_add_u32(out, msg->id) < 0 || + buffer_add_u32(out, htonl(status)) < 0 || + buffer_add_ssh_string(out, s) < 0 || + buffer_add_u32(out, 0) < 0 || /* language string */ + sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { + ssh_buffer_free(out); + ssh_string_free(s); + return -1; + } + + ssh_buffer_free(out); + ssh_string_free(s); + + return 0; +} + +int sftp_reply_data(sftp_client_message msg, const void *data, int len) { + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + if (buffer_add_u32(out, msg->id) < 0 || + buffer_add_u32(out, ntohl(len)) < 0 || + buffer_add_data(out, data, len) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + ssh_buffer_free(out); + return -1; + } + ssh_buffer_free(out); + + return 0; +} + +/* + * This function will return you a new handle to give the client. + * the function accepts an info that can be retrieved later with + * the handle. Care is given that a corrupted handle won't give a + * valid info (or worse). + */ +ssh_string sftp_handle_alloc(sftp_session sftp, void *info) { + ssh_string ret; + uint32_t val; + int i; + + if (sftp->handles == NULL) { + sftp->handles = malloc(sizeof(void *) * SFTP_HANDLES); + if (sftp->handles == NULL) { + return NULL; + } + memset(sftp->handles, 0, sizeof(void *) * SFTP_HANDLES); + } + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == NULL) { + break; + } + } + + if (i == SFTP_HANDLES) { + return NULL; /* no handle available */ + } + + val = i; + ret = ssh_string_new(4); + if (ret == NULL) { + return NULL; + } + + memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); + sftp->handles[i] = info; + + return ret; +} + +void *sftp_handle(sftp_session sftp, ssh_string handle){ + uint32_t val; + + if (sftp->handles == NULL) { + return NULL; + } + + if (ssh_string_len(handle) != sizeof(uint32_t)) { + return NULL; + } + + memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); + + if (val > SFTP_HANDLES) { + return NULL; + } + + return sftp->handles[val]; +} + +void sftp_handle_remove(sftp_session sftp, void *handle) { + int i; + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == handle) { + sftp->handles[i] = NULL; + break; + } + } +} + +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/src/socket.c b/libssh/src/socket.c new file mode 100644 index 00000000..85b87b77 --- /dev/null +++ b/libssh/src/socket.c @@ -0,0 +1,846 @@ +/* + * socket.c - socket functions for the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#if _MSC_VER >= 1400 +#include +#undef open +#define open _open +#undef close +#define close _close +#undef read +#define read _read +#undef write +#define write _write +#endif /* _MSC_VER */ +#else /* _WIN32 */ +#include +#include +#include +#include +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/session.h" + +/** + * @internal + * + * @defgroup libssh_socket The SSH socket functions. + * @ingroup libssh + * + * Functions for handling sockets. + * + * @{ + */ + +enum ssh_socket_states_e { + SSH_SOCKET_NONE, + SSH_SOCKET_CONNECTING, + SSH_SOCKET_CONNECTED, + SSH_SOCKET_EOF, + SSH_SOCKET_ERROR, + SSH_SOCKET_CLOSED +}; + +struct ssh_socket_struct { + socket_t fd_in; + socket_t fd_out; + int fd_is_socket; + int last_errno; + int read_wontblock; /* reading now on socket will + not block */ + int write_wontblock; + int data_except; + enum ssh_socket_states_e state; + ssh_buffer out_buffer; + ssh_buffer in_buffer; + ssh_session session; + ssh_socket_callbacks callbacks; + ssh_poll_handle poll_in; + ssh_poll_handle poll_out; +}; + +static int sockets_initialized = 0; + +static int ssh_socket_unbuffered_read(ssh_socket s, void *buffer, uint32_t len); +static int ssh_socket_unbuffered_write(ssh_socket s, const void *buffer, + uint32_t len); + +/** + * \internal + * \brief inits the socket system (windows specific) + */ +int ssh_socket_init(void) { + if (sockets_initialized == 0) { +#ifdef _WIN32 + struct WSAData wsaData; + + /* Initiates use of the Winsock DLL by a process. */ + if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { + return -1; + } + +#endif + ssh_poll_init(); + + sockets_initialized = 1; + } + + return 0; +} + +/** + * @brief Cleanup the socket system. + */ +void ssh_socket_cleanup(void) { + if (sockets_initialized == 1) { + ssh_poll_cleanup(); +#ifdef _WIN32 + WSACleanup(); +#endif + sockets_initialized = 0; + } +} + + +/** + * \internal + * \brief creates a new Socket object + */ +ssh_socket ssh_socket_new(ssh_session session) { + ssh_socket s; + + s = malloc(sizeof(struct ssh_socket_struct)); + if (s == NULL) { + ssh_set_error_oom(session); + return NULL; + } + s->fd_in = SSH_INVALID_SOCKET; + s->fd_out= SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + s->session = session; + s->in_buffer = ssh_buffer_new(); + if (s->in_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(s); + return NULL; + } + s->out_buffer=ssh_buffer_new(); + if (s->out_buffer == NULL) { + ssh_set_error_oom(session); + ssh_buffer_free(s->in_buffer); + SAFE_FREE(s); + return NULL; + } + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + s->poll_in=s->poll_out=NULL; + s->state=SSH_SOCKET_NONE; + return s; +} + +/** + * @internal + * @brief Reset the state of a socket so it looks brand-new + * @param[in] s socket to rest + */ +void ssh_socket_reset(ssh_socket s){ + s->fd_in = SSH_INVALID_SOCKET; + s->fd_out= SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + buffer_reinit(s->in_buffer); + buffer_reinit(s->out_buffer); + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + s->poll_in=s->poll_out=NULL; + s->state=SSH_SOCKET_NONE; +} + +/** + * @internal + * @brief the socket callbacks, i.e. callbacks to be called + * upon a socket event. + * @param s socket to set callbacks on. + * @param callbacks a ssh_socket_callback object reference. + */ + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks){ + s->callbacks=callbacks; +} + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param userdata Userdata to be passed to the callback function, + * in this case the socket object. + * + * @return 0 on success, < 0 when the poll object has been removed + * from its poll context. + */ +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s){ + ssh_socket s=(ssh_socket )v_s; + char buffer[4096]; + int r; + int err=0; + socklen_t errlen=sizeof(err); + /* Do not do anything if this socket was already closed */ + if(!ssh_socket_is_open(s)){ + return -1; + } + if(revents & POLLERR || revents & POLLHUP){ + /* Check if we are in a connecting state */ + if(s->state==SSH_SOCKET_CONNECTING){ + s->state=SSH_SOCKET_ERROR; + getsockopt(fd,SOL_SOCKET,SO_ERROR,(char *)&err,&errlen); + s->last_errno=err; + ssh_socket_close(s); + if(s->callbacks && s->callbacks->connected) + s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR,err, + s->callbacks->userdata); + return -1; + } + /* Then we are in a more standard kind of error */ + /* force a read to get an explanation */ + revents |= POLLIN; + } + if(revents & POLLIN){ + s->read_wontblock=1; + r=ssh_socket_unbuffered_read(s,buffer,sizeof(buffer)); + if(r<0){ + if(p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + if(s->callbacks && s->callbacks->exception){ + s->callbacks->exception( + SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno,s->callbacks->userdata); + /* p may have been freed, so don't use it + * anymore in this function */ + p = NULL; + return -2; + } + } + if(r==0){ + if(p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + if(p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + if(s->callbacks && s->callbacks->exception){ + s->callbacks->exception( + SSH_SOCKET_EXCEPTION_EOF, + 0,s->callbacks->userdata); + /* p may have been freed, so don't use it + * anymore in this function */ + p = NULL; + return -2; + } + } + if(r>0){ + /* Bufferize the data and then call the callback */ + r = buffer_add_data(s->in_buffer,buffer,r); + if (r < 0) { + return -1; + } + if(s->callbacks && s->callbacks->data){ + do { + r= s->callbacks->data(buffer_get_rest(s->in_buffer), + buffer_get_rest_len(s->in_buffer), + s->callbacks->userdata); + buffer_pass_bytes(s->in_buffer,r); + } while (r > 0); + /* p may have been freed, so don't use it + * anymore in this function */ + p = NULL; + } + } + } +#ifdef _WIN32 + if(revents & POLLOUT || revents & POLLWRNORM){ +#else + if(revents & POLLOUT){ +#endif + /* First, POLLOUT is a sign we may be connected */ + if(s->state == SSH_SOCKET_CONNECTING){ + ssh_log(s->session,SSH_LOG_PACKET,"Received POLLOUT in connecting state"); + s->state = SSH_SOCKET_CONNECTED; + ssh_poll_set_events(p,POLLOUT | POLLIN); + ssh_socket_set_blocking(ssh_socket_get_fd_in(s)); + if(s->callbacks && s->callbacks->connected) + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK,0,s->callbacks->userdata); + return 0; + } + /* So, we can write data */ + s->write_wontblock=1; + if(p != NULL) { + ssh_poll_remove_events(p, POLLOUT); + } + + /* If buffered data is pending, write it */ + if(buffer_get_rest_len(s->out_buffer) > 0){ + ssh_socket_nonblocking_flush(s); + } else if(s->callbacks && s->callbacks->controlflow){ + /* Otherwise advertise the upper level that write can be done */ + s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK,s->callbacks->userdata); + } + /* TODO: Find a way to put back POLLOUT when buffering occurs */ + } + /* Return -1 if one of the poll handlers disappeared */ + return (s->poll_in == NULL || s->poll_out == NULL) ? -1 : 0; +} + +/** @internal + * @brief returns the input poll handle corresponding to the socket, + * creates it if it does not exist. + * @returns allocated and initialized ssh_poll_handle object + */ +ssh_poll_handle ssh_socket_get_poll_handle_in(ssh_socket s){ + if(s->poll_in) + return s->poll_in; + s->poll_in=ssh_poll_new(s->fd_in,0,ssh_socket_pollcallback,s); + if(s->fd_in == s->fd_out && s->poll_out == NULL) + s->poll_out=s->poll_in; + return s->poll_in; +} + +/** @internal + * @brief returns the output poll handle corresponding to the socket, + * creates it if it does not exist. + * @returns allocated and initialized ssh_poll_handle object + */ +ssh_poll_handle ssh_socket_get_poll_handle_out(ssh_socket s){ + if(s->poll_out) + return s->poll_out; + s->poll_out=ssh_poll_new(s->fd_out,0,ssh_socket_pollcallback,s); + if(s->fd_in == s->fd_out && s->poll_in == NULL) + s->poll_in=s->poll_out; + return s->poll_out; +} + +/** \internal + * \brief Deletes a socket object + */ +void ssh_socket_free(ssh_socket s){ + if (s == NULL) { + return; + } + ssh_socket_close(s); + ssh_buffer_free(s->in_buffer); + ssh_buffer_free(s->out_buffer); + SAFE_FREE(s); +} + +#ifndef _WIN32 +int ssh_socket_unix(ssh_socket s, const char *path) { + struct sockaddr_un sunaddr; + socket_t fd; + sunaddr.sun_family = AF_UNIX; + snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == SSH_INVALID_SOCKET) { + ssh_set_error(s->session, SSH_FATAL, + "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", + strerror(errno)); + return -1; + } + + if (fcntl(fd, F_SETFD, 1) == -1) { + ssh_set_error(s->session, SSH_FATAL, + "Error from fcntl(fd, F_SETFD, 1): %s", + strerror(errno)); + close(fd); + return -1; + } + + if (connect(fd, (struct sockaddr *) &sunaddr, + sizeof(sunaddr)) < 0) { + ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", + strerror(errno)); + close(fd); + return -1; + } + ssh_socket_set_fd(s,fd); + return 0; +} +#endif + +/** \internal + * \brief closes a socket + */ +void ssh_socket_close(ssh_socket s){ + if (ssh_socket_is_open(s)) { +#ifdef _WIN32 + closesocket(s->fd_in); + /* fd_in = fd_out under win32 */ + s->last_errno = WSAGetLastError(); +#else + close(s->fd_in); + if(s->fd_out != s->fd_in && s->fd_out != -1) + close(s->fd_out); + s->last_errno = errno; +#endif + s->fd_in = s->fd_out = SSH_INVALID_SOCKET; + } + if(s->poll_in != NULL){ + if(s->poll_out == s->poll_in) + s->poll_out = NULL; + ssh_poll_free(s->poll_in); + s->poll_in=NULL; + } + if(s->poll_out != NULL){ + ssh_poll_free(s->poll_out); + s->poll_out=NULL; + } +} + +/** + * @internal + * @brief sets the file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + * @warning this function updates boths the input and output + * file descriptors + */ +void ssh_socket_set_fd(ssh_socket s, socket_t fd) { + s->fd_in = s->fd_out = fd; + if(s->poll_in) + ssh_poll_set_fd(s->poll_in,fd); +} + +/** + * @internal + * @brief sets the input file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + */ +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd) { + s->fd_in = fd; + if(s->poll_in) + ssh_poll_set_fd(s->poll_in,fd); +} + +/** + * @internal + * @brief sets the output file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + */ +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd) { + s->fd_out = fd; + if(s->poll_out) + ssh_poll_set_fd(s->poll_out,fd); +} + + + +/** \internal + * \brief returns the input file descriptor of the socket + */ +socket_t ssh_socket_get_fd_in(ssh_socket s) { + return s->fd_in; +} + +/** \internal + * \brief returns nonzero if the socket is open + */ +int ssh_socket_is_open(ssh_socket s) { + return s->fd_in != SSH_INVALID_SOCKET; +} + +/** \internal + * \brief read len bytes from socket into buffer + */ +static int ssh_socket_unbuffered_read(ssh_socket s, void *buffer, uint32_t len) { + int rc = -1; + + if (s->data_except) { + return -1; + } + if(s->fd_is_socket) + rc = recv(s->fd_in,buffer, len, 0); + else + rc = read(s->fd_in,buffer, len); +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->read_wontblock = 0; + + if (rc < 0) { + s->data_except = 1; + } + + return rc; +} + +/** \internal + * \brief writes len bytes from buffer to socket + */ +static int ssh_socket_unbuffered_write(ssh_socket s, const void *buffer, + uint32_t len) { + int w = -1; + + if (s->data_except) { + return -1; + } + if (s->fd_is_socket) + w = send(s->fd_out,buffer, len, 0); + else + w = write(s->fd_out, buffer, len); +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->write_wontblock = 0; + /* Reactive the POLLOUT detector in the poll multiplexer system */ + if(s->poll_out){ + ssh_log(s->session, SSH_LOG_PACKET, "Enabling POLLOUT for socket"); + ssh_poll_set_events(s->poll_out,ssh_poll_get_events(s->poll_out) | POLLOUT); + } + if (w < 0) { + s->data_except = 1; + } + + return w; +} + +/** \internal + * \brief returns nonzero if the current socket is in the fd_set + */ +int ssh_socket_fd_isset(ssh_socket s, fd_set *set) { + if(s->fd_in == SSH_INVALID_SOCKET) { + return 0; + } + return FD_ISSET(s->fd_in,set) || FD_ISSET(s->fd_out,set); +} + +/** \internal + * \brief sets the current fd in a fd_set and updates the max_fd + */ + +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) { + if (s->fd_in == SSH_INVALID_SOCKET) { + return; + } + + FD_SET(s->fd_in,set); + FD_SET(s->fd_out,set); + + if (s->fd_in >= 0 && + s->fd_in >= *max_fd && + s->fd_in != SSH_INVALID_SOCKET) { + *max_fd = s->fd_in + 1; + } + if (s->fd_out >= 0 && + s->fd_out >= *max_fd && + s->fd_out != SSH_INVALID_SOCKET) { + *max_fd = s->fd_out + 1; + } +} + +/** \internal + * \brief buffered write of data + * \returns SSH_OK, or SSH_ERROR + * \warning has no effect on socket before a flush + */ +int ssh_socket_write(ssh_socket s, const void *buffer, int len) { + ssh_session session = s->session; + enter_function(); + if(len > 0) { + if (buffer_add_data(s->out_buffer, buffer, len) < 0) { + ssh_set_error_oom(s->session); + return SSH_ERROR; + } + ssh_socket_nonblocking_flush(s); + } + leave_function(); + return SSH_OK; +} + + +/** \internal + * \brief starts a nonblocking flush of the output buffer + * + */ +int ssh_socket_nonblocking_flush(ssh_socket s) { + ssh_session session = s->session; + uint32_t len; + int w; + + enter_function(); + + if (!ssh_socket_is_open(s)) { + session->alive = 0; + /* FIXME use ssh_socket_get_errno */ + ssh_set_error(session, SSH_FATAL, + "Writing packet: error on socket (or connection closed): %s", + strerror(s->last_errno)); + + leave_function(); + return SSH_ERROR; + } + + len = buffer_get_rest_len(s->out_buffer); + if (!s->write_wontblock && s->poll_out && len > 0) { + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_out, POLLOUT); + leave_function(); + return SSH_AGAIN; + } + if (s->write_wontblock && len > 0) { + w = ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer), len); + if (w < 0) { + session->alive = 0; + ssh_socket_close(s); + /* FIXME use ssh_socket_get_errno() */ + /* FIXME use callback for errors */ + ssh_set_error(session, SSH_FATAL, + "Writing packet: error on socket (or connection closed): %s", + strerror(s->last_errno)); + leave_function(); + return SSH_ERROR; + } + buffer_pass_bytes(s->out_buffer, w); + } + + /* Is there some data pending? */ + len = buffer_get_rest_len(s->out_buffer); + if (s->poll_out && len > 0) { + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_out, POLLOUT); + leave_function(); + return SSH_AGAIN; + } + + /* all data written */ + leave_function(); + return SSH_OK; +} + +void ssh_socket_set_write_wontblock(ssh_socket s) { + s->write_wontblock = 1; +} + +void ssh_socket_set_read_wontblock(ssh_socket s) { + s->read_wontblock = 1; +} + +void ssh_socket_set_except(ssh_socket s) { + s->data_except = 1; +} + +int ssh_socket_data_available(ssh_socket s) { + return s->read_wontblock; +} + +int ssh_socket_data_writable(ssh_socket s) { + return s->write_wontblock; +} + +/** @internal + * @brief returns the number of outgoing bytes currently buffered + * @param s the socket + * @returns numbers of bytes buffered, or 0 if the socket isn't connected + */ +int ssh_socket_buffered_write_bytes(ssh_socket s){ + if(s==NULL || s->out_buffer == NULL) + return 0; + return buffer_get_rest_len(s->out_buffer); +} + + +int ssh_socket_get_status(ssh_socket s) { + int r = 0; + + if (s->read_wontblock) { + r |= SSH_READ_PENDING; + } + + if (s->write_wontblock) { + r |= SSH_WRITE_PENDING; + } + + if (s->data_except) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +#ifdef _WIN32 +void ssh_socket_set_nonblocking(socket_t fd) { + u_long nonblocking = 1; + ioctlsocket(fd, FIONBIO, &nonblocking); +} + +void ssh_socket_set_blocking(socket_t fd) { + u_long nonblocking = 0; + ioctlsocket(fd, FIONBIO, &nonblocking); +} + +#else /* _WIN32 */ +void ssh_socket_set_nonblocking(socket_t fd) { + fcntl(fd, F_SETFL, O_NONBLOCK); +} + +void ssh_socket_set_blocking(socket_t fd) { + fcntl(fd, F_SETFL, 0); +} +#endif /* _WIN32 */ + +/** + * @internal + * @brief Launches a socket connection + * If a the socket connected callback has been defined and + * a poll object exists, this call will be non blocking. + * @param s socket to connect. + * @param host hostname or ip address to connect to. + * @param port port number to connect to. + * @param bind_addr address to bind to, or NULL for default. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while connecting to remote host. + * @bug It only tries connecting to one of the available AI's + * which is problematic for hosts having DNS fail-over. + */ + +int ssh_socket_connect(ssh_socket s, const char *host, int port, const char *bind_addr){ + socket_t fd; + ssh_session session=s->session; + enter_function(); + if(s->state != SSH_SOCKET_NONE) { + ssh_set_error(s->session, SSH_FATAL, + "ssh_socket_connect called on socket not unconnected"); + return SSH_ERROR; + } + fd=ssh_connect_host_nonblocking(s->session,host,bind_addr,port); + ssh_log(session,SSH_LOG_PROTOCOL,"Nonblocking connection socket: %d",fd); + if(fd == SSH_INVALID_SOCKET) + return SSH_ERROR; + ssh_socket_set_fd(s,fd); + s->state=SSH_SOCKET_CONNECTING; + /* POLLOUT is the event to wait for in a nonblocking connect */ + ssh_poll_set_events(ssh_socket_get_poll_handle_in(s),POLLOUT); +#ifdef _WIN32 + ssh_poll_add_events(ssh_socket_get_poll_handle_in(s),POLLWRNORM); +#endif + leave_function(); + return SSH_OK; +} + +#ifndef _WIN32 +/** + * @internal + * @brief executes a command and redirect input and outputs + * @param command command to execute + * @param in input file descriptor + * @param out output file descriptor + */ +void ssh_execute_command(const char *command, socket_t in, socket_t out){ + const char *args[]={"/bin/sh","-c",command,NULL}; + /* redirect in and out to stdin, stdout and stderr */ + dup2(in, 0); + dup2(out,1); + dup2(out,2); + close(in); + close(out); + execv(args[0],(char * const *)args); + exit(1); +} + +/** + * @internal + * @brief Open a socket on a ProxyCommand + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while executing the command. + */ + +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command){ + socket_t in_pipe[2]; + socket_t out_pipe[2]; + int pid; + int rc; + ssh_session session=s->session; + enter_function(); + if(s->state != SSH_SOCKET_NONE) + return SSH_ERROR; + + rc = pipe(in_pipe); + if (rc < 0) { + return SSH_ERROR; + } + rc = pipe(out_pipe); + if (rc < 0) { + return SSH_ERROR; + } + + ssh_log(session,SSH_LOG_PROTOCOL,"Executing proxycommand '%s'",command); + pid = fork(); + if(pid == 0){ + ssh_execute_command(command,out_pipe[0],in_pipe[1]); + } + close(in_pipe[1]); + close(out_pipe[0]); + ssh_log(session,SSH_LOG_PROTOCOL,"ProxyCommand connection pipe: [%d,%d]",in_pipe[0],out_pipe[1]); + ssh_socket_set_fd_in(s,in_pipe[0]); + ssh_socket_set_fd_out(s,out_pipe[1]); + s->state=SSH_SOCKET_CONNECTED; + s->fd_is_socket=0; + /* POLLOUT is the event to wait for in a nonblocking connect */ + ssh_poll_set_events(ssh_socket_get_poll_handle_in(s),POLLIN); + ssh_poll_set_events(ssh_socket_get_poll_handle_out(s),POLLOUT); + if(s->callbacks && s->callbacks->connected) + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK,0,s->callbacks->userdata); + leave_function(); + return SSH_OK; +} + +#endif /* _WIN32 */ +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/string.c b/libssh/src/string.c new file mode 100644 index 00000000..5ef90b0e --- /dev/null +++ b/libssh/src/string.c @@ -0,0 +1,270 @@ +/* + * string.c - ssh string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/string.h" + +/** + * @defgroup libssh_string The SSH string functions + * @ingroup libssh + * + * @brief String manipulations used in libssh. + * + * @{ + */ + +/** + * @brief Create a new SSH String object. + * + * @param[in] size The size of the string. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_new(size_t size) { + struct ssh_string_struct *str = NULL; + + if (size > UINT_MAX - sizeof(struct ssh_string_struct)) { + return NULL; + } + + str = malloc(sizeof(struct ssh_string_struct) + size); + if (str == NULL) { + return NULL; + } + + str->size = htonl(size); + str->data[0] = 0; + + return str; +} + +/** + * @brief Fill a string with given data. The string should be big enough. + * + * @param s An allocated string to fill with data. + * + * @param data The data to fill the string with. + * + * @param len Size of data. + * + * @return 0 on success, < 0 on error. + */ +int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { + if ((s == NULL) || (data == NULL) || + (len == 0) || (len > ssh_string_len(s))) { + return -1; + } + + memcpy(s->data, data, len); + + return 0; +} + +/** + * @brief Create a ssh string using a C string + * + * @param[in] what The source 0-terminated C string. + * + * @return The newly allocated string, NULL on error with errno + * set. + * + * @note The nul byte is not copied nor counted in the ouput string. + */ +struct ssh_string_struct *ssh_string_from_char(const char *what) { + struct ssh_string_struct *ptr; + size_t len; + + if(what == NULL) { + errno = EINVAL; + return NULL; + } + + len = strlen(what); + + ptr = ssh_string_new(len); + if (ptr == NULL) { + return NULL; + } + + memcpy(ptr->data, what, len); + + return ptr; +} + +/** + * @brief Return the size of a SSH string. + * + * @param[in] s The the input SSH string. + * + * @return The size of the content of the string, 0 on error. + */ +size_t ssh_string_len(struct ssh_string_struct *s) { + if (s == NULL) { + return ntohl(0); + } + + return ntohl(s->size); +} + +/** + * @brief Get the the string as a C nul-terminated string. + * + * This is only available as long as the SSH string exists. + * + * @param[in] s The SSH string to get the C string from. + * + * @return The char pointer, NULL on error. + */ +const char *ssh_string_get_char(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } + s->data[ssh_string_len(s)] = '\0'; + + return (const char *) s->data; +} + +/** + * @brief Convert a SSH string to a C nul-terminated string. + * + * @param[in] s The SSH input string. + * + * @return An allocated string pointer, NULL on error with errno + * set. + * + * @note If the input SSH string contains zeroes, some parts of the output + * string may not be readable with regular libc functions. + */ +char *ssh_string_to_char(struct ssh_string_struct *s) { + size_t len; + char *new; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len + 1 < len) { + return NULL; + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + memcpy(new, s->data, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Deallocate a char string object. + * + * @param[in] s The string to delete. + */ +void ssh_string_free_char(char *s) { + SAFE_FREE(s); +} + +/** + * @brief Copy a string, return a newly allocated string. The caller has to + * free the string. + * + * @param[in] s String to copy. + * + * @return Newly allocated copy of the string, NULL on error. + */ +struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) { + struct ssh_string_struct *new; + size_t len; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len == 0) { + return NULL; + } + + new = ssh_string_new(len); + if (new == NULL) { + return NULL; + } + + memcpy(new->data, s->data, len); + + return new; +} + +/** + * @brief Destroy the data in a string so it couldn't appear in a core dump. + * + * @param[in] s The string to burn. + */ +void ssh_string_burn(struct ssh_string_struct *s) { + if (s == NULL) { + return; + } + memset(s->data, 'X', ssh_string_len(s)); +} + +/** + * @brief Get the payload of the string. + * + * @param s The string to get the data from. + * + * @return Return the data of the string or NULL on error. + */ +void *ssh_string_data(struct ssh_string_struct *s) { + if (s == NULL) { + return NULL; + } + + return s->data; +} + +/** + * @brief Deallocate a SSH string object. + * + * \param[in] s The SSH string to delete. + */ +void ssh_string_free(struct ssh_string_struct *s) { + SAFE_FREE(s); +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/libssh/src/threads.c b/libssh/src/threads.c new file mode 100644 index 00000000..107c65d2 --- /dev/null +++ b/libssh/src/threads.c @@ -0,0 +1,176 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @defgroup libssh_threads The SSH threading functions. + * @ingroup libssh + * + * Threading with libssh + * @{ + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" + +static int threads_noop (void **lock){ + (void)lock; + return 0; +} + +static unsigned long threads_id_noop (void){ + return 1; +} + +static struct ssh_threads_callbacks_struct ssh_threads_noop = +{ + "threads_noop", + threads_noop, + threads_noop, + threads_noop, + threads_noop, + threads_id_noop +}; + +struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void) { + return &ssh_threads_noop; +} + +static struct ssh_threads_callbacks_struct *user_callbacks =&ssh_threads_noop; + +#ifdef HAVE_LIBGCRYPT + +/* Libgcrypt specific way of handling thread callbacks */ + +static struct gcry_thread_cbs gcrypt_threads_callbacks; + +static int libgcrypt_thread_init(void){ + if(user_callbacks == NULL) + return SSH_ERROR; + if(user_callbacks == &ssh_threads_noop){ + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || GCRY_THREAD_OPTION_DEFAULT; + } else { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || GCRY_THREAD_OPTION_USER; + } + gcrypt_threads_callbacks.mutex_init=user_callbacks->mutex_init; + gcrypt_threads_callbacks.mutex_destroy=user_callbacks->mutex_destroy; + gcrypt_threads_callbacks.mutex_lock=user_callbacks->mutex_lock; + gcrypt_threads_callbacks.mutex_unlock=user_callbacks->mutex_unlock; + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcrypt_threads_callbacks); + return SSH_OK; +} +#else + +/* Libcrypto specific stuff */ + +static void **libcrypto_mutexes; + +static void libcrypto_lock_callback(int mode, int i, const char *file, int line){ + (void)file; + (void)line; + if(mode & CRYPTO_LOCK){ + user_callbacks->mutex_lock(&libcrypto_mutexes[i]); + } else { + user_callbacks->mutex_unlock(&libcrypto_mutexes[i]); + } +} + +static int libcrypto_thread_init(void){ + int n=CRYPTO_num_locks(); + int i; + if(user_callbacks == &ssh_threads_noop) + return SSH_OK; + libcrypto_mutexes=malloc(sizeof(void *) * n); + if (libcrypto_mutexes == NULL) + return SSH_ERROR; + for (i=0;imutex_init(&libcrypto_mutexes[i]); + } + CRYPTO_set_id_callback(user_callbacks->thread_id); + CRYPTO_set_locking_callback(libcrypto_lock_callback); + + return SSH_OK; +} + +static void libcrypto_thread_finalize(void){ + int n=CRYPTO_num_locks(); + int i; + if (libcrypto_mutexes==NULL) + return; + for (i=0;imutex_destroy(&libcrypto_mutexes[i]); + } + SAFE_FREE(libcrypto_mutexes); + +} + +#endif + +/** @internal + * @brief inits the threading with the backend cryptographic libraries + */ + +int ssh_threads_init(void){ + static int threads_initialized=0; + int ret; + if(threads_initialized) + return SSH_OK; + /* first initialize the user_callbacks with our default handlers if not + * already the case + */ + if(user_callbacks == NULL){ + user_callbacks=&ssh_threads_noop; + } + + /* Then initialize the crypto libraries threading callbacks */ +#ifdef HAVE_LIBGCRYPT + ret = libgcrypt_thread_init(); +#else /* Libcrypto */ + ret = libcrypto_thread_init(); +#endif + if(ret == SSH_OK) + threads_initialized=1; + return ret; +} + +void ssh_threads_finalize(void){ +#ifdef HAVE_LIBGCRYPT +#else + libcrypto_thread_finalize(); +#endif +} + +int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct *cb){ + user_callbacks=cb; + return SSH_OK; +} + +const char *ssh_threads_get_type(void) { + if(user_callbacks != NULL) + return user_callbacks->type; + return NULL; +} + +/** + * @} + */ diff --git a/libssh/src/threads/CMakeLists.txt b/libssh/src/threads/CMakeLists.txt new file mode 100644 index 00000000..b95525e4 --- /dev/null +++ b/libssh/src/threads/CMakeLists.txt @@ -0,0 +1,125 @@ +project(libssh-threads C) + +set(LIBSSH_THREADS_PUBLIC_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR} + CACHE INTERNAL "libssh public include directories" +) + +set(LIBSSH_THREADS_PRIVATE_INCLUDE_DIRS + ${CMAKE_BINARY_DIR} +) + +set(LIBSSH_THREADS_SHARED_LIBRARY + ssh_threads_shared + CACHE INTERNAL "libssh threads shared library" +) + +if (WITH_STATIC_LIB) + set(LIBSSH_THREADS_STATIC_LIBRARY + ssh_threads_static + CACHE INTERNAL "libssh threads static library" + ) +endif (WITH_STATIC_LIB) + +set(LIBSSH_THREADS_LINK_LIBRARIES + ${LIBSSH_SHARED_LIBRARY} +) + +set(libssh_threads_SRCS +) + +# build and link pthread +if (CMAKE_USE_PTHREADS_INIT) + set(libssh_threads_SRCS + ${libssh_threads_SRCS} + pthread.c + ) + + set(LIBSSH_THREADS_LINK_LIBRARIES + ${LIBSSH_THREADS_LINK_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ) +endif (CMAKE_USE_PTHREADS_INIT) + +set(LIBSSH_THREADS_LINK_LIBRARIES + ${LIBSSH_THREADS_LINK_LIBRARIES} + CACHE INTERNAL "libssh threads link libraries" +) + +include_directories( + ${LIBSSH_THREADS_PUBLIC_INCLUDE_DIRS} + ${LIBSSH_THREADS_PRIVATE_INCLUDE_DIRS} +) + +add_library(${LIBSSH_THREADS_SHARED_LIBRARY} SHARED ${libssh_threads_SRCS}) + +target_link_libraries(${LIBSSH_THREADS_SHARED_LIBRARY} ${LIBSSH_THREADS_LINK_LIBRARIES}) + +set_target_properties( + ${LIBSSH_THREADS_SHARED_LIBRARY} + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh_threads + DEFINE_SYMBOL + LIBSSH_EXPORTS +) + +if (WITH_VISIBILITY_HIDDEN) + set_target_properties(${LIBSSH_THREADS_SHARED_LIBRARY} PROPERTIES COMPILE_FLAGS "-fvisibility=hidden") +endif (WITH_VISIBILITY_HIDDEN) + +install( + TARGETS + ${LIBSSH_THREADS_SHARED_LIBRARY} + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + COMPONENT libraries +) + +if (WITH_STATIC_LIB) + add_library(${LIBSSH_THREADS_STATIC_LIBRARY} STATIC ${libssh_threads_SRCS}) + + if (MSVC) + set(OUTPUT_SUFFIX static) + else (MSVC) + set(OUTPUT_SUFFIX ) + endif (MSVC) + + set_target_properties( + ${LIBSSH_THREADS_STATIC_LIBRARY} + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh_threads + ARCHIVE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUFFIX} + ) + + if (WIN32) + set_target_properties( + ${LIBSSH_THREADS_STATIC_LIBRARY} + PROPERTIES + COMPILE_FLAGS + "-DLIBSSH_STATIC" + ) + endif (WIN32) + + install( + TARGETS + ${LIBSSH_THREADS_STATIC_LIBRARY} + DESTINATION + ${LIB_INSTALL_DIR}/${OUTPUT_SUFFIX} + COMPONENT + libraries + ) +endif (WITH_STATIC_LIB) diff --git a/libssh/src/threads/pthread.c b/libssh/src/threads/pthread.c new file mode 100644 index 00000000..829fa5c6 --- /dev/null +++ b/libssh/src/threads/pthread.c @@ -0,0 +1,99 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include + +#ifdef HAVE_PTHREAD + +#include +#include +#include + +/** @brief Defines the needed callbacks for pthread. Use this if your + * OS supports libpthread and want to use it for threading. + * @code + * #include + * #include + * #include + * SSH_THREADS_PTHREAD(ssh_pthread_callbacks); + * int main(){ + * ssh_init_set_threads_callbacks(&ssh_pthread_callbacks); + * ssh_init(); + * ... + * } + * @endcode + * @param name name of the structure to be declared, containing the + * callbacks for threading + * + */ + +static int ssh_pthread_mutex_init (void **priv){ + int err = 0; + *priv = malloc (sizeof (pthread_mutex_t)); + if (*priv==NULL) + return ENOMEM; + err = pthread_mutex_init (*priv, NULL); + if (err != 0){ + free (*priv); + *priv=NULL; + } + return err; +} + +static int ssh_pthread_mutex_destroy (void **lock) { + int err = pthread_mutex_destroy (*lock); + free (*lock); + *lock=NULL; + return err; +} + +static int ssh_pthread_mutex_lock (void **lock) { + return pthread_mutex_lock (*lock); +} + +static int ssh_pthread_mutex_unlock (void **lock){ + return pthread_mutex_unlock (*lock); +} + +static unsigned long ssh_pthread_thread_id (void){ +#if _WIN32 + return (unsigned long) pthread_self().p; +#else + return (unsigned long) pthread_self(); +#endif +} + +static struct ssh_threads_callbacks_struct ssh_threads_pthread = +{ + .type="threads_pthread", + .mutex_init=ssh_pthread_mutex_init, + .mutex_destroy=ssh_pthread_mutex_destroy, + .mutex_lock=ssh_pthread_mutex_lock, + .mutex_unlock=ssh_pthread_mutex_unlock, + .thread_id=ssh_pthread_thread_id +}; + +struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void) { + return &ssh_threads_pthread; +} + +#endif /* HAVE_PTHREAD */ diff --git a/libssh/src/wrapper.c b/libssh/src/wrapper.c new file mode 100644 index 00000000..b8a489d4 --- /dev/null +++ b/libssh/src/wrapper.c @@ -0,0 +1,345 @@ +/* + * wrapper.c - wrapper for crytpo functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * Why a wrapper? + * + * Let's say you want to port libssh from libcrypto of openssl to libfoo + * you are going to spend hours to remove every references to SHA1_Update() + * to libfoo_sha1_update after the work is finished, you're going to have + * only this file to modify it's not needed to say that your modifications + * are welcome. + */ + +#include "config.h" + + +#include +#include +#include + +#ifdef WITH_ZLIB +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/pki.h" + +/* it allocates a new cipher structure based on its offset into the global table */ +static struct ssh_cipher_struct *cipher_new(int offset) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = malloc(sizeof(struct ssh_cipher_struct)); + if (cipher == NULL) { + return NULL; + } + + /* note the memcpy will copy the pointers : so, you shouldn't free them */ + memcpy(cipher, &ssh_get_ciphertab()[offset], sizeof(*cipher)); + + return cipher; +} + +static void cipher_free(struct ssh_cipher_struct *cipher) { +#ifdef HAVE_LIBGCRYPT + unsigned int i; +#endif + + if (cipher == NULL) { + return; + } + + if(cipher->key) { +#ifdef HAVE_LIBGCRYPT + for (i = 0; i < (cipher->keylen / sizeof(gcry_cipher_hd_t)); i++) { + gcry_cipher_close(cipher->key[i]); + } +#elif defined HAVE_LIBCRYPTO + /* destroy the key */ + memset(cipher->key, 0, cipher->keylen); +#endif + SAFE_FREE(cipher->key); + } + SAFE_FREE(cipher); +} + +struct ssh_crypto_struct *crypto_new(void) { + struct ssh_crypto_struct *crypto; + + crypto = malloc(sizeof(struct ssh_crypto_struct)); + if (crypto == NULL) { + return NULL; + } + ZERO_STRUCTP(crypto); + return crypto; +} + +void crypto_free(struct ssh_crypto_struct *crypto){ + int i; + if (crypto == NULL) { + return; + } + + SAFE_FREE(crypto->server_pubkey); + + cipher_free(crypto->in_cipher); + cipher_free(crypto->out_cipher); + + bignum_free(crypto->e); + bignum_free(crypto->f); + bignum_free(crypto->x); + bignum_free(crypto->y); + bignum_free(crypto->k); +#ifdef HAVE_ECDH + SAFE_FREE(crypto->ecdh_client_pubkey); + SAFE_FREE(crypto->ecdh_server_pubkey); +#endif + if(crypto->session_id != NULL){ + memset(crypto->session_id, '\0', crypto->digest_len); + SAFE_FREE(crypto->session_id); + } + if(crypto->secret_hash != NULL){ + memset(crypto->secret_hash, '\0', crypto->digest_len); + SAFE_FREE(crypto->secret_hash); + } +#ifdef WITH_ZLIB + if (crypto->compress_out_ctx && + (deflateEnd(crypto->compress_out_ctx) != 0)) { + inflateEnd(crypto->compress_out_ctx); + } + if (crypto->compress_in_ctx && + (deflateEnd(crypto->compress_in_ctx) != 0)) { + inflateEnd(crypto->compress_in_ctx); + } +#endif /* WITH_ZLIB */ + if(crypto->encryptIV) + SAFE_FREE(crypto->encryptIV); + if(crypto->decryptIV) + SAFE_FREE(crypto->decryptIV); + if(crypto->encryptMAC) + SAFE_FREE(crypto->encryptMAC); + if(crypto->decryptMAC) + SAFE_FREE(crypto->decryptMAC); + if(crypto->encryptkey){ + memset(crypto->encryptkey, 0, crypto->digest_len); + SAFE_FREE(crypto->encryptkey); + } + if(crypto->decryptkey){ + memset(crypto->decryptkey, 0, crypto->digest_len); + SAFE_FREE(crypto->decryptkey); + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(crypto->client_kex.methods[i]); + SAFE_FREE(crypto->server_kex.methods[i]); + SAFE_FREE(crypto->kex_methods[i]); + } + + memset(crypto,0,sizeof(*crypto)); + + SAFE_FREE(crypto); +} + +static int crypt_set_algorithms2(ssh_session session){ + const char *wanted; + int i = 0; + int rc = SSH_ERROR; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + + enter_function(); + /* we must scan the kex entries to find crypto algorithms and set their appropriate structure */ + /* out */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + while (ssh_ciphertab[i].name && strcmp(wanted, ssh_ciphertab[i].name)) { + i++; + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no crypto algorithm function found for %s", + wanted); + goto error; + } + ssh_log(session, SSH_LOG_PACKET, "Set output algorithm to %s", wanted); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + goto error; + } + i = 0; + + /* in */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + while (ssh_ciphertab[i].name && strcmp(wanted, ssh_ciphertab[i].name)) { + i++; + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "Crypt_set_algorithms: no crypto algorithm function found for %s", + wanted); + goto error; + } + ssh_log(session, SSH_LOG_PACKET, "Set input algorithm to %s", wanted); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* compression */ + if (strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib") == 0) { + session->next_crypto->do_compress_out = 1; + } + if (strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib") == 0) { + session->next_crypto->do_compress_in = 1; + } + if (strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib@openssh.com") == 0) { + session->next_crypto->delayed_compress_out = 1; + } + if (strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib@openssh.com") == 0) { + session->next_crypto->delayed_compress_in = 1; + } + rc = SSH_OK; +error: + leave_function(); + return rc; +} + +static int crypt_set_algorithms1(ssh_session session, enum ssh_des_e des_type) { + int i = 0; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + + /* right now, we force 3des-cbc to be taken */ + while (ssh_ciphertab[i].name && strcmp(ssh_ciphertab[i].name, + des_type == SSH_DES ? "des-cbc-ssh1" : "3des-cbc-ssh1")) { + i++; + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, "cipher 3des-cbc-ssh1 or des-cbc-ssh1 not found!"); + return SSH_ERROR; + } + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return SSH_OK; +} + +int crypt_set_algorithms(ssh_session session, enum ssh_des_e des_type) { + return (session->version == 1) ? crypt_set_algorithms1(session, des_type) : + crypt_set_algorithms2(session); +} + +#ifdef WITH_SERVER +int crypt_set_algorithms_server(ssh_session session){ + char *method = NULL; + int i = 0; + int rc = SSH_ERROR; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + + if (session == NULL) { + return SSH_ERROR; + } + + /* we must scan the kex entries to find crypto algorithms and set their appropriate structure */ + enter_function(); + /* out */ + method = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + while(ssh_ciphertab[i].name && strcmp(method,ssh_ciphertab[i].name)) + i++; + if(!ssh_ciphertab[i].name){ + ssh_set_error(session,SSH_FATAL,"crypt_set_algorithms_server : " + "no crypto algorithm function found for %s",method); + goto error; + } + ssh_log(session,SSH_LOG_PACKET,"Set output algorithm %s",method); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + goto error; + } + i=0; + /* in */ + method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + while(ssh_ciphertab[i].name && strcmp(method,ssh_ciphertab[i].name)) + i++; + if(!ssh_ciphertab[i].name){ + ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server :" + "no crypto algorithm function found for %s",method); + goto error; + } + ssh_log(session,SSH_LOG_PACKET,"Set input algorithm %s",method); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* compression */ + method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + if(strcmp(method,"zlib") == 0){ + ssh_log(session,SSH_LOG_PACKET,"enabling C->S compression"); + session->next_crypto->do_compress_in=1; + } + if(strcmp(method,"zlib@openssh.com") == 0){ + ssh_set_error(session,SSH_FATAL,"zlib@openssh.com not supported"); + goto error; + } + method = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + if(strcmp(method,"zlib") == 0){ + ssh_log(session,SSH_LOG_PACKET,"enabling S->C compression\n"); + session->next_crypto->do_compress_out=1; + } + if(strcmp(method,"zlib@openssh.com") == 0){ + ssh_set_error(session,SSH_FATAL,"zlib@openssh.com not supported"); + goto error; + } + + method = session->next_crypto->kex_methods[SSH_HOSTKEYS]; + session->srv.hostkey = ssh_key_type_from_name(method); + rc = SSH_OK; + error: + leave_function(); + return rc; +} + +#endif /* WITH_SERVER */ +/* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/tests/CMakeLists.txt b/libssh/tests/CMakeLists.txt new file mode 100644 index 00000000..bb39755a --- /dev/null +++ b/libssh/tests/CMakeLists.txt @@ -0,0 +1,48 @@ +project(tests C) + +if (BSD OR SOLARIS) + find_package(Argp) +endif (BSD OR SOLARIS) + +set(TORTURE_LIBRARY torture) + +include_directories( + ${LIBSSH_PUBLIC_INCLUDE_DIRS} + ${CMOCKA_INCLUDE_DIR} + ${OPENSSL_INCLUDE_DIRS} + ${GCRYPT_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} +) + +# create test library +add_library(${TORTURE_LIBRARY} STATIC cmdline.c torture.c) +target_link_libraries(${TORTURE_LIBRARY} + ${CMOCKA_LIBRARY} + ${LIBSSH_STATIC_LIBRARY} + ${LIBSSH_LINK_LIBRARIES} + ${LIBSSH_THREADS_STATIC_LIBRARY} + ${LIBSSH_THREADS_LINK_LIBRARIES} + ${ARGP_LIBRARIES} +) + +set(TEST_TARGET_LIBRARIES + ${TORTURE_LIBRARY} + ${CMOCKA_LIBRARY} + ${LIBSSH_STATIC_LIBRARY} + ${LIBSSH_LINK_LIBRARIES} + ${LIBSSH_THREADS_STATIC_LIBRARY} + ${LIBSSH_THREADS_LINK_LIBRARIES} +) + +add_subdirectory(unittests) +if (WITH_CLIENT_TESTING) + add_subdirectory(client) +endif (WITH_CLIENT_TESTING) + +if (WITH_BENCHMARKS) + add_subdirectory(benchmarks) +endif (WITH_BENCHMARKS) + diff --git a/libssh/tests/authentication.c b/libssh/tests/authentication.c new file mode 100644 index 00000000..248b646f --- /dev/null +++ b/libssh/tests/authentication.c @@ -0,0 +1,74 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ + + +#include +#include +#include +#include + +#include + +#include "tests.h" +static int auth_kbdint(SSH_SESSION *session){ + int err=ssh_userauth_kbdint(session,NULL,NULL); + char *name,*instruction,*prompt,*ptr; + char buffer[128]; + int i,n; + char echo; + while (err==SSH_AUTH_INFO){ + name=ssh_userauth_kbdint_getname(session); + instruction=ssh_userauth_kbdint_getinstruction(session); + n=ssh_userauth_kbdint_getnprompts(session); + if(strlen(name)>0) + printf("%s\n",name); + if(strlen(instruction)>0) + printf("%s\n",instruction); + for(i=0;i&1`" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD vesion : `echo | nc $DEST 22 | head -n1`" +echo "ssh login latency :`(time -f user:%U ssh $DEST 'id > /dev/null') 2>&1`" +./generate.py | dd bs=4096 count=100000 | time ssh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/libssh/tests/benchmarks/bench2.sh b/libssh/tests/benchmarks/bench2.sh new file mode 100755 index 00000000..01d67777 --- /dev/null +++ b/libssh/tests/benchmarks/bench2.sh @@ -0,0 +1,13 @@ +export CIPHER=aes128-cbc +export DEST=localhost + +echo "Upload raw SSH statistics" +echo "local machine: `uname -a`" +echo "Cipher : $CIPHER ; Destination : $DEST (`ssh $DEST uname -a`)" +echo "Local ssh version: `samplessh -V 2>&1`" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD vesion : `echo | nc $DEST 22 | head -n1`" +echo "ssh login latency :`(time -f user:%U samplessh $DEST 'id > /dev/null') 2>&1`" +./generate.py | dd bs=4096 count=100000 | strace samplessh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/libssh/tests/benchmarks/bench_raw.c b/libssh/tests/benchmarks/bench_raw.c new file mode 100644 index 00000000..db1a057c --- /dev/null +++ b/libssh/tests/benchmarks/bench_raw.c @@ -0,0 +1,285 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include +#include + +#define PYTHON_PATH "/usr/bin/python" + +const char python_eater[]= +"#!/usr/bin/python\n" +"import sys\n" +"print 'go'\n" +"sys.stdout.flush()\n" +"toread=XXXXXXXXXX\n" +"read=0\n" +"while(read < toread):\n" +" buffersize=toread-read\n" +" if(buffersize > 4096):\n" +" buffersize=4096\n" +" r=len(sys.stdin.read(buffersize))\n" +" read+=r\n" +" if(r<=0):\n" +" print 'error'\n" +" exit()\n" +"print 'done'\n"; + +static char *get_python_eater(unsigned long bytes){ + char *eater=malloc(sizeof(python_eater)); + char *ptr; + char buf[12]; + + memcpy(eater,python_eater,sizeof(python_eater)); + ptr=strstr(eater,"XXXXXXXXXX"); + if(!ptr){ + free(eater); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return eater; +} + +/** @internal + * @brief uploads a script (python or other) at a specific path on the + * remote host + * @param[in] session an active SSH session + * @param[in] path to copy the file + * @param[in] content of the file to copy + * @return 0 on success, -1 on error + */ +static int upload_script(ssh_session session, const char *path, + const char *script){ + ssh_channel channel; + char cmd[128]; + int err; + + channel=ssh_channel_new(session); + if(!channel) + goto error; + if(ssh_channel_open_session(channel) == SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"cat > %s",path); + if(ssh_channel_request_exec(channel,cmd) == SSH_ERROR) + goto error; + err=ssh_channel_write(channel,script,strlen(script)); + if(err == SSH_ERROR) + goto error; + if(ssh_channel_send_eof(channel) == SSH_ERROR) + goto error; + if(ssh_channel_close(channel) == SSH_ERROR) + goto error; + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error while copying script : %s\n",ssh_get_error(session)); + return -1; +} + +/** @internal + * @brief benchmarks a raw upload (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script =get_python_eater(bytes); + err=upload_script(session,"/tmp/eater.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/eater.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + if((err=ssh_channel_read(channel,buffer,sizeof(buffer)-1,0))==SSH_ERROR) + goto error; + buffer[err]=0; + if(!strstr(buffer,"go")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_channel_write(channel,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + + if(args->verbose>0) + fprintf(stdout,"Finished upload, now waiting the ack\n"); + + if((err=ssh_channel_read(channel,buffer,5,0))==SSH_ERROR) + goto error; + buffer[err]=0; + if(!strstr(buffer,"done")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} + +const char python_giver[] = +"#!/usr/bin/python\n" +"import sys\n" +"r=sys.stdin.read(2)\n" +"towrite=XXXXXXXXXX\n" +"wrote=0\n" +"mtu = 32786\n" +"buf = 'A'*mtu\n" +"while(wrote < towrite):\n" +" buffersize=towrite-wrote\n" +" if(buffersize > mtu):\n" +" buffersize=mtu\n" +" if(buffersize == mtu):\n" +" sys.stdout.write(buf)\n" +" else:\n" +" sys.stdout.write('A'*buffersize)\n" +" wrote+=buffersize\n" +"sys.stdout.flush()\n"; + +static char *get_python_giver(unsigned long bytes){ + char *giver=malloc(sizeof(python_giver)); + char *ptr; + char buf[12]; + + memcpy(giver,python_giver,sizeof(python_giver)); + ptr=strstr(giver,"XXXXXXXXXX"); + if(!ptr){ + free(giver); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return giver; +} + +/** @internal + * @brief benchmarks a raw download (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script =get_python_giver(bytes); + err=upload_script(session,"/tmp/giver.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/giver.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + if((err=ssh_channel_write(channel,"go",2))==SSH_ERROR) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + int r; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_channel_read(channel,buffer,toread,0); + if(r == SSH_ERROR) + goto error; + total += r; + } + + if(args->verbose>0) + fprintf(stdout,"Finished download\n"); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} diff --git a/libssh/tests/benchmarks/bench_scp.c b/libssh/tests/benchmarks/bench_scp.c new file mode 100644 index 00000000..340637ba --- /dev/null +++ b/libssh/tests/benchmarks/bench_scp.c @@ -0,0 +1,150 @@ +/* bench_scp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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 "benchmarks.h" +#include +#include + +#define SCPDIR "/tmp/" +#define SCPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a scp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_WRITE,SCPDIR); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + if(ssh_scp_push_file(scp,SCPFILE,bytes,0777) != SSH_OK) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_scp_write(scp,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += towrite; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} + +/** @internal + * @brief benchmarks a scp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + int r; + size_t size; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_READ,SCPDIR SCPFILE); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + r=ssh_scp_pull_request(scp); + if(r == SSH_SCP_REQUEST_NEWFILE){ + size=ssh_scp_request_get_size(scp); + if(bytes > size){ + printf("Only %d bytes available (on %lu requested).\n",size,bytes); + bytes = size; + } + if(size > bytes){ + printf("File is %d bytes (on %lu requested). Will cut the end\n",size,bytes); + } + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + ssh_scp_accept_request(scp); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_scp_read(scp,buffer,toread); + if(r == SSH_ERROR || r == 0) + goto error; + total += r; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + } else { + fprintf(stderr,"Expected SSH_SCP_REQUEST_NEWFILE, got %d\n",r); + goto error; + } + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp download : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} diff --git a/libssh/tests/benchmarks/bench_sftp.c b/libssh/tests/benchmarks/bench_sftp.c new file mode 100644 index 00000000..9e4ab34d --- /dev/null +++ b/libssh/tests/benchmarks/bench_sftp.c @@ -0,0 +1,239 @@ +/* bench_sftp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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 "benchmarks.h" +#include +#include +#include +#include +#include + +#define SFTPDIR "/tmp/" +#define SFTPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a synchronous sftp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDWR | O_CREAT | O_TRUNC, 0777); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=sftp_write(file,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks a synchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file; + int r; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=sftp_read(file,buffer,toread); + if(r == SSH_ERROR) + goto error; + total += r; + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks an asynchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file; + int r,i; + int warned = 0; + unsigned long toread; + int *ids=NULL; + int concurrent_downloads = args->concurrent_requests; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + ids = malloc(concurrent_downloads * sizeof(int)); + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now, using %d concurrent downloads\n",bytes, + concurrent_downloads); + timestamp_init(&ts); + for (i=0;ichunksize); + if(ids[i]==SSH_ERROR) + goto error; + } + i=0; + while(total < bytes){ + r = sftp_async_read(file, buffer, args->chunksize, ids[i]); + if(r == SSH_ERROR) + goto error; + total += r; + if(r != (int)args->chunksize && total != bytes && !warned){ + fprintf(stderr,"async_sftp_download : receiving short reads (%d, requested %d) " + "the received file will be corrupted and shorted. Adapt chunksize to %d\n", + r, args->chunksize,r); + warned = 1; + } + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + toread = bytes - total; + if(toread < args->chunksize * concurrent_downloads){ + /* we've got enough launched downloads */ + ids[i]=-1; + } + if(toread > args->chunksize) + toread = args->chunksize; + ids[i]=sftp_async_read_begin(file,toread); + if(ids[i] == SSH_ERROR) + goto error; + i = (i+1) % concurrent_downloads; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + free(ids); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + free(ids); + return -1; +} diff --git a/libssh/tests/benchmarks/benchmarks.c b/libssh/tests/benchmarks/benchmarks.c new file mode 100644 index 00000000..5e33dd4b --- /dev/null +++ b/libssh/tests/benchmarks/benchmarks.c @@ -0,0 +1,400 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "benchmarks.h" +#include + +#include +#include +#include + +struct benchmark benchmarks[]= { + { + .name="benchmark_raw_upload", + .fct=benchmarks_raw_up, + .enabled=0 + }, + { + .name="benchmark_raw_download", + .fct=benchmarks_raw_down, + .enabled=0 + }, + { + .name="benchmark_scp_upload", + .fct=benchmarks_scp_up, + .enabled=0 + }, + { + .name="benchmark_scp_download", + .fct=benchmarks_scp_down, + .enabled=0 + }, + { + .name="benchmark_sync_sftp_upload", + .fct=benchmarks_sync_sftp_up, + .enabled=0 + }, + { + .name="benchmark_sync_sftp_download", + .fct=benchmarks_sync_sftp_down, + .enabled=0 + }, + { + .name="benchmark_async_sftp_download", + .fct=benchmarks_async_sftp_down, + .enabled=0 + } +}; + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh benchmarks 2011-08-28"; +const char *argp_program_bug_address = "Aris Adamantiadis "; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "libssh benchmarks"; + + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh benchmark more verbose", + .group = 0 + }, + { + .name = "raw-upload", + .key = '1', + .arg = NULL, + .flags = 0, + .doc = "Upload raw data using channel", + .group = 0 + }, + { + .name = "raw-download", + .key = '2', + .arg = NULL, + .flags = 0, + .doc = "Download raw data using channel", + .group = 0 + }, + { + .name = "scp-upload", + .key = '3', + .arg = NULL, + .flags = 0, + .doc = "Upload data using SCP", + .group = 0 + }, + { + .name = "scp-download", + .key = '4', + .arg = NULL, + .flags = 0, + .doc = "Download data using SCP", + .group = 0 + }, + { + .name = "sync-sftp-upload", + .key = '5', + .arg = NULL, + .flags = 0, + .doc = "Upload data using synchronous SFTP", + .group = 0 + + }, + { + .name = "sync-sftp-download", + .key = '6', + .arg = NULL, + .flags = 0, + .doc = "Download data using synchronous SFTP (slow)", + .group = 0 + + }, + { + .name = "async-sftp-download", + .key = '7', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP (fast)", + .group = 0 + + }, + { + .name = "host", + .key = 'h', + .arg = "HOST", + .flags = 0, + .doc = "Add a host to connect for benchmark (format user@hostname)", + .group = 0 + }, + { + .name = "size", + .key = 's', + .arg = "MBYTES", + .flags = 0, + .doc = "MBytes of data to send/receive per test", + .group = 0 + }, + { + .name = "chunk", + .key = 'c', + .arg = "bytes", + .flags = 0, + .doc = "size of data chunks to send/receive", + .group = 0 + }, + { + .name = "prequests", + .key = 'p', + .arg = "number [20]", + .flags = 0, + .doc = "[async SFTP] number of concurrent requests", + .group = 0 + }, + { + .name = "cipher", + .key = 'C', + .arg = "cipher", + .flags = 0, + .doc = "Cryptographic cipher to be used", + .group = 0 + }, + + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + benchmarks[key - '1'].enabled = 1; + arguments->ntests ++; + break; + case 'v': + arguments->verbose++; + break; + case 's': + arguments->datasize = atoi(arg); + break; + case 'p': + arguments->concurrent_requests = atoi(arg); + break; + case 'c': + arguments->chunksize = atoi(arg); + break; + case 'C': + arguments->cipher = arg; + break; + case 'h': + if(arguments->nhosts >= MAX_HOSTS_CONNECT){ + fprintf(stderr, "Too much hosts\n"); + return ARGP_ERR_UNKNOWN; + } + arguments->hosts[arguments->nhosts]=arg; + arguments->nhosts++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +static void cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else /* HAVE_ARGP_H */ + (void) argc; + (void) argv; + arguments->hosts[0]="localhost"; + arguments->nhosts=1; +#endif /* HAVE_ARGP_H */ +} + +static void arguments_init(struct argument_s *arguments){ + memset(arguments,0,sizeof(*arguments)); + arguments->chunksize=32758; + arguments->concurrent_requests=20; + arguments->datasize = 10; +} + +static ssh_session connect_host(const char *host, int verbose, char *cipher){ + ssh_session session=ssh_new(); + if(session==NULL) + goto error; + if(ssh_options_set(session,SSH_OPTIONS_HOST, host)<0) + goto error; + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if(cipher != NULL){ + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) || + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher)){ + goto error; + } + } + ssh_options_parse_config(session, NULL); + if(ssh_connect(session)==SSH_ERROR) + goto error; + if(ssh_userauth_autopubkey(session,NULL) != SSH_AUTH_SUCCESS) + goto error; + return session; +error: + fprintf(stderr,"Error connecting to \"%s\": %s\n",host,ssh_get_error(session)); + ssh_free(session); + return NULL; +} + +static char *network_speed(float bps){ + static char buf[128]; + if(bps > 1000*1000*1000){ + /* Gbps */ + snprintf(buf,sizeof(buf),"%f Gbps",bps/(1000*1000*1000)); + } else if(bps > 1000*1000){ + /* Mbps */ + snprintf(buf,sizeof(buf),"%f Mbps",bps/(1000*1000)); + } else if(bps > 1000){ + snprintf(buf,sizeof(buf),"%f Kbps",bps/1000); + } else { + snprintf(buf,sizeof(buf),"%f bps",bps); + } + return buf; +} + +static void do_benchmarks(ssh_session session, struct argument_s *arguments, + const char *hostname){ + float ping_rtt=0.0; + float ssh_rtt=0.0; + float bps=0.0; + int i; + int err; + struct benchmark *b; + + if(arguments->verbose>0) + fprintf(stdout,"Testing ICMP RTT\n"); + err=benchmarks_ping_latency(hostname, &ping_rtt); + if(err == 0){ + fprintf(stdout,"ping RTT : %f ms\n",ping_rtt); + } + err=benchmarks_ssh_latency(session, &ssh_rtt); + if(err==0){ + fprintf(stdout, "SSH RTT : %f ms. Theoretical max BW (win=128K) : %s\n",ssh_rtt,network_speed(128000.0/(ssh_rtt / 1000.0))); + } + for (i=0 ; ienabled){ + err=b->fct(session,arguments,&bps); + if(err==0){ + fprintf(stdout, "%s : %s : %s\n",hostname, b->name, network_speed(bps)); + } + } + } +} + +char *buffer; + +int main(int argc, char **argv){ + struct argument_s arguments; + ssh_session session; + int i; + + arguments_init(&arguments); + cmdline_parse(argc, argv, &arguments); + if (arguments.nhosts==0){ + fprintf(stderr,"At least one host (-h) must be specified\n"); + return EXIT_FAILURE; + } + if (arguments.ntests==0){ + for(i=0; i < BENCHMARK_NUMBER ; ++i){ + benchmarks[i].enabled=1; + } + arguments.ntests=BENCHMARK_NUMBER; + } + buffer=malloc(arguments.chunksize > 1024 ? arguments.chunksize : 1024); + if(buffer == NULL){ + fprintf(stderr,"Allocation of chunk buffer failed\n"); + return EXIT_FAILURE; + } + if (arguments.verbose > 0){ + fprintf(stdout, "Will try hosts "); + for(i=0;i 0) + fprintf(stdout,"Connecting to \"%s\"...\n",arguments.hosts[i]); + session=connect_host(arguments.hosts[i], arguments.verbose, arguments.cipher); + if(session != NULL && arguments.verbose > 0) + fprintf(stdout,"Success\n"); + if(session == NULL){ + fprintf(stderr,"Errors occurred, stopping\n"); + return EXIT_FAILURE; + } + do_benchmarks(session, &arguments, arguments.hosts[i]); + ssh_disconnect(session); + ssh_free(session); + } + return EXIT_SUCCESS; +} + diff --git a/libssh/tests/benchmarks/benchmarks.h b/libssh/tests/benchmarks/benchmarks.h new file mode 100644 index 00000000..26da09bb --- /dev/null +++ b/libssh/tests/benchmarks/benchmarks.h @@ -0,0 +1,99 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef BENCHMARKS_H_ +#define BENCHMARKS_H_ + +#include + +/* benchmarks.c */ + +/* maximum number of parallel hosts that may be checked */ +#define MAX_HOSTS_CONNECT 20 + +enum libssh_benchmarks { + BENCHMARK_RAW_UPLOAD=0, + BENCHMARK_RAW_DOWNLOAD, + BENCHMARK_SCP_UPLOAD, + BENCHMARK_SCP_DOWNLOAD, + BENCHMARK_SYNC_SFTP_UPLOAD, + BENCHMARK_SYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_DOWNLOAD, + BENCHMARK_NUMBER +}; + +struct argument_s { + const char *hosts[MAX_HOSTS_CONNECT]; + int verbose; + int nhosts; + int ntests; + unsigned int datasize; + unsigned int chunksize; + int concurrent_requests; + char *cipher; +}; + +extern char *buffer; + +typedef int (*bench_fct)(ssh_session session, struct argument_s *args, + float *bps); + +struct benchmark { + const char *name; + bench_fct fct; + int enabled; +}; + +/* latency.c */ + +struct timestamp_struct { + struct timeval timestamp; +}; + +int benchmarks_ping_latency (const char *host, float *average); +int benchmarks_ssh_latency (ssh_session session, float *average); + +void timestamp_init(struct timestamp_struct *ts); +float elapsed_time(struct timestamp_struct *ts); + +/* bench_raw.c */ + +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_scp.c */ + +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_sftp.c */ + +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +#endif /* BENCHMARKS_H_ */ diff --git a/libssh/tests/benchmarks/latency.c b/libssh/tests/benchmarks/latency.c new file mode 100644 index 00000000..09c50a04 --- /dev/null +++ b/libssh/tests/benchmarks/latency.c @@ -0,0 +1,148 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include + +#include +#include +#include +#include +#include + +#define PING_PROGRAM "/bin/ping" + +/** @internal + * @brief Calculates the RTT of the host with ICMP ping, and returns the + * average of the calculated RTT. + * @param[in] host hostname to ping. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + * @warning relies on an external ping program which may not exist on + * certain OS. + */ +int benchmarks_ping_latency (const char *host, float *average){ + const char *ptr; + char cmd[256]; + char line[1024]; + FILE *fd; + int found=0; + + /* strip out the hostname */ + ptr=strchr(host,'@'); + if(ptr) + ptr++; + else + ptr=host; + + snprintf(cmd,sizeof(cmd),"%s -n -q -c3 %s",PING_PROGRAM, ptr); + fd=popen(cmd,"r"); + if(fd==NULL){ + fprintf(stderr,"Error executing command : %s\n", strerror(errno)); + return -1; + } + + while(!found && fgets(line,sizeof(line),fd)!=NULL){ + if(strstr(line,"rtt")){ + ptr=strchr(line,'='); + if(ptr==NULL) + goto parseerror; + ptr=strchr(ptr,'/'); + if(ptr==NULL) + goto parseerror; + *average=strtof(ptr+1,NULL); + found=1; + break; + } + } + if(!found) + goto parseerror; + pclose(fd); + return 0; + +parseerror: + fprintf(stderr,"Parse error : couldn't locate average in %s",line); + pclose(fd); + return -1; +} + +/** @internal + * @brief initialize a timestamp to the current time. + * @param[out] ts A timestamp_struct pointer. + */ +void timestamp_init(struct timestamp_struct *ts){ + gettimeofday(&ts->timestamp,NULL); +} + +/** @internal + * @brief return the elapsed time since now and the moment ts was initialized. + * @param[in] ts An initialized timestamp_struct pointer. + * @return Elapsed time in milliseconds. + */ +float elapsed_time(struct timestamp_struct *ts){ + struct timeval now; + time_t secdiff; + long usecdiff; /* may be negative */ + + gettimeofday(&now,NULL); + secdiff=now.tv_sec - ts->timestamp.tv_sec; + usecdiff=now.tv_usec - ts->timestamp.tv_usec; + //printf("%d sec diff, %d usec diff\n",secdiff, usecdiff); + return (float) (secdiff*1000) + ((float)usecdiff)/1000; +} + +/** @internal + * @brief Calculates the RTT of the host with SSH channel operations, and + * returns the average of the calculated RTT. + * @param[in] session active SSH session to test. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + */ +int benchmarks_ssh_latency(ssh_session session, float *average){ + float times[3]; + struct timestamp_struct ts; + int i; + ssh_channel channel; + channel=ssh_channel_new(session); + if(channel==NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + + for(i=0;i<3;++i){ + timestamp_init(&ts); + if(ssh_channel_request_env(channel,"TEST","test")==SSH_ERROR && + ssh_get_error_code(session)==SSH_FATAL) + goto error; + times[i]=elapsed_time(&ts); + } + ssh_channel_close(channel); + ssh_channel_free(channel); + channel=NULL; + printf("SSH request times : %f ms ; %f ms ; %f ms\n", times[0], times[1], times[2]); + *average=(times[0]+times[1]+times[2])/3; + return 0; +error: + fprintf(stderr,"Error calculating SSH latency : %s\n",ssh_get_error(session)); + if(channel) + ssh_channel_free(channel); + return -1; +} diff --git a/libssh/tests/chmodtest.c b/libssh/tests/chmodtest.c new file mode 100644 index 00000000..1e6f5112 --- /dev/null +++ b/libssh/tests/chmodtest.c @@ -0,0 +1,33 @@ +#include + +#include +#include "examples_common.h" +#include + +int main(void) { + ssh_session session; + sftp_session sftp; + char buffer[1024*1024]; + int rc; + + session = connect_ssh("localhost", NULL, 0); + if (session == NULL) { + return 1; + } + + sftp=sftp_new(session); + sftp_init(sftp); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_chmod(sftp,"/tmp/test",0644); + if (rc < 0) { + printf("error : %s\n",ssh_get_error(sftp)); + + ssh_disconnect(session); + return 1; + } + + ssh_disconnect(session); + + return 0; +} diff --git a/libssh/tests/client/CMakeLists.txt b/libssh/tests/client/CMakeLists.txt new file mode 100644 index 00000000..e3bb45d3 --- /dev/null +++ b/libssh/tests/client/CMakeLists.txt @@ -0,0 +1,12 @@ +project(clienttests C) + +add_cmocka_test(torture_algorithms torture_algorithms.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_auth torture_auth.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_connect torture_connect.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_knownhosts torture_knownhosts.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_proxycommand torture_proxycommand.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_session torture_session.c ${TORTURE_LIBRARY}) +if (WITH_SFTP) + add_cmocka_test(torture_sftp_static torture_sftp_static.c ${TORTURE_LIBRARY}) + add_cmocka_test(torture_sftp_dir torture_sftp_dir.c ${TORTURE_LIBRARY}) +endif (WITH_SFTP) diff --git a/libssh/tests/client/torture_algorithms.c b/libssh/tests/client/torture_algorithms.c new file mode 100644 index 00000000..180efb71 --- /dev/null +++ b/libssh/tests/client/torture_algorithms.c @@ -0,0 +1,245 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" + + +static void setup(void **state) { + int verbosity=torture_libssh_verbosity(); + ssh_session session = ssh_new(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + *state = session; +} + +static void teardown(void **state) { + ssh_free(*state); +} + +static void test_algorithm(ssh_session session, const char *algo) { + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, algo); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, algo); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_true(rc == SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_aes128_cbc(void **state) { + test_algorithm(*state, "aes128-cbc"); +} + +static void torture_algorithms_aes192_cbc(void **state) { + test_algorithm(*state, "aes192-cbc"); +} + +static void torture_algorithms_aes256_cbc(void **state) { + test_algorithm(*state, "aes256-cbc"); +} + +static void torture_algorithms_aes128_ctr(void **state) { + test_algorithm(*state, "aes128-ctr"); +} + +static void torture_algorithms_aes192_ctr(void **state) { + test_algorithm(*state, "aes192-ctr"); +} + +static void torture_algorithms_aes256_ctr(void **state) { + test_algorithm(*state, "aes256-ctr"); +} + +static void torture_algorithms_3des_cbc(void **state) { + test_algorithm(*state, "3des-cbc"); +} + +static void torture_algorithms_blowfish_cbc(void **state) { + test_algorithm(*state, "blowfish-cbc"); +} + +static void torture_algorithms_zlib(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session,SSH_OPTIONS_HOST,"localhost"); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib"); +#ifdef WITH_ZLIB + assert_true(rc == SSH_OK); +#else + assert_true(rc == SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib"); +#ifdef WITH_ZLIB + assert_true(rc == SSH_OK); +#else + assert_true(rc == SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (ssh_get_openssh_version(session)) { + assert_false(rc == SSH_OK); + ssh_disconnect(session); + return; + } +#endif + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_true(rc == SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_zlib_openssh(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session,SSH_OPTIONS_HOST,"localhost"); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_true(rc == SSH_OK); +#else + assert_true(rc == SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_true(rc == SSH_OK); +#else + assert_true(rc == SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (ssh_get_openssh_version(session)) { + assert_true(rc==SSH_OK); + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_true(rc == SSH_REQUEST_DENIED); + } + ssh_disconnect(session); + return; + } + assert_false(rc == SSH_OK); +#else + assert_true(rc == SSH_OK); +#endif + + ssh_disconnect(session); +} + +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_ECC) +static void torture_algorithms_ecdh_sha2_nistp256(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session,SSH_OPTIONS_HOST,"localhost"); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, "ecdh-sha2-nistp256"); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_true(rc == SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} +#endif + +static void torture_algorithms_dh_group1(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session,SSH_OPTIONS_HOST,"localhost"); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, "diffie-hellman-group1-sha1"); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_true(rc == SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_algorithms_aes128_cbc, setup, teardown), + unit_test_setup_teardown(torture_algorithms_aes192_cbc, setup, teardown), + unit_test_setup_teardown(torture_algorithms_aes256_cbc, setup, teardown), + unit_test_setup_teardown(torture_algorithms_aes128_ctr, setup, teardown), + unit_test_setup_teardown(torture_algorithms_aes192_ctr, setup, teardown), + unit_test_setup_teardown(torture_algorithms_aes256_ctr, setup, teardown), + unit_test_setup_teardown(torture_algorithms_3des_cbc, setup, teardown), + unit_test_setup_teardown(torture_algorithms_blowfish_cbc, setup, teardown), + unit_test_setup_teardown(torture_algorithms_zlib, setup, teardown), + unit_test_setup_teardown(torture_algorithms_zlib_openssh, setup, teardown), + unit_test_setup_teardown(torture_algorithms_dh_group1,setup,teardown), +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_ECC) + unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256,setup,teardown) +#endif + }; + + ssh_init(); + + rc = run_tests(tests); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/client/torture_auth.c b/libssh/tests/client/torture_auth.c new file mode 100644 index 00000000..83bbd406 --- /dev/null +++ b/libssh/tests/client/torture_auth.c @@ -0,0 +1,365 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "agent.c" + +static void setup(void **state) { + int verbosity = torture_libssh_verbosity(); + ssh_session session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; +} + +static void teardown(void **state) { + ssh_disconnect(*state); + ssh_free(*state); +} + +static void torture_auth_autopubkey(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_autopubkey(session, NULL); + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_autopubkey_nonblocking(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PUBLICKEY); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_autopubkey(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_kbdint(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + char *password = getenv("TORTURE_PASSWORD"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + if (password == NULL) { + print_message("*** Please set the environment variable " + "TORTURE_PASSWORD to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_true(rc == SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + + rc = ssh_userauth_kbdint_setanswer(session, 0, password); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if(rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_kbdint_nonblocking(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + char *password = getenv("TORTURE_PASSWORD"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + if (password == NULL) { + print_message("*** Please set the environment variable " + "TORTURE_PASSWORD to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_INTERACTIVE); + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_true(rc == SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + do { + rc = ssh_userauth_kbdint_setanswer(session, 0, password); + } while (rc == SSH_AUTH_AGAIN); + assert_false(rc < 0); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if(rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + } + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_password(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + char *password = getenv("TORTURE_PASSWORD"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + if (password == NULL) { + print_message("*** Please set the environment variable " + "TORTURE_PASSWORD to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, password); + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_password_nonblocking(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + char *password = getenv("TORTURE_PASSWORD"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + if (password == NULL) { + print_message("*** Please set the environment variable " + "TORTURE_PASSWORD to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc==SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PASSWORD); + + do { + rc = ssh_userauth_password(session, NULL, password); + } while(rc==SSH_AUTH_AGAIN); + + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_agent(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + if (!agent_is_running(session)){ + print_message("*** Agent not running. Test ignored"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_agent(session, NULL); + assert_true(rc == SSH_AUTH_SUCCESS); +} + +static void torture_auth_agent_nonblocking(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + int rc; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + if (!agent_is_running(session)){ + print_message("*** Agent not running. Test ignored"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PUBLICKEY); + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_true(rc == SSH_AUTH_SUCCESS); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_auth_kbdint, setup, teardown), + unit_test_setup_teardown(torture_auth_kbdint_nonblocking, setup, teardown), + unit_test_setup_teardown(torture_auth_password, setup, teardown), + unit_test_setup_teardown(torture_auth_password_nonblocking, setup, teardown), + unit_test_setup_teardown(torture_auth_autopubkey, setup, teardown), + unit_test_setup_teardown(torture_auth_autopubkey_nonblocking, setup, teardown), + unit_test_setup_teardown(torture_auth_agent, setup, teardown), + unit_test_setup_teardown(torture_auth_agent_nonblocking, setup, teardown), + }; + + ssh_init(); + + rc = run_tests(tests); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/client/torture_connect.c b/libssh/tests/client/torture_connect.c new file mode 100644 index 00000000..0e23fcd7 --- /dev/null +++ b/libssh/tests/client/torture_connect.c @@ -0,0 +1,132 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include + +#define HOST "localhost" +/* Should work until Apnic decides to assign it :) */ +#define BLACKHOLE "1.1.1.1" + +static void setup(void **state) { + int verbosity=torture_libssh_verbosity(); + ssh_session session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; +} + +static void teardown(void **state) { + ssh_session session = *state; + ssh_disconnect(session); + ssh_free(session); +} + +static void torture_connect_nonblocking(void **state) { + ssh_session session = *state; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, HOST); + assert_true(rc == SSH_OK); + ssh_set_blocking(session,0); + + do { + rc = ssh_connect(session); + assert_true(rc != SSH_ERROR); + } while(rc == SSH_AGAIN); + + assert_true(rc==SSH_OK); + +} + +static void torture_connect_timeout(void **state) { + ssh_session session = *state; + struct timeval before, after; + int rc; + long timeout = 2; + time_t sec; + suseconds_t usec; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, BLACKHOLE); + assert_true(rc == SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + assert_true(rc == SSH_OK); + + rc = gettimeofday(&before, NULL); + assert_true(rc == 0); + rc = ssh_connect(session); + assert_true(rc == SSH_ERROR); + rc = gettimeofday(&after, NULL); + assert_true(rc == 0); + sec = after.tv_sec - before.tv_sec; + usec = after.tv_usec - before.tv_usec; + /* Borrow a second for the missing usecs, but don't bother calculating */ + if(usec < 0) + sec--; + assert_in_range(sec,1,3); +} + +static void torture_connect_double(void **state) { + ssh_session session = *state; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, HOST); + assert_true(rc == SSH_OK); + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + +} + +static void torture_connect_failure(void **state){ + /* + * The intent of this test is to check that a fresh + * ssh_new/ssh_disconnect/ssh_free sequence doesn't crash/leak + * and the behavior of a double ssh_disconnect + */ + ssh_session session = *state; + ssh_disconnect(session); +} +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_connect_nonblocking, setup, teardown), + unit_test_setup_teardown(torture_connect_double, setup, teardown), + unit_test_setup_teardown(torture_connect_failure, setup, teardown), + unit_test_setup_teardown(torture_connect_timeout, setup, teardown), + }; + + ssh_init(); + + rc = run_tests(tests); + + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/client/torture_knownhosts.c b/libssh/tests/client/torture_knownhosts.c new file mode 100644 index 00000000..fe827cbc --- /dev/null +++ b/libssh/tests/client/torture_knownhosts.c @@ -0,0 +1,108 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "torture.h" +#include "session.c" + +#define KNOWNHOSTFILES "libssh_torture_knownhosts" + +static void setup(void **state) { + int verbosity=torture_libssh_verbosity(); + ssh_session session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; +} + +static void teardown(void **state) { + ssh_session session = *state; + + ssh_disconnect(session); + ssh_free(session); + + unlink(KNOWNHOSTFILES); +} + +static void torture_knownhosts_port(void **state) { + ssh_session session = *state; + char buffer[200]; + char *p; + FILE *file; + int rc; + + /* Connect to localhost:22, force the port to 1234 and then write + * the known hosts file. Then check that the entry written is + * [localhost]:1234 + */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, KNOWNHOSTFILES); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc==SSH_OK); + + session->opts.port = 1234; + rc = ssh_write_knownhost(session); + assert_true(rc == SSH_OK); + + file = fopen(KNOWNHOSTFILES, "r"); + assert_true(file != NULL); + p = fgets(buffer, sizeof(buffer), file); + assert_false(p == NULL); + fclose(file); + buffer[sizeof(buffer) - 1] = '\0'; + assert_true(strstr(buffer,"[localhost]:1234 ") != NULL); + + ssh_disconnect(session); + ssh_free(session); + + /* Now, connect back to the ssh server and verify the known host line */ + *state = session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, KNOWNHOSTFILES); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + session->opts.port = 1234; + rc = ssh_is_server_known(session); + assert_true(rc == SSH_SERVER_KNOWN_OK); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_knownhosts_port, setup, teardown), + }; + + ssh_init(); + + rc = run_tests(tests); + + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/client/torture_proxycommand.c b/libssh/tests/client/torture_proxycommand.c new file mode 100644 index 00000000..8cf68685 --- /dev/null +++ b/libssh/tests/client/torture_proxycommand.c @@ -0,0 +1,57 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include "libssh/priv.h" + +static void setup(void **state) { + ssh_session session = ssh_new(); + + *state = session; +} + +static void teardown(void **state) { + ssh_free(*state); +} + +static void torture_options_set_proxycommand(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "nc localhost 22"); + assert_true(rc == 0); + rc = ssh_connect(session); + assert_true(rc == SSH_OK); +} + +static void torture_options_set_proxycommand_notexist(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "this_command_does_not_exist"); + assert_true(rc == SSH_OK); + rc = ssh_connect(session); + assert_true(rc == SSH_ERROR); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_options_set_proxycommand, setup, teardown), + unit_test_setup_teardown(torture_options_set_proxycommand_notexist, setup, teardown), + }; + + + ssh_init(); + + rc = run_tests(tests); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/client/torture_session.c b/libssh/tests/client/torture_session.c new file mode 100644 index 00000000..d66901da --- /dev/null +++ b/libssh/tests/client/torture_session.c @@ -0,0 +1,108 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2012 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. + */ + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#define BUFLEN 4096 +static char buffer[BUFLEN]; + +static void setup(void **state) { + int verbosity = torture_libssh_verbosity(); + ssh_session session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; +} + +static void teardown(void **state) { + ssh_disconnect(*state); + ssh_free(*state); +} + +static void torture_channel_read_error(void **state) { + ssh_session session = *state; + char *user = getenv("TORTURE_USER"); + ssh_channel channel; + int rc; + int i; + + if (user == NULL) { + print_message("*** Please set the environment variable TORTURE_USER" + " to enable this test!!\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, user); + assert_true(rc == SSH_OK); + + rc = ssh_connect(session); + assert_true(rc == SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_true(ssh_get_error_code(session) == SSH_REQUEST_DENIED); + } + assert_true(ssh_auth_list(session) & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_autopubkey(session, NULL); + assert_true(rc == SSH_AUTH_SUCCESS); + + channel = ssh_channel_new(session); + assert_true(channel != NULL); + + rc = ssh_channel_open_session(channel); + assert_true(rc == SSH_OK); + + rc = ssh_channel_request_exec(channel, "hexdump -C /dev/urandom"); + assert_true(rc == SSH_OK); + + /* send crap and for server to send us a disconnect */ + write(ssh_get_fd(session),"AAAA", 4); + + for (i=0;i<20;++i){ + rc = ssh_channel_read(channel,buffer,sizeof(buffer),0); + if (rc == SSH_ERROR) + break; + } + assert_true(rc == SSH_ERROR); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_channel_read_error, setup, teardown), + }; + + ssh_init(); + + rc = run_tests(tests); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/client/torture_sftp_dir.c b/libssh/tests/client/torture_sftp_dir.c new file mode 100644 index 00000000..8d2bcda8 --- /dev/null +++ b/libssh/tests/client/torture_sftp_dir.c @@ -0,0 +1,74 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +static void setup(void **state) { + ssh_session session; + struct torture_sftp *t; + const char *host; + const char *user; + const char *password; + + host = getenv("TORTURE_HOST"); + if (host == NULL) { + host = "localhost"; + } + + user = getenv("TORTURE_USER"); + password = getenv("TORTURE_PASSWORD"); + + session = torture_ssh_session(host, user, password); + assert_false(session == NULL); + t = torture_sftp_session(session); + assert_false(t == NULL); + + *state = t; +} + +static void teardown(void **state) { + struct torture_sftp *t = *state; + + assert_false(t == NULL); + + torture_rmdirs(t->testdir); + torture_sftp_close(t); +} + +static void torture_sftp_mkdir(void **state) { + struct torture_sftp *t = *state; + char tmpdir[128] = {0}; + int rc; + + assert_false(t == NULL); + + snprintf(tmpdir, sizeof(tmpdir) - 1, "%s/mkdir_test", t->testdir); + + rc = sftp_mkdir(t->sftp, tmpdir, 0755); + if(rc != SSH_OK) + fprintf(stderr,"error:%s\n",ssh_get_error(t->sftp->session)); + assert_true(rc == 0); + + /* check if it really has been created */ + assert_true(torture_isdir(tmpdir)); + + rc = sftp_rmdir(t->sftp, tmpdir); + assert_true(rc == 0); + + /* check if it has been deleted */ + assert_false(torture_isdir(tmpdir)); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_sftp_mkdir, setup, teardown) + }; + + ssh_init(); + + rc = run_tests(tests); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/client/torture_sftp_static.c b/libssh/tests/client/torture_sftp_static.c new file mode 100644 index 00000000..7631def0 --- /dev/null +++ b/libssh/tests/client/torture_sftp_static.c @@ -0,0 +1,32 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +static void torture_sftp_ext_new(void **state) { + sftp_ext x; + + (void) state; + + x = sftp_ext_new(); + assert_false(x == NULL); + assert_int_equal(x->count, 0); + assert_true(x->name == NULL); + assert_true(x->data == NULL); + + sftp_ext_free(x); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test(torture_sftp_ext_new), + }; + + ssh_init(); + + rc = run_tests(tests); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/cmdline.c b/libssh/tests/cmdline.c new file mode 100644 index 00000000..4e2a7d02 --- /dev/null +++ b/libssh/tests/cmdline.c @@ -0,0 +1,71 @@ +#include "config.h" +#include "torture.h" + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh test 0.2"; +const char *argp_program_bug_address = ""; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "libssh test test"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh test more verbose", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case 'v': + arguments->verbose++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +/* static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else + (void) argc; + (void) argv; + (void) arguments; +#endif /* HAVE_ARGP_H */ +} diff --git a/libssh/tests/connection.c b/libssh/tests/connection.c new file mode 100644 index 00000000..889c5117 --- /dev/null +++ b/libssh/tests/connection.c @@ -0,0 +1,31 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ + +#include +#include +#include "tests.h" +SSH_OPTIONS *set_opts(int argc, char **argv){ + SSH_OPTIONS *options=ssh_options_new(); + char *host=NULL; + if(ssh_options_getopt(options,&argc, argv)){ + fprintf(stderr,"error parsing command line :%s\n",ssh_get_error(options)); + return NULL; + } + int i; + while((i=getopt(argc,argv,""))!=-1){ + switch(i){ + default: + fprintf(stderr,"unknown option %c\n",optopt); + } + } + if(optind < argc) + host=argv[optind++]; + if(host==NULL){ + fprintf(stderr,"must provide an host name\n"); + return NULL; + } + ssh_options_set_host(options,host); + return options; +} diff --git a/libssh/tests/ctest-default.cmake b/libssh/tests/ctest-default.cmake new file mode 100644 index 00000000..a8a3e211 --- /dev/null +++ b/libssh/tests/ctest-default.cmake @@ -0,0 +1,71 @@ +## The directory to run ctest in. +set(CTEST_DIRECTORY "$ENV{HOME}/workspace/tmp/dashboards/libssh") + +## The hostname of the machine +set(CTEST_SITE "host.libssh.org") +## The buildname +set(CTEST_BUILD_NAME "Linux_2.6-GCC_4.5-x86_64-default") + +## The Makefile generator to use +set(CTEST_CMAKE_GENERATOR "Unix Makefiles") + +## The Build configuration to use. +set(CTEST_BUILD_CONFIGURATION "Debug") + +## The build options for the project +set(CTEST_BUILD_OPTIONS "-DWITH_TESTING=ON -DWITH_SSH1=ON -WITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DDEBUG_CRYPTO=ON -DWITH_GCRYPT=OFF") + +#set(CTEST_CUSTOM_MEMCHECK_IGNORE torture_rand) + +## The Model to set: Nightly, Continous, Experimental +set(CTEST_MODEL "Experimental") + +## The branch +#set(CTEST_GIT_BRANCH "--branch v0-5") + +## Wether to enable memory checking. +set(WITH_MEMCHECK FALSE) + +## Wether to enable code coverage. +set(WITH_COVERAGE FALSE) + +####################################################################### + +if (WITH_COVERAGE AND NOT WIN32) + set(CTEST_BUILD_CONFIGURATION "Profiling") +endif (WITH_COVERAGE AND NOT WIN32) + +set(CTEST_SOURCE_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/source") +set(CTEST_BINARY_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/build") + +set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_SOURCE_DIR}/tests/valgrind.supp) + +find_program(CTEST_GIT_COMMAND NAMES git) +find_program(CTEST_COVERAGE_COMMAND NAMES gcov) +find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) + +if(NOT EXISTS "${CTEST_SOURCE_DIRECTORY}") + set(CTEST_CHECKOUT_COMMAND "${CTEST_GIT_COMMAND} clone ${CTEST_GIT_BRANCH} git://git.libssh.org/projects/libssh.git ${CTEST_SOURCE_DIRECTORY}") +endif() + +set(CTEST_UPDATE_COMMAND "${CTEST_GIT_COMMAND}") + +set(CTEST_CONFIGURE_COMMAND "${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE:STRING=${CTEST_BUILD_CONFIGURATION}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DWITH_TESTING:BOOL=ON ${CTEST_BUILD_OPTIONS}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"-G${CTEST_CMAKE_GENERATOR}\"") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"${CTEST_SOURCE_DIRECTORY}\"") + +ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY}) + +ctest_start(${CTEST_MODEL} TRACK ${CTEST_MODEL}) +ctest_update(SOURCE ${CTEST_SOURCE_DIRECTORY}) +ctest_configure(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_build(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}) +if (WITH_MEMCHECK AND CTEST_COVERAGE_COMMAND) + ctest_coverage(BUILD ${CTEST_BINARY_DIRECTORY}) +endif (WITH_MEMCHECK AND CTEST_COVERAGE_COMMAND) +if (WITH_MEMCHECK AND CTEST_MEMORYCHECK_COMMAND) + ctest_memcheck(BUILD ${CTEST_BINARY_DIRECTORY}) +endif (WITH_MEMCHECK AND CTEST_MEMORYCHECK_COMMAND) +ctest_submit() diff --git a/libssh/tests/generate.py b/libssh/tests/generate.py new file mode 100755 index 00000000..08c2d5b1 --- /dev/null +++ b/libssh/tests/generate.py @@ -0,0 +1,10 @@ +#!/usr/bin/python +import os +a="" +for i in xrange(4096): + a+=chr(i % 256); +while True: + try: + os.write(1,a) + except: + exit(0) diff --git a/libssh/tests/sftp_stress/main.c b/libssh/tests/sftp_stress/main.c new file mode 100644 index 00000000..c9b0ad9f --- /dev/null +++ b/libssh/tests/sftp_stress/main.c @@ -0,0 +1,174 @@ +/* + * main.c + * + * Created on: 22 juin 2009 + * Author: aris + */ +#include +#include +#include +#include +#include +#include +#include +#include +#define TEST_READ 1 +#define TEST_WRITE 2 +#define NTHREADS 3 +#define FILESIZE 100000 +unsigned char samplefile[FILESIZE]; +volatile int stop=0; + +const char* hosts[]={"localhost","barebone"}; +void signal_stop(){ + stop=1; + printf("Stopping...\n"); +} + +SSH_SESSION *connect_host(const char *hostname); +int sftp_test(SSH_SESSION *session, int test); + +int docycle(const char *host, int test){ + SSH_SESSION *session=connect_host(host); + int ret=SSH_ERROR; + if(!session){ + printf("connect failed\n"); + } else { + printf("Connected\n"); + ret=sftp_test(session,test); + if(ret != SSH_OK){ + printf("Error in sftp\n"); + } + ssh_disconnect(session); + } + return ret; +} + +int thread(){ + while(docycle(hosts[rand()%2],TEST_WRITE) == SSH_OK) + if(stop) + break; + return 0; +} + +int main(int argc, char **argv){ + int i; + pthread_t threads[NTHREADS]; + ssh_init(); + srand(time(NULL)); + for(i=0;i +#include +#include +#include "tests.h" + +void do_connect(SSH_SESSION *session) { + char buf[4096] = {0}; + CHANNEL *channel; + + int error = ssh_connect(session); + if (error != SSH_OK) { + fprintf(stderr,"Error at connection: %s\n", ssh_get_error(session)); + return; + } + printf("Connected\n"); + + ssh_is_server_known(session); + + error = authenticate(session); + if(error != SSH_AUTH_SUCCESS) { + fprintf(stderr,"Error at authentication: %s\n", ssh_get_error(session)); + return; + } + printf("Authenticated\n"); + channel = ssh_channel_new(session); + ssh_channel_open_session(channel); + printf("Execute 'ls' on the channel\n"); + error = ssh_channel_request_exec(channel, "ls"); + if(error != SSH_OK){ + fprintf(stderr, "Error executing command: %s\n", ssh_get_error(session)); + return; + } + printf("--------------------output----------------------\n"); + while (ssh_channel_read(channel, buf, sizeof(buf), 0)) { + printf("%s", buf); + } + printf("\n"); + printf("---------------------end------------------------\n"); + ssh_channel_send_eof(channel); + fprintf(stderr, "Exit status: %d\n", ssh_channel_get_exit_status(channel)); + + printf("\nChannel test finished\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +int main(int argc, char **argv){ + SSH_OPTIONS *options=set_opts(argc, argv); + SSH_SESSION *session=ssh_new(); + if(options==NULL){ + return 1; + } + ssh_set_options(session,options); + do_connect(session); + ssh_disconnect(session); + ssh_finalize(); + return 0; +} diff --git a/libssh/tests/test_pcap.c b/libssh/tests/test_pcap.c new file mode 100644 index 00000000..01aa714a --- /dev/null +++ b/libssh/tests/test_pcap.c @@ -0,0 +1,50 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* Simple test for the pcap functions */ + +#include +#include +#include + +#include +#include +#include + +int main(int argc, char **argv){ + ssh_pcap_file pcap; + ssh_pcap_context ctx; + ssh_buffer buffer=ssh_buffer_new(); + char *str="Hello, this is a test string to test the capabilities of the" + "pcap file writer."; + printf("Simple pcap tester\n"); + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,"test.cap") != SSH_OK){ + printf("error happened\n"); + return EXIT_FAILURE; + } + buffer_add_data(buffer,str,strlen(str)); + ctx=ssh_pcap_context_new(NULL); + ssh_pcap_context_set_file(ctx,pcap); + ssh_pcap_context_write(ctx,SSH_PCAP_DIR_OUT,str,strlen(str),strlen(str)); + + return EXIT_SUCCESS; +} diff --git a/libssh/tests/test_socket.c b/libssh/tests/test_socket.c new file mode 100644 index 00000000..84f7b35e --- /dev/null +++ b/libssh/tests/test_socket.c @@ -0,0 +1,93 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* Simple test for the socket callbacks */ + +#include +#include +#include +#include + +#include +#include +#include + +int stop=0; +ssh_socket s; + +static int data_rcv(const void *data, size_t len, void *user){ + printf("Received data: '"); + fwrite(data,1,len,stdout); + printf("'\n"); + ssh_socket_write(s,"Hello you !\n",12); + ssh_socket_nonblocking_flush(s); + return len; +} + +static void controlflow(int code,void *user){ + printf("Control flow: %x\n",code); +} + +static void exception(int code, int errno_code,void *user){ + printf("Exception: %d (%d)\n",code,errno_code); + stop=1; +} + +static void connected(int code, int errno_code,void *user){ + if(code == SSH_SOCKET_CONNECTED_OK) + printf("Connected: %d (%d)\n",code, errno_code); + else { + printf("Error while connecting:(%d, %d:%s)\n",code,errno_code,strerror(errno_code)); + stop=1; + } +} + +struct ssh_socket_callbacks_struct callbacks={ + data_rcv, + controlflow, + exception, + connected, + NULL +}; +int main(int argc, char **argv){ + ssh_session session; + ssh_poll_ctx ctx; + int verbosity=SSH_LOG_FUNCTIONS; + if(argc < 3){ + printf("Usage : %s host port\n", argv[0]); + return EXIT_FAILURE; + } + session=ssh_new(); + ssh_options_set(session,SSH_OPTIONS_LOG_VERBOSITY,&verbosity); + ssh_init(); + s=ssh_socket_new(session); + ctx=ssh_poll_ctx_new(2); + ssh_socket_set_callbacks(s, &callbacks); + ssh_poll_ctx_add_socket(ctx,s); + if(ssh_socket_connect(s,argv[1],atoi(argv[2]),NULL) != SSH_OK){ + printf("ssh_socket_connect: %s\n",ssh_get_error(session)); + return EXIT_FAILURE; + } + while(!stop) + ssh_poll_ctx_dopoll(ctx,-1); + printf("finished\n"); + return EXIT_SUCCESS; +} diff --git a/libssh/tests/test_tunnel.c b/libssh/tests/test_tunnel.c new file mode 100644 index 00000000..27f667b7 --- /dev/null +++ b/libssh/tests/test_tunnel.c @@ -0,0 +1,76 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ +#include +#include +#include +#include "tests.h" +#define ECHO_PORT 7 +void do_connect(SSH_SESSION *session){ + int error=ssh_connect(session); + if(error != SSH_OK){ + fprintf(stderr,"Error at connection :%s\n",ssh_get_error(session)); + return; + } + printf("Connected\n"); + ssh_is_server_known(session); + // we don't care what happens here + error=authenticate(session); + if(error != SSH_AUTH_SUCCESS){ + fprintf(stderr,"Error at authentication :%s\n",ssh_get_error(session)); + return; + } + printf("Authenticated\n"); + CHANNEL *channel=ssh_channel_new(session); + error=ssh_channel_open_forward(channel,"localhost",ECHO_PORT,"localhost",42); + if(error!=SSH_OK){ + fprintf(stderr,"Error when opening forward:%s\n",ssh_get_error(session)); + return; + } + printf("Forward opened\n"); + int i=0; + char string[20]; + char buffer[20]; + for(i=0;i<2000;++i){ + sprintf(string,"%d\n",i); + ssh_channel_write(channel,string,strlen(string)); + do { + error=ssh_channel_poll(channel,0); + //if(error < strlen(string)) + //usleep(10); + } while(error < strlen(string) && error >= 0); + if(error>0){ + error=ssh_channel_read_nonblocking(channel,buffer,strlen(string),0); + if(error>=0){ + if(memcmp(buffer,string,strlen(string))!=0){ + fprintf(stderr,"Problem with answer: wanted %s got %s\n",string,buffer); + } else { + printf("."); + fflush(stdout); + } + } + + } + if(error==-1){ + fprintf(stderr,"Channel reading error : %s\n",ssh_get_error(session)); + break; + } + } + printf("\nChannel test finished\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +int main(int argc, char **argv){ + SSH_OPTIONS *options=set_opts(argc, argv); + SSH_SESSION *session=ssh_new(); + if(options==NULL){ + return 1; + } + ssh_set_options(session,options); + do_connect(session); + ssh_disconnect(session); + ssh_finalize(); + return 0; +} diff --git a/libssh/tests/tests.h b/libssh/tests/tests.h new file mode 100644 index 00000000..dd001f1f --- /dev/null +++ b/libssh/tests/tests.h @@ -0,0 +1,8 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ +#include +int authenticate (SSH_SESSION *session); +SSH_OPTIONS *set_opts(int argc, char **argv); + diff --git a/libssh/tests/torture.c b/libssh/tests/torture.c new file mode 100644 index 00000000..a75b0a94 --- /dev/null +++ b/libssh/tests/torture.c @@ -0,0 +1,316 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * 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. + */ + +#include "config.h" + +#include +#include +#ifndef _WIN32 +# include +# include +# include +# include +# include +#endif + +#include "torture.h" + +static int verbosity = 0; + +#ifndef _WIN32 +static int _torture_auth_kbdint(ssh_session session, + const char *password) { + const char *prompt; + char echo; + int err; + + if (session == NULL || password == NULL) { + return SSH_AUTH_ERROR; + } + + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_ERROR) { + return err; + } + + if (ssh_userauth_kbdint_getnprompts(session) != 1) { + return SSH_AUTH_ERROR; + } + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + if (prompt == NULL) { + return SSH_AUTH_ERROR; + } + + if (ssh_userauth_kbdint_setanswer(session, 0, password) < 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_INFO) { + if (ssh_userauth_kbdint_getnprompts(session) != 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + } + + return err; +} + +int torture_rmdirs(const char *path) { + DIR *d; + struct dirent *dp; + struct stat sb; + char *fname; + + if ((d = opendir(path)) != NULL) { + while(stat(path, &sb) == 0) { + /* if we can remove the directory we're done */ + if (rmdir(path) == 0) { + break; + } + switch (errno) { + case ENOTEMPTY: + case EEXIST: + case EBADF: + break; /* continue */ + default: + closedir(d); + return 0; + } + + while ((dp = readdir(d)) != NULL) { + size_t len; + /* skip '.' and '..' */ + if (dp->d_name[0] == '.' && + (dp->d_name[1] == '\0' || + (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { + continue; + } + + len = strlen(path) + strlen(dp->d_name) + 2; + fname = malloc(len); + if (fname == NULL) { + return -1; + } + snprintf(fname, len, "%s/%s", path, dp->d_name); + + /* stat the file */ + if (lstat(fname, &sb) != -1) { + if (S_ISDIR(sb.st_mode) && !S_ISLNK(sb.st_mode)) { + if (rmdir(fname) < 0) { /* can't be deleted */ + if (errno == EACCES) { + closedir(d); + SAFE_FREE(fname); + return -1; + } + torture_rmdirs(fname); + } + } else { + unlink(fname); + } + } /* lstat */ + SAFE_FREE(fname); + } /* readdir */ + + rewinddir(d); + } + } else { + return -1; + } + + closedir(d); + return 0; +} + +int torture_isdir(const char *path) { + struct stat sb; + + if (lstat (path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 1; + } + + return 0; +} + +ssh_session torture_ssh_session(const char *host, + const char *user, + const char *password) { + ssh_session session; + int method; + int rc; + + if (host == NULL) { + return NULL; + } + + session = ssh_new(); + if (session == NULL) { + return NULL; + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + goto failed; + } + + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + goto failed; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity) < 0) { + goto failed; + } + + if (ssh_connect(session)) { + goto failed; + } + + /* We are in testing mode, so consinder the hostkey as verified ;) */ + + /* This request should return a SSH_REQUEST_DENIED error */ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_ERROR) { + goto failed; + } + method = ssh_userauth_list(session, NULL); + if (method == 0) { + goto failed; + } + + if (password != NULL) { + if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = _torture_auth_kbdint(session, password); + } else if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + } + } else { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR) { + goto failed; + } + } + if (rc != SSH_AUTH_SUCCESS) { + goto failed; + } + + return session; +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + +#ifdef WITH_SFTP + +struct torture_sftp *torture_sftp_session(ssh_session session) { + struct torture_sftp *t; + char template[] = "/tmp/ssh_torture_XXXXXX"; + char *p; + int rc; + + if (session == NULL) { + return NULL; + } + + t = malloc(sizeof(struct torture_sftp)); + if (t == NULL) { + return NULL; + } + + t->ssh = session; + t->sftp = sftp_new(session); + if (t->sftp == NULL) { + goto failed; + } + + rc = sftp_init(t->sftp); + if (rc < 0) { + goto failed; + } + + p = mkdtemp(template); + if (p == NULL) { + goto failed; + } + /* useful if TESTUSER is not the local user */ + chmod(template,0777); + t->testdir = strdup(p); + if (t->testdir == NULL) { + goto failed; + } + + return t; +failed: + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + ssh_disconnect(t->ssh); + ssh_free(t->ssh); + free(t); + + return NULL; +} + +void torture_sftp_close(struct torture_sftp *t) { + if (t == NULL) { + return; + } + + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + + if (t->ssh != NULL) { + if (ssh_is_connected(t->ssh)) { + ssh_disconnect(t->ssh); + } + ssh_free(t->ssh); + } + + free(t->testdir); + free(t); +} +#endif /* WITH_SFTP */ + +#endif /* _WIN32 */ + + +int torture_libssh_verbosity(void){ + return verbosity; +} + +int main(int argc, char **argv) { + struct argument_s arguments; + + arguments.verbose=0; + torture_cmdline_parse(argc, argv, &arguments); + verbosity=arguments.verbose; + + return torture_run_tests(); +} + +/* vim: set ts=4 sw=4 et cindent syntax=c.doxygen: */ diff --git a/libssh/tests/torture.h b/libssh/tests/torture.h new file mode 100644 index 00000000..dbc1906a --- /dev/null +++ b/libssh/tests/torture.h @@ -0,0 +1,76 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * 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. + */ + +#ifndef _TORTURE_H +#define _TORTURE_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/sftp.h" + +#include + +/* Used by main to communicate with parse_opt. */ +struct argument_s { + char *args[2]; + int verbose; +}; + +struct torture_sftp { + ssh_session ssh; + sftp_session sftp; + char *testdir; +}; + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments); + +int torture_rmdirs(const char *path); +int torture_isdir(const char *path); + +/* + * Returns the verbosity level asked by user + */ +int torture_libssh_verbosity(void); + +ssh_session torture_ssh_session(const char *host, + const char *user, + const char *password); + +struct torture_sftp *torture_sftp_session(ssh_session session); +void torture_sftp_close(struct torture_sftp *t); + +/* + * This function must be defined in every unit test file. + */ +int torture_run_tests(void); + +#endif /* _TORTURE_H */ diff --git a/libssh/tests/unittests/CMakeLists.txt b/libssh/tests/unittests/CMakeLists.txt new file mode 100644 index 00000000..d8a6125f --- /dev/null +++ b/libssh/tests/unittests/CMakeLists.txt @@ -0,0 +1,16 @@ +project(unittests C) + +add_cmocka_test(torture_buffer torture_buffer.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_callbacks torture_callbacks.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_init torture_init.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_list torture_list.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_misc torture_misc.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_options torture_options.c ${TORTURE_LIBRARY}) +add_cmocka_test(torture_isipaddr torture_isipaddr.c ${TORTURE_LIBRARY}) +if (UNIX AND NOT WIN32) + # requires ssh-keygen + add_cmocka_test(torture_keyfiles torture_keyfiles.c ${TORTURE_LIBRARY}) + add_cmocka_test(torture_pki torture_pki.c ${TORTURE_LIBRARY}) + # requires pthread + add_cmocka_test(torture_rand torture_rand.c ${TORTURE_LIBRARY}) +endif (UNIX AND NOT WIN32) diff --git a/libssh/tests/unittests/torture_buffer.c b/libssh/tests/unittests/torture_buffer.c new file mode 100644 index 00000000..511cdf45 --- /dev/null +++ b/libssh/tests/unittests/torture_buffer.c @@ -0,0 +1,132 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#define LIMIT (8*1024*1024) + +static void setup(void **state) { + ssh_buffer buffer; + buffer = ssh_buffer_new(); + *state = (void *) buffer; +} + +static void teardown(void **state) { + ssh_buffer_free(*state); +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void torture_growing_buffer(void **state) { + ssh_buffer buffer = *state; + int i; + + for(i=0;iused >= 128){ + if(buffer_get_rest_len(buffer) * 2 < buffer->allocated){ + assert_true(buffer_get_rest_len(buffer) * 2 >= buffer->allocated); + } + } + } +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void torture_growing_buffer_shifting(void **state) { + ssh_buffer buffer = *state; + int i; + unsigned char c; + for(i=0; i<1024;++i){ + buffer_add_data(buffer,"S",1); + } + for(i=0;iused >= 128){ + if(buffer_get_rest_len(buffer) * 4 < buffer->allocated){ + assert_true(buffer_get_rest_len(buffer) * 4 >= buffer->allocated); + return; + } + } + } +} + +/* + * Test the behavior of buffer_prepend_data + */ +static void torture_buffer_prepend(void **state) { + ssh_buffer buffer = *state; + uint32_t v; + buffer_add_data(buffer,"abcdef",6); + buffer_prepend_data(buffer,"xyz",3); + assert_int_equal(buffer_get_rest_len(buffer),9); + assert_int_equal(memcmp(buffer_get_rest(buffer), "xyzabcdef", 9), 0); +// Now remove 4 bytes and see if we can replace them + buffer_get_u32(buffer,&v); + assert_int_equal(buffer_get_rest_len(buffer),5); + assert_int_equal(memcmp(buffer_get_rest(buffer), "bcdef", 5), 0); + buffer_prepend_data(buffer,"aris",4); + assert_int_equal(buffer_get_rest_len(buffer),9); + assert_int_equal(memcmp(buffer_get_rest(buffer), "arisbcdef", 9), 0); + /* same thing but we add 5 bytes now */ + buffer_get_u32(buffer,&v); + assert_int_equal(buffer_get_rest_len(buffer),5); + assert_int_equal(memcmp(buffer_get_rest(buffer), "bcdef", 5), 0); + buffer_prepend_data(buffer,"12345",5); + assert_int_equal(buffer_get_rest_len(buffer),10); + assert_int_equal(memcmp(buffer_get_rest(buffer), "12345bcdef", 10), 0); + +} + +/* + * Test the behavior of buffer_get_ssh_string with invalid data + */ +static void torture_buffer_get_ssh_string(void **state) { + ssh_buffer buffer; + int i,j,k,l; + /* some values that can go wrong */ + uint32_t values[] = {0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128]; + (void)state; + memset(data,'X',sizeof(data)); + for(i=0; i < (int)(sizeof(values)/sizeof(values[0]));++i){ + for(j=0; j< (int)sizeof(data);++j){ + for(k=1;k<5;++k){ + buffer=buffer_new(); + for(l=0;l +#include + +static int myauthcallback (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) { + (void) prompt; + (void) buf; + (void) len; + (void) echo; + (void) verify; + (void) userdata; + return 0; +} + +static void setup(void **state) { + struct ssh_callbacks_struct *cb; + + cb = malloc(sizeof(struct ssh_callbacks_struct)); + assert_false(cb == NULL); + ZERO_STRUCTP(cb); + + cb->userdata = (void *) 0x0badc0de; + cb->auth_function = myauthcallback; + + ssh_callbacks_init(cb); + *state = cb; +} + +static void teardown(void **state) { + free(*state); +} + +static void torture_callbacks_size(void **state) { + struct ssh_callbacks_struct *cb = *state;; + + assert_int_not_equal(cb->size, 0); +} + +static void torture_callbacks_exists(void **state) { + struct ssh_callbacks_struct *cb = *state; + + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); + assert_int_equal(ssh_callbacks_exists(cb, log_function), 0); + + /* + * We redefine size so auth_function is outside the range of + * callbacks->size. + */ + cb->size = (unsigned char *) &cb->auth_function - (unsigned char *) cb; + assert_int_equal(ssh_callbacks_exists(cb, auth_function), 0); + + /* Now make it one pointer bigger so we spill over the auth_function slot */ + cb->size += sizeof(void *); + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_callbacks_size, setup, teardown), + unit_test_setup_teardown(torture_callbacks_exists, setup, teardown), + }; + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_init.c b/libssh/tests/unittests/torture_init.c new file mode 100644 index 00000000..85bd95e1 --- /dev/null +++ b/libssh/tests/unittests/torture_init.c @@ -0,0 +1,23 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" + +static void torture_ssh_init(void **state) { + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +int torture_run_tests(void) { + const UnitTest tests[] = { + unit_test(torture_ssh_init), + }; + + return run_tests(tests); +} diff --git a/libssh/tests/unittests/torture_isipaddr.c b/libssh/tests/unittests/torture_isipaddr.c new file mode 100644 index 00000000..c2a1e079 --- /dev/null +++ b/libssh/tests/unittests/torture_isipaddr.c @@ -0,0 +1,56 @@ +#define LIBSSH_STATIC + +#include "torture.h" + +#include "misc.c" +#include "error.c" + +/* + * Test the behavior of ssh_is_ipaddr() + */ +static void torture_ssh_is_ipaddr(void **state) { + (void)state; + + assert_int_equal(ssh_is_ipaddr("127.0.0.1"),1); + assert_int_equal(ssh_is_ipaddr("0.0.0.0"),1); + assert_int_equal(ssh_is_ipaddr("1.1.1.1"),1); + assert_int_equal(ssh_is_ipaddr("255.255.255.255"),1); + assert_int_equal(ssh_is_ipaddr("128.128.128.128"),1); + assert_int_equal(ssh_is_ipaddr("1.10.100.1"),1); + assert_int_equal(ssh_is_ipaddr("0.1.10.100"),1); + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0000:0000:0000:0202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0:0:0:202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80::202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("::1"),1); + + assert_int_equal(ssh_is_ipaddr("::ffff:192.0.2.128"),1); + + assert_int_equal(ssh_is_ipaddr("0.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0.0.0.0.a"),0); + assert_int_equal(ssh_is_ipaddr("a.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0a.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr(""),0); + assert_int_equal(ssh_is_ipaddr("0.0.0."),0); + assert_int_equal(ssh_is_ipaddr("0.0"),0); + assert_int_equal(ssh_is_ipaddr("0"),0); + assert_int_equal(ssh_is_ipaddr("255.255.255"),0); + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1002"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr(":1"), 0); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test(torture_ssh_is_ipaddr) + }; + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_keyfiles.c b/libssh/tests/unittests/torture_keyfiles.c new file mode 100644 index 00000000..9446bc6d --- /dev/null +++ b/libssh/tests/unittests/torture_keyfiles.c @@ -0,0 +1,261 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include "legacy.c" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#define LIBSSH_PASSPHRASE "libssh-rocks" + +static void setup_rsa_key(void **state) { + ssh_session session; + int rc; + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = system("ssh-keygen -t rsa -q -N \"\" -f " LIBSSH_RSA_TESTKEY); + assert_true(rc == 0); + + session = ssh_new(); + *state = session; +} + +static void setup_dsa_key(void **state) { + ssh_session session; + int rc; + + unlink(LIBSSH_DSA_TESTKEY); + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + rc = system("ssh-keygen -t dsa -q -N \"\" -f " LIBSSH_DSA_TESTKEY); + assert_true(rc == 0); + + session = ssh_new(); + *state = session; +} + +static void setup_both_keys(void **state) { + setup_rsa_key(state); + ssh_free(*state); + setup_dsa_key(state); +} + +static void setup_both_keys_passphrase(void **state) { + ssh_session session; + int rc; + + rc = system("ssh-keygen -t rsa -N " LIBSSH_PASSPHRASE " -f " LIBSSH_RSA_TESTKEY); + assert_true(rc == 0); + + rc = system("ssh-keygen -t dsa -N " LIBSSH_PASSPHRASE " -f " LIBSSH_DSA_TESTKEY); + assert_true(rc == 0); + + session = ssh_new(); + *state = session; +} +static void teardown(void **state) { + unlink(LIBSSH_DSA_TESTKEY); + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + ssh_free(*state); +} + +static void torture_pubkey_from_file(void **state) { + ssh_session session = *state; + ssh_string pubkey; + int type, rc; + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + + assert_true(rc == 0); + + ssh_string_free(pubkey); + + /* test if it returns 1 if pubkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == 1); + + /* test if it returns -1 if privkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == -1); +} + +static int torture_read_one_line(const char *filename, char *buffer, size_t len) { + FILE *fp; + size_t rc; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + rc = fread(buffer, len, 1, fp); + if (rc != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + + fclose(fp); + + return 0; +} + +static void torture_pubkey_generate_from_privkey(void **state) { + ssh_session session = *state; + ssh_private_key privkey = NULL; + ssh_public_key pubkey = NULL; + ssh_string pubkey_orig = NULL; + ssh_string pubkey_new = NULL; + char pubkey_line_orig[512] = {0}; + char pubkey_line_new[512] = {0}; + int type_orig = 0; + int type_new = 0; + int rc; + + /* read the publickey */ + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey_orig, + &type_orig); + assert_true(rc == 0); + assert_true(pubkey_orig != NULL); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_orig, + sizeof(pubkey_line_orig)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + privkey = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_true(privkey != NULL); + + pubkey = publickey_from_privatekey(privkey); + assert_true(pubkey != NULL); + type_new = privkey->type; + privatekey_free(privkey); + + pubkey_new = publickey_to_string(pubkey); + publickey_free(pubkey); + + assert_true(pubkey_new != NULL); + + assert_true(ssh_string_len(pubkey_orig) == ssh_string_len(pubkey_new)); + assert_memory_equal(ssh_string_data(pubkey_orig), + ssh_string_data(pubkey_new), + ssh_string_len(pubkey_orig)); + + rc = ssh_publickey_to_file(session, LIBSSH_RSA_TESTKEY ".pub", pubkey_new, type_new); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_new, + sizeof(pubkey_line_new)); + assert_true(rc == 0); + + assert_string_equal(pubkey_line_orig, pubkey_line_new); + + ssh_string_free(pubkey_orig); + ssh_string_free(pubkey_new); +} + +/** + * @brief tests the privatekey_from_file function without passphrase + */ +static void torture_privatekey_from_file(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, NULL); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, SSH_KEYTYPE_DSS, NULL); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, 0, NULL); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } +} + +/** + * @brief tests the privatekey_from_file function with passphrase + */ +static void torture_privatekey_from_file_passphrase(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, LIBSSH_PASSPHRASE); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, SSH_KEYTYPE_DSS, LIBSSH_PASSPHRASE); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, LIBSSH_PASSPHRASE); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, 0, LIBSSH_PASSPHRASE); + assert_true(key != NULL); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_pubkey_from_file, + setup_rsa_key, + teardown), + unit_test_setup_teardown(torture_pubkey_generate_from_privkey, + setup_rsa_key, teardown), + unit_test_setup_teardown(torture_privatekey_from_file, + setup_both_keys, + teardown), + unit_test_setup_teardown(torture_privatekey_from_file_passphrase, + setup_both_keys_passphrase, teardown), + }; + + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_list.c b/libssh/tests/unittests/torture_list.c new file mode 100644 index 00000000..75f41825 --- /dev/null +++ b/libssh/tests/unittests/torture_list.c @@ -0,0 +1,91 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include "error.c" +#include "misc.c" + +static void torture_ssh_list_new(void **state) { + struct ssh_list *xlist; + + (void) state; + + xlist = ssh_list_new(); + + assert_true(xlist != NULL); + assert_true(xlist->root == NULL); + assert_true(xlist->end == NULL); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_append(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_true(xlist != NULL); + + rc = ssh_list_append(xlist, "item1"); + assert_true(rc == 0); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_append(xlist, "item3"); + assert_true(rc == 0); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->root->next->data, "item2"); + assert_string_equal((const char *) xlist->root->next->next->data, "item3"); + assert_string_equal((const char *) xlist->end->data, "item3"); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_prepend(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_true(xlist != NULL); + + rc = ssh_list_prepend(xlist, "item1"); + assert_true(rc == 0); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_prepend(xlist, "item3"); + assert_true(rc == 0); + assert_string_equal((const char *) xlist->root->data, "item3"); + assert_string_equal((const char *) xlist->root->next->data, "item1"); + assert_string_equal((const char *) xlist->root->next->next->data, "item2"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + ssh_list_free(xlist); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test(torture_ssh_list_new), + unit_test(torture_ssh_list_append), + unit_test(torture_ssh_list_prepend), + }; + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_misc.c b/libssh/tests/unittests/torture_misc.c new file mode 100644 index 00000000..26770324 --- /dev/null +++ b/libssh/tests/unittests/torture_misc.c @@ -0,0 +1,210 @@ + +#include +#ifndef _WIN32 + +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + +#define LIBSSH_STATIC +#include + +#include "torture.h" +#include "misc.c" +#include "error.c" + +#define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." + + +static void setup(void **state) { + ssh_session session = ssh_new(); + *state = session; +} + +static void teardown(void **state) { + ssh_free(*state); +} + +static void torture_get_user_home_dir(void **state) { +#ifndef _WIN32 + struct passwd *pwd = getpwuid(getuid()); +#endif /* _WIN32 */ + char *user; + + (void) state; + + user = ssh_get_user_home_dir(); + assert_false(user == NULL); +#ifndef _WIN32 + assert_string_equal(user, pwd->pw_dir); +#endif /* _WIN32 */ + + SAFE_FREE(user); +} + +static void torture_basename(void **state) { + char *path; + + (void) state; + + path=ssh_basename(TORTURE_TEST_DIR "/test"); + assert_true(path != NULL); + assert_string_equal(path, "test"); + SAFE_FREE(path); + path=ssh_basename(TORTURE_TEST_DIR "/test/"); + assert_true(path != NULL); + assert_string_equal(path, "test"); + SAFE_FREE(path); +} + +static void torture_dirname(void **state) { + char *path; + + (void) state; + + path=ssh_dirname(TORTURE_TEST_DIR "/test"); + assert_true(path != NULL); + assert_string_equal(path, TORTURE_TEST_DIR ); + SAFE_FREE(path); + path=ssh_dirname(TORTURE_TEST_DIR "/test/"); + assert_true(path != NULL); + assert_string_equal(path, TORTURE_TEST_DIR); + SAFE_FREE(path); +} + +static void torture_ntohll(void **state) { + uint64_t value = 0x0123456789abcdef; + uint32_t sample = 1; + unsigned char *ptr = (unsigned char *) &sample; + uint64_t check; + + (void) state; + + if (ptr[0] == 1){ + /* we're in little endian */ + check = 0xefcdab8967452301; + } else { + /* big endian */ + check = value; + } + value = ntohll(value); + assert_true(value == check); +} + +#ifdef _WIN32 + +static void torture_path_expand_tilde_win(void **state) { + char *d; + + (void) state; + + d = ssh_path_expand_tilde("~\\.ssh"); + assert_false(d == NULL); + print_message("Expanded path: %s\n", d); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_string_equal(d, "/guru/meditation"); + free(d); +} + +#else /* _WIN32 */ + +static void torture_path_expand_tilde_unix(void **state) { + char h[256]; + char *d; + + (void) state; + + snprintf(h, 256 - 1, "%s/.ssh", getenv("HOME")); + + d = ssh_path_expand_tilde("~/.ssh"); + assert_string_equal(d, h); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_string_equal(d, "/guru/meditation"); + free(d); + + snprintf(h, 256 - 1, "~%s/.ssh", getenv("USER")); + d = ssh_path_expand_tilde(h); + + snprintf(h, 256 - 1, "%s/.ssh", getenv("HOME")); + assert_string_equal(d, h); + free(d); +} + +#endif /* _WIN32 */ + +static void torture_path_expand_escape(void **state) { + ssh_session session = *state; + const char *s = "%d/%h/by/%r"; + char *e; + + session->opts.sshdir = strdup("guru"); + session->opts.host = strdup("meditation"); + session->opts.username = strdup("root"); + + e = ssh_path_expand_escape(session, s); + assert_string_equal(e, "guru/meditation/by/root"); + free(e); +} + +static void torture_path_expand_known_hosts(void **state) { + ssh_session session = *state; + char *tmp; + + session->opts.sshdir = strdup("/home/guru/.ssh"); + + tmp = ssh_path_expand_escape(session, "%d/known_hosts"); + assert_string_equal(tmp, "/home/guru/.ssh/known_hosts"); + free(tmp); +} + +static void torture_timeout_elapsed(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(50000); + assert_true(ssh_timeout_elapsed(&ts,25)); + assert_false(ssh_timeout_elapsed(&ts,30000)); + assert_false(ssh_timeout_elapsed(&ts,75)); + assert_true(ssh_timeout_elapsed(&ts,0)); + assert_false(ssh_timeout_elapsed(&ts,-1)); +} + +static void torture_timeout_update(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(50000); + assert_int_equal(ssh_timeout_update(&ts,25), 0); + assert_in_range(ssh_timeout_update(&ts,30000),29000,29960); + assert_in_range(ssh_timeout_update(&ts,75),1,40); + assert_int_equal(ssh_timeout_update(&ts,0),0); + assert_int_equal(ssh_timeout_update(&ts,-1),-1); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test(torture_get_user_home_dir), + unit_test(torture_basename), + unit_test(torture_dirname), + unit_test(torture_ntohll), +#ifdef _WIN32 + unit_test(torture_path_expand_tilde_win), +#else + unit_test(torture_path_expand_tilde_unix), +#endif + unit_test_setup_teardown(torture_path_expand_escape, setup, teardown), + unit_test_setup_teardown(torture_path_expand_known_hosts, setup, teardown), + unit_test(torture_timeout_elapsed), + unit_test(torture_timeout_update), + }; + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_options.c b/libssh/tests/unittests/torture_options.c new file mode 100644 index 00000000..76da3edb --- /dev/null +++ b/libssh/tests/unittests/torture_options.c @@ -0,0 +1,197 @@ +#define LIBSSH_STATIC + +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +# include +#endif + +#include "torture.h" +#include +#include + +static void setup(void **state) { + ssh_session session = ssh_new(); + *state = session; +} + +static void teardown(void **state) { + ssh_free(*state); +} + +static void torture_options_set_host(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_string_equal(session->opts.host, "localhost"); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); + assert_true(rc == 0); + assert_string_equal(session->opts.host, "meditation"); + assert_string_equal(session->opts.username, "guru"); +} + +static void torture_options_get_host(void **state) { + ssh_session session = *state; + int rc; + char* host = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_string_equal(session->opts.host, "localhost"); + + assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host)); + + assert_string_equal(host, "localhost"); + free(host); +} + +static void torture_options_set_port(void **state) { + ssh_session session = *state; + int rc; + unsigned int port = 42; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_true(rc == 0); + assert_true(session->opts.port == port); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "23"); + assert_true(rc == 0); + assert_true(session->opts.port == 23); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); + assert_true(rc == -1); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); + assert_true(rc == -1); +} + +static void torture_options_get_port(void **state) { + ssh_session session = *state; + unsigned int given_port = 1234; + unsigned int port_container; + int rc; + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); + assert_true(rc == 0); + rc = ssh_options_get_port(session, &port_container); + assert_true(rc == 0); + assert_int_equal(port_container, 1234); +} + +static void torture_options_get_user(void **state) { + ssh_session session = *state; + char* user = NULL; + int rc; + rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); + assert_true(rc == SSH_OK); + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_string_equal(user, "magicaltrevor"); + free(user); +} + +static void torture_options_set_fd(void **state) { + ssh_session session = *state; + socket_t fd = 42; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_FD, &fd); + assert_true(rc == 0); + assert_true(session->opts.fd == fd); + + rc = ssh_options_set(session, SSH_OPTIONS_FD, NULL); + assert_true(rc == SSH_ERROR); + assert_true(session->opts.fd == SSH_INVALID_SOCKET); +} + +static void torture_options_set_user(void **state) { + ssh_session session = *state; + int rc; +#ifndef _WIN32 +# ifndef NSS_BUFLEN_PASSWD +# define NSS_BUFLEN_PASSWD 4096 +# endif /* NSS_BUFLEN_PASSWD */ + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + + /* get local username */ + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + assert_true(rc == 0); +#endif /* _WIN32 */ + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); + assert_true(rc == 0); + assert_string_equal(session->opts.username, "guru"); + + + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + assert_true(rc == 0); + +#ifndef _WIN32 + assert_string_equal(session->opts.username, pwd.pw_name); +#endif +} + +/* TODO */ +#if 0 +static voidtorture_options_set_sshdir) +{ +} +END_TEST +#endif + +static void torture_options_set_identity(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity->root->data, "identity1"); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity->root->data, "identity2"); + assert_string_equal(session->opts.identity->root->next->data, "identity1"); +} + +static void torture_options_get_identity(void **state) { + ssh_session session = *state; + char *identity = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_true(rc == SSH_OK); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity->root->data, "identity2"); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_true(rc == SSH_OK); + assert_string_equal(identity, "identity2"); + free(identity); +} + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test_setup_teardown(torture_options_set_host, setup, teardown), + unit_test_setup_teardown(torture_options_get_host, setup, teardown), + unit_test_setup_teardown(torture_options_set_port, setup, teardown), + unit_test_setup_teardown(torture_options_get_port, setup, teardown), + unit_test_setup_teardown(torture_options_set_fd, setup, teardown), + unit_test_setup_teardown(torture_options_set_user, setup, teardown), + unit_test_setup_teardown(torture_options_get_user, setup, teardown), + unit_test_setup_teardown(torture_options_set_identity, setup, teardown), + unit_test_setup_teardown(torture_options_get_identity, setup, teardown), + }; + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_pki.c b/libssh/tests/unittests/torture_pki.c new file mode 100644 index 00000000..31618a41 --- /dev/null +++ b/libssh/tests/unittests/torture_pki.c @@ -0,0 +1,1037 @@ +#define LIBSSH_STATIC + +#include "torture.h" +#include "pki.c" +#include +#include + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_ecdsa" +#define LIBSSH_PASSPHRASE "libssh-rocks" +const unsigned char HASH[] = "12345678901234567890"; + +static void setup_rsa_key(void **state) { + int rc; + + (void) state; /* unused */ + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = system("ssh-keygen -t rsa -q -N \"\" -f " LIBSSH_RSA_TESTKEY); + assert_true(rc == 0); +} + +static void setup_dsa_key(void **state) { + int rc; + + (void) state; /* unused */ + + unlink(LIBSSH_DSA_TESTKEY); + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + rc = system("ssh-keygen -t dsa -q -N \"\" -f " LIBSSH_DSA_TESTKEY); + assert_true(rc == 0); +} + +#ifdef HAVE_OPENSSL_ECC +static void setup_ecdsa_key(void **state) { + int rc; + + (void) state; /* unused */ + + unlink(LIBSSH_ECDSA_TESTKEY); + unlink(LIBSSH_ECDSA_TESTKEY ".pub"); + + rc = system("ssh-keygen -t ecdsa -q -N \"\" -f " LIBSSH_ECDSA_TESTKEY); + assert_true(rc == 0); +} +#endif + +static void setup_both_keys(void **state) { + (void) state; /* unused */ + + setup_rsa_key(state); + setup_dsa_key(state); +} + +static void setup_both_keys_passphrase(void **state) { + int rc; + + (void) state; /* unused */ + + rc = system("ssh-keygen -t rsa -q -N " LIBSSH_PASSPHRASE " -f " LIBSSH_RSA_TESTKEY); + assert_true(rc == 0); + + rc = system("ssh-keygen -t dsa -q -N " LIBSSH_PASSPHRASE " -f " LIBSSH_DSA_TESTKEY); + assert_true(rc == 0); +} + +static void teardown(void **state) { + (void) state; /* unused */ + + unlink(LIBSSH_DSA_TESTKEY); + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + unlink(LIBSSH_ECDSA_TESTKEY); + unlink(LIBSSH_ECDSA_TESTKEY ".pub"); +} + +static char *read_file(const char *filename) { + char *key; + int fd; + int size; + struct stat buf; + + assert_true(filename != NULL); + assert_true(*filename != '\0'); + + stat(filename, &buf); + + key = malloc(buf.st_size + 1); + assert_true(key != NULL); + + fd = open(filename, O_RDONLY); + assert_true(fd >= 0); + + size = read(fd, key, buf.st_size); + assert_true(size == buf.st_size); + + close(fd); + + key[size] = '\0'; + return key; +} + +static int torture_read_one_line(const char *filename, char *buffer, size_t len) { + FILE *fp; + size_t rc; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + rc = fread(buffer, len, 1, fp); + if (rc != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + + fclose(fp); + + return 0; +} + +static void torture_pki_keytype(void **state) { + enum ssh_keytypes_e type; + const char *type_c; + + (void) state; /* unused */ + + type = ssh_key_type(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name("42"); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type_c = ssh_key_type_to_char(SSH_KEYTYPE_UNKNOWN); + assert_true(type_c == NULL); + + type_c = ssh_key_type_to_char(42); + assert_true(type_c == NULL); +} + +static void torture_pki_signature(void **state) +{ + ssh_signature sig; + + (void) state; /* unused */ + + sig = ssh_signature_new(); + assert_true(sig != NULL); + + ssh_signature_free(sig); +} + +static void torture_pki_import_privkey_base64_RSA(void **state) { + int rc; + char *key_str; + ssh_key key; + const char *passphrase = LIBSSH_PASSPHRASE; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_RSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + ssh_key_free(key); +} + +static void torture_pki_import_privkey_base64_NULL_key(void **state) { + int rc; + char *key_str; + ssh_key key; + const char *passphrase = LIBSSH_PASSPHRASE; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_RSA_TESTKEY); + assert_true(key_str != NULL); + + key = ssh_key_new(); + assert_true(key != NULL); + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, NULL); + assert_true(rc == -1); + + free(key_str); + ssh_key_free(key); +} + +static void torture_pki_import_privkey_base64_NULL_str(void **state) { + int rc; + char *key_str; + ssh_key key = NULL; + const char *passphrase = LIBSSH_PASSPHRASE; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_RSA_TESTKEY); + assert_true(key_str != NULL); + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_true(rc == -1); + + free(key_str); + ssh_key_free(key); +} + +static void torture_pki_import_privkey_base64_DSA(void **state) { + int rc; + char *key_str; + ssh_key key; + const char *passphrase = LIBSSH_PASSPHRASE; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_DSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + free(key_str); + ssh_key_free(key); +} + +#ifdef HAVE_ECC +static void torture_pki_import_privkey_base64_ECDSA(void **state) { + int rc; + char *key_str; + ssh_key key; + const char *passphrase = LIBSSH_PASSPHRASE; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_ECDSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + free(key_str); + ssh_key_free(key); +} +#endif + +static void torture_pki_import_privkey_base64_passphrase(void **state) { + int rc; + char *key_str; + ssh_key key = NULL; + const char *passphrase = LIBSSH_PASSPHRASE; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_RSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + ssh_key_free(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(key_str, "wrong passphrase !!", NULL, + NULL, &key); + assert_true(rc == -1); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(key_str, NULL, NULL, NULL, &key); + assert_true(rc == -1); +#endif + + free(key_str); + + /* same for DSA */ + key_str = read_file(LIBSSH_DSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + ssh_key_free(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(key_str, "wrong passphrase !!", NULL, NULL, &key); + assert_true(rc == -1); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(key_str, NULL, NULL, NULL, &key); + assert_true(rc == -1); +#endif + + free(key_str); +} + +static void torture_pki_pki_publickey_from_privatekey_RSA(void **state) { + int rc; + char *key_str; + ssh_key key; + ssh_key pubkey; + const char *passphrase = NULL; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_RSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + + free(key_str); + ssh_key_free(key); + ssh_key_free(pubkey); +} + +static void torture_pki_pki_publickey_from_privatekey_DSA(void **state) { + int rc; + char *key_str; + ssh_key key; + ssh_key pubkey; + const char *passphrase = NULL; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_DSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + + free(key_str); + ssh_key_free(key); + ssh_key_free(pubkey); +} + +#ifdef HAVE_ECC +static void torture_pki_publickey_from_privatekey_ECDSA(void **state) { + int rc; + char *key_str; + ssh_key key; + ssh_key pubkey; + const char *passphrase = NULL; + + (void) state; /* unused */ + + key_str = read_file(LIBSSH_ECDSA_TESTKEY); + assert_true(key_str != NULL); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + + free(key_str); + ssh_key_free(key); + ssh_key_free(pubkey); +} +#endif + +static void torture_pki_publickey_dsa_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key, *key_buf, *p; + const char *q; + ssh_key key; + int rc; + + (void) state; /* unused */ + + key_buf = read_file(LIBSSH_DSA_TESTKEY ".pub"); + assert_true(key_buf != NULL); + + q = p = key_buf; + while (*p != ' ') p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_DSS); + + q = ++p; + while (*p != ' ') p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + ssh_key_free(key); +} + +#ifdef HAVE_ECC +static void torture_pki_publickey_ecdsa_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key, *key_buf, *p; + const char *q; + ssh_key key; + int rc; + + (void) state; /* unused */ + + key_buf = read_file(LIBSSH_ECDSA_TESTKEY ".pub"); + assert_true(key_buf != NULL); + + q = p = key_buf; + while (*p != ' ') p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_ECDSA); + + q = ++p; + while (*p != ' ') p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + ssh_key_free(key); +} +#endif + +static void torture_pki_publickey_rsa_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key, *key_buf, *p; + const char *q; + ssh_key key; + int rc; + + (void) state; /* unused */ + + key_buf = read_file(LIBSSH_RSA_TESTKEY ".pub"); + assert_true(key_buf != NULL); + + q = p = key_buf; + while (*p != ' ') p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + assert_true(((type == SSH_KEYTYPE_RSA) || + (type == SSH_KEYTYPE_RSA1))); + + q = ++p; + while (*p != ' ') p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + ssh_key_free(key); +} + +static void torture_generate_pubkey_from_privkey_rsa(void **state) { + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + ssh_key privkey; + ssh_key pubkey; + int rc; + + (void) state; /* unused */ + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", + pubkey_original, + sizeof(pubkey_original)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_RSA_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + assert_string_equal(pubkey_original, pubkey_generated); + + ssh_key_free(privkey); + ssh_key_free(pubkey); +} + +static void torture_generate_pubkey_from_privkey_dsa(void **state) { + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + ssh_key privkey; + ssh_key pubkey; + int rc; + + (void) state; /* unused */ + + rc = torture_read_one_line(LIBSSH_DSA_TESTKEY ".pub", + pubkey_original, + sizeof(pubkey_original)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_DSA_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_DSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + assert_string_equal(pubkey_original, pubkey_generated); + + ssh_key_free(privkey); + ssh_key_free(pubkey); +} + +#ifdef HAVE_ECC +static void torture_generate_pubkey_from_privkey_ecdsa(void **state) { + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + ssh_key privkey; + ssh_key pubkey; + int rc; + + (void) state; /* unused */ + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_original, + sizeof(pubkey_original)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ECDSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ECDSA_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + assert_string_equal(pubkey_original, pubkey_generated); + + ssh_key_free(privkey); + ssh_key_free(pubkey); +} +#endif + +static void torture_pki_duplicate_key_rsa(void **state) +{ + int rc; + char *b64_key; + char *b64_key_gen; + ssh_key pubkey; + ssh_key privkey; + ssh_key privkey_dup; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + ssh_key_free(pubkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + privkey_dup = ssh_key_dup(privkey); + assert_true(privkey_dup != NULL); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key_gen); + assert_true(rc == 0); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + ssh_key_free(pubkey); + ssh_key_free(privkey); + ssh_key_free(privkey_dup); + ssh_string_free_char(b64_key); + ssh_string_free_char(b64_key_gen); +} + +static void torture_pki_duplicate_key_dsa(void **state) +{ + int rc; + char *b64_key; + char *b64_key_gen; + ssh_key pubkey; + ssh_key privkey; + ssh_key privkey_dup; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + ssh_key_free(pubkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + privkey_dup = ssh_key_dup(privkey); + assert_true(privkey_dup != NULL); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key_gen); + assert_true(rc == 0); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + ssh_key_free(pubkey); + ssh_key_free(privkey); + ssh_key_free(privkey_dup); + ssh_string_free_char(b64_key); + ssh_string_free_char(b64_key_gen); +} + +#ifdef HAVE_ECC +static void torture_pki_duplicate_key_ecdsa(void **state) +{ + int rc; + char *b64_key; + char *b64_key_gen; + ssh_key pubkey; + ssh_key privkey; + ssh_key privkey_dup; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + ssh_key_free(pubkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + privkey_dup = ssh_key_dup(privkey); + assert_true(privkey_dup != NULL); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key_gen); + assert_true(rc == 0); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + ssh_key_free(pubkey); + ssh_key_free(privkey); + ssh_key_free(privkey_dup); + ssh_string_free_char(b64_key); + ssh_string_free_char(b64_key_gen); +} +#endif + +static void torture_pki_generate_key_rsa(void **state) +{ + int rc; + ssh_key key; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + ssh_free(session); +} + +static void torture_pki_generate_key_rsa1(void **state) +{ + int rc; + ssh_key key; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA1, 1024, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA1, 2048, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA1, 4096, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + ssh_free(session); +} + +static void torture_pki_generate_key_dsa(void **state) +{ + int rc; + ssh_key key; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 1024, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 2048, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 3072, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + ssh_free(session); +} + +#ifdef HAVE_ECC +static void torture_pki_generate_key_ecdsa(void **state) +{ + int rc; + ssh_key key; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 384, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 512, &key); + assert_true(rc == SSH_OK); + assert_true(key != NULL); + sign = pki_do_sign(key, HASH, 20); + assert_true(sign != NULL); + rc = pki_signature_verify(session,sign,key,HASH,20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + ssh_key_free(key); + key=NULL; + + ssh_free(session); +} +#endif + +int torture_run_tests(void) { + int rc; + const UnitTest tests[] = { + unit_test(torture_pki_keytype), + + unit_test(torture_pki_signature), + + /* ssh_pki_import_privkey_base64 */ + unit_test_setup_teardown(torture_pki_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + unit_test_setup_teardown(torture_pki_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + unit_test_setup_teardown(torture_pki_import_privkey_base64_RSA, + setup_rsa_key, + teardown), + unit_test_setup_teardown(torture_pki_import_privkey_base64_DSA, + setup_dsa_key, + teardown), +#ifdef HAVE_ECC + unit_test_setup_teardown(torture_pki_import_privkey_base64_ECDSA, + setup_ecdsa_key, + teardown), +#endif + unit_test_setup_teardown(torture_pki_import_privkey_base64_passphrase, + setup_both_keys_passphrase, + teardown), + /* ssh_pki_export_privkey_to_pubkey */ + unit_test_setup_teardown(torture_pki_pki_publickey_from_privatekey_RSA, + setup_rsa_key, + teardown), + unit_test_setup_teardown(torture_pki_pki_publickey_from_privatekey_DSA, + setup_dsa_key, + teardown), +#ifdef HAVE_ECC + unit_test_setup_teardown(torture_pki_publickey_from_privatekey_ECDSA, + setup_ecdsa_key, + teardown), +#endif + /* public key */ + unit_test_setup_teardown(torture_pki_publickey_dsa_base64, + setup_dsa_key, + teardown), + unit_test_setup_teardown(torture_pki_publickey_rsa_base64, + setup_rsa_key, + teardown), +#ifdef HAVE_ECC + unit_test_setup_teardown(torture_pki_publickey_ecdsa_base64, + setup_ecdsa_key, + teardown), +#endif + + unit_test_setup_teardown(torture_generate_pubkey_from_privkey_dsa, + setup_dsa_key, + teardown), + unit_test_setup_teardown(torture_generate_pubkey_from_privkey_rsa, + setup_rsa_key, + teardown), +#ifdef HAVE_ECC + unit_test_setup_teardown(torture_generate_pubkey_from_privkey_ecdsa, + setup_ecdsa_key, + teardown), +#endif + + unit_test_setup_teardown(torture_pki_duplicate_key_rsa, + setup_rsa_key, + teardown), + unit_test_setup_teardown(torture_pki_duplicate_key_dsa, + setup_dsa_key, + teardown), +#ifdef HAVE_ECC + unit_test_setup_teardown(torture_pki_duplicate_key_ecdsa, + setup_ecdsa_key, + teardown), +#endif + unit_test(torture_pki_generate_key_rsa), + unit_test(torture_pki_generate_key_rsa1), + unit_test(torture_pki_generate_key_dsa), +#ifdef HAVE_ECC + unit_test(torture_pki_generate_key_ecdsa), +#endif + }; + + (void)setup_both_keys; + + ssh_init(); + rc=run_tests(tests); + ssh_finalize(); + return rc; +} diff --git a/libssh/tests/unittests/torture_rand.c b/libssh/tests/unittests/torture_rand.c new file mode 100644 index 00000000..829989b0 --- /dev/null +++ b/libssh/tests/unittests/torture_rand.c @@ -0,0 +1,68 @@ +#define LIBSSH_STATIC +#include +#include +#include +#include +#include "torture.h" + +#ifdef HAVE_LIBGCRYPT +#define NUM_LOOPS 1000 +#else +/* openssl is much faster */ +#define NUM_LOOPS 20000 +#endif +#define NUM_THREADS 100 + +static void setup(void **state) { + (void) state; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + ssh_init(); +} + +static void teardown(void **state) { + (void) state; + + ssh_finalize(); +} + +static void *torture_rand_thread(void *threadid) { + char buffer[12]; + int i; + int r; + + (void) threadid; + + buffer[0] = buffer[1] = buffer[10] = buffer[11] = 'X'; + for(i = 0; i < NUM_LOOPS; ++i) { + r = ssh_get_random(&buffer[2], i % 8 + 1, 0); + assert_true(r == 1); + } + + pthread_exit(NULL); +} + +static void torture_rand_threading(void **state) { + pthread_t threads[NUM_THREADS]; + int i; + int err; + + (void) state; + + for(i = 0; i < NUM_THREADS; ++i) { + err = pthread_create(&threads[i], NULL, torture_rand_thread, NULL); + assert_int_equal(err, 0); + } + for(i = 0; i < NUM_THREADS; ++i) { + err=pthread_join(threads[i], NULL); + assert_int_equal(err, 0); + } +} + +int torture_run_tests(void) { + const UnitTest tests[] = { + unit_test_setup_teardown(torture_rand_threading, setup, teardown), + }; + + return run_tests(tests); +} diff --git a/libssh/tests/valgrind.supp b/libssh/tests/valgrind.supp new file mode 100644 index 00000000..4b553ead --- /dev/null +++ b/libssh/tests/valgrind.supp @@ -0,0 +1,106 @@ +### GLIBC +{ + glibc_regcomp + Memcheck:Leak + fun:*alloc + ... + fun:regcomp +} +{ + glibc_getaddrinfo_leak + Memcheck:Leak + fun:malloc + fun:make_request + fun:__check_pf + fun:getaddrinfo + fun:getai + fun:ssh_connect_host_nonblocking +} + +### OPENSSL +{ + openssl_crypto_value8 + Memcheck:Value8 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_value4 + Memcheck:Value4 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_cond + Memcheck:Cond + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_BN_cond + Memcheck:Cond + fun:BN_* +} + +{ + openssl_bn_value8 + Memcheck:Value8 + fun:bn_* +} + +{ + openssl_bn_value4 + Memcheck:Value4 + fun:bn_* +} + +{ + openssl_AES_cond + Memcheck:Cond + fun:AES_* +} + +{ + openssl_DES_cond + Memcheck:Cond + fun:DES_* +} + +{ + openssl_DES_value8 + Memcheck:Value8 + fun:DES_* +} + +{ + openssl_DES_value4 + Memcheck:Value4 + fun:DES_* +} + +{ + openssl_BF_cond + Memcheck:Cond + fun:BF_* +} + +{ + openssl_SHA1_cond + Memcheck:Cond + fun:SHA1_* +} + +{ + openssl_CRYPTO_leak + Memcheck:Leak + fun:*alloc + fun:CRYPTO_* +} +{ + openssl_CRYPTO_leak + Memcheck:Cond + fun:OPENSSL_cleanse +}