From 9b822d09b25fdbaf6c44ec31b710aab7bf905ece Mon Sep 17 00:00:00 2001 From: Synacktiv Date: Mon, 26 Jun 2017 11:47:01 +0200 Subject: [PATCH] Initial commit --- AUTHORS | 1 + COPYING | 674 +++++++++++++++++ ChangeLog | 18 + Doxyfile-client | 1513 +++++++++++++++++++++++++++++++++++++++ Doxyfile-server | 1513 +++++++++++++++++++++++++++++++++++++++ FAQ | 46 ++ INSTALL | 30 + Makefile | 14 + README | 147 ++++ TODO | 3 + client/Makefile | 25 + client/channel.c | 389 ++++++++++ client/commands.c | 191 +++++ client/controller.c | 334 +++++++++ client/main.c | 240 +++++++ client/memcheck.sh | 6 + client/netsock.c | 295 ++++++++ client/r2tcli.h | 163 +++++ client/socks5-proto.h | 50 ++ client/socks5.c | 310 ++++++++ client/tunnel.c | 471 ++++++++++++ common/Makefile | 12 + common/Makefile.mingw32 | 12 + common/compiler.h | 41 ++ common/debug.h | 67 ++ common/iobuf.c | 219 ++++++ common/iobuf.h | 91 +++ common/list.h | 109 +++ common/msgparser.c | 113 +++ common/msgparser.h | 28 + common/netaddr.c | 148 ++++ common/nethelper.c | 510 +++++++++++++ common/nethelper.h | 95 +++ common/print.c | 184 +++++ common/print.h | 38 + common/rdp2tcp.h | 99 +++ server/Makefile.mingw32 | 31 + server/Makefile.nmake | 23 + server/aio.c | 256 +++++++ server/channel.c | 209 ++++++ server/commands.c | 111 +++ server/errors.c | 62 ++ server/events.c | 160 +++++ server/main.c | 156 ++++ server/process.c | 226 ++++++ server/r2twin.h | 119 +++ server/tunnel.c | 574 +++++++++++++++ server/wtsapi32.h | 731 +++++++++++++++++++ tools/Makefile | 6 + tools/rdp2tcp.py | 192 +++++ tools/rdpupload | 238 ++++++ tools/test-client | 211 ++++++ 52 files changed, 11504 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Doxyfile-client create mode 100644 Doxyfile-server create mode 100644 FAQ create mode 100644 INSTALL create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 client/Makefile create mode 100644 client/channel.c create mode 100644 client/commands.c create mode 100644 client/controller.c create mode 100644 client/main.c create mode 100755 client/memcheck.sh create mode 100644 client/netsock.c create mode 100644 client/r2tcli.h create mode 100644 client/socks5-proto.h create mode 100644 client/socks5.c create mode 100644 client/tunnel.c create mode 100644 common/Makefile create mode 100644 common/Makefile.mingw32 create mode 100644 common/compiler.h create mode 100644 common/debug.h create mode 100644 common/iobuf.c create mode 100644 common/iobuf.h create mode 100644 common/list.h create mode 100644 common/msgparser.c create mode 100644 common/msgparser.h create mode 100644 common/netaddr.c create mode 100644 common/nethelper.c create mode 100644 common/nethelper.h create mode 100644 common/print.c create mode 100644 common/print.h create mode 100644 common/rdp2tcp.h create mode 100644 server/Makefile.mingw32 create mode 100644 server/Makefile.nmake create mode 100644 server/aio.c create mode 100644 server/channel.c create mode 100644 server/commands.c create mode 100644 server/errors.c create mode 100644 server/events.c create mode 100644 server/main.c create mode 100644 server/process.c create mode 100644 server/r2twin.h create mode 100644 server/tunnel.c create mode 100644 server/wtsapi32.h create mode 100644 tools/Makefile create mode 100755 tools/rdp2tcp.py create mode 100755 tools/rdpupload create mode 100755 tools/test-client diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..6b1d05d --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Nicolas Collignon diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..fbc916e --- /dev/null +++ b/ChangeLog @@ -0,0 +1,18 @@ +Version 0.1 public release (OSSIR) + + SOCKS5 minimal support + + doxygenification + + stricter controller protocol + + added X11 scripting tool (rdpupload) + + added rdp2tcp test script (test-client) + + dozen of bug fixes + +Version 0.0.3 + + tcp reverse connect + + process stdin/stdout forwarding + + IPv6 support + + dozen of bug fixes + +Version 0.0.2 almost reliable tunneling ... + + added controller listener to add tunnel on-demand + +Version 0.0.1 SSTIC rump nuked edition :) diff --git a/Doxyfile-client b/Doxyfile-client new file mode 100644 index 0000000..8834787 --- /dev/null +++ b/Doxyfile-client @@ -0,0 +1,1513 @@ +# Doxyfile 1.5.8 + +# 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 = rdp2tcp-client + +# 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 = 0.1 + +# 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 = docs/client + +# 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 = YES + +# 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, 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, and Ukrainian. + +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 = + +# 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 = + +# 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 = 8 + +# 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 + +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 = NO + +# 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 = NO + +# 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 = YES + +# 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 = YES + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = 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 = NO + +# 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 = YES + +# 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 = client \ + common + +# 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 = *.c \ + *.h + +# 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 = + +# 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 = + +# 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 = * + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = 5 + +# 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. + +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 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 FRAME, 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 (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# 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 + +#--------------------------------------------------------------------------- +# 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 = latex + +# 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 + +# 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 = a4wide + +# 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 = NO + +# 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 + +#--------------------------------------------------------------------------- +# 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 = NO + +# 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 = NO + +# 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 = + +# 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 = NO + +# 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 = YES + +# 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 = NO + +# 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 = YES + +# 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 = YES + +# 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 = + +# 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 = 2 + +# 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 = YES + +# 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 = NO + +# 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 + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/Doxyfile-server b/Doxyfile-server new file mode 100644 index 0000000..321f70c --- /dev/null +++ b/Doxyfile-server @@ -0,0 +1,1513 @@ +# Doxyfile 1.5.8 + +# 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 = rdp2tcp-server + +# 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 = 0.1 + +# 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 = docs/server + +# 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 = YES + +# 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, 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, and Ukrainian. + +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 = + +# 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 = + +# 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 = 8 + +# 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 + +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 = NO + +# 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 = NO + +# 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 = YES + +# 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 = YES + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = 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 = NO + +# 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 = YES + +# 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 = server \ + common + +# 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 = *.c \ + *.h + +# 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 = + +# 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 = + +# 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 = * + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = NO + +# 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 = 5 + +# 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. + +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 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 FRAME, 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 (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# 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 + +#--------------------------------------------------------------------------- +# 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 = latex + +# 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 + +# 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 = a4wide + +# 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 = NO + +# 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 + +#--------------------------------------------------------------------------- +# 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 = NO + +# 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 = NO + +# 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 = + +# 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 = NO + +# 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 = YES + +# 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 = NO + +# 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 = YES + +# 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 = YES + +# 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 = + +# 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 = 2 + +# 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 = NO + +# 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 + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/FAQ b/FAQ new file mode 100644 index 0000000..163e046 --- /dev/null +++ b/FAQ @@ -0,0 +1,46 @@ + +Q: I started rdesktop without the "-r addin:rdp2tcp:" argument, ... +A: It is not possible to start the client on-demand if the rdesktop + has not been started with the correct command line. + This feature will not be implemented since it would require extra + patches for rdesktop. + +Q: I want to use rdp2tcp without rdesktop because ... +A: It is not possible to use the client without rdesktop already running. + rdp2tcp is a rdesktop virtual channel helper, not a RDP implementation. + +Q: How to check if rdesktop is compiled with OOP patch ? +A: Run "rdesktop 2>&1 | grep addin". If you see no result, + rdesktop is not patched. + +Q: rdesktop says "Error executing child: No such file or directory" +A: The rdp2tcp client path provided with the "-r addin:rdp2tcp:" + argument is not correct. + +Q: I get "error: cannot attach RDP stream" when i start rdp2tcp server +A: rdp2tcp.exe was not started within a Terminal Server session. + +Q: I forwarded remote port 445 on my Linux host but smbclient fails + when trying to connect to local port XYZ. +A: This is a smbclient bug, not a rdp2tcp bug! smbclient selects its + protocol (netbios or SMB) based on the port number. It will try + to use netbios protocol whenever the destination port is not 445. + Therefore you have 3 choices: + - patch smbclient :) + - wrap the smbclient with a SOCKS5 client (tsocks, proxychains, ...) + - starts another tunneling tools (ex: socat) locally to forward + port 445 to port XYZ (must be done as root since 445 < 1024). + +Q: the input generated by rdpupload looks like garbage +A: your keyboard layout is different than the keyboard layout of the TS. + restart rdesktop with "-g " + +Q: rdpupload is slow ... +A: It is not expected to run fast ... + "debug" encoding generates smaller payload than "vb" encoding. + try to play with "-s" option (ex: -s 0.5). + compress/pack the file you are trying to upload (ex: upx) + +Q: Where can I find a precompiled rdp2tcp.exe server binary ? +A: Precompiled binaries are not provided ... Use mingw32. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..9927048 --- /dev/null +++ b/INSTALL @@ -0,0 +1,30 @@ + +-[ prerequisites ]----------------------------- + + - rdesktop dependencies + - a compiler for the rdesktop host + - a compiler for Windows + +-[ steps ]------------------------------------- + +1) get rdesktop source code + http://www.rdesktop.org/#download + +2) get rdesktop out-of-process virtual channel patch (oop.patch) + http://sf.net/tracker/index.php?func=detail&aid=1472969&group_id=24366&atid=381349 + +3) apply rdesktop patch + +4) compile & install patched rdesktop + +5) compile client with "make client" + +6) compile server + + case 1: cross-compilation from UNIX, use mingw32 + you may have to fix the compiler path in server/Makefile.mingw32 + run "make server-mingw32" + + case 2: compilation from Windows, use whatever you want .. + ex: "nmake /nologo /f Makefile.nmake" + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6778f02 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +all: client + +client: client/rdp2tcp +client/rdp2tcp: + make -C client + +server-mingw32: server/rdp2tcp.exe +server/rdp2tcp.exe: + make -C server -f Makefile.mingw32 + +clean: + make -C client clean + make -C server -f Makefile.mingw32 clean + make -C tools clean diff --git a/README b/README new file mode 100644 index 0000000..7dae300 --- /dev/null +++ b/README @@ -0,0 +1,147 @@ + + + rdp2tcp 0.1 + +rdp2tcp is a tunneling tool on top of remote desktop protocol (RDP). +It uses RDP virtual channel capabilities to multiplex several ports +forwarding over an already established rdesktop session. + +Available features: + - tcp port forwarding + - reverse tcp port forwarding + - process stdin/out forwarding + - SOCKS5 minimal support + +The code is splitted into 2 parts: + - the client running on the rdesktop client side + - the server running on the Terminal Server side + +Once both rdp2tcp client and server are running, tunnels management is +performed by the controller (on client side). The controller typically +listen on localhost (port 8477) waiting for new tunnel registrations. + + +-[ client (rdesktop side) ]-------------------- + +First of all, rdesktop must be compiled with OOP patch (see INSTALL). +The OOP patch comes with a additional rdesktop command line option. + + -r addin:NAME:HANDLER[:OPT1[:OPTN]] + + NAME: the name of the RDP virtual channel + HANDLER: the path of the executable which handle + the virtual channel. + OPT: argument passed to HANDLER executable + +The rdp2tcp client must be initialized when the rdesktop client starts. + + rdesktop -r addin:rdp2tcp:/path/to/rdp2tcp + +rdp2tcp client usage: + + rdp2tcp [[HOST] PORT] + + HOST: rdp2tcp controller hostname or IP address (default is 127.0.0.1). + PORT: rdp2tcp controller port (default is 8477). + +Several instances of rdp2tcp client can be run on a single rdesktop session: + + rdesktop -r addin:rdp2tcp-1:/path/to/rdp2tcp:8477 \ + -r addin:rdp2tcp-2:/path/to/rdp2tcp:8478 + +After rdesktop is started with rdp2tcp channel configured, port forwarding +can be configured by connecting to the controller and sending commands. +All commands are ASCII and ends with a CR "\n". + + * List rdp2tcp managed sockets: + "l\n" + + * Remove tunnel + "- LHOST LPORT\n" + + LHOST: tunnel local host + LPORT: tunnel local port + + * Start SOCKS5 proxy + "s LHOST LPORT\n" + + LHOST: proxy local host + LPORT: proxy local port + + * stdin/stdout forwarding tunnel (bind on rdesktop) + "x LHOST LPORT CMD\n" + + LHOST: local listener host + LPORT: local listener port + CMD: command line to execute on Terminal Server host + + * TCP forwarding tunnel (bind on rdesktop) + "t LHOST LPORT RHOST RPORT\n" + + LHOST: local listener host + LPORT: local listener port + RHOST: remote target host + RPORT: remote target port + + * TCP reverse-connect tunnel (bind on Terminal Server) + "r LHOST LPORT RHOST RPORT\n" + + LHOST: local target host + LPORT: local target port + RHOST: remote listener host + RPORT: remote listener port + +rdp2tcp.py (located in "tools" folder) can be used to manage tunnels with +simple command lines. +ex: "rdp2tcp.py add forward LHOST LPORT RHOST RPORT" + + +-[ server (Terminal Server side) ]------------- + +Before starting the rdp2tcp server, you must be logged on the Terminal Server +with one or more rdp2tcp clients attached to rdesktop. + +The rdp2tcp server won't magically appear on the Terminal Server. So the +rdp2tcp.exe executable must be first uploaded. + +rdp2tcp.exe doesn't require to be run with a privileged Windows account. + +Terminal Server policy may block file sharing through the RDP session. +Thus you may have to find a way to upload the .exe binary on the remote +system. The binary can be uploaded by scripting the TS input. + +Uploading binary data to the server can be automated by encoding data to +key stroke sequences that will be given to rdesktop as keyboard input. + +The rdpupload script (located in "tools" folder) generates a X11 script. +xte (http://hoopajoo.net/projects/xautomation.html) run the X11 script. + + 1) start rdesktop with rdp2tcp client + 2) tools/rdpupload -x -f vb server/rdp2tcp.exe | xte" + 3) focus on the rdesktop window within 5 seconds + 4) xte will feed rdesktop with keyboard input. focused window must + not change or you may get some trouble :) + 5) run the Visual Basic script uploaded by xte. + 6) run rdp2tcp server by using the executable generated by the + Visual Basic script. + + +-[ dev ]--------------------------------------- + + - edit Makefile / enable -DDEBUG + - use client/memcheck.sh to use valgrind as a RDP channel wrapper + - doxygen can be used to generate the project documentation + "doxygen Doxyfile-client" --> docs/client/html + "doxygen Doxyfile-server" --> docs/server/html + - export DEBUG (-1 to 2) environment variable to print debug statements + - export TRACE (00 to ff) environment variable to print function traces + + bit 0: I/O buffer management + 1: network socket + 2: RDP virtual channel + 3: events loop + 4: process + 5: rdp2tcp controller + 6: tunnel management + 7: SOCKS5 protocol + diff --git a/TODO b/TODO new file mode 100644 index 0000000..e7151ae --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +- tunnel id on 16 bits ? +- support fixed address family +FIXME: 64 bits ? diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..b0d0f43 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,25 @@ +BIN=rdp2tcp +CC=gcc +CFLAGS=-Wall -g -I../common +#CFLAGS=-Wall -g -I../common -DDEBUG +LDFLAGS= +OBJS=main.o netsock.o tunnel.o channel.o commands.o controller.o socks5.o \ + ../common/nethelper.o \ + ../common/netaddr.o \ + ../common/iobuf.o \ + ../common/print.o \ + ../common/msgparser.o + +all: clean_common $(BIN) + +clean_common: + $(MAKE) -C ../common clean + +$(BIN): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + rm -f $(OBJS) $(BIN) diff --git a/client/channel.c b/client/channel.c new file mode 100644 index 0000000..227f0fd --- /dev/null +++ b/client/channel.c @@ -0,0 +1,389 @@ +/** + * @file channel.c + * TS virtual channel management + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" +#include "msgparser.h" + +#include +#include +#include +#include +#include + +extern int debug_level; + +/** TS virtual channel singleton */ +typedef struct _vchannel { + time_t ts; /**< timestamp of last channel activity */ + int last_state; /**< virtual channel previous state */ + iobuf_t ibuf; /**< input buffer */ + iobuf_t obuf; /**< output buffer */ +} vchannel_t; + +static vchannel_t vc; + +/** + * initialize TS virtual channel + */ +int channel_init(void) +{ + trace_chan(""); + + vc.ts = 0; + vc.last_state = -1; + iobuf_init2(&vc.ibuf, &vc.obuf, "chan"); + + return 0; +} + +/** + * destroy TS virtual channel I/O buffers + */ +void channel_kill(void) +{ + trace_chan(""); + + iobuf_kill2(&vc.ibuf, &vc.obuf); +} + +/** + * check whether virtual channel is currently connected + * @return 0 if rcp2tcp.exe is not started on TS server + */ +int channel_is_connected(void) +{ + int connected; + time_t now; + + time(&now); + + connected = (vc.ts && (vc.ts + RDP2TCP_PING_DELAY + 4 > now)); + //trace_chan(connected ? "yes" : "no"); + + if (vc.last_state != connected) { + vc.last_state = connected; + info(0, "virtual channel %s", connected?"connected":"disconnected"); + } + + return connected; +} + +/** + * handle virtual channel read-event + * @return 0 on success + */ +int channel_read_event(void) +{ + ssize_t r; + char *ptr; + unsigned int msglen, avail; + + //trace_chan(""); + + ptr = (char *)&msglen; + avail = 4; + do { + r = read(RDP_FD_IN, ptr, avail); + if (r <= 0) + goto chan_read_err; + ptr += r; + avail -= r; + } while (avail > 0); + + ptr = iobuf_reserve(&vc.ibuf, msglen, &avail); + if (!ptr) + return error("failed to reserve channel memory"); + + do { + r = read(RDP_FD_IN, ptr, msglen); + //trace_chan("r=%u/%u", r, msglen); + if (r < 0) + goto chan_read_err; + +#ifdef DEBUG + if (debug_level > 2) { + fputs("[in] ", stderr); + fprint_hex(ptr, r, stderr); + fputc('\n', stderr); + } +#endif + + print_xfer("chan", 'r', (unsigned int)r); + + ptr += r; + msglen -= r; + } while (msglen > 0); + + iobuf_commit(&vc.ibuf, r); + commands_parse(&vc.ibuf); + time(&vc.ts); + + return 0; + +chan_read_err: + if (r < 0) + error("failed to read from channel pipe (%s)", strerror(errno)); + else if (r == 0) + error("channel closed"); + return -1; +} + +/** + * check whether data must be written to the TS virtual channel + * @return 0 if virtual channel output buffer is empty + */ +int channel_want_write(void) +{ + //trace_chan(iobuf_datalen(&vc.obuf) > 0 ? "yes" : "no"); + return iobuf_datalen(&vc.obuf) > 0; +} + +/** + * handle virtual channel write-event + * @return 0 on success + */ +void channel_write_event(void) +{ + int ret, fd; + unsigned int w; + + trace_chan(""); +#ifdef DEBUG + if (debug_level > 2) iobuf_dump(&vc.obuf); +#endif + + fd = RDP_FD_OUT; + ret = net_write(&fd, &vc.obuf, NULL, 0, &w); + if (ret >= 0) { + if (w > 0) + print_xfer("chan", 'w', (unsigned int) w); + + } else { + if (ret == NETERR_CLOSED) + error("rdesktop pipe closed"); + else + error("failed to write to rdesktop pipe (%s)", strerror(errno)); + bye(); + } +} + +/** + * reserve memory into virtual channel ouput buffer + * @param[in] size requested minimal buffer size + * @param[out] out_avail allocated size + * @return NULL on memory allocation error + */ +static void *write_reserve(unsigned int size, unsigned int *out_avail) +{ + char *ptr; + unsigned int avail; + + assert(size || out_avail); + //trace_chan(""); + + // need extra space for size header + ptr = iobuf_reserve(&vc.obuf, size+4, &avail); + if (!ptr) { + error("failed to allocate channel memory"); + return NULL; + } + + if (out_avail) + *out_avail = avail - 4; + + return ptr + 4; +} + +/** + * commit memory into virtual channel output buffer + * @param[in] size commited buffer size + */ +static void write_commit(unsigned int size) +{ + assert(size); + //trace_chan("size=%u", size); + + *(unsigned int *)(iobuf_allocptr(&vc.obuf)) = htonl(size); + iobuf_commit(&vc.obuf, size+4); +} + +/** + * send a ping message to rdp2tcp server + * @return 0 if message cannot be queued + */ +int channel_ping(void) +{ + r2tmsg_t msg; + + trace_chan(""); + msg.cmd = R2TCMD_PING; + msg.id = 0; + + return !iobuf_append(&vc.obuf, &msg, 2); +} + +/** + * function called whenever a ping message is sent by rdp2tcp server + */ +void channel_pong(void) +{ + //trace_chan(""); + + if (vc.last_state != 1) { + vc.last_state = 1; + info(0, "virtual channel connected"); + } + time(&vc.ts); +} + +#if 0 +// purify/valgrind/amd64 +static unsigned int purify_strlen(const char *x) +{ + unsigned int i; + i = 0; + while (*x++) ++i; + return i; +} +#endif + +/** + * send a rdp2tcp tunnel request command to the rdp2tcp server + * @param[in] tunaf preferred address family (TUNAF_IPV4/IPV6/ANY) + * @param[in] rhost remote tunnel hostname + * @param[in] rport remote tunnel port + * @param[in] reverse_connect 0 for tcp-connect or 1 for tcp-bind + * @return the tunnel ID or 0xff on error + */ +unsigned char channel_request_tunnel( + unsigned char tunaf, + const char *rhost, + unsigned short rport, + int reverse_connect) +{ + unsigned char tid; + unsigned int hlen; + r2tmsg_connreq_t *msg; + + assert((tunaf <= TUNAF_IPV6) && rhost && *rhost); + trace_chan("tunaf=0x%02x, rhost=%s, rport=%hu", tunaf, rhost, rport); + + tid = tunnel_generate_id(); + if (tid == 0xff) + return 0xff; + + hlen = 1 + strlen(rhost); + msg = write_reserve(5 + hlen, NULL); + if (!msg) + return 0xff; + + msg->cmd = (!reverse_connect ? R2TCMD_CONN : R2TCMD_BIND); + msg->id = tid; + msg->port = htons(rport); + msg->af = tunaf; + memcpy(msg->hostname, rhost, hlen); + + write_commit(5 + hlen); + + return tid; +} + +/** + * notify the server a tunnel has been closed + * @param[in] tid the tunnel ID + */ +void channel_close_tunnel(unsigned char tid) +{ + r2tmsg_t *msg; + + assert(tid != 0xff); + trace_chan("tid=0x%02x", tid); + + msg = write_reserve(2, NULL); + if (msg) { + msg->cmd = R2TCMD_CLOSE; + msg->id = tid; + write_commit(2); + } +} + +/** + * receive data from tcp tunnel and forward it to the RDP channel + * @param[in] ns tunnel socket + * @return 0 or 1 on success + */ +int channel_forward_recv(netsock_t *ns) +{ + int ret; + unsigned int r, off; + unsigned char *msg; + + assert(valid_netsock(ns) && ((ns->type == NETSOCK_TUNCLI) + || (ns->type == NETSOCK_RTUNCLI) || (ns->type == NETSOCK_S5CLI))); + trace_chan("id=0x%02x", ns->tid); + + off = iobuf_datalen(&vc.obuf); + ret = netsock_read(ns, &vc.obuf, 6, &r); + if (!ret) { + msg = iobuf_dataptr(&vc.obuf) + off; + *(unsigned int*)msg = htonl(r + 2); + msg[4] = R2TCMD_DATA; + msg[5] = ns->tid; + } + + if (ret < 0) + tunnel_close(ns, 1); + + return 0; +} + +/** + * forward data from I/O buffer to the RDP channel + * @param[in] ibuf input buffer + * @param[in] tid tunnel identifier + * @return 0 or 1 on success + */ +int channel_forward_iobuf(iobuf_t *ibuf, unsigned char tid) +{ + r2tmsg_t *msg; + unsigned int len; + + assert(valid_iobuf(ibuf) && (tid != 0xff)); + trace_chan("tid=0x%02x", tid); + + len = iobuf_datalen(ibuf); + assert(len > 0); + + msg = write_reserve(len+2, NULL); + if (!msg) + return -1; + + msg->cmd = R2TCMD_DATA; + msg->id = tid; + memcpy(((char *)msg)+2, iobuf_dataptr(ibuf), len); + write_commit(len + 2); + + iobuf_consume(ibuf, len); + + return 0; +} + diff --git a/client/commands.c b/client/commands.c new file mode 100644 index 0000000..9e55e26 --- /dev/null +++ b/client/commands.c @@ -0,0 +1,191 @@ +/** + * @file commands.c + * rdp2tcp commands handlers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" +#include "msgparser.h" + +#include + +extern const char *r2t_errors[R2TERR_MAX]; + +static int badproto(netsock_t *cli) +{ + assert(valid_netsock(cli)); + + tunnel_close(cli, 0); + return error("bad server protocol"); +} + +static netsock_t *check_tunnel_id(const r2tmsg_t *msg) +{ + netsock_t *ns; + + ns = tunnel_lookup(msg->id); + if (!ns) { + warn("unknown tunnel 0x%02x", msg->id); + channel_close_tunnel(msg->id); + } + + return ns; +} + +static int check_binding_answer( + int mode, + const r2tmsg_connans_t *msg, + unsigned int len) +{ + netsock_t *cli; + int af; + unsigned short port; + + assert(msg && (len >= 3)); + trace_chan("len=%u, tid=%u, err=%u", len, msg->id, msg->err); + + cli = check_tunnel_id((const r2tmsg_t*)msg); + if (!cli) + return 0; + + if ((mode == 2) || (msg->err == 0)) { + if (len < 8) + return badproto(cli); + + port = ntohs(msg->port); + + switch (msg->af) { + + case TUNAF_ANY: + if (mode) + return badproto(cli); + // process tunnel + if (len != 10) + return badproto(cli); + af = AF_UNSPEC; + break; + + case TUNAF_IPV4: + if (len != 10) + return badproto(cli); + af = AF_INET; + break; + + case TUNAF_IPV6: + if (len != 22) + return badproto(cli); + af = AF_INET6; + break; + default: + return badproto(cli); + } + + if (mode != 2) { + if (cli->type == NETSOCK_TUNCLI) + tunnel_connect_event(cli, af, &msg->addr[0], port); + else if (cli->type == NETSOCK_S5CLI) + socks5_connect_event(cli, af, &msg->addr[0], port); + else + tunnel_bind_event(cli, af, &msg->addr[0], port); + + } else { + + if (!tunnel_lookup(msg->err)) { + tunnel_revconnect_event(cli, msg->err, af, &msg->addr[0], port); + } else { + // server allocated an already used tunnel ID + channel_close_tunnel(msg->err); + } + } + + } else { + error("failed to %s tunnel 0x%02x (%s)", + (mode ? "bind" : "connect"), + cli->tid, + (msg->err >= R2TERR_MAX ? "???" : r2t_errors[msg->err])); + tunnel_close(cli, 0); + } + + return 0; +} + +static int cmd_conn(const r2tmsg_t *msg, unsigned int len) +{ + return check_binding_answer(0, (const r2tmsg_connans_t *)msg, len); +} + +static int cmd_bind(const r2tmsg_t *msg, unsigned int len) +{ + return check_binding_answer(1, (const r2tmsg_connans_t *)msg, len); +} + +static int cmd_close(const r2tmsg_t *msg, unsigned int len) +{ + netsock_t *tun; + + assert(msg && (len >= 2)); + trace_chan("len=%u", len); + + tun = check_tunnel_id(msg); + if (tun) + netsock_cancel(tun); + + return 0; +} + +static int cmd_data(const r2tmsg_t *msg, unsigned int len) +{ + netsock_t *clitun; + + assert(msg && (len >= 3)); + trace_chan("len=%u", len); + + clitun = check_tunnel_id(msg); + if (!clitun) + return 0; + + return tunnel_write(clitun, ((const char *)msg)+2, len-2); +} + +static int cmd_ping(const r2tmsg_t *msg, unsigned int len) +{ + assert(msg && (len >= 2)); + //trace_chan("len=%u", len); + + channel_pong(); + return 0; +} + +static int cmd_rconn(const r2tmsg_t *msg, unsigned int len) +{ + return check_binding_answer(2, (const r2tmsg_connans_t *)msg, len); +} + +/** + * handlers for each command + */ +const cmdhandler_t cmd_handlers[R2TCMD_MAX] = { + cmd_conn, // R2TCMD_CONN + cmd_close, // R2TCMD_CLOSE + cmd_data, // R2TCMD_DATA + cmd_ping, // R2TCMD_PING + cmd_bind, // R2TCMD_BIND + cmd_rconn // R2TCMD_RCONN +}; + diff --git a/client/controller.c b/client/controller.c new file mode 100644 index 0000000..5a82c57 --- /dev/null +++ b/client/controller.c @@ -0,0 +1,334 @@ +/** + * @file controller.c + * rdp2tcp controller + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" +#include "nethelper.h" + +#include +#include +#include +#include + +#ifndef PTR_DIFF +#define PTR_DIFF(e,s) \ + ((unsigned int)(((unsigned long)(e))-((unsigned long)(s)))) +#endif + +/** + * send an answer to the controller client + * @param[in] cli controller client socket + * @param[in] fmt format string + * @return -1 on error + */ +int controller_answer(netsock_t *cli, const char *fmt, ...) +{ + int ret; + va_list va; + char buf[256]; + + assert(valid_netsock(cli) && fmt && *fmt); + + va_start(va, fmt); + ret = vsnprintf(buf, sizeof(buf)-2, fmt, va); + va_end(va); + + if (ret > 0) { + buf[ret] = '\n'; + ret = netsock_write(cli, buf, ret+1); + } else { + ret = error("failed to prepare controller answer"); + } + + return ret; +} + + +/** + * start controller server + * @param[in] host local hostname + * @param[in] port local tcp port + * @return 0 on success + */ +int controller_start(const char *host, unsigned short port) +{ + netsock_t *ns; + + assert(host && *host && port); + trace_ctrl("host=%s, port=%hu", host, port); + + ns = netsock_bind(NULL, host, port, 0); + if (!ns) + return -1; + + ns->type = NETSOCK_CTRLSRV; + info(0, "controller listening on %s:%hu", host, port); + + return 0; +} + +/** + * handle controller network accept-event + * @param[in] ns controller socket + */ +void controller_accept_event(netsock_t *ns) +{ + netsock_t *cli; + char buf[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(ns) && (ns->type == NETSOCK_CTRLSRV)); + trace_ctrl(""); + + cli = netsock_accept(ns); + if (cli) { + cli->type = NETSOCK_CTRLCLI; + cli->tid = 0xff; + iobuf_init2(&cli->u.ctrlcli.ibuf, &cli->u.ctrlcli.obuf, "ctrl"); + info(1, "accepted controller %s", netaddr_print(&cli->addr, buf)); + } +} + +extern struct list_head all_sockets; + +static int dump_sockets(netsock_t *cli) +{ + int ret; + netsock_t *ns; + char host1[NETADDRSTR_MAXSIZE], host2[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(cli)); + + ret = 0; + + list_for_each(ns, &all_sockets) { + + if (ns == cli) + continue; + + if (ns->addr.ip4.sin_family) + netaddr_print(&ns->addr, host1); + + switch (ns->type) { + + case NETSOCK_CTRLSRV: + ret = controller_answer(cli, "ctrlsrv %s", host1); + break; + + case NETSOCK_TUNSRV: + if (!ns->u.tunsrv.rport) { + ret = controller_answer(cli, "tunsrv %s %s", host1, + ns->u.tunsrv.rhost); + } else { + ret = controller_answer(cli, "tunsrv %s %s:%hu", host1, + ns->u.tunsrv.rhost, ns->u.tunsrv.rport); + } + break; + + case NETSOCK_S5SRV: + ret = controller_answer(cli, "s5srv %s", host1); + break; + + case NETSOCK_CTRLCLI: + ret = controller_answer(cli, "ctrlcli %s", host1); + break; + + case NETSOCK_TUNCLI: + if (!ns->state != NETSTATE_CONNECTED) { + ret = controller_answer(cli, "tuncli %s tid=%hu", + host1, ns->tid); + break; + } + + + if (ns->u.tuncli.is_process) { + ret = controller_answer(cli, "tuncli %s 0x%x pid %u", + host1, ns->tid, + *(unsigned int *)&ns->u.tuncli.raddr.pid); + break; + } + + ret = controller_answer(cli, "tuncli %s 0x%x %s", + host1, ns->tid, + netaddr_print(&ns->u.tuncli.raddr, host2)); + break; + + case NETSOCK_S5CLI: + ret = controller_answer(cli, "s5cli %s 0x%x", + host1, ns->tid); + break; + + case NETSOCK_RTUNSRV: + ret = controller_answer(cli, "rtunsrv %s:%hu %s:%hu 0x%x", + ns->u.rtunsrv.lhost, ns->u.rtunsrv.lport, + &ns->u.rtunsrv.lhost[ns->u.rtunsrv.lhost_len], + ns->u.rtunsrv.rport, ns->tid); + break; + + //case NETSOCK_RTUNCLI: + default: + ret = controller_answer(cli, "rtuncli %s 0x%x %s", + host1, ns->tid, + netaddr_print(&ns->u.tuncli.raddr, host2)); + break; + } + + if (ret) + break; + } + + if (ret >= 0) + ret = controller_answer(cli, "\n"); + + return ret; +} + +static char *extract_port(char *data, unsigned short *out_port) +{ + char *ptr, *end; + long port; + + ptr = strchr(data, ' '); + if (!ptr) + return NULL; + *ptr = 0; + + end = NULL; + port = strtol(ptr+1, &end, 10); + if (!end || (port <= 0) || (port > 0xffff)) + return NULL; + + if (*end && (*end != ' ')) + return NULL; + + *out_port = (unsigned short) port; + + return end; +} + +/** + * handle controller network read-event + * @param[in] cli controller socket + */ +int controller_read_event(netsock_t *cli) +{ + char cmd, *data, *end, *lhost, *rhost; + int ret; + unsigned int avail, parsed; + unsigned short lport, rport; + const char valid_commands[] = "ltrxs-"; + char host[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(cli) && (cli->type == NETSOCK_CTRLCLI)); + trace_ctrl(""); + + ret = netsock_read(cli, &cli->u.ctrlcli.ibuf, 0, NULL); + if (ret) + return ret; + + data = iobuf_dataptr(&cli->u.ctrlcli.ibuf); + avail = iobuf_datalen(&cli->u.ctrlcli.ibuf); + assert(avail); + parsed = 0; + + // for each line + do { + + end = memchr(data, '\n', avail-parsed); + if (!end) { + ret = 1; + break; + } + *end = 0; + if (!*data) goto badproto; + + parsed += PTR_DIFF(end, data) + 1; + + if (end[-1] == '\r') + end[-1] = 0; + + cmd = *data; + if (!strchr(valid_commands, cmd)) + goto badproto; + + debug(0, "cmd=\"%s\"", data); + + if (cmd == 'l') { // list sockets + ret = dump_sockets(cli); + + } else { + // commands with argc >= 2 + + if (*++data != ' ') goto badproto; + if (!*++data) goto badproto; + + lhost = data; + data = extract_port(data, &lport); + if (!data) goto badproto; + + if (cmd == '-') { // remove tunnel + ret = tunnel_del(cli, lhost, lport); + + } else if (cmd == 's') { // add socks5 server + ret = socks5_bind(cli, lhost, lport); + + } else { + // commands with argc >= 3 + + if (*data++ != ' ') goto badproto; + if (!*data) goto badproto; + + if (cmd == 'x') { // exec & forward stdin/stdout + ret = tunnel_add(cli, lhost, lport, AF_UNSPEC, data, 0); + + } else { + // commands with argc == 4 + + rhost = data; + if (!extract_port(data, &rport)) + return -1; + + if (cmd == 't') { // add TCP tunnel + ret = tunnel_add(cli, lhost, lport, + AF_UNSPEC, rhost, rport); + + } else { // cmd == 'r' reverse TCP connect + ret = tunnel_add_reverse(cli, lhost, lport, + AF_UNSPEC, rhost, rport); + } + } + } + } + + data = end + 1; + + } while (!ret && (parsed < avail)); + + if (parsed > 0) + iobuf_consume(&cli->u.ctrlcli.ibuf, parsed); + + return ret; + +badproto: + info(0, "closing controller %s (bad protocol)", + netaddr_print(&cli->addr,host)); + return -1; +} + diff --git a/client/main.c b/client/main.c new file mode 100644 index 0000000..937d197 --- /dev/null +++ b/client/main.c @@ -0,0 +1,240 @@ +/** + * @file main.c + * main loop + * @mainpage rdp2tcp + * @section sec_ts TS virtual channel + * @li channel.c + * @section sec_tun rdp2tcp tunnels + * @li tunnel.c + * @li commands.c + * @li socks5.c + * @li controller.c + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * default rdp2tcp controller TCP port + */ +#define R2T_PORT 8477 + +extern struct list_head all_sockets; +static int killme = 0; + +void bye(void) +{ + netsock_t *ns, *bak; + + list_for_each_safe(ns, bak, &all_sockets) + netsock_close(ns); + + channel_kill(); + exit(0); +} + +static void handle_cleanup(int sig) +{ + if (sig == SIGPIPE) + info(0, "rdesktop pipe is broken"); + bye(); +} + +static void setup(int argc, char **argv) +{ + const char *host; + int port; + + print_init(); + + if (argc > 3) + exit(0); + + if (argc == 3) { + port = atoi(argv[2]); + if ((port <= 0) || (port > 0xffff)) { + error("invalid controller port %i", port); + exit(0); + } + host = argv[1]; + } else if (argc == 2) { + port = R2T_PORT; + host = argv[1]; + } else { + port = R2T_PORT; + host = "127.0.0.1"; + } + + if (controller_start(host, port)) + exit(0); + + channel_init(); +} + +int main(int argc, char **argv) +{ + int ret, fd, max_fd, last_state, state; + netsock_t *ns, *bak; + fd_set rfd, wfd, *pwfd; + struct timeval tv, *ptv; + + setup(argc, argv); + + signal(SIGUSR1, handle_cleanup); + signal(SIGINT, handle_cleanup); + signal(SIGPIPE, handle_cleanup); + + last_state = 0; + + while (!killme) { + + FD_ZERO(&rfd); + FD_SET(RDP_FD_IN, &rfd); + max_fd = RDP_FD_IN; + + FD_ZERO(&wfd); + pwfd = NULL; + ptv = NULL; + state = channel_is_connected(); + if (state != last_state) { + + if (!state) // connected --> disconnected + tunnels_kill_clients(); + else // disconnected --> connected + tunnels_restart(); + + last_state = state; + } + + if (state) { + // channel is connected + if (channel_want_write()) { + FD_SET(RDP_FD_OUT, &wfd); + max_fd = RDP_FD_OUT; + pwfd = &wfd; + } + tv.tv_sec = 1; + tv.tv_usec = 0; + ptv = &tv; + } + + list_for_each(ns, &all_sockets) { + + assert(valid_netsock(ns)); + + if (ns->state != NETSTATE_CANCELLED) { + fd = ns->fd; + + if (netsock_want_read(ns)) { + FD_SET(fd, &rfd); + if (fd > max_fd) max_fd = fd; + } + + if (netsock_want_write(ns)) { + FD_SET(fd, &wfd); + pwfd = &wfd; + if (fd > max_fd) max_fd = fd; + } + } + } + + //debug(1, "channel connected: %i", channel_is_connected()); + + ret = select(max_fd+1, &rfd, pwfd, NULL, ptv); + if (ret == -1) { + error("select error (%s)", strerror(errno)); + break; + } + + if (ret == 0) { + // channel ping timeout + //info(0, "channel timeout"); + continue; + } + + if (FD_ISSET(RDP_FD_OUT, &wfd)) + channel_write_event(); + + if (FD_ISSET(RDP_FD_IN, &rfd)) { + if (channel_read_event() < 0) + break; + } + + list_for_each_safe(ns, bak, &all_sockets) { + + assert(valid_netsock(ns)); + + if (ns->state == NETSTATE_CANCELLED) { + debug(0, "closing cancelled connection"); + netsock_close(ns); + continue; + } + + if (ns->type == NETSOCK_RTUNSRV) + continue; + + fd = ns->fd; + if (netsock_is_server(ns)) { + // server socket + if (FD_ISSET(fd, &rfd)) { + if (ns->type == NETSOCK_TUNSRV) + tunnel_accept_event(ns); + else if (ns->type == NETSOCK_S5SRV) + socks5_accept_event(ns); + else + controller_accept_event(ns); + } + + } else { + // client socket + ret = 0; + + if (FD_ISSET(fd, &wfd)) + ret = tunnel_write_event(ns); + + if ((ret >= 0) && FD_ISSET(fd, &rfd)) { + + if (ns->type == NETSOCK_S5CLI) + ret = socks5_read_event(ns); + else if (ns->type == NETSOCK_CTRLCLI) + ret = controller_read_event(ns); + else + ret = channel_forward_recv(ns); + } + + if (ret < 0) + netsock_close(ns); + } + } + } + + bye(); + return 0; +} + diff --git a/client/memcheck.sh b/client/memcheck.sh new file mode 100755 index 0000000..64a505b --- /dev/null +++ b/client/memcheck.sh @@ -0,0 +1,6 @@ +#!/bin/sh +valgrind --tool=memcheck \ + -v --log-file=/tmp/rdp2tcp.log \ + --leak-check=full --show-reachable=yes \ + --leak-resolution=med --track-origins=yes \ + ./rdp2tcp diff --git a/client/netsock.c b/client/netsock.c new file mode 100644 index 0000000..4a8df3a --- /dev/null +++ b/client/netsock.c @@ -0,0 +1,295 @@ +/** + * @file netsock.c + * network sockets management + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" +#include "nethelper.h" + +#include +#include +#include +#include +#include + +/** + * all network sockets double-linked list + */ +LIST_HEAD_INIT(all_sockets); + +/** + * check if main loop must wait for network-write event + * @param[in] ns netsock socket + */ +int netsock_want_write(netsock_t *ns) +{ + assert(valid_netsock(ns)); + + switch (ns->type) { + + case NETSOCK_CTRLCLI: + return iobuf_datalen(&ns->u.ctrlcli.obuf) > 0; + + case NETSOCK_TUNCLI: + case NETSOCK_RTUNCLI: + return (ns->state != NETSTATE_CONNECTED) + || (iobuf_datalen(&ns->u.tuncli.obuf) > 0); + + case NETSOCK_S5CLI: + return iobuf_datalen(&ns->u.sockscli.obuf) > 0; + //return (ns->u.sockscli.state < S5STATE_CONNECTED) + // || (iobuf_datalen(&ns->u.sockscli.obuf) > 0); + } + + return 0; +} + +/** + * cancel a network socket / delayed netsock_close + * @param[in] ns netsock socket + */ +void netsock_cancel(netsock_t *ns) +{ + assert(valid_netsock(ns) && (ns->state != NETSTATE_CANCELLED)); + ns->state = NETSTATE_CANCELLED; +} + +/** + * close a network socket + * @param[in] ns netsock socket + */ +void netsock_close(netsock_t *ns) +{ + assert(ns && (((ns->type == NETSOCK_UNDEF) || valid_netsock(ns)))); + + list_del(&ns->list); + + if (ns->type != NETSOCK_RTUNSRV) + close(ns->fd); + + switch (ns->type) { + + case NETSOCK_CTRLCLI: + iobuf_kill2(&ns->u.ctrlcli.ibuf, &ns->u.ctrlcli.obuf); + break; + + case NETSOCK_TUNCLI: + iobuf_kill(&ns->u.tuncli.obuf); + break; + + case NETSOCK_S5CLI: + iobuf_kill2(&ns->u.sockscli.ibuf, &ns->u.sockscli.obuf); + break; + } + + free(ns); +} + +/** + * allocate a netsock_t structure + * @param[in] cli caller socket + * @param[in] fd socket + * @param[in] addr associated socket address + * @param[in] extra_size extra padding allocated for structure + * @return allocated structure + */ +netsock_t *netsock_alloc( + netsock_t *cli, + int fd, + netaddr_t *addr, + unsigned int extra_size) +{ + netsock_t *ns; + + ns = calloc(1, sizeof(*ns)+extra_size); + if (ns) { + ns->type = NETSOCK_UNDEF; + ns->type = NETSTATE_INIT; + ns->tid = 0xff; + ns->fd = fd; + if (addr) + memcpy(&ns->addr, addr, sizeof(*addr)); + list_add_tail(&ns->list, &all_sockets); + } else { + error("failed to allocated socket structure"); + if (cli) + controller_answer(cli, "failed to allocated socket structure"); + close(fd); + } + + return ns; +} + +/** + * start a server socket + * @param[in] cli caller socket + * @param[in] host listening address + * @param[in] port listening port + * @param[in] extra_size extra padding allocated for structure + * @return allocated structure + */ +netsock_t *netsock_bind( + netsock_t *cli, + const char *host, + unsigned short port, + unsigned int extra_size) +{ + netsock_t *srv; + int ret, err, fd; + netaddr_t addr; + + assert((!cli || valid_netsock(cli)) && host && *host && port); + + ret = net_server(AF_UNSPEC, host, port, &fd, &addr, &err); + if (ret < 0) { + error("%s", net_error(ret, err)); + if (cli) + controller_answer(cli, "error: %s", net_error(ret, err)); + return NULL; + } + + srv = netsock_alloc(NULL, fd, &addr, extra_size); + if (srv) + srv->state = NETSTATE_CONNECTED; + + return srv; +} + +/** + * accept client socket + * @param[in] srv server socket + * @return allocated structure + */ +netsock_t *netsock_accept(netsock_t *srv) +{ + netsock_t *cli; + int ret, fd; + netaddr_t addr; + + assert(valid_netsock(srv)); + + ret = net_accept(&srv->fd, &fd, &addr); + if (ret) { + error("failed to accept connection (%s)", strerror(ret)); + return NULL; + } + + cli = netsock_alloc(NULL, fd, &addr, 0); + if (cli) + cli->state = NETSTATE_CONNECTED; + + return cli; +} + +/** + * start a client socket + * @param[in] host client address + * @param[in] port client port + * @return allocated structure + */ +netsock_t *netsock_connect(const char *host, unsigned short port) +{ + netsock_t *cli; + int ret, err, fd; + netaddr_t addr; + + assert(host && *host && port); + + ret = net_client(AF_UNSPEC, host, port, &fd, &addr, &err); + if (ret < 0) { + error("failed to connect to %s:%hu (%s)", + host, port, net_error(ret, err)); + return NULL; + } + + cli = netsock_alloc(NULL, fd, &addr, 0); + if (cli) + cli->state = (ret ? NETSTATE_CONNECTING : NETSTATE_CONNECTED); + + return cli; +} + +/** + * async read from socket + * @param[in] ns network socket + * @param[in] ibuf input buffer + * @param[in] prefix_size head padding used for buffer allocation + * @param[out] out_size total bytes transferred + * @return -1 on error, 0 on success, 1 if pending + */ +int netsock_read( + netsock_t *ns, + iobuf_t *ibuf, + unsigned int prefix_size, + unsigned int *out_size) +{ + int ret; + unsigned int r; + char host[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(ns) && ibuf); + + ret = net_read(&ns->fd, ibuf, prefix_size, &ns->min_io_size, &r); + if (ret < 0) { + netaddr_print(&ns->addr, host); + if (ret == NETERR_CLOSED) + info(0, "connection %s closed", host); + else + error("failed to recv data from %s (%s)", host, strerror(errno)); + + } else if (r > 0) { + if (out_size) + *out_size = r; + print_xfer("tcp", 'r', r); + } + + return ret; +} + +/** + * async write to socket + * @param[in] ns network socket + * @param[in] buf optional data to send + * @param[in] len buffer size + * @return -1 on error, 0 on success, 1 if pending + */ +int netsock_write(netsock_t *ns, const void *buf, unsigned int len) +{ + int ret; + unsigned int w; + char host[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(ns) && (buf || !len)); + + ret = net_write(&ns->fd, &ns->u.tuncli.obuf, buf, len, &w); + if (ret < 0) { + netaddr_print(&ns->addr, host); + if (ret == NETERR_CLOSED) + info(0, "connection %s closed", host); + else + error("failed to send data to %s (%s)", host, strerror(errno)); + + } else if (w > 0) { + print_xfer("tcp", 'w', w); + } + + return ret; +} + diff --git a/client/r2tcli.h b/client/r2tcli.h new file mode 100644 index 0000000..c738625 --- /dev/null +++ b/client/r2tcli.h @@ -0,0 +1,163 @@ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __R2TCLIENT_H__ +#define __R2TCLIENT_H__ + +#include "debug.h" +#include "print.h" +#include "list.h" +#include "iobuf.h" +#include "rdp2tcp.h" +#include "nethelper.h" + +#include +#include + +// netsock.c +#define NETSOCK_CTRLSRV 0 +#define NETSOCK_TUNSRV 1 +#define NETSOCK_S5SRV 2 +#define NETSOCK_CTRLCLI 3 +#define NETSOCK_TUNCLI 4 +#define NETSOCK_S5CLI 5 +#define NETSOCK_RTUNSRV 6 +#define NETSOCK_RTUNCLI 7 +#define NETSOCK_UNDEF 0xff + +#define NETSTATE_INIT 0 +#define NETSTATE_CANCELLED 1 +#define NETSTATE_CONNECTING 2 +#define NETSTATE_CONNECTED 3 +#define NETSTATE_AUTHENTICATING 4 +#define NETSTATE_AUTHENTICATED 5 + +/** network socket (tunnel, client or server) */ +typedef struct _netsock { + struct list_head list; /**< double-linked list */ + int fd; /**< socket descriptor */ + unsigned char type; /**< socket type */ + unsigned char state; /**< tunnel state */ + unsigned char tid; /**< tunnel identifier */ + unsigned int min_io_size; /**< minimal input buffer size */ + netaddr_t addr; /**< socket address */ + union { + struct { + unsigned char raf; /**< remote address family */ + unsigned short rport; /**< remote port */ + char rhost[0]; /**< remote host */ + } tunsrv; + struct { + iobuf_t obuf; /**< output buffer */ + netaddr_t raddr; /**< remote address */ + unsigned char is_process; /**< 1 if tunnel is a process */ + } tuncli; + struct { + iobuf_t obuf; /**< output buffer */ + iobuf_t ibuf; /**< input buffer */ + } ctrlcli; + struct { + iobuf_t obuf; /**< output buffer */ + iobuf_t ibuf; /**< input buffer */ + } sockscli; + struct { + unsigned short lport; /**< local port */ + unsigned short rport; /**< remote port */ + unsigned short lhost_len; /**< size of local host string */ + unsigned char bound; /**< 1 if remote server is listening */ + char lhost[0]; /**< local host followed by remote host */ + } rtunsrv; + } u; +} netsock_t; + +#define valid_netsock(ns) \ + ((ns) && (ns)->list.next && (ns)->list.prev \ + && (((ns)->fd != -1) || ((ns)->type == NETSOCK_RTUNSRV)) \ + && ((ns)->type <= NETSOCK_RTUNCLI) \ + && (((ns)->addr.ip4.sin_family == AF_INET) \ + || ((ns)->addr.ip4.sin_family == AF_INET6) \ + || ((ns)->type == NETSOCK_RTUNSRV))) + +#define netsock_is_server(ns) ((ns)->type <= NETSOCK_S5SRV) + +/** + * check if main loop must wait for network-read event + * @param[in] ns netsock socket + */ +#define netsock_want_read(ns) ((ns)->state >= NETSTATE_CONNECTED) + +netsock_t *netsock_alloc(netsock_t *, int, netaddr_t *, unsigned int); +netsock_t *netsock_bind(netsock_t *, const char*,unsigned short,unsigned int); +netsock_t *netsock_accept(netsock_t *); +netsock_t *netsock_connect(const char *, unsigned short); +int netsock_read(netsock_t *, iobuf_t *, unsigned int, unsigned int *); +int netsock_write(netsock_t *, const void *, unsigned int); +int netsock_want_write(netsock_t *); +void netsock_cancel(netsock_t *); +void netsock_close(netsock_t *); + +// channel.c +#define RDP_FD_IN 0 +#define RDP_FD_OUT 1 + +int channel_init(void); +void channel_kill(void); +int channel_is_connected(void); +int channel_read_event(void); +int channel_want_write(void); +void channel_write_event(void); +int channel_ping(void); +void channel_pong(void); +unsigned char channel_request_tunnel(unsigned char, const char *, unsigned short, int); +int channel_forward_recv(netsock_t *); +int channel_forward_iobuf(iobuf_t *, unsigned char); +void channel_close_tunnel(unsigned char); + +// controller.c +int controller_start(const char *, unsigned short); +void controller_accept_event(netsock_t *); +int controller_read_event(netsock_t *); +int controller_answer(netsock_t *, const char *, ...); + +// tunnel.c +int tunnel_add(netsock_t *, char *, unsigned short, int, char *, unsigned short); +int tunnel_add_reverse(netsock_t *, char *, unsigned short, int, char *, unsigned short); +int tunnel_del(netsock_t *, char *, unsigned short); +void tunnel_accept_event(netsock_t *); +void tunnel_connect_event(netsock_t *, int, const void *, unsigned short); +void tunnel_revconnect_event(netsock_t *, unsigned char, int, + const void *, unsigned short); +void tunnel_bind_event(netsock_t *, int, const void *, unsigned short); +int tunnel_write_event(netsock_t *); +int tunnel_write(netsock_t *, const void *, unsigned int); +void tunnel_close(netsock_t *, int); +unsigned char tunnel_generate_id(void); +netsock_t *tunnel_lookup(unsigned char); +void tunnels_kill_clients(void); +void tunnels_restart(void); + +// socks5.c +int socks5_bind(netsock_t *, const char *, unsigned short); +void socks5_connect_event(netsock_t *, int, const void *, unsigned short); +void socks5_accept_event(netsock_t *); +int socks5_read_event(netsock_t *); + +// main.c +void bye(void); + +#endif diff --git a/client/socks5-proto.h b/client/socks5-proto.h new file mode 100644 index 0000000..c36f36b --- /dev/null +++ b/client/socks5-proto.h @@ -0,0 +1,50 @@ +/** + * @file socks5-proto.h + * SOCKS5 protocol specifications + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __SOCKS5_PROTO_H__ +#define __SOCKS5_PROTO_H__ + +// http://tools.ietf.org/html/rfc1928 + +#define SOCKS5_VERSION 0x05 + +#define SOCKS5_ATYPE_IPV4 0x01 +#define SOCKS5_ATYPE_FQDN 0x03 +#define SOCKS5_ATYPE_IPV6 0x04 + +#define SOCKS5_NOAUTH 0x00 + +#define SOCKS5_CONNECT 0x01 +#define SOCKS5_BIND 0x02 +#define SOCKS5_UDPASSOC 0x03 + +#define SOCKS5_SUCCESS 0x00 +#define SOCKS5_ERROR 0x01 +#define SOCKS5_FORBIDDEN 0x02 +#define SOCKS5_NETUNREACH 0x03 +#define SOCKS5_PORTUNREACH 0x04 +#define SOCKS5_CONNREFUSED 0x05 +#define SOCKS5_TTLEXPIRED 0x06 +#define SOCKS5_UNKCOMMAND 0x07 +#define SOCKS5_UNKADDRTYPE 0x08 + +#endif diff --git a/client/socks5.c b/client/socks5.c new file mode 100644 index 0000000..70c6cc1 --- /dev/null +++ b/client/socks5.c @@ -0,0 +1,310 @@ +/** + * @file socks5.c + * SOCKS5 server implementation + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" +#include "socks5-proto.h" +#include "nethelper.h" + +#include +#include +#include +#include +#include + +#ifdef DEBUG +extern int debug_level; +#endif + +static int socks_error(netsock_t *cli, unsigned char ret) +{ + unsigned char out[2]; + + out[0] = SOCKS5_VERSION; + out[1] = ret; + + netsock_write(cli, out, 2); + return -1; +} + +/** + * handle SOCKS5 server network accept-event + * @param[in] cli client socket + * @param[in] af client address family + * @param[in] addr client address + * @param[in] port client TCP port + */ +void socks5_connect_event( + netsock_t *cli, + int af, + const void *addr, + unsigned short port) +{ + unsigned int addr_len; + unsigned char ans[4+16+2]; + + assert(valid_netsock(cli) && (cli->type == NETSOCK_S5CLI) + && addr && ((af == AF_INET) || (af == AF_INET6))); + trace_socks(""); + + if (cli->state != NETSTATE_CONNECTING) { + // server went wrong ... + error("invalid SOCKS5 protocol state"); + tunnel_close(cli, 1); + return; + } + + ans[0] = SOCKS5_VERSION; + ans[1] = SOCKS5_SUCCESS; + ans[2] = 0; + if (af == AF_INET) { + ans[3] = SOCKS5_ATYPE_IPV4; + addr_len = 4; + } else { + ans[3] = SOCKS5_ATYPE_IPV6; + addr_len = 16; + } + + memcpy(&ans[4], addr, addr_len); + ans[4+addr_len] = (unsigned char) (port >> 8); + ans[5+addr_len] = (unsigned char) (port & 0xff); + + cli->state = NETSTATE_CONNECTED; + + if (netsock_write(cli, ans, addr_len+6) >= 0) { + + if (iobuf_datalen(&cli->u.sockscli.ibuf) > 0) { + if (channel_forward_iobuf(&cli->u.sockscli.ibuf, + cli->tid) < 0) { + tunnel_close(cli, 1); + } + } + } else { + // cancel tunnel + tunnel_close(cli, 1); + } +} + +static int socks5_setup(netsock_t *cli) +{ + unsigned int len, methods_count, port_off; + unsigned short port; + unsigned char tunaf, tid, *buf, out[2]; + iobuf_t *ibuf; + char *host, ip[INET6_ADDRSTRLEN+1]; + + ibuf = &cli->u.sockscli.ibuf; + + if (netsock_read(cli, ibuf, 0, NULL) < 0) + return -1; + +#ifdef DEBUG + if (debug_level > 2) iobuf_dump(ibuf); +#endif + + len = iobuf_datalen(ibuf); + if (!len) // need more data + return 1; + + buf = iobuf_dataptr(ibuf); + if (buf[0] != SOCKS5_VERSION) + return error("SOCKS5 protocol version not supported (0x%02x)", buf[0]); + + if (cli->state == NETSTATE_AUTHENTICATING) { + + if (len < 2) + return 1; + + methods_count = (unsigned int) buf[1]; + if (!methods_count) + return error("no SOCKS authentication method proposed"); + + if (methods_count + 2 > len) // need more data + return 1; + + if (!memchr(buf+2, SOCKS5_NOAUTH, methods_count)) { + // no valid auth + return error("SOCKS5 authentication not supported"); + } + + iobuf_consume(ibuf, methods_count+2); + out[0] = 5; + out[1] = SOCKS5_NOAUTH; + netsock_write(cli, &out, 2); + cli->state = NETSTATE_AUTHENTICATED; + debug(0, "SOCKS5 client authenticated"); + return 0; + } + + if (cli->state != NETSTATE_AUTHENTICATED) + return error("invalid SOCKS5 protocol state 0x%02x", cli->state); + + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + + if (len < 8) // need more data + return 1; + + if (buf[2] != 0) + return error("invalid SOCKS5 reserved field (0x%02x)", buf[2]); + + if (buf[1] != SOCKS5_CONNECT) { + warn("unsupported SOCKS5 command 0x%02x", buf[1]); + return socks_error(cli, SOCKS5_UNKCOMMAND); + } + + host = ip; + ip[0] = 0; + + switch (buf[3]) { + + case SOCKS5_ATYPE_IPV4: + if (len < 10) + return 1; + tunaf = TUNAF_IPV4; + if (!inet_ntop(AF_INET, buf+4, ip, sizeof(ip)-1)) + return error("failed to convert SOCKS5 IPv4 address"); + port_off = 8; + break; + + case SOCKS5_ATYPE_FQDN: + if (len < 7+(unsigned int)buf[4]) + return 1; + + tunaf = TUNAF_ANY; + len = (unsigned int) buf[4]; + host = malloc(len+1); + if (!host) + return error("failed to allocate SOCKS5 hostname"); + memcpy(host, buf+5, len); + host[len] = 0; + if (host && !*host) { + if (host) free(host); + return error("empty SOCKS5 domain"); + } + port_off = 5 + (unsigned int) buf[4]; + break; + + case SOCKS5_ATYPE_IPV6: + if (len < 22) + return 1; + tunaf = TUNAF_IPV6; + if (!inet_ntop(AF_INET6, buf+4, ip, sizeof(ip)-1)) + return error("failed to convert SOCKS5 IPv6 address"); + port_off = 20; + break; + + default: + return socks_error(cli, SOCKS5_UNKADDRTYPE); + } + + port = ntohs((((unsigned short)buf[port_off+1]) << 8) | buf[port_off]); + if (!port) { + if (host && (host != ip)) + free(host); + return error("invalid SOCKS5 port"); + } + iobuf_consume(ibuf, port_off+2); + + info(0, "SOCKS5 forward request to %s:%hu", host, port); + + tid = channel_request_tunnel(tunaf, host, port, 0); + if (host && (host != ip)) + free(host); + + if (tid == 0xff) + return -1; + + cli->tid = tid; + cli->state = NETSTATE_CONNECTING; + + return 0; +} + +/** + * handle SOCKS5 client network read-event + * @param[in] cli client socket + * @return 0 on success + */ +int socks5_read_event(netsock_t *cli) +{ + assert(valid_netsock(cli) && (cli->type == NETSOCK_S5CLI)); + trace_socks("state=0x%02x", cli->state); + + if (cli->state != NETSTATE_CONNECTED) + return socks5_setup(cli); + + return channel_forward_recv(cli); +} + +/** + * handle SOCKS5 server network accept-event + * @param[in] srv server socket + * @return 0 on success + */ +void socks5_accept_event(netsock_t *srv) +{ + netsock_t *cli; + char host[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(srv) && (srv->type == NETSOCK_S5SRV)); + trace_socks(""); + + cli = netsock_accept(srv); + if (cli) { + info(0, "accepted socks5 client %s", netaddr_print(&cli->addr, host)); + if (channel_is_connected()) { + cli->type = NETSOCK_S5CLI; + cli->tid = 0xff; + cli->state = NETSTATE_AUTHENTICATING; + iobuf_init2(&cli->u.sockscli.ibuf, &cli->u.sockscli.obuf, "socks5"); + } else { + error("channel not connected"); + netsock_close(cli); + } + } +} + +/** + * start a SOCKS5 server + * @param[in] cli socket of client who requested server start + * @param[in] host local server hostname or IP address + * @param[in] port local TCP port + */ +int socks5_bind(netsock_t *cli, const char *host, unsigned short port) +{ + netsock_t *srv; + + assert(valid_netsock(cli) && (cli->type == NETSOCK_CTRLCLI) + && host && *host && port); + trace_socks("host=%s, port=%hu", host, port); + + srv = netsock_bind(cli, host, port, 0); + if (!srv) + return 0; // soft-error + srv->type = NETSOCK_S5SRV; + + return controller_answer(cli, "SOCKS5 server listening on %s:%hu", host, port); +} + diff --git a/client/tunnel.c b/client/tunnel.c new file mode 100644 index 0000000..0537c4d --- /dev/null +++ b/client/tunnel.c @@ -0,0 +1,471 @@ +/** + * @file tunnel.c + * rdp2tcp tunnel management + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2tcli.h" +#include "nethelper.h" + +#include +#include + +extern struct list_head all_sockets; + +/** + * lookup socket by tunnel ID + * @param[in] tid tunnel ID + * @return NULL if socket was not found + */ +netsock_t *tunnel_lookup(unsigned char tid) +{ + netsock_t *ns; + + assert(tid != 0xff); + trace_tun("id=0x%02x", tid); + + list_for_each(ns, &all_sockets) { + if (ns->tid == tid) + return ns; + } + + return NULL; +} + +static unsigned char last_tid = 0xff; + +/** + * generate a unused tunnel ID + * @return 0xff on error (all tunnel ID are used) + */ +unsigned char tunnel_generate_id(void) +{ + unsigned char tid; + + for (tid=last_tid+1; tid!=last_tid; ++tid) { + if (!tunnel_lookup(tid)) { + last_tid = tid; + return tid; + } + } + + error("failed to find available tunnel id"); + return 0xff; +} + +static unsigned char sysaf_to_rdpaf(int af) +{ + switch (af) { + case AF_INET: return TUNAF_IPV4; + case AF_INET6: return TUNAF_IPV6; + } + return TUNAF_ANY; +} + +/** + * register a new TCP forwarding tunnel + * @param[in] cli socket of the client who requested the tunnel + * @param[in] lhost local hostname or IP address + * @param[in] lport local TCP port + * @param[in] raf remote address family (AF_INET/INET6/UNSPEC) + * @param[in] rhost remote hostname + * @param[in] rport remote TCP port + * @return 0 or 1 if the controller is still connected + */ +int tunnel_add( + netsock_t *cli, + char *lhost, + unsigned short lport, + int raf, + char *rhost, + unsigned short rport) +{ + size_t rhost_len; + netsock_t *ns; + char str[NETADDRSTR_MAXSIZE*2 + 64]; + + assert(valid_netsock(cli) && lhost && *lhost && lport && rhost && *rhost); + trace_tun("%s:%hu --> %s:%hu", lhost, lport, rhost, rport); + + rhost_len = strlen(rhost) + 1; + ns = netsock_bind(cli, lhost, lport, rhost_len); + if (!ns) + return 0; // soft error, no need to kill client + + + ns->type = NETSOCK_TUNSRV; + ns->u.tunsrv.raf = sysaf_to_rdpaf(raf); + ns->u.tunsrv.rport = rport; + memcpy(ns->u.tunsrv.rhost, rhost, rhost_len); + + if (rport) { + snprintf(str, sizeof(str)-1, "tunnel [%s]:%hu --> [%s]:%hu registered", + lhost, lport, rhost, rport); + } else { + snprintf(str, sizeof(str)-1, "tunnel [%s]:%hu --> %s registered", + lhost, lport, rhost); + } + + info(0, str); + return controller_answer(cli, str); +} + +/** + * register a new reverse connect TCP tunnel + * @param[in] cli socket of the client who requested the tunnel + * @param[in] lhost local hostname or IP address + * @param[in] lport local TCP port + * @param[in] raf remote address family (AF_INET/INET6/UNSPEC) + * @param[in] rhost remote hostname + * @param[in] rport remote TCP port + * @return 0 or 1 if the controller is still connected + */ +int tunnel_add_reverse( + netsock_t *cli, + char *lhost, + unsigned short lport, + int raf, + char *rhost, + unsigned short rport) +{ + size_t lhost_len, rhost_len; + netsock_t *ns; + char str[NETADDRSTR_MAXSIZE*2 + 64]; + + assert(valid_netsock(cli) && lhost && *lhost && lport && rhost && *rhost); + trace_tun("%s:%hu <-- %s:%hu", lhost, lport, rhost, rport); + + lhost_len = strlen(lhost) + 1; + rhost_len = strlen(rhost) + 1; + ns = netsock_alloc(cli, -1, NULL, lhost_len + rhost_len); + if (!ns) + return 0; // soft-error .. maybe hard but dont kill client + + ns->type = NETSOCK_RTUNSRV; + ns->u.rtunsrv.lport = lport; + ns->u.rtunsrv.rport = rport; + ns->u.rtunsrv.lhost_len = (unsigned short) lhost_len; + memcpy(ns->u.rtunsrv.lhost, lhost, lhost_len); + memcpy(&ns->u.rtunsrv.lhost[lhost_len], rhost, rhost_len); + + if (channel_is_connected()) { + // request tunnel binding right now if channel is connected + ns->tid = channel_request_tunnel(TUNAF_ANY, rhost, rport, 1); + if (ns->tid == 0xff) { + netsock_close(ns); + return controller_answer(cli, "error: failed to request port binding"); + } + } + + snprintf(str, sizeof(str)-1, "tunnel [%s]:%hu <-- [%s]:%hu is being registred", + lhost, lport, rhost, rport); + info(0, str); + return controller_answer(cli, str); +} + +/** + * try to remove tunnel removal + * @param[in] cli socket of client who requested tunnel removal + * @param[in] lhost tunnel local hostname + * @param[in] lport tunnel local TCP port + * @return 0 or 1 if the controller is still connected + */ +int tunnel_del(netsock_t *cli, char *lhost, unsigned short lport) +{ + netsock_t *ns; + int ret, err; + netaddr_t addr; + + assert(valid_netsock(cli) && lhost && *lhost && lport); + trace_tun("host=%s:%i", lhost, lport); + + ret = net_resolve(AF_UNSPEC, lhost, lport, &addr, &err); + if (ret) + return controller_answer(cli, "error: %s", net_error(ret, err)); + + list_for_each(ns, &all_sockets) { + + ret = 1; + + switch (ns->type) { + + case NETSOCK_TUNSRV: + case NETSOCK_S5SRV: + ret = netaddr_cmp(&ns->addr, &addr); + break; + + case NETSOCK_RTUNSRV: + ret = ((lport != ns->u.rtunsrv.lport) + || strcmp(lhost, ns->u.rtunsrv.lhost)); + break; + } + + if (!ret) { + tunnel_close(ns, 1); + info(0, "tunnel [%s]:%hu removed", lhost, lport); + return controller_answer(cli, "tunnel [%s]:%hu removed",lhost,lport); + } + } + + return controller_answer(cli,"error: tunnel [%s]:%hu not found",lhost,lport); +} + +/** + * close a tunnel + * @param[in] ns tunnel socket + * @param[in] notify_server 0 if rdp2tcp server must be notified + */ +void tunnel_close(netsock_t *ns, int notify_server) +{ + unsigned char tid; + //char host[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(ns)); + + tid = ns->tid; + trace_tun("tid=0x%02x, notify=%i", tid, notify_server); + + if (tid != 0xff) { + if (notify_server) + channel_close_tunnel(tid); + + if (tid == last_tid) + --last_tid; + } + + netsock_cancel(ns); +} + +/** + * handle tcp-connect tunnel network accept-event + * @param[in] srv tunnel socket + */ +void tunnel_accept_event(netsock_t *srv) +{ + unsigned char tid; + netsock_t *cli; + char host1[NETADDRSTR_MAXSIZE], host2[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(srv) && (srv->type == NETSOCK_TUNSRV)); + trace_tun(""); + + cli = netsock_accept(srv); + if (cli) { + cli->type = NETSOCK_TUNCLI; + iobuf_init(&cli->u.tuncli.obuf, 'w', "tun"); + + info(0, "accepted local tunnel client %s on %s", + netaddr_print(&cli->addr, host1), + netaddr_print(&srv->addr, host2)); + + if (channel_is_connected()) { + tid = channel_request_tunnel(srv->u.tunsrv.raf, + srv->u.tunsrv.rhost, + srv->u.tunsrv.rport, 0); + + if (tid != 0xff) { + info(0, "reserved tunnel 0x%02x for %s", + tid, netaddr_print(&cli->addr, host1)); + cli->tid = tid; + cli->state = NETSTATE_CONNECTING; + } else { + netsock_close(cli); + } + + } else { + netsock_close(cli); + error("channel not connected"); + } + } +} + +/** + * handle remote tcp-connect/process tunnel network connect-event + * @param[in] ns tunnel socket + * @param[in] af remote address family (AF_INET/INET6/UNSPEC) + * @param[in] addr remote tunnel address + * @param[in] port remote tunnel TCP port + */ +void tunnel_connect_event( + netsock_t *ns, + int af, + const void *addr, + unsigned short port) +{ + unsigned int pid; + char host[NETADDRSTR_MAXSIZE]; + + assert(valid_netsock(ns) && (ns->type == NETSOCK_TUNCLI) && addr); + + trace_tun("id=0x%02x, af=%s, port=%hu", + ns->tid, + af == AF_INET ? "ipv4" : (af == AF_UNSPEC ? "proc" : "ipv6"), port); + + ns->state = NETSTATE_CONNECTED; + + if (af != AF_UNSPEC) { + // tcp forwarding + netaddr_set(af, addr, port, &ns->u.tuncli.raddr); + info(0, "connected remote tunnel 0x%02x to %s", + ns->tid, netaddr_print(&ns->u.tuncli.raddr, host)); + } else { + // process stdin/out forwarding + pid = ntohl(*(unsigned int *)addr); + ns->u.tuncli.raddr.pid = pid; + ns->u.tuncli.is_process = 1; + info(0, "connected remote tunnel 0x%02x to process %u", + ns->tid, ns->u.tuncli.raddr.pid); + } +} + +/** + * handle tcp-listen tunnel network bind-event + * @param[in] ns tunnel (NETSOCK_RTUNSRV) + * @param[in] af remote address family (AF_INET/INET6/UNSPEC) + * @param[in] addr remote tunnel address + * @param[in] port remote tunnel TCP port + */ +void tunnel_bind_event( + netsock_t *ns, + int af, + const void *addr, + unsigned short port) +{ + assert(valid_netsock(ns) && (ns->type == NETSOCK_RTUNSRV) && addr && port); + trace_tun("id=0x%02x, af=%s, port=%hu", + ns->tid, + af == AF_INET ? "ipv4" : "ipv6", port); + + ns->u.rtunsrv.bound = 1; + netaddr_set(af, addr, port, &ns->addr); +} + +/** + * handle tcp-listen tunnel network connect-event + * @param[in] srv tunnel (NETSOCK_RTUNSRV) + * @param[in] new_id new tunnel id + * @param[in] af remote address family (AF_INET/INET6) + * @param[in] addr remote tunnel address + * @param[in] port remote tunnel TCP port + */ +void tunnel_revconnect_event( + netsock_t *srv, + unsigned char new_id, + int af, + const void *addr, + unsigned short port) +{ + netsock_t *cli; + + assert(valid_netsock(srv) && (srv->type == NETSOCK_RTUNSRV)); + trace_tun("new_id=0x%02x", new_id); + + cli = netsock_connect(srv->u.rtunsrv.lhost, srv->u.rtunsrv.lport); + if (cli) { + cli->type = NETSOCK_RTUNCLI; + cli->tid = new_id; + netaddr_set(af, addr, port, &cli->u.tuncli.raddr); + iobuf_init(&cli->u.tuncli.obuf, 'w', "rtuncli"); + } else { + channel_close_tunnel(new_id); + } +} + +/** + * write data to tunnel client + * @param[in] ns client socket + * @param[in] buf data to write + * @param[in] len size of buffer + * @return -1 on error + */ +int tunnel_write(netsock_t *ns, const void *buf, unsigned int len) +{ + assert(valid_netsock(ns) + && ((ns->type == NETSOCK_TUNCLI) || (ns->type == NETSOCK_RTUNCLI) + || (ns->type == NETSOCK_S5CLI))); + trace_tun("len=%u, state=%u", len, ns->state); + + return netsock_write(ns, buf, len); +} + +/** + * send tunnel queued data + * @param[in] ns client socket + * @return -1 on error + */ +int tunnel_write_event(netsock_t *ns) +{ + if ((ns->type == NETSOCK_RTUNCLI) && (ns->state != NETSTATE_CONNECTED)) + ns->state = NETSTATE_CONNECTED; + + return netsock_write(ns, NULL, 0); +} + +/** + * close all tunnels clients connections + */ +void tunnels_kill_clients(void) +{ + netsock_t *ns, *bak; + char host[NETADDRSTR_MAXSIZE]; + + list_for_each_safe(ns, bak, &all_sockets) { + + if (ns->type == NETSOCK_RTUNSRV) { + ns->tid = 0xff; + ns->u.rtunsrv.bound = 0; + memset(&ns->addr, 0, sizeof(ns->addr)); + + } else if (ns->type > NETSOCK_CTRLCLI) { + info(0, "closing tunnel client %s", + netaddr_print(&ns->addr, host)); + netsock_close(ns); + } + } +} + +/** + * re-bind reverse-connect tunnels + */ +void tunnels_restart(void) +{ + netsock_t *ns, *bak; + const char *rhost; + unsigned short rport; + + list_for_each_safe(ns, bak, &all_sockets) { + + if (ns->type == NETSOCK_RTUNSRV) { + + rhost = &ns->u.rtunsrv.lhost[ns->u.rtunsrv.lhost_len]; + rport = ns->u.rtunsrv.rport; + + ns->tid = channel_request_tunnel(TUNAF_ANY, rhost, rport, 1); + if (ns->tid != 0xff) { + info(0, "restarted %s:%hu <-- %s:%hu", + ns->u.rtunsrv.lhost, ns->u.rtunsrv.lport, rhost, rport); + } else { + error("failed to restart %s:%hu <-- %s:%hu", + ns->u.rtunsrv.lhost, ns->u.rtunsrv.lport, rhost, rport); + netsock_close(ns); + } + } + } +} + diff --git a/common/Makefile b/common/Makefile new file mode 100644 index 0000000..2183307 --- /dev/null +++ b/common/Makefile @@ -0,0 +1,12 @@ +CC=gcc +CFLAGS=-Wall -g +# -DDEBUG +OBJS= iobuf.o print.o msgparser.o nethelper.o netaddr.o + +all: $(OBJS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + rm -f $(OBJS) $(BIN) diff --git a/common/Makefile.mingw32 b/common/Makefile.mingw32 new file mode 100644 index 0000000..f736375 --- /dev/null +++ b/common/Makefile.mingw32 @@ -0,0 +1,12 @@ +CC=i586-mingw32msvc-gcc +CFLAGS=-Wall -g \ + -D_WIN32_WINNT=0x0501 -DDEBUG +OBJS= iobuf.o print.o msgparser.o nethelper.o netaddr.o + +all: $(OBJS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + rm -f $(OBJS) $(BIN) diff --git a/common/compiler.h b/common/compiler.h new file mode 100644 index 0000000..8587430 --- /dev/null +++ b/common/compiler.h @@ -0,0 +1,41 @@ +/** + * @file compiler.h + * Microsoft Visual Studio compiler support + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __COMPILER_H__ +#define __COMPILER_H__ + +#ifdef __GNUC__ +// gcc +#define PACK(decl) decl __attribute__((__packed__)) +#else +#define PACK(decl) __pragma(pack(push,1)) decl __pragma(pack(pop)) +// Visual Studio +#if defined(_DEBUG) && !defined(DEBUG) +#define DEBUG +#endif +#define ssize_t int +#define inline __inline +#define snprintf _snprintf +#define typeof(x) void * +#endif + +#endif diff --git a/common/debug.h b/common/debug.h new file mode 100644 index 0000000..f24d8dd --- /dev/null +++ b/common/debug.h @@ -0,0 +1,67 @@ +/** + * @file debug.h + * debug/trace support + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#if defined(_DEBUG) && !defined(DEBUG) +#define DEBUG +#endif + +#ifndef DEBUG +#define NDEBUG +#endif + +#include + +#ifdef DEBUG +#define debug __debug +void __debug(int, const char *, ...); + +extern int tracing_flags; + +void __trace(const char *, int, const char *, const char *, ...); + +#define trace(cat, ...) \ + { if (tracing_flags & (1 << (cat))){ \ + __trace(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);} } + +#define LIB_TRACING_CATS \ + "iobuf", "sock", "chan", "evt", "proc", "ctrl", "tun", "socks" + +#else +/** print debug statement */ +#define debug(a, ...) ((void)0) +/** generate call trace */ +#define trace(...) ((void)0) +#endif + +#define trace_iobuf(...) trace(0, __VA_ARGS__) +#define trace_sock(...) trace(1, __VA_ARGS__) +#define trace_chan(...) trace(2, __VA_ARGS__) +#define trace_evt(...) trace(3, __VA_ARGS__) +#define trace_proc(...) trace(4, __VA_ARGS__) +#define trace_ctrl(...) trace(5, __VA_ARGS__) +#define trace_tun(...) trace(6, __VA_ARGS__) +#define trace_socks(...) trace(7, __VA_ARGS__) + +#endif diff --git a/common/iobuf.c b/common/iobuf.c new file mode 100644 index 0000000..66368f9 --- /dev/null +++ b/common/iobuf.c @@ -0,0 +1,219 @@ +/** + * @file iobuf.c + * @brief I/O buffer helpers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "debug.h" +#include "iobuf.h" + +#include +#include +#include +#ifdef DEBUG +#include +#endif + +/** + * @brief initialize I/O buffer + * @param[out] buf buffer to initialize + */ +void __iobuf_init(iobuf_t *buf +#ifdef DEBUG + , char type, const char *name +#endif +) +{ + assert(buf); + + buf->data = NULL; + buf->size = 0; + buf->total = 0; +#ifdef DEBUG + buf->name = name; + buf->type = type; +#endif + + trace_iobuf("[%c] %s", type, name); +} + + +/** + * initialize 2 I/O buffers + * @param[out] ibuf input buffer + * @param[out] obuf output buffer + */ +void __iobuf_init2(iobuf_t *ibuf, iobuf_t *obuf +#ifdef DEBUG + , const char *name +#endif + ) +{ + assert(ibuf && obuf); + trace_iobuf("%s", name); + +#ifdef DEBUG + __iobuf_init(ibuf, 'r', name); + __iobuf_init(obuf, 'w', name); +#else + __iobuf_init(ibuf); + __iobuf_init(obuf); +#endif +} + +/** + * destroy an I/O buffer + * @param[in] buf buffer to destroy + */ +void iobuf_kill(iobuf_t *buf) +{ + assert_iobuf(buf); + trace_iobuf("[%c] %s", buf->type, buf->name); + + if (buf->data) + free(buf->data); +} + +/** + * destroy 2 I/O buffers + * @param[in] ibuf input buffer + * @param[in] obuf output buffer + */ +void iobuf_kill2(iobuf_t *ibuf, iobuf_t *obuf) +{ + assert(valid_iobuf(ibuf) && valid_iobuf(obuf)); + + iobuf_kill(obuf); + iobuf_kill(ibuf); +} + +/** + * @brief consume data in an I/O buffer + * @param[in] buf I/O buffer where data will be consumed + * @param[in] consumed size of consumed data + */ +void iobuf_consume(iobuf_t *buf, unsigned int consumed) +{ + unsigned int size; + + assert(valid_iobuf(buf) && (consumed > 0) && (consumed <= buf->size)); + + size = buf->size - consumed; + trace_iobuf("[%c] %s, consumed=%u, remaining=%u", + buf->type, buf->name, consumed, size); + + if (size) + memmove(buf->data, buf->data + consumed, size); + + buf->size = size; +} + +/** + * @brief reserve space in an I/O buffer + * @param[in] buf I/O buffer where data will be written + * @param[in] size size to reserve + * @param[out] reserved will hold the size of allocated data + * @return pointer where data have been allocated + * @note if size is 0 reserved must be non-NULL + */ +void *iobuf_reserve(iobuf_t *buf, unsigned int size, unsigned int *reserved) +{ + unsigned int avail; + void *bak, *data; + + assert(valid_iobuf(buf) && (size || reserved)); + + avail = buf->total - buf->size; + + if (!size) + size = IOBUF_MIN_SIZE; + + trace_iobuf("[%c] %s, size=%u, avail=%u", + buf->type, buf->name, size, avail); + + if (size > avail) { + bak = buf->data; + data = realloc(bak, buf->size + size); + if (!data) + return NULL; + buf->data = data; + buf->total = buf->size + size; + } + + if (reserved) + *reserved = size; + + return buf->data + buf->size; +} + +/** + * commit data to an I/O buffer + * @param[in] buf I/O buffer where data have been written + * @param[in] commited size of data to commit + * @note data must have been previously allocated with iobuf_reserve + */ +void iobuf_commit(iobuf_t *buf, unsigned int commited) +{ + assert(valid_iobuf(buf) && (commited > 0) + && (commited <= (buf->total - buf->size))); + trace_iobuf("[%c] %s, commited=%u, total=%u, size=%u", + buf->type, buf->name, commited, buf->total, buf->size); + + buf->size += commited; +} + +/** + * append data to an I/O buffer + * @param[in] buf I/O buffer to hold data + * @param[in] data content to append + * @param[in] size size of data to append + * @return pointer where data have been written or NULL if memory + * cannot be allocated + */ +void *iobuf_append(iobuf_t *buf, const void *data, unsigned int size) +{ + void *ptr; + + assert(valid_iobuf(buf) && data && size); + trace_iobuf("[%c] %s, size=%u", buf->type, buf->name, size); + + ptr = iobuf_reserve(buf, size, NULL); + if (!ptr) + return NULL; + memcpy(ptr, data, size); + iobuf_commit(buf, size); + + return ptr; +} + +#ifdef DEBUG +void iobuf_dump(iobuf_t *buf) +{ + unsigned int i, len; + unsigned char *data; + + data = (unsigned char *)iobuf_dataptr(buf); + fprintf(stderr, "[%s-%c] ", buf->name, buf->type); + for (i=0, len=iobuf_datalen(buf); i. + */ +#ifndef __MPROXY_IOBUF_H__ +#define __MPROXY_IOBUF_H__ + +#include "debug.h" +#include + +#ifndef IOBUF_MIN_SIZE +#define IOBUF_MIN_SIZE 2048 +#endif + +/** I/O buffer */ +typedef struct iobuf { + unsigned int size; /**< used size */ + unsigned int total; /**< allocated size */ + char *data; /**< data buffer */ +#ifdef DEBUG + const char *name; + char type; +#endif +} iobuf_t; + +#ifdef DEBUG +#define valid_iobuf(x) \ + ((x) && (((x)->size <= (x)->total) && ((x)->data || !((x)->total))) \ + && (x)->name && (((x)->type == 'r') || (x)->type == 'w')) +void iobuf_dump(iobuf_t *); +void __iobuf_init(iobuf_t *, char, const char *); +void __iobuf_init2(iobuf_t *, iobuf_t *, const char *); +#define iobuf_init(buf, type, name) __iobuf_init(buf, type, name) +#define iobuf_init2(buf1, buf2, name) __iobuf_init2(buf1, buf2, name) + +#else +#define valid_iobuf(x) \ + ((x) && (((x)->size <= (x)->total) && ((x)->data || !((x)->total)))) +void __iobuf_init(iobuf_t *); +void __iobuf_init2(iobuf_t *, iobuf_t *); +#define iobuf_init(buf, type, name) __iobuf_init(buf) +#define iobuf_init2(buf1, buf2, name) __iobuf_init2(buf1, buf2) +#endif + +#define assert_iobuf(x) assert(valid_iobuf(x)) + +void iobuf_kill(iobuf_t *); +void iobuf_kill2(iobuf_t *, iobuf_t *); + +#if defined(_WIN32) && !defined(__GNUC__) +#define inline __inline +#endif + +static inline unsigned int iobuf_datalen(iobuf_t *buf) +{ + return buf->size; +} + +static inline void *iobuf_dataptr(iobuf_t *buf) +{ + return buf->size ? buf->data : 0; +} + +static inline void *iobuf_allocptr(iobuf_t *buf) +{ + return ((char *)buf->data) + buf->size; +} + +void iobuf_consume(iobuf_t *, unsigned int); + +void *iobuf_reserve(iobuf_t *, unsigned int, unsigned int *); +void iobuf_commit(iobuf_t *, unsigned int); +void *iobuf_append(iobuf_t *, const void *, unsigned int); +//void iobuf_xfer(iobuf_t *, iobuf_t *); + +#endif +// vim: ts=3 sw=3 diff --git a/common/list.h b/common/list.h new file mode 100644 index 0000000..79111f3 --- /dev/null +++ b/common/list.h @@ -0,0 +1,109 @@ +/** + * @file list.h + * doubled-linked list helpers + * @note this code is a rip of Linux kernel source (include/linux/list.h) + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __R2T_LIST__ +#define __R2T_LIST__ + +#include "compiler.h" + +/** double-linked list */ +struct list_head { + struct list_head *next, *prev; +}; + +/** + * define an initialized double-linked list + */ +#define LIST_HEAD_INIT(name) \ + struct list_head name = { &(name), &(name) } + +/** + * check whether the list pointer is valid + */ +#define valid_list_head(l) ((l) && (l)->next && (l)->prev) + +static inline void list_init(struct list_head *head) +{ + head->next = head; + head->prev = head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + */ +static inline void list_add_tail(struct list_head *lst, struct list_head *head) +{ + head->prev->next = lst; + lst->next = head; + lst->prev = head->prev; + head->prev = lst; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *lst) +{ + lst->next->prev = lst->prev; + lst->prev->next = lst->next; +} + +/** + * iterate over a list + * @param[in] pos the struct list_head to use as a loop cursor + * @param[in] head the head for the list + */ +#define list_for_each(pos, head) \ + for (pos = (typeof(pos))(head)->next; \ + ((struct list_head *)pos) != (head); \ + pos = (typeof(pos)) ((struct list_head *)pos)->next) + +/** + * iterate over a list safe against removal of list entry + * @param[in] pos the struct list_head to use as a loop cursor + * @param[in] n another struct list_head to use as temporary storage + * @param[in] head the head for the list + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (typeof(pos))(head)->next, \ + n = (typeof(pos))((struct list_head *)pos)->next; \ + ((struct list_head *)pos) != (head); \ + pos = n, n = (typeof(pos)) ((struct list_head *)pos)->next) + +#endif diff --git a/common/msgparser.c b/common/msgparser.c new file mode 100644 index 0000000..398631b --- /dev/null +++ b/common/msgparser.c @@ -0,0 +1,113 @@ +/** + * @file msgparser.c + * rdp2tcp commands parser + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "print.h" +#include "iobuf.h" +#include "msgparser.h" + +#include +#ifndef _WIN32 +#include +#else +#include +#endif + +extern int debug_level; +extern const cmdhandler_t cmd_handlers[]; + +/** + * parse rdp2tcp commands and call specific handlers + * @param[in] ibuf input buffer + * @return 0 on success, -1 on error + */ +int commands_parse(iobuf_t *ibuf) +{ + unsigned char cmd, *data; + unsigned int off, msg_len, avail; + static const unsigned char r2t_min_size[R2TCMD_MAX] = { + 3, // R2TCMD_CONN + 2, // R2TCMD_CLOSE + 2, // R2TCMD_DATA + 1, // R2TCMD_PING + 3, // R2TCMD_BIND + 2 // R2TCMD_RCONN + }; + + assert(valid_iobuf(ibuf) && (iobuf_datalen(ibuf)>0)); + +#ifdef DEBUG + if (debug_level > 0) iobuf_dump(ibuf); +#endif + + off = 0; + data = iobuf_dataptr(ibuf); + avail = iobuf_datalen(ibuf); + debug(1, "commands_parse(avail=%u)", avail); + + // for each command + while (off + 5 < avail) { + + msg_len = ntohl(*(unsigned int*)(data+off)); + if (!msg_len || (msg_len > RDP2TCP_MAX_MSGLEN)) + return error("invalid channel msg size 0x%08x", msg_len); + + if (off+msg_len+4 > avail) + break; + + off += 4; + + cmd = data[off]; + if (cmd >= R2TCMD_MAX) + return error("invalid command id 0x%02x", cmd); + + if (msg_len < (unsigned int)r2t_min_size[cmd]) + return error("command 0x%02x too short 0x%08x < 0x%08x", + cmd, msg_len, (unsigned int)r2t_min_size[cmd]); + + if (!cmd_handlers[cmd]) + return error("command 0x%02x not supported", cmd); + + // call specific command handler + if (cmd_handlers[cmd]((const r2tmsg_t*)(data+off), msg_len)) + return -1; + + off += msg_len; + } + + if (off > 0) + iobuf_consume(ibuf, off); + + return 0; +} + +// R2TERR_xxx error strings +const char *r2t_errors[R2TERR_MAX] = { + "", + "generic error", + "bad message", + "connection refused", + "forbidden", + "address not available", + "failed to resolve hostname", + "executable not found" +}; + diff --git a/common/msgparser.h b/common/msgparser.h new file mode 100644 index 0000000..7fd2ef8 --- /dev/null +++ b/common/msgparser.h @@ -0,0 +1,28 @@ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __R2TMSG_PARSER_H__ +#define __R2TMSG_PARSER_H__ + +#include "rdp2tcp.h" + +typedef int (*cmdhandler_t)(const r2tmsg_t *, unsigned int); + +int commands_parse(iobuf_t *); + +#endif diff --git a/common/netaddr.c b/common/netaddr.c new file mode 100644 index 0000000..b060b55 --- /dev/null +++ b/common/netaddr.c @@ -0,0 +1,148 @@ +/** + * @file netaddr.c + * netaddr_t structure helpers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nethelper.h" +#include "debug.h" + +#include +#include +#ifndef _WIN32 +#include +#endif + +/** + * setup a netaddr_t structure + * @param[in] af address family (AF_INET or AF_INET6) + * @param[in] addr address in binary format + * @param[in] port service port + * @param[out] a the netaddr_t structure to setup + */ +void netaddr_set(int af, const void *addr, unsigned short port, netaddr_t *a) +{ + assert(((af == AF_INET) || (af == AF_INET6)) && addr && port && a); + + if (af == AF_INET) { + a->ip4.sin_family = AF_INET; + a->ip4.sin_port = ntohs(port); + memcpy(&a->ip4.sin_addr, addr, 4); + } else { + a->ip6.sin6_family = AF_INET; + a->ip6.sin6_port = ntohs(port); + memcpy(&a->ip6.sin6_addr, addr, 16); + } +} + +/** + * compare 2 netaddr_t structures + * @return 0 if both structures are the same + */ +int netaddr_cmp(const netaddr_t *a, const netaddr_t *b) +{ + assert(a && ((netaddr_af(a) == AF_INET) || (netaddr_af(a) == AF_INET6)) + && b && ((netaddr_af(b) == AF_INET) || (netaddr_af(b) == AF_INET6))); + + if (netaddr_af(a) != netaddr_af(b)) + return 1; + + if (netaddr_af(a) == AF_INET) { + + if (((struct sockaddr_in*)a)->sin_port + != ((struct sockaddr_in*)b)->sin_port) + return 1; + + + return ((struct sockaddr_in*)a)->sin_addr.s_addr + != ((struct sockaddr_in*)b)->sin_addr.s_addr; + } + + + if (((struct sockaddr_in6*)a)->sin6_port + != ((struct sockaddr_in6*)b)->sin6_port) + return 1; + + return memcmp(&((struct sockaddr_in6*)a)->sin6_addr, + &((struct sockaddr_in6*)b)->sin6_addr, 16); +} + +/** + * convert a netaddr_t structure to a string + * @param[in] addr netaddr_t structure to serialize + * @param[out] buf output string buffer + * @return a pointer to buf + * @note buf size must be at least NETADDRSTR_MAXSIZE + */ +const char *netaddr_print(const netaddr_t *addr, char *buf) +{ + unsigned short port; + char *ptr; +#ifndef _WIN32 + void *a; +#else + DWORD len, addr_len; +#endif + + assert(buf && addr); + if ((netaddr_af(addr) != AF_INET) && (netaddr_af(addr) != AF_INET6)) + return (const char*)memcpy(buf, "???", 4); + + ptr = buf; + //memset(buf, 0, NETADDRSTR_MAXSIZE); + + if (netaddr_af(addr) == AF_INET) { +#ifndef _WIN32 + a = (void *)&addr->ip4.sin_addr; +#else + addr_len = sizeof(struct sockaddr_in); +#endif + port = addr->ip4.sin_port; + } else { + *ptr++ = '['; +#ifndef _WIN32 + a = (void *)&addr->ip6.sin6_addr; +#else + addr_len = sizeof(struct sockaddr_in6); +#endif + port = addr->ip6.sin6_port; + } + +#ifndef _WIN32 + if (inet_ntop(netaddr_af(addr), a, ptr, INET6_ADDRSTRLEN)) + ptr += strlen(ptr); + else + *ptr++ = '?'; +#else + len = INET6_ADDRSTRLEN; + ptr[0] = 0; + if (!WSAAddressToStringA((struct sockaddr *)addr, addr_len, NULL, ptr, &len)) + ptr += len; + else + *ptr++ = '?'; +#endif + + if (netaddr_af(addr) == AF_INET6) + *ptr++ = ']'; + + snprintf(ptr, 7, ":%hu", ntohs(port)); + + return (const char*) buf; +} + diff --git a/common/nethelper.c b/common/nethelper.c new file mode 100644 index 0000000..e070201 --- /dev/null +++ b/common/nethelper.c @@ -0,0 +1,510 @@ +/** + * @file nethelper.c + * network client/server helpers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nethelper.h" +#include "debug.h" + +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#endif + +#ifndef _WIN32 +#define nethelper_error errno +#define nethelper_badsock -1 +#define close_sock(x) close(x) +#define net_fd(s) (*(s)) + +#else +#define nethelper_error WSAGetLastError() +#define nethelper_badsock INVALID_SOCKET +#define close_sock(x) closesocket(x) +#define ENOMEM ERROR_NOT_ENOUGH_MEMORY +#define net_fd(s) ((s)->fd) + +/** + * initialize network subsystem + */ +void net_init(void) +{ + WSADATA wsa; + WSAStartup(MAKEWORD(2,2), &wsa); +} + +/** + * close socket and associated event + */ +void net_close(sock_t *s) +{ + closesocket(s->fd); + WSACloseEvent(s->evt); +} + +/** + * update socket events filter + * @param[in] s the socket + * @param[in] obuf output buffer associated with the socket + * @return 0 on success + */ +int net_update_watch(sock_t *s, iobuf_t *obuf) +{ + assert(valid_sock(s) && valid_iobuf(obuf)); + return WSAEventSelect(s->fd, s->evt, + (iobuf_datalen(obuf) > 0 + ? FD_READ|FD_WRITE|FD_CLOSE + : FD_READ|FD_CLOSE)); +} +#endif // _WIN32 + +/** + * return a string describing the error + * @return the error description + * @note the returned string is hold in a static buffer + */ +const char *net_error(int ret, int err) +{ + const char *x; + static char buffer[512]; +#ifdef _WIN32 + static char msg[512]; +#endif + static const char *actions_errors[] = { + "failed to resolve hostname", + "no valid address", + "failed to create socket", + "failed to bind socket", + "failed to setup socket", + "failed to connect", + "failed to receive", + "failed to send" + }; + + x = ((ret >= NETERR_SEND) && (ret < 0)) ? actions_errors[-ret-1] : "???"; + +#ifndef _WIN32 + snprintf(buffer, sizeof(buffer)-1, "%s (%s)", x, + (ret == NETERR_RESOLVE ? gai_strerror(err) : strerror(err))); +#else + msg[0] = 0; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_IGNORE_INSERTS + |FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)msg, sizeof(msg), NULL); + snprintf(buffer, sizeof(buffer)-1, "%s (%s)", x, msg); +#endif + return (const char *) buffer; +} + +static int netres( + int mode, + int pref_af, + const char *host, + unsigned short port, + sock_t *out_sock, + netaddr_t *addr, + int *err) +{ +#ifndef _WIN32 + int fd; +#else + SOCKET fd; + WSAEVENT evt; +#endif + int ret, n; + struct addrinfo hints, *res, *ptr; + char service[8]; + + assert(((pref_af==AF_UNSPEC) || (pref_af==AF_INET) || (pref_af==AF_INET6)) + && host && *host && port && addr && err && (out_sock || !mode)); + *err = 0; + + if (addr) + memset(addr, 0, sizeof(*addr)); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pref_af; + hints.ai_socktype = SOCK_STREAM; + snprintf(service, sizeof(service)-1, "%hu", port); + + res = NULL; + ret = getaddrinfo(host, service, &hints, &res); + if (ret) { + *err = ret; + return NETERR_RESOLVE; + } + + ret = NETERR_NOADDR; + fd = nethelper_badsock; +#ifdef _WIN32 + evt = WSA_INVALID_EVENT; +#endif + + // for each hostname resolution result + for (ptr=res; ptr; ptr=ptr->ai_next) { + + if (addr) + memcpy(addr, ptr->ai_addr, ptr->ai_addrlen); + + if (!mode) { // resolve-only + ret = 0; + break; + } + + // create new async socket + fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); + if (fd == nethelper_badsock) { + *err = nethelper_error; + ret = NETERR_SOCKET; + break; + } + +#ifndef _WIN32 + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK); +#else + evt = WSACreateEvent(); + if (evt == WSA_INVALID_EVENT) { + *err = nethelper_error; + ret = NETERR_SOCKET; + break; + } +#endif + + if (mode == 1) { + // tcp server + n = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,(const void*)&n, sizeof(n)); + + if (!bind(fd, ptr->ai_addr, ptr->ai_addrlen)) { + + if (!listen(fd, 5)) { +#ifdef _WIN32 + if (WSAEventSelect(fd, evt, FD_ACCEPT)) { + *err = nethelper_error; + ret = NETERR_SOCKET; + break; + } +#endif + ret = 0; + } else { + *err = nethelper_error; + ret = NETERR_LISTEN; + } + break; + } + ret = NETERR_BIND; + + } else { + // tcp client +#ifdef _WIN32 + if (WSAEventSelect(fd, evt, FD_CONNECT|FD_CLOSE)) { + *err = nethelper_error; + ret = NETERR_SOCKET; + break; + } +#endif + if (!connect(fd, ptr->ai_addr, ptr->ai_addrlen)) { +#ifdef _WIN32 + if (WSAEventSelect(fd, evt, FD_READ|FD_CLOSE)) { + *err = nethelper_error; + ret = NETERR_SOCKET; + break; + } +#endif + ret = 0; + break; + } + if (net_pending()) { + ret = 1; + break; + } + *err = nethelper_error; + ret = NETERR_CONNECT; + } + +#ifdef _WIN32 + WSACloseEvent(evt); +#endif + close_sock(fd); + *err = nethelper_error; + } + + freeaddrinfo(res); + + if ((ret >= 0) && mode) { +#ifndef _WIN32 + *out_sock = fd; +#else + out_sock->fd = fd; + out_sock->evt = evt; +#endif + } else if (fd != nethelper_badsock) { + close_sock(fd); +#ifdef _WIN32 + if (evt != WSA_INVALID_EVENT) + WSACloseEvent(evt); +#endif + } + + return ret; +} + +/** + * resolve a hostname + * @return -1 on error, 0 on success + */ +int net_resolve( + int pref_af, + const char *host, + unsigned short port, + netaddr_t *addr, + int *err) +{ + + return netres(0, pref_af, host, port, NULL, addr, err); +} + +/** + * resolve a hostname and bind a socket server + * @return -1 on error, 0 on success + */ +int net_server( + int pref_af, + const char *host, + unsigned short port, + sock_t *out_sock, + netaddr_t *addr, + int *err) +{ + return netres(1, pref_af, host, port, out_sock, addr, err); +} + +/** + * resolve a hostname and connect a socket client + * @return -1 on error, 0 on success, 1 if connection is pending + */ +int net_client( + int pref_af, + const char *host, + unsigned short port, + sock_t *out_sock, + netaddr_t *addr, + int *err) +{ + + return netres(2, pref_af, host, port, out_sock, addr, err); +} + +/** + * accept a client connection + * @param[in] srv the server socket + * @param[out] cli client socket + * @param[out] addr client network address + * @return 0 on success + */ +int net_accept(sock_t *srv, sock_t *cli, netaddr_t *addr) +{ + socklen_t addrlen; + + assert(valid_sock(srv) && cli && addr); + addrlen = sizeof(*addr); + +#if defined(HAVE_ACCEPT4) + *cli = accept4(*srv, (struct sockaddr *)addr, &addrlen, SOCK_NONBLOCK); + if (*cli == nethelper_badsock) + return nethelper_error; + +#elif !defined(_WIN32) + *cli = accept(*srv, (struct sockaddr *)addr, &addrlen); + if (*cli == nethelper_badsock) + return nethelper_error; + fcntl(*cli, F_SETFL, fcntl(*cli, F_GETFL)|O_NONBLOCK); + +#else + cli->fd = accept(srv->fd, (struct sockaddr *)addr, &addrlen); + if (cli->fd == nethelper_badsock) + return nethelper_error; + + cli->evt = WSACreateEvent(); + if (cli->evt == WSA_INVALID_EVENT) { + return nethelper_error; + } + if (WSAEventSelect(cli->fd, cli->evt, FD_READ|FD_CLOSE)) { + WSACloseEvent(cli->evt); + return nethelper_error; + } +#endif + + return 0; +} + +/** + * async read from file descriptor to I/O buffer + * @param[in] s socket + * @param[in,out] ibuf input buffer + * @param[in] prefix_size allocation head padding size + * @param[in,out] min_size minimal I/O chunk size + * @param[out] out_size hold transfer size on success + * @return -1 on error, 0 on success and 1 if the operation would block + */ +int net_read( + sock_t *s, + iobuf_t *ibuf, + unsigned int prefix_size, + unsigned int *min_size, + unsigned int *out_size) +{ + ssize_t ret; + unsigned int avail, curr_min_size; + char *buf; + + assert(valid_sock(s) && valid_iobuf(ibuf) && out_size); + + if (min_size && !*min_size) + *min_size = IOBUF_MIN_SIZE; + + curr_min_size = (min_size ? *min_size : IOBUF_MIN_SIZE); + + *out_size = 0; + + buf = iobuf_reserve(ibuf, curr_min_size, &avail); + if (!buf) + return -ENOMEM; + assert(avail > prefix_size); + +#ifndef _WIN32 + ret = read(net_fd(s), buf+prefix_size, avail-prefix_size); +#else + ret = recv(net_fd(s), buf+prefix_size, avail-prefix_size, 0); +#endif + if (ret > 0) { + iobuf_commit(ibuf, prefix_size + (unsigned int) ret); + *out_size = (unsigned int) ret; + + if (ret == (avail - prefix_size)) { + // increase I/O chunks size (perfs..) + curr_min_size <<= 1; + if (curr_min_size > NETBUF_MAX_SIZE) + curr_min_size = NETBUF_MAX_SIZE; + *min_size = curr_min_size; + } + return 0; + } + + if (!ret) + return NETERR_CLOSED; + + if (net_pending()) + return 1; + + return -(int)nethelper_error; +} + +/** + * async write from I/O buffer to file descriptor + * @param[in] s socket + * @param[in] obuf I/O output buffer + * @param[in] data data to append to I/O buffer + * @param[in] size size of data to append + * @param[out] out_size hold transfer size on success + * @return -1 on error, 0 on success and 1 if the operation would block + */ +int net_write( + sock_t *s, + iobuf_t *obuf, + const void *data, + unsigned int size, + unsigned int *out_size) +{ + ssize_t ret; + unsigned int used; + + assert(valid_sock(s) && valid_iobuf(obuf ) && (data || !size) && out_size); + + *out_size = 0; + used = iobuf_datalen(obuf); + + if (size > 0) { + if (!used) { + // try zero-copy send +#ifndef _WIN32 + ret = write(net_fd(s), data, size); +#else + ret = send(net_fd(s), data, size, 0); +#endif + if (ret < 0) + return net_pending() ? 1 : -(int)nethelper_error; + + if (!ret) + return NETERR_CLOSED; + + data = ((const char *)data) + ret; + size -= ret; + *out_size = (unsigned int) ret; + if (!size) { +#ifdef _WIN32 + // don't watch FD_WRITE events anymore + if (WSAEventSelect(s->fd, s->evt, FD_READ|FD_CLOSE)) + return -(int)nethelper_error; +#endif + return 0; + } + } + + if (!iobuf_append(obuf, data, size)) + return -ENOMEM; + + return 1; + } + + if (!used) + return 0; + +#ifndef _WIN32 + ret = write(net_fd(s), iobuf_dataptr(obuf), used); +#else + ret = send(net_fd(s), iobuf_dataptr(obuf), used, 0); +#endif + if (ret < 0) + return net_pending() ? 1 : -(int)nethelper_error; + + if (!ret) + return NETERR_CLOSED; + + iobuf_consume(obuf, (unsigned int) ret); + *out_size = (unsigned int) ret; + +#ifdef _WIN32 + if (used == (unsigned int)ret) { + // don't watch FD_WRITE events anymore + if (WSAEventSelect(s->fd, s->evt, FD_READ|FD_CLOSE)) + return -(int)nethelper_error; + } +#endif + return 0; +} diff --git a/common/nethelper.h b/common/nethelper.h new file mode 100644 index 0000000..8468800 --- /dev/null +++ b/common/nethelper.h @@ -0,0 +1,95 @@ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __NETHELPER_H__ +#define __NETHELPER_H__ + +#include "compiler.h" +#include "iobuf.h" + +#define NETERR_RESOLVE -1 +#define NETERR_NOADDR -2 +#define NETERR_SOCKET -3 +#define NETERR_BIND -4 +#define NETERR_LISTEN -5 +#define NETERR_CONNECT -6 +#define NETERR_RECV -7 +#define NETERR_SEND -8 + +#define NETERR_CLOSED -1000 + +#ifndef _WIN32 +#include +#include +#include +#include + +typedef int sock_t; +#define net_init() ((void)0) +#define net_exit() ((void)0) +#define net_close(x) close(x) +#define net_pending() ((errno == EINPROGRESS) || (errno == EAGAIN)) +#define valid_sock(s) ((s) && (*(s) != -1)) + +#else +#include +#include + +typedef struct { + SOCKET fd; + WSAEVENT evt; +} sock_t; + +void net_init(void); +#define net_exit() WSACleanup() +void net_close(sock_t *); +#define net_pending() (WSAGetLastError() == WSAEWOULDBLOCK) +#define valid_sock(s) ((s) && ((s)->fd != INVALID_SOCKET) \ + && ((s)->evt != WSA_INVALID_EVENT)) + +int net_update_watch(sock_t *, iobuf_t *); +#endif + +#ifndef NETBUF_MAX_SIZE +#define NETBUF_MAX_SIZE (1024*16) +#endif + +/** peer address */ +typedef union { + struct sockaddr_in ip4; /**< IPv4 address */ + struct sockaddr_in6 ip6; /**< IPv6 address */ + unsigned int pid; /**< process identifier */ +} netaddr_t; + +#define netaddr_af(na) (na)->ip4.sin_family +void netaddr_set(int, const void *, unsigned short, netaddr_t *); + +int netaddr_cmp(const netaddr_t *, const netaddr_t *); +#define NETADDRSTR_MAXSIZE (1+INET6_ADDRSTRLEN+1+1+5+1) +const char *netaddr_print(const netaddr_t *, char *); + +const char *net_error(int, int); + +int net_resolve(int, const char *, unsigned short, netaddr_t *, int *); +int net_server(int, const char *, unsigned short, sock_t *, netaddr_t *,int*); +int net_client(int, const char *, unsigned short, sock_t *, netaddr_t *,int*); +int net_accept(sock_t *, sock_t *, netaddr_t *); +int net_read(sock_t*, iobuf_t*, unsigned int, unsigned int*, unsigned int*); +int net_write(sock_t *, iobuf_t *, const void *, unsigned int, unsigned int *); + +#endif diff --git a/common/print.c b/common/print.c new file mode 100644 index 0000000..2808702 --- /dev/null +++ b/common/print.c @@ -0,0 +1,184 @@ +/** + * @file print.c + * debug/info/warn/error messages helpers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +#include "debug.h" +#include "print.h" + +#define PRINT_INFO 0 +#define PRINT_WARN 1 +#define PRINT_ERR 2 +#define PRINT_DBG 3 +#define PRINT_MAX 4 + +int info_level = 3; +static FILE *print_fps[PRINT_MAX]; + +/* common code {{{ */ +static void do_print( + unsigned int fid, + const char *prefix, + const char *fmt, + va_list va) +{ + FILE *fp; + + assert(print_fps[fid] && fmt); + + fp = print_fps[fid]; + if (prefix) + fputs(prefix, fp); + vfprintf(fp, fmt, va); + fputc('\n', fp); +} +/* }}} */ +/* debug {{{ */ +int debug_level = -1; +int tracing_flags = 0; + +void __debug(int level, const char *fmt, ...) +{ + va_list va; + + if (level <= debug_level) { + va_start(va, fmt); + do_print(PRINT_DBG, "debug: ", fmt, va); + va_end(va); + } +} + +void __trace( + const char *file, + int line, + const char *func, + const char *fmt, + ...) +{ + va_list va; + + fprintf(stderr, " %s(", func); + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + fputs(")\n", stderr); +} + +/* }}} */ +/* info/warn/error API {{{ */ +void print_init(void) +{ +#ifdef DEBUG + char *val; + + val = getenv("DEBUG"); + if (val) + debug_level = atoi(val); + val = getenv("TRACE"); + if (val) + tracing_flags = (int)strtoul(val, NULL, 16); + print_fps[3] = stderr; +#endif + print_fps[0] = stderr; + print_fps[1] = stderr; + print_fps[2] = stderr; +} + +/** + * @brief print information on stdout + * @param[in] level information verbosity level + * @param[in] fmt format string + * @see warn error + */ +void info(int level, const char *fmt, ...) +{ + va_list va; + + assert(fmt); + + if (level <= info_level) { + va_start(va, fmt); + do_print(PRINT_INFO, NULL, fmt, va); + va_end(va); + } +} + +/** + * @brief print a warning on stderr + * @param[in] fmt format string + * @return always -1 + * @see info, error + */ +int warn(const char *fmt, ...) +{ + va_list va; + + assert(fmt); + + va_start(va, fmt); + do_print(PRINT_WARN, "warn: ", fmt, va); + va_end(va); + + return -1; +} +/** + * @brief print an error on stderr + * @param[in] fmt format string + * @return always -1 + * @see info, warn + */ +int error(const char *fmt, ...) +{ + va_list va; + + assert(fmt); + + va_start(va, fmt); + do_print(PRINT_ERR, "error: ", fmt, va); + va_end(va); + + return -1; +} +/* }}} */ + +/** + * print I/O transfer length + */ +void print_xfer(const char *name, char rw, unsigned int size) +{ + info(1, (rw=='r'?"%-6s < %-8u":"%-6s %8u >"), name, size); +} + +#ifdef DEBUG +void fprint_hex(void *data, unsigned int len, FILE *fp) +{ + unsigned int i; + for (i=0; i. + */ +#ifndef __PRINT_H__ +#define __PRINT_H__ + +#include +#include "debug.h" + +void print_init(void); + +void info(int, const char *, ...); +int warn(const char *, ...); +int error(const char *, ...); + +void print_xfer(const char *, char, unsigned int); + +#ifdef DEBUG +void fprint_hex(void *, unsigned int, FILE *); +#endif + +#endif +// vim: ts=3 sw=3 diff --git a/common/rdp2tcp.h b/common/rdp2tcp.h new file mode 100644 index 0000000..88ea88e --- /dev/null +++ b/common/rdp2tcp.h @@ -0,0 +1,99 @@ +/** + * @file rdp2tcp.h + * rdp2tcp protocol specification + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __RDP2TCP_H__ +#define __RDP2TCP_H__ + +#include "compiler.h" + +#define RDP2TCP_MAX_MSGLEN (512*1024) +/** + * default TS virtual channel name + */ +#define RDP2TCP_CHAN_NAME "rdp2tcp" +#define RDP2TCP_PING_DELAY 5 // secs + +// rdp2tcp commands +#define R2TCMD_CONN 0x00 +#define R2TCMD_CLOSE 0x01 +#define R2TCMD_DATA 0x02 +#define R2TCMD_PING 0x03 +#define R2TCMD_BIND 0x04 +#define R2TCMD_RCONN 0x05 +#define R2TCMD_MAX 0x06 + +// address family on wire +#define TUNAF_ANY 0x00 +#define TUNAF_IPV4 0x01 +#define TUNAF_IPV6 0x02 + +// rdp2tcp error codes +#define R2TERR_SUCCESS 0x00 +#define R2TERR_GENERIC 0x01 +#define R2TERR_BADMSG 0x02 +#define R2TERR_CONNREFUSED 0x03 +#define R2TERR_FORBIDDEN 0x04 +#define R2TERR_NOTAVAIL 0x05 +#define R2TERR_RESOLVE 0x06 +#define R2TERR_NOTFOUND 0x07 +#define R2TERR_MAX 0x08 + +/** generic rdp2tcp message header */ +PACK(struct _r2tmsg { + unsigned char cmd; /**< R2TCMD_xxx */ + unsigned char id; /**< tunnel identifier */ +}); +typedef struct _r2tmsg r2tmsg_t; + +/** R2TCMD_CONN or R2TCMD_BIND message (client --> server) */ +PACK(struct _r2tmsg_connreq { + unsigned char cmd; /**< R2TCMD_CONN or R2TCMD_BIND */ + unsigned char id; /**< tunnel identifier */ + unsigned short port; /**< TCP port or 0 for process tunnel */ + unsigned char af; /**< address family */ + char hostname[0]; /**< tunnel remote hostname or command line */ +}); +typedef struct _r2tmsg_connreq r2tmsg_connreq_t; + +/** R2TCMD_CONN or R2TCMD_BIND message (server --> client) */ +PACK(struct _r2tmsg_connans { + unsigned char cmd; /**< R2TCMD_CONN or R2TCMD_BIND */ + unsigned char id; /**< tunnel identifier */ + unsigned char err; /**< error code */ + unsigned char af; /**< address family */ + unsigned short port; /**< TCP port or 0 for process tunnel */ + unsigned char addr[16]; /**< tunnel address */ +}); +typedef struct _r2tmsg_connans r2tmsg_connans_t; + +/** R2TCMD_RCONN message (server --> client) */ +PACK(struct _r2tmsg_rconnreq { + unsigned char cmd; /**< R2TCMD_RCONN */ + unsigned char id; /**< local tunnel identifier */ + unsigned char rid; /**< remote tunnel identifier */ + unsigned char af; /**< address family */ + unsigned short port; /**< TCP port */ + unsigned char addr[16]; /**< tunnel address */ +}); +typedef struct _r2tmsg_rconnreq r2tmsg_rconnreq_t; + +#endif diff --git a/server/Makefile.mingw32 b/server/Makefile.mingw32 new file mode 100644 index 0000000..a169c3a --- /dev/null +++ b/server/Makefile.mingw32 @@ -0,0 +1,31 @@ +BIN=rdp2tcp.exe +CC=i586-mingw32msvc-gcc +CFLAGS=-Wall -g \ + -D_WIN32_WINNT=0x0501 \ + -I../common + +# -D_WIN32_WINNT=0x0501 +# -D_WIN32_WINNT=0x0501 -DDEBUG + +LDFLAGS=-lwtsapi32 -lws2_32 +OBJS= ../common/iobuf.o \ + ../common/print.o \ + ../common/msgparser.o \ + ../common/nethelper.o \ + ../common/netaddr.o \ + errors.o aio.o events.o \ + tunnel.o channel.o process.o commands.o main.o + +all: clean_common $(BIN) + +clean_common: + $(MAKE) -C ../common clean + +$(BIN): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + rm -f $(OBJS) $(BIN) diff --git a/server/Makefile.nmake b/server/Makefile.nmake new file mode 100644 index 0000000..1376340 --- /dev/null +++ b/server/Makefile.nmake @@ -0,0 +1,23 @@ +BIN=rdp2tcp.exe +CFLAGS=/nologo /D_CRT_SECURE_NO_WARNINGS /W3 /I..\common +#CFLAGS=/nologo /DEBUG /D_CRT_SECURE_NO_WARNINGS /W3 /I..\common +LDFLAGS=/nologo +#LDFLAGS=/nologo /DEBUG + +LIBS= wtsapi32.lib ws2_32.lib +OBJS= ..\common\iobuf.obj \ + ..\common\print.obj \ + ..\common\msgparser.obj \ + ..\common\nethelper.obj \ + ..\common\netaddr.obj \ + errors.obj aio.obj events.obj \ + tunnel.obj channel.obj process.obj commands.obj main.obj + +all: $(BIN) + +$(BIN): $(OBJS) + link /out:$(BIN) $(LDFLAGS) *.obj $(LIBS) + +clean: + erase $(OBJS) $(BIN) + diff --git a/server/aio.c b/server/aio.c new file mode 100644 index 0000000..8aef285 --- /dev/null +++ b/server/aio.c @@ -0,0 +1,256 @@ +/** + * @file aio.c + * async I/O helpers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2twin.h" +#include "print.h" + +#ifdef DEBUG +extern int debug_level; +#endif + +/** + * initialize async I/O forwarding + * @param[out] rio pointer to allocated aio_t (reading) + * @param[out] wio pointer to allocated aio_t (writing) + * @return 0 on success + */ +int __aio_init_forward(aio_t *rio, aio_t *wio +#ifdef DEBUG + , const char *name +#endif + ) +{ + HANDLE evt1, evt2; + + assert(rio && wio); + + evt1 = CreateEvent(NULL, TRUE, TRUE, NULL); + if (!evt1) + return syserror("CreateEvent"); + + evt2 = CreateEvent(NULL, TRUE, TRUE, NULL); + if (!evt2) { + CloseHandle(evt1); + return syserror("CreateEvent"); + } + +#ifdef DEBUG + __iobuf_init2(&rio->buf, &wio->buf, name); +#else + __iobuf_init2(&rio->buf, &wio->buf); +#endif + + rio->io.hEvent = evt1; + wio->io.hEvent = evt2; + rio->min_io_size = 1024; + wio->min_io_size = 0; + + return 0; +} + +/** + * destroy async I/O forwarding + * @param[in,out] rio aio_t initialized with aio_init_forward (reading) + * @param[in,out] wio aio_t initialized with aio_init_forward (writing) + */ +void aio_kill_forward(aio_t *rio, aio_t *wio) +{ + assert(valid_aio(rio) && valid_aio(wio)); + + iobuf_kill2(&rio->buf, &wio->buf); + CloseHandle(rio->io.hEvent); + CloseHandle(wio->io.hEvent); +} + +/** + * async read from file descriptor to I/O buffer + * @param[in,out] rio aio_t structure associated with input buffer + * @param[in] fd file descriptor + * @param[in] name name of stream to be displayed on console + * @param[in] callback function called data are received + * @param[in] ctx context passed as argument to callback function + * @return -1 on error + */ +int aio_read( + aio_t *rio, + HANDLE fd, + const char *name, + aio_readcb_t callback, + void *ctx) +{ + iobuf_t *ibuf; + char *data; + DWORD len, r; + unsigned int avail, min_io_size; + + assert(valid_aio(rio) && name && *name && callback); + ibuf = &rio->buf; + min_io_size = rio->min_io_size; + len = 0; + + if (rio->pending) { + rio->pending = 0; + if (!GetOverlappedResult(fd, &rio->io, &len, FALSE)) { + ResetEvent(rio->io.hEvent); + return syserror("GetOverlappedResult"); + } + + if (!len) { + ResetEvent(rio->io.hEvent); + return error("fd closed"); + } + + if (len == min_io_size) { // increase I/O chunks size (perfs..) + min_io_size <<= 1; + if (min_io_size > NETBUF_MAX_SIZE) min_io_size = NETBUF_MAX_SIZE; + rio->min_io_size = min_io_size; + } + + print_xfer(name, 'r', (unsigned int) len); + iobuf_commit(ibuf, len); + if (callback(ibuf, ctx) < 0) { + ResetEvent(rio->io.hEvent); + return -1; + } + } + + data = iobuf_reserve(ibuf, min_io_size, &avail); + if (!data) { + ResetEvent(rio->io.hEvent); + return error("failed to allocate %s buffer", name); + } + + r = 0; + if (ReadFile(fd, data, (DWORD)avail, &r, &rio->io)) { + + trace_chan("%i/%i overlap=%u", r, avail, len); + if (r == 0) { + ResetEvent(rio->io.hEvent); + return error("fd closed"); + } + + if (r == min_io_size) { // increase I/O chunks size (perfs..) + min_io_size <<= 1; + if (min_io_size > NETBUF_MAX_SIZE) min_io_size = NETBUF_MAX_SIZE; + rio->min_io_size = min_io_size; + } + + print_xfer(name, 'r', r); + iobuf_commit(ibuf, (unsigned int)r); + if (callback(ibuf, ctx) < 0) { + ResetEvent(rio->io.hEvent); + return -1; + } + + } else { + + switch (GetLastError()) { + + case ERROR_IO_PENDING: + rio->pending = 1; + break; + + case ERROR_BROKEN_PIPE: + info(0, "child process has closed pipe"); + break; + + default: + ResetEvent(rio->io.hEvent); + return syserror("failed to read"); + } + } + + return 0; +} + +/** + * async write from I/O buffer to file descriptor + * @param[in,out] wio aio_t structure associated with output buffer + * @param[in] fd file descriptor + * @param[in] name name of stream to be displayed on console + * @return -1 on error + */ +int aio_write(aio_t *wio, HANDLE fd, const char *name) +{ + iobuf_t *obuf; + DWORD len, w; + + assert(valid_aio(wio) && name && *name); + obuf = &wio->buf; + + if (wio->pending) { + wio->pending = 0; + len = 0; + if (!GetOverlappedResult(fd, &wio->io, &len, FALSE)) { + ResetEvent(wio->io.hEvent); + return syserror("GetOverlappedResult"); + } + iobuf_consume(obuf, (unsigned int)len); + print_xfer(name, 'w', len); + } + + len = (DWORD) iobuf_datalen(obuf); + if (len == 0) { + ResetEvent(wio->io.hEvent); + return 0; + } + +#ifdef DEBUG + if (debug_level > 0) iobuf_dump(obuf); +#endif + + w = 0; + if (WriteFile(fd, iobuf_dataptr(obuf), len, &w, &wio->io)) { + + if (w == 0) { + ResetEvent(wio->io.hEvent); + return error("fd closed"); + } + + iobuf_consume(obuf, w); + print_xfer(name, 'w', w); + + } else { + + switch (GetLastError()) { + + case ERROR_IO_PENDING: + wio->pending = 1; + break; + + case ERROR_BROKEN_PIPE: + info(0, "child process has closed pipe"); + break; + + case ERROR_INVALID_FUNCTION: + ResetEvent(wio->io.hEvent); + return error("not running within a TS session"); + + default: + ResetEvent(wio->io.hEvent); + return syserror("failed to write"); + } + } + + return 0; +} + diff --git a/server/channel.c b/server/channel.c new file mode 100644 index 0000000..93e4750 --- /dev/null +++ b/server/channel.c @@ -0,0 +1,209 @@ +/** + * @file channel.c + * TS virtual channel management + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "print.h" +#include "r2twin.h" +#include "rdp2tcp.h" +#include "msgparser.h" +#include "wtsapi32.h" + +#ifndef CHANNEL_CHUNK_LENGTH +/** minimal chunk size supported by TS virtual channel */ +#define CHANNEL_CHUNK_LENGTH 1600 +#endif + +#ifdef DEBUG +extern int debug_level; +#endif + +static vchannel_t vc; + +/** + * check whether channel is connected + */ +int channel_is_connected(void) +{ + return vc.connected; +} + +/** + * initialize the TS virtual channel associated with rdp2tcp session + * @param[in] name virtual channel name + * @return 0 on success + * @note only 1 virtual channel per server instance + */ +int channel_init(const char *name) +{ + HANDLE ts, *hbuf; + DWORD buflen = 0; + + trace_chan("%s", name); + memset(&vc, 0, sizeof(vc)); + + ts = WTSVirtualChannelOpen( + WTS_CURRENT_SERVER_HANDLE, + WTS_CURRENT_SESSION, + (LPSTR) name); + if (!ts) + return syserror("WTSVirtualChannelOpen"); + + hbuf = NULL; + buflen = sizeof(HANDLE *); + if (!WTSVirtualChannelQuery(ts, WTSVirtualFileHandle, (void **)&hbuf, &buflen)) { + syserror("WTSVirtualChannelQuery"); + WTSVirtualChannelClose(ts); + return -1; + } + + vc.ts = ts; + vc.chan = *hbuf; + WTSFreeMemory(hbuf); + + if (aio_init_forward(&vc.rio, &vc.wio, "chan")) { + CloseHandle(vc.chan); + WTSVirtualChannelClose(vc.ts); + return -1; + } + + events_init(vc.wio.io.hEvent, vc.rio.io.hEvent); + + return 0; +} + +/** + * destroy TS virtual channel associated with rdp2tcp session + */ +void channel_kill(void) +{ + trace_chan(""); + CancelIo(vc.chan); + aio_kill_forward(&vc.rio, &vc.wio); + CloseHandle(vc.chan); + // TODO why does it throw invalid handle exception ? + WTSVirtualChannelClose(vc.ts); +} + +static int on_read_completed(iobuf_t *ibuf, void *bla) +{ + return commands_parse(ibuf); +} + +/** + * handle TS virtual channel read-event + * @return 0 on success + */ +int channel_read_event(void) +{ + trace_chan("pending=%i", vc.rio.pending); + return aio_read(&vc.rio, vc.chan, "chan", on_read_completed, NULL); +} + +/** + * check whether a async I/O write is pending + */ +int channel_write_pending(void) +{ + //trace_chan("pending=%i", (int)vc.wio.pending); + return vc.wio.pending; +} + +/** + * process TS virtual channel write-event + * @return 0 on success + */ +int channel_write_event(void) +{ + int ret; + + ret = aio_write(&vc.wio, vc.chan, "chan"); + trace_chan("pending=%i, outavail=%u, connected=%i, ret=%i", + vc.wio.pending, iobuf_datalen(&vc.wio.buf), vc.connected, ret); + + + if ((ret >= 0) ^ !!vc.connected) { + info(0, "channel %sconnected", vc.connected?"dis":""); + vc.connected ^= 1; + } + + return 0; +} + +/** + * send a message through TS virtual channel + * @param[in] cmd rdp2tcp command (R2TCMD_xxx) + * @param[in] tun_id rdp2tcp tunnel ID + * @param[in] data data to write + * @param[in] data_len size of buffer + * @return 0 on success + */ +int channel_write( + unsigned char cmd, + unsigned char tun_id, + const void *data, + unsigned int data_len) +{ + unsigned char *ptr; + unsigned int used; + + trace_chan("cmd=%02x id=%02x len=%u", cmd, tun_id, data_len); + used = iobuf_datalen(&vc.wio.buf); + + ptr = iobuf_reserve(&vc.wio.buf, data_len+6, NULL); + if (!ptr) + return error("failed to append %u bytes to channel buffer", data_len+6); + *((unsigned int *)ptr) = htonl(data_len+2); + + ptr[4] = cmd; + ptr[5] = tun_id; + memcpy(ptr+6, data, data_len); + iobuf_commit(&vc.wio.buf, data_len+6); + + if (used > 0) + return 0; + + return channel_write_event(); +} + +/** + * forward tunnel input buffer to virtual channel + * @param[in] tun tunnel + * @return -1 on error + */ +int channel_forward(tunnel_t *tun) +{ + iobuf_t *ibuf; + unsigned int len; + int ret; + + ibuf = &tun->rio.buf; + len = iobuf_datalen(ibuf); + ret = 0; + + if (len > 0) { + ret = channel_write(R2TCMD_DATA, tun->id, iobuf_dataptr(ibuf), len); + if (ret >= 0) + iobuf_consume(ibuf, len); + } + + return ret; +} + diff --git a/server/commands.c b/server/commands.c new file mode 100644 index 0000000..92bbc3d --- /dev/null +++ b/server/commands.c @@ -0,0 +1,111 @@ +/** + * @file commands.c + * rdp2tcp commands handling + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "rdp2tcp.h" +#include "r2twin.h" +#include "msgparser.h" + +static int protoerror(unsigned char tid, unsigned char err, const char *errstr) +{ + channel_write(R2TCMD_CONN, tid, &err, 1); + return error("protocol error (%s)", errstr); +} + +static int start_tcp_tunnel( + const r2tmsg_connreq_t *msg, + unsigned int len, + int bind_tunnel) +{ + static const int r2taf_to_sysaf[3] = { AF_UNSPEC, AF_INET, AF_INET6 }; + + if (len < 7) + return protoerror(msg->id, R2TERR_BADMSG, "command too small"); + + if (tunnel_lookup(msg->id)) + return error("tunnel 0x%02x is already used", msg->id); + + if (msg->af > TUNAF_IPV6) + return protoerror(msg->id, R2TERR_BADMSG, "invalid address family"); + + if (msg->hostname[len-6]) + return protoerror(msg->id, R2TERR_BADMSG, "invalid hostname"); + + tunnel_create(msg->id, r2taf_to_sysaf[msg->af], + msg->hostname, ntohs(msg->port), bind_tunnel); + + return 0; +} + +static int cmd_conn(const r2tmsg_connreq_t *msg, unsigned int len) +{ + trace_chan("len=%u, tid=0x%02x, af=0x%02x, port=0x%04x", + len, msg->id, msg->af, msg->port); + + return start_tcp_tunnel(msg, len, 0); +} + +static int cmd_bind(const r2tmsg_connreq_t *msg, unsigned int len) +{ + trace_chan("len=%u, tid=0x%02x, af=0x%02x, port=0x%04x", + len, msg->id, msg->af, msg->port); + + return start_tcp_tunnel(msg, len, 1); +} + +static int cmd_close(const r2tmsg_t *msg, unsigned int len) +{ + tunnel_t *tun; + + trace_chan("len=%u, tid=0x%02x", len, msg->id); + tun = tunnel_lookup(msg->id); + if (!tun) { + error("invalid tunnel id 0x%02x", msg->id); + return 0; + } + + tunnel_close(tun); + return 0; +} + +static int cmd_data(const r2tmsg_t *msg, unsigned int len) +{ + tunnel_t *tun; + + trace_chan("len=%u, id=0x%02x", len, msg->id); + tun = tunnel_lookup(msg->id); + if (!tun) { + error("invalid tunnel id 0x%02x", msg->id); + return 0; + } + + return tunnel_write(tun, ((const char *)msg)+2, len-2); +} + +const cmdhandler_t cmd_handlers[R2TCMD_MAX] = { + (cmdhandler_t) cmd_conn, /* R2TCMD_CONN */ + (cmdhandler_t) cmd_close, /* R2TCMD_CLOSE */ + (cmdhandler_t) cmd_data, /* R2TCMD_DATA */ + NULL, + (cmdhandler_t) cmd_bind, /* R2TCMD_BIND */ + NULL +}; + diff --git a/server/errors.c b/server/errors.c new file mode 100644 index 0000000..59ac937 --- /dev/null +++ b/server/errors.c @@ -0,0 +1,62 @@ +/** + * @file errors.c + * windows error printing + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2twin.h" + +#include +#include + + +static int do_error(const char *func, DWORD err) +{ + char *buffer; + + buffer = NULL; + if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_ALLOCATE_BUFFER + |FORMAT_MESSAGE_MAX_WIDTH_MASK + |FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&buffer, 0, NULL) > 0) { + if (buffer) { + error("%s (%lu: %s)\n", func, err, buffer); + LocalFree(buffer); + return -1; + } + } + + return error("%s (%lu)\n", func, err); +} + +/** print winsock-level error */ +int wsaerror(const char *func) +{ + return do_error(func, WSAGetLastError()); +} + +/** print win32-level error */ +int syserror(const char *func) +{ + return do_error(func, GetLastError()); +} + diff --git a/server/events.c b/server/events.c new file mode 100644 index 0000000..105ac82 --- /dev/null +++ b/server/events.c @@ -0,0 +1,160 @@ +/** + * @file events.c + * async loop helpers + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2twin.h" +#include "rdp2tcp.h" + +extern struct list_head all_tunnels; + +static unsigned int events_count = 0; +static HANDLE all_events[0x102] = {0, }; +static unsigned char evtid_to_tunid[0x102] = {0, }; + +/** initialize the TS events loop + * @param[in] wevt TS virtual channel write-event + * @param[in] revt TS virtual channel read-event */ +void events_init(HANDLE wevt, HANDLE revt) +{ + trace_evt("wevt=%x, revt=%x", wevt, revt); + all_events[0] = wevt; + all_events[1] = revt; + events_count = 2; +} + +/** register a network tunnel event + * @param[in] evt TS virtual channel socket event + * @param[in] id rdp2tcp tunnel ID + * @return 0 on success */ +int event_add_tunnel(HANDLE evt, unsigned char id) +{ + unsigned int i; + + trace_evt("evt=%x, id=0x%02x", evt, id); + + i = events_count; + if (i >= 0x101) + return -1; + + all_events[i] = evt; + evtid_to_tunid[i] = id; + + ++events_count; + + return 0; +} + +/** register a process tunnel event + * @param[in] proc child process handle + * @param[in] re child process read event + * @param[in] we child process write event + * @param[in] id rdp2tcp tunnel ID + * @return 0 on success */ +int event_add_process(HANDLE proc, HANDLE re, HANDLE we, unsigned char id) +{ + unsigned int i; + + trace_evt("proc=%x, revt=%x, wevt=%x, id=%u", proc, re, we, id); + + i = events_count; + if (i+2 >= 0x101) + return -1; + + all_events[i] = proc; + all_events[i+1] = re; // read overlapped event + all_events[i+2] = we; // write overlapped event + + evtid_to_tunid[i] = id; + evtid_to_tunid[i+1] = id; + evtid_to_tunid[i+2] = id; + + events_count += 3; + + return 0; +} + +/** remove events associated with a rdp2tcp tunnel + * @param[in] id rdp2tcp tunnel ID */ +void event_del_tunnel(unsigned char id) +{ + unsigned int i, j; + + trace_evt("id=0x%02x", id); + + for (i=2, j=0; i 0) { + if (i < events_count) { + memmove(&evtid_to_tunid[i-j], &evtid_to_tunid[i], + sizeof(evtid_to_tunid[0]) * (events_count-i)); + memmove(&all_events[i-j], &all_events[i], + sizeof(all_events[0]) * (events_count-i)); + } + events_count -= j; + } +} + +/** wait for tunnel events + * @param[out] out_tun tunnel associated with last event + * @param[out] out_h last event handle + * @return the last event type (EVT_xxx) or -1 on error */ +int event_wait(tunnel_t **out_tun, HANDLE *out_h) +{ + DWORD ret, off; + tunnel_t *tun; + + off = (channel_write_pending() ? 0 : 1); + + ret = WaitForMultipleObjects(events_count-off, &all_events[off], FALSE, + RDP2TCP_PING_DELAY*1000); + + if (ret == WAIT_FAILED) { + assert(GetLastError() != ERROR_INVALID_HANDLE); + return syserror("WaitForMultipleObjects"); + } + + if (ret == WAIT_TIMEOUT) + return EVT_PING; + + ret -= WAIT_OBJECT_0; + trace_evt("off=%i --> 0x%x (evt=0x%x)", off, ret, all_events[off+ret]); + + if (ret == 0) + return (off == 0 ? EVT_CHAN_WRITE : EVT_CHAN_READ); + + if ((ret == 1) && (off == 0)) + return EVT_CHAN_READ; + + tun = tunnel_lookup(evtid_to_tunid[off+ret]); + if (!tun) + return error("invalid tunnel event 0x%02x", evtid_to_tunid[off+ret]); + + *out_tun = tun; + *out_h = all_events[off+ret]; + return EVT_TUNNEL; +} + + diff --git a/server/main.c b/server/main.c new file mode 100644 index 0000000..1ed7648 --- /dev/null +++ b/server/main.c @@ -0,0 +1,156 @@ +/** + * @file main.c + * main loop + * @mainpage main server loop + * @section sec_ts virtual channel + * @li channel.c + * @section sec_tun rdp2tcp tunnels + * @li tunnel.c + * @li process.c + * @section sec_aio async helpers + * @li events.c + * @li aio.c + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "print.h" +#include "rdp2tcp.h" +#include "r2twin.h" + +#include +#include + +void bye(void) +{ + channel_kill(); + tunnels_kill(); + net_exit(); + exit(0); +} + +static BOOL WINAPI on_signal(DWORD sig) +{ + switch (sig) { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + bye(); + return TRUE; + } + + return FALSE; +} + +static void setup(void) +{ + print_init(); + net_init(); + SetConsoleCtrlHandler(on_signal, TRUE); +} + +static void usage(char *n) +{ + fprintf(stderr, "usage: %s [vname]\n", n); + exit(0); +} + +static time_t last_ping = 0; + +static int ping(time_t *now) +{ + + time(now); + if (!last_ping || (last_ping + RDP2TCP_PING_DELAY - 1 < *now)) { + last_ping = *now; + return channel_write(R2TCMD_PING, 0, NULL, 0); + } + + return 0; +} + +int main(int argc, char **argv) +{ + int ret; + const char *chan_name; + tunnel_t *tun; + HANDLE h; + time_t now; + + if (argc > 2) + usage(argv[0]); + + chan_name = (argc == 2 ? argv[1] : RDP2TCP_CHAN_NAME); + + setup(); + + do { + if (channel_init(chan_name)) + break; + + ret = ping(&now); + + // I/O loop + while (ret >= 0) { + + switch (event_wait(&tun, &h)) { + + case EVT_CHAN_WRITE: // virtual channel outgoing data + debug(0, "EVT_CHAN_WRITE"); + ret = channel_write_event(); + if (!ret) + last_ping = now; + break; + + case EVT_CHAN_READ: // virtual channel incoming data + debug(0, "EVT_CHAN_READ"); + ret = channel_read_event(); + if (ret >= 0) + ping(&now); + break; + + case EVT_TUNNEL: // tcp tunnel incoming/outgoing data + debug(0, "EVT_TUNNEL"); + ret = tunnel_event(tun, h); + break; + + case EVT_PING: // ping delay + if (channel_is_connected()) { + debug(0, "EVT_PING"); + ret = ping(&now); + } else { + debug(0, "channel still disconnected"); + } + break; + + default: + ret = -1; + + } + + } + + channel_kill(); + Sleep(1000); + + } while (1); + + bye(); + return 0; +} + diff --git a/server/process.c b/server/process.c new file mode 100644 index 0000000..55ea3d1 --- /dev/null +++ b/server/process.c @@ -0,0 +1,226 @@ +/** + * @file process.c + * process std-forwarding tunnel + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "r2twin.h" +#include "print.h" +#include "rdp2tcp.h" + +#include + +/** default stdin/stdout pipes name prefix */ +#define PIPE_NAME "r2tcmd" + +static void pipe_close(HANDLE *pfd) +{ + CloseHandle(pfd[0]); + CloseHandle(pfd[1]); +} + +static int pipe_create(HANDLE *pfd, int parent_fd) +{ + HANDLE fd; + SECURITY_ATTRIBUTES sattr; + char name[128]; + + memset(&sattr, 0, sizeof(sattr)); + sattr.nLength = sizeof(sattr); + sattr.bInheritHandle = TRUE; + + snprintf(name, sizeof(name)-1, "\\\\.\\pipe\\" PIPE_NAME "-%lu-%i", + GetCurrentProcessId(), rand()); + + fd = CreateNamedPipeA(name, + PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE|PIPE_WAIT, 2, NETBUF_MAX_SIZE/2, NETBUF_MAX_SIZE/2, + 5000 /*msec*/, &sattr); + if (fd == INVALID_HANDLE_VALUE) + return syserror("CreateNamedPipe"); + + pfd[0] = fd; + + fd = CreateFileA(name, GENERIC_WRITE, 0, &sattr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd == INVALID_HANDLE_VALUE) { + syserror("CreateFile"); + CloseHandle(pfd[0]); + return -1; + } + + pfd[1] = fd; + + if (!SetHandleInformation(pfd[parent_fd], HANDLE_FLAG_INHERIT, 0)) { + syserror("SetHandleInformation"); + pipe_close(pfd); + return -1; + } + + return 0; +} + +static int start_child( + const char *cmd, + HANDLE *out_std, + PROCESS_INFORMATION *pi, + unsigned char *err) +{ + BOOL res; + HANDLE stderr_child, pstdin[2], pstdout[2]; + STARTUPINFOA si; + + trace_proc("%s", cmd); + *err = R2TERR_GENERIC; + + // stdin pipe + if (pipe_create(pstdin, 1)) + return -1; + + // stdout pipe + if (pipe_create(pstdout, 0)) { + pipe_close(pstdin); + return -1; + } + + // stderr pipe + stderr_child = INVALID_HANDLE_VALUE; + if (DuplicateHandle(GetCurrentProcess(), pstdout[1], + GetCurrentProcess(), &stderr_child, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + + memset(pi, 0, sizeof(*pi)); + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = pstdin[0]; + si.hStdOutput = pstdout[1]; + si.hStdError = stderr_child; + + res = CreateProcessA(NULL, (char*)cmd, NULL, NULL, TRUE, 0, + NULL, NULL, &si, pi); + if (res) { + CloseHandle(stderr_child); + CloseHandle(pi->hThread); + CloseHandle(pstdin[0]); + CloseHandle(pstdout[1]); + out_std[0] = pstdout[0]; + out_std[1] = pstdin[1]; + return 0; + } + + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + *err = R2TERR_NOTFOUND; + break; + + default: + syserror("CreateProcess"); + } + + CloseHandle(stderr_child); + + } else { + syserror("DuplicateHandle"); + } + + pipe_close(pstdout); + pipe_close(pstdin); + + return -1; +} + +/** + * spawn a child process and attach stdin/stdout/stderr to tunnel + * @param[in] tun the new tunnel + * @param[in] cmd command line to execute + * @return 0 on success + */ +int process_start(tunnel_t *tun, const char *cmd) +{ + int ret; + unsigned int ans_len; + unsigned long pid; + HANDLE pstd[2]; + PROCESS_INFORMATION pi; + r2tmsg_connans_t ans; + + trace_proc("tid=0x%02x cmd=%s", tun->id, cmd); + + memset(&ans, 0, sizeof(ans)); + ans_len = 1; + + ret = start_child(cmd, pstd, &pi, &ans.err); + if (!ret) { + + if (!aio_init_forward(&tun->rio, &tun->wio, "proc")) { + + if (!event_add_process(pi.hProcess, tun->rio.io.hEvent, + tun->wio.io.hEvent, tun->id)) { + + tun->rfd = pstd[0]; + tun->wfd = pstd[1]; + tun->proc = pi.hProcess; + + info(0, "started process %s with pid %u for tunnel 0x%02x", + cmd, (unsigned int) pi.dwProcessId, tun->id); + + ans.err = R2TERR_SUCCESS; + ans.af = TUNAF_ANY; + pid = htonl(pi.dwProcessId); + memcpy(&ans.addr[0], &pid, 4); + ans_len = 8; + + } else { + aio_kill_forward(&tun->rio, &tun->wio); + } + } + } + + if ((channel_write(R2TCMD_CONN, tun->id, &ans.err, ans_len) >= 0) + && (ans.err == R2TERR_SUCCESS)) { + tun->connected = 1; + return 0; + } + + if (!ret) { + event_del_tunnel(tun->id); + TerminateProcess(pi.hProcess, 0); + CloseHandle(pi.hProcess); + CloseHandle(pstd[0]); + CloseHandle(pstd[1]); + } + + return error("failed to start process %s for tunnel 0x%02x", cmd, tun->id); +} + +/** + * stop a tunnel associated with a process + * @param[in] tun the process tunnel + */ +void process_stop(tunnel_t *tun) +{ + TerminateProcess(tun->proc, 0); + CloseHandle(tun->proc); + CloseHandle(tun->rfd); + CloseHandle(tun->wfd); + aio_kill_forward(&tun->rio, &tun->wio); +} + diff --git a/server/r2twin.h b/server/r2twin.h new file mode 100644 index 0000000..f00fa71 --- /dev/null +++ b/server/r2twin.h @@ -0,0 +1,119 @@ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __R2TWIN_H__ +#define __R2TWIN_H__ + +#include "compiler.h" +#include "debug.h" +#include "print.h" +#include "list.h" +#include "iobuf.h" +#include "nethelper.h" + +/** async I/O instance */ +typedef struct _aio { + iobuf_t buf; /**< I/O buffer */ + unsigned int min_io_size; /**< minimal I/O buffer size */ + int pending; /**< 1 if an I/O is pending */ + OVERLAPPED io; /**< async event */ +} aio_t; + +/** TS virtual channel */ +typedef struct _vchannel { + HANDLE ts; /**< RDP channel handle */ + HANDLE chan; /**< RDP channel I/O handle */ + int connected:1; /**< 1 if channel is conneced */ + aio_t rio; /**< input aio_t */ + aio_t wio; /**< output aio_t */ +} vchannel_t; + +/** rdp2tcp tunnel */ +typedef struct _tunnel { + struct list_head list; /**< double-linked list */ + sock_t sock; /**< tunnel socket */ + unsigned char connected; /**< 1 if tunnel is connected */ + unsigned char server; /**< 1 for reverse-connect tunnel */ + unsigned char id; /**< tunnel identifier */ + HANDLE proc; /**< child process HANDLE */ + HANDLE rfd; /**< child process stdout/stderr HANDLE */ + HANDLE wfd; /**< child process stdin HANDLE */ + aio_t rio; /**< input aio_t */ + aio_t wio; /**< output aio_t */ + netaddr_t addr; /**< network address */ +} tunnel_t; + +/* aio.c ***/ +#define valid_aio(aio) ((aio) && valid_iobuf(&(aio)->buf) && (aio)->io.hEvent) +#ifdef DEBUG +int __aio_init_forward(aio_t *, aio_t *, const char *); +#define aio_init_forward(rio, wio, name) __aio_init_forward(rio, wio, name) +#else +int __aio_init_forward(aio_t *, aio_t *); +#define aio_init_forward(rio, wio, name) __aio_init_forward(rio, wio) +#endif + +void aio_kill_forward(aio_t *, aio_t *); + +typedef int (*aio_readcb_t)(iobuf_t *, void *); +int aio_read(aio_t *, HANDLE, const char *, aio_readcb_t, void *); +int aio_write(aio_t *, HANDLE, const char *); + +/* events ***/ +#define EVT_CHAN_WRITE 0 +#define EVT_CHAN_READ 1 +#define EVT_TUNNEL 2 +#define EVT_PING 3 + +void events_init(HANDLE, HANDLE); +int event_add_tunnel(HANDLE, unsigned char); +void event_del_tunnel(unsigned char); +int event_add_process(HANDLE, HANDLE, HANDLE, unsigned char); +int event_wait(tunnel_t **, HANDLE *); + +/* channel.c ***/ +int channel_init(const char *); +void channel_kill(void); +int channel_is_connected(void); +int channel_read_event(void); +int channel_write_event(void); +int channel_write_pending(void); +int channel_write(unsigned char, unsigned char, const void *, unsigned int); +int channel_forward(tunnel_t *); + +/* tunnel.c ***/ +#define valid_tunnel(tun) ((tun) && (tun)->list.next && (tun)->list.prev) +void tunnel_create(unsigned char, int, const char *, unsigned short, int); +tunnel_t *tunnel_lookup(unsigned char); +int tunnel_event(tunnel_t *, HANDLE); +int tunnel_write(tunnel_t *tun, const void *, unsigned int); +void tunnel_close(tunnel_t *); +void tunnels_kill(void); + +/* errors.c ***/ +int wsaerror(const char *); +int syserror(const char *); + +/* process.c ***/ +int process_start(tunnel_t *, const char *); +void process_stop(tunnel_t *); + +/* main.c ***/ +void bye(void); + +#endif diff --git a/server/tunnel.c b/server/tunnel.c new file mode 100644 index 0000000..e426164 --- /dev/null +++ b/server/tunnel.c @@ -0,0 +1,574 @@ +/** + * @file tunnel.c + * rdp2tcp tunnels management + */ +/* + * This file is part of rdp2tcp + * + * Copyright (C) 2010-2011, Nicolas Collignon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "rdp2tcp.h" +#include "r2twin.h" +#include "print.h" + +#include + +extern const char *r2t_errors[R2TERR_MAX]; + +/** global tunnels double-linked list */ +LIST_HEAD_INIT(all_tunnels); + +/** lookup rdp2tcp tunnel + * @param[in] id rdp2tcp tunnel ID + * @return NULL if tunnel is not found */ +tunnel_t *tunnel_lookup(unsigned char id) +{ + tunnel_t *tun; + + //trace_tun("id=0x%02x", id); + list_for_each(tun, &all_tunnels) { + if (tun->id == id) + return tun; + } + + return NULL; +} + +static unsigned char wsa_to_r2t_error(int err) +{ + switch (err) { + case WSAEACCES: return R2TERR_FORBIDDEN; + case WSAECONNREFUSED: return R2TERR_CONNREFUSED; + case WSAEADDRNOTAVAIL: return R2TERR_NOTAVAIL; + case WSAHOST_NOT_FOUND: return R2TERR_RESOLVE; + } + + return R2TERR_GENERIC; +} + +/** + * generate a unused tunnel ID + * @return 0xff on error (all tunnel ID are used) + // in most cases tunnel IDs are generated by the client + // this is the single case where it is generated by the server + */ +static unsigned char tunnel_generate_id(void) +{ + int ok; + unsigned char tid; + static unsigned char last_tid = 0xff; + + ok = 1; + for (tid=last_tid+1; tid!=last_tid; ++tid) { + if (!tunnel_lookup(tid)) { + last_tid = tid; + return tid; + } + } + + return 0xff; +} + +static unsigned int netaddr_to_connans( + const netaddr_t *addr, + r2tmsg_connans_t *msg) +{ + unsigned int msg_len; + + memset(msg, 0, sizeof(*msg)); + msg->err = R2TERR_SUCCESS; + + if (netaddr_af(addr) == AF_INET) { + msg->af = TUNAF_IPV4; + msg->port = addr->ip4.sin_port; + memcpy(&msg->addr, &addr->ip4.sin_addr, 4); + msg_len = 8; + } else { + msg->af = TUNAF_IPV6; + msg->port = addr->ip6.sin6_port; + memcpy(&msg->addr, &addr->ip6.sin6_addr, 16); + msg_len = 20; + } + + return msg_len; +} + +static int tunnel_socksend_event(tunnel_t *tun) +{ + int ret; + unsigned int w; + + assert(valid_tunnel(tun)); + trace_tun("id=0x%02x, used=%u", tun->id, iobuf_datalen(&tun->wio.buf)); + + ret = net_write(&tun->sock, &tun->wio.buf, NULL, 0, &w); + if (ret < 0) + return error("%s", net_error(NETERR_SEND, ret)); + + if (w > 0) + print_xfer("tcp", 'w', w); + + return 0; +} + +static int tunnel_connect_event(tunnel_t *tun, int err) +{ + unsigned int ans_len; + r2tmsg_connans_t ans; + char host[NETADDRSTR_MAXSIZE]; + + trace_tun("id=0x%02x, err=%i", tun->id, err); + + memset(&ans, 0, sizeof(ans)); + ans.err = R2TERR_GENERIC; + ans_len = 1; + + if (!err) { + tun->connected = 1; + info(0, "tunnel 0x%02x connected to %s", tun->id, + netaddr_print(&tun->addr, host)); + + if (!net_update_watch(&tun->sock, &tun->wio.buf)) { + if (iobuf_datalen(&tun->wio.buf) && (tunnel_socksend_event(tun) < 0)) + err = 1; + if (!err) + ans_len = netaddr_to_connans(&tun->addr, &ans); + } + + } else { + ans.err = wsa_to_r2t_error(err); + } + + if (ans.err != R2TERR_SUCCESS) { + error("failed to connect tunnel 0x%02x (%i %s)", tun->id, + err, r2t_errors[ans.err]); + } + + if (channel_write(R2TCMD_CONN, tun->id, &ans.err, ans_len) >= 0) { + if (ans.err == R2TERR_SUCCESS) + return 0; + } + + return -1; +} + + +static int host_connect( + tunnel_t *tun, + int pref_af, + const char *host, + unsigned short port) +{ + int ret, err; + unsigned char msg; + + ret = net_client(pref_af, host, port, &tun->sock, &tun->addr, &err); + debug(0, "net_client(%s, %hu) -> %i / %i", host, port, ret, err); + + if (ret >= 0) { + info(0, "connect%s to %s:%hu", (ret > 0 ? "ing" : "ed"), + host, port); + + if (!event_add_tunnel(tun->sock.evt, tun->id)) { + iobuf_init2(&tun->rio.buf, &tun->wio.buf, "tcp"); + if (!ret) { + ret = tunnel_connect_event(tun, 0); + } else { + //WSAEventSelect(tun->sock.fd, tun->sock.evt, FD_CONNECT|FD_CLOSE); + } + return ret; + } + } + + msg = wsa_to_r2t_error(err); + channel_write(R2TCMD_CONN, tun->id, &msg, 1); + if (ret >= 0) + net_close(&tun->sock); + + return -1; +} + +static int host_bind( + tunnel_t *tun, + int pref_af, + const char *host, + unsigned short port) +{ + int ret, err; + unsigned int ans_len; + r2tmsg_connans_t ans; + + memset(&ans, 0, sizeof(ans)); + ans_len = 1; + + ret = net_server(pref_af, host, port, &tun->sock, &tun->addr, &err); + debug(0, "bind %s:%hu ... %i/%i", host, port, ret, err); + if (!ret) { + info(0, "listening on %s:%hu", host, port); + ans_len = netaddr_to_connans(&tun->addr, &ans); + ans.err = 0; + if (event_add_tunnel(tun->sock.evt, tun->id)) { + ans.err = R2TERR_GENERIC; + net_close(&tun->sock); + ret = -1; + } + + } else { + ans.err = wsa_to_r2t_error(err); + error("failed to bind %s:%hu (%i %s)", host, port, err, r2t_errors[ans.err]); + } + + if (channel_write(R2TCMD_BIND, tun->id, &ans.err, ans_len) >= 0) { + if (!ans.err) { + tun->connected = 1; + tun->server = 1; + return 0; + } + } + + if (!ret) { + event_del_tunnel(tun->id); + net_close(&tun->sock); + } + + return -1; +} + + +static tunnel_t *tunnel_alloc(unsigned char id) +{ + tunnel_t *tun; + + tun = calloc(1, sizeof(*tun)); + if (tun) { + tun->id = id; + } else { + error("failed to allocate tunnel"); + } + + return tun; +} + +/** + * create rdp2tcp tunnel + * @param[in] id rdp2tcp tunnel ID + * @param[in] pref_af preferred address family + * @param[in] host tunnel hostname or command line + * @param[in] port tcp tunnel port or 0 for process tunnel + * @param[in] bind_socket 1 for reverse connect tunnel + */ +void tunnel_create( + unsigned char id, + int pref_af, + const char *host, + unsigned short port, + int bind_socket) +{ + tunnel_t *tun; + int ret; + + assert(host && *host); + trace_tun("id=0x%02x, pref_af=%i, host=%s, port=%hu", id, pref_af, host, port); + + tun = tunnel_alloc(id); + if (!tun) + return; + + if (port > 0) { + // tcp tunnel + if (!bind_socket) + ret = host_connect(tun, pref_af, host, port); + else + ret = host_bind(tun, pref_af, host, port); + } else { + // process stdin/out tunnel + ret = process_start(tun, host); + } + + if (ret >= 0) { + list_add_tail(&tun->list, &all_tunnels); + debug(0, "tunnel 0x%02x created", id); + + } else { + debug(0, "failed to create tunnel 0x%02x", id); + free(tun); + } +} + +/** close rdp2tcp tunnel + * @param[in] tun established tunnel */ +void tunnel_close(tunnel_t *tun) +{ + assert(valid_tunnel(tun)); + trace_tun("id=0x%02x", tun->id); + + list_del(&tun->list); + + event_del_tunnel(tun->id); + + if (!tun->proc) { + if (!tun->server) + iobuf_kill2(&tun->rio.buf, &tun->wio.buf); + net_close(&tun->sock); + + } else { + CancelIo(tun->rfd); + CancelIo(tun->wfd); + process_stop(tun); + } + + free(tun); +} + +static int tunnel_sockrecv_event(tunnel_t *tun) +{ + int ret; + unsigned int r; + + assert(valid_tunnel(tun)); + + ret = net_read(&tun->sock, &tun->rio.buf, 0, &tun->rio.min_io_size, &r); + trace_tun("id=0x%02x --> ret=%i, r=%u", tun->id, ret, r); + if (ret < 0) + return error("%s", net_error(NETERR_RECV, ret)); + + if (r > 0) { + print_xfer("tcp", 'r', r); + if (channel_forward(tun) < 0) + return error("failed to forward"); + + // if (net_update_watch(&tun->sock, &tun->wio.buf)) + // return wsaerror("WSAEventSelect"); + } + + return 0; +} + +static int on_read_completed(iobuf_t *ibuf, tunnel_t *tun) +{ + assert(valid_iobuf(ibuf) && valid_tunnel(tun)); + return channel_forward(tun); +} + +static int tunnel_fdread_event(tunnel_t *tun) +{ + assert(valid_tunnel(tun)); + return aio_read(&tun->rio, tun->rfd, "tun", + (aio_readcb_t)on_read_completed, tun); +} + +static int tunnel_fdwrite_event(tunnel_t *tun) +{ + assert(valid_tunnel(tun)); + return aio_write(&tun->wio, tun->wfd, "tun"); +} + +static int tunnel_accept_event(tunnel_t *tun) +{ + tunnel_t *cli; + sock_t cli_sock; + int ret; + unsigned char tid; + unsigned int msg_len; + netaddr_t addr; + r2tmsg_rconnreq_t msg; + char host[NETADDRSTR_MAXSIZE]; + + assert(valid_tunnel(tun)); + trace_tun("id=0x%02x", tun->id); + + ret = net_accept(&tun->sock, &cli_sock, &addr); + if (ret) + return wsaerror("accept"); + + tid = tunnel_generate_id(); + if (tid == 0xff) { + error("failed to generate tunnel identifier"); + net_close(&cli_sock); + return 0; + } + trace_tun("srvid=0x%02x cliid=0x%02x", tun->id, tid); + + info(0, "accepted %s on tunnel 0x%02x", + netaddr_print(&addr, host), tun->id); + + cli = tunnel_alloc(tid); + if (!cli) { + net_close(&cli_sock); + return 0; // soft error + } + + if (event_add_tunnel(cli_sock.evt, tid)) { + net_close(&cli_sock); + free(cli); + return 0; // soft error + } + cli->sock.fd = cli_sock.fd; + cli->sock.evt = cli_sock.evt; + cli->connected = 1; + cli->id = tid; + iobuf_init2(&cli->rio.buf, &cli->wio.buf, "tcp"); + list_add_tail(&cli->list, &all_tunnels); + + msg_len = netaddr_to_connans(&addr, (r2tmsg_connans_t *)&msg); + msg.rid = tid; + + if (channel_write(R2TCMD_RCONN, tun->id, &msg.rid, msg_len) < 0) + tunnel_close(tun); + + return 0; +} + +static int tunnel_close_event(tunnel_t *tun) +{ + assert(valid_tunnel(tun)); + + channel_write(R2TCMD_CLOSE, tun->id, NULL, 0); + tunnel_close(tun); + + return 0; +} + +/** handle tunnel event + * @param[in] tun tunnel associated with event + * @param[in] h event handle + * @return 0 on success + */ +int tunnel_event(tunnel_t *tun, HANDLE h) +{ + int ret, evt; + WSANETWORKEVENTS events; + + assert(valid_tunnel(tun) && h); + trace_tun("id=0x%02x %s h=%x", tun->id, tun->proc ? "proc" : "tcp", h); + + if (tun->proc) { // process tunnel + + if (h == tun->proc) { // process is dead + info(0, "tunnel 0x%02x process has terminated", tun->id); + return tunnel_close_event(tun); + } + if (h == tun->rio.io.hEvent) { + ret = tunnel_fdread_event(tun); + } else { + assert(h == tun->wio.io.hEvent); + ret = tunnel_fdwrite_event(tun); + } + + } else { // socket tunnel + + ret = 0; + events.lNetworkEvents = 0; + + if (!WSAEnumNetworkEvents(tun->sock.fd, tun->sock.evt, &events)) { + + evt = (int) events.lNetworkEvents; + + debug(1, "close=%i, conn=%i/%i, read=%i, write=%i, accept=%i", + !!(evt & FD_CLOSE), !!(evt & FD_CONNECT), tun->connected, + !!(evt & FD_READ), !!(evt & FD_WRITE), + !!(evt & FD_ACCEPT)); + + if (evt & FD_ACCEPT) { + debug(0, "FD_ACCEPT"); + ret = tunnel_accept_event(tun); + + } else if (evt & FD_CONNECT) { + debug(0, "FD_CONNECT"); + ret = tunnel_connect_event(tun, events.iErrorCode[FD_CONNECT_BIT]); + if (!ret) { + assert(tun->connected); + ret = tunnel_socksend_event(tun); + if (ret >= 0) + ret = tunnel_sockrecv_event(tun); + } + + } else if (evt & FD_WRITE) { + debug(0, "FD_WRITE"); + ret = tunnel_socksend_event(tun); + } + + if ((ret >= 0) && (evt & FD_READ)) { + debug(0, "FD_READ"); + ret = tunnel_sockrecv_event(tun); + } + + if (evt & FD_CLOSE) { + debug(0, "FD_CLOSE"); + return tunnel_close_event(tun); + } + + } else { + if (WSAGetLastError() != ERROR_IO_PENDING) + return wsaerror("WSAEnumNetworkEvents"); + } + } + + if (ret < 0) + tunnel_close(tun); + + return 0; +} + +/** write data ti rdp2tcp tunnel + * @param[in] tun established tunnel + * @param[in] data data to write + * @param[in] len buffer size + * @return 0 on success */ +int tunnel_write(tunnel_t *tun, const void *data, unsigned int len) +{ + unsigned int used; + iobuf_t *obuf; + + assert(valid_tunnel(tun) && data && len); + trace_tun("id=0x%02x, data=%p, len=%u, connected=%i", + tun->id, data, len, tun->connected); + + obuf = &tun->wio.buf; + trace_tun("====> %c %u %u", obuf->type, obuf->size, obuf->total); + assert(valid_iobuf(obuf)); + + used = iobuf_datalen(obuf); + if (len > 0) { + if (!iobuf_append(obuf, data, len)) + return error("failed to append %u bytes to tunnel buffer", len); + } + + if ((used > 0) || !tun->connected) + return 0; + + if (tun->proc) + return tunnel_fdwrite_event(tun); + + if (net_update_watch(&tun->sock, &tun->wio.buf)) { + return wsaerror("WSAEventSelect"); + } + + return tunnel_socksend_event(tun); +} + +/** destroy all tunnels */ +void tunnels_kill(void) +{ + tunnel_t *tun, *bak; + + trace_tun(""); + + list_for_each_safe(tun, bak, &all_tunnels) { + tunnel_close(tun); + } +} + diff --git a/server/wtsapi32.h b/server/wtsapi32.h new file mode 100644 index 0000000..861c1e0 --- /dev/null +++ b/server/wtsapi32.h @@ -0,0 +1,731 @@ +/********************************************************************* +* +* WTSAPI32.H +* +* Windows Terminal Server public APIs +* +* Copyright (c) 1997-2001 Microsoft Corporation +* +**********************************************************************/ + +#ifndef _INC_WTSAPI +#define _INC_WTSAPI + +#if _MSC_VER > 1000 +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/*=================================================================== +== Defines +=====================================================================*/ + +/* + * Specifies the current server + */ +#define WTS_CURRENT_SERVER ((HANDLE)NULL) +#define WTS_CURRENT_SERVER_HANDLE ((HANDLE)NULL) +#define WTS_CURRENT_SERVER_NAME (NULL) + +/* + * Specifies the current session (SessionId) + */ +#define WTS_CURRENT_SESSION ((DWORD)-1) + +/* + * Possible pResponse values from WTSSendMessage() + */ +#ifndef IDTIMEOUT +#define IDTIMEOUT 32000 +#endif +#ifndef IDASYNC +#define IDASYNC 32001 +#endif + +/* + * Shutdown flags + */ +#define WTS_WSD_LOGOFF 0x00000001 // log off all users except + // current user; deletes + // WinStations (a reboot is + // required to recreate the + // WinStations) +#define WTS_WSD_SHUTDOWN 0x00000002 // shutdown system +#define WTS_WSD_REBOOT 0x00000004 // shutdown and reboot +#define WTS_WSD_POWEROFF 0x00000008 // shutdown and power off (on + // machines that support power + // off through software) +#define WTS_WSD_FASTREBOOT 0x00000010 // reboot without logging users + // off or shutting down + + +/*=================================================================== +== WTS_CONNECTSTATE_CLASS - Session connect state +=====================================================================*/ + +typedef enum _WTS_CONNECTSTATE_CLASS { + WTSActive, // User logged on to WinStation + WTSConnected, // WinStation connected to client + WTSConnectQuery, // In the process of connecting to client + WTSShadow, // Shadowing another WinStation + WTSDisconnected, // WinStation logged on without client + WTSIdle, // Waiting for client to connect + WTSListen, // WinStation is listening for connection + WTSReset, // WinStation is being reset + WTSDown, // WinStation is down due to error + WTSInit, // WinStation in initialization +} WTS_CONNECTSTATE_CLASS; + + +/*===================================================================== +== WTS_SERVER_INFO - returned by WTSEnumerateServers (version 1) +=====================================================================*/ + +/* + * WTSEnumerateServers() returns two variables: pServerInfo and Count. + * The latter is the number of WTS_SERVER_INFO structures contained in + * the former. In order to read each server, iterate i from 0 to + * Count-1 and reference the server name as + * pServerInfo[i].pServerName; for example: + * + * for ( i=0; i < Count; i++ ) { + * _tprintf( TEXT("%s "), pServerInfo[i].pServerName ); + * } + * + * The memory returned looks like the following. P is a pServerInfo + * pointer, and D is the string data for that pServerInfo: + * + * P1 P2 P3 P4 ... Pn D1 D2 D3 D4 ... Dn + * + * This makes it easier to iterate the servers, using code similar to + * the above. + */ + +typedef struct _WTS_SERVER_INFOW { + LPWSTR pServerName; // server name +} WTS_SERVER_INFOW, * PWTS_SERVER_INFOW; + +typedef struct _WTS_SERVER_INFOA { + LPSTR pServerName; // server name +} WTS_SERVER_INFOA, * PWTS_SERVER_INFOA; + +#ifdef UNICODE +#define WTS_SERVER_INFO WTS_SERVER_INFOW +#define PWTS_SERVER_INFO PWTS_SERVER_INFOW +#else +#define WTS_SERVER_INFO WTS_SERVER_INFOA +#define PWTS_SERVER_INFO PWTS_SERVER_INFOA +#endif + + +/*===================================================================== +== WTS_SESSION_INFO - returned by WTSEnumerateSessions (version 1) +=====================================================================*/ + +/* + * WTSEnumerateSessions() returns data in a similar format to the above + * WTSEnumerateServers(). It returns two variables: pSessionInfo and + * Count. The latter is the number of WTS_SESSION_INFO structures + * contained in the former. Iteration is similar, except that there + * are three parts to each entry, so it would look like this: + * + * for ( i=0; i < Count; i++ ) { + * _tprintf( TEXT("%-5u %-20s %u\n"), + pSessionInfo[i].SessionId, + * pSessionInfo[i].pWinStationName, + * pSessionInfo[i].State ); + * } + * + * The memory returned is also segmented as the above, with all the + * structures allocated at the start and the string data at the end. + * We'll use S for the SessionId, P for the pWinStationName pointer + * and D for the string data, and C for the connect State: + * + * S1 P1 C1 S2 P2 C2 S3 P3 C3 S4 P4 C4 ... Sn Pn Cn D1 D2 D3 D4 ... Dn + * + * As above, this makes it easier to iterate the sessions. + */ + +typedef struct _WTS_SESSION_INFOW { + DWORD SessionId; // session id + LPWSTR pWinStationName; // name of WinStation this session is + // connected to + WTS_CONNECTSTATE_CLASS State; // connection state (see enum) +} WTS_SESSION_INFOW, * PWTS_SESSION_INFOW; + +typedef struct _WTS_SESSION_INFOA { + DWORD SessionId; // session id + LPSTR pWinStationName; // name of WinStation this session is + // connected to + WTS_CONNECTSTATE_CLASS State; // connection state (see enum) +} WTS_SESSION_INFOA, * PWTS_SESSION_INFOA; + + +#ifdef UNICODE +#define WTS_SESSION_INFO WTS_SESSION_INFOW +#define PWTS_SESSION_INFO PWTS_SESSION_INFOW +#else +#define WTS_SESSION_INFO WTS_SESSION_INFOA +#define PWTS_SESSION_INFO PWTS_SESSION_INFOA +#endif + + +/*===================================================================== +== WTS_PROCESS_INFO - returned by WTSEnumerateProcesses (version 1) +=====================================================================*/ + +/* + * WTSEnumerateProcesses() also returns data similar to + * WTSEnumerateServers(). It returns two variables: pProcessInfo and + * Count. The latter is the number of WTS_PROCESS_INFO structures + * contained in the former. Iteration is similar, except that there + * are four parts to each entry, so it would look like this: + * + * for ( i=0; i < Count; i++ ) { + * GetUserNameFromSid( pProcessInfo[i].pUserSid, UserName, + * sizeof(UserName) ); + * _tprintf( TEXT("%-5u %-20s %-5u %s\n"), + * pProcessInfo[i].SessionId, + * UserName, + * pProcessInfo[i].ProcessId, + * pProcessInfo[i].pProcessName ); + * } + * + * The memory returned is also segmented as the above, with all the + * structures allocated at the start and the string data at the end. + * We'll use S for the SessionId, R for the ProcessId, P for the + * pProcessName pointer and D for the string data, and U for pUserSid: + * + * S1 R1 P1 U1 S2 R2 P2 U2 S3 R3 P3 U3 ... Sn Rn Pn Un D1 D2 D3 ... Dn + * + * As above, this makes it easier to iterate the processes. + */ + +typedef struct _WTS_PROCESS_INFOW { + DWORD SessionId; // session id + DWORD ProcessId; // process id + LPWSTR pProcessName; // name of process + PSID pUserSid; // user's SID +} WTS_PROCESS_INFOW, * PWTS_PROCESS_INFOW; + +typedef struct _WTS_PROCESS_INFOA { + DWORD SessionId; // session id + DWORD ProcessId; // process id + LPSTR pProcessName; // name of process + PSID pUserSid; // user's SID +} WTS_PROCESS_INFOA, * PWTS_PROCESS_INFOA; + +#ifdef UNICODE +#define WTS_PROCESS_INFO WTS_PROCESS_INFOW +#define PWTS_PROCESS_INFO PWTS_PROCESS_INFOW +#else +#define WTS_PROCESS_INFO WTS_PROCESS_INFOA +#define PWTS_PROCESS_INFO PWTS_PROCESS_INFOA +#endif + + +/*===================================================================== +== WTS_INFO_CLASS - WTSQuerySessionInformation +== (See additional typedefs for more info on structures) +=====================================================================*/ + +#define WTS_PROTOCOL_TYPE_CONSOLE 0 // Console +#define WTS_PROTOCOL_TYPE_ICA 1 // ICA Protocol +#define WTS_PROTOCOL_TYPE_RDP 2 // RDP Protocol + +typedef enum _WTS_INFO_CLASS { + WTSInitialProgram, + WTSApplicationName, + WTSWorkingDirectory, + WTSOEMId, + WTSSessionId, + WTSUserName, + WTSWinStationName, + WTSDomainName, + WTSConnectState, + WTSClientBuildNumber, + WTSClientName, + WTSClientDirectory, + WTSClientProductId, + WTSClientHardwareId, + WTSClientAddress, + WTSClientDisplay, + WTSClientProtocolType, +} WTS_INFO_CLASS; + + +/*===================================================================== +== WTSQuerySessionInformation - (WTSClientAddress) +=====================================================================*/ + +typedef struct _WTS_CLIENT_ADDRESS { + DWORD AddressFamily; // AF_INET, AF_IPX, AF_NETBIOS, AF_UNSPEC + BYTE Address[20]; // client network address +} WTS_CLIENT_ADDRESS, * PWTS_CLIENT_ADDRESS; + + +/*===================================================================== +== WTSQuerySessionInformation - (WTSClientDisplay) +=====================================================================*/ + +typedef struct _WTS_CLIENT_DISPLAY { + DWORD HorizontalResolution; // horizontal dimensions, in pixels + DWORD VerticalResolution; // vertical dimensions, in pixels + DWORD ColorDepth; // 1=16, 2=256, 4=64K, 8=16M +} WTS_CLIENT_DISPLAY, * PWTS_CLIENT_DISPLAY; + + +/*===================================================================== +== WTS_CONFIG_CLASS - WTSQueryUserConfig/WTSSetUserConfig +=====================================================================*/ + + +typedef enum _WTS_CONFIG_CLASS { + //Initial program settings + WTSUserConfigInitialProgram, // string returned/expected + WTSUserConfigWorkingDirectory, // string returned/expected + WTSUserConfigfInheritInitialProgram, // DWORD returned/expected + // + WTSUserConfigfAllowLogonTerminalServer, //DWORD returned/expected + //Timeout settings + WTSUserConfigTimeoutSettingsConnections, //DWORD returned/expected + WTSUserConfigTimeoutSettingsDisconnections, //DWORD returned/expected + WTSUserConfigTimeoutSettingsIdle, //DWORD returned/expected + //Client device settings + WTSUserConfigfDeviceClientDrives, //DWORD returned/expected + WTSUserConfigfDeviceClientPrinters, //DWORD returned/expected + WTSUserConfigfDeviceClientDefaultPrinter, //DWORD returned/expected + //Connection settings + WTSUserConfigBrokenTimeoutSettings, //DWORD returned/expected + WTSUserConfigReconnectSettings, //DWORD returned/expected + //Modem settings + WTSUserConfigModemCallbackSettings, //DWORD returned/expected + WTSUserConfigModemCallbackPhoneNumber, // string returned/expected + //Shadow settings + WTSUserConfigShadowingSettings, //DWORD returned/expected + //User Profile settings + WTSUserConfigTerminalServerProfilePath, // string returned/expected + //Terminal Server home directory + WTSUserConfigTerminalServerHomeDir, // string returned/expected + WTSUserConfigTerminalServerHomeDirDrive, // string returned/expected + WTSUserConfigfTerminalServerRemoteHomeDir, // DWORD 0:LOCAL 1:REMOTE + +} WTS_CONFIG_CLASS; + + +/*===================================================================== +== WTS_EVENT - Event flags for WTSWaitSystemEvent +=====================================================================*/ + +#define WTS_EVENT_NONE 0x00000000 // return no event +#define WTS_EVENT_CREATE 0x00000001 // new WinStation created +#define WTS_EVENT_DELETE 0x00000002 // existing WinStation deleted +#define WTS_EVENT_RENAME 0x00000004 // existing WinStation renamed +#define WTS_EVENT_CONNECT 0x00000008 // WinStation connect to client +#define WTS_EVENT_DISCONNECT 0x00000010 // WinStation logged on without + // client +#define WTS_EVENT_LOGON 0x00000020 // user logged on to existing + // WinStation +#define WTS_EVENT_LOGOFF 0x00000040 // user logged off from + // existing WinStation +#define WTS_EVENT_STATECHANGE 0x00000080 // WinStation state change +#define WTS_EVENT_LICENSE 0x00000100 // license state change +#define WTS_EVENT_ALL 0x7fffffff // wait for all event types +#define WTS_EVENT_FLUSH 0x80000000 // unblock all waiters + +/*===================================================================== +== WTS_VIRTUAL_CLASS - WTSVirtualChannelQuery +=====================================================================*/ +typedef enum _WTS_VIRTUAL_CLASS { + WTSVirtualClientData, // Virtual channel client module data + // (C2H data) + WTSVirtualFileHandle +} WTS_VIRTUAL_CLASS; + + +/*===================================================================== +== Windows Terminal Server public APIs +=====================================================================*/ + +BOOL +WINAPI +WTSEnumerateServersW( + IN LPWSTR pDomainName, + IN DWORD Reserved, + IN DWORD Version, + OUT PWTS_SERVER_INFOW * ppServerInfo, + OUT DWORD * pCount + ); + +BOOL +WINAPI +WTSEnumerateServersA( + IN LPSTR pDomainName, + IN DWORD Reserved, + IN DWORD Version, + OUT PWTS_SERVER_INFOA * ppServerInfo, + OUT DWORD * pCount + ); + +#ifdef UNICODE +#define WTSEnumerateServers WTSEnumerateServersW +#else +#define WTSEnumerateServers WTSEnumerateServersA +#endif + +/*------------------------------------------------*/ + +HANDLE +WINAPI +WTSOpenServerW( + IN LPWSTR pServerName + ); + +HANDLE +WINAPI +WTSOpenServerA( + IN LPSTR pServerName + ); + +#ifdef UNICODE +#define WTSOpenServer WTSOpenServerW +#else +#define WTSOpenServer WTSOpenServerA +#endif + +/*------------------------------------------------*/ + +VOID +WINAPI +WTSCloseServer( + IN HANDLE hServer + ); + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSEnumerateSessionsW( + IN HANDLE hServer, + IN DWORD Reserved, + IN DWORD Version, + OUT PWTS_SESSION_INFOW * ppSessionInfo, + OUT DWORD * pCount + ); + +BOOL +WINAPI +WTSEnumerateSessionsA( + IN HANDLE hServer, + IN DWORD Reserved, + IN DWORD Version, + OUT PWTS_SESSION_INFOA * ppSessionInfo, + OUT DWORD * pCount + ); + +#ifdef UNICODE +#define WTSEnumerateSessions WTSEnumerateSessionsW +#else +#define WTSEnumerateSessions WTSEnumerateSessionsA +#endif + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSEnumerateProcessesW( + IN HANDLE hServer, + IN DWORD Reserved, + IN DWORD Version, + OUT PWTS_PROCESS_INFOW * ppProcessInfo, + OUT DWORD * pCount + ); + +BOOL +WINAPI +WTSEnumerateProcessesA( + IN HANDLE hServer, + IN DWORD Reserved, + IN DWORD Version, + OUT PWTS_PROCESS_INFOA * ppProcessInfo, + OUT DWORD * pCount + ); + +#ifdef UNICODE +#define WTSEnumerateProcesses WTSEnumerateProcessesW +#else +#define WTSEnumerateProcesses WTSEnumerateProcessesA +#endif + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSTerminateProcess( + IN HANDLE hServer, + IN DWORD ProcessId, + IN DWORD ExitCode + ); + + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSQuerySessionInformationW( + IN HANDLE hServer, + IN DWORD SessionId, + IN WTS_INFO_CLASS WTSInfoClass, + OUT LPWSTR * ppBuffer, + OUT DWORD * pBytesReturned + ); + +BOOL +WINAPI +WTSQuerySessionInformationA( + IN HANDLE hServer, + IN DWORD SessionId, + IN WTS_INFO_CLASS WTSInfoClass, + OUT LPSTR * ppBuffer, + OUT DWORD * pBytesReturned + ); + +#ifdef UNICODE +#define WTSQuerySessionInformation WTSQuerySessionInformationW +#else +#define WTSQuerySessionInformation WTSQuerySessionInformationA +#endif + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSQueryUserConfigW( + IN LPWSTR pServerName, + IN LPWSTR pUserName, + IN WTS_CONFIG_CLASS WTSConfigClass, + OUT LPWSTR * ppBuffer, + OUT DWORD * pBytesReturned + ); + +BOOL +WINAPI +WTSQueryUserConfigA( + IN LPSTR pServerName, + IN LPSTR pUserName, + IN WTS_CONFIG_CLASS WTSConfigClass, + OUT LPSTR * ppBuffer, + OUT DWORD * pBytesReturned + ); + +#ifdef UNICODE +#define WTSQueryUserConfig WTSQueryUserConfigW +#else +#define WTSQueryUserConfig WTSQueryUserConfigA +#endif + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSSetUserConfigW( + IN LPWSTR pServerName, + IN LPWSTR pUserName, + IN WTS_CONFIG_CLASS WTSConfigClass, + IN LPWSTR pBuffer, + IN DWORD DataLength + ); + +BOOL +WINAPI +WTSSetUserConfigA( + IN LPSTR pServerName, + IN LPSTR pUserName, + IN WTS_CONFIG_CLASS WTSConfigClass, + IN LPSTR pBuffer, + IN DWORD DataLength + ); + +#ifdef UNICODE +#define WTSSetUserConfig WTSSetUserConfigW +#else +#define WTSSetUserConfig WTSSetUserConfigA +#endif + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSSendMessageW( + IN HANDLE hServer, + IN DWORD SessionId, + IN LPWSTR pTitle, + IN DWORD TitleLength, + IN LPWSTR pMessage, + IN DWORD MessageLength, + IN DWORD Style, + IN DWORD Timeout, + OUT DWORD * pResponse, + IN BOOL bWait + ); + +BOOL +WINAPI +WTSSendMessageA( + IN HANDLE hServer, + IN DWORD SessionId, + IN LPSTR pTitle, + IN DWORD TitleLength, + IN LPSTR pMessage, + IN DWORD MessageLength, + IN DWORD Style, + IN DWORD Timeout, + OUT DWORD * pResponse, + IN BOOL bWait + ); + +#ifdef UNICODE +#define WTSSendMessage WTSSendMessageW +#else +#define WTSSendMessage WTSSendMessageA +#endif + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSDisconnectSession( + IN HANDLE hServer, + IN DWORD SessionId, + IN BOOL bWait + ); + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSLogoffSession( + IN HANDLE hServer, + IN DWORD SessionId, + IN BOOL bWait + ); + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSShutdownSystem( + IN HANDLE hServer, + IN DWORD ShutdownFlag + ); + +/*------------------------------------------------*/ + +BOOL +WINAPI +WTSWaitSystemEvent( + IN HANDLE hServer, + IN DWORD EventMask, + OUT DWORD * pEventFlags + ); + +/*------------------------------------------------*/ + +HANDLE +WINAPI +WTSVirtualChannelOpen( + IN HANDLE hServer, + IN DWORD SessionId, + IN LPSTR pVirtualName /* ascii name */ + ); + +BOOL +WINAPI +WTSVirtualChannelClose( + IN HANDLE hChannelHandle + ); + +BOOL +WINAPI +WTSVirtualChannelRead( + IN HANDLE hChannelHandle, + IN ULONG TimeOut, + OUT PCHAR Buffer, + IN ULONG BufferSize, + OUT PULONG pBytesRead + ); + +BOOL +WINAPI +WTSVirtualChannelWrite( + IN HANDLE hChannelHandle, + IN PCHAR Buffer, + IN ULONG Length, + OUT PULONG pBytesWritten + ); + +BOOL +WINAPI +WTSVirtualChannelPurgeInput( + IN HANDLE hChannelHandle + ); + +BOOL +WINAPI +WTSVirtualChannelPurgeOutput( + IN HANDLE hChannelHandle + ); + + +BOOL +WINAPI +WTSVirtualChannelQuery( + IN HANDLE hChannelHandle, + IN WTS_VIRTUAL_CLASS, + OUT PVOID *ppBuffer, + OUT DWORD *pBytesReturned + ); + +/*------------------------------------------------*/ + + +VOID +WINAPI +WTSFreeMemory( + IN PVOID pMemory + ); + +/* Flags for Console Notification */ + +#define NOTIFY_FOR_ALL_SESSIONS 1 +#define NOTIFY_FOR_THIS_SESSION 0 + + +BOOL WINAPI +WTSRegisterSessionNotification( + HWND hWnd, + DWORD dwFlags + ); + +BOOL WINAPI +WTSUnRegisterSessionNotification( + HWND hWnd + ); + +BOOL WINAPI +WTSQueryUserToken( + ULONG SessionId, + PHANDLE phToken + ); + + +#ifdef __cplusplus +} +#endif + +#endif /* !_INC_WTSAPI */ + diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..bb67088 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,6 @@ + +all: + + +clean: + rm -f *.pyc diff --git a/tools/rdp2tcp.py b/tools/rdp2tcp.py new file mode 100755 index 0000000..2d3edb9 --- /dev/null +++ b/tools/rdp2tcp.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +import socket +from random import randint +from time import sleep +from os import system + +def connect_to(host, port): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + s.connect((host, port)) + return s + +class R2TException(Exception): + pass + +server_type_names = {\ + 'ctrl':'controller',\ + 'tun':'tunnel',\ + 'rtun':'tunnel',\ + 's5':'socks5' +} + +class R2TServer: + + def __init__(self, type, lhost, rhost=None, rev=False): + self.type = type + self.lhost = lhost + self.rhost = rhost + self.reverse = rev + self.clients = [] + + def __str__(self): + global server_type_names + out = server_type_names[self.type] + ' ' + self.lhost + if self.rhost: + out += ' %s %s' % (self.reverse and '<--' or '-->', self.rhost) + return out + +class R2TClient: + def __init__(self, rhost): + self.rhost = rhost + def __str__(self): + return self.rhost + +class rdp2tcp: + + def __init__(self, host, port): + try: + s = connect_to(host, port) + except socket.error, e: + raise R2TException(e[1]) + self.sock = s + + def close(self): + self.sock.close() + + def __read_answer(self, end_marker='\n'): + data = '' + while True: + data += self.sock.recv(4096) + #print '['+ repr(data) + '] => ' + repr(end_marker) + if end_marker in data: + break + if data.startswith('error: '): + raise R2TException(data[7:-1]) + + return data[:data.find(end_marker)] + + def add_tunnel(self, type, src, dst): + msg = '%s %s %i %s' % (type, src[0], src[1], dst[0]) + if type != 'x': msg += ' %i' % dst[1] + self.sock.sendall(msg+'\n') + return self.__read_answer() + + def del_tunnel(self, src): + self.sock.sendall('- %s %i\n' % src) + return self.__read_answer() + + def info(self): + self.sock.sendall('l\n') + return self.__read_answer('\n\n') + +if __name__ == '__main__': + from sys import argv, exit, stdin, stdout + + def usage(): + print """ +usage: %s [-h host] [-p port] [args..] + +commands: + info + add forward + add reverse + add process + add socks5 + del + sh [args]""" % argv[0] + exit(0) + + + def popup_telnet(x, type, dst): + + laddr = ('127.0.0.1', randint(1025, 0xffff)) + try: + print x.add_tunnel(type, laddr, dst) + except R2TException, e: + print 'error:', e + return + + try: + system('xterm -e telnet %s %i &' % laddr) + sleep(0.8) + except KeyboardInterrupt: + stdout.write('\n') + + try: + print x.del_tunnel(laddr) + except R2TException, e: + print 'error:', e + + + argc = len(argv) + if argc < 2: + usage() + + host,port = '127.0.0.1',8477 + + i = 1 + while argv[i].startswith('-'): + if argv[i] == '-h': + pass + elif argv[i] == '-p': + pass + i += 2 + + cmd = argv[i] + if cmd not in ('info','add','del','sh','telnet'): + usage() + + try: + r2t = rdp2tcp(host, port) + except R2TException, e: + print 'error: %s' % str(e) + exit(0) + + argc -= i + 1 + if cmd == 'add': + argc -= 1 + arg = argv[i+1] + if arg == 'forward' and argc == 4: + type = 't' + src,dst = (argv[i+2], int(argv[i+3])),(argv[i+4], int(argv[i+5])) + elif arg == 'reverse' and argc == 4: + type = 'r' + src,dst = (argv[i+2], int(argv[i+3])),(argv[i+4], int(argv[i+5])) + elif arg == 'process' and argc == 3: + type = 'x' + src,dst = (argv[i+2], int(argv[i+3])),(argv[i+4], 0) + elif arg == 'socks5' and argc == 2: + type = 's' + src,dst = (argv[i+2], int(argv[i+3])),('', 0) + else: + usage() + + try: + print r2t.add_tunnel(type, src, dst) + except R2TException, e: + print 'error: %s' % str(e) + + elif cmd == 'del': + if argc != 2: usage() + + try: + print r2t.del_tunnel((argv[i+1], int(argv[i+2]))) + except R2TException, e: + print 'error: %s' % str(e) + + elif cmd == 'info': + print r2t.info() + + elif cmd == 'sh': + proc = 'cmd.exe' + if argc >= 1: + proc += ' /C ' + ' '.join(argv[i+1:]) + popup_telnet(r2t, 'x', (proc, 0)) + + elif cmd == 'telnet': + if argc != 2: + usage() + popup_telnet(r2t, 't', (argv[i+1], int(argv[i+2]))) + + r2t.close() + diff --git a/tools/rdpupload b/tools/rdpupload new file mode 100755 index 0000000..fa96990 --- /dev/null +++ b/tools/rdpupload @@ -0,0 +1,238 @@ +#!/usr/bin/env python +# +# rdpupload 0.1 -- nicolas.collignon@hsc.fr +# +# upload binary file to rdesktop by simulating rdesktop keyboard input +# a.k.a "rdesktop scripting through X11 automation" +# +# this tool can be used to upload a binary on a Terminal Server when +# file sharing or clipboard support (through RDP) is blocked by the +# ecurity policy. +# +# X11 automation is performed with xte scripts (generated with -x). +# they are supposed to be run with the xte program from xautomation. +# http://hoopajoo.net/projects/xautomation.html +# +# supported encoding: +# +# +# + +from sys import stderr +from os import path +import string + +# debug.com encoder +def encode_debug(in_data, out_name): + + i, j, line = 0, 256, '' + out = 'n %s\nr cx\n%x\nf 0100 ffff 00\n' % (out_name, len(in_data)) + + for c in in_data: + + byte = ord(c) + if byte != 0: + i += 1 + if not line: line = 'e %0x' % j + line += ' %02x' % byte + + elif line: + out += line + '\n' + i, line = 0, '' + + j += 1 + + if i == 20: + out += line + '\n' + i, line = 0, '' + + return out + +# VB encoder +def encode_vb(in_data, out_name): + off, size = 0, len(in_data) + out = """With CreateObject("ADODB.Stream") +.Type=2 +.Open +""" +# out = """Dim x +#Set x=CreateObject("ADODB.Stream") +#x.Type=2 +#x.Open +#""" + off, size = 0, len(in_data) + + while off < size: + + avail = min(size - off, 32) + out += '.WriteText ' + #out += 'x.WriteText ' + out += '&'.join('chr(%i)' % ord(b) for b in in_data[off:off+avail]) + out += '\n' + + off += avail + + return out + '.SaveToFile "%s",2\n.Close\nEnd With\n' % out_name + #return out + 'x.SaveToFile "%s",2\n' % out_name + +# xte encoder +def encode_xte(in_data, out, focus_delay=5.0, ksleep=1.0): + + def xsleep(t, use_coeff=True): + out.write('usleep %i\n' % int(1000000*t*(use_coeff and ksleep or 1))) + + word_charset = string.letters + string.digits + string.punctuation + byte2sym = {' ':'space', '\t':'Tab', '\n':'Return'} + word = '' + + xsleep(focus_delay, False) + + for byte in in_data: + + if byte in word_charset: + word += byte + if len(word) > 6: + xsleep(0.080) + out.write('str %s\n' % word) + word = '' + + elif byte == '\r': + continue + + else: + if word: + if len(word) > 4: xsleep(0.080) + out.write('str %s\n' % word) + word = '' + + if not byte2sym.has_key(byte): + stderr.write('error: byte 0x%x not allowed' % byte) + return + + xsleep(0.050) + out.write('key %s\n' % byte2sym[byte]) + + if word: + out.write('str %s\n' % word) + +if __name__ == '__main__': + from sys import argv, exit, stdin, stdout + from getopt import getopt, GetoptError + from os import path + + def usage(): + stderr.write(""" +usage: %s [-x] [-f format] [-t delay] [-s coeff] + + -x,--xte generate xte script + + -f,--format fmt input encoding (default is no encoding) + debug -> generate debug.com script + vb -> generate VB script + base64 -> generate base64 encoded payload + + -t,--delay float delay before starting input simulation (default is 5 sec) + -s,--sleep float sleep factor (default is 1.0) + + infile input file + + outfile output file (ascii or xte script) + +""" % argv[0]) + exit(0) + + # default config + + xte_output = False + fmt = 'raw' + start_delay = 5.0 + sleep_factor = 1.0 + + # parse arguments + + argc = len(argv) + if argc < 3 or argc > 10: + usage() + + try: + opts, args = getopt(argv[1:], 'hxf:t:s:', ['xte','format','delay','sleep']) + except GetoptError, err: + stderr.write('error: %s\n' % str(err)) + exit(0) + + if len(args) != 2: + usage() + + for o, a in opts: + + if o in ('-x','--xte'): + xte_output = True + + elif o in ('-f','--format'): + if a not in ('debug','base64','vb'): + stderr.write('error: bad format\n') + exit(0) + fmt = a + + elif o in ('-t','--delay'): + start_delay = float(a) + if start_delay <= 0: + stderr.write('error: bad start delay') + exit(0) + + elif o in ('-s','--sleep'): + sleep_factor = float(a) + if sleep_factor <= 0: + stderr.write('error: bad sleep factor') + exit(0) + + else: + usage() + + try: + if args[0] == '-': + fin = stdin + out_name = 'outbin' + else: + fin = open(args[0], 'rb') + out_name = path.basename(args[0]).replace('.','_') + + if args[1] == '-': + fout = stdout + else: + fout = open(args[1], 'wb') + + except IOError, e: + stderr.write(str(e)+'\n') + + # read input + + data_in = fin.read() + fin.close() + + # encode input + + if fmt == 'raw': + data_out = data_in + + elif fmt == 'debug': + if len(data_in) > 65280: + stderr.write('error: input file bigger than 65280 bytes\n') + exit(0) + + data_out = encode_debug(data_in, out_name) + + elif fmt == 'base64': + data_out = data_in.encode('base64') + + elif fmt == 'vb': + data_out = encode_vb(data_in, out_name) + + # encode output + + if xte_output: + encode_xte(data_out, fout, start_delay, sleep_factor) + else: + fout.write(data_out) + + fout.close() diff --git a/tools/test-client b/tools/test-client new file mode 100755 index 0000000..b99e3c0 --- /dev/null +++ b/tools/test-client @@ -0,0 +1,211 @@ +#!/usr/bin/env python +from rdp2tcp import rdp2tcp, R2TException +from sys import exit +import time +import socket +import os + +def tcp_sock(local_port): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + s.connect(('127.0.0.1', local_port)) + s.settimeout(30) + return s + +def cmd(cmdline): + os.system('xterm -e %s &' % cmdline) + +############################################################### +# send shit to controller -> expect connection closed +def test_controller_proto(): + + badmsgs = [ \ + '\n', \ + ' l\n', \ + 't\n', 'r\n', 's\n', 'x\n', \ + 's a 0\n', \ + 's a -1\n', \ + 's a 65536\n', \ + 's a 65535A\n', \ + '\x00\n' \ + ] + + print 'controller protocol tests' + for msg in badmsgs: + + s = tcp_sock(8477) + s.sendall(msg) + data = s.recv(256) + if data != '': + print 'error: server didnt closed connection' + print '>>> ', repr(msg) + print '<<< ', data + s.close() + + + +############################################################### +# python API test +def setup_tunnels(): + print 'tunnels setup' + try: + r2t = rdp2tcp('127.0.0.1', 8477) + except R2TException, e: + print e + exit(0) + + try: + # forward + r2t.add_tunnel('t', ('127.0.0.1',4444),('127.0.0.1',4444)) + # forward + r2t.add_tunnel('t', ('127.0.0.1',4445),('127.0.0.1',445)) + # reverse + r2t.add_tunnel('r', ('127.0.0.1',22),('127.0.0.1',2222)) + # forward + r2t.add_tunnel('t', ('127.0.0.1',2222),('127.0.0.1',2222)) + # forward + r2t.add_tunnel('t', ('::1',2223),('127.0.0.1',2222)) + # forward + r2t.add_tunnel('t', ('localhost',2224),('127.0.0.1',2222)) + # process + r2t.add_tunnel('x', ('127.0.0.1',4446),('cmd.exe',0)) + # socks5 + r2t.add_tunnel('s', ('127.0.0.1',65480),('',0)) + + print r2t.info() + + except R2TException, e: + print e + + r2t.close() + +############################################################### +# test behaviour of buggy tunnel clients +def test_connect_and_close(): + targets = [ \ + 8477, \ + 4444, \ + 4445, \ + 2222, \ + 4446, \ + 65480 \ + ] + + print 'connect() + close() tests' + for port in targets: + s = tcp_sock(port) + s.close() + + for port in targets: + print 'connect(%i) + sleep(2) + close() tests' % port + s = tcp_sock(port) + time.sleep(2) + s.close() + + print 'connect() + send(garbage) + close() tests' + garbage = ''.join(chr(i) for i in xrange(0x100)) + for port in targets: + s = tcp_sock(port) + s.sendall(garbage) + s.close() + +############################################################### +# test ssh tunnel by boucing on remote host +# use forward tunnel + reverse tunnel +# server talks first +def test_ssh(): + + print '1 SSH test' + cmd('ssh -p 2222 127.0.0.1 "ls /usr/lib | less"') + raw_input('Press Key when SSH is closed') + print '2 SSH test' + cmd('ssh -p 2222 127.0.0.1 "ls /usr/lib | less"') + cmd('ssh -p 2222 127.0.0.1 "ls /usr/lib | less"') + raw_input('Press Key when both SSH are closed') + + +############################################################### +# test forward tunnel using "dir c:\windows\system32" +# server talks first +def test_cmd(): + print 'cmd.exe test' + cmd('telnet 127.0.0.1 4446') + raw_input('Press Key when cmd.exe is closed') + +############################################################### +# test forward tunnel using smbclient +# client talks first +def test_smb(): + print 'smbclient test' + cmd('smbclient -U Administrator -p 4445 //127.0.0.1/c$') + raw_input('Press Key when smbclient is closed') + +############################################################### +# test socks5 protocol errors +def test_socks5(): + + # expect connection closed + badmsgs = [ '\x00', '\x04', '\x06', '\x05\x00' ] + + print 'socks5 protocol tests 1' + for msg in badmsgs: + + s = tcp_sock(65480) + s.sendall(msg) + try: + data = s.recv(256) + if data != '' and da: + print 'error: socket5 server didnt closed connection' + print '>>> ', repr(msg) + print '<<< ', repr(data) + except socket.error, e: + if e[0] != 104: + print 'error: %s' % str(e) + print '>>> ', repr(msg) + s.close() + + # expect socks5 error + badmsgs = [ \ + '\x05\x01\x00\x00', \ + '\x05\x01\x00\x05\x00', \ + '\x05\x01\x00\x05\x02', \ + '\x05\x01\x00\x05\x03', \ + '\x05\x01\x00\x05\x00', \ + '\x05\x01\x00\x05\x01\x01', \ + '\x05\x01\x00\x05\x01\x00\x00', \ + '\x05\x01\x00\x05\x01\x00\x02', \ + '\x05\x01\x00\x05\x01\x00\xff', \ + '\x05\x01\x00\x05\x01\x00\x03\x00', \ + '\x05\x01\x00\x05\x01\x00\x03\x00\x41', \ + ] + + print 'socks5 protocol tests 2' + for msg in badmsgs: + + s = tcp_sock(65480) + s.sendall(msg) + try: + data = s.recv(256) + if len(data) != 2 or data[0] != '\x05': + print 'error: socket5 server didnt closed connection' + print '>>> ', repr(msg) + print '<<< ', repr(data) + except socket.error, e: + if e[0] != 104: + print 'error: %s' % str(e) + print '>>> ', repr(msg) + s.close() + +if __name__ == '__main__': + + socket.setdefaulttimeout(5) + + setup_tunnels() + if 1: + #test_controller_proto() + #test_controller_proto() + test_connect_and_close() + #test_socks5() + #test_ssh() + #test_cmd() + #test_smb() + pass