diff -Nru python-x2go-0.1.1.8/COPYING python-x2go-0.5.0.6/COPYING --- python-x2go-0.1.1.8/COPYING 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/COPYING 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff -Nru python-x2go-0.1.1.8/debian/changelog python-x2go-0.5.0.6/debian/changelog --- python-x2go-0.1.1.8/debian/changelog 2011-12-12 09:20:51.000000000 +0000 +++ python-x2go-0.5.0.6/debian/changelog 2017-12-12 06:52:58.000000000 +0000 @@ -1,37 +1,1409 @@ -python-x2go (0.1.1.8-0ubuntu1) precise; urgency=low +python-x2go (0.5.0.6-0~201709231950~ubuntu12.04.1) precise; urgency=low - * New upstream release. - - For detailed summary please see: - http://code.x2go.org/gitweb?p=python-x2go.git;a=shortlog + * Auto build. - -- Bhavani Shankar Mon, 12 Dec 2011 14:48:01 +0530 + -- Mihai Moldovan Tue, 12 Dec 2017 06:52:58 +0000 -python-x2go (0.1.1.3-0ubuntu1) oneiric; urgency=low +python-x2go (0.5.0.6-0x2go1) unstable; urgency=medium - * New upstream release (0.1.1.3): - - A lot of bugfixes and a few new feature, sees - http://code.x2go.org/gitweb?p=python-x2go.git;a=shortlog for details + [ Mike Gabriel ] + * New upstream version (0.5.0.6): + - x2go/defaults.py: Support LXQt sessions by default. + - x2go/session.py: Only release locks if actually locked. + - x2go/session.py: Drop duplicated method is_locked(). - -- Stéphane Graber Sat, 09 Jul 2011 22:46:31 +0200 + -- X2Go Release Manager Sat, 23 Sep 2017 21:48:51 +0200 -python-x2go (0.1.0.0-0ubuntu2) oneiric; urgency=low +python-x2go (0.5.0.5-0x2go1) unstable; urgency=low - * dh_python2 transition. - * removed debian/pyversions no longer needed. - * debian/control - - increased min debhelper version to 7.0.50. - - increased min python version to >= 2.6.6-3~. - - removed B-D on python-support. - - changed XS-P-V to X-P-V. - - removed XB-P-V no longer needed. - * debian/rules - - switched to dh7 tiny rules. + [ Mike Gabriel ] + * New upstream version (0.5.0.5): + - documentation: Fix wording in docstring. + - Don't blindly release gevent locks. We need to check if a + semaphore is locked for some code paths and only then release it. + (Fixes: #1016). - -- Charlie Smotherman Sat, 18 Jun 2011 21:55:47 -0500 + [ Mihai Moldovan ] + * New upstream version (0.5.0.5): + - x2go/__init__.py: update mailing list address. + * debian/control: + - Maintainer change in package: X2Go Developers . + - Uploaders: add myself. Also, force a rebuild due to the changed + versioning. -python-x2go (0.1.0.0-0ubuntu1) oneiric; urgency=low + -- X2Go Release Manager Mon, 15 May 2017 16:38:16 +0200 - * Initial upload (LP: #785981) - * Packaging taken from x2go's git repository +python-x2go (0.5.0.4-0x2go1) unstable; urgency=medium + + [ Mike Gabriel ] + * New upstream version (0.5.0.4): + - No such constant loglevel_WARNING, must be logolevel_WARN. + - End session gracefully if we fail setting up the SSH agent forwarding + socket. + - Don't check for "fuse" group membership to decide if a user can use sshfs. + The "fuse" group has been dropped from Debian jessie and beyond. + Furthermore, some openSUSE versions use the "trusted" group for granting + (and never used a group named fuse). + * debian/control: + + Allow qvd-nxproxy as an alternative to nxproxy. Allows co-installation + of PyHoca-CLI+GUI and the TheQVD client. + + -- X2Go Release Manager Tue, 28 Jul 2015 21:26:48 +0200 + +python-x2go (0.5.0.3-0x2go1) unstable; urgency=medium + + * New upstream version (0.5.0.3): + - Fix catching control session exceptions in X2GoSession class. + - Catch EOFError in x2go_forward_tunnel_handle(). + - Only sync password and passphrase if we do not enforce password + authentication. + - After calling x2gomountdirs, write stdout and _stderr_ to + the logging instance. + - Catch yet another X2GoControlSessionException. This time during + control_backend.remote_peer() during X2GoSession._resume(). + + -- Mike Gabriel Sun, 25 Jan 2015 13:21:05 +0100 + +python-x2go (0.5.0.2-0x2go1) unstable; urgency=medium + + * New upstream version (0.5.0.2): + - Catch control session disconnects during session's + run_command call. + - Fix cross-user desktop sharing feature since introduction of + clipboard mode feature. + * debian/control: + + Bump Standards: to 3.9.6. No changes needed. + + Mention "client side printing support" in LONG_DESCRIPTION. + * debian/copyright: + + Relicense packaging files under same license as upstream files. + + -- Mike Gabriel Thu, 27 Nov 2014 13:03:30 +0100 + +python-x2go (0.5.0.1-0x2go1) unstable; urgency=medium + + * New upstream version (0.5.0.1): + - Update TODO file. + - Update README file. + - Provide API inline documentation for http(s) session + broker client implementation. + + -- Mike Gabriel Mon, 20 Oct 2014 22:52:42 +0200 + +python-x2go (0.5.0.0-0x2go1) unstable; urgency=low + + [ Mike Gabriel ] + * New upstream version (0.5.0.0): + - Split up session profile backend into generic and storage specific + parts. + - Fully rework backend concept in Python X2Go. Breaks compatibility + with earlier versions of Python X2Go concerning backends (probably + not really used by third-party products, if at all). + - Fix setting default values in X2GoClientXConfig class. + - Default to xdg-open as default PDF viewer command. + - Provide session profile backend for a http broker. + - Make session profile backends more unicode robust. + - X2GoSessionProfile.get_server_hostname must return unicode objects. + - Speed-optimize session profile ID <-> name mapping. + - Handle injection of PKey (Paramiko SSH key) objects for authentication + from the broker session profiles backend. + - Allow catching "connection refused" errors while talking to an X2Go + Session Broker (X2GoBrokerConnectionException). + - Support cookie based authentication against a http(s) session broker. + - On Windows: Improve debugging when a new X-Server port has to be + allocated. + - Capture broker connection problems during selectsession calls to the + broker via a HOOK method. + - Allow user interaction via a HOOK if broker connection problems occur. + - Handle broker setups that don't require credentials. Connection can + be established simply by leaving the password (and authid) empty. + - Fix detection of matching path names in X2GoIniFiles. + - Make sure X2GoClientXConfig config file really gets written to disk + (after we changed the internas of X2GoIniFile for this new major release). + - Rename hook method HOOK_no_known_xserver_found to + HOOK_no_installed_xservers_found. Call this new hook if no installed + X-Servers could be found on the system. + - Only check running X-Servers that have the same WMI SessionId as the + current X2Go application. + - Session profiles: default value type for exports session profile option + is an empty dictionary. + - Make X2GoClient's constructor aware of non-usable X-Server ports. + - Windows: Fix crash while attempting to find the session window. + - Support SSH proxy autologin feature of X2Go Session Broker. + - Provide Telekinesis support in Python X2Go. + - Stop manipulating session profiles in X2GoSshProxy class. Esp. stop + manipulating session profiles with deprecated session options. + - Type-hardening of X2GoSshProxy class. Accept hosts as list and strings. + If hosts are given as a list, a random list element will be taken as + host (for connecting and for the SSH proxy tunnel setup). + - Type-hardening of X2GoControlSession class's C{connect()} method. + Handle hostnames that come in as lists gracefully. + - Don't construct the sshproxy_tunnel parameter in x2go/utils.py. Leave + that to higher level classes that know more about X2Go internals. + - Add support for a subsystem string when setting up port forwarding + tunnels. + - Use gevent to spawn the TeKi client start-up process (instead of waiting + for it to return). + - Provide support for new session parameter: clipboard. (Fixes: #508). + - Split up NX output and NX errors into two separate files. + - Silent ignore it if we cannot detect the local Xlib.display.Display() + instance (happens with polyinstantiated /tmp dirs). + - Don't start telekinesis client if not support server-side. Don't attempt + at starting telekinesis client, if it is not installed. + - Disallow server-side users to override X2Go Server commands via + ~/bin (or similar). (Fixes: #334). + - Handle non-available color depth in X2Go session name gracefully. + (Fixes: #358). + - Make sure that the x2gosuspend-session/x2goterminate-session commands + are sent to the X2Go Server before we take down the NX proxy subprocess. + - Create a "session.window" file in the session directory. This file for now + contains one line "ID:". The file appears once a session window + comes up (start/resume), and disappears once the session window closes + (suspend/terminate). + - Only enable Telekinesis client debugging if the logger instance is in + debug mode. + - Performance tests have shown, that enabling SSH compression is not a + good idea. NX should handle that instead (and does). + - Better control the startup bootstrap of the Telekinesis client + subsystem. + - Newly understand our own Paramiko/SSH forwarding tunnel code. Become + aware of handling multiple connects on the same tunnel. + - Rename LICENSE.txt to COPYING. + - Be more exact when detecting the NX proxy window id. + - On non-Windows platforms, enforce usage of the "ares" DNS resolver in + python-gevent (which is available since Python gevent 1.0~). (Fixes: + #588). + - Use Xlib to detect client-side destop geometry. + - For reverse port forwardings use IPv4 localhost address only. + - Assure proper NX Proxy cleanup when sessions suspends/ + terminates. + - Assure proper Telekinesis client cleanup when sessions suspends/ + terminates. + - Clean up terminal sessions properly when the clean_sessions() method + of the control session has got called. + - Don't use compression on TeKi sshfs mounts. + - Handle duplicate profile names gracefully (i.e. append a " (1)", + " (2)", ... to the session profile name). (Fixes: #500). + - Support server-side Telekinesis versions that ship their own + (teki-)sftpserver. + - Use session_name, not session_info object's __str__() method to obtain + session name (in X2GoTelekinesis). + - Handle socket errors on the reverse port forwarding tunnels more + gracefully. + - Handle sudden control session death during local folder sharing + gracefully. + - Don't choke on non-initialized SSH transport objects when initializing + SFTP client. + - Fix transport lock release in X2GoControlSession._x2go_sftp_put(). + - Fix session lock release in various methods of the X2GoSession class. + - Release _share_local_folder_lock on instance X2GoTerminalSession + destruction. + - Detect non-installed sshfs (required for Telekinesis). + - X2GoControlSession: Don't mess with the associated_terminals dict if + the control session has already died away (i.e. been forcefully + disconnect). + - If the listsessions command detects a terminated or suspended session, + we have to destroy the corresponding X2GoTerminalSession() to trigger + a proper cleanup of that instance. + - Fix various hrefs in __doc__ strings. + - Fix creating/renaming/reconfiguring session profiles. Handle host + option properly (as list). + - Make sure we do a deepcopy of the default session profile parameters. + - Detect more exceptions in the requests module when authenticating against a + session broker. + - Only convert the value of the export session profile option if not + already a Python dictionary. + - Capture X2GoControlSessionException occurrences during client-side folder + sharing initializaation while starting/resuming a session. + - X2GoSessionRegistry: Don't report about sessions that have a not yet + fully assigned session name / profile name / profile id. + * debian/control: + + Add dependencies: python-requests, python-simplejson. + + Add R (python-x2go): sshfs. + + Add S (python-x2go): telekinesis-client, mteleplayer-clientside. + + Update D (python-x2go): python-paramiko (>= 1.15.1-0~). (Fixes: #602). + * python-x2go.spec: + + Add dependencies: python-requests, python-simplejson. + + Additionally adapt to building on openSUSE/SLES. + + Add all python packages under R to BR (for epydoc run). + + Update R for python-x2go: python-paramiko >= 1.15.1. + + [ Mike DePaulo ] + * New upstream version (0.5.0.0): + - Windows: Fix compatibility with PulseAudio 3.0 & later (Fixes: #532) + - Windows: Prevent high PulseAudio CPU usage on Windows XP by lowering + PulseAudio's CPU priority from "high" to "normal" on XP specifically. + Also do so on Windows Server 2003 (R2) (Fixes: #537) + + -- Mike Gabriel Mon, 20 Oct 2014 12:40:34 +0200 + +python-x2go (0.4.0.10-0x2go1) UNRELEASED; urgency=low + + [ Mike Gabriel ] + * New upstream version (0.4.0.10): + - Don't pass default values to ConfigParser constructor when initializing + an INI file. + - Return color depth on MS Windows machines. + - Preserve class-wide control session options (like add_to_known_hosts, + forward_sshagent, etc.). Thanks to Dick Kniep for reporting this. + + -- Mike Gabriel Sun, 05 Jan 2014 16:35:57 +0100 + +python-x2go (0.4.0.9-0x2go1) unstable; urgency=low + + [ Mike Gabriel ] + * New upstream version (0.4.0.9): + - Agent channels in Paramiko can raise an EOFError if the connection + has got disrupted. Ignoring this. + - Store the session password in base64 encoded string in order to make + it harder spotting the long term stored (for the duration of the session) + plain text password. + - Support encryption passphrases on SSH private key files (X2Go SSH + connections as well as SSH proxy connections). + - Invalidate SSH private keys (filename, pkey object) when look_for_keys is + requested. + - Keep private key information even if force_password_auth is set in the + control session's connect() method. + - Fix parameter handling in X2GoSession.connect(). + - Rewrite passwords that are not string/unicode to an empty string. + - No Unicode chars in log messages. Eliminated one more in checkhosts.py. + - Implement two-factor authentication. + - Compat fix in _paramiko monkey patch module to also work with early + Paramiko versions. + - Handle echoing ~/.*shrc files gracefully via SSH client connections. Do + not allow data injections via ~/.*shrc files. (Fixes: #335). + - Properly handle (=expand) the "~" character in key filenames. (Brought to + attention by Eldamir on IRC. Thanks!). + - Differentiate between desktop sharing errors and desktop sharing access + that gets denied by the other/remote user. + - Report about found session window / session window retitling in debug + mode. + - Fix session window detection when local session manager is the i3 session + manager (which uses _NET_CLIENT_LIST_STACKING instead of + _NET_CLIENT_LIST). + - Check for pulse cookie file in old (~/.pulse-cookie) and new + (~/.config/pulse/cookie) location. + - Import python-x2go-py3.patch from Fedora. Thanks to Orion!!! + - Improve setup.py script: make it run with Python3 and older Python2 + versions. + - Fix tests for two-factor authentication in control session and SSH proxy + code. + - Fix regression: Make password logins with PyHoca-CLI succeed again. + - Make channel compression to all authentication methods. + - Set keepalive on proxy channel. + - Only use []: if is not 22. + - Handle host key checks for hosts that do not have a port specified. + * debian/source/format: + + Switch to format 1.0. + * python-x2go.spec: + + Ship python-x2go.spec (RPM package definitions) in upstream project. + (Thanks to the Fedora package maintainers). + + Clear (Fedora package) changelog. + + Drop dependency on python-cups. + + [ Orion Poplawski ] + * debian/control: + + Drop python-cups from Depends: field. Python CUPS is no dependency if + Python X2Go. (Fixes: #329). + + [ Kenneth Pedersen ] + * New upstream version (0.4.0.9): + - Color depth detection: Stop using win32api.GetSystemMetrics(2) which actually + returns the width of a vertical scroll bar in pixels. Instead, create a screen + display context and query it for the color depth. (Fixes: #330). + + -- Mike Gabriel Wed, 08 Jan 2014 15:14:16 +0100 + +python-x2go (0.4.0.8-0~x2go1) unstable; urgency=low + + * New upstream version (0.4.0.8): + - Fix session profile updates with changes to the host parameter. + - Use the session object's lock to detect if updating the session + status in the session registry is something appropriate to do. + - Do not overwrite not-yet-suspended terminal session objects during + session resumption. + - Remove shbang from x2go/tests/runalltests.py, as well. + + -- Mike Gabriel Wed, 07 Aug 2013 12:17:27 +0200 + +python-x2go (0.4.0.7-0~x2go1) unstable; urgency=low + + [ Mike Gabriel ] + * New upstream version (0.4.0.7): + - Drop duplicate method in terminal backend: is_desktop_session(). + - Ignore non-registered session UUIDs in X2GoClient.clean_sessions() method. + - Make _update_mounts in session registry cache more failsafe, this probably + fixes an accumulation of server disconnects observed in recent version of + Python X2Go. + - Add some sanity checks before actually starting to add a session profile. + * /debian/control: + + Replace LDAP support with session brokerage support in LONG_DESCRIPTION. + + [ Orion Poplawski ] + * New upstream version (0.4.0.7): + - Remove shbangs from python-x2go library code. (Fixes: #283). + + -- Mike Gabriel Sat, 03 Aug 2013 22:28:13 +0200 + +python-x2go (0.4.0.6-0~x2go1) unstable; urgency=low + + * New upstream version (0.4.0.6): + - Use XFCE as desktop environment in example scripts. + - Make Python X2Go aware of the MATE desktop environment. + - When resuming sessions be tolerant towards disrupted connections. + + -- Mike Gabriel Sun, 28 Jul 2013 19:42:52 +0200 + +python-x2go (0.4.0.5-0~x2go1) unstable; urgency=low + + * New upstream version (0.4.0.5): + - Paramiko monkey patch: Hostnames with the default SSH_PORT are stored + in hostname-only format to the known_hosts file. Fixes redundant requests + for confirming the remote host's fingerprint if port 22 is used. + - Improve log message when setting up port forwarding tunnel. + + -- Mike Gabriel Thu, 18 Jul 2013 22:32:11 +0200 + +python-x2go (0.4.0.4-0~x2go1) unstable; urgency=low + + * New upstream version (0.4.0.4): + - Save exports in session profile directly after mounting/unmounting a share + if the session profile parameter restoreexports is set. + - Fix the restoreexports logic on mount/unmount/unmount all requests. + Make sure client-side offline network shares do not get purged from the + session profile configuration if unavailable. (Fixes: #192). + - Become aware of fixed paramiko features since paramiko 1.11.0. Stop + monkey patching those methods that got fixed in 1.11.0. + - Ignore KeyError exceptions in session cache for suddenly removed cache items. + Silence/fix some race conditions on connection failures. + - Support mounting client-side folders on UNC paths. + - Enable keepalive callbacks on open SSH client connections. + - Only do x2golistmounts calls for the session cache on running sessions. + - Fix renaming of profile names. + + -- Mike Gabriel Tue, 18 Jun 2013 20:27:42 +0200 + +python-x2go (0.4.0.3-0~x2go1) unstable; urgency=low + + * New upstream version (0.4.0.3): + - Fix inheritance of Paramiko/SSH exception. + - Make Python X2Go aware of the Cinnamon desktop shell. + - Fix faulty value creations for the export session profile parameter. + (Fixes: #162). This issue occured when the restoreexports feature + had been activated in the session profile. + - Prevent Exception when creating new session profiles. Spotted by + Dick Kniep. Thanks! + + -- Mike Gabriel Sun, 21 Apr 2013 22:40:43 +0200 + +python-x2go (0.4.0.2-0~x2go1) unstable; urgency=low + + * New upstream version (0.4.0.2): + - Empty session profile name/id cache when adding new profiles. (Fixes: + #130, #147). + + -- Mike Gabriel Sun, 03 Mar 2013 15:55:57 +0100 + +python-x2go (0.4.0.1-0~x2go1) unstable; urgency=low + + * Documentation fix and typo fix relevant for win32 build. + + -- Mike Gabriel Wed, 13 Feb 2013 12:36:50 +0100 + +python-x2go (0.4.0.0-0~x2go1) unstable; urgency=low + + [ Orion Poplawski ] + * New upstream version (0.4.0.0): + - Importing all of x2go in setup.py causes rpmbuild problems due to + DISPLAY not being set. It is overkill as well, causing extra dependencies + to be installed at build time. (Fixes: #91). + + [ Mike Gabriel ] + * Bump version to 0.4.0.0. + * WARNING: starting with version 0.4.0.0 of PyHoca-GUI, PyHoca-CLI and Python + X2Go, all class identifiers are now X2Go..., not X2go... anymore. + * New upstream version (0.4.0.0): + - Add session profile option ,,display'' to default session profile options. + - Catch any kind of exception when writing session profile files and return + True or False in cases where I/O errors occur. + - Avoid the known_hosts file being flushed with localhost:[] + entries. Store host keys of SSH-proxied hosts under the [
]: + the system has _behind_ the SSH proxy gateway. (Hopefully fixes: #18, + partially fixes: #53). + - Add session profile option: uniquehostkeyaliases. Allow the + (by-design-unique) X2Go session profile ID to be a representative for + :. Update session profile IDs on hostname changes. + Re-arrange class structure for MissingHostKey policies, also provide an + X2goAutoAddPolicy class. + - Fix auto-starting and auto-resuming of sessions. + - Avoid false-positive notifications of dead control session directly after + a disconnect request from the user. + - Improve desktop sharing code. Add code to obtain version information of + server-side X2Go components. + - Add session type filter for list of sharable desktops. + - Sort X2Go feature list, add force option for X2GoClient queries of server + features and server components. Add alias get_server_components (for + get_server_versions). + - Add low latency support for link types 'modem' and 'isdn'. Selecting + either link quality will double nearly all connection timeout values. + (Fixes: #53). + + -- Mike Gabriel Tue, 12 Feb 2013 08:41:52 +0100 + +python-x2go (0.2.1.1-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.1.1): + - Make sure that internal calls to _X2goClient__list_sessions are not + overridden by other method definitions in classes that inherit from + X2goClient class. (Fixes: #89). + - Rewrite colour depth of 17bits to 16bits when invoking rdesktop. + Relevant for X2Go-proxied RDP sessions started with PyHoca-GUI under + Windows. + - Handle control sessions being None in session list cache. + - In cases where several session profiles connect to the same machine + under the same user ID, we have to strictly differentiate between + running/suspend sessions associated to the several connected session + profiles. + - Make connection disruptures more robust. (Fixes: #23). + + -- Mike Gabriel Tue, 18 Dec 2012 12:36:34 +0100 + +python-x2go (0.2.1.0-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.1.0): + - Prepare for staying compatible with new SSH proxy feature + in X2Go Client. + - Add sshproxy_port option to session (SSH proxy) options. + - Allow smooth session profile migration, prefer : (set + via sshproxy_host) for SSH proxy port detection. + - Implementation of session profile parameters ,,sshproxysameuser'' + and ,,sshproxysameauth''. + - Fixing typos in __doc__ strings. + - Add support for starting maximized session windows. (Fixes: #12). + - Fix category ,,Utility'' when parsing .desktop files. + - For ,,maxdim'' session property fallback to _NET_DESKTOP_GEOMETRY if + _NET_WORKAREA is not available from the window manager. + - Mention ,,maximize'' as possible value for session option geometry + in __doc__string of class X2goTerminalSessionSTDOUT. + - Implement SSH agent authentication forwarding. + - Implement X2Go session profile features ,,autologin'' and + ,,sshproxyautologin'' (meaning: look_for_keys and allow_agent in Python + Paramiko terms). + - Make X2goClient instance available in initial X2goSession instances. + - Allow post-initialization updating of forward_sshagent class property. + - Enable autologin and sshproxyautologin for new session profiles. + - Only monkey patch Python Paramiko based on the currently used Paramiko + version (our monkey patches have been sent upstream, so we might not + need the monkey patching for paramiko >= 1.8.0 anymore). + - Rename session type XFCE4 to XFCE (using an unversioned name). + - Avoid false positive notifications about started-by-other sessions. + - Introduce locks for session registrations. + - Wait for mounting of print and mimebox spooling share. + - Allow mixing key file, key object, key discovery and agent + authentication. + - Add progress bar support for session startup / resuming. (Fixes: #14). + - Set the session name in case a session start failed due to lack of + forwarding tunneling support in the server's SSH daemon. + - Fall back to password auth if agent auth and key discovery fail. + - Give the session window more time to appear. + - Catch rare condition in utils.find_session_window in case where + the list of window IDs is not available. + - Disable SSH agent forwarding on MS Windows platform for current + versions of Python Paramiko. + - Allow usernames containing space characters in user names (common + on MS Windows). + - Use threading.Lock to prohibit simultaneous calls of + get_published_applications() of control sessions. + - Implement some internal locking for X2goSession objects. + - Add option to disable auto-registration of pubapp sessions. + - Implement functionality for restoring mounted shares on session + resumption / re-start. Sponsored by Dick Kniep, LinDix NL. + - Catch exceptions where a user tries to resume a session that has + just been removed from the session list on the server (race + condition). + - Consolidating management of shared and unshared client-side + folders. + - Before suspending/terminating a session, make sure that the list of + shared folders is up-to-date. + - Fix password authentication in case no private RSA/DSA key for a + client user exists. + * /debian/rules: + + Allow package build on systems with missing dh_python2. + * /debian/control: + + Versioned depend on python-paramiko (>= 1.8.0-0~). + * /debian/pyversions: + + Drop file as it is deprecated. + + -- Mike Gabriel Mon, 10 Dec 2012 13:01:51 +0100 + +python-x2go (0.2.0.10-0~x2go2) unstable; urgency=low + + * /debian/control: + + Allow build on Ubuntu 10.04 (reduce Python version in Build-Depends). + + -- Mike Gabriel Tue, 25 Sep 2012 20:14:03 +0200 + +python-x2go (0.2.0.10-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.0.10): + - Use session type 'D' for X2Go-proxied RDP session when run in + fullscreen mode. (Fixes: #27). + - Indentation fix in defaults.py. + - Make Python X2Go aware of DirectRDP settings in session profiles. + - Ignore DirectRDP options in session profile config for now. + - Remove bashism from X2Go server command calls. Caused servers + with no installed x2goserver-xsession package to crash desktop + sessions (observed with KDE4 on Ubuntu 12.04). + - Handle 16bit and 17bit colour depth (Windows clients) as + equal when resuming sessions. + * Maintainer change in package: X2Go Developers . + + -- Mike Gabriel Tue, 25 Sep 2012 16:03:24 +0200 + +python-x2go (0.2.0.9-0~x2go2) unstable; urgency=low + + * Make sure we can build against python-gevent provided on + packages.x2go.org. + + -- Mike Gabriel Fri, 17 Aug 2012 18:13:19 +0200 + +python-x2go (0.2.0.9-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.0.9): + - Become fully functional for users that have SHELLs than sh or bash + on remote X2Go server. + * Import packaging file from Debian package. + + -- Mike Gabriel Fri, 17 Aug 2012 16:17:26 +0200 + +python-x2go (0.2.0.8-0~x2go2) unstable; urgency=low + + * New upstream version (0.2.0.8): + - Catch IOError exceptions during SFTP client operations. + - Proper use of except statement for multiple exception catching. + + -- Mike Gabriel Mon, 23 Jul 2012 21:16:28 +0200 + +python-x2go (0.2.0.7-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.0.7): + - Refresh server feature list on re-connecting (log-off, log-on). + - Fix property method X2goControlSession._x2go_remote_home on broken + connections. + - Make sure SSH proxy sessions get torn down on control session disconnect + no matter what happens to the control session itself. + + -- Mike Gabriel Thu, 12 Jul 2012 21:27:24 +0200 + +python-x2go (0.2.0.6-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.0.6) + - Ignore non-master sessions before calling the foldersharing- + not-available hook. + - Add several double underscore method aliases in X2goSession class. + - Add several double underscore method aliases in X2goClient class. + - Avoid double notifications on SSHFS being unavailable for the + authenticated user. + - X2goSession.HOOK_printing_not_available(): Fix log message. + + -- Mike Gabriel Mon, 02 Jul 2012 20:54:37 +0200 + +python-x2go (0.2.0.5-0~x2go1) unstable; urgency=low + + * Bugfix release (0.2.0.5): + - Fix for building Python X2Go in pbuilder environment. Catch + Xlib.error.DisplayConnectionError and ignore it. Now the real fix!!! + + -- Mike Gabriel Tue, 12 Jun 2012 15:50:17 +0200 + +python-x2go (0.2.0.4-0~x2go1) unstable; urgency=low + + * Bugfix release (0.2.0.4): + - Fix for building Python X2Go in pbuilder environment. Catch + Xlib.error.DisplayConnectionError and ignore it. + + -- Mike Gabriel Tue, 12 Jun 2012 10:09:26 +0200 + +python-x2go (0.2.0.3-0~x2go1) unstable; urgency=low + + * Bugfix release (0.2.0.3): + - Only notify HOOK_auto_connect, if the session really is configured + to auto-connect (while at the same time no SSH key is present). + + -- Mike Gabriel Sun, 10 Jun 2012 21:22:47 +0200 + +python-x2go (0.2.0.2-0~x2go1) unstable; urgency=low + + * Bugfix release (0.2.0.2): + - Be tolerant if we can not terminate a session after failure of the + forwarding tunnel. + - Improve session management, handle exceptions more gracefully. + - Ignoring timeouts for x2golistmounts and x2golistdesktops. + - Add support to X2goSession class to launch sessions from the Python + interactive shell in five steps. + - Mark sessions as dead whenever an X2goControlSessionException occurs. + - Catch control session deaths when querying X2goSession.is_alive(). + - Do not create a high CPU load after a network failure, do not try to + execute a remote command if the session has already died away. + - The master_sessions dict may never have None values. + + -- Mike Gabriel Fri, 08 Jun 2012 09:19:49 +0200 + +python-x2go (0.2.0.1-0~x2go1) unstable; urgency=low + + * Bugfix release (0.2.0.1) + - Re-add lost line in control session's connect method that let + SSH host key checks fail. + + -- Mike Gabriel Wed, 30 May 2012 00:25:59 +0200 + +python-x2go (0.2.0.0-0~x2go1) unstable; urgency=low + + * New upstream version (0.2.0.0) + - License change upstream: GPLv3+ -> AGPLv3+ + - Add support for session window title renaming. + - Add support for bringing session windows on top (MS Windows only, + for now) + - Terminal sessions now remember the X window of a terminal session as + an internal property. + - Fix many undefined symbols reported by Debian developer + Jakub Wilk. (THANKS!) + - Add default value for new session profile parameter xinerama (ignored by + Python X2Go for now). + - Replace any non-code string ,,X2go'' by ,,X2Go''. + - Add support for session port re-allocation on session resume (feature of + x2goserver >= 3.1.0.0). + - Provide client-side cache of shared local folders, detect server-side + unsharing of client-side folders. + - Introduce concept of master sessions per profile to X2goClient class. Only + the master session can mount/unmount client-side shared folders. + - Fix IndexError if x2gomountdirs did not deliver any of the expected + results to stdout. + - Handle session titles that just contain blanks (e.g. " ") gracefully. + - Fix X2Go desktop sharing support. + - New feature: allow sessions on localhost system. + - Tolerate user names containing "-" characters. + - Provide hook methods for SSHFS failures (local folder sharing, printing, + MIME box). + - Code cleanup: remove all unnecessary imports. Stop defining variables that + get never used. + - Rename control session method is_folder_sharing_available to + is_sshfs_available. + - Draw all Xlib code into utils.py + - Fix faking of WindowsError exception object in printactions.py and + mimeboxactions.py. + - Amend list of default session options. + - Update list of unsupported session options. + - Retrieve feature list from X2Go server per session. + - Add published applications support. + - Handle empty control session in the session list cache. + - Render and cache dictionary based published applications menu tree in + Python X2Go. Cache the tree once rendered. + - Fix availability check of client-side folder sharing. + - Add support for published applications with no category submenus. Fix + default language in published applications menu tree. + - Fix X2Go printing, do not spawn a gevent process for printing. + - Make published_applications_no_submenus an session option that + controls how many menu items will be shown without rendering category + based submenus. + - Better support auto-resuming and auto-starting of session with Python + X2Go. + - Include development location of nxproxy in possible file locations (only + takes effect on Windows). + - Add X2goClient method is_profile_connected. + - Fix auto_start_or_resume method when using SSH proxy with interactive + authentication. + - Provide default value for PUBAPP_MAX_NO_SUBMENUS in + defaults.py. + - On MS Windows: make shipped VcXsrv-Server known to Python X2Go. + The shipped VcXsrv has to rest in $CLIENTAPPDIR/VcXsrv/vcxsrv.exe. + - On MS Windows: make VcXsrv-Server at the development location + known to Python X2Go. + - Normalize paths to configuration files. + - Make new ini config defaults available in configurations, update list + of known X-Servers if new ones are provided in defautlts.py. + - Provide function merge_ordered_lists in utils.py, merge list of + default known_xservers with configured known_xservers. + - Make sure xconfig configuration changes provided by defaults.py get + written to the X configuration file. + - Add X2goClient method is_session_profile(), return registered session + for a specific session name if it has already been registered instead + of registering a new session. + - Provide X2goClient method get_session_info(), do not auto start/resume + sessions in published applications mode, provide hook method for + auto-connecting interactively. + - Provide X2goSession method get_session_profile_option(). + - Fix unexpected keyword error during connect() in X2goSession instance. + - Fix metatype detection of session profiles. + - Implement X2Go-Top category in .desktop files that get used in the + context of published applications. + - If the SSH proxy connection tries to bind to a used port, detect an + unused local port and write this port change to the session profile. + - Use double-quotes for pulseaudio options. + - Export X2GO_SESSION and PULSE_CLIENTCONFIG to published applications. + - When starting pulseaudio on Windows use --exit-idle-time=-1. + - Fix X2goSession.get_shared_folders() method on Windows. + - No list_sessions() calls on server when querying the status of an + X2goSession instance. + - Fix automatic mounting of Windows-stylish shared local folders. + - Transform blanks in mount points into underscores. + - Provide X2goClient.get_published_applications() method. + - Fill session profile configurations with missing default values and then + detect the profile meta type. + - Support published applications that have to be run in a terminal window. + - Make sure that pulseaudio.exe has its PID directory. Otherwise it will + fail to start the first time if the user is new to X2Go. + - Silence warnings that occur during session info queries in case a session + startup has not yet been completed fully. + - On MS Windows: Use nxproxy-3.5.0.12 when testing Python X2Go applications. + - On MS Windows: Use VcXsrv-1.12.0.1 when testing Python X2Go applications. + - On MS Windows: If the configured X-server display port is already in use, + try to detect the next available display number. + - Make transitions of master sessions more robust. Only allow local folder + sharing for running sessions. + - On MS Windows: Re-use a left behind stray X-server that might have not + get killed by a previous instance of Python X2Go Client. This trick is + nasty, but works around faulty abortion of client implementations. + - On MS Windows: Handle detection of free TCP/IP X display port far more + intelligently. + - On unused port detection bind to 127.0.0.1 by default. + - Provide X2goSession.get_session_type() method. + - Do not call HOOK method if self.allow_share_local_folders is False. + - Try to derive language information from X2goClient instance. + - Make timeout on command execution customizable. + - Make sure path names in X2goPrintActions and X2goMIMEboxActions get + normalized to OS-specific path names. + - Protect session cache from deletion while being processed. + - Adapt python-x2go to launching Unity-2d on Ubuntu precise. + - Ignore X windows with empty title while finding session window.. + - Catch exceptions while calling SSH transport's getpeername() method. + - Fix control session failure notifications. Show them immediately after + the connection has broken. + - Allow custom commands to be desktop sessions. + - X2goSession instances cannot raise X2goClientExceptions. + - Be more tolerant against suspension failures while taking over a session. + - Use Paramiko/SSH transport compression if available. + - Prohibit simultaneous calls to terminal_session.share_local_folders(). + - Cache SSH transport's getpeername() and get_username(). + - Catch session startup failures due to faulty port forwarding tunnels + and make them notifiable via hooks. + - Properly set setkbd value for x2gostartagent and x2goresume-session. + - Add support for re-registering sessions after session profile changes. + - Add new session profile parameter: ,,variant''. Add support to set the + keyboard layout _and_ the keyboard variant from the client-side. + - Give functionality to the ,,setdpi'' and the ,,dpi'' session profile + parameter (setting the DPI allows font scaling). + - Use proper locking of session actions that are critical to being executed + in parallel. + * Depend on python-xlib. + * The Python setuptools module does not have to be installed as dependency + with python-x2go. + + -- Mike Gabriel Tue, 29 May 2012 16:51:25 +0200 + +python-x2go (0.1.1.9-0-x2go1) unstable; urgency=low + + * New upstream version (0.1.1.9), bugfix release for 0.1.1.x series: + - Ignore session registry exceptions for profiles that just got disconnected. + - Fix exception raisal in X2goTerminalSessionSTDOUT. + - Print access to an X2Go server is not controlled by x2goprint group membership, + but by fuse membership. + - Add XFCE4 support. + - Add ,,autostart'' parameter to default session profile parameters. + - Add support for session window title renaming from client-side. + - Introduce additional session profile parameter: setsessiontitle. + - Fix for list processing in INI files. + - Make terminal backend ,,applications'' aware. + - Allow session parameter change for already registered sessions. + + -- Mike Gabriel Fri, 27 Jan 2012 23:36:13 +0100 + +python-x2go (0.1.1.8-0-x2go1) unstable; urgency=low + + [ Mike Gabriel ] + * New upstream version (0.1.1.8), bugfix release for 0.1.1.x series: + - Bugfix for: Test for existence of remote home directory on connect. + - Unshare local folders during session cleanup. + - Remove local session cache folders after sessions have terminated. + - Fix missing import of socket module in backends/control/_stdout.py. + - Catch failures on sftp_write in control session instance. + - Always disconnect from X2goSession instance. + - Use random passwords for checking SSH host keys. + - Fix duplication of SSH keys in known_hosts file, use hashed hostnames in + known_hosts file. Make sure SSH keys written to known_hosts file are + available to other SSHClient instances immediately. + + -- Mike Gabriel Wed, 12 Oct 2011 10:54:23 +0200 + +python-x2go (0.1.1.7-0-x2go1) unstable; urgency=low + + [ Mike Gabriel ] + * New upstream version (0.1.1.7), bugfix+feature release for 0.1.1.x series: + - Add support for x2goumount-session calls. + - Differentiate between spool folders and data folders when unsharing + all folders, return exitcode from X2goTerminalSessionSTDOUT.unshare_* + methods. + - Use TCP_NODELAY socket option for audio rev forwarding tunnels. + - Use TCP_NODELAY socket option for graphics forwarding tunnels. + - Typo fixes in session.py, related to calling _X2goSession__disconnect + method. + - Compatibility fix for X2Go folder sharing (session profile attribute: + export). + - Provide test method to query server if folder sharing is available. + - Test for existence of remote home directory on connect. + + [ Dick Kniep ] + * Fix for upstream version 0.1.1.7: + - Typo in utils.py (true instead of True). + + -- Mike Gabriel Sun, 25 Sep 2011 02:08:01 +0200 + +python-x2go (0.1.1.6-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.1.6), bugfix release for 0.1.1.x series: + - Fix IPv4 enforcement for localhost connections. + - Be tolerant against trailing whitespaces in hostnames. + - Fix handling of lists in session profiles (i.e. ini files). Fixes breakage + with x2goclient's rootless vs. desktop mode after pyhoca-gui has been used. + + -- Mike Gabriel Wed, 14 Sep 2011 21:35:54 +0200 + +python-x2go (0.1.1.5-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.1.5), bugfix release for 0.1.1.x series: + - Call hook method instead of exception raisal if session startup has failed. + - Handle full path command string appropriately. + - Fix for X2goSessionRegistry.forget method, do not complain if session_uuid + is already forgotten. + - If sound is set to false in session profile use snd_system='none' in + terminal session. + - Stabilize sshfs related problems in case remote user is not in fuse group. + - Do not ignore usekbd session profile option anymore. + - Fix for X2goSession.has_terminated method. + - Fix for executing commands with arguments that contain a slash (thanks to Dick + Kniep for digging this out). + - Make X2goTerminalSessionSTDOUT.has_command resistable against empty command + strings. + - Catching faulty x2gostartagent behaviour. + + -- Mike Gabriel Sun, 11 Sep 2011 18:31:28 +0200 + +python-x2go (0.1.1.4-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.1.4): + - bugfix for x2gmountdirs calls that occurred if the client username contained blanks. + + -- Mike Gabriel Mon, 18 Jul 2011 13:51:02 +0200 + +python-x2go (0.1.1.3-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.1.3): + - README/TODO update. + - Desktop sharing: try ''@.0'' additionally to ''@'' when + trying to find a desktop for sharing. + - Fix AttributeError if no graphical proxy instance has been declared yet. + - Do not allow any interruption during X2Go cleanup calls. + - Stabilize desktop sharing if the remote session is not available. + - Enforce IPv4 on all SSH proxy and other SSHClient connections when connecting to + ''localhost''. + - Detect SFTP client connections failures, abort session if that happens. + + -- Mike Gabriel Wed, 06 Jul 2011 22:15:01 +0200 + +python-x2go (0.1.1.2-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.1.2): + - bugfix version + - Use X2goRegistryException for session query for non-existing sessions. + - Catch this exception in X2goClient. + - Fix desktop sharing. + - Improve error handling / logging in forward.py. + - Add X2goSession method that detects if auto-connecting a session profile + is probably possible. + - Fix MIME box action SAVEAS. + - Fix for session status notification for sessions with PENDING terminal session. + + -- Mike Gabriel Fri, 01 Jul 2011 14:53:43 +0200 + +python-x2go (0.1.1.1-0~x2go1) unstable; urgency=low + + * New bugfix version (0.1.1.1): + - Fix for local_color_depth function if no $DISPLAY is set. + - __doc__ string fixes. + + -- Mike Gabriel Fri, 24 Jun 2011 02:25:10 +0200 + +python-x2go (0.1.1.0-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.1.0): + - Add X2Go desktop sharing support. + - Fix SSH authentication failures (close session on failure). + - Close SSH connection first, then close down SSH proxy. + - Make sure SSH proxy password gets forgotten between two sessions. + - Add X2goSession status property ,,faulty''. + - Make sure list session and list desktop commands always return. + - Rely on X2goSessionListInfo backend to handle exceptions appropriately. + - Assure that rev forwarding tunnels use IPv4 (replace localhost with 127.0.0.1). + - Explicitly tunnel over IPv4 for NX proxy. + - Make cache more configurable (session list updates, desktop list updates). + - Adds an auto_update_listdesktops_cache to X2goClient constructor kwargs. + - Fix multiple notifications for the same session state change, reliably + differentiate between found sessions after connect and newly started + sessions from another client. + - Mark terminals as PENDING even before the X2goTerminalSession object is + created. + - Change of sleep times when starting/stopping NX proxy. + - Make fw tunneling more robust against failures. + - Test focus put on client inter-operation. It is reliably possible now to move + sessions between different clients without resume failures. + - Add X2goSession lock support. + - Skip session auto registration during startups of new sessions (avoids + duplicate sessions in the session registry. + - Do not start X2Go service tunnels (audio, sshfs) if session startup failed. + - Fix NX proxy startup post-check. + - Force 16bit colour depth for RDP-proxy sessions. + - Faulty sessions (without a NX proxy fw tunnel) will get terminated whenever + the X2Go server (SSHd) denies the tunnel setup. + - Detect local color depth and use it as default for new sessions. + - Add compatibility check methods for color depth. + - Provide X2goClient method for retrieval of session by session name. + + -- Mike Gabriel Fri, 24 Jun 2011 01:48:57 +0200 + +python-x2go (0.1.0.3-0~x2go1) unstable; urgency=low + + * New upstream version (0.1.0.3): + - Make SSH proxy code more robust against connection failures. + - Make sure, SSH sessions get closed when not needed any more. + + -- Mike Gabriel Wed, 08 Jun 2011 16:18:45 +0200 + +python-x2go (0.1.0.2-0~x2go1) unstable; urgency=low + + * new upstream version (bugfix release 0.1.0.2): + - fix for creation of new session profile list + + -- Mike Gabriel Fri, 27 May 2011 14:21:51 +0200 + +python-x2go (0.1.0.1-0~x2go1) unstable; urgency=low + + * new upstream version (bugfix release 0.1.0.1): + - fixes encoding problem on systems that cannot provide an encoding type + * adds locales as dependency + + -- Mike Gabriel Fri, 27 May 2011 13:07:23 +0200 + +python-x2go (0.1.0.0-0~x2go1) unstable; urgency=low + + * new upstream version (0.1.0.0): + - adds/updates many of the __doc__ strings + - fix for multiple session notifications + - fix for failing MIME box when resuming sessions + + -- Mike Gabriel Wed, 25 May 2011 19:56:57 +0200 + +python-x2go (0.0.45.0-0~x2go1) unstable; urgency=low + + * new upstream version (0.0.45.0): + - fixes local folder sharing after session resumption + + -- Mike Gabriel Mon, 23 May 2011 22:48:46 +0200 + +python-x2go (0.0.44.2-0~x2go1) unstable; urgency=low + + * new upstream (0.0.44.2): + - fixes location for test scripts + - repair of exmample test scripts (we neeeed tests!!!!!! -> TODO) + + -- Mike Gabriel Sat, 21 May 2011 23:17:06 +0200 + +python-x2go (0.0.44.0-0~x2go1) unstable; urgency=low + + * new upstream features (0.0.44.0): + - adds get_session_server_hostname method to X2goClient class + - removes (for now) group membership check on server (x2gousers) + + -- Mike Gabriel Tue, 17 May 2011 10:35:09 +0200 + +python-x2go (0.0.43.0-0~x2go2) unstable; urgency=low + + * fixes control file + + -- Mike Gabriel Tue, 26 Apr 2011 00:48:30 +0200 + +python-x2go (0.0.43.0-0~x2go1) unstable; urgency=low + + * new upstream features (0.0.43.0): + - renames feature x2godropbox to x2gomimebox + * epydoc build now runs during package build + + -- Mike Gabriel Tue, 26 Apr 2011 00:44:24 +0200 + +python-x2go (0.0.42.0-0~x2go2) unstable; urgency=low + + * re-arranged the debian folder quite a bit when + testing/building with pbuilder + + -- Mike Gabriel Mon, 25 Apr 2011 21:56:46 +0200 + +python-x2go (0.0.42.0-0~x2go1) unstable; urgency=low + + * switches to source format 3.0 (native) + * building on behalf of X2Go project (changes version/revision string of + package) + * building only for unstable + + -- Mike Gabriel Mon, 25 Apr 2011 15:09:24 +0200 + +python-x2go (0.0.42.0-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * new upstream features (0.0.42.0): + - adds unity-2d support + - session state update after connect + - adds profile name validation + + -- Mike Gabriel Mon, 18 Mar 2011 15:59:28 +0200 + +python-x2go (0.0.41.0-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * new upstream features (0.0.41.0): + - improved session state recognition + - speeding up bootstrap of X2goClient + - catching missing dropbox/spool folders + + -- Mike Gabriel Fri, 26 Mar 2011 20:43:12 +0100 + +python-x2go (0.0.40.0-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * new upstream features (0.0.40.0): + - fixing false HOOK_* notifications + - work on Windows subprocess creation + - gsprint.exe path now configurable + - ignoring dotfiles in X2goDropbox + - hard-coded file extension blacklist for X2goDropbox + + -- Mike Gabriel Thu, 10 Mar 2011 23:57:00 +0100 + +python-x2go (0.0.39.0-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * new upstream features (0.0.39.0): + - re-worked infrastructure for providing a printing preferences dialog + + -- Mike Gabriel Tue, 1 Mar 2011 00:32:00 +0100 + +python-x2go (0.0.38.0-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * new upstream features (0.0.38.0): + - added host check/authorization framework + - bugfix in case of network failure + + -- Mike Gabriel Sun, 27 Feb 2011 02:20:00 +0100 + +python-x2go (0.0.37.0-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * added x2goserver patches for Python X2go's X2goDropbox feature to /doc dir + * new upstream features (0.0.37.0): + - added support for gsprint.exe on Windows + - added basic support for injecting env variables into server session + - dropbox extensions are not accepted without preceeding dot (,,.'') + - fixed missing X2goSession instance into X2goRevFwTunnelSFTP instances + - tested all print actions for Windows/Linux again + - non-existent PDFSAVE folders are now created + + -- Mike Gabriel Fri, 25 Feb 2011 17:50:15 +0100 + +python-x2go (0.0.36.1-0~nwt1) natty lucid maverick stable testing unstable; urgency=low + + * New upstream bugfix release: unicode error when using X2goDropbox + + -- Mike Gabriel Tue, 22 Feb 2011 19:16:35 +0100 + +python-x2go (0.0.36-0~nwt3) natty lucid maverick stable testing unstable; urgency=low + + * building also for Ubuntu 11.04 (natty) + + -- Mike Gabriel Tue, 22 Feb 2011 12:42:35 +0100 + +python-x2go (0.0.36-0~nwt2) lucid maverick stable testing unstable; urgency=low + + * using standards 3.9.1 + * wrapped too-long-line in Debian changelog + + -- Mike Gabriel Tue, 22 Feb 2011 12:19:35 +0100 + +python-x2go (0.0.36-0~nwt1) lucid maverick testing unstable; urgency=low + + * fixed non-starting sshfs issue + * made XServer VcXsrv known to Python X2Go (relevant for Windows) + * catching forward tunnel setup failure by session and client hook method + * improved handling of network failures (i.e. when triggering disconnect + events) + * added new x2goclient default session profile options + * killing PulseAuddio daemon properly on Windows + + -- Mike Gabriel Tue, 22 Feb 2011 12:14:22 +0100 + +python-x2go (0.0.35-0~nwt1) lucid maverick testing unstable; urgency=low + + * added PulseAudio support for Windows + * added fixes for Unicode issues in PyHoca-GUI + + -- Mike Gabriel Wed, 19 Jan 2011 18:20:00 +0100 + +python-x2go (0.0.34-0~nwt1) lucid maverick testing unstable; urgency=low + + * catching pulse audio failures (if not installed) + * catching exceptions if X2goSession is called as standalone instance + + -- Mike Gabriel Mon, 17 Jan 2011 00:20:00 +0100 + +python-x2go (0.0.33-0~nwt1) lucid maverick testing unstable; urgency=low + + * improved stability of SSH forwarding tunneling (nxproxy, SSH proxy) + * SSH proxy session profile options can now be changed during runtime + + -- Mike Gabriel Fri, 14 Jan 2011 03:40:00 +0100 + +python-x2go (0.0.32-0~nwt1) lucid maverick testing unstable; urgency=low + + * fixed a rarely occurring exception when calling + X2goControlSession.terminate() + * changing the server name between connects now gets recognized + * fixed link speed issue (was complete rubbish so far...) + * thanks to Gerry Reno for testing + + -- Mike Gabriel Tue, 11 Jan 2011 21:00:00 +0100 + +python-x2go (0.0.31-0~nwt1) lucid maverick testing unstable; urgency=low + + * fixing connected status for auto-registered sessions in X2goSessionRegistry + * now X2goSessionRegistry.update_status() can differentiate between + ,,already running'' sessions (directly after connect and newly appearing + running sessions while having been connected for quite a while + * added ,,return_session_names'' to X2goSessionRegistry's enumerating methods + * PDFVIEW printaction for MS Windows, PRINT (on default printer) action for MS + Windows + + -- Mike Gabriel Mon, 3 Jan 2011 21:42:00 +0100 + +python-x2go (0.0.30-0~nwt1) lucid maverick testing unstable; urgency=low + + * adds encodings support to local folder sharing + * bugfix: now unknown session profile options are really ignored + * introduces new Python-X2Go-only session profile option ,,useexports'' + + -- Mike Gabriel Fri, 31 Dec 2010 22:30:00 +0100 + +python-x2go (0.0.29-0~nwt1) lucid maverick testing unstable; urgency=low + + * bugfix release (add_to_known_host / known_hosts when using X2goSSHProxy) + + -- Mike Gabriel Fri, 31 Dec 2010 13:30:00 +0100 + +python-x2go (0.0.28-0~nwt1) lucid maverick testing unstable; urgency=low + + * bugfix release (add_to_known_host / known_hosts issue) + + -- Mike Gabriel Fri, 31 Dec 2010 00:45:00 +0100 + +python-x2go (0.0.27-0~nwt1) lucid maverick testing unstable; urgency=low + + * bugfix release + + -- Mike Gabriel Fri, 31 Dec 2010 00:15:00 +0100 + +python-x2go (0.0.26-0~nwt1) lucid maverick testing unstable; urgency=low + + * added XDMCP support (does not work due to server-side problems) + * added SSH proxy support (NOTE: SSH proxy support adds options to + the sessions file) + * notification system and session management improved + * stale-paramiko-sessions problem fixed + * defunct-nxproxy problem fixed + * providing X2goPrintActionDIALOG + * introduction of HOOK methods in X2goClient class + * many bugfixes and performance improvements + + -- Mike Gabriel Thu, 30 Dec 2010 22:00:00 +0100 + +python-x2go (0.0.25-0~nwt1) lucid maverick testing unstable; urgency=low + + * renamed set_loglevel_none() method to set_loglevel_quiet() + * on Unix: subprocess needs a shell=False, on Windows: shell=True + * got rid of nxproxy DOS window, when running on Windows (running nxproxy + in shell) + * added X2goXServer class and X2gClientXConfig class -> support for + starting an external XServer application on MS Windows + * added support for profile metatype information (analyzing session profile + and summarizing the session profile type in a short string) + + -- Mike Gabriel Fri, 17 Dec 2010 16:00:00 +0100 + +python-x2go (0.0.24-0~nwt2) lucid maverick testing unstable; urgency=low + + * incremented egg version number + + -- Mike Gabriel Tue, 14 Dec 2010 16:33:00 +0100 + +python-x2go (0.0.24-0~nwt1) lucid maverick testing unstable; urgency=low + + * minor fixes + * Depends on Python 2.6 or higher (not Python3) + + -- Mike Gabriel Tue, 14 Dec 2010 11:32:46 +0100 + +python-x2go (0.0.23-0~nwt1) lucid maverick testing unstable; urgency=low + + * added MS remote desktop (RDP) support + + -- Mike Gabriel Tue, 14 Dec 2010 11:31:40 +0100 + +python-x2go (0.0.22-0~nwt1) lucid maverick testing unstable; urgency=low + + * fixed example scripts + * catching x2golistsessions exceptions in info-stdout backend + * minor fixes + + -- Mike Gabriel Fri, 10 Dec 2010 13:30:00 +0100 + +python-x2go (0.0.21-0~nwt1) lucid maverick testing unstable; urgency=low + + * fixed add_to_known_hosts/force_password_auth parameter passing + + -- Mike Gabriel Thu, 09 Dec 2010 23:50:00 +0100 + +python-x2go (0.0.20-0~nwt1) lucid maverick testing unstable; urgency=low + + * skipping some version numbers as we have made quite some changes + * much work on error handling + * much work on non-blocking I/O + * session profile support added + * loads of bugfixes + * introduction of one control session and many possible terminal sessions + -> all communication between server+client uses a single SSH transport + * X2goClient class now has a guardian for all sessions + * caching x2golistsession results (increases responsiveness of GUI applications) + * etc. + + -- Mike Gabriel Thu, 09 Dec 2010 23:30:00 +0100 + +python-x2go (0.0.14-0~nwt1) lucid maverick testing unstable; urgency=low + + * added X2Go printing support + + -- Mike Gabriel Thu, 28 Oct 2010 23:00:00 +0200 + +python-x2go (0.0.13-0~nwt1) lucid maverick testing unstable; urgency=low + + * added loglevel_DEBUG_SFTPXFER + * introduced shutil.rmtree in sFTP server implementation + * introduced shutil.move in sFTP server implementation + + -- Mike Gabriel Wed, 13 Oct 2010 23:40:00 +0200 + +python-x2go (0.0.12-0~nwt1) lucid maverick testing unstable; urgency=low + + * fixed typo in session.py + + -- Mike Gabriel Wed, 13 Oct 2010 14:10:00 +0200 + +python-x2go (0.0.11-0~nwt1) lucid maverick testing unstable; urgency=low + + * added local folder sharing support + * integrated an SFTP server into Python X2go + + -- Mike Gabriel Wed, 13 Oct 2010 14:00:00 +0200 + +python-x2go (0.0.10-0~nwt1) lucid maverick testing unstable; urgency=low + + * added thread cleanup support (X2goSessionGuardian) in case of the sudden + death of an X2goSession instance (SIGINT, SIGTERM, KeyboardInterrupt, sys.exit()) + + -- Mike Gabriel Thu, 07 Oct 2010 14:00:00 +0200 + +python-x2go (0.0.9-0~nwt1) lucid maverick testing unstable; urgency=low + + * debian dependency: gevent < 0.13.x does not have the StreamServer class, + added version dependency to control file + * allowing dupload/reprepro for multiple distro versions + * changes in authentication handling on X2goSession.connect() calls + * X2goProxy descructor now is responsible for proxy shutdown + * switching to /usr/bin/nxproxy (Ubuntu/Debian package) + * updated README file + + -- Mike Gabriel Tue, 05 Oct 2010 22:30:00 +0200 + +python-x2go (0.0.8-0~nwt2) lucid maverick testing unstable; urgency=low + + * fixing a bug (c+p typo) in rforward.py + + -- Mike Gabriel Wed, 06 Oct 2010 14:30:00 +0200 + +python-x2go (0.0.8-0~nwt1) unstable; urgency=low + + * reworked __doc__ strings + * add X2goExceptions + * typo in license/copyright comment head + * update example Python scripts + + -- Mike Gabriel Tue, 05 Oct 2010 22:30:00 +0200 + +python-x2go (0.0.7-0~nwt1) unstable; urgency=low + + * minor fix in X2goSession.start_sound() + + -- Mike Gabriel Tue, 05 Oct 2010 22:30:00 +0200 + +python-x2go (0.0.6-0~nwt1) unstable; urgency=low + + * added more control mechs for reverse forwarding tunnels + i.e. a pause() and resume() method + * X2goSession methods to stop audio / folder sharing during + a running session + + -- Mike Gabriel Tue, 05 Oct 2010 18:43:00 +0200 + +python-x2go (0.0.5-0~nwt1) unstable; urgency=low + + * added Paramiko/SSH reverse forwarding tunnel support + * Pulse Audio support has been added + * SSH connections back to the X2Go client are possible + * distro codename lucid -> unstable + * linitian version 3.9.0 + + -- Mike Gabriel Tue, 05 Oct 2010 16:43:00 +0200 + +python-x2go (0.0.4-0~nwt1) lucid; urgency=low + + * added force_password_auth param to X2goSession.connect() + * replaced *args, **kwargs in X2goSession.connect() by proper param strings + * fixed X2goSessionParams.rewrite_session_type(), the method always returned 'R' + * added __doc__ strings for client.py (X2goClient class et al.) + * fixed password authentication (was broken, only pub/priv key auth worked properly) + + -- Mike Gabriel Fri, 01 Oct 2010 10:00:00 +0200 + +python-x2go (0.0.3-0~nwt1) lucid; urgency=low + + * fixed a bug in X2goServerSessionInfo.rewrite_session_type() + that broke session_type desktop. + + -- Mike Gabriel Fri, 01 Oct 2010 01:40:00 +0200 + +python-x2go (0.0.2-0~nwt1) lucid; urgency=low + + * __doc__ strings added for most classes/modules + + -- Mike Gabriel Fri, 01 Oct 2010 01:30:00 +0200 + +python-x2go (0.0.1-0~nwt2) lucid; urgency=low + + * automatic build of epydoc based API documentation: html, pdf + + -- Mike Gabriel Thu, 30 Sep 2010 09:00:00 +0200 + +python-x2go (0.0.1-0~nwt1) lucid; urgency=low + + * complete python-x2go rewrite from scratch + + -- Mike Gabriel Thu, 30 Sep 2010 02:00:00 +0200 - -- Stéphane Graber Fri, 20 May 2011 18:27:17 -0400 diff -Nru python-x2go-0.1.1.8/debian/control python-x2go-0.5.0.6/debian/control --- python-x2go-0.1.1.8/debian/control 2011-07-09 20:45:45.000000000 +0000 +++ python-x2go-0.5.0.6/debian/control 2017-12-12 06:52:58.000000000 +0000 @@ -1,16 +1,21 @@ Source: python-x2go Section: python Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Mike Gabriel -Build-Depends: - debhelper (>= 7.0.50), - python (>= 2.6.6-3~), +Maintainer: X2Go Packaging Team +Uploaders: + Mike Gabriel , + Mihai Moldovan , +Build-Depends: + debhelper (>= 7.0.50~), +# python (>= 2.6.6-3~), + python (>= 2.6.5-0~), python-setuptools, python-epydoc, python-gevent, - python-paramiko -Standards-Version: 3.9.2 + python-paramiko, + python-xlib, + locales +Standards-Version: 3.9.6 Homepage: http://code.x2go.org/releases/source/python-x2go Vcs-Git: git://code.x2go.org/python-x2go.git Vcs-Browser: http://code.x2go.org/gitweb?p=python-x2go.git;a=summary @@ -21,22 +26,47 @@ Depends: ${python:Depends}, ${misc:Depends}, - python-gevent (>= 0.13.0-0~0), - python-paramiko, - python-cups, - nxproxy + python-gevent (>= 0.13.6-0~), + python-paramiko (>= 1.15.1-0~), + python-requests, + python-simplejson, + python-xlib, + nxproxy | qvd-nxproxy, + sshfs, Recommends: cups-bsd | lpr, - pulseaudio -Description: Python module for X2go client support - X2go is a server based computing environment with + pulseaudio, +Suggests: + telekinesis-client, + mteleplayer-clientside, +Description: Python module providing X2Go client API + X2Go is a server based computing environment with + - session resuming + - low bandwidth support + - session brokerage support + - client side mass storage mounting support + - client side printing support + - audio support + - authentication by smartcard and USB stick + . + This Python module allows you to integrate X2Go client + support into your Python applications by providing a + Python-based X2Go client API. + +Package: python-x2go-doc +Architecture: all +Section: doc +Depends: + ${misc:Depends}, +Description: Python module providing X2Go client API (documentation) + X2Go is a server based computing environment with - session resuming - - low bandwith support - - LDAP support + - low bandwidth support + - session brokerage support - client side mass storage mounting support + - client side printing support - audio support - authentication by smartcard and USB stick . - This Python module allows you to integrate X2go client - support into your Python applications by providing a - Python-based X2go client API. + This package contains the Python X2Go client API + documentation generated with Epydoc. diff -Nru python-x2go-0.1.1.8/debian/copyright python-x2go-0.5.0.6/debian/copyright --- python-x2go-0.1.1.8/debian/copyright 2011-07-09 16:58:26.000000000 +0000 +++ python-x2go-0.5.0.6/debian/copyright 2017-12-12 06:52:58.000000000 +0000 @@ -1,22 +1,681 @@ -Copyright (C) 2010 by Mike Gabriel +Source: http://code.x2go.org/releases/source/python-x2go -Python X2go 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. +Files: * +Copyright: 2010-2016, Mike Gabriel +License: AGPL-3+ -Python X2go 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. +Files: x2go/gevent_subprocess.py +Copyright: 2010-2016, Mark Visser + 2010-2016, Mike Gabriel +License: AGPL-3+ +Comment: The file was placed in the ,,public domain'' by Mark Visser and + in context of the X2Go project it is published as GPL-3. The file was + obtained here: + http://groups.google.com/group/gevent/browse_thread/thread/dba1a5d29e0a60ff -You should have received a copy of the GNU General Public License -along with this program; if not, write to the -Free Software Foundation, Inc., -51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +Files: Makefile.docupload +Copyright: 2010-2016, Mike Gabriel +License: GPL-3+ + 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, write to the + Free Software Foundation, Inc., + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + . + On Debian systems, the full text of the GNU General Public + License version 3 can be found in the file + `/usr/share/common-licenses/GPL-3'. -On Debian/Ubuntu systems a copy of the GPLv3 license can be found at -/usr/share/common-licenses/ - -This license also applies to contained packaging files for Debian/Ubuntu. +Files: debian/* +Copyright: 2010-2016, Mike Gabriel +License: AGPL-3+ +License: AGPL-3+ + Python X2Go is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + . + Python X2Go 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 Affero General Public License for more details. + . + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + . + The full license text can be read below. + . + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for + software and other kinds of works, specifically designed to ensure + cooperation with the community in the case of network server software. + . + The licenses for most software and other practical works are designed + to take away your freedom to share and change the works. By contrast, + our General Public Licenses are 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. + . + 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. + . + Developers that use our General Public Licenses protect your rights + with two steps: (1) assert copyright on the software, and (2) offer + you this License which gives you legal permission to copy, distribute + and/or modify the software. + . + A secondary benefit of defending all users' freedom is that + improvements made in alternate versions of the program, if they + receive widespread use, become available for other developers to + incorporate. Many developers of free software are heartened and + encouraged by the resulting cooperation. However, in the case of + software used on network servers, this result may fail to come about. + The GNU General Public License permits making a modified version and + letting the public access it on a server without ever releasing its + source code to the public. + . + The GNU Affero General Public License is designed specifically to + ensure that, in such cases, the modified source code becomes available + to the community. It requires the operator of a network server to + provide the source code of the modified version running there to the + users of that server. Therefore, public use of a modified version, on + a publicly accessible server, gives the public access to the source + code of the modified version. + . + An older license, called the Affero General Public License and + published by Affero, was designed to accomplish similar goals. This is + a different license, not a version of the Affero GPL, but Affero has + released a new version of the Affero GPL which permits relicensing under + this license. + . + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + . + .Notwithstanding any other provision of this License, if you modify the + Program, your modified version must prominently offer all users + interacting with it remotely through a computer network (if your version + supports such interaction) an opportunity to receive the Corresponding + Source of your version by providing access to the Corresponding Source + from a network server at no charge, through some standard or customary + means of facilitating copying of software. This Corresponding Source + shall include the Corresponding Source for any work covered by version 3 + of the GNU General Public License that is incorporated pursuant to the + following paragraph. + . + 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 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 work with which it is combined will remain governed by version + 3 of the GNU General Public License. + . + 14. Revised Versions of this License. + . + The Free Software Foundation may publish revised and/or new versions of + the GNU Affero 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 Affero 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 Affero 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 Affero 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 diff -Nru python-x2go-0.1.1.8/debian/git-build-recipe.manifest python-x2go-0.5.0.6/debian/git-build-recipe.manifest --- python-x2go-0.1.1.8/debian/git-build-recipe.manifest 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/debian/git-build-recipe.manifest 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,2 @@ +# git-build-recipe format 0.4 deb-version {debupstream}-0~201709231950 +lp:~x2go/x2go/+git/python-x2go git-commit:fa04b4f34c6741d962c6bf4d5ce0b9f90aec4545 diff -Nru python-x2go-0.1.1.8/debian/python-x2go-doc.doc-base python-x2go-0.5.0.6/debian/python-x2go-doc.doc-base --- python-x2go-0.1.1.8/debian/python-x2go-doc.doc-base 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/debian/python-x2go-doc.doc-base 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,10 @@ +Document: python-x2go +Title: Python X2Go Client API +Author: Mike Gabriel +Abstract: This document describes how to use the Python X2Go Client API + in Python applications. +Section: Programming/Python + +Format: HTML +Index: /usr/share/doc/python-x2go-doc/html/index.html +Files: /usr/share/doc/python-x2go-doc/html/* diff -Nru python-x2go-0.1.1.8/debian/python-x2go-doc.docs python-x2go-0.5.0.6/debian/python-x2go-doc.docs --- python-x2go-0.1.1.8/debian/python-x2go-doc.docs 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/debian/python-x2go-doc.docs 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1 @@ +.epydoc/html/ diff -Nru python-x2go-0.1.1.8/debian/python-x2go-doc.links python-x2go-0.5.0.6/debian/python-x2go-doc.links --- python-x2go-0.1.1.8/debian/python-x2go-doc.links 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/debian/python-x2go-doc.links 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1 @@ +usr/share/doc/python-x2go-doc/html usr/share/doc/python-x2go/html diff -Nru python-x2go-0.1.1.8/debian/python-x2go-doc.lintian-overrides python-x2go-0.5.0.6/debian/python-x2go-doc.lintian-overrides --- python-x2go-0.1.1.8/debian/python-x2go-doc.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/debian/python-x2go-doc.lintian-overrides 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,2 @@ +# epydoc produces some duplicate files, we cannot change that without great effort +python-x2go-doc: duplicate-files \ No newline at end of file diff -Nru python-x2go-0.1.1.8/debian/python-x2go.docs python-x2go-0.5.0.6/debian/python-x2go.docs --- python-x2go-0.1.1.8/debian/python-x2go.docs 2011-07-09 16:58:26.000000000 +0000 +++ python-x2go-0.5.0.6/debian/python-x2go.docs 2017-12-12 06:52:58.000000000 +0000 @@ -1,5 +1,3 @@ README README.Trinity-Desktop TODO -.epydoc/html/ -#.epydoc/pdf/ diff -Nru python-x2go-0.1.1.8/debian/python-x2go.install python-x2go-0.5.0.6/debian/python-x2go.install --- python-x2go-0.1.1.8/debian/python-x2go.install 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/debian/python-x2go.install 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1 @@ +usr diff -Nru python-x2go-0.1.1.8/debian/pyversions python-x2go-0.5.0.6/debian/pyversions --- python-x2go-0.1.1.8/debian/pyversions 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/debian/pyversions 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -2.5- \ No newline at end of file diff -Nru python-x2go-0.1.1.8/debian/rules python-x2go-0.5.0.6/debian/rules --- python-x2go-0.1.1.8/debian/rules 2011-07-09 16:58:26.000000000 +0000 +++ python-x2go-0.5.0.6/debian/rules 2017-12-12 06:52:58.000000000 +0000 @@ -1,19 +1,27 @@ #!/usr/bin/make -f # debian/rules file - for python-x2go # Based on sample debian/rules file - for GNU Hello (1.3). -# Copyright 2010 by Mike Gabriel +# Copyright 2010-2016 by Mike Gabriel + +WITH_PYTHON2 = $(shell test -f /usr/bin/dh_python2 && echo "--with python2") %: - dh $@ --with python2 + dh ${@} ${WITH_PYTHON2} override_dh_auto_build: mkdir -p .epydoc/html - mkdir -p .epydoc/pdf - mkdir -p .epydoc/pdf.tmp rm -Rf .epydoc/html/* - epydoc --debug -n "Python X2go" -u http://www.x2go.org -v --html --no-private -o .epydoc/html x2go/ + epydoc --debug -n "Python X2Go" -u http://www.x2go.org -v --html --no-private -o .epydoc/html x2go/ + dh_auto_build + +# tests from upstream are currently broken... +override_dh_auto_test: + +override_dh_python2: + rm debian/python-x2go/usr/lib/*/dist-packages/x2go/tests -Rfv + dh_python2 override_dh_auto_clean: - rm -Rfv debian/*.log debian/python-x2go debian/python-x2go.*.debhelper debian/python-x2go.substvars - rm -Rfv debian/tmp debian/patches build x2go.egg-info - rm -Rfv .epydoc + rm -Rfv build x2go.egg-info .epydoc test.pyc + find x2go -name *.pyc -exec rm -fv "{}" \; + dh_auto_clean diff -Nru python-x2go-0.1.1.8/debian/source/format python-x2go-0.5.0.6/debian/source/format --- python-x2go-0.1.1.8/debian/source/format 2011-07-09 16:58:26.000000000 +0000 +++ python-x2go-0.5.0.6/debian/source/format 2017-12-12 06:52:58.000000000 +0000 @@ -1 +1 @@ -3.0 (quilt) +1.0 diff -Nru python-x2go-0.1.1.8/debian/watch python-x2go-0.5.0.6/debian/watch --- python-x2go-0.1.1.8/debian/watch 2011-07-09 16:58:26.000000000 +0000 +++ python-x2go-0.5.0.6/debian/watch 2017-12-12 06:52:58.000000000 +0000 @@ -1,3 +1,2 @@ version=3 - -http://code.x2go.org/releases/source/python-x2go/python-x2go_(.*)\.tar\.gz +http://code.x2go.org/releases/source/python-x2go/python-x2go-(.+)\.tar\.gz diff -Nru python-x2go-0.1.1.8/examples/x2go_resume_session.py python-x2go-0.5.0.6/examples/x2go_resume_session.py --- python-x2go-0.1.1.8/examples/x2go_resume_session.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/examples/x2go_resume_session.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,19 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (C) 2010 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. @@ -25,10 +25,9 @@ # import x2go before you import other thread based modules (e.g. paramiko) import x2go +import sys import getpass -import os,sys -import time -import paramiko +import gevent # modify to your needs... server = "server.mydomain.tld" @@ -37,7 +36,7 @@ password = getpass.getpass() -cli = x2go.X2goClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) +cli = x2go.X2GoClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) s_uuid = cli.register_session(server, port=port, username=username, geometry="800x600", add_to_known_hosts=True) cli.connect_session(s_uuid, password=password) @@ -50,7 +49,7 @@ try: while cli.session_ok(s_uuid): - time.sleep(1) + gevent.sleep(2) except KeyboardInterrupt: pass diff -Nru python-x2go-0.1.1.8/examples/x2go_start_session.py python-x2go-0.5.0.6/examples/x2go_start_session.py --- python-x2go-0.1.1.8/examples/x2go_start_session.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/examples/x2go_start_session.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,19 +1,19 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (C) 2010 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. @@ -25,7 +25,6 @@ # import x2go before you import other thread based modules (e.g. paramiko) import x2go -import sys import gevent import getpass @@ -33,11 +32,11 @@ server = "server.mydomain.tld" port = 22 username = "foo" -command = "GNOME" +command = "XFCE" password = getpass.getpass() -cli = x2go.X2goClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) +cli = x2go.X2GoClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) s_uuid = cli.register_session(server, port=port, username=username, cmd=command, diff -Nru python-x2go-0.1.1.8/examples/x2go_start_session_with_progress_status.py python-x2go-0.5.0.6/examples/x2go_start_session_with_progress_status.py --- python-x2go-0.1.1.8/examples/x2go_start_session_with_progress_status.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/examples/x2go_start_session_with_progress_status.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +### +### short example for python-phoca usage +### + +# import x2go before you import other thread based modules (e.g. paramiko) +import x2go + +import gevent +import getpass +import threading + +# modify to your needs... +server = "server.mydomain.tld" +port = 22 +username = "foo" +command = "XFCE" + +def my_progress_bar(ps): + + for status in ps: + print '---------------' + print 'SESSION STATUS: ' + '#' * status + "(" + str(status) + "%%)" + print '---------------' + +password = getpass.getpass() + +cli = x2go.X2GoClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) +s_uuid = cli.register_session(server, port=port, + username=username, + cmd=command, + add_to_known_hosts=True, + ) +cli.connect_session(s_uuid, password=password) + +# clean sessions and check the result +cli.clean_sessions(s_uuid) + +# initialize a ProgressStatus event and iterator +progress_event = threading.Event() +progress_status = x2go.utils.ProgressStatus(progress_event, cli.get_session(s_uuid).get_progress_status) + +# start the status bar +gevent.spawn(my_progress_bar, progress_status) + +# start the session +gevent.spawn(cli.start_session, s_uuid, progress_event=progress_event) + +# wait long enough for session to come up completely +while (cli.get_session(s_uuid).get_progress_status() < 100) and (cli.get_session(s_uuid).get_progress_status() != -1): + gevent.sleep(1) + +try: + while cli.session_ok(s_uuid): + gevent.sleep(2) +except KeyboardInterrupt: + pass + +# suspend the session +cli.suspend_session(s_uuid) diff -Nru python-x2go-0.1.1.8/LICENSE python-x2go-0.5.0.6/LICENSE --- python-x2go-0.1.1.8/LICENSE 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,675 +0,0 @@ - - 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 -Nru python-x2go-0.1.1.8/Makefile.docupload python-x2go-0.5.0.6/Makefile.docupload --- python-x2go-0.1.1.8/Makefile.docupload 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/Makefile.docupload 2017-12-12 06:52:58.000000000 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/make -f # Makefile.docupload file - for python-x2go -# Copyright 2010-2011 by Mike Gabriel , GPLv3 applies to this file +# Copyright 2010-2016 by Mike Gabriel , GPLv3+ applies to this file VERSION=`head -n1 debian/changelog | sed 's,.*(\(.*\)).*,\1,' | cut -d"-" -f1` DOC_HOST=code.x2go.org @@ -14,9 +14,9 @@ mkdir -p .epydoc/pdf mkdir -p .epydoc/pdf.tmp rm -Rf .epydoc/html/* - epydoc --debug -n "Python X2go" -u http://www.x2go.org -v --html --no-private -o .epydoc/html x2go/ + epydoc --debug -n "Python X2Go" -u http://www.x2go.org -v --html --no-private -o .epydoc/html x2go/ #epydoc --debug --pdf --no-private -o .epydoc/pdf.tmp x2go/ - #mv .epydoc/pdf.tmp/api.pdf .epydoc/pdf/Python-X2go_API.pdf + #mv .epydoc/pdf.tmp/api.pdf .epydoc/pdf/Python-X2Go_API.pdf #rm -Rf .epydoc/pdf.tmp diff -Nru python-x2go-0.1.1.8/python-x2go.spec python-x2go-0.5.0.6/python-x2go.spec --- python-x2go-0.1.1.8/python-x2go.spec 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/python-x2go.spec 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,134 @@ +#if 0%{?fedora} +#global with_python3 1 +#endif + +Name: python-x2go +Version: 0.5.0.6 +Release: 0.0x2go1%{?dist} +Summary: Python module providing X2Go client API + +Group: Development/Languages +%if 0%{?suse_version} +License: AGPL-3.0+ +%else +License: AGPLv3+ +%endif +URL: http://www.x2go.org/ +Source0: http://code.x2go.org/releases/source/%{name}/%{name}-%{version}.tar.gz + +BuildArch: noarch +%if 0%{?suse_version} +BuildRequires: python-devel +BuildRequires: fdupes +%else +BuildRequires: python2-devel +%endif +BuildRequires: python-setuptools +%if 0%{?with_python3} +BuildRequires: python3-devel +# For 2to3 +BuildRequires: python-tools +%endif # if with_python3 +# For doc build +BuildRequires: epydoc +BuildRequires: python-gevent +BuildRequires: python-paramiko +BuildRequires: python-xlib +BuildRequires: python-requests +BuildRequires: python-simplejson +Requires: nxproxy +Requires: python-gevent +Requires: python-paramiko >= 1.15.1 +Requires: python-xlib +Requires: python-requests +Requires: python-simplejson + +%description +X2Go is a server based computing environment with: + - session resuming + - low bandwidth support + - session brokerage support + - client side mass storage mounting support + - audio support + - authentication by smartcard and USB stick + +This Python module allows you to integrate X2Go client support into your +Python applications by providing a Python-based X2Go client API. + + +%package doc +Summary: Python X2Go client API documentation +Group: Documentation +Requires: %{name} = %{version}-%{release} + +%description doc +This package contains the Python X2Go client API documentation. + + +%if 0%{?with_python3} +%package -n python3-x2go +Summary: Python module providing X2Go client API +Group: Development/Languages + +%description -n python3-x2go +X2Go is a server based computing environment with: + - session resuming + - low bandwidth support + - session brokerage support + - client side mass storage mounting support + - audio support + - authentication by smartcard and USB stick + +This Python module allows you to integrate X2Go client support into your +Python applications by providing a Python-based X2Go client API. +%endif # with_python3 + + +%prep +%setup -q +# Remove shbang from library scipts +find x2go -name '*.py' | xargs sed -i '1s|^#!/usr/bin/env python||' +# Python3 +%if 0%{?with_python3} +rm -rf %{py3dir} +cp -a . %{py3dir} +2to3 --write --nobackups %{py3dir} +%endif # with_python3 + + +%build +%{__python} setup.py build +%if 0%{?with_python3} +pushd %{py3dir} +%{__python3} setup.py build +popd +%endif # with_python3 + +# Build the docs +mkdir -p epydoc/html +epydoc --debug -n "Python X2Go" -u http://www.x2go.org -v --html --no-private -o epydoc/html x2go/ + + +%install +%if 0%{?with_python3} +pushd %{py3dir} +%{__python3} setup.py install --skip-build --root %{buildroot} +popd +%endif # with_python3 +%{__python} setup.py install --skip-build --root %{buildroot} +%if 0%{?fdupes:1} +%fdupes %buildroot/%_prefix +%endif + + +%files +%defattr(-,root,root) +%doc COPYING README* TODO +%{python_sitelib}/* + +%files doc +%defattr(-,root,root) +%doc epydoc/html + + +%changelog diff -Nru python-x2go-0.1.1.8/README python-x2go-0.5.0.6/README --- python-x2go-0.1.1.8/README 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/README 2017-12-12 06:52:58.000000000 +0000 @@ -1,39 +1,39 @@ -python-x2go - Copyright (C) 2010-2011 by Mike Gabriel +python-x2go - Copyright (C) 2010-2016 by Mike Gabriel -Published under the terms of the GNU General Public License. -See http://www.gnu.org/licenses/gpl.html for a recent copy. +Published under the terms of the GNU Affero General Public License. +See http://www.gnu.org/licenses/agpl.html for a recent copy. -=== What is Python X2go? === +=== What is Python X2Go? === -The Python X2go module integrates X2go client support into your python programmes. -Python X2go is used in the headless X2go client »pyhoca-cli« and by the tiny systray GUI -application »pyhoca-gui« +The Python X2Go module integrates X2Go client support into your python programmes. +Python X2Go is used in the headless X2Go client »pyhoca-cli« and by the tiny systray GUI +applet »pyhoca-gui« -Python X2go takes advantages of the NX Proxy by NoMachine published under GPL. +Python X2Go takes advantages of the NX Proxy by NoMachine published under GPL. On Windows and MacOS X systems you have to make sure that you have installed a current NX Proxy executable somewhere on your system. It probably will be sufficient to have -the X2go project's x2goclient package installed on your client machine. +the X2Go project's X2Go Client application installed on your client machine. -On Debian / Ubuntu systems there is a nxproxy package available within the distribution. -Python X2go defaults to using the distro nxproxy binary. +On Debian / Ubuntu systems there is an nxproxy package available within the distribution. +Python X2Go defaults to using the distro nxproxy binary. -Python X2go was originally inspired by work of Jörg Sawatzki . -Unfortunately, Jörg had to give up work on his ideas due to licensing issues with his -contractor. To make his ideas furthermore available to the OSS community this complete -rewrite of Jörgs ideas has been called into life. +Python X2Go was originally inspired by work of Jörg Sawatzki . +Unfortunately, Jörg had to give up work on his ideas due to licensing issues with his +contractor. To make his ideas furthermore available to the OSS community this complete +rewrite of Jörg's ideas has been called into life. -In case you have the opinion that parts of the presented code are not as much a rewrite +In case you have the opinion that parts of the presented code are not as much a rewrite as they should be, may I ask you to contact me directly and in person. I am sure, things can -be sorted out in a non-complicated and heartful fashion. Thanks in advance for your directness +be sorted out in a non-complicated and heartful fashion. Thanks in advance for your directness and wholeheartedness concerning this inner process. === Requirements === -* X2go Server - - you need a working X2go server to connect to. In order to use shadowing make sure - you have at least X2go Baikal (3.1.x) installed +* X2Go Server + - you need a working X2Go server to connect to. In order to use shadowing or mTelePlayer + make sure you have at least X2Go Server (>= 4.1.0.0) installed * on the client system you will need - the python-gevent library (for details refer to: http://www.gevent.org/) - the python-paramiko library (SSH implementation in Python, http://www.lag.net/paramiko/) @@ -44,30 +44,34 @@ - also working: if no Ghostscript/GSView is installed the win32api ,,print'' command will be executed on incoming PDF print spool files. Win32api will try launch the print function of the default PDF viewer application (mostly Adobe Acrobat Reader) - - if you have a choice: install Ghostscript/GSview on the system that uses Python X2go + - if you have a choice: install Ghostscript/GSview on the system that uses Python X2Go applications... it's highly recommended -=== Current features === +=== Current features === -* start X2go agent session -* suspend X2go session -* resume X2go session -* terminate X2go session -* clean user's X2go sessions -* list user's X2go sessions +* start X2Go agent session +* suspend X2Go session +* resume X2Go session +* terminate X2Go session +* clean user's X2Go sessions +* list user's X2Go sessions * use Pulse Audio sound -* X2go printing +* X2Go printing * reading/writing session profiles from file -* sharing of local (client-side) folders (SFTP server is integrated in Python X2go) +* sharing of local (client-side) folders (SFTP server is integrated in Python X2Go) * connect via proxy SSH server -* X2go MIME box support -* desktop sharing (shadow sessions) +* X2Go MIME box support * color depth auto-recognition +* X2Go desktop sharing support +* X2Go published applications support +* Session window re-titling for desktop and shared desktop sessions +* X2Go Session Brokerage (HTTP or HTTPS) +* Telekinesis Client support included (required for multimedia support inside + the X2Go session using mTelePlayer as media player) === Installation === - Ubuntu: ------- We use Launchpad for Ubuntu packaging: @@ -76,7 +80,7 @@ $ apt-get update $ apt-get install python-x2go -From Ubuntu oneiric on python-x2go is also available as a part of the +From Ubuntu oneiric on python-x2go is also available as a part of the Ubuntu GNU/Linux distribution. @@ -105,11 +109,11 @@ === How to use it from the command line? === -A good example for the usage of Python X2go is the pyhoca-cli programme, a headless -X2go client that aims at 100% compatibility with X2goClient applications released in -the X2go project. +A good example for the usage of Python X2Go is the pyhoca-cli programme, a headless +X2Go client that aims at 100% compatibility with X2Go Client applications released in +the X2Go project. -On Debian/Ubuntu you can easily install the X2go Client by running APT again: +On Debian/Ubuntu you can easily install the X2Go Client by running APT again: $ apt-get install pyhoca-cli @@ -130,6 +134,6 @@ For now, bugs can be reported via mail to mike.gabriel@das-netzwerkteam.de -light+love, 20110718 +light+love, 20142010 Mike Gabriel diff -Nru python-x2go-0.1.1.8/README.Trinity-Desktop python-x2go-0.5.0.6/README.Trinity-Desktop --- python-x2go-0.1.1.8/README.Trinity-Desktop 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/README.Trinity-Desktop 2017-12-12 06:52:58.000000000 +0000 @@ -1,13 +1,13 @@ -Trinity Deskop (KDE3.5 fork) and (Python) X2go +Trinity Deskop (KDE3.5 fork) and (Python) X2Go ============================================== -Python X2go brings support to start Trinity Desktops on remote -X2go servers. +Python X2Go brings support to start Trinity Desktops on remote +X2Go servers. -For this to work let a symbolic link on the X2go server +For this to work let a symbolic link on the X2Go server point from /usr/local/bin/starttrinity to Trinity's startkde script: root@x2goserver:~ ln -s /opt/trinity/bin/startkde /usr/local/bin/starttrinity light+love, 20110527 -Mike Gabriel \ No newline at end of file +Mike Gabriel diff -Nru python-x2go-0.1.1.8/setup.py python-x2go-0.5.0.6/setup.py --- python-x2go-0.1.1.8/setup.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/setup.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,34 +1,49 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010 by Mike Gabriel -# -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +import os + from setuptools import setup, find_packages -import x2go + +# silence pyflakes, the correct __VERSION__ will be detected below... +__VERSION__ = "0.0.0.0" +try: + # for python3.x + for line in open(os.path.join('x2go', '__init__.py'),encoding='utf-8').readlines(): + if (line.startswith('__VERSION__')): + exec(line.strip()) +except TypeError: + # for older python2.x versions + for line in file(os.path.join('x2go', '__init__.py')).readlines(): + if (line.startswith('__VERSION__')): + exec(line.strip()) +MODULE_VERSION = __VERSION__ setup( name = "x2go", - version = x2go.__VERSION__, - description = "Python X2go implements an X2go client/session library in python based on the paramiko SSH library.", - license = 'GPL', + version = MODULE_VERSION, + description = "Python X2Go implements an X2Go client/session library in Python based on the Python Paramiko SSH module.", + license = 'AGPLv3+', author = 'Mike Gabriel', url = 'http://www.x2go.org', packages = find_packages('.'), package_dir = {'': '.'}, - install_requires = ['setuptools', 'gevent', 'paramiko', ] + install_requires = ['gevent', 'paramiko', ] ) diff -Nru python-x2go-0.1.1.8/test.py python-x2go-0.5.0.6/test.py --- python-x2go-0.1.1.8/test.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/test.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,25 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010 by Mike Gabriel -# -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """ -Unit tests for Python X2go. +Unit tests for Python X2Go. """ import os diff -Nru python-x2go-0.1.1.8/TODO python-x2go-0.5.0.6/TODO --- python-x2go-0.1.1.8/TODO 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/TODO 2017-12-12 06:52:58.000000000 +0000 @@ -1,19 +1,21 @@ -python-x2go - Copyright (C) 2010-2011 by Mike Gabriel +python-x2go - Copyright (C) 2010-2016 by Mike Gabriel -Published under the terms of the GNU General Public License. -See http://www.gnu.org/licenses/gpl.html for a recent copy. +Published under the terms of the GNU Affero General Public License. +See http://www.gnu.org/licenses/agpl.html for a recent copy. === python-x2go TODOs === -as of 20110701 +as of 20141020 -------------- -* add LDAP support -* add HTTP broker support -* add Windows Registry backend (session profiles, client configs) -* add gconf backend (session profiles, client configs) +* add SSH broker support +* drop Python Paramiko and wrap around openSSH +* become ready for X2Go Server 5 (openSSH with socket tunnel endpoints plus + event based session management) +* low priority: add Windows Registry backend (session profiles, client configs) +* even lower priority: add gconf backend (session profiles, client configs) -=== Python X2go wishlist === +=== Python X2Go wishlist === * SOCKS client support @@ -21,5 +23,5 @@ To report bugs and ideas, please add your contributions and comments on http://code.x2go.org/ -light+love, 20110701 +light+love, 20141020 Mike Gabriel diff -Nru python-x2go-0.1.1.8/x2go/backends/control/__init__.py python-x2go-0.5.0.6/x2go/backends/control/__init__.py --- python-x2go-0.1.1.8/x2go/backends/control/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/control/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -from x2go.defaults import BACKEND_CONTROLSESSION_DEFAULT - -from _stdout import X2goControlSessionSTDOUT - -X2goControlSession = eval(BACKEND_CONTROLSESSION_DEFAULT) diff -Nru python-x2go-0.1.1.8/x2go/backends/control/plain.py python-x2go-0.5.0.6/x2go/backends/control/plain.py --- python-x2go-0.1.1.8/x2go/backends/control/plain.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/control/plain.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,1932 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoControlSession} class - core functions for handling your individual X2Go sessions. + +This backend handles X2Go server implementations that respond via server-side PLAIN text output. + +""" +__NAME__ = 'x2gocontrolsession-pylib' + +# modules +import os +import types +import paramiko +import gevent +import copy +import string +import random +import re +import locale +import threading +import cStringIO +import base64 +import uuid + +from gevent import socket + +# Python X2Go modules +import x2go.sshproxy as sshproxy +import x2go.log as log +import x2go.utils as utils +import x2go.x2go_exceptions as x2go_exceptions +import x2go.defaults as defaults +import x2go.checkhosts as checkhosts + +from x2go.defaults import BACKENDS as _BACKENDS + +import x2go._paramiko +x2go._paramiko.monkey_patch_paramiko() + +def _rerewrite_blanks(cmd): + """\ + In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. + Commands get rewritten in the terminal sessions. This re-rewrite function helps + displaying command string in log output. + + @param cmd: command that has to be rewritten for log output + @type cmd: C{str} + + @return: the command with ,,X2GO_SPACE_CHAR'' re-replaced by blanks + @rtype: C{str} + + """ + # X2Go run command replace X2GO_SPACE_CHAR string with blanks + if cmd: + cmd = cmd.replace("X2GO_SPACE_CHAR", " ") + return cmd + +def _rewrite_password(cmd, user=None, password=None): + """\ + In command strings Python X2Go replaces some macros with actual values: + + - X2GO_USER -> the user name under which the user is authenticated via SSH + - X2GO_PASSWORD -> the password being used for SSH authentication + + Both macros can be used to on-the-fly authenticate via RDP. + + @param cmd: command that is to be sent to an X2Go server script + @type cmd: C{str} + @param user: the SSH authenticated user name + @type password: the password being used for SSH authentication + + @return: the command with macros replaced + @rtype: C{str} + + """ + # if there is a ,,-u X2GO_USER'' parameter in RDP options then we will replace + # it by our X2Go session password + if cmd and user: + cmd = cmd.replace('X2GO_USER', user) + # if there is a ,,-p X2GO_PASSWORD'' parameter in RDP options then we will replace + # it by our X2Go session password + if cmd and password: + cmd = cmd.replace('X2GO_PASSWORD', password) + return cmd + + +class X2GoControlSession(paramiko.SSHClient): + """\ + In the Python X2Go concept, X2Go sessions fall into two parts: a control session and one to many terminal sessions. + + The control session handles the SSH based communication between server and client. It is mainly derived from + C{paramiko.SSHClient} and adds on X2Go related functionality. + + """ + def __init__(self, + profile_name='UNKNOWN', + add_to_known_hosts=False, + known_hosts=None, + forward_sshagent=False, + unique_hostkey_aliases=False, + terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], + info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], + list_backend=_BACKENDS['X2GoServerSessionList']['default'], + proxy_backend=_BACKENDS['X2GoProxy']['default'], + client_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_CLIENT_ROOTDIR), + sessions_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SESSIONS_ROOTDIR), + ssh_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SSH_ROOTDIR), + logger=None, loglevel=log.loglevel_DEFAULT, + published_applications_no_submenus=0, + low_latency=False, + **kwargs): + """\ + Initialize an X2Go control session. For each connected session profile there will be one SSH-based + control session and one to many terminal sessions that all server-client-communicate via this one common control + session. + + A control session normally gets set up by an L{X2GoSession} instance. Do not use it directly!!! + + @param profile_name: the profile name of the session profile this control session works for + @type profile_name: C{str} + @param add_to_known_hosts: Auto-accept server host validity? + @type add_to_known_hosts: C{bool} + @param known_hosts: the underlying Paramiko/SSH systems C{known_hosts} file + @type known_hosts: C{str} + @param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side + @type forward_sshagent: C{bool} + @param unique_hostkey_aliases: instead of storing []: in known_hosts file, use the + (unique-by-design) profile ID + @type unique_hostkey_aliases: C{bool} + @param terminal_backend: X2Go terminal session backend to use + @type terminal_backend: C{str} + @param info_backend: backend for handling storage of server session information + @type info_backend: C{X2GoServerSessionInfo*} instance + @param list_backend: backend for handling storage of session list information + @type list_backend: C{X2GoServerSessionList*} instance + @param proxy_backend: backend for handling the X-proxy connections + @type proxy_backend: C{X2GoProxy*} instance + @param client_rootdir: client base dir (default: ~/.x2goclient) + @type client_rootdir: C{str} + @param sessions_rootdir: sessions base dir (default: ~/.x2go) + @type sessions_rootdir: C{str} + @param ssh_rootdir: ssh base dir (default: ~/.ssh) + @type ssh_rootdir: C{str} + @param published_applications_no_submenus: published applications menus with less items than C{published_applications_no_submenus} + are rendered without submenus + @type published_applications_no_submenus: C{int} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoControlSession} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + @param low_latency: set this boolean switch for weak connections, it will double all timeout values. + @type low_latency: C{bool} + @param kwargs: catch any non-defined parameters in C{kwargs} + @type kwargs: C{dict} + + """ + self.associated_terminals = {} + self.terminated_terminals = [] + + self.profile_name = profile_name + self.add_to_known_hosts = add_to_known_hosts + self.known_hosts = known_hosts + self.forward_sshagent = forward_sshagent + self.unique_hostkey_aliases = unique_hostkey_aliases + + self.hostname = None + self.port = None + + self.sshproxy_session = None + + self._session_auth_rsakey = None + self._remote_home = None + self._remote_group = {} + self._remote_username = None + self._remote_peername = None + + self._server_versions = None + self._server_features = None + + if logger is None: + self.logger = log.X2GoLogger(loglevel=loglevel) + else: + self.logger = copy.deepcopy(logger) + self.logger.tag = __NAME__ + + self._terminal_backend = terminal_backend + self._info_backend = info_backend + self._list_backend = list_backend + self._proxy_backend = proxy_backend + + self.client_rootdir = client_rootdir + self.sessions_rootdir = sessions_rootdir + self.ssh_rootdir = ssh_rootdir + + self._published_applications_menu = {} + + self.agent_chan = None + self.agent_handler = None + + paramiko.SSHClient.__init__(self) + if self.add_to_known_hosts: + self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + self.session_died = False + + self.low_latency = low_latency + + self.published_applications_no_submenus = published_applications_no_submenus + self._already_querying_published_applications = threading.Lock() + + self._transport_lock = threading.Lock() + + def get_hostname(self): + """\ + Get the hostname as stored in the properties of this control session. + + @return: the hostname of the connected X2Go server + @rtype: C{str} + + """ + return self.hostname + + def get_port(self): + """\ + Get the port number of the SSH connection as stored in the properties of this control session. + + @return: the server-side port number of the control session's SSH connection + @rtype: C{str} + + """ + return self.port + + def load_session_host_keys(self): + """\ + Load known SSH host keys from the C{known_hosts} file. + + If the file does not exist, create it first. + + """ + if self.known_hosts is not None: + utils.touch_file(self.known_hosts) + self.load_host_keys(self.known_hosts) + + def __del__(self): + """\ + On instance descruction, do a proper session disconnect from the server. + + """ + self.disconnect() + + def test_sftpclient(self): + ssh_transport = self.get_transport() + try: + self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) + except (AttributeError, paramiko.SFTPError): + raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') + + def _x2go_sftp_put(self, local_path, remote_path, timeout=20): + """ + Put a local file on the remote server via sFTP. + + During sFTP operations, remote command execution gets blocked. + + @param local_path: full local path name of the file to be put on the server + @type local_path: C{str} + @param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant + @type remote_path: C{str} + @param timeout: this SFTP put action should not take longer then the given value + @type timeout: C{int} + + @raise X2GoControlSessionException: if the SSH connection dropped out + + """ + ssh_transport = self.get_transport() + self._transport_lock.acquire() + if ssh_transport and ssh_transport.is_authenticated(): + self.logger('sFTP-put: %s -> %s:%s' % (os.path.normpath(local_path), self.remote_peername(), remote_path), loglevel=log.loglevel_DEBUG) + + if self.low_latency: timeout = timeout * 2 + timer = gevent.Timeout(timeout) + timer.start() + + try: + try: + self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) + except paramiko.SFTPError: + self._transport_lock.release() + raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') + try: + self.sftp_client.put(os.path.normpath(local_path), remote_path) + except (x2go_exceptions.SSHException, socket.error, IOError): + # react to connection dropped error for SSH connections + self.session_died = True + self._transport_lock.release() + raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP put action.') + + except gevent.timeout.Timeout: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') + finally: + timer.cancel() + + self.sftp_client = None + if self._transport_lock.locked(): + self._transport_lock.release() + + def _x2go_sftp_write(self, remote_path, content, timeout=20): + """ + Create a text file on the remote server via sFTP. + + During sFTP operations, remote command execution gets blocked. + + @param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant + @type remote_path: C{str} + @param content: a text file, multi-line files use Unix-link EOL style + @type content: C{str} + @param timeout: this SFTP write action should not take longer then the given value + @type timeout: C{int} + + @raise X2GoControlSessionException: if the SSH connection dropped out + + """ + ssh_transport = self.get_transport() + self._transport_lock.acquire() + if ssh_transport and ssh_transport.is_authenticated(): + self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG) + + if self.low_latency: timeout = timeout * 2 + timer = gevent.Timeout(timeout) + timer.start() + + try: + try: + self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) + except paramiko.SFTPError: + self._transport_lock.release() + raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') + try: + remote_fileobj = self.sftp_client.open(remote_path, 'w') + self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) + remote_fileobj.write(content) + remote_fileobj.close() + except (x2go_exceptions.SSHException, socket.error, IOError): + self.session_died = True + self._transport_lock.release() + self.logger('sFTP-write: opening remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN) + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP write action.') + + except gevent.timeout.Timeout: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') + finally: + timer.cancel() + + self.sftp_client = None + if self._transport_lock.locked(): + self._transport_lock.release() + + def _x2go_sftp_remove(self, remote_path, timeout=20): + """ + Remote a remote file from the server via sFTP. + + During sFTP operations, remote command execution gets blocked. + + @param remote_path: full remote path name of the server-side file to be removed, path names have to be Unix-compliant + @type remote_path: C{str} + @param timeout: this SFTP remove action should not take longer then the given value + @type timeout: C{int} + + @raise X2GoControlSessionException: if the SSH connection dropped out + + """ + ssh_transport = self.get_transport() + self._transport_lock.acquire() + if ssh_transport and ssh_transport.is_authenticated(): + self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG) + + if self.low_latency: timeout = timeout * 2 + timer = gevent.Timeout(timeout) + timer.start() + + try: + try: + self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) + except paramiko.SFTPError: + self._transport_lock.release() + raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') + try: + self.sftp_client.remove(remote_path) + except (x2go_exceptions.SSHException, socket.error, IOError): + self.session_died = True + self._transport_lock.release() + self.logger('sFTP-write: removing remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN) + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP remove action.') + + except gevent.timeout.Timeout: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') + finally: + timer.cancel() + + self.sftp_client = None + if self._transport_lock.locked(): + self._transport_lock.release() + + def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, timeout=20, **kwargs): + """ + Execute an X2Go server-side command via SSH. + + During SSH command executions, sFTP operations get blocked. + + @param cmd_line: the command to be executed on the remote server + @type cmd_line: C{str} or C{list} + @param loglevel: use this loglevel for reporting about remote command execution + @type loglevel: C{int} + @param timeout: if commands take longer than C{} to be executed, consider the control session connection + to have died. + @type timeout: C{int} + @param kwargs: parameters that get passed through to the C{paramiko.SSHClient.exec_command()} method. + @type kwargs: C{dict} + + @return: C{True} if the command could be successfully executed on the remote X2Go server + @rtype: C{bool} + + @raise X2GoControlSessionException: if the command execution failed (due to a lost connection) + + """ + if type(cmd_line) == types.ListType: + cmd = " ".join(cmd_line) + else: + cmd = cmd_line + + cmd_uuid = str(uuid.uuid1()) + cmd = 'echo X2GODATABEGIN:%s; PATH=/usr/local/bin:/usr/bin:/bin sh -c \"%s\"; echo X2GODATAEND:%s' % (cmd_uuid, cmd, cmd_uuid) + + if self.session_died: + self.logger("control session seams to be dead, not executing command ,,%s'' on X2Go server %s" % (_rerewrite_blanks(cmd), self.profile_name,), loglevel=loglevel) + return (cStringIO.StringIO(), cStringIO.StringIO(), cStringIO.StringIO('failed to execute command')) + + self._transport_lock.acquire() + + _retval = None + _password = None + + ssh_transport = self.get_transport() + if ssh_transport and ssh_transport.is_authenticated(): + + if self.low_latency: timeout = timeout * 2 + timer = gevent.Timeout(timeout) + timer.start() + try: + self.logger("executing command on X2Go server ,,%s'': %s" % (self.profile_name, _rerewrite_blanks(cmd)), loglevel=loglevel) + if self._session_password: + _password = base64.b64decode(self._session_password) + _retval = self.exec_command(_rewrite_password(cmd, user=self.get_transport().get_username(), password=_password), **kwargs) + except AttributeError: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') + except EOFError: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') + except x2go_exceptions.SSHException: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') + except gevent.timeout.Timeout: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session command timed out') + except socket.error: + self.session_died = True + self._transport_lock.release() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') + finally: + timer.cancel() + + else: + self._transport_lock.release() + raise x2go_exceptions.X2GoControlSessionException('the X2Go control session is not connected (while issuing SSH command=%s)' % cmd) + + if self._transport_lock.locked(): + self._transport_lock.release() + + # sanitized X2Go relevant data, protect against data injection via .bashrc files + (_stdin, _stdout, _stderr) = _retval + raw_stdout = _stdout.read() + + sanitized_stdout = '' + is_x2go_data = False + for line in raw_stdout.split('\n'): + if line.startswith('X2GODATABEGIN:'+cmd_uuid): + is_x2go_data = True + continue + if not is_x2go_data: continue + if line.startswith('X2GODATAEND:'+cmd_uuid): break + sanitized_stdout += line + "\n" + + _stdout_new = cStringIO.StringIO(sanitized_stdout) + + _retval = (_stdin, _stdout_new, _stderr) + return _retval + + @property + def _x2go_server_versions(self): + """\ + Render a dictionary of server-side X2Go components and their versions. Results get cached + once there has been one successful query. + + """ + if self._server_versions is None: + self._server_versions = {} + (stdin, stdout, stderr) = self._x2go_exec_command('which x2goversion >/dev/null && x2goversion') + _lines = stdout.read().split('\n') + for _line in _lines: + if ':' not in _line: continue + comp = _line.split(':')[0].strip() + version = _line.split(':')[1].strip() + self._server_versions.update({comp: version}) + self.logger('server-side X2Go components and their versions are: %s' % self._server_versions, loglevel=log.loglevel_DEBUG) + return self._server_versions + + def query_server_versions(self, force=False): + """\ + Do a query for the server-side list of X2Go components and their versions. + + @param force: do not use the cached component list, really ask the server (again) + @type force: C{bool} + + @return: dictionary of X2Go components (as keys) and their versions (as values) + @rtype: C{list} + + """ + if force: + self._server_versions = None + return self._x2go_server_versions + get_server_versions = query_server_versions + + @property + def _x2go_server_features(self): + """\ + Render a list of server-side X2Go features. Results get cached once there has been one successful query. + + """ + if self._server_features is None: + (stdin, stdout, stderr) = self._x2go_exec_command('which x2gofeaturelist >/dev/null && x2gofeaturelist') + self._server_features = stdout.read().split('\n') + self._server_features = [ f for f in self._server_features if f ] + self._server_features.sort() + self.logger('server-side X2Go features are: %s' % self._server_features, loglevel=log.loglevel_DEBUG) + return self._server_features + + def query_server_features(self, force=False): + """\ + Do a query for the server-side list of X2Go features. + + @param force: do not use the cached feature list, really ask the server (again) + @type force: C{bool} + + @return: list of X2Go feature names + @rtype: C{list} + + """ + if force: + self._server_features = None + return self._x2go_server_features + get_server_features = query_server_features + + @property + def _x2go_remote_home(self): + """\ + Retrieve and cache the remote home directory location. + + """ + if self._remote_home is None: + (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') + stdout_r = stdout.read() + if stdout_r: + self._remote_home = stdout_r.split()[0] + self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) + return self._remote_home + else: + return self._remote_home + + def _x2go_remote_group(self, group): + """\ + Retrieve and cache the members of a server-side POSIX group. + + @param group: remote POSIX group name + @type group: C{str} + + @return: list of POSIX group members + @rtype: C{list} + + """ + if not self._remote_group.has_key(group): + (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) + self._remote_group[group] = stdout.read().split('\n')[0].split(',') + self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) + return self._remote_group[group] + else: + return self._remote_group[group] + + def is_x2gouser(self, username): + """\ + Is the remote user allowed to launch X2Go sessions? + + FIXME: this method is currently non-functional. + + @param username: remote user name + @type username: C{str} + + @return: C{True} if the remote user is allowed to launch X2Go sessions + @rtype: C{bool} + + """ + ### + ### FIXME: + ### + # discussion about server-side access restriction based on posix group membership or similar currently + # in process (as of 20110517, mg) + #return username in self._x2go_remote_group('x2gousers') + return True + + def is_sshfs_available(self): + """\ + Check if the remote user is allowed to use SSHFS mounts. + + @return: C{True} if the user is allowed to connect client-side shares to the X2Go session + @rtype: C{bool} + + """ + (stdin, stdout, stderr) = self._x2go_exec_command('which fusermount') + + # if which returns the full path of fusermount, the current use is allowed to execute it + return bool(stdout.read()) + + def remote_username(self): + """\ + Returns (and caches) the control session's remote username. + + @return: SSH transport's user name + @rtype: C{str} + + @raise X2GoControlSessionException: on SSH connection loss + + """ + if self._remote_username is None: + if self.get_transport() is not None: + try: + self._remote_username = self.get_transport().get_username() + except: + self.session_died = True + raise x2go_exceptions.X2GoControlSessionException('Lost connection to X2Go server') + return self._remote_username + + def remote_peername(self): + """\ + Returns (and caches) the control session's remote host (name or ip). + + @return: SSH transport's peer name + @rtype: C{tuple} + + @raise X2GoControlSessionException: on SSH connection loss + + """ + if self._remote_peername is None: + if self.get_transport() is not None: + try: + self._remote_peername = self.get_transport().getpeername() + except: + self.session_died = True + raise x2go_exceptions.X2GoControlSessionException('Lost connection to X2Go server') + return self._remote_peername + + @property + def _x2go_session_auth_rsakey(self): + """\ + Generate (and cache) a temporary RSA host key for the lifetime of this control session. + + """ + if self._session_auth_rsakey is None: + self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) + return self._session_auth_rsakey + + def set_profile_name(self, profile_name): + """\ + Manipulate the control session's profile name. + + @param profile_name: new profile name for this control session + @type profile_name: C{str} + + """ + self.profile_name = profile_name + + def check_host(self, hostname, port=22): + """\ + Wraps around a Paramiko/SSH host key check. + + @param hostname: the remote X2Go server's hostname + @type hostname: C{str} + @param port: the SSH port of the remote X2Go server + @type port: C{int} + + @return: C{True} if the host key check succeeded, C{False} otherwise + @rtype: C{bool} + + """ + # trailing whitespace tolerance + hostname = hostname.strip() + + # force into IPv4 for localhost connections + if hostname in ('localhost', 'localhost.localdomain'): + hostname = '127.0.0.1' + + return checkhosts.check_ssh_host_key(self, hostname, port=port) + + def connect(self, hostname, port=22, username=None, password=None, passphrase=None, pkey=None, + key_filename=None, timeout=None, allow_agent=False, look_for_keys=False, + use_sshproxy=False, sshproxy_host=None, sshproxy_port=22, sshproxy_user=None, sshproxy_password=None, sshproxy_force_password_auth=False, + sshproxy_key_filename=None, sshproxy_pkey=None, sshproxy_look_for_keys=False, sshproxy_passphrase='', sshproxy_allow_agent=False, + sshproxy_tunnel=None, + add_to_known_hosts=None, + forward_sshagent=None, + unique_hostkey_aliases=None, + force_password_auth=False, + session_instance=None, + ): + """\ + Connect to an X2Go server and authenticate to it. This method is directly + inherited from the C{paramiko.SSHClient} class. The features of the Paramiko + SSH client connect method are recited here. The parameters C{add_to_known_hosts}, + C{force_password_auth}, C{session_instance} and all SSH proxy related parameters + have been added as X2Go specific parameters + + The server's host key is checked against the system host keys + (see C{load_system_host_keys}) and any local host keys (C{load_host_keys}). + If the server's hostname is not found in either set of host keys, the missing host + key policy is used (see C{set_missing_host_key_policy}). The default policy is + to reject the key and raise an C{SSHException}. + + Authentication is attempted in the following order of priority: + + - The C{pkey} or C{key_filename} passed in (if any) + - Any key we can find through an SSH agent + - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} + - Plain username/password auth, if a password was given + + If a private key requires a password to unlock it, and a password is + passed in, that password will be used to attempt to unlock the key. + + @param hostname: the server to connect to + @type hostname: C{str} + @param port: the server port to connect to + @type port: C{int} + @param username: the username to authenticate as (defaults to the + current local username) + @type username: C{str} + @param password: a password to use for authentication or for unlocking + a private key + @type password: C{str} + @param passphrase: a passphrase to use for unlocking + a private key in case the password is already needed for two-factor + authentication + @type passphrase: C{str} + @param key_filename: the filename, or list of filenames, of optional + private key(s) to try for authentication + @type key_filename: C{str} or list(str) + @param pkey: an optional private key to use for authentication + @type pkey: C{PKey} + @param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side + (will update the class property of the same name) + @type forward_sshagent: C{bool} + @param unique_hostkey_aliases: update the unique_hostkey_aliases class property + @type unique_hostkey_aliases: C{bool} + @param timeout: an optional timeout (in seconds) for the TCP connect + @type timeout: float + @param look_for_keys: set to C{True} to enable searching for discoverable + private key files in C{~/.ssh/} + @type look_for_keys: C{bool} + @param allow_agent: set to C{True} to enable connecting to a local SSH agent + for acquiring authentication information + @type allow_agent: C{bool} + @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() + is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() + is used + @type add_to_known_hosts: C{bool} + @param force_password_auth: non-paramiko option, disable pub/priv key authentication + completely, even if the C{pkey} or the C{key_filename} parameter is given + @type force_password_auth: C{bool} + @param session_instance: an instance L{X2GoSession} using this L{X2GoControlSession} + instance. + @type session_instance: C{obj} + @param use_sshproxy: connect through an SSH proxy + @type use_sshproxy: C{True} if an SSH proxy is to be used for tunneling the connection + @param sshproxy_host: hostname of the SSH proxy server + @type sshproxy_host: C{str} + @param sshproxy_port: port of the SSH proxy server + @type sshproxy_port: C{int} + @param sshproxy_user: username that we use for authenticating against C{} + @type sshproxy_user: C{str} + @param sshproxy_password: a password to use for SSH proxy authentication or for unlocking + a private key + @type sshproxy_password: C{str} + @param sshproxy_passphrase: a passphrase to use for unlocking + a private key needed for the SSH proxy host in case the sshproxy_password is already needed for + two-factor authentication + @type sshproxy_passphrase: C{str} + @param sshproxy_force_password_auth: enforce using a given C{sshproxy_password} even if a key(file) is given + @type sshproxy_force_password_auth: C{bool} + @param sshproxy_key_filename: local file location of the private key file + @type sshproxy_key_filename: C{str} + @param sshproxy_pkey: an optional private key to use for SSH proxy authentication + @type sshproxy_pkey: C{PKey} + @param sshproxy_look_for_keys: set to C{True} to enable connecting to a local SSH agent + for acquiring authentication information (for SSH proxy authentication) + @type sshproxy_look_for_keys: C{bool} + @param sshproxy_allow_agent: set to C{True} to enable connecting to a local SSH agent + for acquiring authentication information (for SSH proxy authentication) + @type sshproxy_allow_agent: C{bool} + @param sshproxy_tunnel: the SSH proxy tunneling parameters, format is: ::: + @type sshproxy_tunnel: C{str} + + @return: C{True} if an authenticated SSH transport could be retrieved by this method + @rtype: C{bool} + + @raise BadHostKeyException: if the server's host key could not be + verified + @raise AuthenticationException: if authentication failed + @raise SSHException: if there was any other error connecting or + establishing an SSH session + @raise socket.error: if a socket error occurred while connecting + @raise X2GoSSHProxyException: any SSH proxy exception is passed through while establishing the SSH proxy connection and tunneling setup + @raise X2GoSSHAuthenticationException: any SSH proxy authentication exception is passed through while establishing the SSH proxy connection and tunneling setup + @raise X2GoRemoteHomeException: if the remote home directory does not exist or is not accessible + @raise X2GoControlSessionException: if the remote peer has died unexpectedly + + """ + _fake_hostname = None + + if hostname and type(hostname) not in (types.UnicodeType, types.StringType): + hostname = [hostname] + if hostname and type(hostname) is types.ListType: + hostname = random.choice(hostname) + + if not username: + self.logger('no username specified, cannot connect without username', loglevel=log.loglevel_ERROR) + raise paramiko.AuthenticationException('no username specified, cannot connect without username') + + if type(password) not in (types.StringType, types.UnicodeType): + password = '' + if type(sshproxy_password) not in (types.StringType, types.UnicodeType): + sshproxy_password = '' + + if unique_hostkey_aliases is None: + unique_hostkey_aliases = self.unique_hostkey_aliases + # prep the fake hostname with the real hostname, so we trigger the corresponding code path in + # x2go.checkhosts and either of its missing host key policies + if unique_hostkey_aliases: + if port != 22: _fake_hostname = "[%s]:%s" % (hostname, port) + else: _fake_hostname = hostname + + if add_to_known_hosts is None: + add_to_known_hosts = self.add_to_known_hosts + + if forward_sshagent is None: + forward_sshagent = self.forward_sshagent + + if look_for_keys: + key_filename = None + pkey = None + + _twofactorauth = False + if password and (passphrase is None) and not force_password_auth: passphrase = password + + if use_sshproxy and sshproxy_host and sshproxy_user: + try: + if not sshproxy_tunnel: + sshproxy_tunnel = "localhost:44444:%s:%s" % (hostname, port) + self.sshproxy_session = sshproxy.X2GoSSHProxy(known_hosts=self.known_hosts, + add_to_known_hosts=add_to_known_hosts, + sshproxy_host=sshproxy_host, + sshproxy_port=sshproxy_port, + sshproxy_user=sshproxy_user, + sshproxy_password=sshproxy_password, + sshproxy_passphrase=sshproxy_passphrase, + sshproxy_force_password_auth=sshproxy_force_password_auth, + sshproxy_key_filename=sshproxy_key_filename, + sshproxy_pkey=sshproxy_pkey, + sshproxy_look_for_keys=sshproxy_look_for_keys, + sshproxy_allow_agent=sshproxy_allow_agent, + sshproxy_tunnel=sshproxy_tunnel, + session_instance=session_instance, + logger=self.logger, + ) + hostname = self.sshproxy_session.get_local_proxy_host() + port = self.sshproxy_session.get_local_proxy_port() + _fake_hostname = self.sshproxy_session.get_remote_host() + _fake_port = self.sshproxy_session.get_remote_port() + if _fake_port != 22: + _fake_hostname = "[%s]:%s" % (_fake_hostname, _fake_port) + + except: + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + self.sshproxy_session = None + raise + + if self.sshproxy_session is not None: + self.sshproxy_session.start() + + # divert port to sshproxy_session's local forwarding port (it might have changed due to + # SSH connection errors + gevent.sleep(.1) + port = self.sshproxy_session.get_local_proxy_port() + + if not add_to_known_hosts and session_instance: + self.set_missing_host_key_policy(checkhosts.X2GoInteractiveAddPolicy(caller=self, session_instance=session_instance, fake_hostname=_fake_hostname)) + + if add_to_known_hosts: + self.set_missing_host_key_policy(checkhosts.X2GoAutoAddPolicy(caller=self, session_instance=session_instance, fake_hostname=_fake_hostname)) + + # trailing whitespace tolerance in hostname + hostname = hostname.strip() + + self.logger('connecting to [%s]:%s' % (hostname, port), loglevel=log.loglevel_NOTICE) + + self.load_session_host_keys() + + _hostname = hostname + # enforce IPv4 for localhost address + if _hostname in ('localhost', 'localhost.localdomain'): + _hostname = '127.0.0.1' + + # update self.forward_sshagent via connect method parameter + if forward_sshagent is not None: + self.forward_sshagent = forward_sshagent + + if timeout and self.low_latency: + timeout = timeout * 2 + + if key_filename and "~" in key_filename: + key_filename = os.path.expanduser(key_filename) + + if key_filename or pkey or look_for_keys or allow_agent or (password and force_password_auth): + try: + if password and force_password_auth: + self.logger('trying password based SSH authentication with server', loglevel=log.loglevel_DEBUG) + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=None, + key_filename=None, timeout=timeout, allow_agent=False, + look_for_keys=False) + elif (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: + self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=pkey, + key_filename=key_filename, timeout=timeout, allow_agent=False, + look_for_keys=False) + else: + self.logger('trying SSH key discovery or agent authentication with server', loglevel=log.loglevel_DEBUG) + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=None, + key_filename=None, timeout=timeout, allow_agent=allow_agent, + look_for_keys=look_for_keys) + + except (paramiko.PasswordRequiredException, paramiko.SSHException), e: + self.close() + if type(e) == paramiko.SSHException and str(e).startswith('Two-factor authentication requires a password'): + self.logger('X2Go Server requests two-factor authentication', loglevel=log.loglevel_NOTICE) + _twofactorauth = True + if passphrase is not None: + self.logger('unlock SSH private key file with provided password', loglevel=log.loglevel_INFO) + try: + if not password: password = None + if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: + self.logger('re-trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) + try: + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, passphrase=passphrase, pkey=pkey, + key_filename=key_filename, timeout=timeout, allow_agent=False, + look_for_keys=False) + except TypeError: + if _twofactorauth and password and passphrase and password != passphrase: + self.logger('your version of Paramiko/SSH does not support authentication workflows which require SSH key decryption in combination with two-factor authentication', loglevel=log.loglevel_WARN) + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=pkey, + key_filename=key_filename, timeout=timeout, allow_agent=False, + look_for_keys=False) + else: + self.logger('re-trying SSH key discovery now with passphrase for unlocking the key(s)', loglevel=log.loglevel_DEBUG) + try: + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, passphrase=passphrase, pkey=None, + key_filename=None, timeout=timeout, allow_agent=allow_agent, + look_for_keys=look_for_keys) + except TypeError: + if _twofactorauth and password and passphrase and password != passphrase: + self.logger('your version of Paramiko/SSH does not support authentication workflows which require SSH key decryption in combination with two-factor authentication', loglevel=log.loglevel_WARN) + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=None, + key_filename=None, timeout=timeout, allow_agent=allow_agent, + look_for_keys=look_for_keys) + + except paramiko.AuthenticationException, auth_e: + # the provided password cannot be used to unlock any private SSH key file (i.e. wrong password) + raise paramiko.AuthenticationException(str(auth_e)) + + except paramiko.SSHException, auth_e: + if str(auth_e) == 'No authentication methods available': + raise paramiko.AuthenticationException('Interactive password authentication required!') + else: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise auth_e + + else: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise e + + except paramiko.AuthenticationException, e: + self.close() + if password: + self.logger('next auth mechanism we\'ll try is password authentication', loglevel=log.loglevel_DEBUG) + try: + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, + key_filename=None, pkey=None, timeout=timeout, allow_agent=False, look_for_keys=False) + except: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise + else: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise e + + except paramiko.SSHException, e: + if str(e) == 'No authentication methods available': + raise paramiko.AuthenticationException('Interactive password authentication required!') + else: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise e + + except: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise + + # if there is no private key (and no agent auth), we will use the given password, if any + else: + # create a random password if password is empty to trigger host key validity check + if not password: + password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) + self.logger('performing SSH password authentication with server', loglevel=log.loglevel_DEBUG) + #try: + paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, + timeout=timeout, allow_agent=False, look_for_keys=False) + #except paramiko.AuthenticationException, e: + # self.close() + # if self.sshproxy_session: + # self.sshproxy_session.stop_thread() + # raise e + #except: + # self.close() + # if self.sshproxy_session: + # self.sshproxy_session.stop_thread() + # raise + + self.set_missing_host_key_policy(paramiko.RejectPolicy()) + + self.hostname = hostname + self.port = port + + # preparing reverse tunnels + ssh_transport = self.get_transport() + ssh_transport.reverse_tunnels = {} + + # mark Paramiko/SSH transport as X2GoControlSession + ssh_transport._x2go_session_marker = True + try: + self._session_password = base64.b64encode(password) + except TypeError: + self._session_password = None + + if ssh_transport is not None: + + # since Paramiko 1.7.7.1 there is compression available, let's use it if present... + if x2go._paramiko.PARAMIKO_FEATURE['use-compression']: + ssh_transport.use_compression(compress=False) + # enable keep alive callbacks + ssh_transport.set_keepalive(5) + + self.session_died = False + self.query_server_features(force=True) + if self.forward_sshagent: + if x2go._paramiko.PARAMIKO_FEATURE['forward-ssh-agent']: + try: + self.agent_chan = ssh_transport.open_session() + self.agent_handler = paramiko.agent.AgentRequestHandler(self.agent_chan) + self.logger('Requesting SSH agent forwarding for control session of connected session profile %s' % self.profile_name, loglevel=log.loglevel_INFO) + except EOFError, e: + # if we come across an EOFError here, we must assume the session is dead... + self.session_died = True + raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped while setting up SSH agent forwarding socket.') + else: + self.logger('SSH agent forwarding is not available in the Paramiko version used with this instance of Python X2Go', loglevel=log.loglevel_WARN) + + else: + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + + self._remote_home = None + if not self.home_exists(): + self.close() + if self.sshproxy_session: + self.sshproxy_session.stop_thread() + raise x2go_exceptions.X2GoRemoteHomeException('remote home directory does not exist') + + return (self.get_transport() is not None) + + def dissociate(self, terminal_session): + """\ + Drop an associated terminal session. + + @param terminal_session: the terminal session object to remove from the list of associated terminals + @type terminal_session: C{X2GoTerminalSession*} + + """ + for t_name in self.associated_terminals.keys(): + if self.associated_terminals[t_name] == terminal_session: + del self.associated_terminals[t_name] + if self.terminated_terminals.has_key(t_name): + del self.terminated_terminals[t_name] + + def disconnect(self): + """\ + Disconnect this control session from the remote server. + + @return: report success or failure after having disconnected + @rtype: C{bool} + + """ + if self.associated_terminals: + t_names = self.associated_terminals.keys() + for t_obj in self.associated_terminals.values(): + try: + if not self.session_died: + t_obj.suspend() + except x2go_exceptions.X2GoTerminalSessionException: + pass + except x2go_exceptions.X2GoControlSessionException: + self.session_died + t_obj.__del__() + for t_name in t_names: + try: + del self.associated_terminals[t_name] + except KeyError: + pass + + self._remote_home = None + self._remote_group = {} + + self._session_auth_rsakey = None + + # in any case, release out internal transport lock + if self._transport_lock.locked(): + self._transport_lock.release() + + # close SSH agent auth forwarding objects + if self.agent_handler is not None: + self.agent_handler.close() + + if self.agent_chan is not None: + try: + self.agent_chan.close() + except EOFError: + pass + + retval = False + try: + if self.get_transport() is not None: + retval = self.get_transport().is_active() + try: + self.close() + except IOError: + pass + except AttributeError: + # if the Paramiko _transport object has not yet been initialized, ignore it + # but state that this method call did not close the SSH client, but was already closed + pass + + # take down sshproxy_session no matter what happened to the control session itself + if self.sshproxy_session is not None: + self.sshproxy_session.stop_thread() + + return retval + + def home_exists(self): + """\ + Test if the remote home directory exists. + + @return: C{True} if the home directory exists, C{False} otherwise + @rtype: C{bool} + + """ + (_stdin, _stdout, _stderr) = self._x2go_exec_command('stat -tL "%s"' % self._x2go_remote_home, loglevel=log.loglevel_DEBUG) + if _stdout.read(): + return True + return False + + + def is_alive(self): + """\ + Test if the connection to the remote X2Go server is still alive. + + @return: C{True} if the connection is still alive, C{False} otherwise + @rtype: C{bool} + + """ + try: + if self._x2go_exec_command('echo', loglevel=log.loglevel_DEBUG): + return True + except x2go_exceptions.X2GoControlSessionException: + self.session_died = True + self.disconnect() + return False + + def has_session_died(self): + """\ + Test if the connection to the remote X2Go server died on the way. + + @return: C{True} if the connection has died, C{False} otherwise + @rtype: C{bool} + + """ + return self.session_died + + def get_published_applications(self, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=defaults.PUBAPP_MAX_NO_SUBMENUS): + """\ + Retrieve the menu tree of published applications from the remote X2Go server. + + The C{raw} option lets this method return a C{list} of C{dict} elements. Each C{dict} elements has a + C{desktop} key containing a shortened version of the text output of a .desktop file and an C{icon} key + which contains the desktop base64-encoded icon data. + + The {very_raw} lets this method return the output of the C{x2gogetapps} script as is. + + @param lang: locale/language identifier + @type lang: C{str} + @param refresh: force reload of the menu tree from X2Go server + @type refresh: C{bool} + @param raw: retrieve a raw output of the server list of published applications + @type raw: C{bool} + @param very_raw: retrieve a very raw output of the server list of published applications + @type very_raw: C{bool} + + @return: an i18n capable menu tree packed as a Python dictionary + @rtype: C{list} + + """ + self._already_querying_published_applications.acquire() + + if defaults.X2GOCLIENT_OS != 'Windows' and lang is None: + lang = locale.getdefaultlocale()[0] + elif lang is None: + lang = 'en' + + if 'X2GO_PUBLISHED_APPLICATIONS' in self.get_server_features(): + if self._published_applications_menu is {} or \ + not self._published_applications_menu.has_key(lang) or \ + raw or very_raw or refresh or \ + (self.published_applications_no_submenus != max_no_submenus): + + self.published_applications_no_submenus = max_no_submenus + + ### STAGE 1: retrieve menu from server + + self.logger('querying server (%s) for list of published applications' % self.profile_name, loglevel=log.loglevel_NOTICE) + (stdin, stdout, stderr) = self._x2go_exec_command('which x2gogetapps >/dev/null && x2gogetapps') + _raw_output = stdout.read() + + if very_raw: + self.logger('published applications query for %s finished, return very raw output' % self.profile_name, loglevel=log.loglevel_NOTICE) + self._already_querying_published_applications.release() + return _raw_output + + ### STAGE 2: dissect the text file retrieved from server, cut into single menu elements + + _raw_menu_items = _raw_output.split('\n') + _raw_menu_items = [ i.replace('\n', '') for i in _raw_menu_items ] + _menu = [] + for _raw_menu_item in _raw_menu_items: + if '\n' in _raw_menu_item and '' in _raw_menu_item: + _menu_item = _raw_menu_item.split('\n')[0] + _raw_menu_item.split('\n')[1] + _icon_base64 = _raw_menu_item.split('\n')[1].split('\n')[0] + else: + _menu_item = _raw_menu_item + _icon_base64 = None + if _menu_item: + _menu.append({ 'desktop': _menu_item, 'icon': _icon_base64, }) + _menu_item = None + _icon_base64 = None + + if raw: + self.logger('published applications query for %s finished, returning raw output' % self.profile_name, loglevel=log.loglevel_NOTICE) + self._already_querying_published_applications.release() + return _menu + + if len(_menu) > max_no_submenus >= 0: + _render_submenus = True + else: + _render_submenus = False + + # STAGE 3: create menu structure in a Python dictionary + + _category_map = { + lang: { + 'Multimedia': [], + 'Development': [], + 'Education': [], + 'Games': [], + 'Graphics': [], + 'Internet': [], + 'Office': [], + 'System': [], + 'Utilities': [], + 'Other Applications': [], + 'TOP': [], + } + } + _empty_menus = _category_map[lang].keys() + + for item in _menu: + + _menu_entry_name = '' + _menu_entry_fallback_name = '' + _menu_entry_comment = '' + _menu_entry_fallback_comment = '' + _menu_entry_exec = '' + _menu_entry_cat = '' + _menu_entry_shell = False + + lang_regio = lang + lang_only = lang_regio.split('_')[0] + + for line in item['desktop'].split('\n'): + if re.match('^Name\[%s\]=.*' % lang_regio, line) or re.match('Name\[%s\]=.*' % lang_only, line): + _menu_entry_name = line.split("=")[1].strip() + elif re.match('^Name=.*', line): + _menu_entry_fallback_name = line.split("=")[1].strip() + elif re.match('^Comment\[%s\]=.*' % lang_regio, line) or re.match('Comment\[%s\]=.*' % lang_only, line): + _menu_entry_comment = line.split("=")[1].strip() + elif re.match('^Comment=.*', line): + _menu_entry_fallback_comment = line.split("=")[1].strip() + elif re.match('^Exec=.*', line): + _menu_entry_exec = line.split("=")[1].strip() + elif re.match('^Terminal=.*(t|T)(r|R)(u|U)(e|E).*', line): + _menu_entry_shell = True + elif re.match('^Categories=.*', line): + if 'X2Go-Top' in line: + _menu_entry_cat = 'TOP' + elif 'Audio' in line or 'Video' in line: + _menu_entry_cat = 'Multimedia' + elif 'Development' in line: + _menu_entry_cat = 'Development' + elif 'Education' in line: + _menu_entry_cat = 'Education' + elif 'Game' in line: + _menu_entry_cat = 'Games' + elif 'Graphics' in line: + _menu_entry_cat = 'Graphics' + elif 'Network' in line: + _menu_entry_cat = 'Internet' + elif 'Office' in line: + _menu_entry_cat = 'Office' + elif 'Settings' in line: + continue + elif 'System' in line: + _menu_entry_cat = 'System' + elif 'Utility' in line: + _menu_entry_cat = 'Utilities' + else: + _menu_entry_cat = 'Other Applications' + + if not _menu_entry_exec: + continue + else: + # FIXME: strip off any noted options (%f, %F, %u, %U, ...), this can be more intelligent + _menu_entry_exec = _menu_entry_exec.replace('%f', '').replace('%F','').replace('%u','').replace('%U','') + if _menu_entry_shell: + _menu_entry_exec = "x-terminal-emulator -e '%s'" % _menu_entry_exec + + if not _menu_entry_cat: + _menu_entry_cat = 'Other Applications' + + if not _render_submenus: + _menu_entry_cat = 'TOP' + + if _menu_entry_cat in _empty_menus: + _empty_menus.remove(_menu_entry_cat) + + if not _menu_entry_name: _menu_entry_name = _menu_entry_fallback_name + if not _menu_entry_comment: _menu_entry_comment = _menu_entry_fallback_comment + if not _menu_entry_comment: _menu_entry_comment = _menu_entry_name + + _menu_entry_icon = item['icon'] + + _category_map[lang][_menu_entry_cat].append( + { + 'name': _menu_entry_name, + 'comment': _menu_entry_comment, + 'exec': _menu_entry_exec, + 'icon': _menu_entry_icon, + } + ) + + for _cat in _empty_menus: + del _category_map[lang][_cat] + + for _cat in _category_map[lang].keys(): + _sorted = sorted(_category_map[lang][_cat], key=lambda k: k['name']) + _category_map[lang][_cat] = _sorted + + self._published_applications_menu.update(_category_map) + self.logger('published applications query for %s finished, return menu tree' % self.profile_name, loglevel=log.loglevel_NOTICE) + + else: + # FIXME: ignoring the absence of the published applications feature for now, handle it appropriately later + pass + + self._already_querying_published_applications.release() + return self._published_applications_menu + + def start(self, **kwargs): + """\ + Start a new X2Go session. + + The L{X2GoControlSession.start()} method accepts any parameter + that can be passed to any of the C{X2GoTerminalSession} backend class + constructors. + + @param kwargs: parameters that get passed through to the control session's + L{resume()} method, only the C{session_name} parameter will get removed + before pass-through + @type kwargs: C{dict} + + @return: return value of the cascaded L{resume()} method, denoting the success or failure + of the session startup + @rtype: C{bool} + + """ + if 'session_name' in kwargs.keys(): + del kwargs['session_name'] + return self.resume(**kwargs) + + def resume(self, session_name=None, session_instance=None, session_list=None, **kwargs): + """\ + Resume a running/suspended X2Go session. + + The L{X2GoControlSession.resume()} method accepts any parameter + that can be passed to any of the C{X2GoTerminalSession*} backend class constructors. + + @return: True if the session could be successfully resumed + @rtype: C{bool} + + @raise X2GoUserException: if the remote user is not allowed to launch/resume X2Go sessions. + + """ + if self.get_transport() is not None: + + if not self.is_x2gouser(self.get_transport().get_username()): + raise x2go_exceptions.X2GoUserException('remote user %s is not allowed to run X2Go commands' % self.get_transport().get_username()) + + session_info = None + try: + if session_name is not None: + if session_list: + session_info = session_list[session_name] + else: + session_info = self.list_sessions()[session_name] + except KeyError: + _success = False + + _terminal = self._terminal_backend(self, + profile_name=self.profile_name, + session_info=session_info, + info_backend=self._info_backend, + list_backend=self._list_backend, + proxy_backend=self._proxy_backend, + client_rootdir=self.client_rootdir, + session_instance=session_instance, + sessions_rootdir=self.sessions_rootdir, + **kwargs) + + _success = False + try: + if session_name is not None: + _success = _terminal.resume() + else: + _success = _terminal.start() + except x2go_exceptions.X2GoTerminalSessionException: + _success = False + + if _success: + while not _terminal.ok(): + gevent.sleep(.2) + + if _terminal.ok(): + self.associated_terminals[_terminal.get_session_name()] = _terminal + self.get_transport().reverse_tunnels[_terminal.get_session_name()] = { + 'sshfs': (0, None), + 'snd': (0, None), + } + + return _terminal or None + + return None + + def share_desktop(self, desktop=None, user=None, display=None, share_mode=0, **kwargs): + """\ + Share another already running desktop session. Desktop sharing can be run + in two different modes: view-only and full-access mode. + + @param desktop: desktop ID of a sharable desktop in format C{@} + @type desktop: C{str} + @param user: user name and display number can be given separately, here give the + name of the user who wants to share a session with you + @type user: C{str} + @param display: user name and display number can be given separately, here give the + number of the display that a user allows you to be shared with + @type display: C{str} + @param share_mode: desktop sharing mode, 0 stands for VIEW-ONLY, 1 for FULL-ACCESS mode + @type share_mode: C{int} + + @return: True if the session could be successfully shared + @rtype: C{bool} + + @raise X2GoDesktopSharingException: if C{username} and C{dislpay} do not relate to a + sharable desktop session + + """ + if desktop: + user = desktop.split('@')[0] + display = desktop.split('@')[1] + if not (user and display): + raise x2go_exceptions.X2GoDesktopSharingException('Need user name and display number of shared desktop.') + + cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display) + + kwargs['cmd'] = cmd + kwargs['session_type'] = 'shared' + + return self.start(**kwargs) + + def list_desktops(self, raw=False, maxwait=20): + """\ + List all desktop-like sessions of current user (or of users that have + granted desktop sharing) on the connected server. + + @param raw: if C{True}, the raw output of the server-side X2Go command + C{x2golistdesktops} is returned. + @type raw: C{bool} + + @return: a list of X2Go desktops available for sharing + @rtype: C{list} + + @raise X2GoTimeOutException: on command execution timeouts, with the server-side C{x2golistdesktops} + command this can sometimes happen. Make sure you ignore these time-outs and to try again + + """ + if raw: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") + return stdout.read(), stderr.read() + + else: + + # this _success loop will catch errors in case the x2golistsessions output is corrupt + # this should not be needed and is a workaround for the current X2Go server implementation + + if self.low_latency: + maxwait = maxwait * 2 + + timeout = gevent.Timeout(maxwait) + timeout.start() + try: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") + _stdout_read = stdout.read() + _listdesktops = _stdout_read.split('\n') + except gevent.timeout.Timeout: + # if we do not get a reply here after seconds we will raise a time out, we have to + # make sure that we catch this at places where we want to ignore timeouts (e.g. in the + # desktop list cache) + raise x2go_exceptions.X2GoTimeOutException('x2golistdesktop command timed out') + finally: + timeout.cancel() + + return _listdesktops + + def list_mounts(self, session_name, raw=False, maxwait=20): + """\ + List all mounts for a given session of the current user on the connected server. + + @param session_name: name of a session to query a list of mounts for + @type session_name: C{str} + @param raw: if C{True}, the raw output of the server-side X2Go command + C{x2golistmounts} is returned. + @type raw: C{bool} + @param maxwait: stop processing C{x2golistmounts} after C{} seconds + @type maxwait: C{int} + + @return: a list of client-side mounts for X2Go session C{} on the server + @rtype: C{list} + + @raise X2GoTimeOutException: on command execution timeouts, queries with the server-side + C{x2golistmounts} query should normally be processed quickly, a time-out may hint that the + control session has lost its connection to the X2Go server + + """ + if raw: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name) + return stdout.read(), stderr.read() + + else: + + if self.low_latency: + maxwait = maxwait * 2 + + # this _success loop will catch errors in case the x2golistmounts output is corrupt + + timeout = gevent.Timeout(maxwait) + timeout.start() + try: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name) + _stdout_read = stdout.read() + _listmounts = {session_name: [ line for line in _stdout_read.split('\n') if line ] } + except gevent.timeout.Timeout: + # if we do not get a reply here after seconds we will raise a time out, we have to + # make sure that we catch this at places where we want to ignore timeouts + raise x2go_exceptions.X2GoTimeOutException('x2golistmounts command timed out') + finally: + timeout.cancel() + + return _listmounts + + def list_sessions(self, raw=False): + """\ + List all sessions of current user on the connected server. + + @param raw: if C{True}, the raw output of the server-side X2Go command + C{x2golistsessions} is returned. + @type raw: C{bool} + + @return: normally an instance of a C{X2GoServerSessionList*} backend is returned. However, + if the raw argument is set, the plain text output of the server-side C{x2golistsessions} + command is returned + @rtype: C{X2GoServerSessionList} instance or str + + @raise X2GoControlSessionException: on command execution timeouts, if this happens the control session will + be interpreted as disconnected due to connection loss + """ + if raw: + if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") + else: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") + return stdout.read(), stderr.read() + + else: + + # this _success loop will catch errors in case the x2golistsessions output is corrupt + # this should not be needed and is a workaround for the current X2Go server implementation + _listsessions = {} + _success = False + _count = 0 + _maxwait = 20 + + # we will try this 20 times before giving up... we might simply catch the x2golistsessions + # output in the middle of creating a session in the database... + while not _success and _count < _maxwait: + _count += 1 + try: + if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") + else: + (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") + _stdout_read = stdout.read() + _listsessions = self._list_backend(_stdout_read, info_backend=self._info_backend).sessions + _success = True + except KeyError: + gevent.sleep(1) + except IndexError: + gevent.sleep(1) + except ValueError: + gevent.sleep(1) + + if _count >= _maxwait: + self.session_died = True + self.disconnect() + raise x2go_exceptions.X2GoControlSessionException('x2golistsessions command failed after we have tried 20 times') + + # update internal variables when list_sessions() is called + if _success and not self.session_died: + for _session_name, _terminal in self.associated_terminals.items(): + if _session_name in _listsessions.keys(): + # update the whole session_info object within the terminal session + if hasattr(self.associated_terminals[_session_name], 'session_info') and not self.associated_terminals[_session_name].is_session_info_protected(): + self.associated_terminals[_session_name].session_info.update(_listsessions[_session_name]) + else: + self.associated_terminals[_session_name].__del__() + try: del self.associated_terminals[_session_name] + except KeyError: pass + self.terminated_terminals.append(_session_name) + if _terminal.is_suspended(): + self.associated_terminals[_session_name].__del__() + try: del self.associated_terminals[_session_name] + except KeyError: pass + + return _listsessions + + def clean_sessions(self, destroy_terminals=True, published_applications=False): + """\ + Find X2Go terminals that have previously been started by the + connected user on the remote X2Go server and terminate them. + + @param destroy_terminals: destroy the terminal session instances after cleanup + @type destroy_terminals: C{bool} + @param published_applications: also clean up published applications providing sessions + @type published_applications: C{bool} + + """ + session_list = self.list_sessions() + if published_applications: + session_names = session_list.keys() + else: + session_names = [ _sn for _sn in session_list.keys() if not session_list[_sn].is_published_applications_provider() ] + for session_name in session_names: + if self.associated_terminals.has_key(session_name): + self.associated_terminals[session_name].terminate() + if destroy_terminals: + if self.associated_terminals[session_name] is not None: + self.associated_terminals[session_name].__del__() + try: del self.associated_terminals[session_name] + except KeyError: pass + else: + self.terminate(session_name=session_name) + + def is_connected(self): + """\ + Returns C{True} if this control session is connected to the remote server (that + is: if it has a valid Paramiko/SSH transport object). + + @return: X2Go session connected? + @rtype: C{bool} + + """ + return self.get_transport() is not None and self.get_transport().is_authenticated() + + def is_running(self, session_name): + """\ + Returns C{True} if the given X2Go session is in running state, + C{False} else. + + @param session_name: X2Go name of the session to be queried + @type session_name: C{str} + + @return: X2Go session running? If C{} is not listable by the L{list_sessions()} method then C{None} is returned + @rtype: C{bool} or C{None} + + """ + session_infos = self.list_sessions() + if session_name in session_infos.keys(): + return session_infos[session_name].is_running() + return None + + def is_suspended(self, session_name): + """\ + Returns C{True} if the given X2Go session is in suspended state, + C{False} else. + + @return: X2Go session suspended? If C{} is not listable by the L{list_sessions()} method then C{None} is returned + @rtype: C{bool} or C{None} + + """ + session_infos = self.list_sessions() + if session_name in session_infos.keys(): + return session_infos[session_name].is_suspended() + return None + + def has_terminated(self, session_name): + """\ + Returns C{True} if the X2Go session with name C{} has been seen + by this control session and--in the meantime--has been terminated. + + If C{} has not been seen, yet, the method will return C{None}. + + @return: X2Go session has terminated? + @rtype: C{bool} or C{None} + + """ + session_infos = self.list_sessions() + if session_name in self.terminated_terminals: + return True + if session_name not in session_infos.keys() and session_name in self.associated_terminals.keys(): + # do a post-mortem tidy up + self.terminate(session_name) + return True + if self.is_suspended(session_name) or self.is_running(session_name): + return False + + return None + + def suspend(self, session_name): + """\ + Suspend X2Go session with name C{} on the connected + server. + + @param session_name: X2Go name of the session to be suspended + @type session_name: C{str} + + @return: C{True} if the session could be successfully suspended + @rtype: C{bool} + + """ + _ret = False + _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] + if session_name in _session_names: + + self.logger('suspending associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) + stdout.read() + stderr.read() + if self.associated_terminals.has_key(session_name): + if self.associated_terminals[session_name] is not None: + self.associated_terminals[session_name].__del__() + try: del self.associated_terminals[session_name] + except KeyError: pass + _ret = True + + else: + + self.logger('suspending non-associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) + stdout.read() + stderr.read() + _ret = True + + return _ret + + def terminate(self, session_name, destroy_terminals=True): + """\ + Terminate X2Go session with name C{} on the connected + server. + + @param session_name: X2Go name of the session to be terminated + @type session_name: C{str} + + @return: C{True} if the session could be successfully terminated + @rtype: C{bool} + + """ + + _ret = False + if session_name in self.associated_terminals.keys(): + + self.logger('terminating associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) + stdout.read() + stderr.read() + + if destroy_terminals: + if self.associated_terminals[session_name] is not None: + self.associated_terminals[session_name].__del__() + try: del self.associated_terminals[session_name] + except KeyError: pass + + self.terminated_terminals.append(session_name) + _ret = True + + else: + + self.logger('terminating non-associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) + stdout.read() + stderr.read() + _ret = True + + return _ret diff -Nru python-x2go-0.1.1.8/x2go/backends/control/_stdout.py python-x2go-0.5.0.6/x2go/backends/control/_stdout.py --- python-x2go-0.1.1.8/x2go/backends/control/_stdout.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/control/_stdout.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,925 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goControlSessionSTDOUT class - core functions for handling your individual X2go sessions. - -This backend handles X2go server implementations that respond via server-side STDOUT. - -""" -__NAME__ = 'x2gocontrolsession-pylib' - -# modules -import os -import types -import paramiko -import gevent - -import copy -import binascii - -import string -import random - -from gevent import socket - -# Python X2go modules -import x2go.sshproxy as sshproxy -import x2go.log as log -import x2go.utils as utils -import x2go.x2go_exceptions as x2go_exceptions -import x2go.defaults as defaults -import x2go.checkhosts as checkhosts - -from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession -from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo -from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList -from x2go.backends.proxy import X2goProxy as _X2goProxy - -from x2go.monkey_patch_paramiko import monkey_patch_paramiko -monkey_patch_paramiko() - -def _rerewrite_blanks(cmd): - # X2go run command replace X2GO_SPACE_CHAR string with blanks - if cmd: - cmd = cmd.replace("X2GO_SPACE_CHAR", " ") - return cmd - -def _rewrite_password(cmd, user=None, password=None): - - # if there is a ,,-u X2GO_USER'' parameter in RDP options then we will replace - # it by our X2go session password - if cmd and user: - cmd = cmd.replace('X2GO_USER', user) - # if there is a ,,-p X2GO_PASSWORD'' parameter in RDP options then we will replace - # it by our X2go session password - if cmd and password: - cmd = cmd.replace('X2GO_PASSWORD', password) - return cmd - - -class X2goControlSessionSTDOUT(paramiko.SSHClient): - """\ - STILL UNDOCUMENTED - - @param logger: you can pass an L{X2goLogger} object to the - L{X2goControlSessionSTDOUT} constructor - @type logger: L{X2goLogger} instance - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be - constructed with the given loglevel - @type loglevel: int - - """ - associated_terminals = None - - def __init__(self, - profile_name='UNKNOWN', - add_to_known_hosts=False, - known_hosts=None, - terminal_backend=_X2goTerminalSession, - info_backend=_X2goServerSessionInfo, - list_backend=_X2goServerSessionList, - proxy_backend=_X2goProxy, - client_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_CLIENT_ROOTDIR), - sessions_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SESSIONS_ROOTDIR), - ssh_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SSH_ROOTDIR), - logger=None, loglevel=log.loglevel_DEFAULT, - *args, **kwargs): - """\ - Initialize an X2go session. With the L{X2goControlSessionSTDOUT} class you can start - new X2go sessions, resume suspended sessions or suspend resp. terminate - currently running sessions on a connected X2go server. - - """ - self.associated_terminals = {} - self.terminated_terminals = [] - - self.profile_name = profile_name - self.add_to_known_hosts = add_to_known_hosts - self.known_hosts = known_hosts - - self.hostname = None - self.port = None - - self.sshproxy_session = None - - self._session_auth_rsakey = None - self._remote_home = None - self._remote_group = {} - - self.locked = False - - if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) - else: - self.logger = copy.deepcopy(logger) - self.logger.tag = __NAME__ - - self._terminal_backend = terminal_backend - self._info_backend = info_backend - self._list_backend = list_backend - self._proxy_backend = proxy_backend - - self.client_rootdir = client_rootdir - self.sessions_rootdir = sessions_rootdir - self.ssh_rootdir = ssh_rootdir - - paramiko.SSHClient.__init__(self, *args, **kwargs) - if self.add_to_known_hosts: - self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - self.session_died = False - - def load_session_host_keys(self): - if self.known_hosts is not None: - utils.touch_file(self.known_hosts) - self.load_host_keys(self.known_hosts) - - def __del__(self): - - self.disconnect() - - def _x2go_sftp_put(self, local_path, remote_path): - - self.logger('sFTP-put: %s -> %s:%s' % (os.path.normpath(local_path), self.get_transport().getpeername(), remote_path), loglevel=log.loglevel_DEBUG) - self.sftp_client.put(os.path.normpath(local_path), remote_path) - - def _x2go_sftp_write(self, remote_path, content): - - self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG) - try: - remote_fileobj = self.sftp_client.open(remote_path, 'w') - self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) - remote_fileobj.write(content) - remote_fileobj.close() - except SSHException: - self.logger('sFTP-write: opening remote file %s on host %s failed' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_WARN) - - def _x2go_sftp_remove(self, remote_path): - - self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG) - self.sftp_client.remove(remote_path) - - def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, **kwargs): - - while self.locked: - gevent.sleep(.1) - - self.locked = True - _retval = None - - if type(cmd_line) == types.ListType: - cmd = " ".join(cmd_line) - else: - cmd = cmd_line - if self.get_transport() is not None: - - timeout = gevent.Timeout(20) - timeout.start() - try: - self.logger("executing command on X2go server ,,%s'': %s" % (self.profile_name, _rerewrite_blanks(cmd)), loglevel) - _retval = self.exec_command(_rewrite_password(cmd, user=self.get_transport().get_username(), password=self._session_password), **kwargs) - except AttributeError: - self.session_died = True - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.locked = False - raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') - except EOFError: - self.session_died = True - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.locked = False - raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') - except x2go_exceptions.SSHException: - self.session_died = True - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.locked = False - raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') - except gevent.timeout.Timeout: - self.session_died = True - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.locked = False - raise x2go_exceptions.X2goControlSessionException('the X2go control session command timed out') - except socket.error: - self.session_died = True - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.locked = False - raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') - finally: - self.locked = False - timeout.cancel() - - else: - self.locked = False - raise x2go_exceptions.X2goControlSessionException('the X2go control session is not connected') - return _retval - - @property - def _x2go_remote_home(self): - - if self._remote_home is None: - (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') - self._remote_home = stdout.read().split()[0] - self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) - return self._remote_home - else: - return self._remote_home - - def _x2go_remote_group(self, group): - - if not self._remote_group.has_key(group): - (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) - self._remote_group[group] = stdout.read().split('\n')[0].split(',') - self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) - return self._remote_group[group] - else: - return self._remote_group[group] - - def is_x2gouser(self, username): - ### - ### FIXME: - ### - # discussion about server-side access restriction based on posix group membership or similar currently - # in process (as of 20110517, mg) - #return username in self._x2go_remote_group('x2gousers') - return True - - def is_folder_sharing_available(self): - if self.remote_username() in self._x2go_remote_group('fuse'): - return True - return False - - def remote_username(self): - """\ - Returns the control session's remote username. - - """ - if self.get_transport() is not None: - return self.get_transport().get_username() - else: - return None - - @property - def _x2go_session_auth_rsakey(self): - if self._session_auth_rsakey is None: - self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) - return self._session_auth_rsakey - - def set_profile_name(self, profile_name): - self.profile_name = profile_name - - def check_host(self, hostname, port=22): - """\ - Wraps around a Paramiko/SSH host key check. - - """ - # trailing whitespace tolerance - hostname = hostname.strip() - - # force into IPv4 for localhost connections - if hostname in ('localhost', 'localhost.localdomain'): - hostname = '127.0.0.1' - - return checkhosts.check_ssh_host_key(self, hostname, port=port) - - def connect(self, hostname, port=22, username='', password='', pkey=None, - use_sshproxy=False, sshproxy_host='', sshproxy_user='', sshproxy_password='', - sshproxy_key_filename='', sshproxy_tunnel='', - key_filename=None, timeout=None, allow_agent=False, look_for_keys=False, - session_instance=None, - add_to_known_hosts=False, force_password_auth=False): - """\ - Connect to an X2go server and authenticate to it. This method is directly - inherited from the paramiko.SSHClient module. The features of the Paramiko - SSH client connect method are recited here. The parameters C{add_to_known_hosts} - and C{force_password_auth} have been added as a parameter for X2go. - - The server's host key - is checked against the system host keys (see C{load_system_host_keys}) - and any local host keys (C{load_host_keys}). If the server's hostname - is not found in either set of host keys, the missing host key policy - is used (see C{set_missing_host_key_policy}). The default policy is - to reject the key and raise an C{SSHException}. - - Authentication is attempted in the following order of priority: - - - The C{pkey} or C{key_filename} passed in (if any) - - Any key we can find through an SSH agent - - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} - - Plain username/password auth, if a password was given - - If a private key requires a password to unlock it, and a password is - passed in, that password will be used to attempt to unlock the key. - - @param hostname: the server to connect to - @type hostname: str - @param port: the server port to connect to - @type port: int - @param username: the username to authenticate as (defaults to the - current local username) - @type username: str - @param password: a password to use for authentication or for unlocking - a private key - @type password: str - @param pkey: an optional private key to use for authentication - @type pkey: C{PKey} - @param key_filename: the filename, or list of filenames, of optional - private key(s) to try for authentication - @type key_filename: str or list(str) - @param timeout: an optional timeout (in seconds) for the TCP connect - @type timeout: float - @param allow_agent: set to False to disable connecting to the SSH agent - @type allow_agent: C{bool} - @param look_for_keys: set to False to disable searching for discoverable - private key files in C{~/.ssh/} - @type look_for_keys: C{bool} - @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() - is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() - is used - @type add_to_known_hosts: C{bool} - @param force_password_auth: non-paramiko option, disable pub/priv key authentication - completely, even if the C{pkey} or the C{key_filename} parameter is given - @type force_password_auth: C{bool} - @param session_instance: an instance L{X2goSession} using this L{X2goControlSessionSTDOUT} - instance. - @type session_instance: C{instance} - - @raise BadHostKeyException: if the server's host key could not be - verified - @raise AuthenticationException: if authentication failed - @raise SSHException: if there was any other error connecting or - establishing an SSH session - @raise socket.error: if a socket error occurred while connecting - - """ - if use_sshproxy and sshproxy_host and sshproxy_user: - try: - self.sshproxy_session = sshproxy.X2goSSHProxy(known_hosts=self.known_hosts, - sshproxy_host=sshproxy_host, - sshproxy_user=sshproxy_user, - sshproxy_password=sshproxy_password, - sshproxy_key_filename=sshproxy_key_filename, - sshproxy_tunnel=sshproxy_tunnel, - session_instance=session_instance, - logger=self.logger, - ) - - except: - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - self.sshproxy_session = None - raise - - if self.sshproxy_session is not None: - self.sshproxy_session.start() - - # divert port to sshproxy_session's local forwarding port (it might have changed due to - # SSH connection errors - gevent.sleep(.1) - port = self.sshproxy_session.get_local_proxy_port() - - if not add_to_known_hosts and session_instance: - self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance)) - - if add_to_known_hosts: - self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - # disable pub/priv key authentication if forced - if force_password_auth: - key_filename = None - pkey = None - - # trailing whitespace tolerance in hostname - hostname = hostname.strip() - - self.logger('connecting to [%s]:%s' % (hostname, port), loglevel=log.loglevel_NOTICE) - - self.load_session_host_keys() - - _hostname = hostname - # enforce IPv4 for localhost address - if _hostname in ('localhost', 'localhost.localdomain'): - _hostname = '127.0.0.1' - - if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: - try: - self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) - paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=pkey, - key_filename=key_filename, timeout=timeout, allow_agent=allow_agent, - look_for_keys=look_for_keys) - - except paramiko.AuthenticationException, e: - self.close() - if password: - self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', loglevel=log.loglevel_DEBUG) - try: - paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, - timeout=timeout, allow_agent=allow_agent, - look_for_keys=look_for_keys) - except paramiko.AuthenticationException, e: - self.close() - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - raise e - except: - self.close() - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - raise - else: - self.close() - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - raise(e) - - except: - self.close() - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - raise - - # if there is not private key, we will use the given password, if any - else: - # create a random password if password is empty to trigger host key validity check - if not password: - password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) - self.logger('performing SSH keyboard-interactive authentication with server', loglevel=log.loglevel_DEBUG) - try: - paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, - timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) - except paramiko.AuthenticationException, e: - self.close() - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - raise e - except: - self.close() - if self.sshproxy_session: - self.sshproxy_session.stop_thread() - raise - - self.set_missing_host_key_policy(paramiko.RejectPolicy()) - - self.hostname = hostname - self.port = port - - # if we succeed, we immediately grab us an sFTP client session - try: - self.sftp_client = self.open_sftp() - except: - raise x2go_exceptions.X2goControlSessionException('could not invoke server-side SFTP subsystem') - - # preparing reverse tunnels - ssh_transport = self.get_transport() - ssh_transport.reverse_tunnels = {} - - # mark Paramiko/SSH transport as X2goControlSession - ssh_transport._x2go_session_marker = True - self._session_password = password - - if self.get_transport(): - self.session_died = False - - if not self.home_exists(): - raise x2go_exceptions.X2goRemoteHomeException('remote home directory does not exist') - - return (self.get_transport() is not None) - - def dissociate(self, terminal_session): - """\ - STILL UNDOCUMENTED - - """ - for t_name in self.associated_terminals.keys(): - if self.associated_terminals[t_name] == terminal_session: - del self.associated_terminals[t_name] - if self.terminated_terminals.has_key(t_name): - del self.terminated_terminals[t_name] - - def disconnect(self): - """\ - STILL UNDOCUMENTED - - """ - if self.associated_terminals: - t_names = self.associated_terminals.keys() - for t_obj in self.associated_terminals.values(): - try: - if not self.session_died: - t_obj.suspend() - except x2go_exceptions.X2goTerminalSessionException: - pass - except x2go_exceptions.X2goControlSessionException: - pass - t_obj.__del__() - for t_name in t_names: - try: - del self.associated_terminals[t_name] - except KeyError: - pass - - self._remote_home = None - self._remote_group = {} - - self._session_auth_rsakey = None - - try: - if self.get_transport() is not None: - still_active = self.get_transport().is_active() - self.close() - if self.sshproxy_session is not None: - self.sshproxy_session.stop_thread() - return still_active - return False - except AttributeError: - # if the Paramiko _transport object has not yet been initialized, ignore it - # but state that this method call did not close the SSH client, but was already closed - return False - - - def home_exists(self): - (_stdin, _stdout, _stderr) = self._x2go_exec_command('stat -tL "%s"' % self._x2go_remote_home, loglevel=log.loglevel_DEBUG) - if _stdout.read(): - return True - return False - - - def is_alive(self): - if self._x2go_exec_command('echo', loglevel=log.loglevel_DEBUG): - return True - return False - - def start(self, **kwargs): - """\ - Start a new X2go session. - - The L{X2goControlSessionSTDOUT.start()} method accepts any parameter - that can be passed to any of the C{X2goTerminalSession} backend class - constructors. - - """ - return self.resume(**kwargs) - - def resume(self, session_name=None, session_instance=None, **kwargs): - """\ - Resume a running/suspended X2go session. - - The L{X2goControlSessionSTDOUT.resume()} method accepts any parameter - that can be passed to any of the C{X2goTerminalSession} backend class constructors. - - @return: True if the session could be successfully resumed - @rtype: C{bool} - - """ - if not self.is_x2gouser(self.get_transport().get_username()): - raise x2go_exceptions.X2goUserException('remote user %s is not allowed to run X2go commands' % self.get_transport().get_username()) - - if session_name is not None: - session_info = self.list_sessions()[session_name] - else: - session_info = None - - _terminal = self._terminal_backend(self, - profile_name=self.profile_name, - session_info=session_info, - info_backend=self._info_backend, - list_backend=self._list_backend, - proxy_backend=self._proxy_backend, - client_rootdir=self.client_rootdir, - session_instance=session_instance, - sessions_rootdir=self.sessions_rootdir, - **kwargs) - - _success = False - if session_name is not None: - try: - _success = _terminal.resume() - except x2go_exceptions.X2goFwTunnelException: - pass - - else: - try: - _success = _terminal.start() - except x2go_exceptions.X2goFwTunnelException: - pass - - if _success: - while not _terminal.ok(): - gevent.sleep(.2) - - if _terminal.ok(): - self.associated_terminals[_terminal.get_session_name()] = _terminal - self.get_transport().reverse_tunnels[_terminal.get_session_name()] = { - 'sshfs': (0, None), - 'snd': (0, None), - } - - return _terminal or None - - return None - - def share_desktop(self, desktop=None, user=None, display=None, share_mode=0, **kwargs): - """\ - Share another already running desktop session. Desktop sharing can be run - in two different modes: view-only and full-access mode. - - @param desktop: desktop ID of a sharable desktop in format @ - @type desktop: C{str} - @param user: user name and display number can be given separately, here give the - name of the user who wants to share a session with you. - @type user: C{str} - @param display: user name and display number can be given separately, here give the - number of the display that a user allows you to be shared with. - @type display: C{str} - @param share_mode: desktop sharing mode, 0 is VIEW-ONLY, 1 is FULL-ACCESS. - @type share_mode: C{int} - - @return: True if the session could be successfully shared. - @rtype: C{bool} - - """ - if desktop: - user = desktop.split('@')[0] - display = desktop.split('@')[1] - if not (user and display): - raise x2go_exceptions.X2goDesktopSharingException('Need user name and display number of sharable desktop.') - - cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display) - - kwargs['cmd'] = cmd - kwargs['session_type'] = 'shared' - - return self.start(**kwargs) - - def list_desktops(self, raw=False, maxwait=20): - """\ - List all desktop-like sessions of current user (or of users that have - granted desktop sharing) on the connected server. - - @param raw: if C{True}, the raw output of the server-side X2go command - C{x2godesktopsharing} is returned. - @type raw: C{bool} - - @return: a list of X2go desktops available for sharing - @rtype: C{list} - - """ - if raw: - (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") - return stdout.read(), stderr.read() - - else: - - # this _success loop will catch errors in case the x2golistsessions output is corrupt - # this should not be needed and is a workaround for the current X2go server implementation - - timeout = gevent.Timeout(maxwait) - timeout.start() - try: - (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") - _stdout_read = stdout.read() - _listdesktops = _stdout_read.split('\n') - except gevent.timeout.Timeout: - # if we do not get a reply here after seconds we will raise a time out, we have to - # make sure that we catch this at places where we want to ignore timeouts (e.g. in the - # desktop list cache) - raise x2go_exceptions.X2goTimeOutException('x2golistdesktop command timed out') - finally: - timeout.cancel() - - return _listdesktops - - def list_sessions(self, raw=False): - """\ - List all sessions of current user on the connected server. - - @param raw: if C{True}, the raw output of the server-side X2go command - C{x2golistsessions} is returned. - @type raw: C{bool} - - @return: normally an instance of a C{X2goServerSessionList} backend Bis returned. However, - if the raw argument is set, the plain text output of the x2golistsessions - command is returned - @rtype: C{X2goServerSessionList} instance or str - - """ - if raw: - (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") - return stdout.read(), stderr.read() - - else: - - # this _success loop will catch errors in case the x2golistsessions output is corrupt - # this should not be needed and is a workaround for the current X2go server implementation - _listsessions = {} - _success = False - _count = 0 - _maxwait = 20 - - # we will try this 20 times before giving up... we might simply catch the x2golistsessions - # output in the middle of creating a session in the database... - while not _success and _count < _maxwait: - _count += 1 - try: - (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") - _stdout_read = stdout.read() - _listsessions = self._list_backend(_stdout_read, info_backend=self._info_backend).sessions - _success = True - except KeyError: - gevent.sleep(1) - except IndexError: - gevent.sleep(1) - except ValueError: - gevent.sleep(1) - - if _count >= _maxwait: - raise x2go_exceptions.X2goControlSessionException('x2golistsessions command failed after we have tried 20 times') - - # update internal variables when list_sessions() is called - for _session_name, _session_info in self.associated_terminals.items(): - if _session_name not in _listsessions.keys(): - del self.associated_terminals[_session_name] - self.terminated_terminals.append(_session_name) - elif _session_info.is_suspended(): - del self.associated_terminals[_session_name] - - return _listsessions - - def clean_sessions(self, destroy_terminals=True): - """\ - Find X2go terminals that have previously been started by the - connected user on the remote X2go server and terminate them. - - """ - session_list = self.list_sessions() - for session_name in session_list.keys(): - self.terminate(session_name=session_name, destroy_terminals=destroy_terminals) - - def is_connected(self): - """\ - Returns C{True} if this X2go session is connected to the remote server (that - is if it has a valid Paramiko Transport object). - - @return: X2go session connected? - @rtype: C{bool} - - """ - return self.get_transport() is not None and self.get_transport().is_authenticated() - - def is_running(self, session_name): - """\ - Returns C{True} if the given X2go session is in running state, - C{False} else. - - @param session_name: X2go name of the session to be queried - @type session_name: str - - @return: X2go session running? - @rtype: C{bool} - - """ - session_infos = self.list_sessions() - if session_name in session_infos.keys(): - return session_infos[session_name].is_running() - return None - - def is_suspended(self, session_name): - """\ - Returns C{True} if the given X2go session is in suspended state, - C{False} else. - - @return: X2go session suspended? - @rtype: C{bool} - - """ - session_infos = self.list_sessions() - if session_name in session_infos.keys(): - return session_infos[session_name].is_suspended() - return None - - def has_terminated(self, session_name): - """\ - Returns C{True} if this X2go session is not in the session list on the - connected server, C{False} else. - - Of course, if this command is called before session startup, it will also - return C{True}. - - @return: X2go session has terminate? - @rtype: C{bool} - - """ - session_infos = self.list_sessions() - if session_name not in session_infos.keys(): - if session_name in self.terminated_terminals: - return True - else: - # do a post-mortem tidy up - if session_name in self.associated_terminals.keys(): - self.terminate(session_name) - return True - return False - - def suspend(self, session_name): - """\ - Suspend either this or another available X2go session on the connected - server. - - If L{session_name} is given, L{X2goControlSessionSTDOUT.suspend()} tries to suspend the - corresponding session. - - @param session_name: X2go name of the session to be suspended - @type session_name: str - - @return: True if the session could be successfully suspended - @rtype: C{bool} - - """ - _ret = False - _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] - if session_name in _session_names: - - self.logger('suspending associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) - (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - if self.associated_terminals.has_key(session_name): - if self.associated_terminals[session_name] is not None: - self.associated_terminals[session_name].__del__() - try: del self.associated_terminals[session_name] - except KeyError: pass - _ret = True - - else: - - self.logger('suspending non-associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) - (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - _ret = True - - return _ret - - def terminate(self, session_name, destroy_terminals=True): - """\ - Terminate either this or another available X2go session on the connected - server. - - If L{session_name} is given, L{X2goControlSessionSTDOUT.terminate()} tries to terminate the - corresponding session. - - @param session_name: X2go name of the session to be terminated - @type session_name: str - - @return: True if the session could be successfully terminate - @rtype: C{bool} - - """ - - _ret = False - _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] - if session_name in _session_names: - - self.logger('terminating associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) - (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - if self.associated_terminals.has_key(session_name): - if self.associated_terminals[session_name] is not None and destroy_terminals: - self.associated_terminals[session_name].__del__() - try: del self.associated_terminals[session_name] - except KeyError: pass - self.terminated_terminals.append(session_name) - _ret = True - - else: - - self.logger('terminating non-associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) - (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - _ret = True - - return _ret - - diff -Nru python-x2go-0.1.1.8/x2go/backends/info/__init__.py python-x2go-0.5.0.6/x2go/backends/info/__init__.py --- python-x2go-0.1.1.8/x2go/backends/info/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/info/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,27 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -from x2go.defaults import BACKEND_SERVERSESSIONINFO_DEFAULT -from x2go.defaults import BACKEND_SERVERSESSIONLIST_DEFAULT - -from _stdout import X2goServerSessionInfoSTDOUT -from _stdout import X2goServerSessionListSTDOUT - -X2goServerSessionInfo = eval(BACKEND_SERVERSESSIONINFO_DEFAULT) -X2goServerSessionList = eval(BACKEND_SERVERSESSIONLIST_DEFAULT) diff -Nru python-x2go-0.1.1.8/x2go/backends/info/plain.py python-x2go-0.5.0.6/x2go/backends/info/plain.py --- python-x2go-0.1.1.8/x2go/backends/info/plain.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/info/plain.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,380 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2GoServerSessionList and X2GoServerSessionInfo classes - data handling for +X2Go server sessions. + +This backend handles X2Go server implementations that respond with session infos +via server-side PLAIN text output. + +""" +__NAME__ = 'x2goserversessioninfo-pylib' + + +# modules +import types +import re + +class X2GoServerSessionInfo(object): + """\ + L{X2GoServerSessionInfo} is used to store all information + that is retrieved from the connected X2Go server on + C{X2GoTerminalSession.start()} resp. C{X2GoTerminalSession.resume()}. + + """ + def __str__(self): + return self.name + def __repr__(self): + result = 'X2GoServerSessionInfo(' + for p in dir(self): + if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue + result += p + '=' + str(self.__dict__[p]) +',' + return result.strip(',') + ')' + + def _parse_x2golistsessions_line(self, x2go_output): + """\ + Parse a single line of X2Go's listsessions output. + + @param x2go_output: output from ,,x2golistsessions'' command (as list of strings/lines) + @type x2go_output: C{list} + + """ + try: + l = x2go_output.split("|") + self.agent_pid = int(l[0]) + self.name = l[1] + self.display = int(l[2]) + self.hostname = l[3] + self.status = l[4] + # TODO: turn into datetime object + self.date_created = l[5] + self.cookie = l[6] + self.graphics_port = int(l[8]) + self.snd_port = int(l[9]) + # TODO: turn into datetime object + self.date_suspended = l[10] + self.username = l[11] + self.sshfs_port = int(l[13]) + self.local_container = '' + except IndexError, e: + # DEBUGGING CODE + raise e + except ValueError, e: + # DEBUGGING CODE + raise e + + # retrieve Telekinesis ports from list of sessions... + try: + self.tekictrl_port = int(l[14]) + except (IndexError, ValueError), e: + self.tekictrl_port = -1 + try: + self.tekidata_port = int(l[15]) + except (IndexError, ValueError), e: + self.tekidata_port = -1 + + def is_published_applications_provider(self): + """\ + Detect from session info if this session is a published applications provider. + + @return: returns C{True} if this session is a published applications provider + @rtype: C{bool} + + """ + return bool(re.match('.*_stRPUBLISHED_.*', self.name)) + + def is_running(self): + """\ + Is this session running? + + @return: C{True} if the session is running, C{False} otherwise + @rtype: C{bool} + + """ + return self.status == 'R' + + def get_session_type(self): + """\ + Get the session type (i.e. 'D', 'R', 'S' or 'P'). + + @return: session type + @rtype: C{str} + """ + cmd = self.name.split('_')[1] + session_type = cmd[2] + if session_type == 'R' and self.is_published_applications_provider(): + session_type = 'P' + return session_type + + def get_share_mode(self): + """\ + Get the share mode of a shadow session. + + @return: share mode (0: view-only, 1: full access), C{None} when used for non-desktop-sharing sessions + @rtype: C{str} + + """ + share_mode = None + cmd = self.name.split('_')[1] + session_type = cmd[2] + if session_type == 'S': + share_mode = cmd[3] + return share_mode + + def is_suspended(self): + """\ + Is this session suspended? + + @return: C{True} if the session is suspended, C{False} otherwise + @rtype: C{bool} + + """ + return self.status == 'S' + + def is_desktop_session(self): + """\ + Is this session a desktop session? + + @return: C{True} if this session is a desktop session, C{False} otherwise + @rtype: C{bool} + + """ + return self.get_session_type() == 'D' + + def _parse_x2gostartagent_output(self, x2go_output): + """\ + Parse x2gostartagent output. + + @param x2go_output: output from ,,x2gostartagent'' command (as list of strings/lines) + @type x2go_output: C{list} + + """ + try: + l = x2go_output.split("\n") + self.name = l[3] + self.cookie = l[1] + self.agent_pid = int(l[2]) + self.display = int(l[0]) + self.graphics_port = int(l[4]) + self.snd_port = int(l[5]) + self.sshfs_port = int(l[6]) + self.username = '' + self.hostname = '' + # TODO: we have to see how we fill these fields here... + self.date_created = '' + self.date_suspended = '' + # TODO: presume session is running after x2gostartagent, this could be better + self.status = 'R' + self.local_container = '' + self.remote_container = '' + except IndexError, e: + # DEBUGGING CODE + raise e + except ValueError, e: + # DEBUGGING CODE + raise e + + # retrieve Telekinesis ports from x2gostartagent output + try: + self.tekictrl_port = int(l[7]) + except (IndexError, ValueError), e: + self.tekictrl_port = -1 + try: + self.tekidata_port = int(l[8]) + except (IndexError, ValueError), e: + self.tekidata_port = -1 + + def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''): + """\ + Setup a a session info data block, includes parsing of X2Go server's C{x2gostartagent} stdout values. + + @param x2go_output: X2Go server's C{x2gostartagent} command output, each value + separated by a newline character. + @type x2go_output: str + @param username: session user name + @type username: str + @param hostname: hostname of X2Go server + @type hostname: str + @param local_container: X2Go client session directory for config files, cache and session logs + @type local_container: str + @param remote_container: X2Go server session directory for config files, cache and session logs + @type remote_container: str + + """ + self.protect() + self._parse_x2gostartagent_output(x2go_output) + self.username = username + self.hostname = hostname + self.local_container = local_container + self.remote_container = remote_container + + def protect(self): + """\ + Write-protect this session info data structure. + + """ + self.protected = True + + def unprotect(self): + """\ + Remove write-protection from this session info data structure. + + """ + self.protected = False + + def is_protected(self): + """\ + + """ + return self.protected + + def get_status(self): + """\ + Retrieve the session's status from this session info data structure. + + @return: session status + @rtype: C{str} + + """ + return self.status + + def clear(self): + """\ + Clear all properties of a L{X2GoServerSessionInfo} object. + + """ + self.name = '' + self.cookie = '' + self.agent_pid = '' + self.display = '' + self.graphics_port = '' + self.snd_port = '' + self.sshfs_port = '' + self.tekictrl_port = '' + self.tekidata_port = '' + self.username = '' + self.hostname = '' + self.date_created = '' + self.date_suspended = '' + self.status = '' + self.local_container = '' + self.remote_container = '' + self.protected = False + + def update(self, session_info): + """\ + Update all properties of a L{X2GoServerSessionInfo} object. + + @param session_info: a provided session info data structure + @type session_info: C{X2GoServerSessionInfo*} + + """ + if type(session_info) == type(self): + for prop in ('graphics_port', 'snd_port', 'sshfs_port', 'tekictrl_port', 'tekidata_port', 'date_suspended', 'status', ): + if hasattr(session_info, prop): + _new = getattr(session_info, prop) + _current = getattr(self, prop) + if _new != _current: + setattr(self, prop, _new) + + def __init__(self): + """\ + Class constructor, identical to L{clear()} method. + + """ + self.clear() + + +class X2GoServerSessionList(object): + """\ + L{X2GoServerSessionList} is used to store all information + that is retrieved from a connected X2Go server on a + C{X2GoControlSession.list_sessions()} call. + + """ + def __init__(self, x2go_output=None, info_backend=X2GoServerSessionInfo): + """\ + @param x2go_output: X2Go server's C{x2golistsessions} command output, each + session separated by a newline character. Session values are separated + by Unix Pipe Symbols ('|') + @type x2go_output: str + @param info_backend: the session info backend to use + @type info_backend: C{X2GoServerSessionInfo*} + + """ + self.sessions = {} + if x2go_output is not None: + lines = x2go_output.split("\n") + for line in lines: + if not line: + continue + s_info = info_backend() + s_info._parse_x2golistsessions_line(line) + self.sessions[s_info.name] = s_info + + def __call__(self): + return self.sessions + + def set_sessions(self, sessions): + """\ + Set the sessions property directly by parsing a complete data structure. + + """ + self.sessions = sessions + + def get_session_info(self, session_name): + """\ + Retrieve the session information for C{}. + + @param session_name: the queried session name + @type session_name: C{str} + + @return: the session info of C{} + @rtype: C{X2GoServerSessionInfo*} or C{None} + + """ + try: + return self.sessions[session_name] + except KeyError: + return None + + def get_session_with(self, property_name, value, hostname=None): + """\ + Find session with a given display number on a given host. + + @param property_name: match a session based on this property name + @type property_name: C{str} + @param value: the resulting session has to match this value for C{} + @type value: C{str} + @param hostname: the result has to match this hostname + @type hostname: C{str} + + """ + if property_name == 'display': + value = value.lstrip(':') + if '.' in value: value = value.split('.')[0] + + for session in self.sessions.values(): + try: + if str(getattr(session, property_name)) == str(value): + if hostname is None or session.hostname == hostname: + return session + except AttributeError: + pass diff -Nru python-x2go-0.1.1.8/x2go/backends/info/_stdout.py python-x2go-0.5.0.6/x2go/backends/info/_stdout.py --- python-x2go-0.1.1.8/x2go/backends/info/_stdout.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/info/_stdout.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,212 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goServerSessionList and X2goServerSessionInfo classes - data handling for -X2go server sessions. - -This backend handles X2go server implementations that respond with session infos -via server-side STDOUT. - -""" -__NAME__ = 'x2goserversessioninfo-pylib' - - -# modules -import types - - -class X2goServerSessionInfoSTDOUT(object): - """\ - L{X2goServerSessionInfo} is used to store all information - that is retrieved from the connected X2go server on - C{X2goTerminalSessionBACKEND.start()} resp. C{X2goTerminalSessionBACKEND.resume()}. - - """ - def __str__(self): - return self.name - def __repr__(self): - result = 'X2goServerSessionInfoSTDOUT(' - for p in dir(self): - if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue - result += p + '=' + str(self.__dict__[p]) +',' - return result.strip(',') + ')' - - def _parse_x2golistsessions_line(self, x2go_output): - """\ - Parse a single line of X2go's listsessions output. - - """ - try: - l = x2go_output.split("|") - self.name = l[1] - self.cookie = l[6] - self.agent_pid = int(l[0]) - self.display = int(l[2]) - self.status = l[4] - self.graphics_port = int(l[8]) - self.snd_port = int(l[9]) - self.sshfs_port = int(l[13]) - self.username = l[11] - self.hostname = l[3] - # TODO: turn into datetime object - self.date_created = l[5] - # TODO: turn into datetime object - self.date_suspended = l[10] - self.local_container = '' - except IndexError, e: - # DEBUGGING CODE - print 'Encountered IndexError: %s' % str(e) - print 'THIS SHOULD NOT HAPPEN... HERE IS THE x2golistsessions OUTPUT THAT CAUSED THE ERROR...' - print x2go_output - raise e - except ValueError, e: - # DEBUGGING CODE - print 'Encountered IndexError: %s' % str(e) - print 'THIS SHOULD NOT HAPPEN... HERE IS THE x2golistsessions OUTPUT THAT CAUSED THE ERROR...' - print x2go_output - raise e - - def is_running(self): - - return self.status == 'R' - - def is_suspended(self): - - return self.status == 'S' - - def _parse_x2gostartagent_output(self, x2go_output): - """\ - Parse x2gostartagent output. - - """ - try: - l = x2go_output.split("\n") - self.name = l[3] - self.cookie = l[1] - self.agent_pid = int(l[2]) - self.display = int(l[0]) - self.graphics_port = int(l[4]) - self.snd_port = int(l[5]) - self.sshfs_port = int(l[6]) - self.username = '' - self.hostname = '' - # TODO: we have to see how we fill these fields here... - self.date_created = '' - self.date_suspended = '' - # TODO: presume session is running after x2gostartagent, this could be better - self.status = 'R' - self.local_container = '' - self.remote_container = '' - except IndexError, e: - # DEBUGGING CODE - print 'Encountered IndexError: %s' % str(e) - print 'THIS SHOULD NOT HAPPEN... HERE IS THE x2golistsessions OUTPUT THAT CAUSED THE ERROR...' - print x2go_output - raise e - except ValueError, e: - # DEBUGGING CODE - print 'Encountered IndexError: %s' % str(e) - print 'THIS SHOULD NOT HAPPEN... HERE IS THE x2golistsessions OUTPUT THAT CAUSED THE ERROR...' - print x2go_output - raise e - - - def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''): - """\ - Parse X2go server's C{x2gostartagent} stdout values. - - @param x2go_output: X2go server's C{x2gostartagent} command output, each value - separated by a newline character. - @type x2go_output: str - @param username: session user name - @type username: str - @param hostname: hostname of X2go server - @type hostname: str - @param local_container: X2go client session directory for config files, cache and session logs - @type local_container: str - @param remote_container: X2go server session directory for config files, cache and session logs - @type remote_container: str - - """ - self._parse_x2gostartagent_output(x2go_output) - self.username = username - self.hostname = hostname - self.local_container = local_container - self.remote_container = remote_container - - def clear(self): - """\ - Clear all properties of a L{X2goServerSessionInfo} object. - - """ - self.name = '' - self.cookie = '' - self.agent_pid = '' - self.display = '' - self.graphics_port = '' - self.snd_port = '' - self.sshfs_port = '' - self.username = '' - self.hostname = '' - self.date_created = '' - self.date_suspended = '' - self.status = '' - self.local_container = '' - self.remote_container = '' - - __init__ = clear - - -class X2goServerSessionListSTDOUT(object): - """\ - L{X2goServerSessionListSTDOUT} is used to store all information - that is retrieved from a connected X2go server on a - C{X2goControlSessionBACKEND.list_sessions()} call. - - """ - def __init__(self, x2go_output, info_backend=X2goServerSessionInfoSTDOUT): - """\ - @param x2go_output: X2go server's C{x2golistsessions} command output, each - session separated by a newline character. Session values are separated - by Unix Pipe Symbols ('|') - @type x2go_output: str - - """ - self.sessions = {} - lines = x2go_output.split("\n") - for line in lines: - if not line: - continue - s_info = info_backend() - s_info._parse_x2golistsessions_line(line) - self.sessions[s_info.name] = s_info - - def __call__(self): - return self.sessions - - def get_session_info(self, session_name): - """\ - STILL UNDOCUMENTED - - """ - try: - return self.sessions[session_name] - except KeyError: - return None diff -Nru python-x2go-0.1.1.8/x2go/backends/__init__.py python-x2go-0.5.0.6/x2go/backends/__init__.py --- python-x2go-0.1.1.8/x2go/backends/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/_file.py python-x2go-0.5.0.6/x2go/backends/printing/_file.py --- python-x2go-0.1.1.8/x2go/backends/printing/_file.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/_file.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,214 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goClientPrintingFILE} class is one of Python X2go's public API classes. - -Retrieve an instance of this class from your L{X2goClient} instance. -Use this class in your Python X2go based applications to access the »printing« -configuration of your X2go client application. - -""" -__NAME__ = 'x2goprinting-pylib' - -# modules -import types -import ConfigParser - -# Python X2go modules -import x2go.log as log -import x2go.printactions as printactions -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS -from x2go.defaults import X2GO_PRINTING_CONFIGFILES as _X2GO_PRINTING_CONFIGFILES -import x2go.inifiles as inifiles -import x2go.x2go_exceptions as x2go_exceptions - -_print_property_map = { - 'pdfview_cmd': { - 'ini_section': 'view', - 'ini_option': 'command', - }, - 'save_to_folder': { - 'ini_section': 'save', - 'ini_option': 'folder', - }, - 'printer': { - 'ini_section': 'CUPS', - 'ini_option': 'defaultprinter', - }, - 'print_cmd': { - 'ini_section': 'print', - 'ini_option': 'command', - }, -} - -class X2goClientPrintingFILE(inifiles.X2goIniFile): - """\ - L{X2goClientPrinting} provides access to the X2go ini-like file - »printing« as stored in C{~/.x2goclient/printing} resp. globally - C{/etc/x2goclient/printing}. - - An instance of L{X2goClientPrinting} is created on each incoming - print job. This facilitates that on every print job the print action - for this job is derived from the »printing« configuration file. - - Thus, changes on the file are active for the next incoming print job. - - """ - config_files = [] - _print_action = None - defaultValues = _X2GO_CLIENTPRINTING_DEFAULTS - - def __init__(self, config_files=_X2GO_PRINTING_CONFIGFILES, defaults=None, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - @param config_files: a list of configuration files names (e.g. a global filename and a user's home - directory filename) - @type config_files: C{list} - @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override - Python X2go's hard coded defaults in L{defaults} - @type defaults: C{dict} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be - constructed with the given loglevel - @type loglevel: C{int} - - """ - self.client_instance = client_instance - inifiles.X2goIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) - - self._detect_print_action() - - def _detect_print_action(self): - """\ - Derive a print action from sections, keys and their values in a typical - X2go client »printing« configuration file. - - """ - _general_pdfview = self.get('General', 'pdfview', key_type=types.BooleanType) - _view_open = self.get('view', 'open', key_type=types.BooleanType) - _print_startcmd = self.get('print', 'startcmd', key_type=types.BooleanType) - _show_dialog = self.get('General', 'showdialog', key_type=types.BooleanType) - - if _show_dialog and self.client_instance is not None: - self._print_action = printactions.X2goPrintActionDIALOG(client_instance=self.client_instance, logger=self.logger) - - elif _general_pdfview and _view_open: - _view_command = self.get('view', 'command') - self._print_action = printactions.X2goPrintActionPDFVIEW(client_instance=self.client_instance, pdfview_cmd=_view_command, logger=self.logger) - - elif _general_pdfview and not _view_open: - _safe_folder = self.get('save', 'folder') - self._print_action = printactions.X2goPrintActionPDFSAVE(client_instance=self.client_instance, save_to_folder=_safe_folder, logger=self.logger) - - elif not _general_pdfview and not _print_startcmd: - _cups_defaultprinter = self.get('CUPS', 'defaultprinter') - self._print_action = printactions.X2goPrintActionPRINT(client_instance=self.client_instance, printer=_cups_defaultprinter, logger=self.logger) - - elif not _general_pdfview and _print_startcmd: - _print_command = self.get('print', 'command') - self._print_action = printactions.X2goPrintActionPRINTCMD(client_instance=self.client_instance, print_cmd=_print_command, logger=self.logger) - - @property - def print_action(self): - """\ - Return the print action described by the »printing« configuration file. - - This method has property status and wraps around the L{get_print_action} - method. - - """ - return self.get_print_action() - - def get_print_action(self, reload=False, reinit=False, return_name=False): - """\ - Return the print action described by the »printing« configuration file. - - """ - if reload: - self.load() - - if reinit: - self._detect_print_action() - - if return_name: - return self._print_action.__name__ - else: - return self._print_action - - def get_property(self, print_property): - """\ - STILL UNDOCUMENTED - - """ - if print_property in _print_property_map.keys(): - _ini_section = _print_property_map[print_property]['ini_section'] - _ini_option = _print_property_map[print_property]['ini_option'] - return self.get_value(_ini_section, _ini_option) - else: - raise x2go_exceptions.X2goClientPrintingException('No such X2go client printing property ,,%s\'\'' % print_property) - - def set_property(self, print_property, value): - """\ - STILL UNDOCUMENTED - - """ - if print_property in _print_property_map.keys(): - _ini_section = _print_property_map[print_property]['ini_section'] - _ini_option = _print_property_map[print_property]['ini_option'] - _default_type = self.get_type(_ini_section, _ini_option) - if type(value) is types.UnicodeType: - value = value.encode('utf-8') - if _default_type != type(value): - raise x2go_exceptions.X2goClientPrintingException('Type mismatch error for property ,,%s\'\' - is: %s, should be: %s' % (print_property, str(type(value)), str(_default_type))) - self.update_value(_ini_section, _ini_option, value) - else: - raise x2go_exceptions.X2goClientPrintingException('No such X2go client printing property ,,%s\'\'' % print_property) - - def store_print_action(self, print_action, **print_properties): - """\ - STILL UNDOCUMENTED - - """ - if print_action == 'DIALOG': - self.update_value('General', 'showdialog', True) - else: - self.update_value('General', 'showdialog', False) - - if print_action == 'PDFVIEW': - self.update_value('General', 'pdfview', True) - self.update_value('view', 'open', True) - - elif print_action == 'PDFSAVE': - self.update_value('General', 'pdfview', True) - self.update_value('view', 'open', False) - - elif print_action == 'PRINT': - self.update_value('General', 'pdfview', False) - self.update_value('print', 'startcmd', False) - - elif print_action == 'PRINTCMD': - self.update_value('General', 'pdfview', False) - self.update_value('print', 'startcmd', True) - - for print_property in print_properties.keys(): - self.set_property(print_property, print_properties[print_property]) - diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/file.py python-x2go-0.5.0.6/x2go/backends/printing/file.py --- python-x2go-0.1.1.8/x2go/backends/printing/file.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/file.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoClientPrinting} class is one of Python X2Go's public API classes. + +Retrieve an instance of this class from your L{X2GoClient} instance. +Use this class in your Python X2Go based applications to access the »printing« +configuration of your X2Go client application. + +""" +__NAME__ = 'x2goprinting-pylib' + +# modules +import types + +# Python X2Go modules +import x2go.log as log +import x2go.printactions as printactions +# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) +from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS +from x2go.defaults import X2GO_PRINTING_CONFIGFILES as _X2GO_PRINTING_CONFIGFILES +import x2go.inifiles as inifiles +import x2go.x2go_exceptions as x2go_exceptions + +_print_property_map = { + 'pdfview_cmd': { + 'ini_section': 'view', + 'ini_option': 'command', + }, + 'save_to_folder': { + 'ini_section': 'save', + 'ini_option': 'folder', + }, + 'printer': { + 'ini_section': 'CUPS', + 'ini_option': 'defaultprinter', + }, + 'print_cmd': { + 'ini_section': 'print', + 'ini_option': 'command', + }, +} + +class X2GoClientPrinting(inifiles.X2GoIniFile): + """\ + L{x2go.backends.printing.file.X2GoClientPrinting} provides access to the X2Go ini-like file + »printing« as stored in C{~/.x2goclient/printing} resp. globally + C{/etc/x2goclient/printing}. + + An instance of L{x2go.backends.printing.file.X2GoClientPrinting} is created on each incoming + print job. This facilitates that on every print job the print action + for this job is derived from the »printing« configuration file. + + Thus, changes on the file are active for the next incoming print job. + + """ + config_files = [] + _print_action = None + + def __init__(self, config_files=_X2GO_PRINTING_CONFIGFILES, defaults=_X2GO_CLIENTPRINTING_DEFAULTS, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param config_files: a list of configuration files names (e.g. a global filename and a user's home + directory filename) + @type config_files: C{list} + @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override + Python X2Go's hard coded defaults in L{defaults} + @type defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintAction} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + self.client_instance = client_instance + inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) + + self._detect_print_action() + + def _detect_print_action(self): + """\ + Derive a print action from sections, keys and their values in a typical + X2Go client »printing« configuration file. + + """ + _general_pdfview = self.get('General', 'pdfview', key_type=types.BooleanType) + _view_open = self.get('view', 'open', key_type=types.BooleanType) + _print_startcmd = self.get('print', 'startcmd', key_type=types.BooleanType) + _show_dialog = self.get('General', 'showdialog', key_type=types.BooleanType) + + if _show_dialog and self.client_instance is not None: + self._print_action = printactions.X2GoPrintActionDIALOG(client_instance=self.client_instance, logger=self.logger) + + elif _general_pdfview and _view_open: + _view_command = self.get('view', 'command') + self._print_action = printactions.X2GoPrintActionPDFVIEW(client_instance=self.client_instance, pdfview_cmd=_view_command, logger=self.logger) + + elif _general_pdfview and not _view_open: + _safe_folder = self.get('save', 'folder') + self._print_action = printactions.X2GoPrintActionPDFSAVE(client_instance=self.client_instance, save_to_folder=_safe_folder, logger=self.logger) + + elif not _general_pdfview and not _print_startcmd: + _cups_defaultprinter = self.get('CUPS', 'defaultprinter') + self._print_action = printactions.X2GoPrintActionPRINT(client_instance=self.client_instance, printer=_cups_defaultprinter, logger=self.logger) + + elif not _general_pdfview and _print_startcmd: + _print_command = self.get('print', 'command') + self._print_action = printactions.X2GoPrintActionPRINTCMD(client_instance=self.client_instance, print_cmd=_print_command, logger=self.logger) + + @property + def print_action(self): + """\ + Return the print action described by the »printing« configuration file. + + This method has property status and wraps around the L{get_print_action} + method. + + """ + return self.get_print_action() + + def get_print_action(self, reload=False, reinit=False, return_name=False): + """\ + Return the print action described by the »printing« configuration file. + + @param reload: reload the configuration file before retrieving the print action? + @type reload: C{bool} + @param reinit: re-detect the print action from what is stored in cache? + @type reinit: C{bool} + @param return_name: return the print action name, not the class + @type return_name: C{bool} + + @return: the configured print action + @rtype: C{obj} or C{str} + + """ + if reload: + self.load() + + if reinit: + self._detect_print_action() + + if return_name: + return self._print_action.__name__ + else: + return self._print_action + + def get_property(self, print_property): + """\ + Retrieve a printing property as mapped by the L{_print_property_map} dictionary. + + @param print_property: a printing property + @type print_property: C{str} + + @return: the stored value for C{} + @rtype: C{str} + + @raise X2GoClientPrintingException: if the printing property does not exist + + """ + if print_property in _print_property_map.keys(): + _ini_section = _print_property_map[print_property]['ini_section'] + _ini_option = _print_property_map[print_property]['ini_option'] + return self.get_value(_ini_section, _ini_option) + else: + raise x2go_exceptions.X2GoClientPrintingException('No such X2Go client printing property ,,%s\'\'' % print_property) + + def set_property(self, print_property, value): + """\ + Set a printing property as mapped by the L{_print_property_map} dictionary. + + @param print_property: a printing property + @type print_property: C{str} + @param value: the value to be stored as C{} + @rtype: C{str} + + @raise X2GoClientPrintingException: if the printing property does not exist or if there is a type mismatch + + """ + if print_property in _print_property_map.keys(): + _ini_section = _print_property_map[print_property]['ini_section'] + _ini_option = _print_property_map[print_property]['ini_option'] + _default_type = self.get_type(_ini_section, _ini_option) + if type(value) is types.UnicodeType: + value = value.encode('utf-8') + if _default_type != type(value): + raise x2go_exceptions.X2GoClientPrintingException('Type mismatch error for property ,,%s\'\' - is: %s, should be: %s' % (print_property, str(type(value)), str(_default_type))) + self.update_value(_ini_section, _ini_option, value) + else: + raise x2go_exceptions.X2GoClientPrintingException('No such X2Go client printing property ,,%s\'\'' % print_property) + + def store_print_action(self, print_action, **print_properties): + """\ + Accept a new print action configuration. This includes the print action + itself (DIALOG, PDFVIEW, PDFSAVE, PRINT or PRINTCMD) and related printing properties + as mapped by the L{_print_property_map} dictionary. + + @param print_action: the print action name + @type print_action: C{str} + @param print_properties: the printing properties to set for the given print action + @type print_properties: C{dict} + + """ + if print_action == 'DIALOG': + self.update_value('General', 'showdialog', True) + else: + self.update_value('General', 'showdialog', False) + + if print_action == 'PDFVIEW': + self.update_value('General', 'pdfview', True) + self.update_value('view', 'open', True) + + elif print_action == 'PDFSAVE': + self.update_value('General', 'pdfview', True) + self.update_value('view', 'open', False) + + elif print_action == 'PRINT': + self.update_value('General', 'pdfview', False) + self.update_value('print', 'startcmd', False) + + elif print_action == 'PRINTCMD': + self.update_value('General', 'pdfview', False) + self.update_value('print', 'startcmd', True) + + for print_property in print_properties.keys(): + self.set_property(print_property, print_properties[print_property]) + diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/_gconf.py python-x2go-0.5.0.6/x2go/backends/printing/_gconf.py --- python-x2go-0.1.1.8/x2go/backends/printing/_gconf.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/_gconf.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goClientPrintingGCONF} class is one of Python X2go's public API classes. - -Retrieve an instance of this class from your L{X2goClient} instance. -Use this class in your Python X2go based applications to access the »printing« -configuration of your X2go client application. - -""" -__NAME__ = 'x2goprint-pylib' - -# modules -import types -import ConfigParser - -# Python X2go modules -import x2go.log as log -import x2go.printactions as printactions -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS -from x2go.defaults import X2GO_PRINTING_CONFIGFILES as _X2GO_PRINTING_CONFIGFILES -import x2go.inifiles as inifiles - -from x2go.x2go_exceptions import * - -class X2goClientPrintingGCONF(inifiles.X2goIniFile): - """\ - L{X2goClientPrinting} provides access to the X2go ini-like file - »printing« as stored in C{~/.x2goclient/printing} resp. globally - C{/etc/x2goclient/printing}. - - An instance of L{X2goClientPrinting} is created on each incoming - print job. This facilitates that on every print job the print action - for this job is derived from the »printing« configuration file. - - Thus, changes on the file are active for the next incoming print job. - - """ - config_files = [] - _print_action = None - defaultValues = _X2GO_CLIENTPRINTING_DEFAULTS - - def __init__(self, config_files=_X2GO_PRINTING_CONFIGFILES, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - @param config_files: a list of configuration files names (e.g. a global filename and a user's home - directory filename) - @type config_files: C{list} - @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override - Python X2go's hard coded defaults in L{defaults} - @type defaults: C{dict} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be - constructed with the given loglevel - @type loglevel: C{int} - - """ - raise X2goNotImplementedYetException('GCONF backend support is not implemented yet') - - diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/gconf.py python-x2go-0.5.0.6/x2go/backends/printing/gconf.py --- python-x2go-0.1.1.8/x2go/backends/printing/gconf.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/gconf.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoClientPrinting} class is one of Python X2Go's public API classes. + +Retrieve an instance of this class from your L{X2GoClient} instance. +Use this class in your Python X2Go based applications to access the »printing« +configuration of your X2Go client application. + +""" +__NAME__ = 'x2goprint-pylib' + +# modules +import copy + +# Python X2Go modules +import x2go.log as log + +# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) +from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoClientPrinting(object): + """\ + L{x2go.backends.printing.gconf.X2GoClientPrinting} provides access to the GCONF based configuration + of the X2Go client printing setup. + + An instance of L{x2go.backends.printing.gconf.X2GoClientPrinting} is created on each incoming + print job. This facilitates that on every print job the print action for this job is derived + from the »printing« configuration file. + + Thus, changes on the file are active for the next incoming print job. + + """ + _print_action = None + defaultValues = copy.deepcopy(_X2GO_CLIENTPRINTING_DEFAULTS) + + def __init__(self, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override + Python X2Go's hard coded defaults in L{defaults} + @type defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintAction} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + raise X2GoNotImplementedYetException('GCONF backend support is not implemented yet') + + diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/__init__.py python-x2go-0.5.0.6/x2go/backends/printing/__init__.py --- python-x2go-0.1.1.8/x2go/backends/printing/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,27 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -from x2go.defaults import BACKEND_CLIENTPRINTING_DEFAULT - -from _file import X2goClientPrintingFILE -from _winreg import X2goClientPrintingWINREG -from _gconf import X2goClientPrintingGCONF - -X2goClientPrinting = eval(BACKEND_CLIENTPRINTING_DEFAULT) - diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/_winreg.py python-x2go-0.5.0.6/x2go/backends/printing/_winreg.py --- python-x2go-0.1.1.8/x2go/backends/printing/_winreg.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/_winreg.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goClientPrintingWINREG} class is one of Python X2go's public API classes. - -Retrieve an instance of this class from your L{X2goClient} instance. -Use this class in your Python X2go based applications to access the »printing« -configuration of your X2go client application. - -""" -__NAME__ = 'x2goprint-pylib' - -# modules -import types -import ConfigParser - -# Python X2go modules -import x2go.log as log -import x2go.printactions as printactions -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS -from x2go.defaults import X2GO_PRINTING_CONFIGFILES as _X2GO_PRINTING_CONFIGFILES -import x2go.inifiles as inifiles - -class X2goClientPrintingWINREG(inifiles.X2goIniFile): - """\ - L{X2goClientPrinting} provides access to the X2go ini-like file - »printing« as stored in C{~/.x2goclient/printing} resp. globally - C{/etc/x2goclient/printing}. - - An instance of L{X2goClientPrinting} is created on each incoming - print job. This facilitates that on every print job the print action - for this job is derived from the »printing« configuration file. - - Thus, changes on the file are active for the next incoming print job. - - """ - config_files = [] - _print_action = None - defaultValues = _X2GO_CLIENTPRINTING_DEFAULTS - - def __init__(self, config_files=_X2GO_PRINTING_CONFIGFILES, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - @param config_files: a list of configuration files names (e.g. a global filename and a user's home - directory filename) - @type config_files: C{list} - @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override - Python X2go's hard coded defaults in L{defaults} - @type defaults: C{dict} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be - constructed with the given loglevel - @type loglevel: C{int} - - """ - raise X2goNotImplementedYetException('WINREG backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/printing/winreg.py python-x2go-0.5.0.6/x2go/backends/printing/winreg.py --- python-x2go-0.1.1.8/x2go/backends/printing/winreg.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/printing/winreg.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoClientPrinting} class is one of Python X2Go's public API classes. + +Retrieve an instance of this class from your L{X2GoClient} instance. +Use this class in your Python X2Go based applications to access the »printing« +configuration of your X2Go client application. + +""" +__NAME__ = 'x2goprint-pylib' + +# modules +import copy + +# Python X2Go modules +import x2go.log as log + +# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) +from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoClientPrinting(object): + """\ + L{x2go.backends.printing.winreg.X2GoClientPrinting} provides access to the Windows registry + based configuration of the X2Go client printing setup. + + An instance of L{x2go.backends.printing.winreg.X2GoClientPrinting} is created on each incoming + print job. This facilitates that on every print job the print action for this job is derived from the + »printing« configuration file. + + Thus, changes on the file are active for the next incoming print job. + + """ + _print_action = None + defaultValues = copy.deepcopy(_X2GO_CLIENTPRINTING_DEFAULTS) + + def __init__(self, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override + Python X2Go's hard coded defaults in L{defaults} + @type defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintAction} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + raise X2GoNotImplementedYetException('WINREG backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/base.py python-x2go-0.5.0.6/x2go/backends/profiles/base.py --- python-x2go-0.1.1.8/x2go/backends/profiles/base.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/base.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,778 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoSessionProfiles} class - managing x2goclient session profiles. + +L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based +applications. + +""" +__NAME__ = 'x2gosessionprofiles-pylib' + +import copy +import types +import re + +# Python X2Go modules +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS +from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS +import x2go.log as log +import x2go.utils as utils + +from x2go.x2go_exceptions import X2GoProfileException + +class X2GoSessionProfiles(): + + defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) + _non_profile_sections = ('embedded') + + def __init__(self, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT, **kwargs): + """\ + Retrieve X2Go session profiles. Base class for the different specific session profile + configuration backends. + + @param session_profile_defaults: a default session profile + @type session_profile_defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{x2go.backends.profiles.httpbroker.X2GoSessionProfiles} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + self.defaultValues = {} + self._profile_metatypes = {} + self._cached_profile_ids = {} + self.__useexports = {} + self._profiles_need_profile_id_renewal = [] + self.write_user_config = False + + if logger is None: + self.logger = log.X2GoLogger(loglevel=loglevel) + else: + self.logger = copy.deepcopy(logger) + self.logger.tag = __NAME__ + + if utils._checkSessionProfileDefaults(session_profile_defaults): + self.defaultSessionProfile = session_profile_defaults + + self.populate_session_profiles() + + def __call__(self, profile_id_or_name): + """\ + Retrieve the session profile configuration for a given session profile ID (or name) + + @param profile_id_or_name: profile ID or profile name + @type profile_id_or_name: C{str} + + @return: the profile ID's / name's profile configuration + @rtype: C{dict} + + """ + _profile_id = self.check_profile_id_or_name(self, profile_id_or_name) + return self.get_profile_config(profile_id=_profile_id) + + def init_profile_cache(self, profile_id_or_name): + """\ + Some session profile backends (e.g. the broker backends cache + dynamic session profile data). On new connections, it is + recommented to (re-)initialize these caches. + + @param profile_id_or_name: profile ID or profile name + @type profile_id_or_name: C{str} + + """ + profile_id = self.check_profile_id_or_name(profile_id_or_name) + + # allow backend specific clean-up + self._init_profile_cache(profile_id) + + def _init_profile_cache(self, profile_id): + """\ + Inherit from this class to (re-)initialize profile ID based + cache storage. + + """ + pass + + def populate_session_profiles(self): + """\ + Load a session profile set from the configuration storage + backend and make it available for this class. + + @return: a set of session profiles + @rtype: C{dict} + + """ + self.session_profiles = self. _populate_session_profiles() + + # scan for duplicate profile names and handle them... + scan_profile_names = {} + for profile_id in self.session_profiles.keys(): + profile_name = self.to_profile_name(profile_id) + if profile_name not in scan_profile_names.keys(): + scan_profile_names[profile_name] = [profile_id] + else: + scan_profile_names[profile_name].append(profile_id) + _duplicates = {} + for profile_name in scan_profile_names.keys(): + if len(scan_profile_names[profile_name]) > 1: + _duplicates[profile_name] = scan_profile_names[profile_name] + for profile_name in _duplicates.keys(): + i = 1 + for profile_id in _duplicates[profile_name]: + self.update_value(None, 'name', '{name} ({i})'.format(name=profile_name, i=i), profile_id=profile_id) + i += 1 + + def _populate_session_profiles(self): + """\ + Inherit from this class and provide the backend specific way of loading / + populating a set of session profile via this method. + + @return: a set of session profiles + @rtype: C{dict} + + """ + return {} + + def get_profile_metatype(self, profile_id_or_name, force=False): + """\ + Detect a human readable session profile type from the session profile configuration. + + @param profile_id_or_name: profile ID or profile name + @type profile_id_or_name: C{str} + @param force: re-detect the meta type, otherwise use a cached result + @type force: C{bool} + + @return: the profile ID's / name's meta type + @rtype: C{str} + + """ + _profile_id = self.check_profile_id_or_name(profile_id_or_name) + + if not self._profile_metatypes.has_key(_profile_id) or force: + _config = self.get_profile_config(_profile_id) + if _config['host']: + if _config['rdpserver'] and _config['command'] == 'RDP': + _metatype = 'RDP/proxy' + elif _config['published']: + + if _config['command'] in _X2GO_DESKTOPSESSIONS.keys(): + _metatype = '%s + Published Applications' % _config['command'] + else: + _metatype = 'Published Applications' + + elif _config['rootless']: + _metatype = 'Single Applications' + elif _config['command'] in _X2GO_DESKTOPSESSIONS.keys(): + _metatype = '%s Desktop' % _config['command'] + elif _config['command'] in _X2GO_DESKTOPSESSIONS.values(): + _metatype = '%s Desktop' % [ s for s in _X2GO_DESKTOPSESSIONS.keys() if _config['command'] == _X2GO_DESKTOPSESSIONS[s] ][0] + else: + _metatype = 'CUSTOM Desktop' + else: + if _config['rdpserver'] and _config['command'] == 'RDP': + _metatype = 'RDP/direct' + else: + _metatype = 'not supported' + self._profile_metatypes[_profile_id] = unicode(_metatype) + else: + return self._profile_metatypes[_profile_id] + + def is_mutable(self, profile_id_or_name=None, profile_id=None): + """\ + Check if a given profile name (or ID) is mutable or not. + + @param profile_id_or_name: profile name or profile ID + @type profile_id_or_name: C{str} + @param profile_id: if the profile ID is known, pass it in directly and skip + the L{check_profile_id_or_name()} call + @type profile_id: C{str} + + @return: C{True} if the session profile of the specified name/ID is mutable + @rtype: C{bool} + + @raise X2GoProfileException: if no such session profile exists + + """ + try: + profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) + return self._is_mutable(profile_id) + except X2GoProfileException: + return None + + def _is_mutable(self, profile_id): + """\ + Inherit from this base class and provide your own decision making + code here if a given profile ID is mutable or not. + + @param profile_id: profile ID + @type profile_id: C{str} + + @return: C{True} if the session profile of the specified ID is mutable + @rtype: C{bool} + + """ + return False + + def supports_mutable_profiles(self): + """\ + Check if the current session profile backend supports + mutable session profiles. + + @return: list of mutable profiles + @rtype: C{list} + + """ + return self._supports_mutable_profiles() + + def _supports_mutable_profiles(self): + """\ + Inherit from this base class and provide your own decision making + code here if a your session profile backend supports mutable + session profiles or not. + + @return: list of mutable profiles + @rtype: C{list} + + """ + return False + + def mutable_profile_ids(self): + """\ + List all mutable session profiles. + + @return: List up all session profile IDs of mutable session profiles. + @rtype: C{bool} + + """ + return [ p for p in self.profile_ids if self._is_mutable(p) ] + + def write(self): + """\ + Store session profile data to the storage backend. + + @return: C{True} if the write process has been successfull, C{False} otherwise + @rtype: C{bool} + + """ + # then update profile IDs for profiles that have a renamed host attribute... + for profile_id in self._profiles_need_profile_id_renewal: + _config = self.get_profile_config(profile_id=profile_id) + + self._delete_profile(profile_id) + + try: del self._cached_profile_ids[profile_id] + except KeyError: pass + self.add_profile(profile_id=None, force_add=True, **_config) + + self._profiles_need_profile_id_renewal = [] + self._cached_profile_ids = {} + + return self._write() + + def _write(self): + """\ + Write session profiles back to session profile storage backend. Inherit from this + class and adapt to the session profile backend via this method. + + """ + return True + + def get_profile_option_type(self, option): + """\ + Get the data type for a specific session profile option. + + @param option: the option to get the data type for + @type option: will be detected by this method + + @return: the data type of C{option} + @rtype: C{type} + + """ + try: + return type(self.defaultSessionProfile[option]) + except KeyError: + return types.StringType + + def get_profile_config(self, profile_id_or_name=None, parameter=None, profile_id=None): + """\ + The configuration options for a single session profile. + + @param profile_id_or_name: either profile ID or profile name is accepted + @type profile_id_or_name: C{str} + @param parameter: if specified, only the value for the given parameter is returned + @type parameter: C{str} + @param profile_id: profile ID (faster than specifying C{profile_id_or_name}) + @type profile_id: C{str} + + @return: the session profile configuration for the given profile ID (or name) + @rtype: C{dict} + + """ + _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) + _profile_config = {} + if parameter is None: + parameters = self._get_profile_options(_profile_id) + else: + parameters = [parameter] + for option in parameters: + value = self._get_profile_parameter(_profile_id, option, key_type=self.get_profile_option_type(option)) + + if type(value) is types.StringType: + value = unicode(value) + + if option == 'export' and type(value) is types.UnicodeType: + + _value = value.replace(',', ';').strip().strip('"').strip().strip(';').strip() + value = {} + if _value: + _export_paths = _value.split(';') + for _path in _export_paths: + if not re.match('.*:(0|1)$', _path): _path = '%s:1' % _path + _auto_export_path = re.match('.*:1$', _path) and True or False + _export_path = ':'.join(_path.split(':')[:-1]) + value[_export_path] = _auto_export_path + + _profile_config[option] = value + + if parameter is not None: + if parameter in _profile_config.keys(): + value = _profile_config[parameter] + return value + else: + raise X2GoProfileException('no such session profile parameter: %s' % parameter) + + return _profile_config + + def default_profile_config(self): + """\ + Return a default session profile. + + @return: default session profile + @rtype: C{dict} + + """ + return copy.deepcopy(self.defaultSessionProfile) + + def has_profile(self, profile_id_or_name): + """\ + Does a session profile of a given profile ID or profile name exist? + + @param profile_id_or_name: profile ID or profile name + @type profile_id_or_name: C{str} + + @return: C{True} if there is such a session profile, C{False} otherwise + @rtype: C{bool} + + """ + try: + self.check_profile_id_or_name(profile_id_or_name) + return True + except X2GoProfileException: + return False + + def _update_profile_ids_cache(self): + for p in self._get_profile_ids(): + if p not in self._non_profile_sections: + self._cached_profile_ids[p] = self.to_profile_name(p) + + @property + def profile_ids(self): + """\ + Render a list of all profile IDs found in the session profiles configuration. + + """ + if not self._cached_profile_ids: + self._update_profile_ids_cache() + return self._cached_profile_ids.keys() + + def _get_profile_ids(self): + """\ + Inherit from this class and provide a way for actually getting + a list of session profile IDs from the storage backend via this method. + + @return: list of available session profile IDs + @rtype: C{list} + + """ + return [] + + def has_profile_id(self, profile_id): + """\ + Does a session profile of a given profile ID exist? (Faster than L{has_profile()}.) + + @param profile_id: profile ID + @type profile_id: C{str} + + @return: C{True} if there is such a session profile, C{False} otherwise + @rtype: C{bool} + + """ + return unicode(profile_id) in self.profile_ids + + @property + def profile_names(self): + """\ + Render a list of all profile names found in the session profiles configuration. + + """ + if not self._cached_profile_ids: + self._update_profile_ids_cache() + return self._cached_profile_ids.values() + + def has_profile_name(self, profile_name): + """\ + Does a session profile of a given profile name exist? (Faster than L{has_profile()}.) + + @param profile_name: profile name + @type profile_name: C{str} + + @return: C{True} if there is such a session profile, C{False} otherwise + @rtype: C{bool} + + """ + return unicode(profile_name) in self.profile_names + + def to_profile_id(self, profile_name): + """\ + Convert profile name to profile ID. + + @param profile_name: profile name + @type profile_name: C{str} + + @return: profile ID + @rtype: C{str} + + """ + _profile_ids = [ p for p in self.profile_ids if self._cached_profile_ids[p] == profile_name ] + if len(_profile_ids) == 1: + return unicode(_profile_ids[0]) + elif len(_profile_ids) == 0: + return None + else: + raise X2GoProfileException('The sessions config file contains multiple session profiles with name: %s' % profile_name) + + def to_profile_name(self, profile_id): + """\ + Convert profile ID to profile name. + + @param profile_id: profile ID + @type profile_id: C{str} + + @return: profile name + @rtype: C{str} + + """ + try: + _profile_name = self.get_profile_config(profile_id=profile_id, parameter='name') + return unicode(_profile_name) + except: + return u'' + + def add_profile(self, profile_id=None, force_add=False, **kwargs): + """\ + Add a new session profile. + + @param profile_id: a custom profile ID--if left empty a profile ID will be auto-generated + @type profile_id: C{str} + @param kwargs: session profile options for this new session profile + @type kwargs: C{dict} + + @return: the (auto-generated) profile ID of the new session profile + @rtype: C{str} + + """ + if profile_id is None or profile_id in self.profile_ids: + profile_id = utils._genSessionProfileId() + self.session_profiles[profile_id] = self.default_profile_config() + + if 'name' not in kwargs.keys(): + raise X2GoProfileException('session profile parameter ,,name\'\' is missing in method parameters') + + if kwargs['name'] in self.profile_names and not force_add: + raise X2GoProfileException('a profile of name ,,%s\'\' already exists' % kwargs['name']) + + self._cached_profile_ids[profile_id] = kwargs['name'] + + for key, value in kwargs.items(): + self.update_value(None, key, value, profile_id=profile_id) + + _default_session_profile = self.default_profile_config() + for key, value in _default_session_profile.items(): + if key in kwargs: continue + self.update_value(None, key, value, profile_id=profile_id) + + self._cached_profile_ids = {} + + return unicode(profile_id) + + def delete_profile(self, profile_id_or_name): + """\ + Delete a session profile from the configuration file. + + @param profile_id_or_name: profile ID or profile name + @type profile_id_or_name: C{str} + + """ + _profile_id = self.check_profile_id_or_name(profile_id_or_name) + + self._delete_profile(_profile_id) + + self.write_user_config = True + self.write() + self._cached_profile_ids = {} + + def _delete_profile(self, profile_id): + """\ + Inherit from this class and provide a way for actually deleting + a complete session profile from the storage backend via this method. + + """ + pass + + def update_value(self, profile_id_or_name, option, value, profile_id=None): + """\ + Update a value in a session profile. + + @param profile_id_or_name: the profile ID + @type profile_id_or_name: C{str} + @param option: the session profile option of the given profile ID + @type option: C{str} + @param value: the value to update the session profile option with + @type value: any type, depends on the session profile option + @param profile_id: if the profile ID is known, pass it in directly and skip + the L{check_profile_id_or_name()} call + @type profile_id: C{str} + + """ + try: + profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) + except X2GoProfileException: + profile_id = profile_id_or_name + + if not self.is_mutable(profile_id=profile_id): + raise X2GoProfileException("session profile cannot be modified, it is marked as immutable") + + if option == 'name': + profile_name = value + current_profile_name = self.get_value(profile_id, option) + if not profile_name: + raise X2GoProfileException('profile name for profile id %s must not be empty' % profile_id) + else: + if profile_name != current_profile_name: + try: del self._cached_profile_ids[profile_id] + except KeyError: pass + if profile_name in self.profile_names: + raise X2GoProfileException('a profile of name ,,%s\'\' already exists' % profile_name) + self._cached_profile_ids[profile_id] = profile_name + + if option == 'export' and type(value) == types.DictType: + _strvalue = '"' + for folder in value.keys(): + _strvalue += "%s:%s;" % (folder, int(value[folder])) + _strvalue += '"' + _strvalue = _strvalue.replace('""', '') + value = _strvalue + + if option == 'host': + _host = self.get_profile_config(profile_id=profile_id, parameter='host') + if _host != value and _host is not None: + self._profiles_need_profile_id_renewal.append(profile_id) + if type(value) is types.TupleType: + value = list(value) + if type(value) is not types.ListType: + value = value.split(',') + + self._update_value(profile_id, option, value) + + def _update_value(self, profile_id, option, value): + """\ + Inherit from this class and provide for actually updating + a session profile's value in the storage backend via this method. + + """ + pass + + def check_profile_id_or_name(self, profile_id_or_name): + """\ + Detect the profile ID from a given string which maybe profile ID or profile name. + + @param profile_id_or_name: profile ID or profile name + @type profile_id_or_name: C{str} + + @return: profile ID + @rtype: C{str} + + @raise X2GoProfileException: if no such session profile exists + + """ + _profile_id = None + if self.has_profile_name(profile_id_or_name): + # we were given a sesion profile name... + _profile_id = self.to_profile_id(profile_id_or_name) + elif self.has_profile_id(profile_id_or_name): + # we were given a session profile id... + _profile_id = profile_id_or_name + else: + raise X2GoProfileException('No session profile with id or name ,,%s\'\' exists.' % profile_id_or_name) + if _profile_id is not None: + _profile_id = unicode(_profile_id) + return _profile_id + + def to_session_params(self, profile_id_or_name=None, profile_id=None): + """\ + Convert session profile options to L{X2GoSession} constructor method parameters. + + @param profile_id_or_name: either profile ID or profile name is accepted + @type profile_id_or_name: C{str} + @param profile_id: profile ID (fast than specifying C{profile_id_or_name}) + @type profile_id: C{str} + + @return: a dictionary of L{X2GoSession} constructor method parameters + @rtype: C{dict} + + """ + _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) + return utils._convert_SessionProfileOptions_2_SessionParams(self.get_profile_config(_profile_id)) + + def get_session_param(self, profile_id_or_name, param): + """\ + Get a single L{X2GoSession} parameter from a specific session profile. + + @param profile_id_or_name: either profile ID or profile name is accepted + @type profile_id_or_name: C{str} + @param param: the parameter name in the L{X2GoSession} constructor method + @type param: C{str} + + @return: the value of the session profile option represented by C{param} + @rtype: depends on the session profile option requested + + """ + return self.to_session_params(profile_id_or_name)[param] + + def _get_profile_parameter(self, profile_id, option, key_type): + """\ + Inherit from this class and provide a way for actually obtaining + the value of a specific profile parameter. + + @param profile_id: the profile's unique ID + @type profile_id: C{str} + @param option: the session profile option for which to retrieve its value + @type option: C{str} + @param key_type: type of the value to return + @type key_type: C{typeobject} + + @return: value of a session profile parameter + @rtype: C{various types} + + """ + return None + + def _get_profile_options(self, profile_id): + """\ + Inherit from this class and provide a way for actually obtaining + a list of available profile options of a given session profile. + + @return: list of available option is the given session profile + @rtype: C{list} + + """ + return [] + + def get_server_hostname(self, profile_id): + """\ + Retrieve host name of the X2Go Server configured in a session profile. + + @param profile_id: the profile's unique ID + @type profile_id: C{str} + + @return: the host name of the X2Go Server configured by the session profile + of the given profile ID + @rtype: C{list} + + """ + return unicode(self._get_server_hostname(profile_id)) + + def _get_server_hostname(self, profile_id): + """\ + Inherit from this class and provide a way for actually obtaining + a the server host name for a given profile ID. + + @param profile_id: the profile's unique ID + @type profile_id: C{str} + + @return: the host name of the X2Go Server configured by the session profile + of the given profile ID + @rtype: C{list} + + """ + return u'localhost' + + def get_server_port(self, profile_id): + """\ + Retrieve SSH port of the X2Go Server configured in a session profile. + + @param profile_id: the profile's unique ID + @type profile_id: C{str} + + @return: the SSH port of the X2Go Server configured by the session profile + of the given profile ID + @rtype: C{list} + + """ + return self._get_server_port(profile_id) + + def _get_server_port(self, profile_id): + """\ + Inherit from this class and provide a way for actually obtaining + a the server SSH port for a given profile ID. + + @param profile_id: the profile's unique ID + @type profile_id: C{str} + + @return: the SSH port of the X2Go Server configured by the session profile + of the given profile ID + @rtype: C{list} + + """ + return 22 + + def get_pkey_object(self, profile_id): + """\ + If available, return a PKey (Paramiko/SSH private key) object. + + @param profile_id: the profile's unique ID + @type profile_id: C{str} + + @return: a Paramiko/SSH PKey object + @rtype: C{obj} + + """ + return self._get_pkey_object(profile_id) + + def _get_pkey_object(self, profile_id): + """\ + Inherit from this class and provide a way for actually + providing such a PKey object. + + """ + return None diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/_file.py python-x2go-0.5.0.6/x2go/backends/profiles/_file.py --- python-x2go-0.1.1.8/x2go/backends/profiles/_file.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/_file.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,293 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goSessionProfiles} class - managing x2goclient session profiles. - -L{X2goSessionProfiles} is a public API class. Use this class in your Python X2go based -applications. - -""" -__NAME__ = 'x2gosessionprofiles-pylib' - -import copy -import types - -# Python X2go modules -from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES as _X2GO_SESSIONPROFILES_CONFIGFILES -from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS -from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS -import x2go.inifiles as inifiles -import x2go.log as log -import x2go.utils as utils -from x2go.x2go_exceptions import X2goProfileException - - -class X2goSessionProfilesFILE(inifiles.X2goIniFile): - - defaultSessionProfile = _X2GO_SESSIONPROFILE_DEFAULTS - _non_profile_sections = ('embedded') - - - def __init__(self, config_files=_X2GO_SESSIONPROFILES_CONFIGFILES, defaults=None, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - STILL UNDOCUMENTED - - """ - self.defaultValues = {} - self._profile_metatypes = {} - self._cached_profile_ids = [] - self._cached_profile_names = [] - - if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) - else: - self.logger = copy.deepcopy(logger) - self.logger.tag = __NAME__ - - # providing defaults for an X2goSessionProfiles instance will---in the worst case---override your - # existing sessions file in your home directory once you write the sessions back to file... - inifiles.X2goIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) - - if utils._checkSessionProfileDefaults(session_profile_defaults): - self.defaultSessionProfile = session_profile_defaults - - self.session_profiles = [ p for p in self.iniConfig.sections() if p not in self._non_profile_sections ] - for session_profile in self.session_profiles: - self.get_profile_metatype(session_profile) - for key, default_value in self.defaultSessionProfile.iteritems(): - if not self.iniConfig.has_option(session_profile, key): - self._storeValue(session_profile, key, default_value) - - def __call__(self, profile_id_or_name): - """\ - STILL UNDOCUMENTED - - """ - _profile_id = check_profile_id_or_name(self, profile_id_or_name) - return self.get_profile_config(profile_id=_profile_id) - - def get_profile_metatype(self, profile_id_or_name, force=False): - - _profile_id = self.check_profile_id_or_name(profile_id_or_name) - if not self._profile_metatypes.has_key(_profile_id) or force: - _config = self.get_profile_config(_profile_id) - if _config['host']: - if _config['rdpserver'] and _config['command'] == 'RDP': - _metatype = 'RDP/proxy' - elif _config['rootless']: - _metatype = 'Single Applications' - else: - if _config['command'] in _X2GO_DESKTOPSESSIONS.keys(): - _metatype = '%s Desktop' % _config['command'] - elif _config['command'] in _X2GO_DESKTOPSESSIONS.values(): - _metatype = '%s Desktop' % [ s for s in _X2GO_DESKTOPSESSIONS.keys() if _config['command'] == _X2GO_DESKTOPSESSIONS[s] ][0] - else: - _metatype = 'CUSTOM Desktop' - else: - if _config['rdpserver'] and _config['command'] == 'RDP': - _metatype = 'RDP/direct' - else: - _metatype = 'not supported' - self._profile_metatypes[_profile_id] = _metatype - else: - return self._profile_metatypes[_profile_id] - - def get_profile_option_type(self, option): - """\ - STILL UNDOCUMENTED - - """ - try: - return type(self.defaultSessionProfile[option]) - except KeyError: - return types.StringType - - def get_type(self, section, key): - """\ - STILL UNDOCUMENTED - - """ - # we have to handle the get_type method separately... - return self.get_profile_option_type(key) - - def get_profile_config(self, profile_id_or_name=None, profile_id=None): - """\ - STILL UNDOCUMENTED - - """ - _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) - _profile_config = {} - for option in self.iniConfig.options(_profile_id): - _profile_config[option] = self.get(_profile_id, option, key_type=self.get_profile_option_type(option)) - return _profile_config - - def default_profile_config(self): - """\ - STILL UNDOCUMENTED - - """ - return copy.deepcopy(self.defaultSessionProfile) - - def has_profile(self, profile_id_or_name): - try: - _profile_id = self.check_profile_id_or_name(profile_id_or_name) - return True - except X2goProfileException: - return False - - @property - def profile_ids(self): - """\ - STILL UNDOCUMENTED - - """ - if not self._cached_profile_ids: - self._cached_profile_ids = [ s for s in self.iniConfig.sections() if s not in self._non_profile_sections ] - return self._cached_profile_ids - - def has_profile_id(self, profile_id): - """\ - STILL UNDOCUMENTED - - """ - return profile_id in self.profile_ids - - @property - def profile_names(self): - """\ - STILL UNDOCUMENTED - - """ - if not self._cached_profile_names: - self._cached_profile_names = [ self.to_profile_name(p) for p in self.profile_ids ] - return self._cached_profile_names - - def has_profile_name(self, profile_name): - """\ - STILL UNDOCUMENTED - - """ - return profile_name in self.profile_names - - def to_profile_id(self, profile_name): - """\ - STILL UNDOCUMENTED - - """ - _profile_ids = [ p for p in self.profile_ids if self.to_profile_name(p) == profile_name ] - if len(_profile_ids) == 1: - return _profile_ids[0] - elif len(_profile_ids) == 0: - return None - else: - raise X2goProfileException('The sessions config file contains multiple session profiles with name: %s' % profile_name) - - def to_profile_name(self, profile_id): - """\ - STILL UNDOCUMENTED - - """ - _profile_config = self.get_profile_config(profile_id=profile_id) - if _profile_config.has_key('name'): - return _profile_config['name'] - else: - return '' - - def add_profile(self, profile_id=None, **kwargs): - """\ - STILL UNDOCUMENTED - - """ - if profile_id is None: - profile_id = utils._genSessionProfileId() - for key, value in kwargs.items(): - if key in self.defaultSessionProfile: - self.update_value(profile_id, key, value) - else: - raise X2goProfileException('keyword ,,%s\'\' not supported in X2go session profile' % key) - - for key, value in self.defaultSessionProfile.items(): - if key in kwargs: continue - self.update_value(profile_id, key, value) - - self._cached_profile_ids = [] - self._cached_profile_names = [] - - return profile_id - - def delete_profile(self, profile_id_or_name): - """\ - STILL UNDOCUMENTED - - """ - _profile_id = self.check_profile_id_or_name(profile_id_or_name) - self.iniConfig.remove_section(_profile_id) - self.write_user_config = True - self.write() - self._cached_profile_ids = [] - self._cached_profile_names = [] - - def update_value(self, section, key, value): - """\ - STILL UNDOCUMENTED - - """ - profile_id = section - if key == 'name': - profile_name = value - current_profile_name = self.get_value(section, key) - if not profile_name: - raise X2goProfileException('profile name for profile id %s may not be empty' % profile_id) - else: - if profile_name != current_profile_name and profile_name in self.profile_names: - raise X2goProfileException('a profile of name ,,%s'' already exists' % profile_name) - self._cached_profile_names = [] - inifiles.X2goIniFile.update_value(self, section, key, value) - - def check_profile_id_or_name(self, profile_id_or_name): - """\ - STILL UNDOCUMENTED - - """ - _profile_id = None - if self.has_profile_name(profile_id_or_name): - # we were given a sesion profile name... - _profile_id = self.to_profile_id(profile_id_or_name) - elif self.has_profile_id(profile_id_or_name): - # we were given a session profile id... - _profile_id = profile_id_or_name - else: - raise X2goProfileException('No session profile with id or name ,,%s\'\' exists.' % profile_id_or_name) - return _profile_id - - def to_session_params(self, profile_id_or_name=None, profile_id=None): - """\ - STILL UNDOCUMENTED - - """ - _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) - return utils._convert_SessionProfileOptions_2_SessionParams(self.get_profile_config(_profile_id)) - - def get_session_param(self, profile_id_or_name, param): - """\ - STILL UNDOCUMENTED - - """ - return self.to_session_params(profile_id_or_name)[param] diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/file.py python-x2go-0.5.0.6/x2go/backends/profiles/file.py --- python-x2go-0.1.1.8/x2go/backends/profiles/file.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/file.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoSessionProfiles} class - managing x2goclient session profiles. + +L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based +applications. + +""" +__NAME__ = 'x2gosessionprofiles-pylib' + +import random + +# Python X2Go modules +from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES as _X2GO_SESSIONPROFILES_CONFIGFILES +import x2go.backends.profiles.base as base +import x2go.inifiles as inifiles +import x2go.log as log + +class X2GoSessionProfiles(base.X2GoSessionProfiles, inifiles.X2GoIniFile): + + def __init__(self, config_files=_X2GO_SESSIONPROFILES_CONFIGFILES, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT, **kwargs): + """\ + Retrieve X2Go session profiles from a file, typically C{~/.x2goclient/sessions}. + + @param config_files: a list of config file locations, the first file name in this list the user has write access to will be the user configuration file + @type config_files: C{list} + @param session_profile_defaults: a default session profile + @type session_profile_defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{x2go.backends.profiles.file.X2GoSessionProfiles} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + # providing defaults for an X2GoSessionProfiles instance will---in the worst case---override your + # existing sessions file in your home directory once you write the sessions back to file... + inifiles.X2GoIniFile.__init__(self, config_files=config_files, logger=logger, loglevel=loglevel) + base.X2GoSessionProfiles.__init__(self, session_profile_defaults=session_profile_defaults, logger=logger, loglevel=loglevel) + + def get_type(self, section, key): + """\ + Override the inifile class's get_type method due to the special layout of the session profile + class. + + @param section: INI file section + @type section: C{str} + @param key: key in INI file section + @type key: C{str} + + @return: the data type of C{key} in C{section} + @rtype: C{type} + + """ + # we have to handle the get_type method separately... + return self.get_profile_option_type(key) + + def _populate_session_profiles(self): + """\ + Populate the set of session profiles by loading the session + profile configuration from a file in INI format. + + @return: a set of session profiles + @rtype: C{dict} + + """ + session_profiles = [ p for p in self.iniConfig.sections() if p not in self._non_profile_sections and p != 'none' ] + _session_profiles_dict = {} + for session_profile in session_profiles: + for key, default_value in self.defaultSessionProfile.iteritems(): + if not self.iniConfig.has_option(session_profile, key): + self._storeValue(session_profile, key, default_value) + # update cached meta type session profile information + self.get_profile_metatype(session_profile) + _session_profiles_dict[session_profile] = self.get_profile_config(session_profile) + + return _session_profiles_dict + + def _is_mutable(self, profile_id): + return True + + def _supports_mutable_profiles(self): + return True + + def _write(self): + self._write_user_config = self.write_user_config + return inifiles.X2GoIniFile.write(self) + + def _delete_profile(self, profile_id): + self.iniConfig.remove_section(profile_id) + try: del self.session_profiles[profile_id] + except KeyError: pass + + def _update_value(self, profile_id, option, value): + self.session_profiles[profile_id][option] = value + if option == 'host': + value = ','.join(value) + self._X2GoIniFile__update_value(profile_id, option, value) + + def _get_profile_parameter(self, profile_id, option, key_type): + return self.get(profile_id, option, key_type) + + def _get_profile_options(self, profile_id): + return [ o for o in self.iniConfig.options(profile_id) if o != "none" ] + + def _get_profile_ids(self): + return [ s for s in self.iniConfig.sections() if s != "none" ] + + def _get_server_hostname(self, profile_id): + return random.choice(self.get_profile_config(profile_id, 'host')) + + def _get_server_port(self, profile_id): + return self.get_profile_config(profile_id, 'sshport') diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/_gconf.py python-x2go-0.5.0.6/x2go/backends/profiles/_gconf.py --- python-x2go-0.1.1.8/x2go/backends/profiles/_gconf.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/_gconf.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goSessionProfiles} class - managing x2goclient session profiles. - -L{X2goSessionProfiles} is a public API class. Use this class in your Python X2go based -applications. - -""" -__NAME__ = 'x2gosessionprofiles-pylib' - -import copy - -# Python X2go modules -from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES -from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS -import x2go.inifiles as inifiles -import x2go.log as log -import x2go.utils as hostname - -from x2go.x2go_exceptions import X2goProfileException - - -class X2goSessionProfilesGCONF(inifiles.X2goIniFile): - - defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS - _non_profile_sections = ('embedded') - - def __init__(self, config_files=X2GO_SESSIONPROFILES_CONFIGFILES, defaults=None, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - STILL UNDOCUMENTED - - """ - raise X2goNotImplementedYetException('GCONF backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/gconf.py python-x2go-0.5.0.6/x2go/backends/profiles/gconf.py --- python-x2go-0.1.1.8/x2go/backends/profiles/gconf.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/gconf.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoSessionProfiles} class - managing x2goclient session profiles. + +L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based +applications. + +""" +__NAME__ = 'x2gosessionprofiles-pylib' + +# modules +import copy + +# Python X2Go modules +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS +import x2go.log as log +import x2go.backends.profiles.base as base + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoSessionProfiles(base.X2GoSessionProfiles): + + defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) + _non_profile_sections = ('embedded') + + def __init__(self, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Retrieve X2Go session profiles from GConf daemon. + + @param session_profile_defaults: a default session profile + @type session_profile_defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{x2go.backends.profiles.gconf.X2GoSessionProfiles} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + raise X2GoNotImplementedYetException('GCONF backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/httpbroker.py python-x2go-0.5.0.6/x2go/backends/profiles/httpbroker.py --- python-x2go-0.1.1.8/x2go/backends/profiles/httpbroker.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/httpbroker.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,434 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoSessionProfiles} class - managing x2goclient session profiles. + +L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based +applications. + +""" +__NAME__ = 'x2gosessionprofiles-pylib' + +import re +import requests +import urllib3.exceptions +import copy +import types +import time +try: import simplejson as json +except ImportError: import json + +# Python X2Go modules +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS +from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER +import x2go.backends.profiles.base as base +import x2go.log as log +from x2go.utils import genkeypair +import x2go.x2go_exceptions + +class X2GoSessionProfiles(base.X2GoSessionProfiles): + + defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) + + def __init__(self, session_profile_defaults=None, + broker_url="http://localhost:8080/json/", + broker_username=None, + broker_password=None, + logger=None, loglevel=log.loglevel_DEFAULT, + **kwargs): + """\ + Retrieve X2Go session profiles from a HTTP(S) session broker. + + @param session_profile_defaults: a default session profile + @type session_profile_defaults: C{dict} + @param broker_url: URL for accessing the X2Go Session Broker + @type broker_url: C{str} + @param broker_password: use this password for authentication against the X2Go Session Broker (avoid + password string in the C{broker_URL} parameter is highly recommended) + @type broker_password: C{str} + @param logger: you can pass an L{X2GoLogger} object to the + L{x2go.backends.profiles.httpbroker.X2GoSessionProfiles} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + if broker_url.upper() != "HTTP": + match = re.match('^(?P(http(|s)))://(|(?P[a-zA-Z0-9_\.-]+)(|:(?P.*))@)(?P[a-zA-Z0-9\.-]+)(|:(?P[0-9]+))($|/(?P.*)$)', broker_url) + p = match.groupdict() + if p['user']: + self.broker_username = p['user'] + else: + self.broker_username = broker_username + if p['password']: + self.broker_password = p['password'] + elif broker_password: + self.broker_password = broker_password + else: + self.broker_password = None + + # fine-tune the URL + p['path'] = "/{path}".format(**p) + if p['port'] is not None: + p['port'] = ":{port}".format(**p) + + self.broker_url = "{protocol}://{hostname}{port}{path}".format(**p) + + else: + self.broker_username = broker_username + self.broker_password = broker_password + self.broker_url = broker_url + + self.broker_noauth = False + self.broker_authid = None + self._broker_profile_cache = {} + self._mutable_profile_ids = None + self._broker_auth_successful = None + + self._broker_type = "http" + + base.X2GoSessionProfiles.__init__(self, session_profile_defaults=session_profile_defaults, logger=logger, loglevel=loglevel) + if self.broker_url != "HTTP": + self.logger("Using session broker at URL: %s" % self.broker_url, log.loglevel_NOTICE) + + # for broker based autologin, we have to be able to provide public/private key pair + self.broker_my_pubkey, self.broker_my_privkey = genkeypair(local_username=_CURRENT_LOCAL_USER, client_address='127.0.0.1') + + def get_broker_noauth(self): + """\ + Accessor for the class's C{broker_noauth} property. + + @return: C{True} if the broker probably does not expect authentication. + @rtype: C{bool} + + """ + return self.broker_noauth + + def get_broker_username(self): + """\ + Accessor for the class's C{broker_username} property. + + @return: the username used for authentication against the session broker URL + @rtype: C{str} + + """ + return self.broker_username + + def get_broker_url(self): + """\ + Accessor for the class's C{broker_url} property. + + @return: the session broker URL that was used at broker session instantiation + @rtype: C{str} + + """ + return self.broker_url + + def set_broker_url(self, broker_url): + """\ + Mutator for the class's C{broker_url} property. + + @param broker_url: A new broker URL to use with this instance. Format is + C{://:/} (where protocol has to be C{http} + or C{https}. + @type broker_url: C{str} + + @return: the session broker URL that was used at broker session instantiation + @rtype: C{str} + + """ + self.broker_url = broker_url + + def get_broker_type(self): + """\ + Accessor of the class's {_broker_type} property. + + @return: either C{http} or C{https}. + @rtype: C{str} + + """ + return self._broker_type + + def broker_simpleauth(self, broker_username, broker_password): + """\ + Attempt a username / password authentication against the instance's + broker URL. + + @param broker_username: username to use for authentication + @type broker_username: C{str} + @param broker_password: password to use for authentication + @type broker_password: C{str} + + @return: C{True} if authentication has been successful + @rtype: C{bool} + + @raise X2GoBrokerConnectionException: Raised on any kind of connection / + authentication failure. + + """ + if self.broker_url is not None: + request_data = { + 'user': broker_username or '', + } + if self.broker_authid is not None: + request_data['authid'] = self.broker_authid + self.logger("Sending request to broker: user: {user}, authid: {authid}".format(**request_data), log.loglevel_DEBUG) + else: + if broker_password: + request_data['password'] = "" + else: + request_data['password'] = "" + self.logger("Sending request to broker: user: {user}, password: {password}".format(**request_data), log.loglevel_DEBUG) + request_data['password'] = broker_password or '' + try: + r = requests.post(self.broker_url, data=request_data) + except (requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, urllib3.exceptions.LocationParseError): + raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url) + if r.status_code == 200: + payload = json.loads(r.text) + if not self.broker_authid and not self.broker_password: + self.broker_noauth = True + elif payload.has_key('next-authid'): + self.broker_authid = payload['next-authid'] + self.broker_username = broker_username or '' + self.broker_password = broker_password or '' + self._broker_auth_successful = True + self.populate_session_profiles() + return True + self._broker_auth_successful = False + self.broker_authid = None + return False + + def broker_disconnect(self): + """\ + Disconnect from an (already) authenticated broker session. + + All authentication parameters will be dropped (forgotten) and + this instance has to re-authenticate against / re-connect to the + session broker before any new interaction with the broker is possible. + + """ + _profile_ids = copy.deepcopy(self.profile_ids) + + # forget nearly everything... + for profile_id in _profile_ids: + self.init_profile_cache(profile_id) + try: del self._profile_metatypes[profile_id] + except KeyError: pass + try: self._profiles_need_profile_id_renewal.remove(profile_id) + except ValueError: pass + try: del self._cached_profile_ids[profile_id] + except KeyError: pass + del self.session_profiles[profile_id] + self._mutable_profile_ids = None + self._broker_auth_successful = False + self.broker_authid = None + self.broker_password = None + self.broker_noauth = False + + def is_broker_authenticated(self): + """\ + Detect if an authenticated broker session has already been + initiated. Todo so, a simple re-authentication (username, password) + will be attempted. If that fails, user credentials are not provided / + valid. + + @return: C{True} if the broker session has already been authenticated + and user credentials are known / valid + @rtype: C{bool} + + """ + if self._broker_auth_successful is None: + # do a test auth against the given broker URL + try: + self.broker_simpleauth(self.broker_username, self.broker_password) + except x2go.x2go_exceptions.X2GoBrokerConnectionException: + self._broker_auth_successful = False + return self._broker_auth_successful + + def broker_listprofiles(self): + """\ + Obtain a session profile list from the X2Go Session Broker. + + @return: session profiles as a Python dictionary. + @rtype: C{dict} + + """ + if self.broker_url is not None: + request_data = { + 'task': 'listprofiles', + 'user': self.broker_username, + } + if self.broker_authid is not None: + request_data['authid'] = self.broker_authid + self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG) + else: + if self.broker_password: + request_data['password'] = "" + else: + request_data['password'] = "" + self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG) + request_data['password'] = self.broker_password or '' + try: + r = requests.post(self.broker_url, data=request_data) + except requests.exceptions.ConnectionError: + raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url) + if r.status_code == 200 and r.headers['content-type'].startswith("text/json"): + payload = json.loads(r.text) + if payload.has_key('next-authid'): + self.broker_authid = payload['next-authid'] + if payload.has_key('mutable_profile_ids'): + self._mutable_profile_ids = payload['mutable_profile_ids'] + self._broker_auth_successful = True + return payload['profiles'] if payload['task'] == 'listprofiles' else {} + self._broker_auth_successful = False + self.broker_authid = None + return {} + + def broker_selectsession(self, profile_id): + """\ + Select a session from the list of available session profiles (presented by + L{broker_listprofiles}). This method requests a session information dictionary + (server, port, SSH keys, already running / suspended sessions, etc.) from the + session broker for the provided C{profile_id}. + + @param profile_id: profile ID of the selected session profile + @type profile_id: C{str} + + @return: session information (server, port, SSH keys, etc.) for a selected + session profile (i.e. C{profile_id}) + @rtype: C{dict} + + """ + if self.broker_url is not None: + if not self._broker_profile_cache.has_key(profile_id) or not self._broker_profile_cache[profile_id]: + request_data = { + 'task': 'selectsession', + 'profile-id': profile_id, + 'user': self.broker_username, + 'pubkey': self.broker_my_pubkey, + } + if self.broker_authid is not None: + request_data['authid'] = self.broker_authid + self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG) + else: + if self.broker_password: + request_data['password'] = "" + else: + request_data['password'] = "" + self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG) + request_data['password'] = self.broker_password or '' + try: + r = requests.post(self.broker_url, data=request_data) + except requests.exceptions.ConnectionError: + raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url) + if r.status_code == 200 and r.headers['content-type'].startswith("text/json"): + payload = json.loads(r.text) + if payload.has_key('next-authid'): + self.broker_authid = payload['next-authid'] + self._broker_profile_cache[profile_id] = payload['selected_session'] if payload['task'] == 'selectsession' else {} + self._broker_auth_successful = True + else: + self.broker_authid = None + self._broker_auth_successful = False + self._broker_profile_cache[profile_id] + return self._broker_profile_cache[profile_id] + return {} + + def _init_profile_cache(self, profile_id): + if self._broker_profile_cache.has_key(unicode(profile_id)): + del self._broker_profile_cache[unicode(profile_id)] + + def _populate_session_profiles(self): + """\ + Populate the set of session profiles by loading the session + profile configuration from a file in INI format. + + @return: a set of session profiles + @rtype: C{dict} + + """ + if self.is_broker_authenticated() and \ + self.broker_noauth or \ + self.broker_username and self.broker_password: + + session_profiles = self.broker_listprofiles() + _session_profiles = copy.deepcopy(session_profiles) + + for session_profile in _session_profiles: + session_profile = unicode(session_profile) + for key, default_value in self.defaultSessionProfile.iteritems(): + key = unicode(key) + if type(default_value) is types.StringType: + default_value = unicode(default_value) + if not session_profiles[session_profile].has_key(key): + session_profiles[session_profile][key] = default_value + + else: + session_profiles = {} + + return session_profiles + + def _is_mutable(self, profile_id): + if type(self._mutable_profile_ids) is types.ListType and profile_id in self._mutable_profile_ids: + return True + return False + + def _supports_mutable_profiles(self): + if type(self._mutable_profile_ids) is types.ListType: + return True + return False + + def _write(self): + print "not suported, yet" + + def _delete_profile(self, profile_id): + del self.session_profiles[unicode(profile_id)] + + def _update_value(self, profile_id, option, value): + if type(value) is types.StringType: + value = unicode(value) + self.session_profiles[unicode(profile_id)][unicode(option)] = value + + def _get_profile_parameter(self, profile_id, option, key_type): + return key_type(self.session_profiles[unicode(profile_id)][unicode(option)]) + + def _get_profile_options(self, profile_id): + return self.session_profiles[unicode(profile_id)].keys() + + def _get_profile_ids(self): + self.session_profiles.keys() + return self.session_profiles.keys() + + def _get_server_hostname(self, profile_id): + selected_session = self.broker_selectsession(profile_id) + return selected_session['server'] + + def _get_server_port(self, profile_id): + selected_session = self.broker_selectsession(profile_id) + return int(selected_session['port']) + + def _get_pkey_object(self, profile_id): + selected_session = self.broker_selectsession(profile_id) + if selected_session.has_key('authentication_pubkey') and selected_session['authentication_pubkey'] == 'ACCEPTED': + time.sleep(2) + return self.broker_my_privkey + return None diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/_httpsbroker.py python-x2go-0.5.0.6/x2go/backends/profiles/_httpsbroker.py --- python-x2go-0.1.1.8/x2go/backends/profiles/_httpsbroker.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/_httpsbroker.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goSessionProfiles} class - managing x2goclient session profiles. - -L{X2goSessionProfiles} is a public API class. Use this class in your Python X2go based -applications. - -""" -__NAME__ = 'x2gosessionprofiles-pylib' - -import copy - -# Python X2go modules -from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES -from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS -import x2go.inifiles as inifiles -import x2go.log as log -import x2go.utils as utils -from x2go.x2go_exceptions import X2goProfileException - - -class X2goSessionProfilesHTTPSBROKER(inifiles.X2goIniFile): - - defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS - _non_profile_sections = ('embedded') - - def __init__(self, config_files=X2GO_SESSIONPROFILES_CONFIGFILES, defaults=None, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - STILL UNDOCUMENTED - - """ - raise X2goNotImplementedYetException('HTTPSBROKER backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/__init__.py python-x2go-0.5.0.6/x2go/backends/profiles/__init__.py --- python-x2go-0.1.1.8/x2go/backends/profiles/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,28 +1,19 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -from x2go.defaults import BACKEND_SESSIONPROFILES_DEFAULT - -from _file import X2goSessionProfilesFILE -from _winreg import X2goSessionProfilesWINREG -from _httpsbroker import X2goSessionProfilesHTTPSBROKER -from _gconf import X2goSessionProfilesGCONF - -X2goSessionProfiles = eval(BACKEND_SESSIONPROFILES_DEFAULT) - diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/sshbroker.py python-x2go-0.5.0.6/x2go/backends/profiles/sshbroker.py --- python-x2go-0.1.1.8/x2go/backends/profiles/sshbroker.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/sshbroker.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoSessionProfiles} class - managing x2goclient session profiles. + +L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based +applications. + +""" +__NAME__ = 'x2gosessionprofiles-pylib' + +# modules +import copy + +# Python X2Go modules +import x2go.backends.profiles.base as base +import x2go.log as log + +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoSessionProfiles(base.X2GoSessionProfiles): + + defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) + _non_profile_sections = ('embedded') + + def __init__(self, session_profile_defaults=_X2GO_SESSIONPROFILE_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Retrieve X2Go session profiles from a SSH session broker. + + @param session_profile_defaults: a default session profile + @type session_profile_defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{x2go.backends.profiles.httpbroker.X2GoSessionProfiles} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + raise X2GoNotImplementedYetException('HTTPSBROKER backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/_winreg.py python-x2go-0.5.0.6/x2go/backends/profiles/_winreg.py --- python-x2go-0.1.1.8/x2go/backends/profiles/_winreg.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/_winreg.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -L{X2goSessionProfiles} class - managing x2goclient session profiles. - -L{X2goSessionProfiles} is a public API class. Use this class in your Python X2go based -applications. - -""" -__NAME__ = 'x2gosessionprofiles-pylib' - -import copy - -# Python X2go modules -from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES -from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS -import x2go.inifiles as inifiles -import x2go.log as log -import x2go.utils as hostname - -from x2go.x2go_exceptions import X2goProfileException - - -class X2goSessionProfilesWINREG(inifiles.X2goIniFile): - - defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS - _non_profile_sections = ('embedded') - - def __init__(self, config_files=X2GO_SESSIONPROFILES_CONFIGFILES, defaults=None, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - STILL UNDOCUMENTED - - """ - raise X2goNotImplementedYetException('WINREG backend support is not implemented yet') - diff -Nru python-x2go-0.1.1.8/x2go/backends/profiles/winreg.py python-x2go-0.5.0.6/x2go/backends/profiles/winreg.py --- python-x2go-0.1.1.8/x2go/backends/profiles/winreg.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/profiles/winreg.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +L{X2GoSessionProfiles} class - managing x2goclient session profiles. + +L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based +applications. + +""" +__NAME__ = 'x2gosessionprofiles-pylib' + +# modules +import copy + +# Python X2Go modules +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS +import x2go.backends.profiles.base as base +import x2go.log as log + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoSessionProfilesWINREG(base.X2GoSessionProfiles): + + defaultSessionProfile = copy.deepcopy(X2GO_SESSIONPROFILE_DEFAULTS) + _non_profile_sections = ('embedded') + + def __init__(self, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Retrieve X2Go session profiles from the Windows registry. + + @param session_profile_defaults: a default session profile + @type session_profile_defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoSessionProfilesWINREG} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + raise X2GoNotImplementedYetException('WINREG backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/proxy/base.py python-x2go-0.5.0.6/x2go/backends/proxy/base.py --- python-x2go-0.1.1.8/x2go/backends/proxy/base.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/proxy/base.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goProxyBASE class - proxying your connection through NX3 and others. +X2GoProxy class - proxying your connection through NX3 and others. """ __NAME__ = 'x2goproxy-pylib' @@ -26,14 +26,11 @@ # modules import gevent import os -import sys -import types -import time import copy import threading -import cStringIO +import socket -# Python X2go modules +# Python X2Go modules import x2go.forward as forward import x2go.log as log import x2go.utils as utils @@ -50,12 +47,12 @@ from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR -class X2goProxyBASE(threading.Thread): +class X2GoProxy(threading.Thread): """\ - X2goProxy is an abstract class for X2go proxy connections. + X2GoProxy is an abstract class for X2Go proxy connections. - This class needs to be inherited from a concrete proxy class. Only - currently available proxy class is: L{X2goProxyNX3}. + This class needs to be inherited from a concrete proxy class. Only + currently available proxy class is: L{x2go.backends.proxy.nx3.X2GoProxy}. """ PROXY_CMD = '' @@ -71,72 +68,75 @@ fw_tunnel = None proxy = None - def __init__(self, session_info=None, - ssh_transport=None, session_log="session.log", + def __init__(self, session_info=None, + ssh_transport=None, session_log="session.log", session_errors="session.err", sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), proxy_options={}, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT, ): """\ - @param session_info: session information provided as an C{X2goServerSessionInfo*} backend + @param session_info: session information provided as an C{X2GoServerSessionInfo*} backend instance - @type session_info: C{X2goServerSessionInfo*} instance + @type session_info: C{X2GoServerSessionInfo*} instance @param ssh_transport: SSH transport object from C{paramiko.SSHClient} @type ssh_transport: C{paramiko.Transport} instance @param session_log: name of the proxy's session logfile @type session_log: C{str} - @param sessions_rootdir: base dir where X2go session files are stored (by default: ~/.x2go) + @param sessions_rootdir: base dir where X2Go session files are stored (by default: ~/.x2go) @type sessions_rootdir: C{str} - @param proxy_options: a set of very C{X2goProxy*} backend specific options; any option that is not known - to the C{X2goProxy*} backend will simply be ignored + @param proxy_options: a set of very L{base.X2GoProxy} backend specific options; any option that is not known + to the L{base.X2GoProxy} backend will simply be ignored @type proxy_options: C{dict} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goProxy} constructor - @param session_instance: the L{X2goSession} instance this C{X2goProxy*} instance belongs to - @type session_instance: L{X2goSession} instance - @type logger: L{X2goLogger} instance - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{base.X2GoProxy} constructor + @param session_instance: the L{X2GoSession} instance this L{base.X2GoProxy} instance belongs to + @type session_instance: L{X2GoSession} instance + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: int """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.sessions_rootdir = sessions_rootdir self.session_info = session_info + self.session_name = self.session_info.name self.ssh_transport = ssh_transport self.session_log = session_log + self.session_errors = session_errors self.proxy_options = proxy_options self.session_instance = session_instance self.PROXY_ENV = os.environ.copy() self.proxy = None + self.subsystem = 'X2Go Proxy' threading.Thread.__init__(self) self.daemon = True def __del__(self): """\ - On instance destruction make sure the this proxy thread is stopped properly. + On instance destruction make sure this proxy thread is stopped properly. """ self.stop_thread() def _tidy_up(self): """\ - Close any left open port forwarding tunnel, also close session log file, + Close any left open port forwarding tunnel, also close session log file, if left open. """ if self.proxy: - self.logger('Shutting down X2go proxy subprocess', loglevel=log.loglevel_DEBUG) + self.logger('Shutting down X2Go proxy subprocess', loglevel=log.loglevel_DEBUG) try: self.proxy.kill() except OSError, e: - self.logger('X2go proxy shutdown gave a message that we may ignore: %s' % str(e), loglevel=log.loglevel_WARN) + self.logger('X2Go proxy shutdown gave a message that we may ignore: %s' % str(e), loglevel=log.loglevel_WARN) self.proxy = None if self.fw_tunnel is not None: self.logger('Shutting down Paramiko/SSH forwarding tunnel', loglevel=log.loglevel_DEBUG) @@ -154,12 +154,12 @@ """ self._keepalive = False # wait for thread loop to finish... - gevent.sleep(.5) - self._tidy_up() + while self.proxy is not None: + gevent.sleep(.5) def run(self): """\ - Start the X2go proxy command. The X2go proxy command utilizes a + Start the X2Go proxy command. The X2Go proxy command utilizes a Paramiko/SSH based forwarding tunnel (openssh -L option). This tunnel gets started here and is forked into background (Greenlet/gevent). @@ -171,28 +171,34 @@ return None try: - os.mkdir(self.session_info.local_container) + os.makedirs(self.session_info.local_container) except OSError, e: if e.errno == 17: # file exists pass local_graphics_port = self.session_info.graphics_port - local_graphics_port = utils.detect_unused_port(bind_address='', preferred_port=local_graphics_port) - self.fw_tunnel = forward.start_forward_tunnel(local_port=local_graphics_port, - remote_port=self.session_info.graphics_port, - ssh_transport=self.ssh_transport, + try: + if self.ssh_transport.getpeername()[0] in ('::1', '127.0.0.1', 'localhost', 'localhost.localdomain'): + local_graphics_port += 10000 + except socket.error: + raise x2go_exceptions.X2GoControlSessionException('The control session has died unexpectedly.') + local_graphics_port = utils.detect_unused_port(preferred_port=local_graphics_port) + + self.fw_tunnel = forward.start_forward_tunnel(local_port=local_graphics_port, + remote_port=self.session_info.graphics_port, + ssh_transport=self.ssh_transport, session_instance=self.session_instance, + session_name=self.session_name, + subsystem=self.subsystem, logger=self.logger, ) # update the proxy port in PROXY_ARGS self._update_local_proxy_socket(local_graphics_port) - cmd_line = self._generate_cmdline() self.session_log_stdout = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a') self.session_log_stderr = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a') - self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) _stdin = None _shell = False @@ -203,6 +209,10 @@ # allow inheriting classes to do something with backend specific proxy_options... self.process_proxy_options() + # if everything is in place, generate the command line for the subprocess call + cmd_line = self._generate_cmdline() + self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) + while not self.proxy: gevent.sleep(.2) p = self.proxy = subprocess.Popen(cmd_line, @@ -213,7 +223,7 @@ shell=_shell) while self._keepalive: - gevent.sleep(.2) + gevent.sleep(1) if _X2GOCLIENT_OS == 'Windows': _stdin.close() @@ -227,9 +237,11 @@ except WindowsError: pass + self._tidy_up() + def process_proxy_options(self): """\ - Override this method to incorporate elements from C{proxy_options} + Override this method to incorporate elements from C{proxy_options} into actual proxy subprocess execution. This method (if overridden) should (by design) never fail nor raise an exception. @@ -239,7 +251,7 @@ 1. remove processed proxy_options from self.proxy_options 2. once you have finished processing the proxy_options call - the parent class method X2goProxyBASE.process_proxy_options() + the parent class method L{x2go.backends.proxy.base.X2GoProxy.process_proxy_options()} """ # do the logging of remaining options @@ -257,7 +269,7 @@ Start the thread runner and wait for the proxy to come up. @return: a subprocess instance that knows about the externally started proxy command. - @rtype: C{instance} + @rtype: C{obj} """ threading.Thread.start(self) @@ -270,22 +282,24 @@ self.logger('waiting for proxy to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG) gevent.sleep(.4) - # also wait for fw_tunnel to become active - _count = 0 - _maxwait = 40 - while not self.fw_tunnel.is_active and _count < _maxwait: - _count += 1 - self.logger('waiting for port fw tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG) - gevent.sleep(.5) + if self.proxy: + + # also wait for fw_tunnel to become active + _count = 0 + _maxwait = 40 + while self.fw_tunnel and (not self.fw_tunnel.is_active) and (not self.fw_tunnel.failed) and (_count < _maxwait): + _count += 1 + self.logger('waiting for port fw tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG) + gevent.sleep(.5) - return self.proxy + return self.proxy, bool(self.proxy) and (self.fw_tunnel and self.fw_tunnel.is_active) def ok(self): """\ Check if a proxy instance is up and running. - @return: Proxy state (C{True} or C{False}) + @return: Proxy state, C{True} for proxy being up-and-running, C{False} otherwise @rtype C{bool} """ - return bool(self.proxy and self.proxy.poll() is None) + return bool(self.proxy and self.proxy.poll() is None) and self.fw_tunnel.is_active diff -Nru python-x2go-0.1.1.8/x2go/backends/proxy/__init__.py python-x2go-0.5.0.6/x2go/backends/proxy/__init__.py --- python-x2go-0.1.1.8/x2go/backends/proxy/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/proxy/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -from x2go.defaults import BACKEND_PROXY_DEFAULT - -from _nx3 import X2goProxyNX3 - -X2goProxy = eval(BACKEND_PROXY_DEFAULT) diff -Nru python-x2go-0.1.1.8/x2go/backends/proxy/_nx3.py python-x2go-0.5.0.6/x2go/backends/proxy/_nx3.py --- python-x2go-0.1.1.8/x2go/backends/proxy/_nx3.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/proxy/_nx3.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goProxy classes - proxying your connection through NX3 and others. - -""" -__NAME__ = 'x2goproxynx3-pylib' - -# modules -import gevent -import os -import sys -import types -import time -import copy -import threading - -# Python X2go modules -import x2go.forward as forward -import x2go.utils as utils -import x2go.log as log -import base - -from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS - -class X2goProxyNX3(base.X2goProxyBASE): - """\ - X2goNX3Proxy is a NX version 3 based X2go proxy connection class. - - It basically fills L{X2goProxyBASE} variables with sensible content. Its - methods mostly wrap around the corresponding methods of the parent class. - - """ - def __init__(self, *args, **kwargs): - """\ - For available parameters refer to L{X2goProxyBASE} class documentation. - - """ - base.X2goProxyBASE.__init__(self, *args, **kwargs) - - # setting some default environment variables, nxproxy paths etc. - if _X2GOCLIENT_OS == "Windows": - _nxproxy_paths = [ - os.path.join(os.environ["ProgramFiles"], os.path.normpath("PyHoca-GUI/nxproxy/nxproxy.exe")), - os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe")), - os.path.join(os.environ["ProgramFiles"], os.path.normpath("NX Client for Windows/bin/nxproxy.exe")), - ] - if os.environ.has_key('NXPROXY_BINARY'): - _nxproxy_paths.insert(0, os.environ['NXPROXY_BINARY']) - for _nxproxy_cmd in _nxproxy_paths: - if os.path.exists(_nxproxy_cmd): - break - self.PROXY_CMD = _nxproxy_cmd - else: - self.PROXY_CMD = "/usr/bin/nxproxy" - self.PROXY_ENV.update({ - "NX_CLIENT": "/bin/true", - "NX_ROOT": self.sessions_rootdir - }) - self.PROXY_MODE = '-S' - if _X2GOCLIENT_OS == "Windows": - self.PROXY_OPTIONS = [ - "nx/nx" , - "retry=5", - "composite=1", - "connect=127.0.0.1", - "clipboard=1", - "cookie=%s" % self.session_info.cookie, - "port=%d" % self.session_info.graphics_port, - "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_log, ), - ] - else: - self.PROXY_OPTIONS = [ - "nx/nx" , - "retry=5", - "composite=1", - "connect=127.0.0.1", - "clipboard=1", - "cookie=%s" % self.session_info.cookie, - "port=%d" % self.session_info.graphics_port, - "errors=%s" % os.path.join(self.session_info.local_container, self.session_log, ), - ] - - self.PROXY_DISPLAY = self.session_info.display - - def _update_local_proxy_socket(self, port): - for idx, a in enumerate(self.PROXY_OPTIONS): - if a.startswith('port='): - self.PROXY_OPTIONS[idx] = 'port=%s' % port - - def _generate_cmdline(self): - - if _X2GOCLIENT_OS == "Windows": - _options_filename = os.path.join(self.session_info.local_container, 'options') - options = open(_options_filename, 'w') - options.write('%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)) - options.close() - self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join("\\", "..", "S-%s" % self.session_info.name, 'options'), ] - - cmd_line = [ self.PROXY_CMD, ] - cmd_line.append(self.PROXY_MODE) - _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY) - cmd_line.append(_proxy_options) - return cmd_line - - def process_proxy_options(self): - - if self.proxy_options.has_key('defkeymap') and self.proxy_options['defkeymap']: - - # first: draw in xkb_rules_names from xprop output - xkb_rules_names = utils.xkb_rules_names() - _proxy_options = { - 'xkbrules': xkb_rules_names['rules'], - 'xkbmodel': xkb_rules_names['model'], - 'xkblayout': xkb_rules_names['layout'], - 'xkbvariant': xkb_rules_names['variant'], - 'xkboptions': xkb_rules_names['options'], - } - - # merge self.proxy_options into the proxy_options we obtained from xprop - _proxy_options.update(self.proxy_options) - self.proxy_options = _proxy_options - - del self.proxy_options['defkeymap'] - - # create keyboard file - _keyboard = """\ -rules=%s -model=%s -layout=%s -variant=%s -options=%s""" % ( self.proxy_options['xkbrules'], - self.proxy_options['xkbmodel'], - self.proxy_options['xkblayout'], - self.proxy_options['xkbvariant'], - self.proxy_options['xkboptions'], ) - - # remove processed options from self.process_options - del self.proxy_options['xkbrules'] - del self.proxy_options['xkbmodel'] - del self.proxy_options['xkblayout'] - del self.proxy_options['xkbvariant'] - del self.proxy_options['xkboptions'] - - # write the keyboard file into the remote session directory - self.session_instance.control_session._x2go_sftp_write('%s/keyboard' % self.session_info.remote_container, _keyboard) - - # run the base variant of this method (basically for logging of ignored process_options) - base.X2goProxyBASE.process_proxy_options(self) - - def start_proxy(self): - self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO) - self.logger('NX3 Proxy mode is server, cookie=%s, host=127.0.0.1, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG) - self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG) - - p = base.X2goProxyBASE.start_proxy(self) - - if self.ok(): - self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO) - else: - self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR) - - return p diff -Nru python-x2go-0.1.1.8/x2go/backends/proxy/nx3.py python-x2go-0.5.0.6/x2go/backends/proxy/nx3.py --- python-x2go-0.1.1.8/x2go/backends/proxy/nx3.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/proxy/nx3.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2GoProxy classes - proxying your connection through NX3 and others. + +""" +__NAME__ = 'x2goproxynx3-pylib' + +# modules +import os + +# Python X2Go modules +import x2go.log as log +import x2go.backends.proxy.base as base + +from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS + +class X2GoProxy(base.X2GoProxy): + """\ + This L{X2GoProxy} class is a NX version 3 based X2Go proxy connection class. + + It basically fills L{x2go.backends.proxy.base.X2GoProxy} variables with sensible content. Its + methods mostly wrap around the corresponding methods of the parent class. + + """ + def __init__(self, *args, **kwargs): + """\ + For available parameters refer to L{x2go.backends.proxy.base.X2GoProxy} class documentation. + + """ + base.X2GoProxy.__init__(self, *args, **kwargs) + self.subsystem = 'NX Proxy' + + # setting some default environment variables, nxproxy paths etc. + if _X2GOCLIENT_OS == "Windows": + _nxproxy_paths = [ + os.path.join(os.environ["ProgramFiles"], os.path.normpath("PyHoca-GUI/nxproxy/nxproxy.exe")), + os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe")), + os.path.join(os.environ["ProgramFiles"], os.path.normpath("NX Client for Windows/bin/nxproxy.exe")), + os.path.normpath("../pyhoca-contrib/mswin/nxproxy-mswin/nxproxy-3.5.0.27_cygwin-2015-10-18/nxproxy.exe"), + ] + if os.environ.has_key('NXPROXY_BINARY'): + _nxproxy_paths.insert(0, os.environ['NXPROXY_BINARY']) + for _nxproxy_cmd in _nxproxy_paths: + if os.path.exists(_nxproxy_cmd): + break + self.PROXY_CMD = _nxproxy_cmd + else: + self.PROXY_CMD = "/usr/bin/nxproxy" + self.PROXY_ENV.update({ + "NX_CLIENT": "/bin/true", + "NX_ROOT": self.sessions_rootdir + }) + self.PROXY_MODE = '-S' + if _X2GOCLIENT_OS == "Windows": + self.PROXY_OPTIONS = [ + "nx/nx" , + "retry=5", + "composite=1", + "connect=127.0.0.1", + "clipboard=1", + "cookie=%s" % self.session_info.cookie, + "port=%s" % self.session_info.graphics_port, + "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_errors, ), + ] + else: + self.PROXY_OPTIONS = [ + "nx/nx" , + "retry=5", + "composite=1", + "connect=127.0.0.1", + "clipboard=1", + "cookie=%s" % self.session_info.cookie, + "port=%s" % self.session_info.graphics_port, + "errors=%s" % os.path.join(self.session_info.local_container, self.session_errors, ), + ] + + self.PROXY_DISPLAY = self.session_info.display + + def _update_local_proxy_socket(self, port): + """\ + Update the local proxy socket on port changes due to already-bound-to local TCP/IP port sockets. + + @param port: new local TCP/IP socket port + @type port: C{int} + + """ + + for idx, a in enumerate(self.PROXY_OPTIONS): + if a.startswith('port='): + self.PROXY_OPTIONS[idx] = 'port=%s' % port + + def _generate_cmdline(self): + """\ + Generate the NX proxy command line for execution. + + """ + if _X2GOCLIENT_OS == "Windows": + _options_filename = os.path.join(self.session_info.local_container, 'options') + options = open(_options_filename, 'w') + options.write('%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)) + options.close() + self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join("\\", "..", "S-%s" % self.session_info.name, 'options'), ] + + cmd_line = [ self.PROXY_CMD, ] + cmd_line.append(self.PROXY_MODE) + _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY) + cmd_line.append(_proxy_options) + return cmd_line + + def process_proxy_options(self): + base.X2GoProxy.process_proxy_options(self) + + def start_proxy(self): + """\ + Start the thread runner and wait for the proxy to come up. + + @return: a subprocess instance that knows about the externally started proxy command. + @rtype: C{obj} + + """ + self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO) + self.logger('NX3 Proxy mode is server, cookie=%s, host=127.0.0.1, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG) + self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG) + + p, p_ok = base.X2GoProxy.start_proxy(self) + + if self.ok(): + self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO) + else: + self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR) + + return p, self.ok() diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/_file.py python-x2go-0.5.0.6/x2go/backends/settings/_file.py --- python-x2go-0.1.1.8/x2go/backends/settings/_file.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/_file.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goClientSettings class - managing x2goclient settings file (incl. LDAP-Support). - -The L{X2goClientSettings} class one of Python X2go's a public API classes. -Use this class (indirectly by retrieving it from an L{X2goClient} instance) -in your Python X2go based applications to access the -»settings« configuration file of your X2go client application. - -""" -__NAME__ = 'x2gosettings-pylib' - -# modules -import os - -# Python X2go modules -import x2go.log as log -from x2go.defaults import X2GO_SETTINGS_CONFIGFILES as _X2GO_SETTINGS_CONFIGFILES -from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS -import x2go.inifiles as inifiles - - -class X2goClientSettingsFILE(inifiles.X2goIniFile): - """\ - Configuration file based settings for X2goClient instances. - - """ - defaultValues = _X2GO_CLIENTSETTINGS_DEFAULTS - - def __init__(self, config_files=_X2GO_SETTINGS_CONFIGFILES, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - Constructs an L{X2goClientSettings} instance. This is normally done by an L{X2goClient} instance. - You can retrieve this L{X2goClientSettings} instance with the L{X2goClient.get_client_settings()} - method. - - On construction the L{X2goClientSettings} object is filled with values from the configuration files:: - - /etc/x2goclient/settings - ~/.x2goclient/settings - - The files are read in the specified order and config options of both files are merged. Options - set in the user configuration file (C{~/.x2goclient/settings}) override global options set in - C{/etc/x2goclient/settings}. - - """ - inifiles.X2goIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/file.py python-x2go-0.5.0.6/x2go/backends/settings/file.py --- python-x2go-0.1.1.8/x2go/backends/settings/file.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/file.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2GoClientSettings class - managing x2goclient settings file. + +The L{X2GoClientSettings} class one of Python X2Go's a public API classes. +Use this class (indirectly by retrieving it from an L{X2GoClient} instance) +in your Python X2Go based applications to access the +»settings« configuration file of your X2Go client application. + +""" +__NAME__ = 'x2gosettings-pylib' + +# Python X2Go modules +import x2go.log as log +from x2go.defaults import X2GO_SETTINGS_CONFIGFILES as _X2GO_SETTINGS_CONFIGFILES +from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS +import x2go.inifiles as inifiles + + +class X2GoClientSettings(inifiles.X2GoIniFile): + """\ + Configuration file based settings for L{X2GoClient} instances. + + """ + def __init__(self, config_files=_X2GO_SETTINGS_CONFIGFILES, defaults=_X2GO_CLIENTSETTINGS_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Constructs an L{X2GoClientSettings} instance. This is normally done from within an L{X2GoClient} instance. + You can retrieve this L{X2GoClientSettings} instance with the L{X2GoClient.get_client_settings()} + method. + + On construction the L{X2GoClientSettings} object is filled with values from the configuration files:: + + /etc/x2goclient/settings + ~/.x2goclient/settings + + The files are read in the specified order and config options of both files are merged. Options + set in the user configuration file (C{~/.x2goclient/settings}) override global options set in + C{/etc/x2goclient/settings}. + + """ + inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/_gconf.py python-x2go-0.5.0.6/x2go/backends/settings/_gconf.py --- python-x2go-0.1.1.8/x2go/backends/settings/_gconf.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/_gconf.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goClientSettings class - managing x2goclient settings file (incl. LDAP-Support). - -The L{X2goClientSettings} class one of Python X2go's a public API classes. -Use this class (indirectly by retrieving it from an L{X2goClient} instance) -in your Python X2go based applications to access the -»settings« configuration file of your X2go client application. - -""" -__NAME__ = 'x2gosettings-pylib' - -# modules -import os - -# Python X2go modules -import x2go.log as log -from x2go.defaults import X2GO_SETTINGS_CONFIGFILES as _X2GO_SETTINGS_CONFIGFILES -from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS -import x2go.inifiles as inifiles - - -class X2goClientSettingsGCONF(inifiles.X2goIniFile): - """\ - Configuration file based settings for X2goClient instances. - - """ - defaultValues = _X2GO_CLIENTSETTINGS_DEFAULTS - - def __init__(self, config_files=_X2GO_SETTINGS_CONFIGFILES, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - Constructs an L{X2goClientSettings} instance. This is normally done by an L{X2goClient} instance. - You can retrieve this L{X2goClientSettings} instance with the L{X2goClient.get_client_settings()} - method. - - On construction the L{X2goClientSettings} object is filled with values from the configuration files:: - - /etc/x2goclient/settings - ~/.x2goclient/settings - - The files are read in the specified order and config options of both files are merged. Options - set in the user configuration file (C{~/.x2goclient/settings}) override global options set in - C{/etc/x2goclient/settings}. - - """ - raise X2goNotImplementedYetException('GCONF backend support is not implemented yet') - diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/gconf.py python-x2go-0.5.0.6/x2go/backends/settings/gconf.py --- python-x2go-0.1.1.8/x2go/backends/settings/gconf.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/gconf.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2GoClientSettings class - managing x2goclient settings file. + +The L{X2GoClientSettings} class one of Python X2Go's a public API classes. +Use this class (indirectly by retrieving it from an L{X2GoClient} instance) +in your Python X2Go based applications to access the +»settings« configuration file of your X2Go client application. + +""" +__NAME__ = 'x2gosettings-pylib' + +# modules +import copy + +# Python X2Go modules +import x2go.log as log +from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoClientSettings(object): + """\ + Configure settings for L{X2GoClient} instances with the GConf daemon. + + """ + defaultValues = copy.deepcopy(_X2GO_CLIENTSETTINGS_DEFAULTS) + + def __init__(self, defaults=_X2GO_CLIENTSETTINGS_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Constructs an L{X2GoClientSettings} instance. This is normally done from within an L{X2GoClient} instance. + You can retrieve this L{X2GoClientSettings} instance with the L{X2GoClient.get_client_settings()} + method. + + On construction the L{X2GoClientSettings} object is filled with values as found in GConf:: + + + + """ + raise X2GoNotImplementedYetException('GCONF backend support is not implemented yet') + diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/__init__.py python-x2go-0.5.0.6/x2go/backends/settings/__init__.py --- python-x2go-0.1.1.8/x2go/backends/settings/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,27 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -from x2go.defaults import BACKEND_CLIENTSETTINGS_DEFAULT - -from _file import X2goClientSettingsFILE -from _gconf import X2goClientSettingsGCONF -from _winreg import X2goClientSettingsWINREG - -X2goClientSettings = eval(BACKEND_CLIENTSETTINGS_DEFAULT) - diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/_winreg.py python-x2go-0.5.0.6/x2go/backends/settings/_winreg.py --- python-x2go-0.1.1.8/x2go/backends/settings/_winreg.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/_winreg.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goClientSettings class - managing x2goclient settings file (incl. LDAP-Support). - -The L{X2goClientSettings} class one of Python X2go's a public API classes. -Use this class (indirectly by retrieving it from an L{X2goClient} instance) -in your Python X2go based applications to access the -»settings« configuration file of your X2go client application. - -""" -__NAME__ = 'x2gosettings-pylib' - -# modules -import os - -# Python X2go modules -import x2go.log as log -from x2go.defaults import X2GO_SETTINGS_CONFIGFILES as _X2GO_SETTINGS_CONFIGFILES -from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS -import x2go.inifiles as inifiles - - -class X2goClientSettingsWINREG(inifiles.X2goIniFile): - """\ - Configuration file based settings for X2goClient instances. - - """ - defaultValues = _X2GO_CLIENTSETTINGS_DEFAULTS - - def __init__(self, config_files=_X2GO_SETTINGS_CONFIGFILES, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): - """\ - Constructs an L{X2goClientSettings} instance. This is normally done by an L{X2goClient} instance. - You can retrieve this L{X2goClientSettings} instance with the L{X2goClient.get_client_settings()} - method. - - On construction the L{X2goClientSettings} object is filled with values from the configuration files:: - - /etc/x2goclient/settings - ~/.x2goclient/settings - - The files are read in the specified order and config options of both files are merged. Options - set in the user configuration file (C{~/.x2goclient/settings}) override global options set in - C{/etc/x2goclient/settings}. - - """ - raise X2goNotImplementedYetException('WINREG backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/settings/winreg.py python-x2go-0.5.0.6/x2go/backends/settings/winreg.py --- python-x2go-0.1.1.8/x2go/backends/settings/winreg.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/settings/winreg.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2GoClientSettings class - managing x2goclient settings file (incl. LDAP-Support). + +The L{X2GoClientSettings} class one of Python X2Go's a public API classes. +Use this class (indirectly by retrieving it from an L{X2GoClient} instance) +in your Python X2Go based applications to access the +»settings« configuration file of your X2Go client application. + +""" +__NAME__ = 'x2gosettings-pylib' + +# modules +import copy + +# Python X2Go modules +import x2go.log as log +from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS +import x2go.inifiles as inifiles + +from x2go.x2go_exceptions import X2GoNotImplementedYetException + +class X2GoClientSettings(inifiles.X2GoIniFile): + """\ + Windows registry based settings for L{X2GoClient} instances. + + """ + defaultValues = copy.deepcopy(_X2GO_CLIENTSETTINGS_DEFAULTS) + + def __init__(self, defaults=_X2GO_CLIENTSETTINGS_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Constructs an L{X2GoClientSettings} instance. This is normally done from within an L{X2GoClient} instance. + You can retrieve this L{X2GoClientSettings} instance with the L{X2GoClient.get_client_settings()} + method. + + On construction the L{X2GoClientSettings} object is filled with values from the Windows registry:: + + + + + """ + raise X2GoNotImplementedYetException('WINREG backend support is not implemented yet') diff -Nru python-x2go-0.1.1.8/x2go/backends/terminal/__init__.py python-x2go-0.5.0.6/x2go/backends/terminal/__init__.py --- python-x2go-0.1.1.8/x2go/backends/terminal/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/terminal/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -from x2go.defaults import BACKEND_TERMINALSESSION_DEFAULT - -from _stdout import X2goTerminalSessionSTDOUT - -X2goTerminalSession = eval(BACKEND_TERMINALSESSION_DEFAULT) diff -Nru python-x2go-0.1.1.8/x2go/backends/terminal/plain.py python-x2go-0.5.0.6/x2go/backends/terminal/plain.py --- python-x2go-0.1.1.8/x2go/backends/terminal/plain.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/terminal/plain.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,1765 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2GoTerminalSession class - core functions for handling your individual X2Go sessions. + +This backend handles X2Go server implementations that respond with session infos +via server-side PLAIN text output. + +""" +__NAME__ = 'x2goterminalsession-pylib' + +# modules +import os +import types +import gevent +import cStringIO +import copy +import shutil +import threading + +# Python X2Go modules +import x2go.rforward as rforward +import x2go.sftpserver as sftpserver +import x2go.printqueue as printqueue +import x2go.mimebox as mimebox +import x2go.telekinesis as telekinesis +import x2go.log as log +import x2go.defaults as defaults +import x2go.utils as utils +import x2go.x2go_exceptions as x2go_exceptions + +# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) +from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS +from x2go.defaults import LOCAL_HOME as _LOCAL_HOME +from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER +from x2go.defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR +from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR +from x2go.defaults import X2GO_GENERIC_APPLICATIONS as _X2GO_GENERIC_APPLICATIONS +from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS + +from x2go.defaults import BACKENDS as _BACKENDS + +_local_color_depth = utils.local_color_depth() + +def _rewrite_cmd(cmd, params=None): + """\ + Mechansim that rewrites X2Go server commands into something that gets understood by + the server-side script C{x2goruncommand}. + + @param cmd: the current command for execution (as found in the session profile parameter C{cmd}) + @type cmd: C{str} + @param params: an session paramter object + @type params: L{X2GoSessionParams} + + @return: the rewritten command for server-side execution + @rtype: C{str} + + """ + # start with an empty string + cmd = cmd or '' + + # find window manager commands + if cmd in _X2GO_DESKTOPSESSIONS.keys(): + cmd = _X2GO_DESKTOPSESSIONS[cmd] + + if (cmd == 'RDP') and (type(params) == X2GoSessionParams): + _depth = params.depth + if int(_depth) == 17: + _depth = 16 + if params.geometry == 'fullscreen': + cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, _depth) + else: + cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, _depth) + + # place quot marks around cmd if not empty string + if cmd: + cmd = '"%s"' % cmd + + if ((type(params) == X2GoSessionParams) and params.published_applications and cmd == ''): + cmd = 'PUBLISHED' + + return cmd + + +def _rewrite_blanks(cmd): + """\ + In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. + + @param cmd: command that has to be rewritten for passing to the server + @type cmd: C{str} + + @return: the command with blanks rewritten to ,,X2GO_SPACE_CHAR'' + @rtype: C{str} + + """ + # X2Go run command replace X2GO_SPACE_CHAR string with blanks + if cmd: + cmd = cmd.replace(" ", "X2GO_SPACE_CHAR") + return cmd + + +class X2GoSessionParams(object): + """\ + The L{X2GoSessionParams} class is used to store all parameters that + C{X2GoTerminalSession} backend objects are constructed with. + + """ + def rewrite_session_type(self): + """\ + Rewrite the X2Go session type, so that the X2Go server + can understand it (C{desktop} -> C{D}, etc.). + + Also if the object's C{command} property is a known window + manager, the session type will be set to 'D' + (i.e. desktop). + + @return: 'D' if session should probably a desktop session, + 'R' for rootless sessions, 'P' for sessions providing published + applications, and 'S' for desktop sharing sessions + @rtype: C{str} + + """ + cmd = self.cmd + published = self.published_applications + + if published and self.cmd in ('', 'PUBLISHED'): + self.session_type = 'P' + self.cmd = 'PUBLISHED' + else: + if cmd == 'RDP' or cmd.startswith('rdesktop') or cmd.startswith('xfreedrp'): + if self.geometry == 'fullscreen': self.session_type = 'D' + else: self.session_type = 'R' + elif cmd == 'XDMCP': + self.session_type = 'D' + elif cmd in _X2GO_DESKTOPSESSIONS.keys(): + self.session_type = 'D' + elif os.path.basename(cmd) in _X2GO_DESKTOPSESSIONS.values(): + self.session_type = 'D' + + if self.session_type in ("D", "desktop"): + self.session_type = 'D' + elif self.session_type in ("S", "shared", "shadow"): + self.session_type = 'S' + elif self.session_type in ("R", "rootless", "application"): + self.session_type = 'R' + elif self.session_type in ("P", "published", "published_applications"): + self.session_type = 'P' + + return self.session_type + + def update(self, **properties_to_be_updated): + """\ + Update all properties in the object L{X2GoSessionParams} object from + the passed on dictionary. + + @param properties_to_be_updated: a dictionary with L{X2GoSessionParams} + property names as keys und their values to be update in + L{X2GoSessionParams} object. + @type properties_to_be_updated: C{dict} + + """ + for key in properties_to_be_updated.keys(): + setattr(self, key, properties_to_be_updated[key] or '') + self.rewrite_session_type() + + +class X2GoTerminalSession(object): + """\ + Class for managing X2Go terminal sessions on a remote X2Go server via Paramiko/SSH. + + With the L{x2go.backends.terminal.plain.X2GoTerminalSession} class you can start new X2Go sessions, resume suspended + sessions or suspend resp. terminate currently running sessions on a + connected X2Go server. + + An L{x2go.backends.terminal.plain.X2GoTerminalSession} object uses two main data structure classes: + + - L{X2GoSessionParams}: stores all parameters that have been passed to the + constructor method. + + - C{X2GoServerSessionInfo*} backend class: when starting or resuming a session, an object of this class + will be used to store all information retrieved from the X2Go server. + + The terminal session instance works closely together (i.e. depends on) a connected control + session instance (e.g. L{x2go.backends.control.plain.X2GoControlSession}). You never should use either of them as a standalone + instance. Both, control session and terminal session(s) get managed/controlled via L{X2GoSession} instances. + + """ + def __init__(self, control_session, session_info=None, + geometry="800x600", depth=_local_color_depth, link="adsl", pack="16m-jpeg-9", dpi='', + cache_type="unix-kde", + kbtype='null/null', kblayout='null', kbvariant='null', + clipboard='both', + session_type="application", snd_system='pulse', snd_port=4713, cmd=None, + published_applications=False, + set_session_title=False, session_title="", applications=[], + rdp_server=None, rdp_options=None, + xdmcp_server=None, + convert_encoding=False, server_encoding='UTF-8', client_encoding='UTF-8', + rootdir=None, + profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), + print_action=None, print_action_args={}, + info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], + list_backend=_BACKENDS['X2GoServerSessionList']['default'], + proxy_backend=_BACKENDS['X2GoProxy']['default'], proxy_options={}, + printing_backend=_BACKENDS['X2GoClientPrinting']['default'], + client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), + sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), + session_instance=None, + logger=None, loglevel=log.loglevel_DEFAULT): + """\ + Initialize an X2Go session. With the L{x2go.backends.terminal.plain.X2GoTerminalSession} class you can start + new X2Go sessions, resume suspended sessions or suspend resp. terminate + currently running sessions on a connected X2Go server. + + @param geometry: screen geometry of the X2Go session. Can be either C{x}, + C{maximize} or C{fullscreen} + @type geometry: C{str} + @param depth: color depth in bits (common values: C{16}, C{24}) + @type depth: C{int} + @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan}) + @type link: C{str} + @param pack: compression method for NX based session proxying + @type pack: C{str} + @param dpi: dots-per-inch value for the session screen (has an impact on the font size on screen) + @type dpi: C{str} + @param cache_type: a dummy parameter that is passed to the L{x2go.backends.proxy.base.X2GoProxy}. In NX Proxy + (class C{X2GoProxyNX3}) this originally is the session name. With X2Go it + defines the name of the NX cache directory. Best is to leave it untouched. + @type cache_type: C{str} + @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ... + @type kbtype: C{str} + @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ... + @type kblayout: C{str} + @param kbvariant: keyboard variant, e.g. C{nodeadkeys} (for C{de} layout), C{intl} (for C{us} layout), etc. + @type kbvariant: C{str} + @param clipboard: clipboard mode (C{both}: bidirectional copy+paste, C{server}: copy+paste from server to + client, C{client}: copy+paste from client to server, C{none}: disable clipboard completely + @type clipboard: C{str} + @param session_type: either C{desktop}, C{application} (rootless session) or C{shared} + @type session_type: C{str} + @param snd_system: sound system to be used on server (C{none}, C{pulse} (default), + C{arts} (obsolete) or C{esd}) + @type snd_system: C{str} + @param snd_port: local sound port for network capable audio system + @type snd_port: C{int} + @param cmd: command to be run on X2Go server after session start (only used + when L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} is called, ignored on resume, suspend etc. + @type cmd: C{str} + @param published_applications: session is published applications provider + @type published_applications: C{bool} + @param set_session_title: modify the session title (i.e. the Window title) of desktop or shared desktop sessions + @type set_session_title: C{bool} + @param session_title: session title for this (desktop or shared desktop) session + @type session_title: C{str} + @param applications: applications available for rootless application execution + @type applications: C{list} + @param rdp_server: host name of server-side RDP server + @type rdp_server: C{str} + @param rdp_options: options for the C{rdesktop} command executed on the X2Go server (RDP proxy mode of X2Go) + @type rdp_options: C{str} + @param xdmcp_server: XDMCP server to connect to + @type xdmcp_server: C{str} + @param convert_encoding: convert file system encodings between server and client (for client-side shared folders) + @type convert_encoding: C{bool} + @param server_encoding: server-side file system / session encoding + @type server_encoding: C{str} + @param client_encoding: client-side file system encoding (if client-side is MS Windows, this parameter gets overwritten to WINDOWS-1252) + @type client_encoding: C{str} + @param rootdir: X2Go session directory, normally C{~/.x2go} + @type rootdir: C{str} + @param profile_name: the session profile name for this terminal session + @type profile_name: C{str} + @param profile_id: the session profile ID for this terminal session + @type profile_id: C{str} + @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the + resp. C{X2GoPrintActionXXX} class (where XXX equals one of the given short names) + @type print_action: C{str} or C{class} + @param print_action_args: optional arguments for a given print_action (for further info refer to + L{X2GoPrintActionPDFVIEW}, L{X2GoPrintActionPDFSAVE}, L{X2GoPrintActionPRINT} and L{X2GoPrintActionPRINTCMD}) + @type print_action_args: dict + @param info_backend: backend for handling storage of server session information + @type info_backend: C{X2GoServerSessionInfo*} instance + @param list_backend: backend for handling storage of session list information + @type list_backend: C{X2GoServerSessionList*} instance + @param proxy_backend: backend for handling the X-proxy connections + @type proxy_backend: C{X2GoProxy*} instance + @param proxy_options: a set of very C{X2GoProxy} backend specific options; any option that is not known + to the C{X2GoProxy} backend will simply be ignored + @type proxy_options: C{dict} + @param client_rootdir: client base dir (default: ~/.x2goclient) + @type client_rootdir: C{str} + @param sessions_rootdir: sessions base dir (default: ~/.x2go) + @type sessions_rootdir: C{str} + @param session_instance: the L{X2GoSession} instance that is parent to this terminal session + @type session_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{x2go.backends.terminal.plain.X2GoTerminalSession} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + self.proxy = None + self.proxy_subprocess = None + self.proxy_options = proxy_options + + self.telekinesis_client = None + + self.active_threads = [] + self.reverse_tunnels = {} + + self.print_queue = None + self.mimebox_queue = None + + if logger is None: + self.logger = log.X2GoLogger(loglevel=loglevel) + else: + self.logger = copy.deepcopy(logger) + self.logger.tag = __NAME__ + + self.control_session = control_session + self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels + + self.client_rootdir = client_rootdir + self.sessions_rootdir = sessions_rootdir + + self.params = X2GoSessionParams() + + self.params.geometry = str(geometry) + self.params.link = str(link) + self.params.pack = str(pack) + self.params.dpi = str(dpi) + self.params.cache_type = str(cache_type) + self.params.session_type = str(session_type) + self.params.kbtype = str(kbtype) + self.params.kblayout = str(kblayout) + self.params.kbvariant = str(kbvariant) + self.params.snd_system = str(snd_system) + self.params.cmd = str(cmd) + self.params.depth = str(depth) + self.params.clipboard = str(clipboard) + + self.params.published_applications = published_applications + self.published_applications = published_applications + + self.params.rdp_server = str(rdp_server) + self.params.rdp_options = str(rdp_options) + self.params.xdmcp_server = str(xdmcp_server) + + self.params.convert_encoding = convert_encoding + self.params.client_encoding = str(client_encoding) + self.params.server_encoding = str(server_encoding) + + self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or self.sessions_rootdir + self.params.update() + + self.profile_name = profile_name + self.set_session_title = set_session_title + self.session_title = session_title + self.session_window = None + self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") + + self.snd_port = snd_port + self.print_action = print_action + self.print_action_args = print_action_args + self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") + self.session_instance = session_instance + if self.session_instance: + self.client_instance = self.session_instance.client_instance + else: + self.client_instance = None + + self._share_local_folder_busy = False + self._mk_sessions_rootdir(self.params.rootdir) + + self.session_info = session_info + if self.session_info is not None: + if self.session_info.name: + self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) + else: + raise x2go_exceptions.X2GoTerminalSessionException('no valid session info availble') + else: + self.session_info = info_backend() + + self._share_local_folder_lock = threading.Lock() + self._cleaned_up = False + + self.telekinesis_subprocess = None + + def __del__(self): + """\ + Tidy up if terminal session gets destructed. + + """ + self._x2go_tidy_up() + + def _x2go_tidy_up(self): + """\ + Tidy up this terminal session... + - shutdown all forwarding and reverse forwarding tunnels + - shutdown the print queue (if running) + - shutdown the MIME box queue (if running) + - clear the session info + + """ + if self._share_local_folder_lock.locked(): + self._share_local_folder_lock.release() + self.release_telekinesis() + self.release_proxy() + self.session_window = None + self.update_session_window_file() + + try: + + if self.control_session.get_transport() is not None: + try: + for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]: + if _tunnel is not None: + _tunnel.__del__() + except KeyError: + pass + + if self.print_queue is not None: + self.print_queue.__del__() + + if self.mimebox_queue is not None: + self.mimebox_queue.__del__() + + except AttributeError: + pass + + self.session_info.clear() + + def _mk_sessions_rootdir(self, rootdir): + """\ + Create the server-side session root dir (normally ~/.x2go). + + @param rootdir: server-side session root directory + @type rootdir: C{str} + + """ + try: + os.makedirs(rootdir) + except OSError, e: + if e.errno == 17: + # file exists + pass + else: + raise OSError, e + + def _rm_session_dirtree(self): + """\ + Purge client-side session dir (session cache directory). + + """ + if self.session_info.name: + shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True) + + def _rm_desktop_dirtree(self): + """\ + Purge client-side session dir (C- directory) + + """ + if self.session_info.display: + shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True) + + def get_session_name(self): + """\ + Retrieve the X2Go session's name from the session info object. + + @return: the session name + @rtype: C{str} + + """ + return self.session_info.name + + def get_session_info(self): + """\ + Retrieve the X2Go session's session info object. + + @return: the session info object + @rtype: C{X2GoServerSessionInfo*} + + """ + return self.session_info + + def get_session_cmd(self): + """\ + Retrieve the X2Go session's command as stored in the session parameter object. + + @return: the session command + @rtype: C{str} + + """ + return self.params.cmd + + def get_session_type(self): + """\ + Retrieve the X2Go session's session type as stored in the session parameter object. + + @return: the session type + @rtype: C{str} + + """ + return self.params.session_type + + def start_sound(self): + """\ + Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound. + + Currently supported audio protocols: + + - PulseAudio + - Esound (not tested very much) + + @raise X2GoControlSessionException: if the control session of this terminal session is not connected + + """ + _tunnel = None + if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: + if self.params.snd_system == 'pulse': + self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO) + ### + ### PULSEAUDIO + ### + cookie_filepath = None + if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)): + cookie_filepath = os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME) + elif os.path.exists(os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)): + cookie_filepath = os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME) + if cookie_filepath is not None: + # setup pulse client config file on X2Go server + cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ + "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + self.control_session._x2go_sftp_put(local_path=cookie_filepath, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) + + # start reverse SSH tunnel for pulse stream + _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, + remote_host='127.0.0.1', + remote_port=self.snd_port, + ssh_transport=self.control_session.get_transport(), + session_instance=self.session_instance, + logger=self.logger + ) + else: + if self.client_instance: + self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name) + elif self.params.snd_system == 'arts': + ### + ### ARTSD AUDIO + ### + self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARN) + + elif self.params.snd_system == 'esd': + ### + ### ESD AUDIO + ### + + self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO) + self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) + + # start reverse SSH tunnel for pulse stream + _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, + remote_host='127.0.0.1', + remote_port=self.snd_port, + ssh_transport=self.control_session.get_transport(), + session_instance=self.session_instance, + logger=self.logger + ) + + + if _tunnel is not None: + self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) + _tunnel.start() + self.active_threads.append(_tunnel) + + else: + # tunnel has already been started and might simply need a resume call + self.reverse_tunnels[self.session_info.name]['snd'][1].resume() + + def start_sshfs(self): + """\ + Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. + + """ + if not self.control_session.is_sshfs_available(): + raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username) + + # start reverse SSH tunnel for sshfs (folder sharing, printing) + ssh_transport = self.control_session.get_transport() + if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: + + _tunnel = sftpserver.X2GoRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, + ssh_transport=ssh_transport, + auth_key=self.control_session._x2go_session_auth_rsakey, + session_instance=self.session_instance, + logger=self.logger + ) + + if _tunnel is not None: + self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) + _tunnel.start() + self.active_threads.append(_tunnel) + while not _tunnel.ready: + gevent.sleep(.1) + + else: + # tunnel has already been started and might simply need a resume call + self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume() + + def _x2go_pause_rev_fw_tunnel(self, name): + """\ + Pause reverse SSH tunnel of name . + + @param name: tunnel name (either of C{sshfs}, C{snd}) + @type name: C{str} + + """ + _tunnel = self.reverse_tunnels[self.session_info.name][name][1] + if _tunnel is not None: + _tunnel.pause() + + def stop_sound(self): + """\ + Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound. + + """ + self._x2go_pause_rev_fw_tunnel('snd') + + def stop_sshfs(self): + """\ + Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. + + """ + self._x2go_pause_rev_fw_tunnel('sshfs') + + def start_printing(self): + """\ + Initialize X2Go print spooling. + + @raise X2GoUserException: if the X2Go printing feature is not available to this user + + """ + if not self.control_session.is_sshfs_available(): + raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username) + + spool_dir = os.path.join(self.session_info.local_container, 'spool') + if not os.path.exists(spool_dir): + os.makedirs(spool_dir) + self.share_local_folder(local_path=spool_dir, folder_type='spool') + self.print_queue = printqueue.X2GoPrintQueue(profile_name=self.profile_name, + session_name=self.session_info.name, + spool_dir=spool_dir, + print_action=self.print_action, + print_action_args=self.print_action_args, + client_instance=self.client_instance, + printing_backend=self.printing_backend, + logger=self.logger, + ) + self.print_queue.start() + self.active_threads.append(self.print_queue) + + def set_print_action(self, print_action, **kwargs): + """\ + Set a print action for the next incoming print jobs. + + This method is a wrapper for L{X2GoPrintQueue}C{.set_print_action()}. + + @param print_action: print action name or object (i.e. an instance of C{X2GoPrintAction*} classes) + @type print_action: C{str} or C{X2GoPrintAction*} + @param kwargs: print action specific parameters + @type kwargs: dict + + """ + self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs) + + def stop_printing(self): + """\ + Shutdown (pause) the X2Go Print Queue thread. + + """ + if self.print_queue is not None: + self.print_queue.pause() + + def get_printing_spooldir(self): + """\ + Return the server-side printing spooldir path. + + @return: the directory for remote print job spooling + @rtype: C{str} + + """ + return '%s/%s' % (self.session_info.remote_container, 'spool') + + def start_mimebox(self, mimebox_extensions=[], mimebox_action=None): + """\ + Initialize the X2Go MIME box. Open/process incoming files from the server-side locally. + + @param mimebox_extensions: file name extensions that are allowed for local opening/processing + @type mimebox_extensions: C{list} + @param mimebox_action: MIME box action given as name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes). + @type mimebox_action: C{str} or C{obj} + + @raise X2GoUserException: if the X2Go MIME box feature is not available to this user + + """ + if not self.control_session.is_sshfs_available(): + raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username) + + mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox') + if not os.path.exists(mimebox_dir): + os.makedirs(mimebox_dir) + self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox') + self.mimebox_queue = mimebox.X2GoMIMEboxQueue(profile_name=self.profile_name, + session_name=self.session_info.name, + mimebox_dir=mimebox_dir, + mimebox_extensions=mimebox_extensions, + mimebox_action=mimebox_action, + client_instance=self.client_instance, + logger=self.logger, + ) + self.mimebox_queue.start() + self.active_threads.append(self.mimebox_queue) + + def set_mimebox_action(self, mimebox_action, **kwargs): + """\ + Set a MIME box action for the next incoming MIME jobs. + + This method is a wrapper for L{X2GoMIMEboxQueue}C{set_mimebox_action()}. + + @param mimebox_action: MIME box action name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes) + @type mimebox_action: C{str} or C{X2GoMIMEboxAction*} + @param kwargs: MIME box action specific parameters + @type kwargs: dict + + """ + self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs) + + def stop_mimebox(self): + """\ + Shutdown (pause) the X2Go MIME box Queue thread. + + """ + if self.mimebox_queue is not None: + self.mimebox_queue.pause() + + def get_mimebox_spooldir(self): + """\ + Return the server-side MIME box spooldir path. + + @return: the directory where remote MIME box jobs are placed + @rtype: C{str} + + """ + return '%s/%s' % (self.session_info.remote_container, 'mimebox') + + def start_telekinesis(self): + """\ + Initialize Telekinesis client for X2Go. + + """ + if self.telekinesis_client is not None: + del self.telekinesis_client + self.telekinesis_client = None + if self.telekinesis_subprocess is not None: + self.telekinesis_subprocess = None + if self.session_info.tekictrl_port != -1 and self.session_info.tekidata_port != -1: + self.telekinesis_client = telekinesis.X2GoTelekinesisClient(session_info=self.session_info, + ssh_transport=self.control_session.get_transport(), + sessions_rootdir=self.sessions_rootdir, + session_instance=self.session_instance, + logger=self.logger) + if self.telekinesis_client.has_telekinesis_client(): + self.telekinesis_subprocess, telekinesis_ok = self.telekinesis_client.start_telekinesis() + else: + del self.telekinesis_client + self.telekinesis_client = None + + def is_session_info_protected(self): + """\ + Test if this terminal's session info object is write-protected. + + @return: C{True}, if session info object is read-only, C{False} for read-write. + @rtype: C{bool} + + """ + self.session_info.is_protected() + + def session_info_protect(self): + """\ + Protect this terminal session's info object against updates. + + """ + self.session_info.protect() + + def session_info_unprotect(self): + """\ + Allow session info updates from within the list_sessions method of the control session. + + """ + self.session_info.unprotect() + + def share_local_folder(self, local_path=None, folder_type='disk'): + """\ + Share a local folder with the X2Go session. + + @param local_path: the full path to an existing folder on the local + file system + @type local_path: C{str} + @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), + 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling) + @type folder_type: C{str} + + @return: returns C{True} if the local folder has been successfully mounted within the X2Go server session + @rtype: C{bool} + + @raise X2GoUserException: if local folder sharing is not available to this user + @raise Exception: any other exception occuring on the way is passed through by this method + + """ + if not self.control_session.is_sshfs_available(): + raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username) + + if local_path is None: + self.logger('no folder name given...', log.loglevel_WARN) + return False + + if type(local_path) not in (types.StringType, types.UnicodeType): + self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) + return False + + if not os.path.exists(local_path): + self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN) + return False + + local_path = os.path.normpath(local_path) + self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO) + + _auth_rsakey = self.control_session._x2go_session_auth_rsakey + _host_rsakey = defaults.RSAHostKey + + _tmp_io_object = cStringIO.StringIO() + _auth_rsakey.write_private_key(_tmp_io_object) + _tmp_io_object.write('----BEGIN RSA IDENTITY----') + _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) + + # _x2go_key_fname must be a UniX path + _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) + _x2go_key_bundle = _tmp_io_object.getvalue() + + # if there is another call to this method currently being processed, wait for that one to finish + self._share_local_folder_lock.acquire() + + try: + self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) + + _convert_encoding = self.params.convert_encoding + _client_encoding = self.params.client_encoding + _server_encoding = self.params.server_encoding + + if _X2GOCLIENT_OS == 'Windows': + if local_path.startswith('\\\\'): + # we are on a UNC path + if 'X2GO_MOUNT_UNCPATHS' in self.control_session.get_server_features(): + local_path = local_path.repalce('\\\\', '/uncpath/') + else: + local_path = local_path.repalce('\\\\', '/windrive/') + local_path = local_path.replace('\\', '/') + else: + local_path = local_path.replace('\\', '/') + local_path = local_path.replace(':', '') + local_path = '/windrive/%s' % local_path + _convert_encoding = True + _client_encoding = 'WINDOWS-1252' + + if _convert_encoding: + export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s && ' % (_client_encoding, _server_encoding) + else: + export_iconv_settings = '' + + if folder_type == 'disk': + + cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, + 'x2gomountdirs', + 'dir', + str(self.session_info.name), + '\'%s\'' % _CURRENT_LOCAL_USER, + _x2go_key_fname, + '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), + 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), + ] + + elif folder_type == 'spool': + + cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, + 'x2gomountdirs', + 'dir', + str(self.session_info.name), + '\'%s\'' % _CURRENT_LOCAL_USER, + _x2go_key_fname, + '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), + 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), + ] + + elif folder_type == 'mimebox': + + cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, + 'x2gomountdirs', + 'dir', + str(self.session_info.name), + '\'%s\'' % _CURRENT_LOCAL_USER, + _x2go_key_fname, + '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), + 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + _stdout = stdout.read().split('\n') + if _stdout[0]: + self.logger('x2gomountdirs stdout is: %s' % _stdout, log.loglevel_NOTICE) + _stderr = stderr.read().split('\n') + if _stderr[0]: + self.logger('x2gomountdirs stderr is: %s' % _stderr, log.loglevel_WARN) + + except: + self._share_local_folder_lock.release() + raise + self._share_local_folder_lock.release() + + if len(_stdout) >= 6 and _stdout[5].endswith('ok'): + return True + return False + + def unshare_all_local_folders(self): + """\ + Unshare all local folders mount in the X2Go session. + + @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session + @rtype: C{bool} + + """ + self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO) + + cmd_line = [ 'export HOSTNAME &&', + 'x2goumount-session', + self.session_info.name, + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + if not stderr.read(): + self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE) + return True + else: + self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR) + return False + + def unshare_local_folder(self, local_path): + """\ + Unshare local folder given as from X2Go session. + + @return: returns C{True} if the local folder could be successfully unmounted from the X2Go server session + @rtype: C{bool} + + """ + self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO) + + cmd_line = [ 'export HOSTNAME &&', + 'x2goumount-session', + self.session_info.name, + "'%s'" % local_path, + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + if not stderr.read(): + self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE) + return True + else: + self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR) + return False + + def color_depth(self): + """\ + Retrieve the session's color depth. + + @return: the session's color depth + @rtype: C{int} + + """ + return self.params.depth + + def auto_session_window_title(self, dont_set=False): + """\ + Automatically generate an appropriate human-readable session window title. + + The session window title will be provider in the C{session_title} property of + this method. + + @param dont_set: generate the session window title, but do not actually set it + @type dont_set: C{bool} + + """ + _generic_title = 'X2GO-%s' % self.session_info.name + + # no blanks at beginning or end, no blanks-only... + self.session_title = self.session_title.strip() + + if self.params.session_type == 'D': + if self.set_session_title: + + if not self.session_title: + self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname()) + + else: + # session title fallback... (like X2Go server does it...) + self.session_title = _generic_title + + elif self.params.session_type == 'S': + if self.set_session_title: + + shared_user = _generic_title.split('XSHAD')[1] + shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0] + + self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname()) + + else: + # session title fallback... (like X2Go server does it...) + self.session_title = _generic_title + + else: + # do nothing for rootless sessions + self.session_title = _generic_title + + if self.session_title != _generic_title and not dont_set: + self.set_session_window_title(title=self.session_title) + + def find_session_window(self, timeout=60): + """\ + Try for seconds to find the X2Go session window of this + terminal session. + + A background thread will get spawned for this operation. + + @param timeout: try for seconds to find the session window + @type timeout: C{int} + + """ + gevent.spawn(self._find_session_window, timeout=timeout) + + def _find_session_window(self, timeout=0): + """\ + Try for seconds to find the X2Go session window of this + terminal session. + + @param timeout: try for seconds to find the session window + @type timeout: C{int} + + """ + self.session_window = None + + # search for the window of our focus, do this in a loop till the window as been found + # or timeout forces us to give up... + timeout += 1 + while timeout: + + timeout -= 1 + + window = utils.find_session_window(self.session_info.name) + + if window is not None: + if _X2GOCLIENT_OS == "Windows": + self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG) + else: + self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG) + self.session_window = window + + self.update_session_window_file() + break + + gevent.sleep(1) + + def update_session_window_file(self): + """\ + Create a file that contains information on the session window. + . + If the file already exists, its content gets update. + + """ + session_window_file = os.path.join(self.session_info.local_container, 'session.window') + if self.session_window is not None: + f = open(session_window_file,'w') + if _X2GOCLIENT_OS != "Windows": + _id = self.session_window.id + else: + _id = self.session_window + f.write('ID:{window_id}\n'.format(window_id=_id)) + f.close() + self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG) + else: + try: + os.remove(session_window_file) + except OSError,e: + # this is no error in most cases... + self.logger('The session window file %s is already gone (we failed to remove it with error: %s). In most cases this can be safely ignored.' % (session_window_file, str(e)), loglevel=log.loglevel_INFO) + + def set_session_window_title(self, title, timeout=60): + """\ + Modify the session window title. + + A background thread will get spawned for this operation. + + @param title: new title for the terminal session's session window + @type title: C{str} + @param timeout: try for seconds to find the session window + @type timeout: C{int} + + """ + gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout) + + def _set_session_window_title(self, title, timeout=0): + """\ + Modify the session window title. + + @param title: new title for the terminal session's session window + @type title: C{str} + @param timeout: try for seconds to find the session window + @type timeout: C{int} + + """ + self.session_title = title + + if not self.session_title: + self.auto_session_title(dont_set=True) + + timeout += 1 + while timeout: + + timeout -= 1 + + if self.session_window is not None: + self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG) + utils.set_session_window_title(self.session_window, self.session_title) + break + + gevent.sleep(1) + + def raise_session_window(self, timeout=60): + """\ + Try for seconds to raise the X2Go session window of this + terminal session to the top and bring it to focus. + + A background thread will get spawned for this operation. + + @param timeout: try for seconds to raise the session window + @type timeout: C{int} + + """ + gevent.spawn(self._raise_session_window, timeout=timeout) + + def _raise_session_window(self, timeout=0): + """ + Try for seconds to raise the X2Go session window of this + terminal session to the top and bring it to focus. + + @param timeout: try for seconds to raise the session window + @type timeout: C{int} + + """ + timeout += 1 + while timeout: + + timeout -= 1 + + if self.session_window is not None: + + utils.raise_session_window(self.session_window) + break + + gevent.sleep(1) + + def has_command(self, cmd): + """\ + ,,Guess'' if the command C{} exists on the X2Go server and is executable. + The expected result is not 100% safe, however, it comes with a high probability to + be correct. + + @param cmd: session command + @type cmd: C{str} + + @return: C{True} if this method reckons that the command is executable on the remote X2Go server + @rtype: C{bool} + + """ + test_cmd = None; + + cmd = cmd.strip('"').strip('"') + if cmd.find('RDP') != -1: + cmd = 'rdesktop' + + if cmd in _X2GO_GENERIC_APPLICATIONS: + return True + if cmd in _X2GO_DESKTOPSESSIONS.keys(): + return True + elif 'XSHAD' in cmd: + return True + elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features(): + return True + elif cmd and cmd.startswith('/'): + # check if full path is correct _and_ if application is in server path + test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0])) + elif cmd and '/' not in cmd.split()[0]: + # check if application is in server path only + test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0]) + + if test_cmd: + (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd]) + _stdout = stdout.read() + return _stdout.find('OK') != -1 + else: + return False + + def run_command(self, cmd=None, env={}): + """\ + Run a command in this session. + + After L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} has been called + one or more commands can be executed with L{x2go.backends.terminal.plain.X2GoTerminalSession.run_command()} + within the current X2Go session. + + @param cmd: Command to be run + @type cmd: C{str} + @param env: add server-side environment variables + @type env: C{dict} + + @return: stdout.read() and stderr.read() as returned by the run command + on the X2Go server + @rtype: C{tuple} of C{str} + + """ + if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)): + if self.client_instance: + self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) + return False + + if cmd in ("", None): + if self.params.cmd is None: + cmd = 'TERMINAL' + else: + cmd = self.params.cmd + + if cmd == 'XDMCP': + # do not run command when in XDMCP mode... + return None + + if 'XSHAD' in cmd: + # do not run command when in DESKTOP SHARING mode... + return None + + self.params.update(cmd=cmd) + + # do not allow the execution of full path names + if '/' in cmd: + cmd = os.path.basename(cmd) + + cmd_line = [ "setsid x2goruncommand", + str(self.session_info.display), + str(self.session_info.agent_pid), + str(self.session_info.name), + str(self.session_info.snd_port), + _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)), + str(self.params.snd_system), + str(self.params.session_type), + "1>/dev/null 2>/dev/null & exit", + ] + + if self.params.snd_system == 'pulse': + cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line + + if env: + for env_var in env.keys(): + cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): + self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) + + return stdout.read(), stderr.read() + + def is_desktop_session(self): + """\ + Is this (terminal) session a desktop session? + + @return: Returns C{True} is this session is a desktop session. + @rtype: C{bool} + + """ + if self.session_info: + return self.session_info.is_desktop_session() + return False + + def is_published_applications_provider(self): + """\ + Is this (terminal) session a published applications provider? + + @return: Returns C{True} is this session is a provider session for published applications. + @rtype: C{bool} + + """ + if self.session_info and self.is_running(): + return self.session_info.is_published_applications_provider() + return False + + def set_keyboard(self, layout='null', variant='null'): + """\ + Set the keyboard layout and variant for this (running) session. + + @param layout: keyboard layout to be set + @type layout: C{str} + @param variant: keyboard variant to be set + @type variant: C{str} + + @return: returns C{True} if the {setxkbmap} command could be executed successfully. + @rtype: C{bool} + + """ + if not self.is_running(): + return False + + cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display), + 'setxkbmap ' + ] + + if layout != 'null': + self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO) + cmd_line.append('-layout %s' % layout) + if variant != 'null': + self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO) + cmd_line.append('-variant %s' % variant) + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + _stderr = stderr.read() + if not _stderr: + self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE) + return True + else: + self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s failed: %s' % (layout, variant, self.session_info, _stderr.replace('\n', ' ')), log.loglevel_ERROR) + return False + + def exec_published_application(self, exec_name, timeout=20, env={}): + """\ + Executed a published application. + + @param exec_name: application to be executed + @type exec_name: C{str} + @param timeout: execution timeout + @type timeout: C{int} + @param env: session environment dictionary + @type env: C{dict} + + """ + cmd_line = [ + "export DISPLAY=:%s && " % str(self.session_info.display), + "export X2GO_SESSION=%s && " % str(self.get_session_name()), + ] + + if self.params.snd_system == 'pulse': + cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container) + + if env: + for env_var in env.keys(): + cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line + + cmd_line.extend( + [ + "setsid %s" % exec_name, + "1>/dev/null 2>/dev/null & exit", + ] + ) + + self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG) + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout) + + def ok(self): + """\ + X2Go session OK? + + @return: Returns C{True} if this X2Go (terminal) session is up and running, + C{False} otherwise. + @rtype: C{bool} + + """ + _ok = bool(self.session_info.name and self.proxy.ok()) + return _ok + + def is_running(self): + """\ + X2Go session running? + + @return: Returns C{True} if this X2Go (terminal) session is in running state, + C{False} otherwise. + @rtype: C{bool} + + """ + return self.session_info.is_running() + + def is_suspended(self): + """\ + X2Go session suspended? + + @return: Returns C{True} if this X2Go (terminal) session is in suspended state, + C{False} otherwise. + @rtype: C{bool} + + """ + return self.session_info.is_suspended() + + def is_connected(self): + """\ + X2Go session connected? + + @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is + connected/authenticated, C{False} else. + @rtype: C{bool} + + """ + return self.control_session.is_connected() + + def start(self): + """\ + Start a new X2Go session. + + @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running + @rtype: C{bool} + + @raise X2GoTerminalSessionException: if the session startup failed + @raise X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared + + """ + self.params.rewrite_session_type() + + if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)): + if self.client_instance: + self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) + return False + + setkbd = "0" + if self.params.kbtype != "null/null": + setkbd = "1" + + if '/' in self.params.cmd: + self.params.cmd = os.path.basename(self.params.cmd) + + self.params.rewrite_session_type() + + if self.params.geometry == 'maximize': + _geometry = utils.get_workarea_geometry() + if _geometry is None or len(_geometry) != 2: + _geometry = utils.get_desktop_geometry() + if _geometry and len(_geometry) == 2: + self.params.geometry = "%sx%s" % _geometry + else: + self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN) + self.params.geometry = "1024x768" + + cmd_line = [ "x2gostartagent", + str(self.params.geometry), + str(self.params.link), + str(self.params.pack), + str(self.params.cache_type+'-depth_'+self.params.depth), + str(self.params.kblayout), + str(self.params.kbtype), + str(setkbd), + str(self.params.session_type), + str(self.params.cmd), + ] + if self.params.session_type != 'S': + cmd_line.append( + str(self.params.clipboard), + ) + + if self.params.cmd == 'XDMCP' and self.params.xdmcp_server: + cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line + + if self.params.dpi: + cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + _stdout = stdout.read() + _stderr = stderr.read() + + # if the first line of stdout is a "DEN(Y)" string then we will presume that + # we tried to use X2Go desktop sharing and the sharing was rejected + if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr: + raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user') + + try: + self.session_info.initialize(_stdout, + username=self.control_session.remote_username(), + hostname=self.control_session.remote_peername(), + ) + except ValueError: + raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") + except IndexError: + raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") + + # local path may be a Windows path, so we use the path separator of the local system + self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) + # remote path is always a UniX path... + self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, + self.session_info.name, + ) + + # set up SSH tunnel for X11 graphical elements + self.proxy = self.proxy_backend(session_info=self.session_info, + ssh_transport=self.control_session.get_transport(), + sessions_rootdir=self.sessions_rootdir, + session_instance=self.session_instance, + proxy_options=self.proxy_options, + logger=self.logger) + self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() + + if proxy_ok: + self.active_threads.append(self.proxy) + + if self.params.session_type in ('D', 'S'): + self.find_session_window() + self.auto_session_window_title() + self.raise_session_window() + + if self.params.published_applications: + self.control_session.get_published_applications() + + else: + raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") + + return proxy_ok + + def resume(self): + """\ + Resume a running/suspended X2Go session. + + @return: C{True} if the session could successfully be resumed + @rtype: C{bool} + + @raise X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes + + """ + setkbd = "0" + if self.params.kbtype != "null/null": + setkbd = "1" + + if self.params.geometry == 'maximize': + _geometry = utils.get_workarea_geometry() + if _geometry is None or len(_geometry) != 2: + _geometry = utils.get_desktop_geometry() + if _geometry and len(_geometry) == 2: + self.params.geometry = "%sx%s" % _geometry + else: + self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN) + self.params.geometry = "1024x768" + + cmd_line = [ "x2goresume-session", self.session_info.name, + self.params.geometry, + self.params.link, + self.params.pack, + self.params.kblayout, + self.params.kbtype, + setkbd, + self.params.clipboard, + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + # re-allocate (if needed) server-side ports for graphics, sound and sshfs + for stdout_line in stdout.read(): + try: + _new_value = stdout_line.split("=")[1].strip() + if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port): + try: + self.session_info.graphics_port = int(_new_value) + self.logger('re-allocating graphics port for session %s, old server-side port is in use; new graphics port is %s' % (self.session_info, self.session_info.graphics_port), loglevel=log.loglevel_NOTICE) + except TypeError: + # if the re-allocation fails, this is fatal!!! + raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.') + elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port): + try: + self.session_info.snd_port = int(_new_value) + self.logger('re-allocating sound port for session %s, old server-side port is in use; new sound port is %s' % (self.session_info, self.session_info.snd_port), loglevel=log.loglevel_NOTICE) + except TypeError: + self.logger('Failed to retrieve new sound port from server for session %s, session will be without sound.' % self.session_info, loglevel=log.loglevel_WARN) + elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port): + try: + self.session_info.sshfs_port = int(_new_value) + self.logger('re-allocating sshfs port for session %s, old server-side port is in use; new sshfs port is %s' % (self.session_info, self.session_info.sshfs_port), loglevel=log.loglevel_NOTICE) + except TypeError: + self.logger('Failed to retrieve new sshfs port from server for session %s, session will be without client-side folder sharing. Neither will there be X2Go printing nor X2Go MIME box support.' % self.session_info, loglevel=log.loglevel_WARN) + except IndexError: + continue + + # local path may be a Windows path, so we use the path separator of the local system + self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) + # remote path is always a UniX path... + self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, + self.session_info.name, + ) + self.proxy = self.proxy_backend(session_info=self.session_info, + ssh_transport=self.control_session.get_transport(), + sessions_rootdir=self.sessions_rootdir, + session_instance=self.session_instance, + proxy_options=self.proxy_options, + logger=self.logger + ) + self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() + + if proxy_ok: + self.params.depth = self.session_info.name.split('_')[2][2:] + + # on a session resume the user name comes in as a user ID. We have to translate this... + self.session_info.username = self.control_session.remote_username() + + if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): + self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) + + if self.params.session_type in ('D', 'S'): + self.find_session_window() + self.auto_session_window_title() + self.raise_session_window() + + if self.is_published_applications_provider(): + self.control_session.get_published_applications() + self.published_applications = True + else: + raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") + + return proxy_ok + + def suspend(self): + """\ + Suspend this X2Go (terminal) session. + + @return: C{True} if the session terminal could be successfully suspended + @rtype: C{bool} + + """ + self.release_telekinesis() + self.control_session.suspend(session_name=self.session_info.name) + self.release_proxy() + + # TODO: check if session has really suspended + _ret = True + + return _ret + + def terminate(self): + """\ + Terminate this X2Go (terminal) session. + + @return: C{True} if the session could be successfully terminated + @rtype: C{bool} + + """ + self.release_telekinesis() + self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) + self.release_proxy() + self.post_terminate_cleanup() + self.__del__() + + # TODO: check if session has really suspended + _ret = True + + return _ret + + def release_proxy(self): + """\ + Let the X2Go proxy command cleanly die away... (by calling its destructor). + + """ + if self.proxy is not None: + self.proxy.__del__() + self.proxy = None + + def release_telekinesis(self): + """\ + Let the attached Telekinesis client cleanly die away... (by calling its destructor). + + """ + if self.telekinesis_client is not None: + self.telekinesis_client.__del__() + self.telekinesis_client = None + + def post_terminate_cleanup(self): + """\ + Do some cleanup after this session has terminated. + + """ + # this method might be called twice (directly and from update_status in the session + # registry instance. So we have to make sure, that this code will not fail + # if called twice. + if not self._cleaned_up and self.session_info.name: + + # otherwise we wipe the session files locally + self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE) + + # if we run in debug mode, we keep local session directories + if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG: + + self._rm_session_dirtree() + self._rm_desktop_dirtree() + + self._cleaned_up = True + + def is_rootless_session(self): + """\ + Test if this terminal session is a rootless session. + + @return: C{True} if this session is of session type rootless ('R'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'R' + + def is_shadow_session(self): + """\ + Test if this terminal session is a desktop sharing (aka shadow) session. + + @return: C{True} if this session is of session type shadow ('S'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'S' + + def is_pubapp_session(self): + """\ + Test if this terminal session is a published applications session. + + @return: C{True} if this session is of session type published applications ('P'). + @rtype: C{bool} + + """ + self.params.rewrite_session_type() + return self.params.session_type == 'P' + diff -Nru python-x2go-0.1.1.8/x2go/backends/terminal/_stdout.py python-x2go-0.5.0.6/x2go/backends/terminal/_stdout.py --- python-x2go-0.1.1.8/x2go/backends/terminal/_stdout.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/backends/terminal/_stdout.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1075 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go 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. -# -# Python X2go is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -X2goTerminalSession class - core functions for handling your individual X2go sessions. - -This backend handles X2go server implementations that respond with session infos -via server-side STDOUT and use NX3 as graphical proxy. - -""" -__NAME__ = 'x2goterminalsession-pylib' - -# modules -import os -import sys -import types -import gevent -import threading -import signal -import cStringIO -import copy -import shutil - -# Python X2go modules -import x2go.rforward as rforward -import x2go.sftpserver as sftpserver -import x2go.printqueue as printqueue -import x2go.mimebox as mimebox -import x2go.log as log -import x2go.defaults as defaults -import x2go.utils as utils -import x2go.x2go_exceptions as x2go_exceptions - -from x2go.cleanup import x2go_cleanup - -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS -from x2go.defaults import LOCAL_HOME as _LOCAL_HOME -from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER -from x2go.defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR -from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR -from x2go.defaults import X2GO_GENERIC_APPLICATIONS as _X2GO_GENERIC_APPLICATIONS - -from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo -from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList -from x2go.backends.proxy import X2goProxy as _X2goProxy -from x2go.backends.printing import X2goClientPrinting as _X2goClientPrinting - -_local_color_depth = utils.local_color_depth() - -def _rewrite_cmd(cmd, params=None): - - # start with an empty string - cmd = cmd or '' - - # find window manager commands - if cmd in defaults.X2GO_DESKTOPSESSIONS.keys(): - cmd = defaults.X2GO_DESKTOPSESSIONS[cmd] - - if (cmd == 'RDP') and (type(params) == X2goSessionParams): - if params.geometry == 'fullscreen': - cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, params.depth) - else: - cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, params.depth) - - # place quot marks around cmd if not empty string - if cmd: - cmd = '"%s"' % cmd - return cmd - - -def _rewrite_blanks(cmd): - # X2go run command replace X2GO_SPACE_CHAR string with blanks - if cmd: - cmd = cmd.replace(" ", "X2GO_SPACE_CHAR") - return cmd - - -class X2goSessionParams(object): - """\ - The L{X2goSessionParams} class is used to store all parameters that - C{X2goTerminalSession} backend objects are constructed with. - - """ - def rewrite_session_type(self): - """\ - Rewrite the X2go session type, so that the X2go server - can understand it (C{desktop} -> C{D}). - - Also if the object's C{command} property is a known window - manager, the session type will be set to 'D' - (i.e. desktop). - - @return: 'D' if session should probably a desktop session, - 'R' (for rootless) else - @rtype: str - - """ - session_type = self.session_type - cmd = self.cmd - - if session_type in ("D", "desktop"): - self.session_type = 'D' - return - elif session_type in ("S", "shared", "shadow"): - self.session_type = 'S' - return - elif cmd: - if cmd == 'RDP': - self.session_type = 'R' - return - elif cmd.startswith('rdesktop'): - self.session_type = 'R' - return - elif cmd == 'XDMCP': - self.session_type = 'D' - return - elif cmd in defaults.X2GO_DESKTOPSESSIONS.keys(): - self.session_type = 'D' - return - elif os.path.basename(cmd) in defaults.X2GO_DESKTOPSESSIONS.values(): - self.session_type = 'D' - return - self.session_type = 'R' - - def update(self, properties_to_be_updated={}): - """\ - Update all properties in the object L{X2goSessionParams} object from - the passed on dictionary. - - @param properties_to_be_updated: a dictionary with L{X2goSessionParams} - property names as keys und their values to be update in - L{X2goSessionParams} object. - @type properties_to_be_updated: dict - - """ - for key in properties_to_be_updated.keys(): - setattr(self, key, properties_to_be_updated[key] or '') - self.rewrite_session_type() - - -class X2goTerminalSessionSTDOUT(object): - """\ - Class for managing X2go sessions on a remote X2go server via Paramiko/SSH. - With the L{X2goTerminalSessionSTDOUT} class you can start new X2go sessions, resume suspended - sessions or suspend resp. terminate currently running sessions on a - connected X2go server. - - When suspending or terminating sessions there are two possible ways: - - 1. Initialize an X2go session object, start a new session (or resume) - and use the L{X2goTerminalSessionSTDOUT.suspend()} or L{X2goTerminalSessionSTDOUT.terminate()} method - to suspend/terminate the current session object. - 2. Alternatively, you can pass a session name to L{X2goTerminalSessionSTDOUT.suspend()} - or L{X2goTerminalSessionSTDOUT.terminate()}. If a session of this name exists on the - X2go server the respective action will be performed on the session. - - An L{X2goTerminalSessionSTDOUT} object uses two main data structure classes: - - - L{X2goSessionParams}: stores all parameters that have been passed to the - constructor method. - - - C{X2goServerSessionInfo} backend class: when starting or resuming a session, an object of this class - will be used to store all information retrieved from the X2go server. - - - """ - def __init__(self, control_session, session_info=None, - geometry="800x600", depth=_local_color_depth, link="adsl", pack="16m-jpeg-9", - cache_type="unix-kde", - keyboard='', kblayout='null', kbtype='null/null', - session_type="application", snd_system='pulse', snd_port=4713, cmd=None, - rdp_server=None, rdp_options=None, - xdmcp_server=None, - convert_encoding=False, server_encoding='UTF-8', client_encoding='UTF-8', - rootdir=None, - profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), - print_action=None, print_action_args={}, - info_backend=_X2goServerSessionInfo, - list_backend=_X2goServerSessionList, - proxy_backend=_X2goProxy, proxy_options={}, - printing_backend=_X2goClientPrinting, - client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), - sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), - session_instance=None, - logger=None, loglevel=log.loglevel_DEFAULT): - """\ - Initialize an X2go session. With the L{X2goTerminalSessionSTDOUT} class you can start - new X2go sessions, resume suspended sessions or suspend resp. terminate - currently running sessions on a connected X2go server. - - @param geometry: screen geometry of the X2go session. Can be either C{x} - or C{fullscreen} - @type geometry: str - @param depth: color depth in bits (common values: C{16}, C{24}) - @type depth: int - @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan}) - @type link: str - @param pack: compression method for NX based session proxying - @type pack: str - @param cache_type: a dummy parameter that is passed to the L{X2goProxyBASE}. In NX Proxy - (class C{X2goProxyNX3}) this originally is the session name. With X2go it - defines the name of the NX cache directory. Best is to leave it untouched. - @type cache_type: str - @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ... - @type kblayout: str - @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ... - @type kbtype: str - @param session_type: either C{desktop}, C{application} (rootless session) or C{shared} - @type session_type: str - @param snd_system: sound system to be used on server (C{none}, C{pulse} (default), - C{arts} (obsolete) or C{esd}) - @type snd_system: str - @param cmd: command to be run on X2go server after session start (only used - when L{X2goTerminalSessionSTDOUT.start()} is called, ignored on resume, suspend etc. - @type cmd: str - @param rootdir: X2go session directory, normally C{~/.x2go} - @type rootdir: str - @param info_backend: backend for handling storage of server session information - @type info_backend: C{X2goServerSessionInfo*} instance - @param list_backend: backend for handling storage of session list information - @type list_backend: C{X2goServerSessionList*} instance - @param proxy_backend: backend for handling the X-proxy connections - @type proxy_backend: C{X2goProxy*} instance - @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the - resp. C{X2goPrintActionXXX} class (where XXX equals one of the given short names) - @type print_action: str or class - @param print_action_args: optional arguments for a given print_action (for further info refer to - L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and L{X2goPrintActionPRINTCMD}) - @type print_action_args: dict - @param proxy_options: a set of very C{X2goProxy*} backend specific options; any option that is not known - to the C{X2goProxy*} backend will simply be ignored - @type proxy_options: C{dict} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goTerminalSessionSTDOUT} constructor - @type logger: L{X2goLogger} instance - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be - constructed with the given loglevel - @type loglevel: int - - """ - self.proxy = None - self.proxy_subprocess = None - self.proxy_options = proxy_options - - self.active_threads = [] - self.reverse_tunnels = {} - - self.print_queue = None - self.mimebox_queue = None - - if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) - else: - self.logger = copy.deepcopy(logger) - self.logger.tag = __NAME__ - - self.control_session = control_session - self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels - - self.client_rootdir = client_rootdir - self.sessions_rootdir = sessions_rootdir - - self.params = X2goSessionParams() - - self.params.geometry = str(geometry) - self.params.link = str(link) - self.params.pack = str(pack) - self.params.cache_type = str(cache_type) - self.params.session_type = str(session_type) - self.params.keyboard = str(keyboard) - self.params.kblayout = str(kblayout) - self.params.kbtype = str(kbtype) - self.params.snd_system = str(snd_system) - self.params.cmd = str(cmd) - self.params.depth = str(depth) - - self.params.rdp_server = str(rdp_server) - self.params.rdp_options = str(rdp_options) - self.params.xdmcp_server = str(xdmcp_server) - - self.params.convert_encoding = convert_encoding - self.params.client_encoding = str(client_encoding) - self.params.server_encoding = str(server_encoding) - - self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or self.sessions_rootdir - self.params.update() - - self.profile_name = profile_name - self.proxy_backend = proxy_backend - - self.snd_port = snd_port - self.print_action = print_action - self.print_action_args = print_action_args - self.printing_backend = printing_backend - self.session_instance = session_instance - if self.session_instance: - self.client_instance = self.session_instance.client_instance - else: - self.client_instance = None - - self._mk_sessions_rootdir(self.params.rootdir) - - self.session_info = session_info - if self.session_info is not None: - if self.session_info.name: - self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) - else: - raise X2goTerminalSessionException('no valid session info availble') - else: - self.session_info = info_backend() - - self._cleaned_up = False - - def __del__(self): - self._x2go_tidy_up() - - def _x2go_tidy_up(self): - - self.release_proxy() - - try: - if self.control_session.get_transport() is not None: - try: - for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]: - if _tunnel is not None: - _tunnel.__del__() - except KeyError: - pass - - if self.print_queue is not None: - self.print_queue.__del__() - - if self.mimebox_queue is not None: - self.mimebox_queue.__del__() - - except AttributeError: - pass - - self.session_info.clear() - - def _mk_sessions_rootdir(self, d): - - try: - os.mkdir(d) - except OSError, e: - if e.errno == 17: - # file exists - pass - else: - raise OSError, e - - def _rm_session_dirtree(self): - - if self.session_info.name: - shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True) - - def _rm_desktop_dirtree(self): - - if self.session_info.display: - shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True) - - def get_session_name(self): - """\ - STILL UNDOCUMENTED - - """ - return self.session_info.name - - def start_sound(self): - """\ - Initialize Paramiko/SSH reverse forwarding tunnel for X2go sound. - - Currently supported audio protocols: - - - PulseAudio - - Esound - - """ - _tunnel = None - if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: - if self.params.snd_system == 'pulse': - self.logger('initializing PulseAudio sound support in X2go session', loglevel=log.loglevel_INFO) - ### - ### PULSEAUDIO - ### - if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)): - # setup pulse client config file on X2go server - cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ - "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - - self.control_session._x2go_sftp_put(local_path='%s/.pulse-cookie' % _LOCAL_HOME, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) - - # start reverse SSH tunnel for pulse stream - _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, - remote_host='127.0.0.1', - remote_port=self.snd_port, - ssh_transport=self.control_session.get_transport(), - session_instance=self.session_instance, - logger=self.logger - ) - else: - if self.client_instance: - self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name) - elif self.params.snd_system == 'arts': - ### - ### ARTSD AUDIO - ### - self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2go...', loglevel=log.loglevel_WARNING) - - elif self.params.snd_system == 'esd': - ### - ### ESD AUDIO - ### - - self.logger('initializing ESD sound support in X2go session', loglevel=log.loglevel_INFO) - self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) - - # start reverse SSH tunnel for pulse stream - _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, - remote_host='127.0.0.1', - remote_port=self.snd_port, - ssh_transport=self.control_session.get_transport(), - session_instance=self.session_instance, - logger=self.logger - ) - - - if _tunnel is not None: - self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) - _tunnel.start() - self.active_threads.append(_tunnel) - - else: - # tunnel has already been started and might simply need a resume call - self.reverse_tunnels[self.session_info.name]['snd'][1].resume() - - def start_sshfs(self): - """\ - Initialize Paramiko/SSH reverse forwarding tunnel for X2go folder sharing. - - """ - if not self.control_session.is_folder_sharing_available(): - raise x2go_exceptions.X2goUserException('remote user %s is not member of X2go server group fuse' % self.session_info.username) - - # start reverse SSH tunnel for sshfs (folder sharing, printing) - ssh_transport = self.control_session.get_transport() - if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: - - _tunnel = sftpserver.X2goRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, - ssh_transport=ssh_transport, - auth_key=self.control_session._x2go_session_auth_rsakey, - session_instance=self.session_instance, - logger=self.logger - ) - - if _tunnel is not None: - self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) - _tunnel.start() - self.active_threads.append(_tunnel) - - else: - # tunnel has already been started and might simply need a resume call - self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume() - - def _x2go_pause_rev_fw_tunnel(self, name): - # pause reverse SSH tunnel of name - ssh_transport = self.get_transport() - _tunnel = self.reverse_tunnels[self.session_info.name][name][1] - if _tunnel is not None: - _tunnel.pause() - - def stop_sound(self): - """\ - Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go sound. - - """ - self._x2go_pause_rev_fw_tunnel('snd') - - def stop_sshfs(self): - """\ - Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go folder sharing. - - """ - self._x2go_pause_rev_fw_tunnel('sshfs') - - def start_printing(self): - """\ - Initialize X2go print spooling. - - """ - if self.session_info.username not in self.control_session._x2go_remote_group('x2goprint'): - raise x2go_exceptions.X2goUserException('remote user %s is not member of X2go server group x2goprint' % self.session_info.username) - - spool_dir = os.path.join(self.session_info.local_container, 'spool') - if not os.path.exists(spool_dir): - os.mkdir(spool_dir) - self.share_local_folder(local_path=spool_dir, folder_type='spool') - self.print_queue = printqueue.X2goPrintQueue(profile_name=self.profile_name, - session_name=self.session_info.name, - spool_dir=spool_dir, - print_action=self.print_action, - print_action_args=self.print_action_args, - client_instance=self.client_instance, - printing_backend=self.printing_backend, - logger=self.logger, - ) - self.print_queue.start() - self.active_threads.append(self.print_queue) - - def set_print_action(self, print_action, **kwargs): - """\ - STILL UNDOCUMENTED - - """ - self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs) - - def stop_printing(self): - """\ - Shutdown (pause) the X2go Print Queue thread. - - """ - if self.print_queue is not None: - self.print_queue.pause() - - def get_printing_spooldir(self): - """\ - Return the server-side printing spooldir path. - - """ - return '%s/%s' % (self.session_info.remote_container, 'spool') - - def start_mimebox(self, mimebox_extensions=[], mimebox_action=None): - """\ - Initialize X2go mimebox handling. - - """ - mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox') - if not os.path.exists(mimebox_dir): - os.mkdir(mimebox_dir) - self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox') - self.mimebox_queue = mimebox.X2goMIMEboxQueue(profile_name=self.profile_name, - session_name=self.session_info.name, - mimebox_dir=mimebox_dir, - mimebox_extensions=mimebox_extensions, - mimebox_action=mimebox_action, - client_instance=self.client_instance, - logger=self.logger, - ) - self.mimebox_queue.start() - self.active_threads.append(self.mimebox_queue) - - def set_mimebox_action(self, mimebox_action, **kwargs): - """\ - STILL UNDOCUMENTED - - """ - self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs) - - def stop_mimebox(self): - """\ - Shutdown (pause) the X2go MIME box Queue thread. - - """ - if self.mimebox_queue is not None: - self.mimebox_queue.pause() - - def get_mimebox_spooldir(self): - """\ - Return the server-side mimebox spooldir path. - - """ - return '%s/%s' % (self.session_info.remote_container, 'mimebox') - - def share_local_folder(self, local_path=None, folder_type='disk'): - """\ - Share a local folder with the X2go session. - - @param local_path: the full path to an existing folder on the local - file system - @type local_path: str - @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), - 'cdrom' (CD/DVD Rom) or 'spool' (for X2go print spooling) - @type folder_type: str - - @return: returns C{True} if the local folder has been successfully mounted within the X2go server session - @rtype: bool - - """ - if not self.control_session.is_folder_sharing_available(): - raise x2go_exceptions.X2goUserException('remote user %s is not member of X2go server group fuse' % self.session_info.username) - - if local_path is None: - self.logger('no folder name given...', log.loglevel_WARN) - return False - - if type(local_path) not in (types.StringType, types.UnicodeType): - self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) - return False - - if not os.path.exists(local_path): - self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN) - return False - - local_path = os.path.normpath(local_path) - self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO) - - _auth_rsakey = self.control_session._x2go_session_auth_rsakey - _host_rsakey = defaults.RSAHostKey - - _tmp_io_object = cStringIO.StringIO() - _auth_rsakey.write_private_key(_tmp_io_object) - _tmp_io_object.write('----BEGIN RSA IDENTITY----') - _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) - - # _x2go_key_fname must be a UniX path - _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) - _x2go_key_bundle = _tmp_io_object.getvalue() - - self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) - - _convert_encoding = self.params.convert_encoding - _client_encoding = self.params.client_encoding - _server_encoding = self.params.server_encoding - - if _X2GOCLIENT_OS == 'Windows': - local_path = local_path.replace('\\', '/') - local_path = local_path.replace(':', '') - local_path = '/windrive/%s' % local_path - _convert_encoding = True - _client_encoding = 'WINDOWS-1252' - - if _convert_encoding: - export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s &&' % (_client_encoding, _server_encoding) - else: - export_iconv_settings = '' - - if folder_type == 'disk': - - cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, - 'x2gomountdirs', - 'dir', - str(self.session_info.name), - '"%s"' % _CURRENT_LOCAL_USER, - _x2go_key_fname, - '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), - 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), - ] - - elif folder_type == 'spool': - - cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, - 'x2gomountdirs', - 'dir', - str(self.session_info.name), - '"%s"' % _CURRENT_LOCAL_USER, - _x2go_key_fname, - '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), - 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), - ] - - elif folder_type == 'mimebox': - - cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, - 'x2gomountdirs', - 'dir', - str(self.session_info.name), - '"%s"' % _CURRENT_LOCAL_USER, - _x2go_key_fname, - '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), - 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), - ] - - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - _stdout = stdout.read().split('\n') - self.logger('x2gomountdirs output is : %s' % _stdout, log.loglevel_NOTICE) - if _stdout[5].endswith('ok'): - return True - return False - - def unshare_all_local_folders(self): - """\ - Unshare all local folders mount in the X2go session. - - @return: returns C{True} if all local folders could be successfully unmounted from the X2go server session - @rtype: bool - - """ - self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO) - - cmd_line = [ 'export HOSTNAME &&', - 'x2goumount-session', - self.session_info.name, - ] - - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - if not stderr.read(): - self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE) - return True - else: - self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR) - return False - - def unshare_local_folder(self, local_path): - """\ - Unshare local folder given as from X2go session. - - @return: returns C{True} if the local folder could be successfully unmounted from the X2go server session - @rtype: bool - - """ - self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO) - - cmd_line = [ 'export HOSTNAME &&', - 'x2goumount-session', - self.session_info.name, - local_path, - ] - - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - if not stderr.read(): - self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE) - return True - else: - self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR) - return False - - def color_depth(self): - """\ - Retrieve the session's color depth. - - @return: the session's color depth - @rtype: C{int} - - """ - return self.params.depth - - def has_command(self, cmd): - """\ - Verify if the command exists on the X2go server. - - """ - test_cmd = None; - - cmd = cmd.strip('"').strip('"') - if cmd.find('RDP') != -1: - cmd = 'rdesktop' - - if cmd in _X2GO_GENERIC_APPLICATIONS: - return True - elif 'XSHAD' in cmd: - return True - elif cmd and cmd.startswith('/'): - # check if full path is correct _and_ if application is in server path - test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0])) - elif cmd and '/' not in cmd.split()[0]: - # check if application is in server path only - test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0]) - - if test_cmd: - (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd]) - _stdout = stdout.read() - return _stdout.find('OK') != -1 - else: - return False - - def run_command(self, cmd=None, env={}): - """\ - Run a command in this session. - - After L{X2goTerminalSessionSTDOUT.start()} has been called - one or more commands can be executed with L{X2goTerminalSessionSTDOUT.run_command()} - within the current X2go session. - - @param cmd: Command to be run - @type cmd: str - - @return: stdout.read() and stderr.read() as returned by the run command - on the X2go server - @rtype: tuple of str - - """ - if not self.has_command(_rewrite_cmd(self.params.cmd)): - if self.client_instance: - self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) - return False - - if cmd in ("", None): - if self.params.cmd is None: - cmd = 'TERMINAL' - else: - cmd = self.params.cmd - - if cmd == 'XDMCP': - # do not run command when in XDMCP mode... - return None - - if 'XSHAD' in cmd: - # do not run command when in DESKTOP SHARING mode... - return None - - self.params.update({'cmd': cmd}) - - # do not allow the execution of full path names - if '/' in cmd: - cmd = os.path.basename(cmd) - - cmd_line = [ "setsid x2goruncommand", - str(self.session_info.display), - str(self.session_info.agent_pid), - str(self.session_info.name), - str(self.session_info.snd_port), - _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)), - str(self.params.snd_system), - str(self.params.session_type), - "&> /dev/null & exit", - ] - - if self.params.snd_system == 'pulse': - cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line - - if env: - for env_var in env.keys(): - cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line - - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - - return stdout.read(), stderr.read() - - def ok(self): - """\ - Returns C{True} if this X2go session is up and running, - C{False} else - - @return: X2go session OK? - @rtype: bool - - """ - return bool(self.session_info.name and self.proxy.ok()) - - def is_running(self): - """\ - Returns C{True} if this X2go session is in running state, - C{False} else. - - @return: X2go session running? - @rtype: bool - - """ - return self.session_info.is_running() - - def is_suspended(self): - """\ - Returns C{True} if this X2go session is in suspended state, - C{False} else. - - @return: X2go session suspended? - @rtype: bool - - """ - return self.session_info.is_suspended() - - def is_connected(self): - """\ - Returns C{True} if this X2go session's Paramiko/SSH transport is - connected/authenticated, C{False} else. - - @return: X2go session connected? - @rtype: bool - """ - return self.control_session.is_connected() - - def start(self): - """\ - Start a new X2go session. - - The L{X2goTerminalSession.start()} method accepts any parameter - that can be passed to the class constructor. - - """ - if not self.has_command(_rewrite_cmd(self.params.cmd)): - if self.client_instance: - self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) - return False - - setkbd = "0" - if self.params.kblayout or self.params.kbtype: - setkbd = "1" - - cmd = self.params.cmd - if '/' in cmd: - cmd = os.path.basename(cmd) - - cmd_line = [ "x2gostartagent", - str(self.params.geometry), - str(self.params.link), - str(self.params.pack), - str(self.params.cache_type+'-depth_'+self.params.depth), - str(self.params.kblayout), - str(self.params.kbtype), - str(setkbd), - str(self.params.session_type), - cmd, - ] - - if self.params.cmd == 'XDMCP' and self.params.xdmcp_server: - cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line - - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - - _stdout = stdout.read() - _stderr = stderr.read() - - # if the first line of stdout is a "DEN(Y)" string then we will presume that - # we tried to use X2go desktop sharing and the sharing was rejected - if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr: - raise x2go_exceptions.X2goDesktopSharingException('X2go desktop sharing has been denied by the remote user') - - try: - self.session_info.initialize(_stdout, - username=self.control_session.remote_username(), - hostname=self.control_session.get_transport().getpeername(), - ) - except ValueError: - raise X2goTerminalSessionException("failed to start X2go session") - except IndexError: - raise X2goTerminalSessionException("failed to start X2go session") - - # local path may be a Windows path, so we use the path separator of the local system - self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) - # remote path is always a UniX path... - self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, - self.session_info.name, - ) - - # let the proxy backend know that we want to define a very special keymap - if (self.params.kbtype.endswith('defkeymap') and self.params.kblayout == 'defkeymap'): - self.proxy_options.update({'defkeymap': True, }) - - # set up SSH tunnel for X11 graphical elements - self.proxy = self.proxy_backend(session_info=self.session_info, - ssh_transport=self.control_session.get_transport(), - sessions_rootdir=self.sessions_rootdir, - session_instance=self.session_instance, - proxy_options=self.proxy_options, - logger=self.logger) - self.proxy_subprocess = self.proxy.start_proxy() - self.active_threads.append(self.proxy) - - return self.ok() - - def resume(self): - """\ - Resume a running/suspended X2go session. - - The L{X2goTerminalSessionSTDOUT.resume()} method accepts any parameter - that can be passed to the class constructor. - - @return: True if the session could be successfully resumed - @rtype: bool - - """ - setkbd = "0" - if self.params.kblayout or self.params.kbtype: - setkbd = "1" - - cmd_line = [ "x2goresume-session", self.session_info.name, - self.params.geometry, - self.params.link, - self.params.pack, - self.params.kblayout, - self.params.kbtype, - setkbd, - ] - - (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) - - self.proxy = self.proxy_backend(session_info=self.session_info, - ssh_transport=self.control_session.get_transport(), - sessions_rootdir=self.sessions_rootdir, - session_instance=self.session_instance, - logger=self.logger - ) - self.proxy_subprocess = self.proxy.start_proxy() - - # local path may be a Windows path, so we use the path separator of the local system - self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) - # remote path is always a UniX path... - self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, - self.session_info.name, - ) - self.params.depth = self.session_info.name.split('_')[2][2:] - # on a session resume the user name comes in as a user ID. We have to translate this... - self.session_info.username = self.control_session.remote_username() - return self.ok() - - def suspend(self): - """\ - Suspend this X2go session terminal. - - @return: True if the session terminal could be successfully suspended - @rtype: bool - - """ - self.control_session.suspend(session_name=self.session_info.name) - self.release_proxy() - - # TODO: check if session has really suspended - _ret = True - - return _ret - - def terminate(self): - """\ - Terminate this X2go session. - - @return: True if the session terminal could be successfully terminate - @rtype: bool - - """ - self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) - self.release_proxy() - self.post_terminate_cleanup() - self.__del__() - - # TODO: check if session has really suspended - _ret = True - - return _ret - - def release_proxy(self): - """\ - STILL UNDOCUMENTED - - """ - if self.proxy is not None: - self.proxy.__del__() - - def post_terminate_cleanup(self): - """\ - STILL UNDOCUMENTED - - """ - # this method might be called twice (directly and from update_status in the session - # registry instance. So we have to make sure, that this code will not fail - # if called twice. - if not self._cleaned_up and self.session_info.name: - - # otherwise we wipe the session files locally - self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE) - - # if we run in debug mode, we keep local session directories - if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG: - - self._rm_session_dirtree() - self._rm_desktop_dirtree() - - self._cleaned_up = True diff -Nru python-x2go-0.1.1.8/x2go/cache.py python-x2go-0.5.0.6/x2go/cache.py --- python-x2go-0.1.1.8/x2go/cache.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/cache.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goListSessionCache class - caching X2go session information. +X2GoListSessionCache class - caching X2Go session information. """ __NAME__ = 'x2gocache-pylib' @@ -27,19 +27,19 @@ import copy import gevent -# Python X2go modules +# Python X2Go modules import log import x2go_exceptions -class X2goListSessionsCache(object): +class X2GoListSessionsCache(object): """\ - For non-blocking operations in client applications using Python X2go, it is - recommended to enable the L{X2goListSessionsCache}. This can be done by calling - the constructor of the L{X2goClient} class. + For non-blocking operations in client applications using Python X2Go, it is + recommended to enable the L{X2GoListSessionsCache}. This can be done by calling + the constructor of the L{X2GoClient} class. The session list and desktop cache gets updated in regular intervals by a threaded - L{X2goSessionGuardian} instance. For the session list and desktop list update, the - X2go server commands C{x2golistsessions} and C{x2godesktopsessions} are called and + L{X2GoSessionGuardian} instance. For the session list and desktop list update, the + X2Go server commands C{x2golistsessions} and C{x2godesktopsessions} are called and the command's stdout is cached in the session list cache. Whenever your client application needs access to either the server's session list @@ -52,20 +52,21 @@ def __init__(self, client_instance, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the L{X2goClient} instance that uses this L{X2goListSessionsCache} - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the L{X2goListSessionsCache} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the L{X2GoClient} instance that uses this L{X2GoListSessionsCache} + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the L{X2GoListSessionsCache} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ self.x2go_listsessions_cache = {} self.last_listsessions_cache = {} + self.protected = False if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -80,6 +81,8 @@ @type profile_name: C{str} """ + while self.protected: + gevent.sleep(.1) try: del self.x2go_listsessions_cache[profile_name] except KeyError: pass @@ -96,7 +99,7 @@ def update_all(self, update_sessions=True, update_desktops=False): """\ - Update L{X2goListSessionsCache} for all connected session profiles. + Update L{X2GoListSessionsCache} for all connected session profiles. @param update_sessions: cache recent session lists from all connected servers @type update_sessions: C{bool} @@ -109,9 +112,9 @@ self.check_cache() - def update(self, profile_name, update_sessions=True, update_desktops=False): + def update(self, profile_name, update_sessions=True, update_desktops=False, update_mounts=False): """\ - Update L{X2goListSessionsCache} (i.e. session/desktops) for session profile C{profile_name}. + Update L{X2GoListSessionsCache} (i.e. session/desktops) for session profile C{profile_name}. @param profile_name: name of profile to update @type profile_name: C{str} @@ -119,60 +122,104 @@ @type update_sessions: C{bool} @param update_desktops: cache recent desktop list from server @type update_desktops: C{bool} + @param update_mounts: cache list of client-side mounts on server + @type update_mounts: C{bool} """ + self.protected = True self.last_listsessions_cache = copy.deepcopy(self.x2go_listsessions_cache) control_session = self.client_instance.client_control_session_of_profile_name(profile_name) if not self.x2go_listsessions_cache.has_key(profile_name): - self.x2go_listsessions_cache[profile_name] = {'sessions': None, 'desktops': None, } + self.x2go_listsessions_cache[profile_name] = {'sessions': None, 'desktops': None, 'mounts': {}, } if update_sessions: self._update_sessions(profile_name, control_session) if update_desktops: self._update_desktops(profile_name, control_session) + if update_mounts: + self._update_mounts(profile_name, control_session) + self.protected = False + + def _update_mounts(self, profile_name, control_session): + """\ + Update mounts list of L{X2GoListSessionsCache} for session profile C{profile_name}. + + @param profile_name: name of profile to update + @type profile_name: C{str} + + @raise X2GoControlSessionException: if the control session's C{list_mounts} method fails + """ + try: + self.x2go_listsessions_cache[profile_name]['mounts'] = {} + if self.x2go_listsessions_cache[profile_name]['sessions']: + for session_name in self.x2go_listsessions_cache[profile_name]['sessions']: + session = self.client_instance.get_session_of_session_name(session_name, return_object=True, match_profile_name=profile_name) + if session is not None and session.is_running(): + if control_session is not None and not control_session.has_session_died(): + self.x2go_listsessions_cache[profile_name]['mounts'].update(control_session.list_mounts(session_name)) + except (x2go_exceptions.X2GoControlSessionException, AttributeError), e: + if profile_name in self.x2go_listsessions_cache.keys(): + del self.x2go_listsessions_cache[profile_name] + self.protected = False + raise x2go_exceptions.X2GoControlSessionException(str(e)) + except x2go_exceptions.X2GoTimeOutException: + pass + except KeyError: + pass def _update_desktops(self, profile_name, control_session): """\ - Update session lists of L{X2goListSessionsCache} for session profile C{profile_name}. + Update session lists of L{X2GoListSessionsCache} for session profile C{profile_name}. @param profile_name: name of profile to update @type profile_name: C{str} + @param control_session: X2Go control session instance + @type control_session: C{obj} + @raise X2GoControlSessionException: if the control session's C{list_desktop} method fails """ try: - self.x2go_listsessions_cache[profile_name]['desktops'] = control_session.list_desktops() - except x2go_exceptions.X2goControlSessionException, e: - try: + if control_session is not None and not control_session.has_session_died(): + self.x2go_listsessions_cache[profile_name]['desktops'] = control_session.list_desktops() + except (x2go_exceptions.X2GoControlSessionException, AttributeError), e: + if profile_name in self.x2go_listsessions_cache.keys(): del self.x2go_listsessions_cache[profile_name] - except KeyError: - pass - raise e + self.protected = False + raise x2go_exceptions.X2GoControlSessionException(str(e)) + except x2go_exceptions.X2GoTimeOutException: + pass + except KeyError: + pass def _update_sessions(self, profile_name, control_session): """\ - Update desktop list of L{X2goListSessionsCache} for session profile C{profile_name}. + Update desktop list of L{X2GoListSessionsCache} for session profile C{profile_name}. @param profile_name: name of profile to update @type profile_name: C{str} + @raise X2GoControlSessionException: if the control session's C{list_sessions} method fails """ try: - self.x2go_listsessions_cache[profile_name]['sessions'] = control_session.list_sessions() - except x2go_exceptions.X2goControlSessionException, e: - try: + if control_session is not None and not control_session.has_session_died(): + self.x2go_listsessions_cache[profile_name]['sessions'] = control_session.list_sessions() + except (x2go_exceptions.X2GoControlSessionException, AttributeError), e: + if profile_name in self.x2go_listsessions_cache.keys(): del self.x2go_listsessions_cache[profile_name] - except KeyError: - pass - raise e + self.protected = False + raise x2go_exceptions.X2GoControlSessionException(str(e)) + except KeyError: + pass def list_sessions(self, session_uuid): """\ - Retrieve a session list from the current cache content of L{X2goListSessionsCache} - for a given L{X2goSession} instance (specified by its unique session UUID). + Retrieve a session list from the current cache content of L{X2GoListSessionsCache} + for a given L{X2GoSession} instance (specified by its unique session UUID). @param session_uuid: unique identifier of session to query cache for @type session_uuid: C{str} + @return: a data object containing available session information - @rtype: C{X2goServerSessionList*} instance + @rtype: C{X2GoServerSessionList*} instance (or C{None}) """ profile_name = self.client_instance.get_session_profile_name(session_uuid) @@ -184,13 +231,14 @@ def list_desktops(self, session_uuid): """\ Retrieve a list of available desktop sessions from the current cache content of - L{X2goListSessionsCache} for a given L{X2goSession} instance (specified by its + L{X2GoListSessionsCache} for a given L{X2GoSession} instance (specified by its unique session UUID). @param session_uuid: unique identifier of session to query cache for @type session_uuid: C{str} - @return: a list of strings representing X2go desktop sessions available for sharing - @rtype: C{list} + + @return: a list of strings representing X2Go desktop sessions available for sharing + @rtype: C{list} (or C{None}) """ profile_name = self.client_instance.get_session_profile_name(session_uuid) @@ -199,21 +247,46 @@ else: return None + def list_mounts(self, session_uuid): + """\ + Retrieve a list of mounted client shares from the current cache content of + L{X2GoListSessionsCache} for a given L{X2GoSession} instance (specified by its + unique session UUID). + + @param session_uuid: unique identifier of session to query cache for + @type session_uuid: C{str} + + @return: a list of strings representing mounted client shares + @rtype: C{list} (or C{None}) + + """ + profile_name = self.client_instance.get_session_profile_name(session_uuid) + if self.is_cached(session_uuid=session_uuid): + return self.x2go_listsessions_cache[profile_name]['mounts'] + else: + return None + def is_cached(self, profile_name=None, session_uuid=None, cache_type=None): """\ - Check if session list is cached. + Check if session information is cached. @param profile_name: name of profile to update @type profile_name: C{str} @param session_uuid: unique identifier of session to query cache for @type session_uuid: C{str} + @return: C{True} if session information is cached + @rtype: C{bool} + """ - if profile_name is None and session_uuid: - profile_name = self.client_instance.get_session_profile_name(session_uuid) + if profile_name is None and session_uuid and self.client_instance: + try: + profile_name = self.client_instance.get_session_profile_name(session_uuid) + except x2go_exceptions.X2GoSessionRegistryException: + raise x2go_exceptions.X2GoSessionCacheException("requested session UUID is not valid anymore") _is_profile_cached = self.x2go_listsessions_cache.has_key(profile_name) _is_cache_type_cached = _is_profile_cached and self.x2go_listsessions_cache[profile_name].has_key(cache_type) if cache_type is None: return _is_profile_cached else: - return _is_cache_type_cached \ No newline at end of file + return _is_cache_type_cached diff -Nru python-x2go-0.1.1.8/x2go/checkhosts.py python-x2go-0.5.0.6/x2go/checkhosts.py --- python-x2go-0.1.1.8/x2go/checkhosts.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/checkhosts.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -Providing mechanisms to C{X2goControlSession*} backends for checking host validity. +Providing mechanisms to C{X2GoControlSession*} backends for checking host validity. """ __NAME__ = 'x2gocheckhosts-pylib' @@ -26,92 +26,37 @@ # modules import paramiko import binascii -import uuid -# Python X2go modules +# Python X2Go modules import sshproxy import log import x2go_exceptions +import random +import string -class X2goInteractiveAddPolicy(paramiko.MissingHostKeyPolicy): +class X2GoMissingHostKeyPolicy(paramiko.MissingHostKeyPolicy): """\ - Policy for making host key information available to Python X2go after a - Paramiko/SSH connect has been attempted. This class needs information - about the associated L{X2goSession} instance. - - Once called, the L{missing_host_key} method of this class will try to call - L{X2goSession.HOOK_check_host_dialog()}. This hook method---if not re-defined - in your application---will then try to call the L{X2goClient.HOOK_check_host_dialog()}, - which then will return C{True} by default if not customized in your application. - - To accept host key checks, make sure to either customize the - L{X2goClient.HOOK_check_host_dialog()} method or the L{X2goSession.HOOK_check_host_dialog()} - method and hook some interactive user dialog to either of them. + Skeleton class for Python X2Go's missing host key policies. """ - def __init__(self, caller=None, session_instance=None): + def __init__(self, caller=None, session_instance=None, fake_hostname=None): """\ @param caller: calling instance @type caller: C{class} - @param session_instance: an X2go session instance - @type session_instance: L{X2goSession} instance + @param session_instance: an X2Go session instance + @type session_instance: L{X2GoSession} instance """ self.caller = caller self.session_instance = session_instance - - def missing_host_key(self, client, hostname, key): - """\ - Handle a missing host key situation. This method calls - - Once called, the L{missing_host_key} method will try to call - L{X2goSession.HOOK_check_host_dialog()}. This hook method---if not re-defined - in your application---will then try to call the L{X2goClient.HOOK_check_host_dialog()}, - which then will return C{True} by default if not customized in your application. - - To accept host key checks, make sure to either customize the - L{X2goClient.HOOK_check_host_dialog()} method or the L{X2goSession.HOOK_check_host_dialog()} - method and hook some interactive user dialog to either of them. - - @param client: SSH client (C{X2goControlSession*}) instance - @type client: C{X2goControlSession*} instance - @param hostname: remote hostname - @type hostname: C{str} - @param key: host key to validate - @type key: Paramiko/SSH key instance - - """ - self.client = client - self.hostname = hostname - if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1): - # if hostname is an IPv4 quadruple with standard SSH port... - self.hostname = '[%s]:22' % self.hostname - self.key = key - client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' % - (self.key.get_name(), self.hostname, binascii.hexlify(self.key.get_fingerprint()))) - if self.session_instance: - self.session_instance.logger('SSH host key verification for host %s with %s fingerprint ,,%s\'\' initiated. We are seeing this X2go server for the first time.' % (self.get_hostname(), self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) - _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(), - port=self.get_hostname_port(), - fingerprint=self.get_key_fingerprint_with_colons(), - fingerprint_type=self.get_key_name(), - ) - if _valid: - paramiko.AutoAddPolicy().missing_host_key(client, self.hostname, key) - else: - if type(self.caller) in (sshproxy.X2goSSHProxy, ): - raise x2go_exceptions.X2goSSHProxyHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % hostname) - else: - raise x2go_exceptions.X2goHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % hostname) - else: - raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % hostname) + self.fake_hostname = fake_hostname def get_client(self): """\ Retrieve the Paramiko SSH/Client. - @return: the associated X2go control session instance. - @rtype: C{X2goControlSession*} instance + @return: the associated X2Go control session instance. + @rtype: C{X2GoControlSession*} instance """ return self.client @@ -124,7 +69,7 @@ @rtype: C{str} """ - return self.hostname + return self.fake_hostname or self.hostname def get_hostname_name(self): """\ @@ -134,7 +79,10 @@ @rtype: C{str} """ - return self.get_hostname().split(':')[0].lstrip('[').rstrip(']') + if ":" in self.get_hostname(): + return self.get_hostname().split(':')[0].lstrip('[').rstrip(']') + else: + return self.get_hostname().lstrip('[').rstrip(']') def get_hostname_port(self): """\ @@ -144,7 +92,10 @@ @rtype: C{str} """ - return self.get_hostname().split(':')[1] + if ":" in self.get_hostname(): + return int(self.get_hostname().split(':')[1]) + else: + return 22 def get_key(self): """\ @@ -160,7 +111,7 @@ """\ Retrieve the host key name of the server to be validated. - @return: host key name (RSA, DSA, ...) + @return: host key name (RSA, DSA, ECDSA...) @rtype: C{str} """ @@ -196,6 +147,110 @@ return _colon_fingerprint.rstrip(':') +class X2GoAutoAddPolicy(X2GoMissingHostKeyPolicy): + + def missing_host_key(self, client, hostname, key): + self.client = client + self.hostname = hostname + self.key = key + if self.session_instance and self.session_instance.control_session.unique_hostkey_aliases: + self.client._host_keys.add(self.session_instance.get_profile_id(), self.key.get_name(), self.key) + else: + self.client._host_keys.add(self.get_hostname(), self.key.get_name(), self.key) + if self.client._host_keys_filename is not None: + self.client.save_host_keys(self.client._host_keys_filename) + self.client._log(paramiko.common.DEBUG, 'Adding %s host key for %s: %s' % + (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint()))) + + +class X2GoInteractiveAddPolicy(X2GoMissingHostKeyPolicy): + """\ + Policy for making host key information available to Python X2Go after a + Paramiko/SSH connect has been attempted. This class needs information + about the associated L{X2GoSession} instance. + + Once called, the L{missing_host_key} method of this class will try to call + L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined + in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()}, + which then will return C{True} by default if not customized in your application. + + To accept host key checks, make sure to either customize the + L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()} + method and hook some interactive user dialog to either of them. + + """ + def missing_host_key(self, client, hostname, key): + """\ + Handle a missing host key situation. This method calls + + Once called, the L{missing_host_key} method will try to call + L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined + in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()}, + which then will return C{True} by default if not customized in your application. + + To accept host key checks, make sure to either customize the + L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()} + method and hook some interactive user dialog to either of them. + + @param client: SSH client (C{X2GoControlSession*}) instance + @type client: C{X2GoControlSession*} instance + @param hostname: remote hostname + @type hostname: C{str} + @param key: host key to validate + @type key: Paramiko/SSH key instance + + @raise X2GoHostKeyException: if the X2Go server host key is not in the C{known_hosts} file + @raise X2GoSSHProxyHostKeyException: if the SSH proxy host key is not in the C{known_hosts} file + @raise SSHException: if this instance does not know its {self.session_instance} + + """ + self.client = client + self.hostname = hostname + if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1): + # if hostname is an IPv4 quadruple with standard SSH port... + self.hostname = '[%s]:22' % self.hostname + self.key = key + self.client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' % + (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint()))) + if self.session_instance: + + if self.fake_hostname is not None: + server_key = client.get_transport().get_remote_server_key() + keytype = server_key.get_name() + our_server_key = client._system_host_keys.get(self.fake_hostname, {}).get(keytype, None) + if our_server_key is None: + if self.session_instance.control_session.unique_hostkey_aliases: + our_server_key = client._host_keys.get(self.session_instance.get_profile_id(), {}).get(keytype, None) + if our_server_key is not None: + self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the X2Go session profile ID of profile ,,%s\'\'.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons(), self.session_instance.profile_name), loglevel=log.loglevel_NOTICE) + return + else: + our_server_key = client._host_keys.get(self.fake_hostname, {}).get(keytype, None) + if our_server_key is not None: + self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the address it has behind the SSH proxy host.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) + return + + self.session_instance.logger('SSH host key verification for host %s with %s fingerprint ,,%s\'\' initiated. We are seeing this X2Go server for the first time.' % (self.get_hostname(), self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) + _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(), + port=self.get_hostname_port(), + fingerprint=self.get_key_fingerprint_with_colons(), + fingerprint_type=self.get_key_name(), + ) + if _valid: + if self.session_instance.control_session.unique_hostkey_aliases and type(self.caller) not in (sshproxy.X2GoSSHProxy, ): + paramiko.AutoAddPolicy().missing_host_key(client, self.session_instance.get_profile_id(), key) + else: + paramiko.AutoAddPolicy().missing_host_key(client, self.get_hostname(), key) + + else: + if type(self.caller) in (sshproxy.X2GoSSHProxy, ): + raise x2go_exceptions.X2GoSSHProxyHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname()) + else: + raise x2go_exceptions.X2GoHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname()) + else: + raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % self.get_hostname()) + + def check_ssh_host_key(x2go_sshclient_instance, hostname, port=22): """\ Perform a Paramiko/SSH host key check by connecting to the host and @@ -203,21 +258,24 @@ connect process). @param x2go_sshclient_instance: a Paramiko/SSH client instance to be used for testing host key validity. - @type x2go_sshclient_instance: C{X2goControlSession*} instance + @type x2go_sshclient_instance: C{X2GoControlSession*} instance @param hostname: hostname of server to validate @type hostname: C{str} @param port: port of server to validate @type port: C{int} + @return: returns a tuple with the following components (, , , , ) @rtype: C{tuple} + @raise SSHException: if an SSH exception occurred, that we did not provocate in L{X2GoInteractiveAddPolicy.missing_host_key()} + """ _hostname = hostname _port = port _fingerprint = 'NO-FINGERPRINT' _fingerprint_type = 'SOME-KEY-TYPE' - _check_policy = X2goInteractiveAddPolicy() + _check_policy = X2GoInteractiveAddPolicy() x2go_sshclient_instance.set_missing_host_key_policy(_check_policy) host_ok = False diff -Nru python-x2go-0.1.1.8/x2go/cleanup.py python-x2go-0.5.0.6/x2go/cleanup.py --- python-x2go-0.1.1.8/x2go/cleanup.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/cleanup.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -A recommended X2go session clean up helper function. +A recommended X2Go session clean up helper function. """ @@ -26,7 +26,7 @@ import paramiko import threading -# Python X2go modules +# Python X2Go modules import guardian import rforward from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS @@ -37,8 +37,8 @@ def x2go_cleanup(e=None, threads=None): """\ - For every Python X2go application you write, please make sure to - capture the C{KeyboardInterrupt} and the C{SystemExit} exceptions and + For every Python X2Go application you write, please make sure to + capture the C{KeyboardInterrupt} and the C{SystemExit} exceptions and call this function if either of the exceptions occurs. Example:: @@ -46,7 +46,7 @@ import x2go try: - my_x2goclient = x2go.X2goClient(...) + my_x2goclient = x2go.X2GoClient(...) [... your code ...] @@ -54,8 +54,8 @@ except (KeyboardInterrupt, SystemExit): x2go.x2go_cleanup() - @param e: if L{x2go_cleanup} got called as you caught an exception in your code this can be the - C{Exception} that we will process at the end of the clean-up (or if clean-up failed or was not + @param e: if L{x2go_cleanup} got called as you caught an exception in your code this can be the + C{Exception} that we will process at the end of the clean-up (or if clean-up failed or was not appropriate) @type e: C{exception} @param threads: a list of threads to clean up @@ -68,13 +68,13 @@ else: threads = threads - # stop X2go reverse forwarding tunnels + # stop X2Go reverse forwarding tunnels for t in threads: - if type(t) == rforward.X2goRevFwTunnel: + if type(t) == rforward.X2GoRevFwTunnel: t.stop_thread() del t - # stop X2go paramiko transports used by X2goTerminalSession objects + # stop X2Go paramiko transports used by X2GoTerminalSession objects for t in threads: if type(t) == paramiko.Transport: if hasattr(t, '_x2go_session_marker'): @@ -84,24 +84,29 @@ # on Windows: stop the XServer that we evoked if _X2GOCLIENT_OS == 'Windows': for t in threads: - if type(t) == xserver.X2goXServer: + if type(t) == xserver.X2GoXServer: t.stop_thread() - gevent.sleep(1) del t # on Windows: stop the PulseAudio daemon that we evoked if _X2GOCLIENT_OS == 'Windows': for t in threads: - if type(t) == pulseaudio.X2goPulseAudio: + if type(t) == pulseaudio.X2GoPulseAudio: t.stop_thread() - gevent.sleep(1) del t + for t in threads: + if type(t) == guardian.X2GoSessionGuardian: + t.stop_thread() + del t + + gevent.sleep(1) + if e is not None: raise e except KeyboardInterrupt: - # do not allow keyboard interrupts during Python X2go cleanup + # do not allow keyboard interrupts during Python X2Go cleanup pass except SystemExit: # neither do we allow SIGTERM signals during cleanup... diff -Nru python-x2go-0.1.1.8/x2go/client.py python-x2go-0.5.0.6/x2go/client.py --- python-x2go-0.1.1.8/x2go/client.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/client.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,66 +1,67 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -L{X2goClient} is a public API class. Use this class in your Python X2go based -applications. Use it as a parent class for your own object oriented L{X2goClient}'ish +L{X2GoClient} is a public API class. Use this class in your Python X2Go based +applications. Use it as a parent class for your own object oriented L{X2GoClient}'ish class implementation. Supported Features ================== Supported features are: - - X2go multi-session management + - X2Go multi-session management - keep track of initiated sessions - - grant access to X2go client config files: C{settings}, C{printing}, C{sessions} + - grant access to X2Go client config files: C{settings}, C{printing}, C{sessions} and C{xconfig} (Windows only) as normally found in C{~/.x2goclient} - - instantiate an X2go session by a set of Python parameters + - instantiate an X2Go session by a set of Python parameters - load a session profile from x2goclient's C{sessions} configuration file and start the---profile-based pre-configured---session - - sharing of local folders with remote X2go sessions - - enabling and mangaging X2go printing (real printing, viewing as PDF, saving + - sharing of local folders with remote X2Go sessions + - enabling and mangaging X2Go printing (real printing, viewing as PDF, saving to a local folder or executing a custom »print« command - transparent tunneling of audio (Pulseaudio, ESD) - - LDAP support for X2go server clusters (NOT IMPLEMENTED YET) + - sharing of other desktops + - LDAP support for X2Go server clusters (NOT IMPLEMENTED YET) Non-Profile Sessions ==================== - A new non-profile based X2go session within an L{X2goClient} instance is setup in the + A new non-profile based X2Go session within an L{X2GoClient} instance is setup in the following way: - - import the Python X2go module and call the session constructor:: + - import the Python X2Go module and call the session constructor:: import x2go - x2go_client = x2go.X2goClient() + x2go_client = x2go.X2GoClient() - - register a new L{X2goClient} session; this creates an L{X2goSession} instance + - register a new L{X2GoClient} session; this creates an L{X2GoSession} instance and calls its constructor method:: x2go_sess_uuid = x2go_client.register_session() - - connect to the session's remote X2go server (SSH/Paramiko):: + - connect to the session's remote X2Go server (SSH/Paramiko):: x2go_client.connect_session(x2go_sess_uuid) - - via the connected X2go client session you can start or resume a remote - X-windows session on an X2go server now:: + - via the connected X2Go client session you can start or resume a remote + X-windows session on an X2Go server now:: x2go_client.start_session(x2go_sess_uuid) @@ -75,15 +76,15 @@ Profiled Sessions ================= - A new profile based X2go session (i.e. using pre-defined session profiles) within an - L{X2goClient} instance is setup in a much easier way: + A new profile based X2Go session (i.e. using pre-defined session profiles) within an + L{X2GoClient} instance is setup in a much easier way: - - import the Python X2go module and call the session constructor:: + - import the Python X2Go module and call the session constructor:: import x2go - x2go_client = x2go.X2goClient() + x2go_client = x2go.X2GoClient() - - register an X2goClient session based on a pre-configured session profile:: + - register an X2GoClient session based on a pre-configured session profile:: x2go_sess_uuid = x2go_client.register_session(profile_name=) @@ -117,17 +118,15 @@ __NAME__ = 'x2goclient-pylib' #modules -import uuid import copy import sys import types import os -import gevent -# Python X2go modules -from registry import X2goSessionRegistry -from guardian import X2goSessionGuardian -from cache import X2goListSessionsCache +# Python X2Go modules +from registry import X2GoSessionRegistry +from guardian import X2GoSessionGuardian +from cache import X2GoListSessionsCache import x2go_exceptions import log import utils @@ -143,107 +142,114 @@ from defaults import X2GO_SETTINGS_FILENAME as _X2GO_SETTINGS_FILENAME from defaults import X2GO_PRINTING_FILENAME as _X2GO_PRINTING_FILENAME from defaults import X2GO_XCONFIG_FILENAME as _X2GO_XCONFIG_FILENAME +from defaults import PUBAPP_MAX_NO_SUBMENUS as _PUBAPP_MAX_NO_SUBMENUS -from defaults import BACKENDS_CONTROLSESSION as _BACKENDS_CONTROLSESSION -from defaults import BACKENDS_TERMINALSESSION as _BACKENDS_TERMINALSESSION -from defaults import BACKENDS_SERVERSESSIONINFO as _BACKENDS_SERVERSESSIONINFO -from defaults import BACKENDS_SERVERSESSIONLIST as _BACKENDS_SERVERSESSIONLIST -from defaults import BACKENDS_PROXY as _BACKENDS_PROXY -from defaults import BACKENDS_SESSIONPROFILES as _BACKENDS_SESSIONPROFILES -from defaults import BACKENDS_CLIENTSETTINGS as _BACKENDS_CLIENTSETTINGS -from defaults import BACKENDS_CLIENTPRINTING as _BACKENDS_CLIENTPRINTING - -import x2go.backends.control as control -import x2go.backends.terminal as terminal -import x2go.backends.info as info -import x2go.backends.proxy as proxy -import x2go.backends.profiles as profiles -import x2go.backends.settings as settings -import x2go.backends.printing as printing +from defaults import BACKENDS as _BACKENDS if _X2GOCLIENT_OS == 'Windows': - from xserver import X2goClientXConfig, X2goXServer - from pulseaudio import X2goPulseAudio + from xserver import X2GoClientXConfig, X2GoXServer + from pulseaudio import X2GoPulseAudio -class X2goClient(object): +class X2GoClient(object): """\ - The X2goClient implements _THE_ public Python X2go API. With it you can - construct your own X2go client application in Python. + The X2GoClient implements _THE_ public Python X2Go API. With it you can + construct your own X2Go client application in Python. Most methods in this class require that you have registered a session - with a remote X2go server (passing of session options, initialization of the + with a remote X2Go server (passing of session options, initialization of the session object etc.) and connected to it (authentication). For these two steps - use these methods: L{X2goClient.register_session()} and L{X2goClient.connect_session()}. + use these methods: L{X2GoClient.register_session()} and L{X2GoClient.connect_session()}. """ - def __init__(self, - control_backend=control.X2goControlSession, - terminal_backend=terminal.X2goTerminalSession, - info_backend=info.X2goServerSessionInfo, - list_backend=info.X2goServerSessionList, - proxy_backend=proxy.X2goProxy, - profiles_backend=profiles.X2goSessionProfiles, - settings_backend=settings.X2goClientSettings, - printing_backend=printing.X2goClientPrinting, + + lang = 'en' + + def __init__(self, + control_backend=_BACKENDS['X2GoControlSession']['default'], + terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], + info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], + list_backend=_BACKENDS['X2GoServerSessionList']['default'], + proxy_backend=_BACKENDS['X2GoProxy']['default'], + profiles_backend=_BACKENDS['X2GoSessionProfiles']['default'], + settings_backend=_BACKENDS['X2GoClientSettings']['default'], + printing_backend=_BACKENDS['X2GoClientPrinting']['default'], + broker_url=None, + broker_password=None, + broker_noauth=False, client_rootdir=None, sessions_rootdir=None, ssh_rootdir=None, start_xserver=False, start_pulseaudio=False, - use_listsessions_cache=False, + use_cache=False, + use_listsessions_cache=False, auto_update_listsessions_cache=False, auto_update_listdesktops_cache=False, + auto_update_listmounts_cache=False, auto_update_sessionregistry=False, auto_register_sessions=False, + no_auto_reg_pubapp_sessions=False, refresh_interval=5, pulseaudio_installdir=os.path.join(os.getcwd(), 'pulseaudio'), logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param control_backend: X2go control session backend to use - @type control_backend: C{class} - @param terminal_backend: X2go terminal session backend to use - @type terminal_backend: C{class} - @param info_backend: X2go session info backend to use - @type info_backend: C{class} - @param list_backend: X2go session list backend to use - @type list_backend: C{class} - @param proxy_backend: X2go proxy backend to use - @type proxy_backend: C{class} - @param profiles_backend: X2go session profiles backend to use - @type profiles_backend: C{class} - @param settings_backend: X2go client settings backend to use - @type settings_backend: C{class} - @param printing_backend: X2go client printing backend to use - @type printing_backend: C{class} + @param control_backend: X2Go control session backend to use + @type control_backend: C{str} + @param terminal_backend: X2Go terminal session backend to use + @type terminal_backend: C{str} + @param info_backend: X2Go session info backend to use + @type info_backend: C{str} + @param list_backend: X2Go session list backend to use + @type list_backend: C{str} + @param proxy_backend: X2Go proxy backend to use + @type proxy_backend: C{str} + @param profiles_backend: X2Go session profiles backend to use + @type profiles_backend: C{str} + @param settings_backend: X2Go client settings backend to use + @type settings_backend: C{str} + @param printing_backend: X2Go client printing backend to use + @type printing_backend: C{str} + @param broker_url: URL pointing to the X2Go Session Broker + @type broker_url: C{str} + @param broker_password: use this password for authentication against the X2Go Session Broker + @type broker_password: C{str} + @param broker_noauth: accessing the X2Go Session Broker works without credentials + @type broker_noauth: C{bool} @param client_rootdir: client base dir (default: ~/.x2goclient) @type client_rootdir: C{str} @param sessions_rootdir: sessions base dir (default: ~/.x2go) @type sessions_rootdir: C{str} @param ssh_rootdir: ssh base dir (default: ~/.ssh) @type ssh_rootdir: C{str} - @param start_xserver: start XServer when registering an L{X2goClient} instance + @param start_xserver: start XServer when registering an L{X2GoClient} instance @type start_xserver: C{bool} - @param start_pulseaudio: start Pulseaudio daemon when registering an L{X2goClient} instance + @param start_pulseaudio: start Pulseaudio daemon when registering an L{X2GoClient} instance @type start_pulseaudio: C{bool} - @param use_listsessions_cache: activate the X2go session list cache in (L{X2goListSessionsCache}) + @param use_cache: alias for C{use_listsessions_cache} + @type use_cache: C{bool} + @param use_listsessions_cache: activate the X2Go session list cache in (L{X2GoListSessionsCache}) @type use_listsessions_cache: C{bool} - @param auto_update_listsessions_cache: activate automatic updates of the X2go session list cache (L{X2goListSessionsCache}) + @param auto_update_listsessions_cache: activate automatic updates of the X2Go session list cache (L{X2GoListSessionsCache}) @type auto_update_listsessions_cache: C{bool} - @param auto_update_listdesktops_cache: activate automatic updates of desktop lists in (L{X2goListSessionsCache}) + @param auto_update_listdesktops_cache: activate automatic updates of desktop lists in (L{X2GoListSessionsCache}) @type auto_update_listdesktops_cache: C{bool} - @param auto_update_sessionregistry: activate automatic updates of the X2go session registry + @param auto_update_listmounts_cache: activate automatic updates of mount lists in (L{X2GoListSessionsCache}) + @type auto_update_listmounts_cache: C{bool} + @param auto_update_sessionregistry: activate automatic updates of the X2Go session registry @type auto_update_sessionregistry: C{bool} - @param auto_register_sessions: activate automatic X2go session registration + @param auto_register_sessions: activate automatic X2Go session registration @type auto_register_sessions: C{bool} - @param refresh_interval: refresh session list cache and session status every C{} seconds + @param no_auto_reg_pubapp_sessions: skip automatic X2Go session registration for suspended/running published applications sessions + @type no_auto_reg_pubapp_sessions: C{bool} + @param refresh_interval: refresh session list cache and session status every C{refresh_interval} seconds @type refresh_interval: C{int} @param pulseaudio_installdir: install path of Pulseaudio binary @type pulseaudio_installdir: C{str} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goClient} constructor - @type logger: L{X2goLogger} instance - @param loglevel: if no X2goLogger object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoClient} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no X2GoLogger object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} @@ -251,37 +257,43 @@ self.listsessions_cache = None if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self._logger_tag = __NAME__ if self.logger.tag is None: self.logger.tag = self._logger_tag - self.control_backend = control_backend - self.terminal_backend = terminal_backend - self.info_backend = info_backend - self.list_backend = list_backend - self.proxy_backend = proxy_backend - self.profiles_backend = profiles_backend - self.settings_backend = settings_backend - self.printing_backend = printing_backend - - self._detect_backend_classes() - - self.client_rootdir = client_rootdir or os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR) - self.sessions_rootdir = sessions_rootdir or os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR) - self.ssh_rootdir = ssh_rootdir or os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR) + self.control_backend = utils._get_backend_class(control_backend, "X2GoControlSession") + self.terminal_backend = utils._get_backend_class(terminal_backend, "X2GoTerminalSession") + self.info_backend = utils._get_backend_class(info_backend, "X2GoServerSessionInfo") + self.list_backend = utils._get_backend_class(list_backend, "X2GoServerSessionList") + self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") + if broker_url is not None: + if broker_url.lower().startswith('ssh'): + profiles_backend = 'sshbroker' + elif broker_url.lower().startswith('http'): + profiles_backend = 'httpbroker' + self.profiles_backend = utils._get_backend_class(profiles_backend, "X2GoSessionProfiles") + self.settings_backend = utils._get_backend_class(settings_backend, "X2GoClientSettings") + self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") + + self.client_rootdir = client_rootdir or os.path.normpath(os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR)) + self.sessions_rootdir = sessions_rootdir or os.path.normpath(os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR)) + self.ssh_rootdir = ssh_rootdir or os.path.normpath(os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR)) + + self.client_rootdir = os.path.normpath(self.client_rootdir) + self.sessions_rootdir = os.path.normpath(self.sessions_rootdir) + self.ssh_rootdir = os.path.normpath(self.ssh_rootdir) - self.pulseaudio_installdir = pulseaudio_installdir + self.pulseaudio_installdir = os.path.normpath(pulseaudio_installdir) if self.client_rootdir is not None: self._has_custom_client_rootdir = True _sessions_config_file = os.path.join(self.client_rootdir, _X2GO_SESSIONPROFILES_FILENAME) _settings_config_file = os.path.join(self.client_rootdir, _X2GO_SETTINGS_FILENAME) _printing_config_file = os.path.join(self.client_rootdir, _X2GO_PRINTING_FILENAME) - _xconfig_config_file = os.path.join(self.client_rootdir, _X2GO_XCONFIG_FILENAME) - self.session_profiles = self.profiles_backend(config_files=[_sessions_config_file], logger=self.logger) + self.session_profiles = self.profiles_backend(config_files=[_sessions_config_file], logger=self.logger, broker_url=broker_url, broker_password=broker_password, broker_noauth=broker_noauth) self.client_settings = self.settings_backend(config_files=[_settings_config_file], logger=self.logger) self.client_printing = self.printing_backend(config_files=[_printing_config_file], client_instance=self, logger=self.logger) else: @@ -290,86 +302,191 @@ self.client_printing = self.printing_backend(client_instance=self, logger=self.logger) if _X2GOCLIENT_OS == 'Windows' and start_xserver: + if self.client_rootdir: - self.client_xconfig = X2goClientXConfig(config_files=[_xconfig_config_file], logger=self.logger) + _xconfig_config_file = os.path.join(self.client_rootdir, _X2GO_XCONFIG_FILENAME) + self.client_xconfig = X2GoClientXConfig(config_files=[_xconfig_config_file], logger=self.logger) else: - self.client_xconfig = X2goClientXConfig(logger=self.logger) - if not self.client_xconfig.known_xservers: - self.HOOK_no_known_xserver_found() - elif not self.client_xconfig.running_xservers: + self.client_xconfig = X2GoClientXConfig(logger=self.logger) + + if not self.client_xconfig.installed_xservers: + self.HOOK_no_installed_xservers_found() + else: + + _last_display = None if type(start_xserver) is types.BooleanType: - p_xs = self.client_xconfig.preferred_xserver + p_xs_name = self.client_xconfig.preferred_xserver_names[0] + _last_display = self.client_xconfig.get_xserver_config(p_xs_name)['last_display'] + _new_display = self.client_xconfig.detect_unused_xdisplay_port(p_xs_name) + p_xs = (p_xs_name, self.client_xconfig.get_xserver_config(p_xs_name)) elif type(start_xserver) is types.StringType: + _last_display = self.client_xconfig.get_xserver_config(start_xserver)['last_display'] + _new_display = self.client_xconfig.detect_unused_xdisplay_port(start_xserver) p_xs = (start_xserver, self.client_xconfig.get_xserver_config(start_xserver)) - if p_xs is not None: - self.xserver = X2goXServer(p_xs[0], p_xs[1], logger=self.logger) - else: - # presume the running XServer listens on :0 - os.environ.update({'DISPLAY': 'localhost:0'}) + + if not self.client_xconfig.running_xservers: + + if p_xs is not None: + self.xserver = X2GoXServer(p_xs[0], p_xs[1], logger=self.logger) + + else: + + if p_xs is not None and _last_display is not None: + if _last_display == _new_display: + # + # FIXME: this trick is nasty, client implementation should rather cleanly shutdown launch X-server processes + # + # re-use a left behind X-server instance of a previous/crashed run of Python X2Go Client + self.logger('found a running (and maybe stray) X-server, trying to re-use it on X DISPLAY port: %s' % _last_display, loglevel=log.loglevel_WARN) + os.environ.update({'DISPLAY': str(_last_display)}) + else: + # presume the running XServer listens on :0 + self.logger('using fallback display for X-server: localhost:0', loglevel=log.loglevel_WARN) + os.environ.update({'DISPLAY': 'localhost:0'}) if _X2GOCLIENT_OS == 'Windows' and start_pulseaudio: - self.pulseaudio = X2goPulseAudio(path=self.pulseaudio_installdir, client_instance=self, logger=self.logger) + self.pulseaudio = X2GoPulseAudio(path=self.pulseaudio_installdir, client_instance=self, logger=self.logger) self.auto_register_sessions = auto_register_sessions - self.session_registry = X2goSessionRegistry(self, logger=self.logger) - self.session_guardian = X2goSessionGuardian(self, auto_update_listsessions_cache=auto_update_listsessions_cache & use_listsessions_cache, + self.no_auto_reg_pubapp_sessions = no_auto_reg_pubapp_sessions + self.session_registry = X2GoSessionRegistry(self, logger=self.logger) + self.session_guardian = X2GoSessionGuardian(self, auto_update_listsessions_cache=auto_update_listsessions_cache & (use_listsessions_cache|use_cache), auto_update_listdesktops_cache=auto_update_listdesktops_cache & use_listsessions_cache, + auto_update_listmounts_cache=auto_update_listmounts_cache & use_listsessions_cache, auto_update_sessionregistry=auto_update_sessionregistry, - auto_register_sessions=auto_register_sessions, + auto_register_sessions=auto_register_sessions, + no_auto_reg_pubapp_sessions=no_auto_reg_pubapp_sessions, refresh_interval=refresh_interval, logger=self.logger ) self.auto_update_sessionregistry = auto_update_sessionregistry if use_listsessions_cache: - self.listsessions_cache = X2goListSessionsCache(self, logger=self.logger) + self.listsessions_cache = X2GoListSessionsCache(self, logger=self.logger) - self.use_listsessions_cache = use_listsessions_cache + self.use_listsessions_cache = use_listsessions_cache | use_cache self.auto_update_listsessions_cache = auto_update_listsessions_cache self.auto_update_listdesktops_cache = auto_update_listdesktops_cache + self.auto_update_listmounts_cache = auto_update_listmounts_cache + + def HOOK_profile_auto_connect(self, profile_name='UNKNOWN'): + """\ + HOOK method: called if a session demands to auto connect the session profile. + + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + + """ + self.logger('HOOK_profile_auto_connect: profile ,,%s'' wants to be auto-connected to the X2Go server.' % profile_name, loglevel=log.loglevel_WARN) + + def HOOK_broker_connection_exception(self, profile_name='UNKNOWN'): + """\ + HOOK method: called if a session demands to auto connect the session profile. + + @param profile_name: profile name of a session that triggered this hook method + @type profile_name: C{str} + + """ + self.logger('HOOK_broker_connection_exception: a broker connection problem occurred triggered by an action on profile ,,%s''.' % profile_name, loglevel=log.loglevel_WARN) + + def HOOK_broker_ignore_connection_problems(self, profile_name='UNKNOWN', is_profile_connected=False): + """\ + HOOK method: called after a broker connection failed for a certain profile. This hook can + be used to allow the user to decide how to proceed after connection problems with the broker. + + @param profile_name: profile name of a session that triggered this hook method + @type profile_name: C{str} + @param is_profile_connected: C{True} if the given session profile is already conneced to the server + @type is_profile_connected: C{bool} + + @return: If this hook returns C{True}, the session startup/resumption will be continued, even if the + broker connection is down. (Default: broker connection problems cause session start-up to fail). + @rtype: C{bool} + + """ + self.logger('HOOK_broker_ignore_connection_problems: use this hook to let the user to decide how to proceed on connection failures (profile name: %s, connected: %s)' % (profile_name, is_profile_connected), loglevel=log.loglevel_WARN) + return False - # user hooks for detecting/notifying what happened during application runtime def HOOK_session_startup_failed(self, profile_name='UNKNOWN'): """\ HOOK method: called if the startup of a session failed. + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + """ self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s'' failed.' % profile_name, loglevel=log.loglevel_WARN) + def HOOK_desktop_sharing_denied(self, profile_name='UNKNOWN'): + """\ + HOOK method: called if the startup of a shadow session was denied by the other user. + + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + + """ + self.logger('HOOK_desktop_sharing_failed: desktop sharing for profile ,,%s'' was denied by the other user.' % profile_name, loglevel=log.loglevel_WARN) + + def HOOK_list_desktops_timeout(self, profile_name='UNKNOWN'): + """\ + HOOK method: called if the x2golistdesktops command generates a timeout due to long execution time. + + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + + """ + self.logger('HOOK_list_desktops_timeout: the server-side x2golistdesktops command for session profile %s took too long to return results. This can happen from time to time, please try again.' % profile_name, loglevel=log.loglevel_WARN) + + def HOOK_no_such_desktop(self, profile_name='UNKNOWN', desktop='UNKNOWN'): + """\ + HOOK method: called if it is tried to connect to a (seen before) sharable desktop that's not available (anymore). + + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + @param desktop: desktop identifier (the X session's $DISPLAY) + @type desktop: C{str} + + """ + self.logger('HOOK_no_such_desktop: the desktop %s (via session profile %s) is not available for sharing (anymore).' % (desktop, profile_name), loglevel=log.loglevel_WARN) + def HOOK_no_known_xserver_found(self): + self.logger('DEPRECATION WARNING: The hook method HOOK_no_known_xserver_found is obsolete. Use HOOK_no_installed_xservers_found instead', loglevel=log.loglevel_WARN) + self.HOOk_no_installed_xservers_found() + + def HOOK_no_installed_xservers_found(self): """\ - HOOK method: called if the Python X2go module could not find any usable XServer - application to start. You will not be able to start X2go sessions without an XServer. + HOOK method: called if the Python X2Go module could not find any usable XServer + application to start. You will not be able to start X2Go sessions without an XServer. """ - self.logger('the Python X2go module could not find any usable XServer application, you will not be able to start X2go sessions without an XServer', loglevel=log.loglevel_WARN) + self.logger('the Python X2Go module could not find any usable XServer application, you will not be able to start X2Go sessions without an XServer', loglevel=log.loglevel_WARN) def HOOK_open_print_dialog(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ - HOOK method: called if an incoming print job has been detected by L{X2goPrintQueue} and a print dialog box is + HOOK method: called if an incoming print job has been detected by L{X2GoPrintQueue} and a print dialog box is requested. @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ - self.logger('HOOK_open_print_dialog: incoming print job detected by X2goClient hook method', loglevel=log.loglevel_WARN) + self.logger('HOOK_open_print_dialog: incoming print job detected by X2GoClient hook method', loglevel=log.loglevel_WARN) def HOOK_no_such_command(self, cmd, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ - HOOK: the command is not available on the connected X2go server. + HOOK: the command is not available on the connected X2Go server. @param cmd: the command that failed @type cmd: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ - self.logger('HOOK_no_such_command: the command %s is not available for X2go server (profile: %s, session: %s)' % (cmd, profile_name, session_name), loglevel=log.loglevel_WARN) + self.logger('HOOK_no_such_command: the command %s is not available for X2Go server (profile: %s, session: %s)' % (cmd, profile_name, session_name), loglevel=log.loglevel_WARN) def HOOK_open_mimebox_saveas_dialog(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ @@ -379,11 +496,11 @@ @type filename: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ - self.logger('HOOK_open_mimebox_saveas_dialog: incoming MIME box job ,, %s'' detected by X2goClient hook method' % filename, loglevel=log.loglevel_WARN) + self.logger('HOOK_open_mimebox_saveas_dialog: incoming MIME box job ,, %s'' detected by X2GoClient hook method' % filename, loglevel=log.loglevel_WARN) def HOOK_printaction_error(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN', err_msg='GENERIC_ERROR', printer=None): """\ @@ -393,7 +510,7 @@ @type filename: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} @param err_msg: if available, an appropriate error message @type err_msg: C{str} @@ -406,7 +523,7 @@ else: self.logger('HOOK_printaction_error: incoming print job ,, %s'' caused error: %s' % (filename, err_msg), loglevel=log.loglevel_ERROR) - def HOOK_check_host_dialog(self, profile_name='UNKNOWN', host='UNKNOWN', port=22, fingerprint='no fingerprint', fingerprint_type='RSA'): + def HOOK_check_host_dialog(self, profile_name='UNKNOWN', host='UNKNOWN', port=22, fingerprint='no fingerprint', fingerprint_type='UNKNOWN'): """\ HOOK method: called if a host check is requested. This hook has to either return C{True} (default) or C{False}. @@ -420,6 +537,7 @@ @type fingerprint: C{str} @param fingerprint_type: finger print type (like RSA, DSA, ...) @type fingerprint_type: C{str} + @return: if host validity is verified, this hook method should return C{True} @rtype: C{bool} @@ -438,6 +556,18 @@ """ self.logger('HOOK_on_control_session_death: the control session of profile %s has died unexpectedly' % profile_name, loglevel=log.loglevel_WARN) + def HOOK_on_failing_SFTP_client(self, profile_name, session_name): + """\ + HOOK method: called SFTP client support is unavailable for the session. + + @param profile_name: profile name of the session that experiences failing SFTP client support + @type profile_name: C{str} + @param session_name: name of session experiencing failing SFTP client support + @type session_name: C{str} + + """ + self.logger('HOOK_on_failing_SFTP_client: new session for profile %s will lack SFTP client support. Check your server setup. Avoid echoing ~/.bashrc files on server.' % profile_name, loglevel=log.loglevel_ERROR) + def HOOK_pulseaudio_not_supported_in_RDPsession(self): """HOOK method: called if trying to run the Pulseaudio daemon within an RDP session, which is not supported by Pulseaudio.""" self.logger('HOOK_pulseaudio_not_supported_in_RDPsession: The pulseaudio daemon cannot be used within RDP sessions', loglevel=log.loglevel_WARN) @@ -456,11 +586,11 @@ @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ - self.logger('HOOK_on_sound_tunnel_failed: setting up X2go sound for %s (%s) support failed' % (profile_name, session_name)) + self.logger('HOOK_on_sound_tunnel_failed: setting up X2Go sound for %s (%s) support failed' % (profile_name, session_name)) def HOOK_rforward_request_denied(self, profile_name='UNKNOWN', session_name='UNKNOWN', server_port=0): """\ @@ -468,39 +598,46 @@ @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} @param server_port: remote server port (starting point of reverse forwarding tunnel) @type server_port: C{str} """ - self.logger('TCP port (reverse) forwarding request for session %s to server port %s has been denied by the X2go server. This is a common issue with SSH, it might help to restart the X2go server\'s SSH daemon.' % (session_name, server_port), loglevel=log.loglevel_WARN) + self.logger('TCP port (reverse) forwarding request for session %s to server port %s has been denied by the X2Go server. This is a common issue with SSH, it might help to restart the X2Go server\'s SSH daemon.' % (session_name, server_port), loglevel=log.loglevel_WARN) - def HOOK_forwarding_tunnel_setup_failed(self, profile_name='UNKNOWN', session_name='UNKNOWN', chain_host='UNKNOWN', chain_port=0): + def HOOK_forwarding_tunnel_setup_failed(self, profile_name='UNKNOWN', session_name='UNKNOWN', chain_host='UNKNOWN', chain_port=0, subsystem=None): """\ HOOK method: called if a port forwarding tunnel setup failed. @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} @param chain_host: hostname of chain host (forwarding tunnel end point) @type chain_host: C{str} @param chain_port: port of chain host (forwarding tunnel end point) @type chain_port: C{str} + @param subsystem: information on the subsystem that provoked this hook call + @type subsystem: C{str} """ - self.logger('Forwarding tunnel request to [%s]:%s for session %s (%s) was denied by remote X2go/SSH server. Session startup failed.' % (chain_host, chain_port, self.session_name, self.profile_name), loglevel=log.loglevel_ERROR) + if type(subsystem) in (types.StringType, types.UnicodeType): + _subsystem = '(%s) ' % subsystem + else: + _subsystem = '' + + self.logger('Forwarding tunnel request to [%s]:%s for session %s (%s) was denied by remote X2Go/SSH server. Subsystem %s startup failed.' % (chain_host, chain_port, session_name, profile_name, _subsystem), loglevel=log.loglevel_ERROR) def HOOK_on_session_has_started_by_me(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ - HOOK method: called if a session has been started by this instance of L{X2goClient}. + HOOK method: called if a session has been started by this instance of L{X2GoClient}. @param session_uuid: unique session identifier of the calling session @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ @@ -514,7 +651,7 @@ @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ @@ -522,13 +659,13 @@ def HOOK_on_session_has_resumed_by_me(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ - HOOK method: called if a session has been resumed by this instance of L{X2goClient}. + HOOK method: called if a session has been resumed by this instance of L{X2GoClient}. @param session_uuid: unique session identifier of the calling session @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ @@ -542,7 +679,7 @@ @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ @@ -556,7 +693,7 @@ @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ @@ -564,13 +701,13 @@ def HOOK_on_session_has_been_suspended(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ - HOOK method: called if a session has been suspended by this instance of L{X2goClient}. + HOOK method: called if a session has been suspended by this instance of L{X2GoClient}. @param session_uuid: unique session identifier of the calling session @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ @@ -584,115 +721,74 @@ @type session_uuid: C{str} @param profile_name: profile name of session that called this hook method @type profile_name: C{str} - @param session_name: X2go session name + @param session_name: X2Go session name @type session_name: C{str} """ self.logger('HOOK_on_session_has_terminated (session_uuid: %s, profile_name: %s): session %s has terminated' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) - def _detect_backend_classes(self): - # CONTROL session backend - if type(self.control_backend) is types.StringType: - try: - _classname = _BACKENDS_CONTROLSESSION[self.control_backend] - except KeyError: - if self.control_backend in _BACKENDS_CONTROLSESSION.values(): - _classname = self.control_backend - else: - raise x2go_exceptions.X2goBackendException('unknown control session backend name %s' % self.control_backend) - self.control_backend = eval('control.%s' % _classname) + def HOOK_printing_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): + """\ + HOOK method: called if X2Go client-side printing is not available. - # TERMINAL session backend - if type(self.terminal_backend) is types.StringType: - try: - _classname = _BACKENDS_TERMINALSESSION[self.terminal_backend] - except KeyError: - if self.terminal_backend in _BACKENDS_TERMINALSESSION.values(): - _classname = self.terminal_backend - else: - raise x2go_exceptions.X2goBackendException('unknown terminal session backend name %s' % self.terminal_backend) - self.terminal_backend = eval('terminal.%s' % _classname) + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + @param session_name: X2Go session name + @type session_name: C{str} - # PROXY session backend - if type(self.proxy_backend) is types.StringType: - try: - _classname = _BACKENDS_PROXY[self.proxy_backend] - except KeyError: - if self.proxy_backend in _BACKENDS_PROXY.values(): - _classname = self.proxy_backend - else: - raise x2go_exceptions.X2goBackendException('unknown proxy backend name %s' % self.proxy_backend) - self.proxy_backend = eval('proxy.%s' % _classname) + """ + self.logger('HOOK_foldersharing_not_available: X2Go\'s client-side printing feature is not available with this session (%s) of profile %s.' % (session_name, profile_name), loglevel=log.loglevel_WARN) - # server session info backend - if type(self.info_backend) is types.StringType: - try: - _classname = _BACKENDS_SERVERSESSIONINFO[self.info_backend] - except KeyError: - if self.info_backend in _BACKENDS_SERVERSESSIONINFO.values(): - _classname = self.info_backend - else: - raise x2go_exceptions.X2goBackendException('unknown server session info backend name %s' % self.info_backend) - self.info_backend = eval('info.%s' % _classname) + def HOOK_mimebox_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): + """\ + HOOK method: called if the X2Go MIME box is not available. - # server session list backend - if type(self.list_backend) is types.StringType: - try: - _classname = _BACKENDS_SERVERSESSIONLIST[self.list_backend] - except KeyError: - if self.list_backend in _BACKENDS_SERVERSESSIONLIST.values(): - _classname = self.list_backend - else: - raise x2go_exceptions.X2goBackendException('unknown server session info backend name %s' % self.list_backend) - self.list_backend = eval('info.%s' % _classname) + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + @param session_name: X2Go session name + @type session_name: C{str} - # session profiles backend - if type(self.profiles_backend) is types.StringType: - try: - _classname = _BACKENDS_SESSIONPROFILES[self.profiles_backend] - except KeyError: - if self.profiles_backend in _BACKENDS_SESSIONPROFILES.values(): - _classname = self.profiles_backend - else: - raise x2go_exceptions.X2goBackendException('unknown session profiles backend name %s' % self.profiles_backend) - self.profiles_backend = eval('profiles.%s' % _classname) + """ + self.logger('HOOK_mimebox_not_available: X2Go\'s MIME box feature is not available with this session (%s) of profile %s.' % (session_name, profile_name), loglevel=log.loglevel_WARN) - # client settings backend - if type(self.settings_backend) is types.StringType: - try: - _classname = _BACKENDS_CLIENTSETTINGS[self.settings_backend] - except KeyError: - if self.settings_backend in _BACKENDS_CLIENTSETTINGS.values(): - _classname = self.settings_backend - else: - raise x2go_exceptions.X2goBackendException('unknown client settings backend name %s' % self.settings_backend) - self.settings_backend = eval('settings.%s' % _classname) + def HOOK_foldersharing_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): + """\ + HOOK method: called if X2Go client-side folder-sharing is not available. - # client printing backend - if type(self.printing_backend) is types.StringType: - try: - _classname = _BACKENDS_CLIENTPRINTING[self.printing_backend] - except KeyError: - if self.printing_backend in _BACKENDS_CLIENTPRINTING.values(): - _classname = self.printing_backend - else: - raise x2go_exceptions.X2goBackendException('unknown client printing backend name %s' % self.printing_backend) - self.printing_backend = eval('printing.%s' % _classname) + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + @param session_name: X2Go session name + @type session_name: C{str} + + """ + self.logger('HOOK_foldersharing_not_available: X2Go\'s client-side folder sharing feature is not available with this session (%s) of profile %s.' % (session_name, profile_name), loglevel=log.loglevel_WARN) + + def HOOK_sshfs_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): + """\ + HOOK method: called if the X2Go server denies SSHFS access. + + @param profile_name: profile name of session that called this hook method + @type profile_name: C{str} + @param session_name: X2Go session name + @type session_name: C{str} + + """ + self.logger('HOOK_sshfs_not_available: the remote X2Go server (%s) denies SSHFS access for session %s. This will result in client-side folder sharing, printing and the MIME box feature being unavailable' % (session_name, profile_name), loglevel=log.loglevel_WARN) def get_client_rootdir(self): """\ - Retrieve the settings root directory of this L{X2goClient} instance. + Retrieve the settings root directory of this L{X2GoClient} instance. - @return: X2go client root directory + @return: X2Go client root directory @rtype: C{str} """ - return self.client_rootdir + return os.path.normpath(self.client_rootdir) __get_client_rootdir = get_client_rootdir @property def has_custom_client_rootdir(self): """\ - Does this L{X2goClient} instance have a customized root dir path? + Does this L{X2GoClient} instance have a customized root dir path? Equals C{True} in case it has. """ @@ -701,29 +797,29 @@ def get_sessions_rootdir(self): """\ - Retrieve the sessions root directory of this L{X2goClient} instance. + Retrieve the sessions root directory of this L{X2GoClient} instance. - @return: X2go sessions root directory + @return: X2Go sessions root directory @rtype: C{str} """ - return self.sessions_rootdir + return os.path.normpath(self.sessions_rootdir) __get_sessions_rootdir = get_sessions_rootdir def get_ssh_rootdir(self): """\ - Retrieve the SSH client root dir used with this L{X2goClient} instance. + Retrieve the SSH client root dir used with this L{X2GoClient} instance. @return: SSH client root directory @rtype: C{str} """ - return self.ssh_rootdir + return os.path.normpath(self.ssh_rootdir) __get_ssh_rootdir = get_ssh_rootdir def get_client_username(self): """\ - Query the local user's username (i.e. the user running the X2go client). + Query the local user's username (i.e. the user running the X2Go client). - @return: the local username this L{X2goClient} instance runs as + @return: the local username this L{X2GoClient} instance runs as @rtype: C{str} """ @@ -732,77 +828,78 @@ def register_all_session_profiles(self, return_objects=False): """\ - Register all session profiles found in the C{sessions} configuration node - as potential X2go sessions. + Register all session profiles found in the C{sessions} configuration node + as potential X2Go sessions. - @param return_objects: if set to C{True} this methods returns a list of L{X2goSession} - instances, otherwise a list of session UUIDs representing the corresponding + @param return_objects: if set to C{True} this methods returns a list of L{X2GoSession} + instances, otherwise a list of session UUIDs representing the corresponding registered sessions is returned @type return_objects: C{bool} - @return: a Python dictionary containing one registered session for each available session profile - configuration, whereas the profile names are used as dictionary keys and L{X2goSession} + @return: a Python dictionary containing one registered session for each available session profile + configuration, whereas the profile names are used as dictionary keys and L{X2GoSession} instances as their values @rtype: C{list} """ sessions = {} for profile_name in self.session_profiles.profile_names: - _obj = self._X2goClient__register_session(profile_name=profile_name, return_object=True) + _obj = self._X2GoClient__register_session(profile_name=profile_name, return_object=True) sessions[_obj.get_profile_name()] = _obj return sessions + __register_all_session_profiles = register_all_session_profiles def register_session(self, server=None, profile_id=None, profile_name=None, session_name=None, - allow_printing=False, - allow_share_local_folders=False, share_local_folders=[], + allow_printing=False, + allow_share_local_folders=False, share_local_folders=[], allow_mimebox=False, mimebox_extensions=[], mimebox_action='OPEN', - add_to_known_hosts=False, known_hosts=None, + add_to_known_hosts=False, known_hosts=None, forward_sshagent=False, proxy_options={}, return_object=False, **kwargs): """\ - Register a new L{X2goSession}. Within one L{X2goClient} - instance you can manage several L{X2goSession} instances on serveral - remote X2go servers under different user names. + Register a new L{X2GoSession}. Within one L{X2GoClient} + instance you can manage several L{X2GoSession} instances on serveral + remote X2Go servers under different user names. - These sessions can be instantiated by passing direct L{X2goSession} + These sessions can be instantiated by passing direct L{X2GoSession} parameters to this method or by specifying the name of an existing session profile - (as found in the L{X2goClient}'s C{sessions} configuration node. + (as found in the L{X2GoClient}'s C{sessions} configuration node. A session profile is a pre-defined set of session options stored in a sessions profile node (e.g. a configuration file). With the FILE backend such session profiles are stored as a file (by default: C{~/.x2goclient/sessions} or globally (for all users on the client) in C{/etc/x2goclient/sessions}). - Python X2go also supports starting multiple X2go sessions for the same + Python X2Go also supports starting multiple X2Go sessions for the same session profile simultaneously. - This method (L{X2goClient.register_session()}) accepts a similar set of parameters - as the L{X2goSession} constructor itself. For a complete set of session options refer + This method (L{X2GoClient.register_session()}) accepts a similar set of parameters + as the L{X2GoSession} constructor itself. For a complete set of session options refer there. - Alternatively, you can also pass a profile name or a profile id - to this method. If you do this, Python X2go tries to find the specified session + Alternatively, you can also pass a profile name or a profile id + to this method. If you do this, Python X2Go tries to find the specified session in the C{sessions} configuration node and then derives the necessary session parameters - from the session profile configuration. Additional L{X2goSession} parameters can + from the session profile configuration. Additional L{X2GoSession} parameters can also be passed to this method---they will override the option values retrieved from the session profile. - @param server: hostname of the remote X2go server + @param server: hostname of the remote X2Go server @type server: C{str} - @param profile_id: id (config section name) of a session profile to load + @param profile_id: id (config section name) of a session profile to load from your session config @type profile_id: C{str} @param profile_name: name of a session profile to load from your session config @type profile_name: C{str} - @param allow_printing: enable X2go printing support for the to-be-registered X2go session + @param allow_printing: enable X2Go printing support for the to-be-registered X2Go session @type allow_printing: C{bool} @param allow_share_local_folders: set local folder sharing to enabled/disabled @type allow_share_local_folders: C{bool} @param share_local_folders: a list of local folders (as strings) to be shared directly after session start up @type share_local_folders: C{list} - @param allow_mimebox: enable X2go MIME box support for the to-be-registered X2go session + @param allow_mimebox: enable X2Go MIME box support for the to-be-registered X2Go session @type allow_mimebox: C{bool} @param mimebox_extensions: MIME box support is only allowed for the given file extensions @type mimebox_extensions: C{list} @@ -813,35 +910,59 @@ @type add_to_known_hosts: C{bool} @param known_hosts: full path to C{known_hosts} file @type known_hosts: C{str} - @param proxy_options: a set of very C{X2goProxy*} backend specific options; any option that is not known - to the C{X2goProxy*} backend will simply be ignored + @param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side + @type forward_sshagent: C{bool} + @param proxy_options: a set of very C{X2GoProxy*} backend specific options; any option that is not known + to the C{X2GoProxy*} backend will simply be ignored @type proxy_options: C{dict} - @param return_object: normally this method returns a unique session UUID. If - C{return_object} is set to C{True} an X2goSession object will be returned + @param return_object: normally this method returns a unique session UUID. If + C{return_object} is set to C{True} an X2GoSession object will be returned instead @type return_object: C{bool} - @param kwargs: any option that is also valid for the L{X2goSession} constructor + @param kwargs: any option that is also valid for the L{X2GoSession} constructor @type kwargs: C{dict} - @return: a unique identifier (UUID) for the newly registered X2go session (or an - X2goSession object if C{return_object} is set to True + @return: a unique identifier (UUID) for the newly registered X2Go session (or an + X2GoSession object if C{return_object} is set to True @rtype: C{str} """ - if known_hosts is None: - known_hosts = os.path.join(_LOCAL_HOME, self.ssh_rootdir, 'known_hosts') - + _p = None + # detect profile name and profile id properly if profile_id and self.session_profiles.has_profile_id(profile_id): _p = profile_id elif profile_name and self.session_profiles.has_profile_name(profile_name): _p = profile_name - else: - _p = None - + elif profile_id: + try: + _p = self.session_profiles.check_profile_id_or_name(profile_id) + except x2go_exceptions.X2GoProfileException: + pass + elif profile_name: + try: + _p = self.session_profiles.check_profile_id_or_name(profile_name) + except x2go_exceptions.X2GoProfileException: + pass if _p: - _profile_id = self.session_profiles.check_profile_id_or_name(_p) _profile_name = self.session_profiles.to_profile_name(_profile_id) + else: + _profile_id = None + + # test if session_name has already been registered. If yes, return it immediately. + if type(session_name) is types.StringType: + _retval = self.get_session_of_session_name(session_name, return_object=return_object, match_profile_name=profile_name) + if _retval is not None: + return _retval + + if known_hosts is None: + known_hosts = os.path.join(_LOCAL_HOME, self.ssh_rootdir, 'known_hosts') + + if _profile_id: + + # initialize session profile cache + self.session_profiles.init_profile_cache(_profile_id) + _params = self.session_profiles.to_session_params(profile_id=_profile_id) del _params['profile_name'] @@ -850,7 +971,27 @@ if k in kwargs.keys(): _params[k] = kwargs[k] - server = _params['server'] + _pkey = None + try: + server = self.session_profiles.get_server_hostname(_profile_id) + _params['port'] = self.session_profiles.get_server_port(_profile_id) + _pkey = self.session_profiles.get_pkey_object(_profile_id) + except x2go_exceptions.X2GoBrokerConnectionException, e: + _profile_name = self.to_profile_name(_profile_id) + self.HOOK_broker_connection_exception(_profile_name) + if not self.HOOK_broker_ignore_connection_problems(_profile_name, is_profile_connected=self.is_profile_connected(_profile_name)): + raise e + server = self.session_profiles.get_profile_config(_profile_name, parameter='host')[0] + _params['port'] = self.session_profiles.get_profile_config(_profile_name, parameter='sshport') + + if _pkey is not None: + self.logger('received PKey object for authentication, ignoring all other auth mechanisms', log.loglevel_NOTICE, tag=self._logger_tag) + _params['pkey'] = _pkey + _params['sshproxy_pkey'] = _pkey + _params['allow_agent'] = False + _params['look_for_keys'] = False + _params['key_filename'] = [] + del _params['server'] _params['client_instance'] = self @@ -860,7 +1001,7 @@ _profile_id = utils._genSessionProfileId() _profile_name = profile_name or sys.argv[0] _params = kwargs - _params['printing'] = printing + _params['printing'] = allow_printing _params['allow_share_local_folders'] = allow_share_local_folders _params['share_local_folders'] = share_local_folders _params['allow_mimebox'] = allow_mimebox @@ -868,6 +1009,7 @@ _params['mimebox_action'] = mimebox_action _params['client_instance'] = self _params['proxy_options'] = proxy_options + _params['forward_sshagent'] = forward_sshagent session_uuid = self.session_registry.register(server=server, profile_id=_profile_id, profile_name=_profile_name, @@ -887,7 +1029,7 @@ known_hosts=known_hosts, **_params) - self.logger('initializing X2go session...', log.loglevel_NOTICE, tag=self._logger_tag) + self.logger('initializing X2Go session...', log.loglevel_NOTICE, tag=self._logger_tag) if return_object: return self.session_registry(session_uuid) else: @@ -895,32 +1037,33 @@ __register_session = register_session ### - ### WRAPPER METHODS FOR X2goSessionRegistry objects + ### WRAPPER METHODS FOR X2GoSessionRegistry objects ### def get_session_summary(self, session_uuid): """\ Retrieves a Python dictionary, containing a short session summary (session status, names, etc.) - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} """ return self.session_registry.session_summary(session_uuid) + __get_session_summary = get_session_summary ### - ### WRAPPER METHODS FOR X2goSession objects + ### WRAPPER METHODS FOR X2GoSession objects ### def get_session_username(self, session_uuid): """\ - After an L{X2goSession} has been set up you can query the + After an L{X2GoSession} has been set up you can query the username that the remote sessions runs as. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: the remote username the X2go session runs as + @return: the remote username the X2Go session runs as @rtype: C{str} """ @@ -933,11 +1076,11 @@ hostname of the host the session is connected to (or about to connect to). - @param session_uuid: the X2go sessions UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: the host an X2go session is connected to - (as an C{(addr,port)} tuple) + @return: the host an X2Go session is connected to + (as an C{(addr,port)} tuple) @rtype: tuple """ @@ -947,13 +1090,13 @@ def get_session_server_hostname(self, session_uuid): """\ Retrieve the server hostname as provided by the calling - application (e.g. like it has been specified in the session + application (e.g. like it has been specified in the session profile). - @param session_uuid: the X2go sessions UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: the hostname for the queried X2go session as specified + @return: the hostname for the queried X2Go session as specified by the calling application @rtype: str @@ -963,13 +1106,13 @@ def get_session(self, session_uuid): """\ - Retrieve the complete L{X2goSession} object that has been + Retrieve the complete L{X2GoSession} object that has been registered under the given session registry hash. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: the L{X2goSession} instance + @return: the L{X2GoSession} instance @rtype: obj """ @@ -978,47 +1121,94 @@ with_session = __get_session """Alias for L{get_session()}.""" - def get_session_of_session_name(self, session_name, return_object=False): + def get_session_of_session_name(self, session_name, return_object=False, match_profile_name=None): """\ - Retrieve session UUID or L{X2goSession} for session name + Retrieve session UUID or L{X2GoSession} for session name from the session registry. - @param session_name: the X2go session's UUID registry hash + @param session_name: the X2Go session's UUID registry hash @type session_name: C{str} - @param return_object: session UUID hash or L{X2goSession} instance wanted? + @param return_object: session UUID hash or L{X2GoSession} instance wanted? @type return_object: C{bool} + @param match_profile_name: only return sessions that match this profile name + @type match_profile_name: C{str} - @return: the X2go session's UUID registry hash or L{X2goSession} instance - @rtype: C{str} or L{X2goSession} instance + @return: the X2Go session's UUID registry hash or L{X2GoSession} instance + @rtype: C{str} or L{X2GoSession} instance """ try: - return self.session_registry.get_session_of_session_name(session_name=session_name, return_object=return_object) - except X2goSessionExceptionRegistryException: + return self.session_registry.get_session_of_session_name(session_name=session_name, return_object=return_object, match_profile_name=match_profile_name) + except x2go_exceptions.X2GoSessionRegistryException: return None __get_session_of_session_name = get_session_of_session_name def get_session_name(self, session_uuid): """\ - Retrieve the server-side X2go session name for the session that has + Retrieve the server-side X2Go session name for the session that has been registered under C{session_uuid}. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: X2go session name + @return: X2Go session name @rtype: C{str} """ return self.session_registry(session_uuid).get_session_name() __get_session_name = get_session_name + def get_session_info(self, session_uuid): + """\ + Retrieve the server-side X2Go session information object for the session that has + been registered under C{session_uuid}. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + + @return: X2Go session info + @rtype: C{obj} + + """ + return self.session_registry(session_uuid).get_session_info() + __get_session_info = get_session_info + + def get_published_applications(self, session_uuid=None, profile_name=None, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=_PUBAPP_MAX_NO_SUBMENUS): + """\ + Retrieve the server-side X2Go published applications menu for the session + registered under C{session_uuid} or for profile name C{profile_name}. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + @param profile_name: a valid session profile name + @type profile_name: C{str} + + @return: a representative of the published applications menu tree + @rtype: C{dict} + + """ + if session_uuid is None and profile_name: + _session_uuids = self._X2GoClient__client_pubapp_sessions_of_profile_name(profile_name, return_objects=False) + if len(_session_uuids): session_uuid = _session_uuids[0] + if session_uuid: + try: + if self.session_registry(session_uuid).is_published_applications_provider(): + return self.session_registry(session_uuid).get_published_applications(lang=lang, refresh=refresh, raw=raw, very_raw=False, max_no_submenus=max_no_submenus) + except x2go_exceptions.X2GoSessionRegistryException: + pass + else: + self.logger('Cannot find a terminal session for profile ,,%s\'\' that can be used to query a published applications menu tree' % profile_name, loglevel=log.loglevel_INFO) + return None + __get_published_applications = get_published_applications + profile_get_published_applications = get_published_applications + __profile_get_published_applications = get_published_applications + def set_session_username(self, session_uuid, username): """\ - Set the session username for the L{X2goSession} that has been registered under C{session_uuid}. + Set the session username for the L{X2GoSession} that has been registered under C{session_uuid}. This can be helpful for modifying user credentials during an authentication phase. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param username: new user name to be used for session authentication @type username: C{str} @@ -1028,12 +1218,13 @@ """ return self.session_registry(session_uuid).set_username(username=username) + __set_session_username = set_session_username def check_session_host(self, session_uuid): """\ - Provide a mechanism to evaluate the validity of an X2go server host. + Provide a mechanism to evaluate the validity of an X2Go server host. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @return: return C{True} if host validation has been successful. @@ -1043,6 +1234,17 @@ return self.session_registry(session_uuid).check_host() __check_session_host = check_session_host + def session_reuses_sshproxy_authinfo(self, session_uuid): + """\ + Check if session with unique identifier is configured to re-use the X2Go session's + password / key for proxy authentication, as well. + + @return: returns C{True} if the session is configured to re-use session password / key for proxy authentication + @rtype: C{bool} + """ + return self.session_registry(session_uuid).reuses_sshproxy_authinfo() + __session_reuses_sshproxy_authinfo = session_reuses_sshproxy_authinfo + def session_uses_sshproxy(self, session_uuid): """\ Check if session with unique identifier is configured to use an @@ -1060,7 +1262,7 @@ Check if the SSH proxy of session with unique identifier is configured adequately to be able to auto-connect to the SSH proxy server (e.g. by public key authentication). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @return: returns C{True} if the session's SSH proxy can auto-connect, C{False} otherwise, C{None} @@ -1074,9 +1276,9 @@ def session_can_auto_connect(self, session_uuid): """\ Check if session with unique identifier is configured adequately - to be able to auto-connect to the X2go server (e.g. by public key authentication). + to be able to auto-connect to the X2Go server (e.g. by public key authentication). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @return: returns C{True} if the session can auto-connect, C{False} otherwise, C{None} @@ -1087,45 +1289,82 @@ return self.session_registry(session_uuid).can_auto_connect() __session_can_auto_connect = session_can_auto_connect + # user hooks for detecting/notifying what happened during application runtime + def session_auto_connect(self, session_uuid): + """\ + Auto-connect a given session. This method is called from within the session itself + and can be used to override the auto-connect procedure from within your + client implementation. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + + @return: returns C{True} if the session could be auto-connected. + @rtype: C{bool} + + """ + self.session_registry(session_uuid).do_auto_connect(redirect_to_client=False) + __session_auto_connect = session_auto_connect + def connect_session(self, session_uuid, - username='', - password='', - sshproxy_user='', - sshproxy_password='', + username=None, + password=None, + passphrase=None, + sshproxy_user=None, + sshproxy_password=None, + sshproxy_passphrase=None, add_to_known_hosts=False, - force_password_auth=False): + force_password_auth=False, + sshproxy_force_password_auth=False, + ): """\ - Connect to a registered X2go session with registry hash C{}. - This method basically wraps around paramiko.SSHClient.connect() for the + Connect to a registered X2Go session with registry hash C{session_uuid} + This method basically wraps around paramiko.SSHClient.connect() for the corresponding session. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param username: user name to be used for session authentication @type username: C{str} - @param password: the user's password for the X2go server that is going to be + @param password: the user's password for the X2Go server that is going to be connected to @type password: C{str} + @param passphrase: a passphrase to use for unlocking + a private key in case the password is already needed for + two-factor authentication + @type passphrase: C{str} @param sshproxy_user: user name to be used for SSH proxy authentication @type sshproxy_user: C{str} @param sshproxy_password: the SSH proxy user's password @type sshproxy_password: C{str} - @param add_to_known_hosts: non-Paramiko option, if C{True} paramiko.AutoAddPolicy() - is used as missing-host-key-policy. If set to C{False} checkhosts.X2goInteractiveAddPolicy() + @param sshproxy_passphrase: a passphrase to use for unlocking + a private key needed for the SSH proxy host in case the sshproxy_password is already needed for + two-factor authentication + @type sshproxy_passphrase: C{str} + @param add_to_known_hosts: non-Paramiko option, if C{True} paramiko.AutoAddPolicy() + is used as missing-host-key-policy. If set to C{False} L{checkhosts.X2GoInteractiveAddPolicy()} is used @type add_to_known_hosts: C{bool} @param force_password_auth: disable SSH pub/priv key authentication mechanisms completely @type force_password_auth: C{bool} + @param sshproxy_force_password_auth: disable SSH pub/priv key authentication mechanisms + completely for SSH proxy connection + @type sshproxy_force_password_auth: C{bool} @return: returns True if this method has been successful @rtype: C{bool} """ - _success = self.session_registry(session_uuid).connect(username=username, password=password, - sshproxy_user=sshproxy_user, sshproxy_password=sshproxy_password, + _success = self.session_registry(session_uuid).connect(username=username, + password=password, + passphrase=passphrase, + sshproxy_user=sshproxy_user, + sshproxy_password=sshproxy_password, + sshproxy_passphrase=sshproxy_passphrase, add_to_known_hosts=add_to_known_hosts, force_password_auth=force_password_auth, + sshproxy_force_password_auth=sshproxy_force_password_auth, ) if self.auto_register_sessions: self.session_registry.register_available_server_sessions(profile_name=self.get_session_profile_name(session_uuid), @@ -1136,9 +1375,9 @@ def disconnect_session(self, session_uuid): """\ - Disconnect an L{X2goSession} by closing down its Paramiko/SSH Transport thread. + Disconnect an L{X2GoSession} by closing down its Paramiko/SSH Transport thread. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} """ self.session_registry(session_uuid).disconnect() @@ -1148,32 +1387,32 @@ def set_session_print_action(self, session_uuid, print_action, **kwargs): """\ - If X2go client-side printing is enable within an X2go session you can use + If X2Go client-side printing is enable within an X2Go session you can use this method to alter the way how incoming print spool jobs are handled/processed. Currently, there are five different print actions available, each defined as an individual print action class: - - B{PDFVIEW} (L{X2goPrintActionPDFVIEW}): view an incoming spool job (a PDF file) + - B{PDFVIEW} (L{X2GoPrintActionPDFVIEW}): view an incoming spool job (a PDF file) locally in a PDF viewer - - B{PDFSAVE} (L{X2goPrintActionPDFSAVE}): save an incoming spool job (a PDF file) + - B{PDFSAVE} (L{X2GoPrintActionPDFSAVE}): save an incoming spool job (a PDF file) under a nice name in a designated folder - - B{PRINT} (L{X2goPrintActionPRINT}): really print the incoming spool job on a real printing device - - B{PRINTCMD} L{X2goPrintActionPRINTCMD}: on each incoming spool job execute an - external command that lets the client user handle the further processing of the + - B{PRINT} (L{X2GoPrintActionPRINT}): really print the incoming spool job on a real printing device + - B{PRINTCMD} L{X2GoPrintActionPRINTCMD}: on each incoming spool job execute an + external command that lets the client user handle the further processing of the print job (PDF) file - - B{DIALOG} (L{X2goPrintActionDIALOG}): on each incoming spool job this print action - will call L{X2goClient.HOOK_open_print_dialog()} + - B{DIALOG} (L{X2GoPrintActionDIALOG}): on each incoming spool job this print action + will call L{X2GoClient.HOOK_open_print_dialog()} Each of the print action classes accepts different print action arguments. For detail - information on these print action arguments please refer to the constructor methods of + information on these print action arguments please refer to the constructor methods of each class individually. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param print_action: one of the named above print actions, either as string or class instance @type print_action: C{str} or C{instance} - @param kwargs: additional information for the given print action (print + @param kwargs: additional information for the given print action (print action arguments), for possible print action arguments and their values see each individual print action class @type kwargs: C{dict} @@ -1182,18 +1421,68 @@ self.session_registry(session_uuid).set_print_action(print_action=print_action, **kwargs) __set_session_print_action = set_session_print_action - def start_session(self, session_uuid): + def set_session_window_title(self, session_uuid, title=''): + """\ + Modify session window title. If the session ID does not occur in the + given title, it will be prepended, so that every X2Go session window + always contains the X2Go session ID of that window. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + @param title: new title for session window + @type title: C{str} + + """ + self.session_registry(session_uuid).set_session_window_title(title=title) + __set_session_window_title = set_session_window_title + + def raise_session_window(self, session_uuid): + """\ + Try to lift the session window above all other windows and bring + it to focus. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + """ + self.session_registry(session_uuid).raise_session_window() + __raise_session_window = raise_session_window + + def session_auto_start_or_resume(self, session_uuid, newest=True, oldest=False, all_suspended=False, start=True): + """\ + Automatically start or resume one or several sessions. + + This method is called from within the session itself on session registration, so this method + can be used to handle auto-start/-resume events. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + @param newest: if resuming, only resume newest/youngest session + @type newest: C{bool} + @param oldest: if resuming, only resume oldest session + @type oldest: C{bool} + @param all_suspended: if resuming, resume all suspended sessions + @type all_suspended: C{bool} + @param start: if no session is to be resumed, start a new session + @type start: C{bool} + + """ + self.session_registry(session_uuid).do_auto_start_or_resume(newest=newest, oldest=oldest, all_suspended=all_suspended, start=start, redirect_to_client=False) + __session_auto_start_or_resume = session_auto_start_or_resume + + def start_session(self, session_uuid, **sessionopts): """\ - Start a new X2go session on the remote X2go server. This method - will open---if everything has been successful till here---the X2go + Start a new X2Go session on the remote X2Go server. This method + will open---if everything has been successful till here---the X2Go session window. Before calling this method you have to register your desired session - with L{register_session} (initialization of session parameters) and + with L{register_session} (initialization of session parameters) and connect to it with L{connect_session} (authentication). - @param session_uuid: the X2go sessions UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @param sessionopts: pass-through of options directly to the session instance's L{X2GoSession.start()} method + @type sessionopts: C{dict} @return: returns True if this method has been successful @rtype: C{bool} @@ -1204,7 +1493,7 @@ self.session_registry.disable_session_auto_registration() # start the actual session - _retval = self.session_registry(session_uuid).start() + _retval = self.session_registry(session_uuid).start(**sessionopts) # re-enable session auto-registration... if self.auto_register_sessions: @@ -1213,11 +1502,11 @@ return _retval __start_session = start_session - def share_desktop_session(self, session_uuid, desktop=None, user=None, display=None, share_mode=0): + def share_desktop_session(self, session_uuid, desktop=None, user=None, display=None, share_mode=0, check_desktop_list=False, **sessionopts): """\ Share another already running desktop session. Desktop sharing can be run in two different modes: view-only and full-access mode. Like new sessions - a to-be-shared session has be registered first with the L{X2goClient} + a to-be-shared session has be registered first with the L{X2GoClient} instance. @param desktop: desktop ID of a sharable desktop in format @ @@ -1230,13 +1519,17 @@ @type display: C{str} @param share_mode: desktop sharing mode, 0 is VIEW-ONLY, 1 is FULL-ACCESS. @type share_mode: C{int} + @param sessionopts: pass-through of options directly to the session instance's L{X2GoSession.share_desktop()} method + @type sessionopts: C{dict} @return: True if the session could be successfully shared. @rtype: C{bool} + @raise X2GoDesktopSharingException: if a given desktop ID does not specify an available desktop session + """ - # X2goClient.list_desktops() uses caching (if enabled, so we prefer lookups here... + # X2GoClient.list_desktops() uses caching (if enabled, so we prefer lookups here...) if desktop: _desktop = desktop user = None @@ -1244,68 +1537,75 @@ else: _desktop = '%s@%s' % (user, display) - if not _desktop in self._X2goClient__list_desktops(session_uuid): - _orig_desktop = _desktop + if not _desktop in self._X2GoClient__list_desktops(session_uuid): _desktop = '%s.0' % _desktop - if not _desktop in self._X2goClient__list_desktops(session_uuid): - raise x2go_exceptions.X2goDesktopSharingException('No such desktop ID: %s' % _orig_desktop) - return self.session_registry(session_uuid).share_desktop(desktop=_desktop, share_mode=share_mode, check_desktop_list=False) + return self.session_registry(session_uuid).share_desktop(desktop=_desktop, share_mode=share_mode, check_desktop_list=check_desktop_list, **sessionopts) __share_desktop_session = share_desktop_session - def resume_session(self, session_uuid=None, session_name=None): + def resume_session(self, session_uuid=None, session_name=None, match_profile_name=None, **sessionopts): """\ - Resume or continue a suspended / running X2go session on a - remote X2go server (as specified when L{register_session} was + Resume or continue a suspended / running X2Go session on a + remote X2Go server (as specified when L{register_session} was called). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param session_name: the server-side name of an X2go session + @param session_name: the server-side name of an X2Go session @type session_name: C{str} + @param match_profile_name: only resume a session if this profile name matches + @type match_profile_name: C{str} + @param sessionopts: pass-through of options directly to the session instance's L{X2GoSession.resume()} method + @type sessionopts: C{dict} @return: returns True if this method has been successful @rtype: C{bool} + @raise X2GoClientException: if the method does not know what session to resume + """ try: if session_uuid is None and session_name is None: - raise x2go_exceptions.X2goClientException('can\'t resume a session without either session_uuid or session_name') + raise x2go_exceptions.X2GoClientException('can\'t resume a session without either session_uuid or session_name') if session_name is None and self.session_registry(session_uuid).session_name is None: - raise x2go_exceptions.X2goClientException('don\'t know which session to resume') + raise x2go_exceptions.X2GoClientException('don\'t know which session to resume') if session_uuid is None: - session_uuid = self.session_registry.get_session_of_session_name(session_name=session_name, return_object=False) - return self.session_registry(session_uuid).resume() + session_uuid = self.session_registry.get_session_of_session_name(session_name=session_name, return_object=False, match_profile_name=match_profile_name) + return self.session_registry(session_uuid).resume(session_list=self._X2GoClient__list_sessions(session_uuid=session_uuid), **sessionopts) else: - return self.session_registry(session_uuid).resume(session_name=session_name) - except x2go_exceptions.X2goControlSessionException: + return self.session_registry(session_uuid).resume(session_name=session_name, session_list=self._X2GoClient__list_sessions(session_uuid=session_uuid), **sessionopts) + except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + if self.session_registry(session_uuid).connected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) __resume_session = resume_session - def suspend_session(self, session_uuid, session_name=None): + def suspend_session(self, session_uuid, session_name=None, match_profile_name=None, **sessionopts): """\ - Suspend an X2go session. + Suspend an X2Go session. Normally, you will use this method to suspend a registered session that you have formerly started/resumed from within your recent - L{X2goClient} instance. For this you simply call this method - using the sessions C{session_uuid}, leave the C{session_name} + L{X2GoClient} instance. For this you simply call this method + using the session's C{session_uuid}, leave the C{session_name} empty. - Alternatively, you can suspend a non-associated X2go session: + Alternatively, you can suspend a non-associated X2Go session: To do this you simply neeed to register (with the L{register_session} - method) an X2go session on the to-be-addressed remote X2go server and - connect (L{connect_session}) to it. Then call this method with - the freshly obtained C{session_uuid} and the remote X2go session + method) an X2Go session on the to-be-addressed remote X2Go server and + connect (L{connect_session}) to it. Then call this method with + the freshly obtained C{session_uuid} and the remote X2Go session name (as shown e.g. in x2golistsessions output). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param session_name: the server-side name of an X2go session (for + @param session_name: the server-side name of an X2Go session (for non-associated session suspend) @type session_name: C{str} + @param match_profile_name: only suspend a session if this profile name matches + @type match_profile_name: C{str} + @param sessionopts: pass-through of options directly to the session instance's L{X2GoSession.suspend()} method + @type sessionopts: C{dict} @return: returns True if this method has been successful @rtype: C{bool} @@ -1313,39 +1613,51 @@ """ try: if session_name is None: - return self.session_registry(session_uuid).suspend() + + # make sure that the current list of shared folders is up-to-date before the session suspends + self.get_shared_folders(session_uuid, check_list_mounts=True) + + return self.session_registry(session_uuid).suspend(**sessionopts) else: - for session in self.session_registry.running_sessions(): + if match_profile_name is None: + running_sessions = self.session_registry.running_sessions() + else: + running_sessions = self.session_registry.running_sessions_of_profile_name(match_profile_name) + for session in running_sessions: if session_name == session.get_session_name(): return session.suspend() - return self.session_registry(session_uuid).control_session.suspend(session_name=session_name) - except x2go_exceptions.X2goControlSessionException: + return self.session_registry(session_uuid).control_session.suspend(session_name=session_name, **sessionopts) + except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + if self.session_registry(session_uuid).conntected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) __suspend_session = suspend_session - def terminate_session(self, session_uuid, session_name=None): + def terminate_session(self, session_uuid, session_name=None, match_profile_name=None, **sessionopts): """\ - Terminate an X2go session. + Terminate an X2Go session. - Normally you will use this method to terminate a registered session that you + Normally you will use this method to terminate a registered session that you have formerly started/resumed from within your recent - L{X2goClient} instance. For this you simply call this method - using the sessions C{session_uuid}, leave the C{session_name} + L{X2GoClient} instance. For this you simply call this method + using the session's C{session_uuid}, leave the C{session_name} empty. - Alternatively, you can terminate a non-associated X2go session: + Alternatively, you can terminate a non-associated X2Go session: To do this you simply neeed to register (L{register_session}) - an X2go session on the to-be-addressed remote X2go server and - connect (L{connect_session}) to it. Then call this method with - the freshly obtained C{session_uuid} and the remote X2go session + an X2Go session on the to-be-addressed remote X2Go server and + connect (L{connect_session}) to it. Then call this method with + the freshly obtained C{session_uuid} and the remote X2Go session name (as shown in e.g. x2golistsessions output). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param session_name: the server-side name of an X2go session + @param session_name: the server-side name of an X2Go session @type session_name: C{str} + @param match_profile_name: only terminate a session if this profile name matches + @type match_profile_name: C{str} + @param sessionopts: pass-through of options directly to the session instance's L{X2GoSession.terminate()} method + @type sessionopts: C{dict} @return: returns True if this method has been successful @rtype: C{bool} @@ -1353,16 +1665,24 @@ """ try: if session_name is None: - return self.session_registry(session_uuid).terminate() + + # make sure that the current list of shared folders is up-to-date before the session terminates + self.get_shared_folders(session_uuid, check_list_mounts=True) + + return self.session_registry(session_uuid).terminate(**sessionopts) else: - for session in self.session_registry.running_sessions() + self.session_registry.suspended_sessions(): + if match_profile_name is None: + terminatable_sessions = self.session_registry.running_sessions() + self.session_registry.suspended_sessions() + else: + terminatable_sessions = self.session_registry.running_sessions_of_profile_name(match_profile_name) + self.session_registry.suspended_sessions_of_profile_name(match_profile_name) + for session in terminatable_sessions: if session_name == session.get_session_name(): return session.terminate() - return self.session_registry(session_uuid).control_session.terminate(session_name=session_name) - except x2go_exceptions.X2goControlSessionException: + return self.session_registry(session_uuid).control_session.terminate(session_name=session_name, **sessionopts) + except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + if self.session_registry(session_uuid).conntected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) __terminate_session = terminate_session def get_session_profile_name(self, session_uuid): @@ -1373,14 +1693,14 @@ For profile based sessions this will be the profile name as used in x2goclient's »sessions« configuration file. - For non-profile based session this will either be a C{profile_name} that + For non-profile based session this will either be a C{profile_name} that was passed to L{register_session} or it will be the application that - instantiated this L{X2goClient} instance. + instantiated this L{X2GoClient} instance. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: X2go session profile name + @return: X2Go session profile name @rtype: C{str} """ @@ -1398,12 +1718,12 @@ session profile creation/modification). For non-profile based sessions this will be a timestamp created on - X2go session registration by C{register_session}. + X2Go session registration by C{register_session}. @param session_uuid: the session profile name @type session_uuid: C{str} - @return: the X2go session profile's id + @return: the X2Go session profile's id @rtype: C{str} """ @@ -1412,10 +1732,10 @@ def session_ok(self, session_uuid): """\ - Test if the X2go session registered as C{session_uuid} is + Test if the X2Go session registered as C{session_uuid} is in a healthy state. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @return: C{True} if session is ok, C{False} otherwise @@ -1427,10 +1747,10 @@ def is_session_connected(self, session_uuid): """\ - Test if the X2go session registered as C{session_uuid} connected - to the X2go server. + Test if the X2Go session registered as C{session_uuid} connected + to the X2Go server. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @return: C{True} if session is connected, C{False} otherwise @@ -1440,14 +1760,43 @@ return self.session_registry(session_uuid).is_connected() __is_session_connected = is_session_connected + def is_profile_connected(self, profile_name): + """\ + Test if the X2Go given session profile has open connections + to the X2Go server. + + @param profile_name: a valid session profile name + @type profile_name: C{str} + + @return: C{True} if profile has a connected session, C{False} otherwise + @rtype: C{bool} + + """ + return bool(self.client_connected_sessions_of_profile_name(profile_name=profile_name)) + __is_profile_connected = is_profile_connected + + def is_session_profile(self, profile_id_or_name): + """\ + Test if the X2Go given session profile is configured in the client's C{sessions} file. + + @param profile_id_or_name: test existence of this session profile name (or id) + @type profile_id_or_name: C{str} + + @return: C{True} if session profile exists, C{False} otherwise + @rtype: C{bool} + + """ + return self.session_profiles.has_profile(profile_id_or_name) + __is_session_profile = is_session_profile + def is_session_running(self, session_uuid, session_name=None): """\ - Test if the X2go session registered as C{session_uuid} is up + Test if the X2Go session registered as C{session_uuid} is up and running. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param session_name: the server-side name of an X2go session + @param session_name: the server-side name of an X2Go session @type session_name: C{str} @return: C{True} if session is running, C{False} otherwise @@ -1462,12 +1811,12 @@ def is_session_suspended(self, session_uuid, session_name=None): """\ - Test if the X2go session registered as C{session_uuid} + Test if the X2Go session registered as C{session_uuid} is in suspended state. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param session_name: the server-side name of an X2go session + @param session_name: the server-side name of an X2Go session @type session_name: C{str} @return: C{True} if session is suspended, C{False} otherwise @@ -1482,12 +1831,12 @@ def has_session_terminated(self, session_uuid, session_name=None): """\ - Test if the X2go session registered as C{session_uuid} + Test if the X2Go session registered as C{session_uuid} has terminated. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param session_name: the server-side name of an X2go session + @param session_name: the server-side name of an X2Go session @type session_name: C{str} @return: C{True} if session has terminated, C{False} otherwise @@ -1502,10 +1851,10 @@ def is_folder_sharing_available(self, session_uuid=None, profile_name=None): """\ - Test if local folder sharing is available for X2go session with unique ID or + Test if local folder sharing is available for X2Go session with unique ID or session profile . - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param profile_name: alternatively, the profile name can be used to perform this query @type profile_name: C{str} @@ -1515,13 +1864,14 @@ """ if session_uuid is None and profile_name: - _connected = self._X2goClient__client_connected_sessions_of_profile_name(profile_name, return_objects=False) - if len(_connected) > 0: - session_uuid = _connected[0] + session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: - return self.session_registry(session_uuid).is_folder_sharing_available() + try: + return self.session_registry(session_uuid).is_folder_sharing_available() + except x2go_exceptions.X2GoSessionRegistryException: + return False else: - self.logger('Cannot find a terminal session for profile ,,%s\'\' that can be used to query folder sharing capabilities' % profile_name, loglevel=log.loglevel_WARN) + self.logger('Cannot find a terminal session for profile ,,%s\'\' that can be used to query folder sharing capabilities' % profile_name, loglevel=log.loglevel_INFO) return False __is_folder_sharing_available = is_folder_sharing_available __profile_is_folder_sharing_available = is_folder_sharing_available @@ -1529,13 +1879,13 @@ def share_local_folder(self, session_uuid=None, local_path=None, profile_name=None, folder_name=None): """\ - Share a local folder with the X2go session registered as C{session_uuid}. + Share a local folder with the X2Go session registered as C{session_uuid}. When calling this method the given client-side folder is mounted - on the X2go server (via sshfs) and (if in desktop mode) provided as a + on the X2Go server (via sshfs) and (if in desktop mode) provided as a desktop icon on your remote session's desktop. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param local_path: the full path to an existing folder on the local (client-side) file system @@ -1549,15 +1899,16 @@ @rtype: C{bool} """ - # compat for Python-X2go (<=0.1.1.6) + # compat for Python-X2Go (<=0.1.1.6) if folder_name: local_path = folder_name if session_uuid is None and profile_name: - _associated = self._X2goClient__client_associated_sessions_of_profile_name(profile_name, return_objects=False) - if len(_associated) > 0: - session_uuid = _associated[0] + session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: - return self.session_registry(session_uuid).share_local_folder(local_path=local_path) + try: + return self.session_registry(session_uuid).share_local_folder(local_path=local_path) + except x2go_exceptions.X2GoSessionException: + return False else: self.logger('Cannot find a terminal session for profile ,,%s\'\' to share a local folder with' % profile_name, loglevel=log.loglevel_WARN) return False @@ -1567,14 +1918,14 @@ def unshare_all_local_folders(self, session_uuid=None, profile_name=None): """\ - Unshare all local folders mounted in X2go session registered as + Unshare all local folders mounted in X2Go session registered as C{session_uuid}. - When calling this method all client-side mounted folders on the X2go + When calling this method all client-side mounted folders on the X2Go server (via sshfs) for session with ID will get unmounted. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param profile_name: alternatively, the profile name can be used to unshare mounted folders @@ -1585,9 +1936,7 @@ """ if session_uuid is None and profile_name: - _associated = self._X2goClient__client_associated_sessions_of_profile_name(profile_name, return_objects=False) - if len(_associated) > 0: - session_uuid = _associated[0] + session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: return self.session_registry(session_uuid).unshare_all_local_folders() else: @@ -1600,19 +1949,19 @@ def unshare_local_folder(self, session_uuid=None, profile_name=None, local_path=None): """\ - Unshare local folder that is mounted in the X2go session registered as + Unshare local folder that is mounted in the X2Go session registered as C{session_uuid}. - When calling this method the given client-side mounted folder on the X2go + When calling this method the given client-side mounted folder on the X2Go server (via sshfs) for session with ID will get unmounted. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param profile_name: alternatively, the profile name can be used to unshare mounted folders @type profile_name: C{str} - @param local_path: the full path of a local folder that is mounted within X2go + @param local_path: the full path of a local folder that is mounted within X2Go session with session ID (or recognized via profile name) and that shall be unmounted from that session. @type local_path: C{str} @@ -1622,9 +1971,7 @@ """ if session_uuid is None and profile_name: - _associated = self._X2goClient__client_associated_sessions_of_profile_name(profile_name, return_objects=False) - if len(_associated) > 0: - session_uuid = _associated[0] + session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: return self.session_registry(session_uuid).unshare_local_folder(local_path=local_path) else: @@ -1635,35 +1982,73 @@ __unshare_local_folder_from_session = unshare_local_folder __unshare_local_folder_from_profile = unshare_local_folder - def get_shared_folders(self, session_uuid=None, profile_name=None): + def get_shared_folders(self, session_uuid=None, profile_name=None, check_list_mounts=False): """\ - Get a list of local folders mounted within X2go session with session hash + Get a list of local folders mounted within X2Go session with session hash from this client. - @return: returns a C{list} of those local folder names that are mounted within X2go session . + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + @param profile_name: alternatively, the profile name can be used to get mounted folders of a session connected profile + @type profile_name: C{str} + @param check_list_mounts: query the server-side mount list for up-to-date information + @type check_list_mounts: C{bool} + + @return: returns a C{list} of those local folder names that are mounted within X2Go session . @rtype: C{list} """ if session_uuid is None and profile_name: - _associated = self._X2goClient__client_associated_sessions_of_profile_name(profile_name, return_objects=False) - if len(_associated) > 0: - session_uuid = _associated[0] - if session_uuid: - return self.session_registry(session_uuid).get_shared_folders() + session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) + + if session_uuid and profile_name is None: + profile_name = self.session_registry(session_uuid).get_profile_name() + + if session_uuid and profile_name: + + mounts = None + if check_list_mounts: + _mounts = self.list_mounts_by_profile_name(profile_name) + mounts = [] + for mount_list in _mounts.values(): + mounts.extend(mount_list) + + return self.session_registry(session_uuid).get_shared_folders(check_list_mounts=check_list_mounts, mounts=mounts) + session_get_shared_folders = get_shared_folders profile_get_shared_folders = get_shared_folders __session_get_shared_folders = get_shared_folders __profile_get_shared_folders = get_shared_folders + def get_master_session(self, profile_name, return_object=True, return_session_name=False): + """\ + Retrieve the master session of a specific profile. + + @param profile_name: the profile name that we query the master session of + @type profile_name: C{str} + @param return_object: return L{X2GoSession} instance + @type return_object: C{bool} + @param return_session_name: return X2Go session name + @type return_session_name: C{bool} + + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) + @rtype: C{list} + + """ + return self.session_registry.get_master_session(profile_name, return_object=return_object, return_session_name=return_session_name) + profile_master_session = get_master_session + __get_master_session = get_master_session + __profile_master_session = profile_master_session + + ### + ### Provide access to the X2GoClient's session registry ### - ### Provide access to the X2goClient's session registry - ### def client_connected_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of X2go sessions that this L{X2goClient} instance is connected to. + Retrieve a list of X2Go sessions that this L{X2GoClient} instance is connected to. - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_profile_names: return as list of session profile names @type return_profile_names: C{bool} @@ -1671,6 +2056,7 @@ @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of connected sessions @rtype: C{list} @@ -1681,7 +2067,7 @@ @property def client_has_connected_sessions(self): """\ - Equals C{True} if there are any connected sessions with this L{X2goClient} instance. + Equals C{True} if there are any connected sessions with this L{X2GoClient} instance. """ return self.session_registry.has_connected_sessions @@ -1689,9 +2075,9 @@ def client_associated_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of X2go sessions associated to this L{X2goClient} instance. + Retrieve a list of X2Go sessions associated to this L{X2GoClient} instance. - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_profile_names: return as list of session profile names @type return_profile_names: C{bool} @@ -1699,6 +2085,7 @@ @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of associated sessions @rtype: C{list} @@ -1709,7 +2096,7 @@ @property def client_has_associated_sessions(self): """\ - Equals C{True} if there are any associated sessions with this L{X2goClient} instance. + Equals C{True} if there are any associated sessions with this L{X2GoClient} instance. """ return self.session_registry.has_associated_sessions @@ -1717,9 +2104,9 @@ def client_running_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of running X2go sessions. + Retrieve a list of running X2Go sessions. - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_profile_names: return as list of session profile names @type return_profile_names: C{bool} @@ -1727,6 +2114,7 @@ @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of running sessions @rtype: C{list} @@ -1737,7 +2125,7 @@ @property def client_has_running_sessions(self): """\ - Equals C{True} if there are any running sessions with this L{X2goClient} instance. + Equals C{True} if there are any running sessions with this L{X2GoClient} instance. """ return self.session_registry.has_running_sessions @@ -1745,9 +2133,9 @@ def client_suspended_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of suspended X2go sessions. + Retrieve a list of suspended X2Go sessions. - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_profile_names: return as list of session profile names @type return_profile_names: C{bool} @@ -1755,6 +2143,7 @@ @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of suspended sessions @rtype: C{list} @@ -1765,7 +2154,7 @@ @property def client_has_suspended_sessions(self): """\ - Equals C{True} if there are any suspended sessions with this L{X2goClient} instance. + Equals C{True} if there are any suspended sessions with this L{X2GoClient} instance. """ return self.session_registry.has_suspended_sessions @@ -1773,9 +2162,9 @@ def client_registered_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of registered X2go sessions. + Retrieve a list of registered X2Go sessions. - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_profile_names: return as list of session profile names @type return_profile_names: C{bool} @@ -1783,6 +2172,7 @@ @type return_profile_ids: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of registered sessions @rtype: C{list} @@ -1793,7 +2183,7 @@ @property def client_control_sessions(self): """\ - Equals a list of all registered X2go control sessions. + Equals a list of all registered X2Go control sessions. """ return self.session_registry.control_sessions @@ -1805,21 +2195,89 @@ @param profile_name: profile name @type profile_name: C{str} + @return: control session instance - @rtype: C{X2goControlSession*} instance + @rtype: C{X2GoControlSession} instance """ return self.session_registry.control_session_of_profile_name(profile_name) __client_control_session_of_profile_name = client_control_session_of_profile_name + def get_server_versions(self, profile_name, component=None, force=False): + """\ + Query the server configured in session profile for the list of install X2Go components + and its versions. + + @param profile_name: use the control session of this profile to query the X2Go server for its component list + @type profile_name: C{str} + @param component: only return the version of a specific component + @type component: C{str} + @param force: refresh component/version data by a query to the server + @type force: C{bool} + + @return: dictionary of server components (as keys) and their versions (as values) or the version of the given + @rtype: C{dict} or C{str} + + @raise X2GoClientException: if component is not available on the X2Go Server. + + """ + control_session = self.client_control_session_of_profile_name(profile_name) + if component is None: + return control_session.get_server_versions(force=force) + else: + try: + return control_session.get_server_versions(force=force)[component] + except KeyError: + raise x2go_exceptions.X2GoClientException('No such component on X2Go Server') + __get_server_versions = get_server_versions + get_server_components = get_server_versions + __get_server_components = get_server_components + + def get_server_features(self, profile_name, force=False): + """\ + Query the server configured in session profile for the list of server-side + X2Go features. + + @param profile_name: use the control session of this profile to query the X2Go server for its feature list + @type profile_name: C{str} + @param force: refresh feature list by a query to the server + @type force: C{bool} + + @return: list of server feature names (as returned by server-side command ,,x2gofeaturelist'' + @rtype: C{list} + + """ + control_session = self.client_control_session_of_profile_name(profile_name) + return control_session.get_server_features(force=force) + __get_server_features = get_server_features + + def has_server_feature(self, profile_name, feature): + """\ + Query the server configured in session profile for the availability + of a certain server feature. + + @param profile_name: use the control session of this profile to query the X2Go server for its feature + @type profile_name: C{str} + @param feature: test the availability of this feature on the X2Go server + @type feature: C{str} + + @return: C{True} if the feature is available on the queried server + @rtype: C{bool} + + """ + control_session = self.client_control_session_of_profile_name(profile_name) + return feature in control_session.get_server_features() + __has_server_feature = has_server_feature + def client_registered_session_of_name(self, session_name, return_object=False): """\ - Retrieve X2go session of a given session name. + Retrieve X2Go session of a given session name. @param session_name: session name @type session_name: C{str} - @return: control session instance - @rtype: C{X2goSession} or C{str} + + @return: session instance of the given name + @rtype: C{X2GoSession} or C{str} """ return self.session_registry.get_session_of_session_name(session_name, return_object=return_object) @@ -1832,52 +2290,60 @@ @param session_name: session name @type session_name: C{str} + @return: C{True} if the given session is registered + @rtype: C{bool} + """ return self.client_registered_session_of_name(session_name) is not None __client_has_registered_session_of_name = client_registered_session_of_name def client_registered_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ - Retrieve registered X2go sessions of profile name . + Retrieve registered X2Go sessions of profile name . @param profile_name: profile name @type profile_name: C{str} - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of registered sessions of profile name @rtype: C{list} + """ return self.session_registry.registered_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_registered_sessions_of_profile_name = client_registered_sessions_of_profile_name def client_connected_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ - Retrieve connected X2go sessions of profile name . + Retrieve connected X2Go sessions of profile name . @param profile_name: profile name @type profile_name: C{str} - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of connected sessions of profile name @rtype: C{list} + """ return self.session_registry.connected_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_connected_sessions_of_profile_name = client_connected_sessions_of_profile_name def client_associated_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ - Retrieve associated X2go sessions of profile name . + Retrieve associated X2Go sessions of profile name . @param profile_name: profile name @type profile_name: C{str} - @param return_objects: return as list of X2go session objects + @param return_objects: return as list of X2Go session objects @type return_objects: C{bool} @param return_session_names: return as list of session names @type return_session_names: C{bool} + @return: list of associated sessions of profile name @rtype: C{list} @@ -1885,34 +2351,94 @@ return self.session_registry.associated_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_associated_sessions_of_profile_name = client_associated_sessions_of_profile_name + def client_pubapp_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): + """\ + Retrieve X2Go sessions of profile name that provide published applications. + + @param profile_name: profile name + @type profile_name: C{str} + @param return_objects: return as list of X2Go session objects + @type return_objects: C{bool} + @param return_session_names: return as list of session names + @type return_session_names: C{bool} + + @return: list of application publishing sessions of profile name + @rtype: C{list} + + """ + return self.session_registry.pubapp_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) + __client_pubapp_sessions_of_profile_name = client_pubapp_sessions_of_profile_name + + + def client_running_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): + """\ + Retrieve running X2Go sessions of profile name . + + @param profile_name: profile name + @type profile_name: C{str} + @param return_objects: return as list of X2Go session objects + @type return_objects: C{bool} + @param return_session_names: return as list of session names + @type return_session_names: C{bool} + + @return: list of running sessions of profile name + @rtype: C{list} + + """ + return self.session_registry.running_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) + __client_running_sessions_of_profile_name = client_running_sessions_of_profile_name + + def client_suspended_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): + """\ + Retrieve suspended X2Go sessions of profile name . + + @param profile_name: profile name + @type profile_name: C{str} + @param return_objects: return as list of X2Go session objects + @type return_objects: C{bool} + @param return_session_names: return as list of session names + @type return_session_names: C{bool} + + @return: list of suspended sessions of profile name + @rtype: C{list} + + """ + return self.session_registry.suspended_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) + __client_suspended_sessions_of_profile_name = client_suspended_sessions_of_profile_name + + ### + ### Provide access to the X2Go server's sessions DB ### - ### Provide access to the X2go server's sessions DB - ### def server_is_alive(self, session_uuid): """\ Test if server that corresponds to the terminal session C{session_uuid} is alive. - @param session_uuid: the X2go session's UUID registry hash + If the session is not connected anymore the L{X2GoClient.HOOK_on_control_session_death()} gets called. + + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: C{True} if X2go server connection for L{X2goSession} instance with is alive. + + @return: C{True} if X2Go server connection for L{X2GoSession} instance with is alive. @rtype: C{bool} + @raise X2GoControlSessionException: if the session is not connected anymore; in that case the L{HOOK_on_control_session_death} gets called. + """ try: return self.session_registry(session_uuid).is_alive() - except x2go_exceptions.X2goControlSessionException: + except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + if self.session_registry(session_uuid).conntected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) return False __server_is_alive = server_is_alive def all_servers_are_alive(self): """\ - Test vitality of all connected X2go servers. + Test vitality of all connected X2Go servers. - @return: C{True} if all connected X2go servers are alive. + @return: C{True} if all connected X2Go servers are alive. @rtype: C{bool} """ @@ -1920,16 +2446,18 @@ for session_uuid in self.client_connected_sessions(): _all_alive = _all_alive and self.server_is_alive(session_uuid) return _all_alive + __all_servers_are_alive = all_servers_are_alive def server_valid_x2gouser(self, session_uuid, username=None): """\ - Check if user is allowed to start an X2go session on a remote server. + Check if user is allowed to start an X2Go session on a remote server. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param username: user name to test validity for @type username: C{str} - @return: Is remote user allowed to start an X2go session? + + @return: Is remote user allowed to start an X2Go session? @rtype: C{str} """ @@ -1939,79 +2467,87 @@ def server_running_sessions(self, session_uuid): """\ Retrieve a list of session names of all server-side running sessions (including those not - instantiated by our L{X2goClient} instance). + instantiated by our L{X2GoClient} instance). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @return: list of session names @rtype: C{list} + @raise X2GoClientException: if the session with UUID C{session_uuid} is not connected + """ - if self._X2goClient__is_session_connected(session_uuid): - session_list = self._X2goClient__list_sessions(session_uuid) + if self._X2GoClient__is_session_connected(session_uuid): + session_list = self._X2GoClient__list_sessions(session_uuid) return [ key for key in session_list.keys() if session_list[key].status == 'R' ] else: - raise x2go_exceptions.X2goClientException('X2go session with UUID %s is not connected' % session_uuid) + raise x2go_exceptions.X2GoClientException('X2Go session with UUID %s is not connected' % session_uuid) __server_running_sessions = server_running_sessions def server_has_running_sessions(self, session_uuid): """\ - Equals C{True} if the X2go server has any running sessions. + Equals C{True} if the X2Go server has any running sessions. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @return: C{True}, if there are running sessions @rtype: C{bool} """ - return len(self._X2goClient__server_running_sessions(session_uuid)) > 0 + return len(self._X2GoClient__server_running_sessions(session_uuid)) > 0 __server_has_running_sessions = server_has_running_sessions def server_has_running_session_of_name(self, session_uuid, session_name): """\ - Equals C{True} if the X2go server has a running session of name . + Equals C{True} if the X2Go server has a running session of name . - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param session_name: session name @type session_name: C{str} """ - return session_name in self._X2goClient__server_running_sessions(session_uuid) + return session_name in self._X2GoClient__server_running_sessions(session_uuid) + __server_has_running_session_of_name = server_has_running_session_of_name def server_suspended_sessions(self, session_uuid): """\ Retrieve a list of session names of all server-side suspended sessions (including those not - instantiated by our L{X2goClient} instance). + instantiated by our L{X2GoClient} instance). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @return: list of session names @rtype: C{list} + @raise X2GoClientException: if the session with UUID C{session_uuid} is not connected + """ - if self._X2goClient__is_session_connected(session_uuid): - session_list = self._X2goClient__list_sessions(session_uuid) + if self._X2GoClient__is_session_connected(session_uuid): + session_list = self._X2GoClient__list_sessions(session_uuid) return [ key for key in session_list.keys() if session_list[key].status == 'S' ] else: - raise x2go_exceptions.X2goClientException('X2go session with UUID %s is not connected' % session_uuid) + raise x2go_exceptions.X2GoClientException('X2Go session with UUID %s is not connected' % session_uuid) __server_suspended_sessions = server_suspended_sessions def server_has_suspended_sessions(self, session_uuid): """\ - Equals C{True} if the X2go server has any suspended sessions. + Equals C{True} if the X2Go server has any suspended sessions. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} """ - return len(self._X2goClient__server_suspended_sessions(session_uuid)) > 0 + return len(self._X2GoClient__server_suspended_sessions(session_uuid)) > 0 + __server_has_suspended_sessions = server_has_suspended_sessions def server_has_suspended_session_of_name(self, session_uuid, session_name): """\ - Equals C{True} if the X2go server has a suspended session of name . + Equals C{True} if the X2Go server has a suspended session of name . - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param session_name: session name @type session_name: C{str} @@ -2019,67 +2555,78 @@ @rtype: C{bool} """ - return session_name in self._X2goClient__server_suspended_sessions(session_uuid) + return session_name in self._X2GoClient__server_suspended_sessions(session_uuid) + __server_has_suspended_session_of_name = server_has_suspended_session_of_name ### ### CLIENT OPERATIONS ON SESSIONS (listing sessions, terminating non-associated sessions etc.) ### - def clean_sessions(self, session_uuid): + def clean_sessions(self, session_uuid, published_applications=False): """\ - Find running X2go sessions that have previously been started by the - connected user on the remote X2go server and terminate them. + Find running X2Go sessions that have previously been started by the + connected user on the remote X2Go server and terminate them. - Before calling this method you have to setup a pro forma remote X2go session - with L{X2goClient.register_session()} (even if you do not intend to open - a real X2go session window on the remote server) and connect to this session (with - L{X2goClient.connect_session()}. + Before calling this method you have to setup a pro forma remote X2Go session + with L{X2GoClient.register_session()} (even if you do not intend to open + a real X2Go session window on the remote server) and connect to this session (with + L{X2GoClient.connect_session()}. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @param published_applications: if C{True}, also terminate sessions that are published applications + provider + @type published_applications: C{bool} """ _destroy_terminals = not ( self.auto_update_sessionregistry == True) - session = self.session_registry(session_uuid) - session.clean_sessions(destroy_terminals=_destroy_terminals) + try: + session = self.session_registry(session_uuid) + session.clean_sessions(destroy_terminals=_destroy_terminals, published_applications=published_applications) + except x2go_exceptions.X2GoSessionRegistryException: + # silently ignore a non-registered session UUID (mostly occurs during disconnects) + pass __clean_sessions = clean_sessions - def list_sessions(self, session_uuid=None, - profile_name=None, profile_id=None, - no_cache=False, refresh_cache=False, + def list_sessions(self, session_uuid=None, + profile_name=None, profile_id=None, + no_cache=False, refresh_cache=False, update_sessionregistry=True, register_sessions=False, raw=False): """\ - Use the X2go session registered under C{session_uuid} to - retrieve a list of running or suspended X2go sessions from the - connected X2go server (for the authenticated user). - - Before calling this method you have to setup a pro forma remote X2go session - with L{X2goClient.register_session()} (even if you do not intend to open - a real X2go session window on the remote server) and connect to this session (with - L{X2goClient.connect_session()}. + Use the X2Go session registered under C{session_uuid} to + retrieve a list of running or suspended X2Go sessions from the + connected X2Go server (for the authenticated user). + + Before calling this method you have to setup a pro forma remote X2Go session + with L{X2GoClient.register_session()} (even if you do not intend to open + a real X2Go session window on the remote server) and connect to this session (with + L{X2GoClient.connect_session()}. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param profile_name: use profile name instead of @type profile_name: C{str} @param profile_id: use profile id instead of or @type profile_id: C{str} - @param no_cache: do not get the session list from cache, query the X2go server directly + @param no_cache: do not get the session list from cache, query the X2Go server directly @type no_cache: C{bool} - @param refresh_cache: query the X2go server directly and update the session list cache + @param refresh_cache: query the X2Go server directly and update the session list cache with the new information @type refresh_cache: C{bool} - @param update_sessionregistry: query the X2go server directly and update the + @param update_sessionregistry: query the X2Go server directly and update the session registry according to the obtained information @type update_sessionregistry: C{bool} - @param register_sessions: query the X2go server directly and register newly found X2go session - as L{X2goSession} instances associated to this L{X2goClient} instance + @param register_sessions: query the X2Go server directly and register newly found X2Go session + as L{X2GoSession} instances associated to this L{X2GoClient} instance @type register_sessions: C{bool} - @param raw: output the session list in X2go's raw C{x2golistsessions} format + @param raw: output the session list in X2Go's raw C{x2golistsessions} format @type raw: C{bool} + @raise X2GoClientException: if the session profile specified by C{session_uuid}, C{profile_name} or C{profile_id} is not connected + or if none of the named parameters has been specified + """ if profile_id is not None: profile_name = self.to_profile_name(profile_id) @@ -2092,12 +2639,12 @@ # thus, we simply grab the first that comes in... session_uuid = _connected_sessions[0].get_uuid() else: - raise x2go_exceptions.X2goClientException('profile ,,%s\'\' is not connected' % profile_name) + raise x2go_exceptions.X2GoClientException('profile ,,%s\'\' is not connected' % profile_name) elif session_uuid is not None: pass else: - raise x2go_exceptions.X2goClientException('must either specify session UUID or profile name') + raise x2go_exceptions.X2GoClientException('must either specify session UUID or profile name') if raw: return self.session_registry(session_uuid).list_sessions(raw=raw) @@ -2110,7 +2657,7 @@ else: # if there is no cache for this session_uuid available, make sure the cache gets updated # before reading from it... - if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type=('sessions'))): + if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type='sessions') or refresh_cache): self.__update_cache_by_session_uuid(session_uuid) _session_list = self.listsessions_cache.list_sessions(session_uuid) @@ -2124,31 +2671,45 @@ return _session_list __list_sessions = list_sessions - def list_desktops(self, session_uuid=None, + def list_desktops(self, session_uuid=None, profile_name=None, profile_id=None, no_cache=False, refresh_cache=False, + exclude_session_types=[], raw=False): """\ - Use the X2go session registered under C{session_uuid} to - retrieve a list of X2go desktop sessions that are available + Use the X2Go session registered under C{session_uuid} to + retrieve a list of X2Go desktop sessions that are available for desktop sharing. - Before calling this method you have to setup a pro forma remote X2go session - with L{X2goClient.register_session()} (even if you do not intend to open - a real X2go session window on the remote server) and connect to this session (with - L{X2goClient.connect_session()}. + Before calling this method you have to setup a pro forma remote X2Go session + with L{X2GoClient.register_session()} (even if you do not intend to open + a real X2Go session window on the remote server) and connect to this session (with + L{X2GoClient.connect_session()}. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param profile_name: use profile name instead of @type profile_name: C{str} @param profile_id: use profile id instead of or @type profile_id: C{str} - @param no_cache: do not get the session list from cache, query the X2go server directly + @param no_cache: do not get the desktop list from cache, query the X2Go server directly @type no_cache: C{bool} - @param raw: output the session list in X2go's raw C{x2golistsessions} format + @param refresh_cache: query the X2Go server directly and update the desktop list cache + with the new information + @type refresh_cache: C{bool} + @param exclude_session_types: session types (e.g. "D", "R", "S" or "P") to be excluded from the + returned list of sharable desktops (this only works for sharing someone's own sessions, for + sharing other users' sessions, the X2Go Desktop Sharing decides on what is sharable and what not). + @type exclude_session_types: C{list} + @param raw: output the session list in X2Go's raw C{x2golistdesktops} format @type raw: C{bool} + @return: a list of available desktops to be shared + @rtype: C{list} + + @raise X2GoClientException: if the session profile specified by C{session_uuid}, C{profile_name} or C{profile_id} is not connected + or if none of the named parameters has been specified + """ if profile_id is not None: profile_name = self.to_profile_name(profile_id) @@ -2161,12 +2722,12 @@ # thus, we simply grab the first that comes in... session_uuid = _connected_sessions[0].get_uuid() else: - raise x2go_exceptions.X2goClientException('profile ,,%s\'\' is not connected' % profile_name) + raise x2go_exceptions.X2GoClientException('profile ,,%s\'\' is not connected' % profile_name) elif session_uuid is not None: pass else: - raise x2go_exceptions.X2goClientException('must either specify session UUID or profile name') + raise x2go_exceptions.X2GoClientException('must either specify session UUID or profile name') if raw: return self.session_registry(session_uuid).list_desktops(raw=raw) @@ -2174,27 +2735,106 @@ if not self.use_listsessions_cache or not self.auto_update_listdesktops_cache or no_cache: _desktop_list = self.session_registry(session_uuid).list_desktops() else: - if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_types=('desktops'))): + if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type='desktops') or refresh_cache): self.__update_cache_by_session_uuid(session_uuid, update_sessions=False, update_desktops=True) _desktop_list = self.listsessions_cache.list_desktops(session_uuid) + # attempt to exclude session types that are requested to be excluded + if exclude_session_types: + + # create an X2GoServerSessionList* instance and operate on that + session_list = self.list_backend() + session_list.set_sessions(self._X2GoClient__list_sessions(session_uuid)) + + # search for a match among the listed sessions + for desktop in copy.deepcopy(_desktop_list): + user = desktop.split('@')[0] + if user == self.get_session_username(session_uuid): + display = desktop.split('@')[1] + session = session_list.get_session_with('display', display, hostname=self.get_session_server_hostname(session_uuid)) + if session is None: continue + if session.get_session_type() in exclude_session_types: + _desktop_list.remove(desktop) + return _desktop_list __list_desktops = list_desktops + def list_mounts_by_profile_name(self, profile_name, + no_cache=False, refresh_cache=False, + raw=False): + """ + For a given profil C{profile_name} to + retrieve its list of mounted client shares for that session. + + @param profile_name: a valid profile name + @type profile_name: C{str} + @param no_cache: do not get the session list from cache, query the X2Go server directly + @type no_cache: C{bool} + @param raw: output the session list in X2Go's raw C{x2golistmounts} format + @type raw: C{bool} + + @return: list of server-side mounted shares for a given profile name + @rtype: C{list} + + """ + sessions = [ s for s in self.client_running_sessions(return_objects=True) if s.get_profile_name() == profile_name ] + + if raw: + _list_mounts = "" + for session in sessions: + _list_mounts += self.__list_mounts(session_uuid=session(), no_cache=no_cache, refresh_cache=refresh_cache, raw=True) + else: + _list_mounts = {} + for session in sessions: + _list_mounts.update(self.__list_mounts(session_uuid=session(), no_cache=no_cache, refresh_cache=refresh_cache, raw=False)) + return _list_mounts + __list_mounts_by_profile_name = list_mounts_by_profile_name + + def list_mounts(self, session_uuid, + no_cache=False, refresh_cache=False, + raw=False): + """\ + Use the X2Go session registered under C{session_uuid} to + retrieve its list of mounted client shares for that session. + + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + @param no_cache: do not get the session list from cache, query the X2Go server directly + @type no_cache: C{bool} + @param raw: output the session list in X2Go's raw C{x2golistmounts} format + @type raw: C{bool} + + @return: list of server-side mounted shares for a given session UUID + @rtype: C{list} + + """ + if raw: + return self.session_registry(session_uuid).list_mounts(raw=raw) + + if not self.use_listsessions_cache or not self.auto_update_listmounts_cache or no_cache: + _mounts_list = self.session_registry(session_uuid).list_mounts() + else: + if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type='mounts') or refresh_cache): + self.__update_cache_by_session_uuid(session_uuid, update_sessions=False, update_mounts=True) + _mounts_list = self.listsessions_cache.list_mounts(session_uuid) + + return _mounts_list + __list_mounts = list_mounts + ### ### Provide access to config file class objects - ### + ### def get_profiles(self): """\ - Returns the L{X2goClient} instance's C{X2goSessionProfiles*} object. + Returns the L{X2GoClient} instance's C{X2GoSessionProfiles*} object. Use this method for object retrieval if you want to modify the »sessions« configuration node (e.g. in ~/.x2goclient with the FILE backend) from within your - Python X2go based application. + Python X2Go based application. return: returns the client's session profiles instance - rtype: C{X2goSessionProfiles*} instance + rtype: C{X2GoSessionProfiles*} instance """ return self.session_profiles @@ -2202,22 +2842,22 @@ get_session_profiles = get_profiles """Alias for L{get_profiles()}.""" - @property def profile_names(self): """\ - Equals a list of all profile names that are known to this L{X2goClient} instance. + Equals a list of all profile names that are known to this L{X2GoClient} instance. """ return self.session_profiles.profile_names + __profile_names = profile_names def get_client_settings(self): """\ - Returns the L{X2goClient} instance's C{X2goClientSettings*} object. + Returns the L{X2GoClient} instance's C{X2GoClientSettings*} object. Use this method for object retrieval if you want to modify the »settings« configuration node (e.g. in ~/.x2goclient with the FILE backend) from within your - Python X2go based application. + Python X2Go based application. return: returns the client's settings configuration node rtype: C{bool} @@ -2228,11 +2868,11 @@ def get_client_printing(self): """\ - Returns the L{X2goClient} instance's C{X2goClientPrinting*} object. + Returns the L{X2GoClient} instance's C{X2GoClientPrinting*} object. Use this method for object retrieval if you want to modify the printing configuration node (e.g. in ~/.x2goclient with the FILE backend) from within your - Python X2go based application. + Python X2Go based application. return: returns the client's printing configuration node rtype: C{bool} @@ -2243,25 +2883,48 @@ ### ### Session profile oriented methods - ### + ### - def get_profile_config(self, profile_id_or_name): + def get_profile_config(self, profile_id_or_name, parameter=None): """\ Returns a dictionary with session options and values that represent the session profile for C{profile_id_or_name}. - @param profile_id_or_name: name or id of an X2go session profile as found + @param profile_id_or_name: name or id of an X2Go session profile as found in the sessions configuration file @type profile_id_or_name: C{str} + @param parameter: if specified, only the value for the given parameter is returned + @type parameter: C{str} @return: a Python dictionary with session profile options - @rtype: C{dict} + @rtype: C{dict} or C{bool}, C{int}, C{str} """ - return self.session_profiles.get_profile_config(profile_id_or_name) + return self.session_profiles.get_profile_config(profile_id_or_name, parameter=parameter) __get_profile_config = get_profile_config with_profile_config = get_profile_config + def set_profile_config(self, profile_id_or_name, parameter, value): + """\ + Set individual session profile parameters for session profile C{profile_id_or_name}. + + @param profile_id_or_name: name or id of an X2Go session profile as found + in the sessions configuration file + @type profile_id_or_name: C{str} + @param parameter: set this parameter with the given C{value} + @type parameter: C{str} + @param value: set this value for the given C{parameter} + @type value: C{bool}, C{int}, C{str}, C{list} or C{dict} + + @return: returns C{True} if this operation has been successful + @rtype: C{dict} + + """ + self.session_profiles.update_value(profile_id_or_name, parameter, value) + self.session_profiles.write_user_config = True + self.session_profiles.write() + __set_profile_config = set_profile_config + def to_profile_id(self, profile_name): """\ Retrieve the session profile ID of the session whose profile name @@ -2309,7 +2972,7 @@ def client_connected_profiles(self, return_profile_names=False): """\ - Retrieve a list of session profiles that are currently connected to an X2go server. + Retrieve a list of session profiles that are currently connected to an X2Go server. @param return_profile_names: return as list of session profile names @type return_profile_names: C{bool} @@ -2325,10 +2988,10 @@ def disconnect_profile(self, profile_name): """\ - Disconnect all L{X2goSession} instances that relate to C{profile_name} by closing down their + Disconnect all L{X2GoSession} instances that relate to C{profile_name} by closing down their Paramiko/SSH Transport thread. - @param profile_name: the X2go session profile name + @param profile_name: the X2Go session profile name @type profile_name: C{str} @return: a return value @rtype: C{bool} @@ -2355,37 +3018,36 @@ """\ Update the session registry stati by profile name. - @param profile_name: the X2go session profile name + @param profile_name: the X2Go session profile name @type profile_name: C{str} - @param session_list: a manually passed on list of X2go sessions - @type session_list: C{X2goServerList*} instances + @param session_list: a manually passed on list of X2Go sessions + @type session_list: C{X2GoServerList*} instances """ session_uuids = self.client_registered_sessions_of_profile_name(profile_name, return_objects=False) if session_uuids: if session_list is None: - session_list = self.list_sessions(session_uuids[0], - update_sessionregistry=False, - register_sessions=False, - ) + session_list = self._X2GoClient__list_sessions(session_uuids[0], + update_sessionregistry=False, + register_sessions=False, + ) try: self.session_registry.update_status(profile_name=profile_name, session_list=session_list) - except x2go_exceptions.X2goControlSessionException: - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + except x2go_exceptions.X2GoControlSessionException: + if self.session_registry(session_uuids[0]).connected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) __update_sessionregistry_status_by_profile_name = update_sessionregistry_status_by_profile_name - def update_sessionregistry_status_by_session_uuid(self, session_uuid): """\ - Update the session registry status of a specific L{X2goSession} instance with + Update the session registry status of a specific L{X2GoSession} instance with session identifier . - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} """ - session_list = self.list_sessions(session_uuid, update_sessionregistry=False, register_sessions=False) + session_list = self._X2GoClient__list_sessions(session_uuid, update_sessionregistry=False, register_sessions=False) if session_list: self.session_registry.update_status(session_uuid=session_uuid, session_list=session_list) __update_sessionregistry_status_by_session_uuid = update_sessionregistry_status_by_session_uuid @@ -2400,47 +3062,55 @@ __update_sessionregistry_status_all_profiles = update_sessionregistry_status_all_profiles - def update_cache_by_profile_name(self, profile_name, cache_types=('sessions'), update_sessions=None, update_desktops=None): + def update_cache_by_profile_name(self, profile_name, cache_types=('sessions'), update_sessions=None, update_desktops=None, update_mounts=None): """\ Update the session list cache by profile name. - @param profile_name: the X2go session profile name + @param profile_name: the X2Go session profile name @type profile_name: C{str} - @param cache_types: specify what cache type to update (available: C{sessions}, C{desktops}) + @param cache_types: specify what cache type to update (available: C{sessions}, C{desktops}, C{mounts}) @type cache_types: C{tuple} or C{list} - @param update_sessions: instead of giving a list of cache types, plainly say C{True} here, if + @param update_sessions: instead of giving a list of cache types, plainly say C{True} here, if you want to update sessions in the session list cache. @type update_sessions: C{bool} - @param update_desktops: instead of giving a list of cache types, plainly say C{True} here, if + @param update_desktops: instead of giving a list of cache types, plainly say C{True} here, if you want to update available desktops in the desktop list cache. @type update_desktops: C{bool} + @param update_mounts: instead of giving a list of cache types, plainly say C{True} here, if + you want to update mounted shares in the mount list cache. + @type update_mounts: C{bool} """ if self.listsessions_cache is not None: _update_sessions = ('sessions' in cache_types) or update_sessions _update_desktops = ('desktops' in cache_types) or update_desktops + _update_mounts = ('mounts' in cache_types) or update_mounts try: - self.listsessions_cache.update(profile_name, update_sessions=_update_sessions, update_desktops=_update_desktops) - except x2go_exceptions.X2goControlSessionException: - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + self.listsessions_cache.update(profile_name, update_sessions=_update_sessions, update_desktops=_update_desktops, update_mounts=_update_mounts, ) + except x2go_exceptions.X2GoControlSessionException: + c_sessions = self.client_connected_sessions_of_profile_name(profile_name, return_objects=True) + if len(c_sessions) and c_sessions[0].connected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) __update_cache_by_profile_name = update_cache_by_profile_name - def update_cache_by_session_uuid(self, session_uuid, cache_types=('sessions'), update_sessions=None, update_desktops=None): + def update_cache_by_session_uuid(self, session_uuid, cache_types=('sessions'), update_sessions=None, update_desktops=None, update_mounts=None): """\ - Update the session list cache of a specific L{X2goSession} instance with + Update the session list cache of a specific L{X2GoSession} instance with session identifier . - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @param cache_types: specify what cache type to update (available: C{sessions}, C{desktops}) + @param cache_types: specify what cache type to update (available: C{sessions}, C{desktops}, C{mounts}) @type cache_types: C{tuple} or C{list} - @param update_sessions: instead of giving a list of cache types, plainly say C{True} here, if + @param update_sessions: instead of giving a list of cache types, plainly say C{True} here, if you want to update sessions in the session list cache. @type update_sessions: C{bool} - @param update_desktops: instead of giving a list of cache types, plainly say C{True} here, if + @param update_desktops: instead of giving a list of cache types, plainly say C{True} here, if you want to update available desktops in the desktop list cache. @type update_desktops: C{bool} + @param update_mounts: instead of giving a list of cache types, plainly say C{True} here, if + you want to update mounted shares in the mount list cache. + @type update_mounts: C{bool} """ profile_name = self.get_session_profile_name(session_uuid) @@ -2448,21 +3118,25 @@ cache_types=cache_types, update_sessions=update_sessions, update_desktops=update_desktops, + update_mounts=update_mounts, ) __update_cache_by_session_uuid = update_cache_by_session_uuid - def update_cache_all_profiles(self, cache_types=('sessions'), update_sessions=None, update_desktops=None): + def update_cache_all_profiles(self, cache_types=('sessions'), update_sessions=None, update_desktops=None, update_mounts=None): """\ Update the session list cache of all session profiles. - @param cache_types: specify what cache type to update (available: C{sessions}, C{desktops}) + @param cache_types: specify what cache type to update (available: C{sessions}, C{desktops}, C{mounts}) @type cache_types: C{tuple} or C{list} - @param update_sessions: instead of giving a list of cache types, plainly say C{True} here, if + @param update_sessions: instead of giving a list of cache types, plainly say C{True} here, if you want to update sessions in the session list cache. @type update_sessions: C{bool} - @param update_desktops: instead of giving a list of cache types, plainly say C{True} here, if + @param update_desktops: instead of giving a list of cache types, plainly say C{True} here, if you want to update available desktops in the desktop list cache. @type update_desktops: C{bool} + @param update_mounts: instead of giving a list of cache types, plainly say C{True} here, if + you want to update mounted shares in the mount list cache. + @type update_mounts: C{bool} """ if self.listsessions_cache is not None: @@ -2471,6 +3145,7 @@ cache_types=cache_types, update_sessions=update_sessions, update_desktops=update_desktops, + update_mounts=update_mounts, ) # remove profiles that are not connected any more from cache object @@ -2478,44 +3153,60 @@ __update_cache_all_profiles = update_cache_all_profiles - def register_available_server_sessions_by_profile_name(self, profile_name): + def register_available_server_sessions_by_profile_name(self, profile_name, re_register=False, skip_pubapp_sessions=False): """\ - Register available sessions that are found on the X2go server the profile - of name C{} is connected to. + Register available sessions that are found on the X2Go server the profile + of name C{profile_name} is connected to. - @param profile_name: the X2go session profile name + @param profile_name: the X2Go session profile name @type profile_name: C{str} + @param re_register: re-register available sessions, needs to be done after session profile changes + @type re_register: C{bool} + @param skip_pubapp_sessions: Do not auto-register published applications sessions. + @type skip_pubapp_sessions: C{bool} """ - session_list = self.list_sessions(profile_name=profile_name, - update_sessionregistry=False, - register_sessions=False, - ) + if profile_name not in self.client_connected_profiles(return_profile_names=True): + return + session_list = self._X2GoClient__list_sessions(profile_name=profile_name, + update_sessionregistry=False, + register_sessions=False, + ) try: - self.session_registry.register_available_server_sessions(profile_name, session_list=session_list) - except x2go_exceptions.X2goControlSessionException: - if self.disconnect_profile(profile_name): - self.HOOK_on_control_session_death(profile_name) + self.session_registry.register_available_server_sessions(profile_name, session_list=session_list, re_register=re_register, skip_pubapp_sessions=skip_pubapp_sessions) + except x2go_exceptions.X2GoControlSessionException, e: + c_sessions = self.client_connected_sessions_of_profile_name(profile_name, return_objects=True) + if len(c_sessions) and c_sessions[0].connected: self.HOOK_on_control_session_death(profile_name) + self.disconnect_profile(profile_name) + raise e __register_available_server_sessions_by_profile_name = register_available_server_sessions_by_profile_name - def register_available_server_sessions_by_session_uuid(self, session_uuid): + def register_available_server_sessions_by_session_uuid(self, session_uuid, skip_pubapp_sessions=False): """\ - Register available sessions that are found on the X2go server that the L{X2goSession} instance + Register available sessions that are found on the X2Go server that the L{X2GoSession} instance with session identifier is connected to. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @param skip_pubapp_sessions: Do not auto-register published applications sessions. + @type skip_pubapp_sessions: C{bool} """ profile_name = self.get_session_profile_name(session_uuid) - self.__register_available_server_sessions_by_profile_name(profile_name) + self.__register_available_server_sessions_by_profile_name(profile_name, skip_pubapp_sessions=skip_pubapp_sessions) __register_available_server_sessions_by_session_uuid = register_available_server_sessions_by_session_uuid - def register_available_server_sessions_all_profiles(self): + def register_available_server_sessions_all_profiles(self, skip_pubapp_sessions=False): """\ - Register all available sessions found on an X2go server for each session profile. + Register all available sessions found on an X2Go server for each session profile. + + @param skip_pubapp_sessions: Do not auto-register published applications sessions. + @type skip_pubapp_sessions: C{bool} """ for profile_name in self.client_connected_profiles(return_profile_names=True): - self.__register_available_server_sessions_by_profile_name(profile_name) + try: + self.__register_available_server_sessions_by_profile_name(profile_name, skip_pubapp_sessions=skip_pubapp_sessions) + except x2go_exceptions.X2GoSessionRegistryException: + pass __register_available_server_sessions_all_profiles = register_available_server_sessions_all_profiles diff -Nru python-x2go-0.1.1.8/x2go/defaults.py python-x2go-0.5.0.6/x2go/defaults.py --- python-x2go-0.1.1.8/x2go/defaults.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/defaults.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,50 +1,58 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -Default variables and values for Python X2go. +Default variables and values for Python X2Go. """ __NAME__ = 'x2godefaults-pylib' -import sys import os import paramiko import platform -## X2go imports -import utils - ## -## Common X2go defaults +## Common X2Go defaults ## X2GOCLIENT_OS = platform.system() -LOCAL_HOME = os.path.expanduser('~') +if X2GOCLIENT_OS != 'Windows': + import Xlib.display + import Xlib.error + + # handle missing X displays on package build + try: + X_DISPLAY = Xlib.display.Display() + except Xlib.error.DisplayNameError: + X_DISPLAY = None + except Xlib.error.DisplayConnectionError: + X_DISPLAY = None + +LOCAL_HOME = os.path.normpath(os.path.expanduser('~')) X2GO_SESSIONS_ROOTDIR = '.x2go' X2GO_CLIENT_ROOTDIR = '.x2goclient' X2GO_SSH_ROOTDIR = os.path.join('.x2go','.ssh') # setting OS dependent variables if X2GOCLIENT_OS == "Windows": - # on Windows we will use the current directory as ,,ROOTDIR'' which + # on Windows we will use the current directory as ,,ROOTDIR'' which # will normally be the application directory ROOT_DIR = os.path.abspath(os.path.curdir) ETC_DIR = os.path.join(ROOT_DIR, 'etc') @@ -55,6 +63,7 @@ SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True SUPPORTED_MIMEBOX = True + SUPPORTED_TELEKINESIS = False elif X2GOCLIENT_OS == "Linux": ROOT_DIR = '/' @@ -66,6 +75,7 @@ SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True SUPPORTED_MIMEBOX = True + SUPPORTED_TELEKINESIS = True elif X2GOCLIENT_OS == "Mac": ROOT_DIR = '/' @@ -77,6 +87,7 @@ SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True SUPPORTED_MIMEBOX = True + SUPPORTED_TELEKINESIS = False else: import exceptions @@ -84,84 +95,75 @@ raise OSNotSupportedException('Platform %s is not supported' % platform.system()) ## -## control and terminal session backend as well as session info and proxy backend defaults +## backends of Python X2Go ## -BACKENDS_CONTROLSESSION = { - 'STDOUT': 'X2goControlSessionSTDOUT', -} -BACKENDS_TERMINALSESSION = { - 'STDOUT': 'X2goTerminalSessionSTDOUT', -} -BACKENDS_SERVERSESSIONINFO = { - 'STDOUT': 'X2goServerSessionInfoSTDOUT', -} -BACKENDS_SERVERSESSIONLIST = { - 'STDOUT': 'X2goServerSessionListSTDOUT', -} -BACKENDS_PROXY = { - 'NX3': 'X2goProxyNX3', -} - -BACKEND_CONTROLSESSION_DEFAULT = 'X2goControlSessionSTDOUT' -BACKEND_TERMINALSESSION_DEFAULT = 'X2goTerminalSessionSTDOUT' -BACKEND_SERVERSESSIONINFO_DEFAULT = 'X2goServerSessionInfoSTDOUT' -BACKEND_SERVERSESSIONLIST_DEFAULT = 'X2goServerSessionListSTDOUT' -BACKEND_PROXY_DEFAULT = 'X2goProxyNX3' - -## -## profile backend defaults -## - -BACKENDS_SESSIONPROFILES = { - 'FILE': 'X2goSessionProfilesFILE', - 'GCONF': 'X2goSessionProfilesGCONF', - 'HTTPSBROKER': 'X2goSessionProfilesHTTPSBROKER', - 'WINREG': 'X2goSessionProfilesWINREG', -} -"""Python X2go backends for storing session profiles.""" -BACKENDS_CLIENTSETTINGS = { - 'FILE': 'X2goClientSettingsFILE', - 'GCONF': 'X2goClientSettingsGCONF', - 'HTTPSBROKER': 'X2goClientSettingsHTTPSBROKER', - 'WINREG': 'X2goClientSettingsWINREG', -} -"""Python X2go backends for storing client settings.""" -BACKENDS_CLIENTPRINTING = { - 'FILE': 'X2goClientPrintingFILE', - 'GCONF': 'X2goClientPrintingGCONF', - 'HTTPSBROKER': 'X2goClientPrintingHTTPSBROKER', - 'WINREG': 'X2goClientPrintingWINREG', +BACKENDS = { + 'X2GoControlSession': { + 'default': 'PLAIN', + 'PLAIN': 'x2go.backends.control.plain', + }, + 'X2GoTerminalSession': { + 'default': 'PLAIN', + 'PLAIN': 'x2go.backends.terminal.plain', + }, + 'X2GoServerSessionInfo': { + 'default': 'PLAIN', + 'PLAIN': 'x2go.backends.info.plain', + }, + 'X2GoServerSessionList': { + 'default': 'PLAIN', + 'PLAIN': 'x2go.backends.info.plain', + }, + 'X2GoProxy': { + 'default': 'NX3', + 'NX3': 'x2go.backends.proxy.nx3', + }, + 'X2GoSessionProfiles': { + 'default': 'FILE', + 'FILE': 'x2go.backends.profiles.file', + 'GCONF': 'x2go.backends.profiles.gconf', + 'HTTPBROKER': 'x2go.backends.profiles.httpbroker', + 'SSHBROKER': 'x2go.backends.profiles.sshbroker', + 'WINREG': 'x2go.backends.profiles.winreg', + }, + 'X2GoClientSettings': { + 'default': 'FILE', + 'FILE': 'x2go.backends.settings.file', + 'GCONF': 'x2go.backends.settings.gconf', + 'WINREG': 'x2go.backends.settings.winreg', + }, + 'X2GoClientPrinting': { + 'default': 'FILE', + 'FILE': 'x2go.backends.printing.file', + 'GCONF': 'x2go.backends.printing.gconf', + 'WINREG': 'x2go.backends.printing.winreg', + } } -"""Python X2go backends for storing print settings.""" - -BACKEND_SESSIONPROFILES_DEFAULT = 'X2goSessionProfilesFILE' -BACKEND_CLIENTSETTINGS_DEFAULT = 'X2goClientSettingsFILE' -BACKEND_CLIENTPRINTING_DEFAULT = 'X2goClientPrintingFILE' ## -## X2go Printing +## X2Go Printing ## X2GO_SETTINGS_FILENAME = 'settings' X2GO_SETTINGS_CONFIGFILES = [ - os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'settings'), - os.path.join(ETC_DIR,X2GO_SETTINGS_FILENAME), + os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'settings')), + os.path.normpath(os.path.join(ETC_DIR,X2GO_SETTINGS_FILENAME)), ] X2GO_PRINTING_FILENAME = 'printing' X2GO_PRINTING_CONFIGFILES = [ - os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'printing'), - os.path.join(ETC_DIR,X2GO_PRINTING_FILENAME), + os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'printing')), + os.path.normpath(os.path.join(ETC_DIR,X2GO_PRINTING_FILENAME)), ] X2GO_SESSIONPROFILES_FILENAME = 'sessions' X2GO_SESSIONPROFILES_CONFIGFILES = [ - os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'sessions'), - os.path.join(ETC_DIR,X2GO_SESSIONPROFILES_FILENAME), + os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'sessions')), + os.path.normpath(os.path.join(ETC_DIR,X2GO_SESSIONPROFILES_FILENAME)), ] X2GO_XCONFIG_FILENAME = 'xconfig' X2GO_XCONFIG_CONFIGFILES = [ - os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'xconfig'), - os.path.join(ETC_DIR,X2GO_XCONFIG_FILENAME), + os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'xconfig')), + os.path.normpath(os.path.join(ETC_DIR,X2GO_XCONFIG_FILENAME)), ] X2GO_CLIENTSETTINGS_DEFAULTS = { @@ -171,61 +173,61 @@ 'server': 'localhost', 'port1': 0, 'port2': 0, - }, + }, 'General': { - # clientport is not needed for Python X2go - 'clientport': 22, + # clientport is not needed for Python X2Go + 'clientport': 22, 'autoresume': True, - }, + }, 'Authorization': { 'newprofile': True, 'suspend': True, 'editprofile': True, 'resume': True - }, + }, 'trayicon': { 'enabled': True, 'mintotray': True, 'noclose': True, 'mincon': True, 'maxdiscon': True, - }, - } + }, +} X2GO_CLIENTPRINTING_DEFAULTS = { 'General': { # showdialog will result in a print action that allows opening a print dialog box 'showdialog': False, # if true, open a PDF viewer (or save as PDF file). If false, print via CUPS or print command 'pdfview': True, - }, + }, 'print': { # If false, print via CUPS. If true, run "command" to process the print job 'startcmd': False, # print command for non-CUPS printing 'command': 'lpr', - # ignored in Python X2go + # ignored in Python X2Go 'stdin': False, - # ignored in Python X2go + # ignored in Python X2Go 'ps': False, - }, + }, 'save': { # a path relative to the user's home directory 'folder': 'PDF', }, 'view': { - # If General->pdfview is true: + # If General->pdfview is true: # if open is true, the PDF viewer command is executed - # if open is false, the incoming print job is saved in ~/PDF folder + # if open is false, the incoming print job is saved in ~/PDF folder 'open': True, # command to execute as PDF viewer - 'command': 'xpdf', - }, + 'command': 'xdg-open', + }, 'CUPS': { - # default print queue for CUPS, if print queue does not exist, the default + # default print queue for CUPS, if print queue does not exist, the default # CUPS queue is detected 'defaultprinter': 'PDF', - }, - } + }, +} if X2GOCLIENT_OS == 'Windows': X2GO_CLIENTPRINTING_DEFAULTS['print'].update({'gsprint': os.path.join(os.environ['ProgramFiles'], 'GhostGum', 'gsview', 'gsprint.exe'), }) @@ -233,57 +235,80 @@ if X2GOCLIENT_OS == 'Windows': X2GO_CLIENTXCONFIG_DEFAULTS = { 'XServers': { - 'known_xservers': ['VcXsrv', 'Xming', 'Cygwin-X', ], + 'known_xservers': ['VcXsrv_development', 'VcXsrv_shipped', 'VcXsrv', 'Xming', 'Cygwin-X', ], }, 'Cygwin-X': { 'display': 'localhost:40', + 'last_display': 'localhost:40', 'process_name': 'XWin.exe', 'test_installed': os.path.join(os.environ['SystemDrive'], '\\', 'cygwin', 'bin', 'XWin.exe'), 'run_command': os.path.join(os.environ['SystemDrive'], '\\', 'cygwin', 'bin', 'XWin.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], - }, + }, 'VcXsrv': { 'display': 'localhost:40', + 'last_display': 'localhost:40', 'process_name': 'vcxsrv.exe', - 'test_installed': os.path.join(os.environ['ProgramFiles'], 'VcXsrv', 'vcxsrv.exe'), + 'test_installed': os.path.join(os.environ['ProgramFiles'], 'VcXsrv', 'vcxsrv.exe'), 'run_command': os.path.join(os.environ['ProgramFiles'], 'VcXsrv', 'vcxsrv.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], - }, + }, + 'VcXsrv_shipped': { + 'display': 'localhost:40', + 'last_display': 'localhost:40', + 'process_name': 'vcxsrv.exe', + 'test_installed': os.path.join(os.getcwd(), 'VcXsrv', 'vcxsrv.exe'), + 'run_command': os.path.join(os.getcwd(), 'VcXsrv', 'vcxsrv.exe'), + 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], + }, + 'VcXsrv_development': { + 'display': 'localhost:40', + 'last_display': 'localhost:40', + 'process_name': 'vcxsrv.exe', + 'test_installed': os.path.join(os.getcwd(), '..', 'pyhoca-contrib', 'mswin', 'vcxsrv-mswin', 'VcXsrv-1.15.2.2-xp+vc2013+x2go1_bin', 'vcxsrv.exe'), + 'run_command': os.path.join(os.getcwd(), '..', 'pyhoca-contrib', 'mswin', 'vcxsrv-mswin', 'VcXsrv-1.15.2.2-xp+vc2013+x2go1_bin', 'vcxsrv.exe'), + 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], + }, 'Xming': { 'display': 'localhost:40', + 'last_display': 'localhost:40', 'process_name': 'Xming.exe', - 'test_installed': os.path.join(os.environ['ProgramFiles'], 'Xming', 'Xming.exe'), + 'test_installed': os.path.join(os.environ['ProgramFiles'], 'Xming', 'Xming.exe'), 'run_command': os.path.join(os.environ['ProgramFiles'], 'Xming', 'Xming.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], - }, - } + }, + } else: # make the variable available when building API documentation with epydoc X2GO_CLIENTXCONFIG_DEFAULTS = {} X2GO_GENERIC_APPLICATIONS = [ 'WWWBROWSER', 'MAILCLIENT', 'OFFICE', 'TERMINAL', ] -"""X2go's generic applications.""" +"""X2Go's generic applications.""" X2GO_SESSIONPROFILE_DEFAULTS = { + 'autologin': True, 'autoconnect': False, 'autostart': False, 'setsessiontitle': False, 'sessiontitle': "", 'speed': 2, 'pack': '16m-jpeg', 'quality': 9, 'iconvto': 'UTF-8', 'iconvfrom': 'UTF-8', 'useiconv': False, - 'usesshproxy': False, 'sshproxyhost': '', 'sshproxyuser': '', 'sshproxytunnel': '', 'sshproxykeyfile': '', - 'useexports': True, 'fstunnel': True, 'export': '', + 'usesshproxy': False, 'sshproxyhost': 'proxyhost.mydomain', 'sshproxyport': 22, 'sshproxyuser': '', 'sshproxykeyfile': '', + 'sshproxytype': 'SSH', 'sshproxysameuser': False, 'sshproxysamepass': False, 'sshproxyautologin': True, + 'uniquehostkeyaliases': False, + 'useexports': True, 'restoreexports': False, 'fstunnel': True, 'export': {}, 'usemimebox': False, 'mimeboxextensions': '', 'mimeboxaction': 'OPEN', - 'fullscreen': False, - 'width': 800,'height': 600,'dpi': 96,'setdpi': False, - 'usekbd':True, 'layout': 'us', 'type': 'pc105/us', - 'sound':False, 'soundsystem': 'pulse', 'startsoundsystem': False, 'soundtunnel':True, 'defsndport':True, 'sndport':4713, - 'name': 'NEW_PROFILE', 'icon': ':icons/128x128/x2gosession.png', - 'host': '', 'user': CURRENT_LOCAL_USER, 'key': '', 'sshport': 22, - 'rootless': True, 'applications': X2GO_GENERIC_APPLICATIONS, 'command':'TERMINAL', + 'fullscreen': False, 'clipboard': 'both', + 'width': 800,'height': 600, 'maxdim': False, 'dpi': 96, 'setdpi': False, 'xinerama': False, 'multidisp': False, 'display': 1, + 'usekbd': True, 'layout': 'us', 'type': 'pc105/us', 'variant': '', + 'sound': False, 'soundsystem': 'pulse', 'startsoundsystem': False, 'soundtunnel':True, 'defsndport':True, 'sndport':4713, + 'name': 'NEW_PROFILE', 'icon': ':icons/128x128/x2gosession.png', + 'host': ['server.mydomain'], 'user': CURRENT_LOCAL_USER, 'key': '', 'sshport': 22, 'krblogin': False, 'forwardsshagent': False, + 'rootless': True, 'applications': X2GO_GENERIC_APPLICATIONS, 'command':'TERMINAL', 'published': False, + 'directrdp': False, 'directrdpsettings': '', 'rdpclient': 'rdesktop', 'rdpport': 3389, 'rdpoptions': '-u X2GO_USER -p X2GO_PASSWORD', 'rdpserver': '', 'print': False, 'xdmcpserver': 'localhost', - } -"""L{X2goSessionProfiles} default values to fill a new session profile with.""" +} +"""L{X2GoSessionProfiles} default values to fill a new session profile with.""" ## -## X2go Proxy defaults +## X2Go Proxy defaults ## # here is a list of NX 3.x compression methods, this is the "%"-hashed list that @@ -333,44 +358,48 @@ '\', \''.join(pack_methods_nx3_noqual[57:62]), \ '\', \''.join(pack_methods_nx3_noqual[62:])) -# pack_methods_nx3 is the complete list of NX3 pack methods that can be used to check options +# pack_methods_nx3 is the complete list of NX3 pack methods that can be used to check options # against pack_methods_nx3 = [ m for m in pack_methods_nx3_noqual if "%" not in m ] for meth in [ m for m in pack_methods_nx3_noqual if "%" in m ]: pack_methods_nx3 += [ meth.replace('%','%s' % str(i)) for i in range(0,10) ] pack_methods_nx3.sort() ## -## X2go session defaults +## X2Go session defaults ## X2GO_DESKTOPSESSIONS={ + 'CINNAMON': 'cinnamon', 'KDE': 'startkde', 'GNOME': 'gnome-session', + 'MATE': 'mate-session', + 'XFCE': 'xfce4-session', 'LXDE': 'startlxde', + 'LXQt': 'startlxqt', 'TRINITY': 'starttrinity', - 'UNITY': 'unity-2d-launcher', + 'UNITY': 'unity', } -"""A dictionary with meta-commands for X2go's window manager sessions.""" +"""A dictionary with meta-commands for X2Go's window manager sessions.""" ## -## X2go SFTP server defaults +## X2Go SFTP server defaults ## RSAKEY_STRENGTH = 1024 RSAHostKey = paramiko.RSAKey.generate(RSAKEY_STRENGTH) """\ -An RSA host key for this client session. Python X2go does not use the +An RSA host key for this client session. Python X2Go does not use the system's host key but generates its own host key for each running application instance. """ X2GO_PRINT_ACTIONS = { - 'PDFVIEW': 'X2goPrintActionPDFVIEW', - 'PDFSAVE': 'X2goPrintActionPDFSAVE', - 'PRINT': 'X2goPrintActionPRINT', - 'PRINTCMD': 'X2goPrintActionPRINTCMD', - 'DIALOG': 'X2goPrintActionDIALOG', + 'PDFVIEW': 'X2GoPrintActionPDFVIEW', + 'PDFSAVE': 'X2GoPrintActionPDFSAVE', + 'PRINT': 'X2GoPrintActionPRINT', + 'PRINTCMD': 'X2GoPrintActionPRINTCMD', + 'DIALOG': 'X2GoPrintActionDIALOG', } """Relating print action names and classes.""" @@ -382,19 +411,24 @@ """Default command for the PRINTCMD print action.""" X2GO_MIMEBOX_ACTIONS = { - 'OPEN': 'X2goMIMEboxActionOPEN', - 'OPENWITH': 'X2goMIMEboxActionOPENWITH', - 'SAVEAS': 'X2goMIMEboxActionSAVEAS', + 'OPEN': 'X2GoMIMEboxActionOPEN', + 'OPENWITH': 'X2GoMIMEboxActionOPENWITH', + 'SAVEAS': 'X2GoMIMEboxActionSAVEAS', } """Relating MIME box action names and classes.""" X2GO_MIMEBOX_EXTENSIONS_BLACKLIST = [ - 'LOCK', 'SYS', 'SWP', + 'LOCK', 'SYS', 'SWP', 'EXE', 'COM', 'CMD', 'PS1', 'PS2', 'BAT', 'JS', 'PY', 'PL', 'SH', ] """Black-listed MIME box file extenstions.""" -# X2go desktop sharing +# X2Go desktop sharing X2GO_SHARE_VIEWONLY=0 +"""Constant representing read-only access to shared desktops.""" X2GO_SHARE_FULLACCESS=1 +"""Constant representing read-write (full) access to shared desktops.""" + +PUBAPP_MAX_NO_SUBMENUS=10 +"""Less than ten applications will not get rendered into submenus.""" diff -Nru python-x2go-0.1.1.8/x2go/forward.py python-x2go-0.5.0.6/x2go/forward.py --- python-x2go-0.1.1.8/x2go/forward.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/forward.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,93 +1,106 @@ -#!/usr/bin/env python - -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ Python Gevent based port forwarding server (openssh -L option) for the -proxying of graphical X2go elements. +proxying of graphical X2Go elements. """ __NAME__ = "x2gofwtunnel-pylib" # modules -import os, sys, copy +import copy # gevent/greenlet import gevent from gevent import select, socket from gevent.server import StreamServer -# Python X2go modules +# Python X2Go modules import log from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS -import x2go_exceptions -class X2goFwServer(StreamServer): +class X2GoFwServer(StreamServer): """\ - L{X2goFwServer} implements a gevent's StreamServer based Paramiko/SSH port + L{X2GoFwServer} implements a gevent's StreamServer based Paramiko/SSH port forwarding server. - An L{X2goFwServer} class object is used to tunnel graphical trafic - through an external proxy command launched by a C{X2goProxy*} backend. + An L{X2GoFwServer} class object is used to tunnel graphical trafic + through an external proxy command launched by a C{X2GoProxy*} backend. """ - def __init__ (self, listener, remote_host, remote_port, ssh_transport, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT,): + def __init__ (self, listener, + remote_host, remote_port, + ssh_transport, session_instance=None, session_name=None, + subsystem=None, logger=None, loglevel=log.loglevel_DEFAULT,): """\ @param listener: listen on TCP/IP socket C{(, )} @type listener: C{tuple} - @param remote_host: hostname or IP of remote host (in case of X2go mostly 127.0.0.1) + @param remote_host: hostname or IP of remote host (in case of X2Go mostly 127.0.0.1) @type remote_host: C{str} @param remote_port: port of remote host @type remote_port: C{int} @param ssh_transport: a valid Paramiko/SSH transport object - @type ssh_transport: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goFwServer} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @type ssh_transport: C{obj} + @param session_instance: the complete L{X2GoSession} instance of the X2Go session this port forwarding server belongs to. + Note: for new L{X2GoSession} instances the object has the session name not yet set(!!!) + @type session_instance: C{obj} + @param session_name: the session name of the X2Go session this port forwarding server belongs to + @type session_name: C{str} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoFwServer} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.chan = None self.is_active = False - self.keepalive = False + self.failed = False + self.keepalive = None + self.listener = listener self.chain_host = remote_host self.chain_port = remote_port self.ssh_transport = ssh_transport + self.session_name = session_name self.session_instance = session_instance + self.subsystem = subsystem self.fw_socket = None - StreamServer.__init__(self, listener, self.x2go_forward_tunnel_handle) + StreamServer.__init__(self, self.listener, self.x2go_forward_tunnel_handle) + + def start(self): + self.keepalive = True + return StreamServer.start(self) def x2go_forward_tunnel_handle(self, fw_socket, address): """\ Handle for SSH/Paramiko forwarding tunnel. @param fw_socket: local end of the forwarding tunnel - @type fw_socket: C{instance} + @type fw_socket: C{obj} @param address: unused/ignored @type address: C{tuple} @@ -101,12 +114,7 @@ _count = 0 _maxwait = 20 - while not _success and _count < _maxwait: - - # it is recommended here to have passed on the session instance to this object... - if self.session_instance: - if not self.session_instance.is_connected(): - break + while not _success and _count < _maxwait and self.keepalive: _count += 1 try: @@ -116,35 +124,34 @@ chan_peername = self.chan.getpeername() _success = True except Exception, e: - self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, - self.chain_port, - _count, - _maxwait, - repr(e)), - loglevel=log.loglevel_WARN) + if self.keepalive: + self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, + self.chain_port, + _count, + _maxwait, + repr(e)), + loglevel=log.loglevel_WARN) gevent.sleep(.4) - if not _success: - self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, - self.chain_port, - _count), - loglevel=log.loglevel_ERROR) + if self.keepalive: + self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, + self.chain_port, + _count), + loglevel=log.loglevel_ERROR) + if self.session_instance: + self.session_instance.set_session_name(self.session_name) + self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port, subsystem=self.subsystem) + self.failed = True + else: + self.logger('connected! Tunnel open %r -> %r (on master connection %r -> %r)' % ( + self.listener, (self.chain_host, self.chain_port), + self.fw_socket.getpeername(), chan_peername), + loglevel=log.loglevel_INFO) # once we are here, we can presume the tunnel to be active... self.is_active = True - if self.chan is None: - self.logger('incoming request to [%s]:%d was rejected by the SSH server.' % - (self.chain_host, self.chain_port), loglevel=log.loglevel_ERROR) - if self.session_instance: - self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port) - return - else: - self.logger('connected! Tunnel open %r -> %r -> %r' % (self.fw_socket.getpeername(), - chan_peername, (self.chain_host, self.chain_port)), - loglevel=log.loglevel_INFO) - self.keepalive = True try: while self.keepalive: r, w, x = select.select([self.fw_socket, self.chan], [], []) @@ -160,10 +167,9 @@ fw_socket.send(data) self.close_channel() self.close_socket() - except socket.error: + except (socket.error, EOFError): pass - self.is_active = False self.logger('Tunnel closed from %r' % (chan_peername,), loglevel=log.loglevel_INFO) @@ -210,19 +216,22 @@ Stop the forwarding tunnel. """ + self.is_active = False self.close_socket() StreamServer.stop(self) def start_forward_tunnel(local_host='127.0.0.1', local_port=22022, remote_host='127.0.0.1', remote_port=22, - ssh_transport=None, + ssh_transport=None, session_instance=None, + session_name=None, + subsystem=None, logger=None, ): """\ Setup up a Paramiko/SSH port forwarding tunnel (like openssh -L option). - The tunnel is used to transport X2go graphics data through a proxy application like nxproxy. + The tunnel is used to transport X2Go graphics data through a proxy application like nxproxy. @param local_host: local starting point of the forwarding tunnel @type local_host: C{int} @@ -232,40 +241,42 @@ @type remote_host: C{str} @param remote_port: ... on port C{} @type remote_port: C{int} - @param ssh_transport: the Paramiko/SSH transport (i.e. the X2go sessions Paramiko/SSH transport object) - @type ssh_transport: C{instance} - @param session_instance: the L{X2goSession} instance that initiates this tunnel - @type session_instance: C{instance} - @param logger: an X2goLogger object - @type logger: C{instance} + @param ssh_transport: the Paramiko/SSH transport (i.e. the X2Go session's Paramiko/SSH transport object) + @type ssh_transport: C{obj} + @param session_instance: the L{X2GoSession} instance that initiates this tunnel + @type session_instance: C{obj} + @param session_name: the session name of the X2Go session this port forwarding server belongs to + @type session_name: C{str} + @param subsystem: a custom string with a component name that tries to evoke a new tunnel setup + @type subsystem: C{str} + @param logger: an X2GoLogger object + @type logger: C{obj} - @return: returns an L{X2goFwServer} instance - @rtype: C{instance} + @return: returns an L{X2GoFwServer} instance + @rtype: C{obj} """ + fw_server = X2GoFwServer(listener=(local_host, local_port), + remote_host=remote_host, remote_port=remote_port, + ssh_transport=ssh_transport, + session_instance=session_instance, session_name=session_name, + subsystem=subsystem, + logger=logger, + ) try: - fw_server = X2goFwServer(listener=(local_host, local_port), - remote_host=remote_host, remote_port=remote_port, - ssh_transport=ssh_transport, session_instance=session_instance, - logger=logger, - ) - try: - fw_server.start() - return fw_server - except socket.error: - pass - except x2go_exceptions.X2goFwTunnelException: - pass - - return None + fw_server.start() + except socket.error: + fw_server.failed = True + fw_server.is_active = False + return fw_server def stop_forward_tunnel(fw_server): """\ Tear down a given Paramiko/SSH port forwarding tunnel. - @param fw_server: an L{X2goFwServer} instance as returned by the L{start_forward_tunnel()} function - @type fw_server: C{instance} + @param fw_server: an L{X2GoFwServer} instance as returned by the L{start_forward_tunnel()} function + @type fw_server: C{obj} """ if fw_server is not None: diff -Nru python-x2go-0.1.1.8/x2go/gevent_subprocess.py python-x2go-0.5.0.6/x2go/gevent_subprocess.py --- python-x2go-0.1.1.8/x2go/gevent_subprocess.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/gevent_subprocess.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. @@ -33,7 +33,7 @@ ### ### ### Thus, I place myself as the copyright holder for code in this file -### for cases in that it is used in context of the X2go project. +### for cases in that it is used in context of the X2Go project. ### @@ -211,4 +211,4 @@ # already died and has been cleaned up return -1 else: - raise \ No newline at end of file + raise diff -Nru python-x2go-0.1.1.8/x2go/guardian.py python-x2go-0.5.0.6/x2go/guardian.py --- python-x2go-0.1.1.8/x2go/guardian.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/guardian.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goSessionGuardian class - a guardian thread that controls X2go session threads +X2GoSessionGuardian class - a guardian thread that controls X2Go session threads and their sub-threads (like reverse forwarding tunnels, Paramiko transport threads, etc.). @@ -30,56 +30,54 @@ import threading import copy -# Python X2go modules +# Python X2Go modules from cleanup import x2go_cleanup -import x2go_exceptions import log -_sigterm_received = False -def _sigterm_handle(s, f): - global _sigterm_received - _sigterm_received = True - return 0 - -class X2goSessionGuardian(threading.Thread): +class X2GoSessionGuardian(threading.Thread): """\ - L{X2goSessionGuardian} thread controls X2go session threads and their sub-threads (like + L{X2GoSessionGuardian} thread controls X2Go session threads and their sub-threads (like reverse forwarding tunnels, Paramiko transport threads, etc.). Its main function is - to tidy up once a session gets interrupted (SIGTERM, SIGINT). + to tidy up once a session gets interrupted (SIGTERM, SIGINT). - There is one L{X2goSessionGuardian} for each L{X2goClient} instance (thus: for normal - setups there should be _one_ L{X2goClient} and _one_ L{X2goSessionGuardian} in use). + There is one L{X2GoSessionGuardian} for each L{X2GoClient} instance (thus: for normal + setups there should be _one_ L{X2GoClient} and _one_ L{X2GoSessionGuardian} in use). """ - - def __init__(self, client_instance, - auto_update_listsessions_cache=False, - auto_update_listdesktops_cache=False, + def __init__(self, client_instance, + auto_update_listsessions_cache=False, + auto_update_listdesktops_cache=False, + auto_update_listmounts_cache=False, auto_update_sessionregistry=False, auto_register_sessions=False, + no_auto_reg_pubapp_sessions=False, refresh_interval=5, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param auto_update_listsessions_cache: let L{X2goSessionGuardian} refresh the session list cache for all L{X2goSession} objects + @param auto_update_listsessions_cache: let L{X2GoSessionGuardian} refresh the session list cache for all L{X2GoSession} objects @type auto_update_listsessions_cache: C{bool} - @param auto_update_listdesktops_cache: let L{X2goSessionGuardian} refresh desktop lists in the session list cache for all L{X2goSession} objects + @param auto_update_listdesktops_cache: let L{X2GoSessionGuardian} refresh desktop lists in the session list cache for all L{X2GoSession} objects @type auto_update_listdesktops_cache: C{bool} + @param auto_update_listmounts_cache: let L{X2GoSessionGuardian} refresh mount lists in the session list cache for all L{X2GoSession} objects + @type auto_update_listmounts_cache: C{bool} @param auto_update_sessionregistry: if set to C{True} the session status will be updated in regular intervals @type auto_update_sessionregistry: C{bool} - @param auto_register_sessions: register new sessions automatically once they appear in the X2go session (e.g. - instantiated by another client that is connected to the same X2go server under same user ID) + @param auto_register_sessions: register new sessions automatically once they appear in the X2Go session (e.g. + instantiated by another client that is connected to the same X2Go server under same user ID) @type auto_register_sessions: C{bool} + @param no_auto_reg_pubapp_sessions: do not auto-register published applications sessions + @type no_auto_reg_pubapp_sessions: C{bool} @param refresh_interval: refresh cache and session registry every seconds @type refresh_interval: C{int} - @param logger: you can pass an L{X2goLogger} object to the L{X2goSessionGuardian} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the L{X2GoSessionGuardian} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -87,8 +85,10 @@ self.client_instance = client_instance self.auto_update_listsessions_cache = auto_update_listsessions_cache self.auto_update_listdesktops_cache = auto_update_listdesktops_cache + self.auto_update_listmounts_cache = auto_update_listmounts_cache self.auto_update_sessionregistry = auto_update_sessionregistry self.auto_register_sessions = auto_register_sessions + self.no_auto_reg_pubapp_sessions = no_auto_reg_pubapp_sessions self.refresh_interval = refresh_interval threading.Thread.__init__(self, target=self.guardian) @@ -97,25 +97,23 @@ def guardian(self): """\ - The handler of this L{X2goSessionGuardian} thread. + The handler of this L{X2GoSessionGuardian} thread. """ - global _sigterm_received - seconds = 0 self._keepalive = True - while not _sigterm_received and self._keepalive: + while self._keepalive: gevent.sleep(1) seconds += 1 if seconds % self.refresh_interval == 0: - self.logger('Entering X2go Guardian client management loop...', loglevel=log.loglevel_DEBUG) - + self.logger('Entering X2Go Guardian client management loop...', loglevel=log.loglevel_DEBUG) if self.auto_update_listsessions_cache: - self.client_instance.update_cache_all_profiles(update_sessions=self.auto_update_listsessions_cache, + self.client_instance.update_cache_all_profiles(update_sessions=self.auto_update_listsessions_cache, update_desktops=self.auto_update_listdesktops_cache, + update_mounts=self.auto_update_listmounts_cache, ) if self.auto_update_sessionregistry and not self.auto_register_sessions: @@ -123,9 +121,9 @@ # session auto-registration will automatically trigger an update of the session registry status if self.auto_register_sessions: - self.client_instance.register_available_server_sessions_all_profiles() + self.client_instance.register_available_server_sessions_all_profiles(skip_pubapp_sessions=self.no_auto_reg_pubapp_sessions) - self.logger('X2go session guardian thread waking up after %s seconds' % seconds, loglevel=log.loglevel_DEBUG) + self.logger('X2Go session guardian thread waking up after %s seconds' % seconds, loglevel=log.loglevel_DEBUG) for session_uuid in self.client_instance.session_registry.keys(): session_summary = self.client_instance.get_session_summary(session_uuid) @@ -134,7 +132,7 @@ def stop_thread(self): """\ - Stop this L{X2goSessionGuardian} thread. + Stop this L{X2GoSessionGuardian} thread. """ self._keepalive = False diff -Nru python-x2go-0.1.1.8/x2go/inifiles.py python-x2go-0.5.0.6/x2go/inifiles.py --- python-x2go-0.1.1.8/x2go/inifiles.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/inifiles.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. @@ -24,7 +24,7 @@ # none so far """\ -X2goProcessIniFile - helper class for parsing .ini files +X2GoProcessIniFile - helper class for parsing .ini files """ __NAME__ = 'x2goinifiles-pylib' @@ -36,29 +36,22 @@ import cStringIO import copy -# Python X2go modules +# Python X2Go modules from defaults import LOCAL_HOME as _current_home import log import utils -class X2goIniFile(object): +class X2GoIniFile(object): """ - Base class for processing the different ini files used by X2go + Base class for processing the different ini files used by X2Go clients. Primarily used to standardize the content of the different - X2go client ini file (settings, printing, sessions, xconfig). + X2Go client ini file (settings, printing, sessions, xconfig). If entries are omitted in an ini file, they are filled with - default values (as hard coded in Python X2go), so the resulting objects + default values (as hard coded in Python X2Go), so the resulting objects always contain the same fields. """ - defaultValues = { - 'none': { - 'none': 'empty', - }, - } - write_user_config = False - user_config_file = None def __init__(self, config_files, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ @@ -66,22 +59,25 @@ directory filename) @type config_files: C{list} @param defaults: a cascaded Python dicitionary structure with ini file defaults (to override - Python X2go's hard coded defaults in L{defaults} - @type defaults: dict - @param logger: you can pass an L{X2goLogger} object to the - L{X2goFwServer} constructor - @type logger: L{X2goLogger} instance - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + Python X2Go's hard coded defaults in L{defaults} + @type defaults: C{dict} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoIniFile} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel - @type loglevel: int + @type loglevel: C{int} """ + self.user_config_file = None + self._write_user_config = True + # make sure a None type gets turned into list type if not config_files: config_files = [] if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -90,27 +86,30 @@ if utils._checkIniFileDefaults(defaults): self.defaultValues = defaults + else: + self.defaultValues = {} # we purposefully do not inherit the SafeConfigParser class # here as we do not want to run into name conflicts between - # X2go ini file options and method / property names in + # X2Go ini file options and method / property names in # SafeConfigParser... This is a pre-cautious approach... self.iniConfig = ConfigParser.SafeConfigParser() self.iniConfig.optionxform = str _create_file = False for file_name in self.config_files: + file_name = os.path.normpath(file_name) if file_name.startswith(_current_home): if not os.path.exists(file_name): utils.touch_file(file_name) _create_file = True + self.user_config_file = file_name break - self.load() if _create_file: - self.write_user_config = True - self.write() + self._write_user_config = True + self._X2GoIniFile__write() def load(self): """\ @@ -122,7 +121,7 @@ self.logger('config files found: %s' % _found_config_files or 'none', loglevel=log.loglevel_INFO, ) for file_name in _found_config_files: - if file_name.startswith(_current_home): + if file_name.startswith(os.path.normpath(_current_home)): # we will use the first file found in the user's home dir for writing modifications self.user_config_file = file_name break @@ -131,10 +130,11 @@ self._fill_defaults() def __repr__(self): - result = 'X2goIniFile(' + result = 'X2GoIniFile(' for p in dir(self): if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue - result += p + '=' + str(self.__dict__[p]) + result += p + '=' + str(self.__dict__[p]) + ',' + result = result.strip(',') return result + ')' def _storeValue(self, section, key, value): @@ -142,7 +142,7 @@ Stores a value for a given section and key. This methods affects a SafeConfigParser object held in - RAM. No configuration file is affected by this + RAM. No configuration file is affected by this method. To write the configuration to disk use the L{write()} method. @@ -165,9 +165,9 @@ def _fill_defaults(self): """\ - Fills a C{SafeConfigParser} object with the default ini file - values as pre-defined in Python X2go or. This SafeConfigParser - object is held in RAM. No configuration file is affected by this + Fills a C{SafeConfigParser} object with the default ini file + values as pre-defined in Python X2Go or. This SafeConfigParser + object is held in RAM. No configuration file is affected by this method. """ @@ -194,7 +194,8 @@ if not self.iniConfig.has_section(section): self.iniConfig.add_section(section) self._storeValue(section, key, value) - self.write_user_config = True + self._write_user_config = True + __update_value = update_value def write(self): """\ @@ -203,12 +204,21 @@ For writing the first of the C{config_files} specified on instance construction that is writable will be used. + @return: C{True} if the user config file has been successfully written, C{False} otherwise. + @rtype: C{bool} + """ - if self.user_config_file and self.write_user_config: - fd = open(self.user_config_file, 'wb') - self.iniConfig.write(fd) - fd.close() - self.write_user_config = False + if self.user_config_file and self._write_user_config: + try: + fd = open(self.user_config_file, 'wb') + self.iniConfig.write(fd) + fd.close() + self._write_user_config = False + return True + except Exception, e: + print e + return False + __write = write def get_type(self, section, key): """\ @@ -220,7 +230,7 @@ @param key: the ini file key in the given section @type key: C{str} - @return: a Python variable type + @return: a Python variable type @rtype: class """ @@ -247,13 +257,15 @@ elif key_type is types.IntType: return self.iniConfig.getint(section, key) elif key_type is types.ListType: - val = self.iniConfig.get(section, key) - if val.startswith('[') and val.endswith(']'): - return eval(val) - elif ',' in val: - val = [ v.strip() for v in val.split(',') ] + _val = self.iniConfig.get(section, key) + _val = _val.strip() + if _val.startswith('[') and _val.endswith(']'): + return eval(_val) + elif ',' in _val: + _val = [ v.strip() for v in _val.split(',') ] else: - val = [ val ] + _val = [ _val ] + return _val else: _val = self.iniConfig.get(section, key) return _val.decode(utils.get_encoding()) diff -Nru python-x2go-0.1.1.8/x2go/__init__.py python-x2go-0.5.0.6/x2go/__init__.py --- python-x2go-0.1.1.8/x2go/__init__.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/__init__.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,193 +1,231 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -Python X2go is a python module that implements X2go client support for -the free X2go server project (U{http://wiki.x2go.org}) in Python. +Python X2Go is a python module that implements X2Go client support for +the free X2Go server project (U{http://wiki.x2go.org}) in Python. Introduction ============ - With Python X2go you can write your own X2go clients or embed X2go client + With Python X2Go you can write your own X2Go clients or embed X2Go client features into already existing application environments. + NOTE: Beginning with v0.4.0.0 of Python X2Go all class names start with + X2Go***. Earlier versions used X2go***. As X2Go is the official name of the + project (i.e. with a capital X and a capital G) we have adapted the class + names to this circumstance. + API Concept =========== - Python X2go consists of quite a few classes. Furthermore, - Python X2go is quite heavily taking advantage of Python\'s + Python X2Go consists of quite a few classes. Furthermore, + Python X2Go is quite heavily taking advantage of Python\'s threading features. When providing a library like Python - X2go, it is always quite a task to keep the library code + X2Go, it is always quite a task to keep the library code compatible with former versions of the same library. This is - intended for Python X2go, but with some restraints. + intended for Python X2Go, but with some restraints. - Python X2go only offers 5 public API classes. With the release of - version 0.1.0.0, we will try to keep these 5 public API classes - of future releases as compatible as possible with versions of Python X2go + Python X2Go only offers five public API classes. With the release of + version 0.1.0.0, we will try to keep these five public API classes + of future releases as compatible as possible with versions of Python X2Go greater/equal than v0.1.0.0. - The 4 public API classes are: + The five public API classes are: - - L{X2goClient} --- a whole X2go client API - - L{X2goSession} --- management of an individual X2go - session started from an L{X2goClient} instance - - L{X2goClientSettings} --- provide access to X2go (global and + - L{X2GoClient} --- a whole X2Go client API + - L{X2GoSession} --- management of an individual X2Go + session--either started standalone or from within an L{X2GoClient} instance + - L{X2GoClientSettings} --- provide access to X2Go (global and user) configuration node »settings« - - L{X2goClientPrinting} --- provide access to X2go (global and + - L{X2GoClientPrinting} --- provide access to X2Go (global and user) configuration node »printing« - - L{X2goSessionProfiles} --- provide access to X2go (global and + - L{X2GoSessionProfiles} --- provide access to X2Go (global and user) configuration node »sessions« - Any other of the Python X2go classes may be subject to internal changes + Plus two extra classes on MS Windows platforms: + + - L{X2GoClientXConfig} and L{X2GoXServer} --- these classes will be initialized + during L{X2GoClient} instantiation on MS Windows platforms and start an installed XServer + + Any other of the Python X2Go classes may be subject to internal changes and the way of addressing these classes in code may vary between different - versions of Python X2go. If you directly use other than the 5 public API + versions of Python X2Go. If you directly use other than the five public API classes in your own applications, so please be warned. API Structure ============= - When using Python X2go in your applications, the basic idea is that you - create your own class and inherit the X2goClient class in that:: + When using Python X2Go in your applications, the basic idea is that you + create your own class and inherit the X2GoClient class in that:: import x2go - class MyX2goClient(x2go.X2goClient): + class MyX2GoClient(x2go.X2GoClient): ... - The L{X2goClient} class flattens the complex structure of Python X2go into - many L{X2goClient} methods that you can use in your own C{MyX2goClient} instances. - - However, it might be handy to retrieve a whole X2go session instance - from the L{X2goClient} instance. This can be achieved by the - L{X2goClient.register_session()} method:: + Python X2Go is capable of handling multiple running/suspended sessions within the + same client instance, so for your application, there should not be any need of + instantiating more than one L{X2GoClient} object in parallel. + + NOTE: Doing so is--herewith--fully disrecommended. + + The L{X2GoClient} class flattens the complex structure of Python X2Go into + many L{X2GoClient} methods that you can use in your own C{MyX2GoClient} instance. + + However, it might be handy to retrieve a whole X2Go session instance + from the L{X2GoClient} instance. This can be achieved by the + L{X2GoClient.register_session()} method:: import x2go - my_x2gocli = MyX2goClient() + my_x2gocli = MyX2GoClient() reg_session_instance = my_x2gocli.register_session(, return_object=True) Whereas can be as simple as:: »profile_name=« - or contain a whole set of L{X2goSession} parameters that can be used to start a - session manually (i.e. a session that is based on a pre-configured session profile + or contain a whole set of L{X2GoSession} parameters that can be used to start a + session manually (i.e. a session that is based on a pre-configured session profile in either of the »sessions« config files). - The L{X2goClient.register_session()} method---in object-retrieval-mode---returns - an L{X2goSession} instance. With this instance you can then manage - your X2go session:: + The L{X2GoClient.register_session()} method---in object-retrieval-mode---returns + an L{X2GoSession} instance. With this instance you can then manage + your X2Go session:: import gevent, getpass pw=getpass.getpass() # authenticate reg_session_instance.connect(password=pw, ) - # launch the session window - reg_session_instance.start() - # or resume the youngest session for + # then launch the session window with either a new session + if start: + reg_session_instance.start() + # or resume a session + if resume: + reg_session_instance.resume(session_name=) + # leave it runnint for 60 seconds gevent.sleep(60) - reg_session_instance.suspend() - # or alternatively: - reg_session_instance.terminate() + # and then suspend + if suspend: + reg_session_instance.suspend() + # or alternatively terminate it + elif terminate: + reg_session_instance.terminate() - How to access---especially how to modify---the X2go client configuration + How to access---especially how to modify---the X2Go client configuration files »settings«, »printing«, »sessions« and »xconfig« (Windows only) - is explained in detail with each class declaration in this API documentation. - Please refer to the class docs of L{X2goClientSettings}, L{X2goClientPrinting}, - L{X2goSessionProfiles} and L{X2goXServer}. + is explained in detail with each class declaration in this API documentation. + Please refer to the class docs of L{X2GoClientSettings}, L{X2GoClientPrinting}, + L{X2GoSessionProfiles} and L{X2GoXServer}. + Configuration and Session Management ==================================== - Python X2go strictly separates configuration management from + Python X2Go strictly separates configuration management from session management. The classes needed for session management - / administration are supposed to only gain »read access« to the - classes handling the X2go client configuration nodes. + / administration are supposed to only gain »read access« to the + classes handling the X2Go client configuration nodes. - A configuration node in Python X2go can be a file, a gconf database + A configuration node in Python X2Go can be a file, a gconf database section, a section in the Windows registry, etc. - NOTE: Each configuration node will be re-read whenever it is needed - by an X2go sesion or the X2goClient class itself. + NOTE: Each configuration node will be re-read whenever it is needed + by an X2Go sesion or the X2GoClient class itself. Conclusively, any change to either of the configuration nodes - will be reflected as a change in your X2go client behaviour: + will be reflected as a change in your X2Go client behaviour: - - L{X2goSessionProfiles}: changes to a session profile in + - L{X2GoSessionProfiles}: changes to a session profile in the »sessions« node will be available for the next registered - L{X2goSession} instance - - L{X2goClientPrinting}: on each incoming X2go print job the - »printing« configuration node will be re-read, thus you can - change your X2go client's print setup during a running session - - L{X2goClientSettings}: also the configuration node »settings« - is re-read whenever needed in the course of X2go session management - - L{X2goXServer} (Windows only): this class will only be initialized + L{X2GoSession} instance + - L{X2GoClientPrinting}: on each incoming X2Go print job the + »printing« configuration node will be re-read, thus you can + change your X2Go client's print setup during a running session + - L{X2GoClientSettings}: also the configuration node »settings« + is re-read whenever needed in the course of X2Go session management + - L{X2GoClientXConfig} and L{X2GoXServer} (Windows only): these classes will only be initialized once (starting the XServer on Windows platforms) on construction - of an L{X2goClient} instance + of an L{X2GoClient} instance Dependencies ============ - Python X2go takes advantage of the libevent/greenlet implementation + Python X2Go takes advantage of the libevent/greenlet implementation gevent (http://www.gevent.org). The least needed version of Python gevent - is 0.13.0 (or above). + is 0.13.0. On MS Windows Python gevent 1.0 is highly recommended. - Python X2go (because of gevent) requires at least Python 2.6. Further recent - information on Python X2go is available at: + Python X2Go (because of gevent) requires at least Python 2.6. Further recent + information on Python X2Go is available at: U{http://wiki.x2go.org/python-x2go} Contact ======= - If you have any questions concerning Python X2go, please sign up for the - x2go-dev list (https://lists.berlios.de/mailman/listinfo/x2go-dev) and post + If you have any questions concerning Python X2Go, please sign up for the + x2go-dev list (https://lists.x2go.org/listinfo/x2go-dev) and post your questions, requests and feedbacks there. + """ __NAME__ = 'python-x2go' -__VERSION__ = '0.1.1.8' +__VERSION__ = '0.5.0.6' + +import os +from defaults import X2GOCLIENT_OS + +if X2GOCLIENT_OS != 'Windows': + # enforce "ares" resolve in gevent (>= 1.0~)... + os.environ["GEVENT_RESOLVER"] = "ares" from gevent import monkey monkey.patch_all() import utils -import guardian -import signal as _signal -_signal.signal (_signal.SIGTERM, guardian._sigterm_handle ) -_signal.signal (_signal.SIGINT, guardian._sigterm_handle ) - -from client import X2goClient -from backends.profiles import X2goSessionProfiles -from backends.printing import X2goClientPrinting -from backends.settings import X2goClientSettings -from session import X2goSession -from sshproxy import X2goSSHProxy +from client import X2GoClient +from backends.profiles.file import X2GoSessionProfiles +from backends.printing.file import X2GoClientPrinting +from backends.settings.file import X2GoClientSettings +from session import X2GoSession +from sshproxy import X2GoSSHProxy from x2go_exceptions import * from log import * from cleanup import x2go_cleanup -from defaults import X2GOCLIENT_OS from defaults import CURRENT_LOCAL_USER from defaults import LOCAL_HOME from defaults import X2GO_CLIENT_ROOTDIR from defaults import X2GO_SESSIONS_ROOTDIR from defaults import X2GO_SSH_ROOTDIR +from defaults import BACKENDS if X2GOCLIENT_OS == 'Windows': - from xserver import X2goClientXConfig, X2goXServer + from xserver import X2GoClientXConfig, X2GoXServer + +# compat section +X2goClient = X2GoClient +X2goSessionProfiles = X2GoSessionProfiles +X2goClientPrinting = X2GoClientPrinting +X2goClientSettings = X2GoClientSettings +X2goSession = X2GoSession +X2goSSHProxy = X2GoSSHProxy +if X2GOCLIENT_OS == 'Windows': + X2goClientXConfig = X2GoClientXConfig + X2goXServer = X2GoXServer diff -Nru python-x2go-0.1.1.8/x2go/log.py python-x2go-0.5.0.6/x2go/log.py --- python-x2go-0.1.1.8/x2go/log.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/log.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goLogger class - flexible handling of log and debug output. +X2GoLogger class - flexible handling of log and debug output. """ __NAME__ = 'x2gologger-pylib' @@ -26,6 +26,7 @@ # modules import os import sys +import types loglevel_NONE = 0 loglevel_ERROR = 8 @@ -37,15 +38,15 @@ loglevel_DEFAULT = loglevel_ERROR | loglevel_WARN | loglevel_NOTICE """\ -Default loglevel of X2goLogger objects is: NOTICE & WARN & ERROR +Default loglevel of X2GoLogger objects is: NOTICE & WARN & ERROR """ -# Python X2go modules +# Python X2Go modules import utils -class X2goLogger(object): +class X2GoLogger(object): """\ - A simple logger class, that is used by all Python X2go classes. + A simple logger class, that is used by all Python X2Go classes. """ name = '' @@ -54,19 +55,19 @@ level = -1 destination = sys.stderr - _loglevel_NAMES = {8: 'error', - 16: 'warn', - 32: 'notice', - 64: 'info', - 128: 'debug', - 1024: 'debug-sftpxfer', + _loglevel_NAMES = {8: 'error', + 16: 'warn', + 32: 'notice', + 64: 'info', + 128: 'debug', + 1024: 'debug-sftpxfer', } def __init__(self, name=sys.argv[0], loglevel=loglevel_DEFAULT, tag=None): """\ - @param name: name of the programme that uses Python X2go + @param name: name of the programme that uses Python X2Go @type name: C{str} - @param loglevel: log level for Python X2go + @param loglevel: log level for Python X2Go @type loglevel: C{int} @param tag: additional tag for all log entries @type tag: C{str} @@ -183,3 +184,7 @@ """ self.loglevel = self.loglevel ^ loglevel_DEBUG_SFTPXFER + +# compat section +X2goLogger = X2GoLogger + diff -Nru python-x2go-0.1.1.8/x2go/mimeboxactions.py python-x2go-0.5.0.6/x2go/mimeboxactions.py --- python-x2go-0.1.1.8/x2go/mimeboxactions.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/mimeboxactions.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,37 +1,32 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ For MIME box jobs there are currently three handling actions available: -L{X2goMIMEboxActionOPEN}, L{X2goMIMEboxActionOPENWITH} and L{X2goMIMEboxActionSAVEAS}. +L{X2GoMIMEboxActionOPEN}, L{X2GoMIMEboxActionOPENWITH} and L{X2GoMIMEboxActionSAVEAS}. """ __NAME__ = 'x2gomimeboxactions-pylib' # modules import os -import sys -import shutil import copy -import types -import threading import time from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS @@ -40,18 +35,17 @@ import win32api else: import gevent_subprocess as subprocess + import x2go_exceptions + WindowsErrror = x2go_exceptions.WindowsError -# Python X2go modules +# Python X2Go modules import log -import defaults -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -import utils import x2go_exceptions _MIMEBOX_ENV = os.environ.copy() -class X2goMIMEboxAction(object): +class X2GoMIMEboxAction(object): __name__ = 'NAME' __description__ = 'DESCRIPTION' @@ -59,60 +53,76 @@ def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ This is a meta class and has no functionality as such. It is used as parent - class by »real« X2go MIME box actions. + class by »real« X2Go MIME box actions. - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goMIMEboxAction} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoMIMEboxAction} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ - # these get set from within the X2goMIMEboxQueue class + # these get set from within the X2GoMIMEboxQueue class self.profile_name = 'UNKNOWN' self.session_name = 'UNKNOWN' self.client_instance = client_instance @property - def name(): + def name(self): """\ - Return the X2go MIME box action's name. + Return the X2Go MIME box action's name. """ return self.__name__ @property - def description(): + def description(self): """\ - Return the X2go MIME box action's description text. + Return the X2Go MIME box action's description text. """ return self.__description__ - def do_process(self, mimebox_file, mimebox_dir, ): + def _do_process(self, mimebox_file, mimebox_dir, ): """\ - Perform the defined MIME box action (doing nothing in L{X2goMIMEboxAction} parent class). + Perform the defined MIME box action (doing nothing in L{X2GoMIMEboxAction} parent class). - @param mimebox_file: file name as placed in to the X2go MIME box directory + @param mimebox_file: file name as placed in to the X2Go MIME box directory @type mimebox_file: C{str} - @param mimebox_dir: location of the X2go sessions's MIME box directory + @param mimebox_dir: location of the X2Go session's MIME box directory @type mimebox_dir: C{str} """ pass + def do_process(self, mimebox_file, mimebox_dir, ): + """\ + Wrapper method for the actual processing of MIME + box actions. + + @param mimebox_file: file name as placed in to the X2Go MIME box directory + @type mimebox_file: C{str} + @param mimebox_dir: location of the X2Go session's MIME box directory + @type mimebox_dir: C{str} + + """ + mimebox_file = os.path.normpath(mimebox_file) + mimebox_dir = os.path.normpath(mimebox_dir) + + self._do_process(mimebox_file, mimebox_dir) + -class X2goMIMEboxActionOPEN(X2goMIMEboxAction): +class X2GoMIMEboxActionOPEN(X2GoMIMEboxAction): """\ MIME box action that opens incoming files in the system's default application. @@ -122,29 +132,32 @@ def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goMIMEboxActionOPEN} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoMIMEboxActionOPEN} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ self.client_instance = client_instance - X2goMIMEboxAction.__init__(self, logger=logger, loglevel=loglevel) + X2GoMIMEboxAction.__init__(self, logger=logger, loglevel=loglevel) - def do_process(self, mimebox_file, mimebox_dir, ): + def _do_process(self, mimebox_file, mimebox_dir, ): """\ Open an incoming MIME box file in the system's default application. @param mimebox_file: file name as placed in to the MIME box directory @type mimebox_file: C{str} - @param mimebox_dir: location of the X2go session's MIME box directory + @param mimebox_dir: location of the X2Go session's MIME box directory @type mimebox_dir: C{str} """ + mimebox_file = os.path.normpath(mimebox_file) + mimebox_dir = os.path.normpath(mimebox_dir) + if _X2GOCLIENT_OS == "Windows": self.logger('opening incoming MIME box file with Python\'s os.startfile() command: %s' % mimebox_file, loglevel=log.loglevel_DEBUG) try: @@ -162,11 +175,11 @@ else: cmd_line = [ 'xdg-open', os.path.join(mimebox_dir, mimebox_file), ] self.logger('opening MIME box file with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) - p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_MIMEBOX_ENV) + subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_MIMEBOX_ENV) time.sleep(20) -class X2goMIMEboxActionOPENWITH(X2goMIMEboxAction): +class X2GoMIMEboxActionOPENWITH(X2GoMIMEboxAction): """\ MIME box action that calls the system's ,,Open with...'' dialog on incoming files. Currently only properly implementable on Windows platforms. @@ -177,29 +190,32 @@ def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goMIMEboxActionOPENWITH} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoMIMEboxActionOPENWITH} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ self.client_instance = client_instance - X2goMIMEboxAction.__init__(self, logger=logger, loglevel=loglevel) + X2GoMIMEboxAction.__init__(self, logger=logger, loglevel=loglevel) - def do_process(self, mimebox_file, mimebox_dir, ): + def _do_process(self, mimebox_file, mimebox_dir, ): """\ Open an incoming MIME box file in the system's default application. @param mimebox_file: file name as placed in to the MIME box directory @type mimebox_file: C{str} - @param mimebox_dir: location of the X2go session's MIME box directory + @param mimebox_dir: location of the X2Go session's MIME box directory @type mimebox_dir: C{str} """ + mimebox_file = os.path.normpath(mimebox_file) + mimebox_dir = os.path.normpath(mimebox_dir) + if _X2GOCLIENT_OS == "Windows": self.logger('evoking Open-with dialog on incoming MIME box file: %s' % mimebox_file, loglevel=log.loglevel_DEBUG) win32api.ShellExecute ( @@ -215,14 +231,14 @@ self.logger('the evocation of the Open-with dialog box is currently not available on Linux, falling back to MIME box action OPEN', loglevel=log.loglevel_WARN) cmd_line = [ 'xdg-open', os.path.join(mimebox_dir, mimebox_file), ] self.logger('opening MIME box file with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) - p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_MIMEBOX_ENV) + subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_MIMEBOX_ENV) time.sleep(20) -class X2goMIMEboxActionSAVEAS(X2goMIMEboxAction): +class X2GoMIMEboxActionSAVEAS(X2GoMIMEboxAction): """\ - MIME box action that allows saving incoming MIME box files to a local folder. What this - MIME box actually does is calling a hook method in the L{X2goClient} instance that + MIME box action that allows saving incoming MIME box files to a local folder. What this + MIME box actually does is calling a hook method in the L{X2GoClient} instance that can be hi-jacked by one of your application's methods which then can handle the ,,Save as...'' request. @@ -232,35 +248,40 @@ def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: an L{X2goClient} instance, within your customized L{X2goClient} make sure + @param client_instance: an L{X2GoClient} instance, within your customized L{X2GoClient} make sure you have a C{HOOK_open_mimebox_saveas_dialog(filename=)} method defined that will actually handle the incoming mimebox file. - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goMIMEboxActionSAVEAS} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoMIMEboxActionSAVEAS} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} + @raise X2GoMIMEboxActionException: if the client_instance has not been passed to the SAVEAS MIME box action + """ if client_instance is None: - raise x2go_exceptions.X2goMIMEboxActionException('the SAVEAS MIME box action needs to know the X2goClient instance (client=)') - X2goMIMEboxAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) + raise x2go_exceptions.X2GoMIMEboxActionException('the SAVEAS MIME box action needs to know the X2GoClient instance (client=)') + X2GoMIMEboxAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) - def do_process(self, mimebox_file, mimebox_dir): + def _do_process(self, mimebox_file, mimebox_dir): """\ - Call an L{X2goClient} hook method (C{HOOK_open_mimebox_saveas_dialog}) that + Call an L{X2GoClient} hook method (C{HOOK_open_mimebox_saveas_dialog}) that can handle the MIME box's SAVEAS action. @param mimebox_file: file name as placed in to the MIME box directory @type mimebox_file: C{str} - @param mimebox_dir: location of the X2go session's MIME box directory + @param mimebox_dir: location of the X2Go session's MIME box directory @type mimebox_dir: C{str} - @param mimebox_file: PDF file name as placed in to the X2go spool directory + @param mimebox_file: PDF file name as placed in to the X2Go spool directory """ - self.logger('Session %s (%s) is calling X2goClient class hook method .HOOK_open_mimebox_saveas_dialog(%s)' % (self.session_name, self.profile_name, mimebox_file), loglevel=log.loglevel_NOTICE) + mimebox_file = os.path.normpath(mimebox_file) + mimebox_dir = os.path.normpath(mimebox_dir) + + self.logger('Session %s (%s) is calling X2GoClient class hook method .HOOK_open_mimebox_saveas_dialog(%s)' % (self.session_name, self.profile_name, mimebox_file), loglevel=log.loglevel_NOTICE) self.client_instance.HOOK_open_mimebox_saveas_dialog(os.path.join(mimebox_dir, mimebox_file), profile_name=self.profile_name, session_name=self.session_name) time.sleep(60) diff -Nru python-x2go-0.1.1.8/x2go/mimebox.py python-x2go-0.5.0.6/x2go/mimebox.py --- python-x2go-0.1.1.8/x2go/mimebox.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/mimebox.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,29 +1,28 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -L{X2goMIMEboxQueue} sets up a thread that listens for incoming files that +L{X2GoMIMEboxQueue} sets up a thread that listens for incoming files that shall be opened locally on the client. -For each file that gets dropped in the MIME box an individual -thread is started (L{X2goMIMEboxJob}) that handles the processing +For each file that gets dropped in the MIME box an individual +thread is started (L{X2GoMIMEboxJob}) that handles the processing of the incoming file. """ @@ -36,22 +35,19 @@ import threading import gevent -# Python X2go modules +# Python X2Go modules import defaults import utils import log import mimeboxactions -if defaults.X2GOCLIENT_OS != 'Windows': - from x2go_exceptions import WindowsError - -class X2goMIMEboxQueue(threading.Thread): +class X2GoMIMEboxQueue(threading.Thread): """\ - If the X2go MIME box is supported in a particaluar L{X2goSession} instance + If the X2Go MIME box is supported in a particaluar L{X2GoSession} instance this class provides a sub-thread for handling incoming files in the MIME box directory. The actual handling of a dropped file is handled by the classes - L{X2goMIMEboxActionOPEN}, L{X2goMIMEboxActionOPENWITH} and L{X2goMIMEboxActionSAVEAS}. + L{X2GoMIMEboxActionOPEN}, L{X2GoMIMEboxActionOPENWITH} and L{X2GoMIMEboxActionSAVEAS}. """ mimebox_action = None @@ -60,7 +56,7 @@ active_jobs = {} mimebox_history = [] - def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN', + def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN', mimebox_dir=None, mimebox_action=None, mimebox_extensions=[], client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ @@ -68,20 +64,20 @@ @type profile_name: C{str} @param mimebox_dir: local directory for incoming MIME box files @type mimebox_dir: C{str} - @param mimebox_action: name or instance of either of the possible X2go print action classes + @param mimebox_action: name or instance of either of the possible X2Go print action classes @type mimebox_action: C{str} or instance - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintQueue} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintQueue} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -89,6 +85,7 @@ self.profile_name = profile_name self.session_name = session_name self.mimebox_dir = mimebox_dir + if self.mimebox_dir: self.mimebox_dir = os.path.normpath(self.mimebox_dir) self.mimebox_extensions = mimebox_extensions self.client_instance = client_instance self.client_rootdir = client_instance.get_client_rootdir() @@ -97,7 +94,7 @@ self._accept_jobs = False if mimebox_action is None: - mimebox_action = mimebox_actions.X2goMIMEboxActionOPEN(client_instance=self.client_instance, logger=self.logger) + mimebox_action = mimeboxactions.X2GoMIMEboxActionOPEN(client_instance=self.client_instance, logger=self.logger) elif type(mimebox_action) in (types.StringType, types.UnicodeType): mimebox_action = self.set_mimebox_action(mimebox_action, client_instance=self.client_instance, logger=self.logger) else: @@ -118,7 +115,7 @@ def pause(self): """\ - Prevent acceptance of new incoming files. The processing of MIME box jobs that + Prevent acceptance of new incoming files. The processing of MIME box jobs that are currently still active will be completed, though. """ @@ -128,7 +125,7 @@ def resume(self): """\ - Resume operation of the X2go MIME box queue and continue accepting new incoming + Resume operation of the X2Go MIME box queue and continue accepting new incoming files. """ @@ -138,7 +135,7 @@ def stop_thread(self): """\ - Stops this L{X2goMIMEboxQueue} thread completely. + Stops this L{X2GoMIMEboxQueue} thread completely. """ self.pause() @@ -160,10 +157,15 @@ def set_mimebox_action(self, mimebox_action, **kwargs): """\ - Modify the MIME box action of this L{X2goMIMEboxQueue} thread during runtime. The + Modify the MIME box action of this L{X2GoMIMEboxQueue} thread during runtime. The change of the MIME box action will be valid for the next incoming file in the MIME box directory. + @param mimebox_action: the MIME box action to execute for incoming files + @type mimebox_action: C{str} or C{obj} + @param kwargs: extra options for the specified MIME box action + @type kwargs: C{dict} + """ if mimebox_action in defaults.X2GO_MIMEBOX_ACTIONS.keys(): mimebox_action = defaults.X2GO_MIMEBOX_ACTIONS[mimebox_action] @@ -173,7 +175,7 @@ def run(self): """\ - This method gets called once the L{X2goMIMEboxQueue} thread is started by the C{X2goMIMEboxQueue.start()} method. + This method gets called once the L{X2GoMIMEboxQueue} thread is started by the C{X2GoMIMEboxQueue.start()} method. """ self.logger('starting MIME box queue thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) @@ -186,14 +188,14 @@ if self._incoming_mimebox_jobs: for _job in self._incoming_mimebox_jobs: - self.logger('processing incoming X2go MIME box job: %s' % _job, loglevel=log.loglevel_NOTICE) - _new_mimeboxjob_thread = X2goMIMEboxJob(target=x2go_mimeboxjob_handler, - kwargs={ + self.logger('processing incoming X2Go MIME box job: %s' % _job, loglevel=log.loglevel_NOTICE) + _new_mimeboxjob_thread = X2GoMIMEboxJob(target=x2go_mimeboxjob_handler, + kwargs={ 'mimebox_file': _job, 'mimebox_extensions': self.mimebox_extensions, 'mimebox_action': self.mimebox_action, 'parent_thread': self, - 'logger': self.logger, + 'logger': self.logger, } ) self.active_jobs['%s' % _job] = _new_mimeboxjob_thread @@ -204,22 +206,22 @@ gevent.sleep(1) -def x2go_mimeboxjob_handler(mimebox_file=None, +def x2go_mimeboxjob_handler(mimebox_file=None, mimebox_extensions=[], mimebox_action=None, parent_thread=None, logger=None, ): """\ - This function is called as a handler function for each incoming X2go MIME box file - represented by the class L{X2goMIMEboxJob}. + This function is called as a handler function for each incoming X2Go MIME box file + represented by the class L{X2GoMIMEboxJob}. - @param mimebox_file: MIME box file name as placed in to the X2go MIME box spool directory + @param mimebox_file: MIME box file name as placed in to the X2Go MIME box spool directory @type mimebox_file: C{str} - @param mimebox_action: an instance of either of the possible C{X2goMIMEboxActionXXX} classes - @type mimebox_action: C{X2goMIMEboxActionXXX} nstance - @param parent_thread: the L{X2goMIMEboxQueue} thread that actually created this handler's L{X2goMIMEboxJob} instance - @type parent_thread: C{instance} - @param logger: the L{X2goMIMEboxQueue}'s logging instance - @type logger: C{instance} + @param mimebox_action: an instance of either of the possible C{X2GoMIMEboxActionXXX} classes + @type mimebox_action: C{X2GoMIMEboxActionXXX} nstance + @param parent_thread: the L{X2GoMIMEboxQueue} thread that actually created this handler's L{X2GoMIMEboxJob} instance + @type parent_thread: C{obj} + @param logger: the L{X2GoMIMEboxQueue}'s logging instance + @type logger: C{obj} """ mimebox_action.profile_name = parent_thread.profile_name @@ -237,9 +239,9 @@ elif not _blacklisted and not _dotfile: logger('file extension of MIME box file %s is prohibited by session profile configuration' % mimebox_file, loglevel=log.loglevel_NOTICE) elif _dotfile: - logger('placing files starting with a dot (.) into the X2go MIME box is prohibited, ignoring the file ,,%s\'\'' % mimebox_file, loglevel=log.loglevel_WARN) + logger('placing files starting with a dot (.) into the X2Go MIME box is prohibited, ignoring the file ,,%s\'\'' % mimebox_file, loglevel=log.loglevel_WARN) else: - logger('file extension of MIME box file %s has been found in Python X2go\' hardcoded MIME box extenstions blacklist' % mimebox_file, loglevel=log.loglevel_WARN) + logger('file extension of MIME box file %s has been found in Python X2Go\' hardcoded MIME box extenstions blacklist' % mimebox_file, loglevel=log.loglevel_WARN) logger('removing MIME box file %s' % mimebox_file, loglevel=log.loglevel_DEBUG) @@ -254,18 +256,18 @@ parent_thread.mimebox_history = parent_thread.mimebox_history[-100:] -class X2goMIMEboxJob(threading.Thread): +class X2GoMIMEboxJob(threading.Thread): """\ - For each X2go MIME box job we create a sub-thread that let's + For each X2Go MIME box job we create a sub-thread that let's the MIME box job be processed in the background. - As a handler for this class the function L{x2go_mimeboxjob_handler()} + As a handler for this class the function L{x2go_mimeboxjob_handler()} is used. """ def __init__(self, **kwargs): """\ - Construct the X2go MIME box job thread... + Construct the X2Go MIME box job thread... All parameters (**kwargs) are passed through to the constructor of C{threading.Thread()}. diff -Nru python-x2go-0.1.1.8/x2go/monkey_patch_paramiko.py python-x2go-0.5.0.6/x2go/monkey_patch_paramiko.py --- python-x2go-0.1.1.8/x2go/monkey_patch_paramiko.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/monkey_patch_paramiko.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2011 by Mike Gabriel -# -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Python X2go 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -"""\ -Monkey Patch for Python Paramiko - -""" - -import paramiko - -def _SSHClient_save_host_keys(self, filename): - """\ - FIXME!!! --- this method should become part of Paramiko - - This method has been taken from SSHClient class in Paramiko and - has been improved and adapted to latest SSH implementations. - - Save the host keys back to a file. - Only the host keys loaded with - L{load_host_keys} (plus any added directly) will be saved -- not any - host keys loaded with L{load_system_host_keys}. - - @param filename: the filename to save to - @type filename: str - - @raise IOError: if the file could not be written - - """ - # update local host keys from file (in case other SSH clients - # have written to the known_hosts file meanwhile. - if self.known_hosts is not None: - self.load_host_keys(self.known_hosts) - - f = open(filename, 'w') - #f.write('# SSH host keys collected by paramiko\n') - _host_keys = self.get_host_keys() - for hostname, keys in _host_keys.iteritems(): - - for keytype, key in keys.iteritems(): - f.write('%s %s %s\n' % (hostname, keytype, key.get_base64())) - - f.close() - - -def _HostKeys_load(self, filename): - """\ - Read a file of known SSH host keys, in the format used by openssh. - This type of file unfortunately doesn't exist on Windows, but on - posix, it will usually be stored in - C{os.path.expanduser("~/.ssh/known_hosts")}. - - If this method is called multiple times, the host keys are merged, - not cleared. So multiple calls to C{load} will just call L{add}, - replacing any existing entries and adding new ones. - - @param filename: name of the file to read host keys from - @type filename: str - - @raise IOError: if there was an error reading the file - - """ - f = open(filename, 'r') - for line in f: - line = line.strip() - if (len(line) == 0) or (line[0] == '#'): - continue - e = paramiko.hostkeys.HostKeyEntry.from_line(line) - if e is not None: - _hostnames = e.hostnames - for h in _hostnames: - if self.check(h, e.key): - e.hostnames.remove(h) - if len(e.hostnames): - self._entries.append(e) - f.close() - - -def _HostKeys_add(self, hostname, keytype, key, hash_hostname=True): - """\ - Add a host key entry to the table. Any existing entry for a - C{(hostname, keytype)} pair will be replaced. - - @param hostname: the hostname (or IP) to add - @type hostname: str - @param keytype: key type (C{"ssh-rsa"} or C{"ssh-dss"}) - @type keytype: str - @param key: the key to add - @type key: L{PKey} - - """ - for e in self._entries: - if (hostname in e.hostnames) and (e.key.get_name() == keytype): - e.key = key - return - if not hostname.startswith('|1|') and hash_hostname: - hostname = self.hash_host(hostname) - self._entries.append(paramiko.hostkeys.HostKeyEntry([hostname], key)) - - -def monkey_patch_paramiko(): - paramiko.SSHClient.save_host_keys = _SSHClient_save_host_keys - paramiko.hostkeys.HostKeys.load = _HostKeys_load - paramiko.hostkeys.HostKeys.add = _HostKeys_add diff -Nru python-x2go-0.1.1.8/x2go/_paramiko.py python-x2go-0.5.0.6/x2go/_paramiko.py --- python-x2go-0.1.1.8/x2go/_paramiko.py 1970-01-01 00:00:00.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/_paramiko.py 2017-12-12 06:52:58.000000000 +0000 @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2016 by Mike Gabriel +# +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2Go 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +Monkey Patch and feature map for Python Paramiko + +""" + +import paramiko +import re +try: + from paramiko.config import SSH_PORT +except ImportError: + SSH_PORT=22 +import platform +from utils import compare_versions + +PARAMIKO_VERSION = paramiko.__version__.split()[0] +PARAMIKO_FEATURE = { + 'forward-ssh-agent': compare_versions(PARAMIKO_VERSION, ">=", '1.8.0') and (platform.system() != "Windows"), + 'use-compression': compare_versions(PARAMIKO_VERSION, ">=", '1.7.7.1'), + 'hash-host-entries': compare_versions(PARAMIKO_VERSION, ">=", '99'), + 'host-entries-reloadable': compare_versions(PARAMIKO_VERSION, ">=", '1.11.0'), + 'preserve-known-hosts': compare_versions(PARAMIKO_VERSION, ">=", '1.11.0'), + 'ecdsa-hostkeys': compare_versions(PARAMIKO_VERSION, ">=", '1.11.6'), +} + +def _SSHClient_save_host_keys(self, filename): + """\ + Available since paramiko 1.11.0... + + This method has been taken from SSHClient class in Paramiko and + has been improved and adapted to latest SSH implementations. + + Save the host keys back to a file. + Only the host keys loaded with + L{load_host_keys} (plus any added directly) will be saved -- not any + host keys loaded with L{load_system_host_keys}. + + @param filename: the filename to save to + @type filename: str + + @raise IOError: if the file could not be written + + """ + # update local host keys from file (in case other SSH clients + # have written to the known_hosts file meanwhile. + if self.known_hosts is not None: + self.load_host_keys(self.known_hosts) + + f = open(filename, 'w') + #f.write('# SSH host keys collected by paramiko\n') + _host_keys = self.get_host_keys() + for hostname, keys in _host_keys.iteritems(): + + for keytype, key in keys.iteritems(): + f.write('%s %s %s\n' % (hostname, keytype, key.get_base64())) + + f.close() + + +def _HostKeys_load(self, filename): + """\ + Available since paramiko 1.11.0... + + Read a file of known SSH host keys, in the format used by openssh. + This type of file unfortunately doesn't exist on Windows, but on + posix, it will usually be stored in + C{os.path.expanduser("~/.ssh/known_hosts")}. + + If this method is called multiple times, the host keys are merged, + not cleared. So multiple calls to C{load} will just call L{add}, + replacing any existing entries and adding new ones. + + @param filename: name of the file to read host keys from + @type filename: str + + @raise IOError: if there was an error reading the file + + """ + f = open(filename, 'r') + for line in f: + line = line.strip() + if (len(line) == 0) or (line[0] == '#'): + continue + e = paramiko.hostkeys.HostKeyEntry.from_line(line) + if e is not None: + _hostnames = e.hostnames + for h in _hostnames: + if self.check(h, e.key): + e.hostnames.remove(h) + if len(e.hostnames): + self._entries.append(e) + f.close() + + +def _HostKeys_add(self, hostname, keytype, key, hash_hostname=True): + """\ + Add a host key entry to the table. Any existing entry for a + C{(hostname, keytype)} pair will be replaced. + + @param hostname: the hostname (or IP) to add + @type hostname: str + @param keytype: key type (C{"ssh-rsa"}, C{"ssh-dss"} or C{"ecdsa-sha2-nistp256"}) + @type keytype: str + @param key: the key to add + @type key: L{PKey} + + """ + # IPv4 and IPv6 addresses using the SSH default port need to be stripped off the port number + if re.match('^\[.*\]\:'+str(SSH_PORT)+'$', hostname): + # so stripping off the port and the square brackets here... + hostname = hostname.split(':')[-2].lstrip('[').rstrip(']') + + for e in self._entries: + if (hostname in e.hostnames) and (e.key.get_name() == keytype): + e.key = key + return + if not hostname.startswith('|1|') and hash_hostname: + hostname = self.hash_host(hostname) + self._entries.append(paramiko.hostkeys.HostKeyEntry([hostname], key)) + + +def monkey_patch_paramiko(): + if not PARAMIKO_FEATURE['preserve-known-hosts']: + paramiko.SSHClient.save_host_keys = _SSHClient_save_host_keys + if not PARAMIKO_FEATURE['host-entries-reloadable']: + paramiko.hostkeys.HostKeys.load = _HostKeys_load + if not PARAMIKO_FEATURE['hash-host-entries']: + paramiko.hostkeys.HostKeys.add = _HostKeys_add diff -Nru python-x2go-0.1.1.8/x2go/printactions.py python-x2go-0.5.0.6/x2go/printactions.py --- python-x2go-0.1.1.8/x2go/printactions.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/printactions.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,43 +1,38 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ Print jobs can either be sent to any of the local print queues (CUPS, Win32API), -be opened in an external PDF viewer, be saved to a local folder or be handed +be opened in an external PDF viewer, be saved to a local folder or be handed over to a custom (print) command. This is defined by four print action classes -(L{X2goPrintActionDIALOG}, L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and -L{X2goPrintActionPRINTCMD}). +(L{X2GoPrintActionDIALOG}, L{X2GoPrintActionPDFVIEW}, L{X2GoPrintActionPDFSAVE}, L{X2GoPrintActionPRINT} and +L{X2GoPrintActionPRINTCMD}). """ __NAME__ = 'x2goprintactions-pylib' # modules import os -import sys import shutil import copy -import types -import threading import time -import re -import string +import gevent from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS in ("Windows"): @@ -46,8 +41,10 @@ import win32print else: import gevent_subprocess as subprocess + import x2go_exceptions + WindowsError = x2go_exceptions.WindowsError -# Python X2go modules +# Python X2Go modules import log import defaults # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) @@ -57,73 +54,91 @@ _PRINT_ENV = os.environ.copy() -class X2goPrintAction(object): +class X2GoPrintAction(object): __name__ = 'NAME' __description__ = 'DESCRIPTION' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - This is a meta class and has no functionality as such. It is used as parent - class by »real« X2go print actions. + This is a meta class and has no functionality as such. It is used as parent + class by »real« X2Go print actions. - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintAction} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ - # these get set from within the X2goPrintQueue class + # these get set from within the X2GoPrintQueue class self.profile_name = 'UNKNOWN' self.session_name = 'UNKNOWN' self.client_instance = client_instance @property - def name(): + def name(self): """\ - Return the X2go print action's name. + Return the X2Go print action's name. """ return self.__name__ @property - def description(): + def description(self): """\ - Return the X2go print action's description text. + Return the X2Go print action's description text. """ return self.__description__ + def _do_print(self, pdf_file, job_title, spool_dir, ): + """ + Perform the defined print action (doing nothing in L{X2GoPrintAction} parent class). + + @param pdf_file: PDF file name as placed in to the X2Go spool directory + @type pdf_file: C{str} + @param job_title: human readable print job title + @type job_title: C{str} + @param spool_dir: location of the X2Go client's spool directory + @type spool_dir: C{str} + + """ + pass + def do_print(self, pdf_file, job_title, spool_dir, ): """\ - Perform the defined print action (doing nothing in L{X2goPrintAction} parent class). + Wrap around the actual print action (C{self._do_print}) with + gevent.spawn(). - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param spool_dir: location of the X2go client's spool directory + @param spool_dir: location of the X2Go client's spool directory @type spool_dir: C{str} """ - pass + pdf_file = os.path.normpath(pdf_file) + spool_dir = os.path.normpath(spool_dir) + + self._do_print(pdf_file, job_title, spool_dir) def _humanreadable_filename(self, pdf_file, job_title, target_path): """\ - Extract a human readable filename for the X2go print job file. + Extract a human readable filename for the X2Go print job file. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} @@ -133,17 +148,17 @@ @rtype: C{str} """ - _hr_path = os.path.expanduser(os.path.join(target_path, '%s.pdf' % utils.slugify(job_title))) + _hr_path = os.path.normpath(os.path.expanduser(os.path.join(os.path.normpath(target_path), '%s.pdf' % utils.slugify(job_title)))) i = 0 while os.path.exists(_hr_path): i += 1 - _hr_path = os.path.expanduser(os.path.join(target_path, '%s(%s).pdf' % (utils.slugify(job_title), i))) + _hr_path = os.path.normpath(os.path.expanduser(os.path.join(os.path.normpath(target_path), '%s(%s).pdf' % (utils.slugify(job_title), i)))) return _hr_path -class X2goPrintActionPDFVIEW(X2goPrintAction): +class X2GoPrintActionPDFVIEW(X2GoPrintAction): """\ Print action that views incoming print job in an external PDF viewer application. @@ -155,14 +170,14 @@ def __init__(self, client_instance=None, pdfview_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} @param pdfview_cmd: command that starts the external PDF viewer application @type pdfview_cmd: C{str} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintActionPDFVIEW} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintActionPDFVIEW} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} @@ -170,24 +185,29 @@ if pdfview_cmd is None: pdfview_cmd = defaults.DEFAULT_PDFVIEW_CMD self.pdfview_cmd = pdfview_cmd - X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) + X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) - def do_print(self, pdf_file, job_title, spool_dir, ): + def _do_print(self, pdf_file, job_title, spool_dir, ): """\ - Open an incoming X2go print job (PDF file) in an external PDF viewer application. + Open an incoming X2Go print job (PDF file) in an external PDF viewer application. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param spool_dir: location of the X2go client's spool directory + @param spool_dir: location of the X2Go client's spool directory @type spool_dir: C{str} + @raise OSError: pass through all C{OSError}s except no. 2 + """ + pdf_file = os.path.normpath(pdf_file) + spool_dir = os.path.normpath(spool_dir) + if _X2GOCLIENT_OS == "Windows": - self.logger('viewing incoming job in PDF viewer with Python\'s os.startfile( command): %s' % pdf_file, loglevel=log.loglevel_DEBUG) + self.logger('viewing incoming job in PDF viewer with Python\'s os.startfile(command): %s' % pdf_file, loglevel=log.loglevel_DEBUG) try: - os.startfile(pdf_file) + gevent.spawn(os.startfile, pdf_file) except WindowsError, win_err: if self.client_instance: self.client_instance.HOOK_printaction_error(pdf_file, @@ -204,11 +224,11 @@ cmd_line = [ self.pdfview_cmd, _hr_filename, ] self.logger('viewing incoming PDF with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) try: - p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) + subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) except OSError, e: if e.errno == 2: cmd_line = [ defaults.DEFAULT_PDFVIEW_CMD, _hr_filename ] - p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) + subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) else: raise(e) self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG) @@ -216,7 +236,7 @@ os.remove(_hr_filename) -class X2goPrintActionPDFSAVE(X2goPrintAction): +class X2GoPrintActionPDFSAVE(X2GoPrintAction): """\ Print action that saves incoming print jobs to a local folder. @@ -228,14 +248,14 @@ def __init__(self, client_instance=None, save_to_folder=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} @param save_to_folder: saving location for incoming print jobs (PDF files) @type save_to_folder: C{str} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintActionPDFSAVE} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintActionPDFSAVE} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} @@ -248,29 +268,32 @@ save_to_folder = os.path.expanduser(save_to_folder) self.save_to_folder = save_to_folder - X2goPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) + X2GoPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) self.logger('Save location for incoming PDFs is: %s' % self.save_to_folder, loglevel=log.loglevel_DEBUG) if not os.path.exists(self.save_to_folder): os.makedirs(self.save_to_folder, mode=0755) - def do_print(self, pdf_file, job_title, spool_dir): + def _do_print(self, pdf_file, job_title, spool_dir): """\ - Save an incoming X2go print job (PDF file) to a local folder. + Save an incoming X2Go print job (PDF file) to a local folder. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param spool_dir: location of the X2go client's spool directory + @param spool_dir: location of the X2Go client's spool directory @type spool_dir: C{str} """ + pdf_file = os.path.normpath(pdf_file) + spool_dir = os.path.normpath(spool_dir) + dest_file = self._humanreadable_filename(pdf_file, job_title, target_path=self.save_to_folder) shutil.copy2(pdf_file, dest_file) -class X2goPrintActionPRINT(X2goPrintAction): +class X2GoPrintActionPRINT(X2GoPrintAction): """\ Print action that actually prints an incoming print job file. @@ -280,33 +303,36 @@ def __init__(self, client_instance=None, printer=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} @param printer: name of the preferred printer, if C{None} the system's/user's default printer will be used @type printer: C{str} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintActionPRINT} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintActionPRINT} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ self.printer = printer - X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) + X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) - def do_print(self, pdf_file, job_title, spool_dir, ): + def _do_print(self, pdf_file, job_title, spool_dir, ): """\ - Actually really print an incoming X2go print job (PDF file) to a local printer device. + Really print an incoming X2Go print job (PDF file) to a local printer device. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param spool_dir: location of the X2go client's spool directory + @param spool_dir: location of the X2Go client's spool directory @type spool_dir: C{str} """ + pdf_file = os.path.normpath(pdf_file) + spool_dir = os.path.normpath(spool_dir) + _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir) if _X2GOCLIENT_OS == 'Windows': _default_printer = win32print.GetDefaultPrinter() @@ -328,12 +354,12 @@ _gsprint_bin = os.path.normpath(os.path.join(_program_files, 'ghostgum', 'gsview', 'gsprint.exe',)) self.logger('Using hard-coded gsprint.exe path: %s' % _gsprint_bin, loglevel=log.loglevel_DEBUG) self.logger('Trying Ghostgum tool ,,gsprint.exe'' for printing first (full path: %s)' % _gsprint_bin, loglevel=log.loglevel_DEBUG) - p = subprocess.Popen([_gsprint_bin, pdf_file, ], - stdin=_stdin, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - shell=_shell, - ) + subprocess.Popen([_gsprint_bin, pdf_file, ], + stdin=_stdin, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=_shell, + ) # give gsprint.exe a little time to find our printer time.sleep(10) @@ -372,7 +398,7 @@ cmd_line = [ 'lpr', '-h', '-r', - '-J%s' % job_title, + '-J%s' % job_title, '%s' % _hr_filename, ] else: @@ -380,11 +406,11 @@ '-h', '-r', '-P%s' % self.printer, - '-J%s' % job_title, + '-J%s' % job_title, '%s' % _hr_filename, ] self.logger('executing local print command: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) - p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) + subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) # this is nasty!!!! self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG) @@ -393,12 +419,12 @@ except OSError: pass -class X2goPrintActionPRINTCMD(X2goPrintAction): +class X2GoPrintActionPRINTCMD(X2GoPrintAction): """\ Print action that calls an external command for further processing of incoming print jobs. The print job's PDF filename will be prepended as last argument to the print command - used in L{X2goPrintActionPRINTCMD} instances. + used in L{X2GoPrintActionPRINTCMD} instances. """ __name__ = 'PRINTCMD' @@ -406,14 +432,14 @@ def __init__(self, client_instance=None, print_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} @param print_cmd: external command to be called on incoming print jobs @type print_cmd: C{str} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintActionPRINTCMD} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintActionPRINTCMD} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} @@ -421,28 +447,31 @@ if print_cmd is None: print_cmd = defaults.DEFAULT_PRINTCMD_CMD self.print_cmd = print_cmd - X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) + X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) - def do_print(self, pdf_file, job_title, spool_dir): + def _do_print(self, pdf_file, job_title, spool_dir): """\ - Execute an external command that has been defined on construction - of this L{X2goPrintActionPRINTCMD} instance. + Execute an external command that has been defined on construction + of this L{X2GoPrintActionPRINTCMD} instance. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param spool_dir: location of the X2go client's spool directory + @param spool_dir: location of the X2Go client's spool directory @type spool_dir: C{str} """ + pdf_file = os.path.normpath(pdf_file) + spool_dir = os.path.normpath(spool_dir) + _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir) shutil.copy2(pdf_file, _hr_filename) self.logger('executing external command ,,%s\'\' on PDF file %s' % (self.print_cmd, _hr_filename), loglevel=log.loglevel_NOTICE) cmd_line = self.print_cmd.split() cmd_line.append(_hr_filename) self.logger('executing external command: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) - p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) + subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) # this is nasty!!!! self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG) @@ -451,11 +480,11 @@ except OSError: pass -class X2goPrintActionDIALOG(X2goPrintAction): +class X2GoPrintActionDIALOG(X2GoPrintAction): """\ Print action that mediates opening a print dialog window. This class is rather empty, the actual print dialog box must be implemented in our GUI application (with the application's - L{X2goClient} instance. + L{X2GoClient} instance. """ __name__ = 'DIALOG' @@ -463,37 +492,41 @@ def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: an L{X2goClient} instance, within your customized L{X2goClient} make sure + @param client_instance: an L{X2GoClient} instance, within your customized L{X2GoClient} make sure you have a C{HOOK_open_print_dialog(filename=)} method defined that will actually open the print dialog. - @type client_instance: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintActionDIALOG} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @type client_instance: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintActionDIALOG} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} + @raise X2GoPrintActionException: if the client_instance has not been passed to the DIALOG print action + """ if client_instance is None: - raise x2go_exceptions.X2goPrintActionException('the DIALOG print action needs to know the X2goClient instance (client=)') - X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) + raise x2go_exceptions.X2GoPrintActionException('the DIALOG print action needs to know the X2GoClient instance (client=)') + X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) - def do_print(self, pdf_file, job_title, spool_dir): + def _do_print(self, pdf_file, job_title, spool_dir): """\ - Execute an external command that has been defined on construction - of this L{X2goPrintActionPRINTCMD} instance. + Execute an external command that has been defined on construction + of this L{X2GoPrintActionPRINTCMD} instance. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param spool_dir: location of the X2go client's spool directory + @param spool_dir: location of the X2Go client's spool directory @type spool_dir: C{str} """ - self.logger('Session %s (%s) is calling X2goClient class hook method .HOOK_open_print_dialog' % (self.session_name, self.profile_name), loglevel=log.loglevel_NOTICE) - _new_print_action = self.client_instance.HOOK_open_print_dialog(profile_name=self.profile_name, session_name=self.session_name) - if type(_new_print_action) != type(self): - _new_print_action.do_print(pdf_file, job_title, spool_dir) + pdf_file = os.path.normpath(pdf_file) + spool_dir = os.path.normpath(spool_dir) + self.logger('Session %s (%s) is calling X2GoClient class hook method .HOOK_open_print_dialog' % (self.session_name, self.profile_name), loglevel=log.loglevel_NOTICE) + _new_print_action = self.client_instance.HOOK_open_print_dialog(profile_name=self.profile_name, session_name=self.session_name) + if _new_print_action and type(_new_print_action) != type(self): + _new_print_action._do_print(pdf_file, job_title, spool_dir) diff -Nru python-x2go-0.1.1.8/x2go/printqueue.py python-x2go-0.5.0.6/x2go/printqueue.py --- python-x2go-0.1.1.8/x2go/printqueue.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/printqueue.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,27 +1,27 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -L{X2goPrintQueue} sets up a thread that listens for incoming print jobs. +L{X2GoPrintQueue} sets up a thread that listens for incoming print jobs. -For each incoming print job in an X2go session's spool directory an -individual thread is started (L{X2goPrintJob}) that handles the processing +For each incoming print job in an X2Go session's spool directory an +individual thread is started (L{X2GoPrintJob}) that handles the processing of the incoming print job. """ @@ -33,25 +33,18 @@ import threading import gevent -# Python X2go modules +# Python X2Go modules import defaults import utils import log -import printactions - -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -from backends.printing import X2goClientPrinting as _X2goClientPrinting - -if defaults.X2GOCLIENT_OS != 'Windows': - from x2go_exceptions import WindowsError from defaults import X2GO_PRINTING_FILENAME as _X2GO_PRINTING_FILENAME +from defaults import BACKENDS as _BACKENDS - -class X2goPrintQueue(threading.Thread): +class X2GoPrintQueue(threading.Thread): """\ - If X2go printing is supported in a particular L{X2goSession} instance - this class provides a sub-thread for handling incoming X2go print jobs. + If X2Go printing is supported in a particular L{X2GoSession} instance + this class provides a sub-thread for handling incoming X2Go print jobs. """ print_action = None @@ -60,14 +53,14 @@ active_jobs = {} job_history = [] - def __init__(self, + def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN', spool_dir=None, print_action=None, print_action_args={}, client_instance=None, - printing_backend=_X2goClientPrinting, + printing_backend=_BACKENDS['X2GoClientPrinting']['default'], logger=None, loglevel=log.loglevel_DEFAULT): """\ @@ -75,25 +68,25 @@ @type profile_name: C{str} @param spool_dir: local spool directory for incoming print job files @type spool_dir: C{str} - @param print_action: name or instance of either of the possible X2go print action classes + @param print_action: name or instance of either of the possible X2Go print action classes @type print_action: C{str} or instance @param print_action_args: depending of the chosen C{print_action} this dictionary may contain different - values; the C{print_action_args} will be passed on to the X2go print action instance constructor, so - refer to either of these: L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPRINT} et al. - @param client_instance: the underlying L{X2goClient} instance - @type client_instance: C{instance} + values; the C{print_action_args} will be passed on to the X2Go print action instance constructor, so + refer to either of these: L{X2GoPrintActionPDFVIEW}, L{X2GoPrintActionPRINT} et al. + @param client_instance: the underlying L{X2GoClient} instance + @type client_instance: C{obj} @param printing_backend: the client printing configuration backend class - @type printing_backend: C{instance} - @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintQueue} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @type printing_backend: C{obj} + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoPrintQueue} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -101,9 +94,10 @@ self.profile_name = profile_name self.session_name = session_name self.spool_dir = spool_dir + if self.spool_dir: self.spool_dir = os.path.normpath(self.spool_dir) self.client_instance = client_instance self.client_rootdir = client_instance.get_client_rootdir() - self.printing_backend = printing_backend + self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") if print_action is not None: self.set_print_action(print_action, client_instance=self.client_instance, logger=logger, **print_action_args) threading.Thread.__init__(self) @@ -119,7 +113,7 @@ def pause(self): """\ - Prevent acceptance of new incoming print jobs. The processing of print jobs that + Prevent acceptance of new incoming print jobs. The processing of print jobs that are currently still active will be completed, though. """ @@ -129,7 +123,7 @@ def resume(self): """\ - Resume operation of the X2go print spooler and continue accepting new incoming + Resume operation of the X2Go print spooler and continue accepting new incoming print jobs. """ @@ -139,7 +133,7 @@ def stop_thread(self): """\ - Stops this L{X2goPrintQueue} thread completely. + Stops this L{X2GoPrintQueue} thread completely. """ self.pause() @@ -154,14 +148,14 @@ job_files = [ jf for jf in l if jf.endswith('.ready') ] jobs = [] for _job_file in job_files: - j = open(os.path.join(self.spool_dir, _job_file), 'r') - content = j.read() + _job_file_handle = open(os.path.join(self.spool_dir, _job_file), 'r') + content = _job_file_handle.read() try: (pdf_filename, job_title) = content.split('\n')[0:2] except ValueError: pdf_filename = content - job_title = 'X2go Print Job' - j.close() + job_title = 'X2Go Print Job' + _job_file_handle.close() jobs.append((_job_file, pdf_filename, job_title)) return [ j for j in jobs if j[1] not in self.active_jobs.keys() ] else: @@ -169,15 +163,18 @@ def set_print_action(self, print_action, **kwargs): """\ - Modify the print action of this L{X2goPrintQueue} thread during runtime. The + Modify the print action of this L{X2GoPrintQueue} thread during runtime. The change of print action will be valid for the next incoming print job. As kwargs you can pass arguments for the print action class to be set. Refer - to the class descriptions of L{X2goPrintActionDIALOG}, L{X2goPrintActionPDFVIEW}, - L{X2goPrintActionPRINT}, etc. + to the class descriptions of L{X2GoPrintActionDIALOG}, L{X2GoPrintActionPDFVIEW}, + L{X2GoPrintActionPRINT}, etc. @param print_action: new print action to be valid for incoming print jobs @type print_action: C{str} or C{class} + @param kwargs: extra options for the specified print action + @type kwargs: C{dict} + """ if print_action in defaults.X2GO_PRINT_ACTIONS.keys(): print_action = defaults.X2GO_PRINT_ACTIONS[print_action] @@ -187,7 +184,7 @@ def run(self): """\ - Start this L{X2goPrintQueue} thread... + Start this L{X2GoPrintQueue} thread... """ self.logger('starting print queue thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) @@ -200,15 +197,15 @@ if self._incoming_print_jobs: for _job in self._incoming_print_jobs: - self.logger('processing incoming X2go print job: %s' % _job[1], loglevel=log.loglevel_NOTICE) - _new_printjob_thread = X2goPrintJob(target=x2go_printjob_handler, - kwargs={ + self.logger('processing incoming X2Go print job: %s' % _job[1], loglevel=log.loglevel_NOTICE) + _new_printjob_thread = X2GoPrintJob(target=x2go_printjob_handler, + kwargs={ 'job_file': _job[0], 'pdf_file': _job[1], 'job_title': _job[2], 'print_action': self.print_action, - 'parent_thread': self, - 'logger': self.logger, + 'parent_thread': self, + 'logger': self.logger, } ) self.active_jobs['%s' % _job[1]] = _new_printjob_thread @@ -221,33 +218,33 @@ def x2go_printjob_handler(job_file=None, pdf_file=None, job_title=None, print_action=None, parent_thread=None, logger=None, ): """\ - This function is called as a handler function for each incoming X2go print job - represented by the class L{X2goPrintJob}. + This function is called as a handler function for each incoming X2Go print job + represented by the class L{X2GoPrintJob}. The handler function will (re-)read the »printing« configuration file (if no explicit C{print_action} is passed to this function...). It then will execute the C{.do_print()} command. - @param pdf_file: PDF file name as placed in to the X2go spool directory + @param pdf_file: PDF file name as placed in to the X2Go spool directory @type pdf_file: C{str} @param job_title: human readable print job title @type job_title: C{str} - @param print_action: an instance of either of the possible C{X2goPrintActionXXX} classes - @type print_action: C{X2goPrintActionXXX} nstance - @param parent_thread: the L{X2goPrintQueue} thread that actually created this handler's L{X2goPrintJob} instance - @type parent_thread: C{instance} - @param logger: the L{X2goPrintQueue}'s logging instance - @type logger: C{instance} + @param print_action: an instance of either of the possible C{X2GoPrintActionXXX} classes + @type print_action: C{X2GoPrintActionXXX} nstance + @param parent_thread: the L{X2GoPrintQueue} thread that actually created this handler's L{X2GoPrintJob} instance + @type parent_thread: C{obj} + @param logger: the L{X2GoPrintQueue}'s logging instance + @type logger: C{obj} """ if print_action is None: if parent_thread.client_instance is not None and parent_thread.client_instance.has_custom_client_rootdir: _printing = parent_thread.printing_backend(config_files=[os.path.join(parent_thread.client_instance.get_client_rootdir(), _X2GO_PRINTING_FILENAME)], - client_instance=parent_thread.client_instance, + client_instance=parent_thread.client_instance, logger=logger ) else: - _printing = parent_thread.printing_backend(client_instance=parent_thread.client_instance, + _printing = parent_thread.printing_backend(client_instance=parent_thread.client_instance, logger=logger ) @@ -271,24 +268,24 @@ del parent_thread.active_jobs['%s' % pdf_file] parent_thread.job_history.append(pdf_file) - # in case we print a lot we do not want to risk an endlessly growing + # in case we print a lot we do not want to risk an endlessly growing # print job history if len(parent_thread.job_history) > 100: parent_thread.job_history = parent_thread.job_history[-100:] -class X2goPrintJob(threading.Thread): +class X2GoPrintJob(threading.Thread): """\ - For each X2go print job we create a sub-thread that let's + For each X2Go print job we create a sub-thread that let's the print job be processed in the background. - As a handler for this class the function L{x2go_printjob_handler()} + As a handler for this class the function L{x2go_printjob_handler()} is used. """ def __init__(self, **kwargs): """\ - Construct the X2go print job thread... + Construct the X2Go print job thread... All parameters (**kwargs) are passed through to the constructor of C{threading.Thread()}. diff -Nru python-x2go-0.1.1.8/x2go/pulseaudio.py python-x2go-0.5.0.6/x2go/pulseaudio.py --- python-x2go-0.1.1.8/x2go/pulseaudio.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/pulseaudio.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,7 +21,7 @@ # none so far """\ -X2goPulseAudio class - a Pulseaudio daemon guardian thread. +X2GoPulseAudio class - a Pulseaudio daemon guardian thread. """ @@ -29,22 +29,28 @@ from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS == 'Windows': - import wmi import win32process import win32con import win32event # modules import os -import subprocess +import sys import threading import gevent import copy +import socket -# Python X2go modules +from defaults import LOCAL_HOME as _LOCAL_HOME + +# Python X2Go modules import log -class X2goPulseAudio(threading.Thread): +import exceptions +class OSNotSupportedException(exceptions.StandardError): pass +""" Exception denoting that this operating system is not supported. """ + +class X2GoPulseAudio(threading.Thread): """ This class controls the Pulse Audio daemon. """ @@ -55,22 +61,22 @@ @param path: full path to pulseaudio.exe @type path: C{str} - @param client_instance: the calling L{X2goClient} instance - @type client_instance: L{X2goClient} instance - @param logger: you can pass an L{X2goLogger} object to the L{X2goClientXConfig} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the calling L{X2GoClient} instance + @type client_instance: L{X2GoClient} instance + @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} + @raise OSNotSupportedException: on non-Windows platforms Python X2Go presumes that pulseaudio is already launched + """ if _X2GOCLIENT_OS not in ("Windows"): - import exceptions - class OSNotSupportedException(exceptions.StandardError): pass raise OSNotSupportedException('classes of x2go.pulseaudio module are for Windows only') if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -85,19 +91,40 @@ def run(self): """\ - This method is called once the C{X2goPulseAudio.start()} method has been called. To tear - down the Pulseaudio daemon call the L{X2goPulseAudio.stop_thread()} method. + This method is called once the C{X2GoPulseAudio.start()} method has been called. To tear + down the Pulseaudio daemon call the L{X2GoPulseAudio.stop_thread()} method. """ self._keepalive = True cmd = 'pulseaudio.exe' cmd_options = [ '-n', - '-L module-native-protocol-tcp port=4713', - '-L module-esound-protocol-tcp port=16001', + '--exit-idle-time=-1', + '-L "module-native-protocol-tcp port=4713 auth-cookie=\\\\.pulse-cookie"', + '-L "module-esound-protocol-tcp port=16001"', '-L module-waveout', ] + + # Fix for python-x2go bug #537. + # Works Around PulseAudio bug #80772. + # Tested with PulseAudio 5.0. + # This argument will not cause PulseAudio 1.1 to fail to launch. + # However, PulseAudio 1.1 ignores it for some reason. + # So yes, the fact that 1.1 ignores it would be a bug in python-x2go if + # we ever ship 1.1 again. + # + # wv.major == 5 matches 2000, XP, and Server 2003 (R2). + # (Not that we support 2000.) + wv = sys.getwindowsversion() + if (wv.major==5): + self.logger('Windows XP or Server 2003 (R2) detected.', loglevel=log.loglevel_DEBUG) + self.logger('Setting PulseAudio to "Normal" CPU priority.', loglevel=log.loglevel_DEBUG) + cmd_options.insert(0,"--high-priority=no") + cmd_options = " %s" % " ".join(cmd_options) + + if not os.path.isdir(os.path.join(_LOCAL_HOME, '.pulse', '%s-runtime' % socket.gethostname())): + os.makedirs(os.path.join(_LOCAL_HOME, '.pulse', '%s-runtime' % socket.gethostname())) self.logger('starting PulseAudio server with command line: %s%s' % (cmd, cmd_options), loglevel=log.loglevel_DEBUG) si = win32process.STARTUPINFO() diff -Nru python-x2go-0.1.1.8/x2go/registry.py python-x2go-0.5.0.6/x2go/registry.py --- python-x2go-0.1.1.8/x2go/registry.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/registry.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goSessionRegistry class - the X2goClient's session registry backend +X2GoSessionRegistry class - the X2GoClient's session registry backend """ __NAME__ = 'x2gosessregistry-pylib' @@ -26,50 +26,45 @@ import os import copy import types -import uuid import time import threading +import re -# Python X2go modules +# Python X2Go modules import log import utils import session -from x2go_exceptions import * - -# import the default terminal session backend -from x2go.backends.control import X2goControlSession as _X2goControlSession -from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession -from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo -from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList -from x2go.backends.proxy import X2goProxy as _X2goProxy -from x2go.backends.settings import X2goClientSettings as _X2goClientSettings -from x2go.backends.printing import X2goClientPrinting as _X2goClientPrinting +import x2go_exceptions from defaults import LOCAL_HOME as _LOCAL_HOME from defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR +from defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS from defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR -class X2goSessionRegistry(object): +from defaults import BACKENDS as _BACKENDS + + +class X2GoSessionRegistry(object): """\ - This class is utilized by L{X2goClient} instances to maintain a good overview on - session status of all associated L{X2goSession} instances. + This class is utilized by L{X2GoClient} instances to maintain a good overview on + session status of all associated L{X2GoSession} instances. """ def __init__(self, client_instance, logger=None, loglevel=log.loglevel_DEFAULT): """\ - @param client_instance: the L{X2goClient} instance that instantiated this L{X2goSessionRegistry} instance. - @type client_instance: L{X2goClient} instance - @param logger: you can pass an L{X2goLogger} object to the L{X2goClientXConfig} constructor - @type logger: C{instance} - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param client_instance: the L{X2GoClient} instance that instantiated this L{X2GoSessionRegistry} instance. + @type client_instance: L{X2GoClient} instance + @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor + @type logger: C{obj} + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -78,9 +73,11 @@ self.registry = {} self.control_sessions = {} + self.master_sessions = {} self._last_available_session_registration = None self._skip_auto_registration = False + self._profile_locks = {} def keys(self): """\ @@ -93,32 +90,36 @@ return self.registry.keys() def __repr__(self): - result = 'X2goSessionRegistry(' + result = 'X2GoSessionRegistry(' for p in dir(self): if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue - result += p + '=' + str(self.__dict__[p]) + result += p + '=' + str(self.__dict__[p]) + ',' + result = result.strip(',') return result + ')' def __call__(self, session_uuid): """\ - Returns the L{X2goSession} instance for a given session UUID hash. + Returns the L{X2GoSession} instance for a given session UUID hash. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} - @return: the corresponding L{X2goSession} instance - @rtype: L{X2goSession} instance + + @return: the corresponding L{X2GoSession} instance + @rtype: L{X2GoSession} instance + + @raise X2GoSessionRegistryException: if the given session UUID could not be found """ try: return self.registry[session_uuid] except KeyError: - raise X2goSessionRegistryException('No session found for UUID %s' % session_uuid) + raise x2go_exceptions.X2GoSessionRegistryException('No session found for UUID %s' % session_uuid) def disable_session_auto_registration(self): """\ This method is used to temporarily skip auto-registration of newly appearing - X2go session on the server side. This is necessary during session startups to - assure that the session registry does not get filled with session UUID + X2Go session on the server side. This is necessary during session startups to + assure that the session registry does not get filled with session UUID duplicates. """ @@ -127,7 +128,7 @@ def enable_session_auto_registration(self): """\ This method is used to temporarily (re-)enable auto-registration of newly appearing - X2go session on the server side. + X2Go session on the server side. """ self._skip_auto_registration = False @@ -136,7 +137,7 @@ """\ Forget the complete record for session UUID C{session_uuid}. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} """ @@ -150,8 +151,9 @@ """\ Retrieve the profile ID of a given session UUID hash. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @return: profile ID @rtype: C{str} @@ -162,8 +164,9 @@ """\ Retrieve the profile name of a given session UUID hash. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @return: profile name @rtype: C{str} @@ -174,8 +177,9 @@ """\ Compose a session summary (as Python dictionary). - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} + @return: session summary dictionary @rtype: C{dict} @@ -219,28 +223,34 @@ def update_status(self, session_uuid=None, profile_name=None, profile_id=None, session_list=None, force_update=False, newly_connected=False): """\ - Update the session status for L{X2goSession} that is represented by a given session UUID hash, + Update the session status for L{X2GoSession} that is represented by a given session UUID hash, profile name or profile ID. - @param session_uuid: the X2go session's UUID registry hash + @param session_uuid: the X2Go session's UUID registry hash @type session_uuid: C{str} @param profile_name: alternatively, a profile name can be specified (the stati of all registered sessions for this session profile will be updated) @type profile_name: C{str} - @param profile_id: alternatively, a profile ID can be given (the stati of all registered sessions for this session + @param profile_id: alternatively, a profile ID can be given (the stati of all registered sessions for this session profile will be updated) @type profile_id: C{str} - @param session_list: an optional C{X2goServerSessionList*} instance (as returned by the L{X2goClient.list_sessions()} command can + @param session_list: an optional C{X2GoServerSessionList*} instance (as returned by the L{X2GoClient.list_sessions()} command can be passed to this method. - @type session_list: C{X2goServerSessionList*} instance + @type session_list: C{X2GoServerSessionList*} instance @param force_update: make sure the session status gets really updated @type force_update: C{bool} + @return: C{True} if this method has been successful + @rtype: C{bool} + + @raise X2GoSessionRegistryException: if the combination of C{session_uuid}, C{profile_name} and C{profile_id} does not match the requirement: + only one of them + """ if session_uuid and profile_name or session_uuid and profile_id or profile_name and profile_id: - raise X2goSessionRegistryException('only one of the possible method parameters is allowed (session_uuid, profile_name or profile_id)') + raise x2go_exceptions.X2GoSessionRegistryException('only one of the possible method parameters is allowed (session_uuid, profile_name or profile_id)') elif session_uuid is None and profile_name is None and profile_id is None: - raise X2goSessionRegistryException('at least one of the method parameters session_uuid, profile_name or profile_id must be given') + raise x2go_exceptions.X2GoSessionRegistryException('at least one of the method parameters session_uuid, profile_name or profile_id must be given') if session_uuid: session_uuids = [ session_uuid ] @@ -251,13 +261,21 @@ for _session_uuid in session_uuids: + # only operate on instantiated X2GoSession objects + if type(self(_session_uuid)) != session.X2GoSession: + continue + + if self(_session_uuid).is_locked(): + continue + if not self(_session_uuid).update_status(session_list=session_list, force_update=force_update): # skip this run, as nothing has changed since the last time... - return False + continue + _last_status = copy.deepcopy(self(_session_uuid)._last_status) _current_status = copy.deepcopy(self(_session_uuid)._current_status) - # at this point we hook into the X2goClient instance and call notification methods + # at this point we hook into the X2GoClient instance and call notification methods # that can be used to inform an application that something has happened _profile_name = self(_session_uuid).get_profile_name() @@ -279,52 +297,106 @@ else: # explicitly ask for the terminal_session object directly here, so we also get 'PENDING' terminal sessions here... if self(_session_uuid).terminal_session: + + # declare as master session if appropriate + if _profile_name not in self.master_sessions.keys(): + self.master_sessions[_profile_name] = self(_session_uuid) + self(_session_uuid).set_master_session() + + elif (not self.master_sessions[_profile_name].is_desktop_session() and self(_session_uuid).is_desktop_session()) or \ + (not self.master_sessions[_profile_name].is_desktop_session() and self(_session_uuid).is_published_applications_provider()): + self(self.master_sessions[_profile_name]()).unset_master_session() + self.master_sessions[_profile_name] = self(_session_uuid) + self(_session_uuid).set_master_session() + if _last_status['suspended']: # from a suspended state self.client_instance.HOOK_on_session_has_resumed_by_me(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) elif _last_status['virgin']: # as a new session self.client_instance.HOOK_on_session_has_started_by_me(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) + else: if _last_status['suspended']: # from a suspended state self.client_instance.HOOK_on_session_has_resumed_by_other(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) - else: - # as a new session + elif _last_status['connected'] and _last_status['virgin']: + # as a new session, do not report directly after connect due to many false positives then... self.client_instance.HOOK_on_session_has_started_by_other(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) - elif _last_status['connected'] and (not _last_status['suspended'] and _current_status['suspended']) and not _current_status['faulty']: + elif _last_status['connected'] and (not _last_status['suspended'] and _current_status['suspended']) and not _current_status['faulty'] and _session_name: + + # unregister as master session + if _profile_name in self.master_sessions.keys(): + if self.master_sessions[_profile_name] == self(_session_uuid): + + self(_session_uuid).unset_master_session() + del self.master_sessions[_profile_name] + # session has been suspended self(_session_uuid).session_cleanup() self.client_instance.HOOK_on_session_has_been_suspended(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) - elif _last_status['connected'] and (not _last_status['terminated'] and _current_status['terminated']) and not _current_status['faulty']: + + elif _last_status['connected'] and (not _last_status['terminated'] and _current_status['terminated']) and not _current_status['faulty'] and _session_name: + + # unregister as master session + if _profile_name in self.master_sessions.keys(): + if self.master_sessions[_profile_name] == self(_session_uuid): + + self(_session_uuid).unset_master_session() + del self.master_sessions[_profile_name] + # session has terminated self.client_instance.HOOK_on_session_has_terminated(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) try: self(_session_uuid).session_cleanup() - except X2goSessionException: pass + except x2go_exceptions.X2GoSessionException: pass try: self(_session_uuid).__del__() - except X2goSessionException: pass + except x2go_exceptions.X2GoSessionException: pass if len(self.virgin_sessions_of_profile_name(profile_name)) > 1: self.forget(_session_uuid) + # detect master sessions for connected profiles that have lost (suspend/terminate) their master session or never had a master session + for _profile_name in [ p for p in self.connected_profiles(return_profile_names=True) if p not in self.master_sessions.keys() ]: + _running_associated_sessions = [ _s for _s in self.running_sessions_of_profile_name(_profile_name, return_objects=True) if _s.is_associated() ] + if _running_associated_sessions: + for _r_a_s in _running_associated_sessions: + if _r_a_s.is_desktop_session(): + self.master_sessions[_profile_name] = _r_a_s + _r_a_s.set_master_session(wait=1) + break + if not self.master_sessions.has_key(_profile_name): + _pubapp_associated_sessions = self.pubapp_sessions_of_profile_name(_profile_name, return_objects=True) + if _pubapp_associated_sessions: + self.master_sessions[_profile_name] = _pubapp_associated_sessions[0] + _pubapp_associated_sessions[0].set_master_session(wait=2) + else: + self.master_sessions[_profile_name] = _running_associated_sessions[0] + _running_associated_sessions[0].set_master_session(wait=2) + return True - def register_available_server_sessions(self, profile_name, session_list=None, newly_connected=False): + def register_available_server_sessions(self, profile_name, session_list=None, newly_connected=False, re_register=False, skip_pubapp_sessions=False): """\ - Register server-side available X2go sessions with this L{X2goSessionRegistry} instance for a given profile name. + Register server-side available X2Go sessions with this L{X2GoSessionRegistry} instance for a given profile name. - @param profile_name: session profile name to register available X2go sessions for + @param profile_name: session profile name to register available X2Go sessions for @type profile_name: C{str} - @param session_list: an optional C{X2goServerSessionList*} instance (as returned by the L{X2goClient.list_sessions()} command can + @param session_list: an optional C{X2GoServerSessionList*} instance (as returned by the L{X2GoClient.list_sessions()} command can be passed to this method. - @type session_list: C{X2goServerSessionList*} instance + @type session_list: C{X2GoServerSessionList*} instance + @param newly_connected: give a hint that the session profile got newly connected + @type newly_connected: C{bool} + @param re_register: re-register available sessions, needs to be done after changes to the session profile + @type re_register: C{bool} + @param skip_pubapp_sessions: Do not register published applications sessions + @type skip_pubapp_sessions: C{bool} """ if self._last_available_session_registration is not None: _now = time.time() _time_delta = _now - self._last_available_session_registration - if _time_delta < 2: - self.logger('registration interval too short (%s), skipping automatic session registration...' % _timedelta, loglevel=log.loglevel_DEBUG) + if _time_delta < 2 and not re_register: + self.logger('registration interval too short (%s), skipping automatic session registration...' % _time_delta, loglevel=log.loglevel_DEBUG) return self._last_available_session_registration = _now @@ -333,7 +405,7 @@ _session_names = [ self(s_uuid).session_name for s_uuid in _registered_sessions if self(s_uuid).session_name is not None ] if _connected_sessions: - # any of the connected sessions is valuable for accessing the profile's control + # any of the connected sessions is valuable for accessing the profile's control # session commands, so we simply take the first that comes in... _ctrl_session = self(_connected_sessions[0]) @@ -341,10 +413,10 @@ session_list = _ctrl_session.list_sessions() # make sure the session registry gets updated before registering new session - # (if the server name has changed, this will kick out obsolete X2goSessions) + # (if the server name has changed, this will kick out obsolete X2GoSessions) self.update_status(profile_name=profile_name, session_list=session_list, force_update=True) for session_name in session_list.keys(): - if session_name not in _session_names and not self._skip_auto_registration: + if (session_name not in _session_names and not self._skip_auto_registration) or re_register: server = _ctrl_session.get_server_hostname() profile_id = _ctrl_session.get_profile_id() @@ -365,31 +437,32 @@ kwargs['client_rootdir'] = _clone_kwargs['client_rootdir'] kwargs['sessions_rootdir'] = _clone_kwargs['sessions_rootdir'] - try: del kwargs['server'] + try: del kwargs['server'] except: pass try: del kwargs['profile_name'] except: pass - try: del kwargs['profile_id'] + try: del kwargs['profile_id'] except: pass # this if clause catches problems when x2golistsessions commands give weird results - if not self.has_session_of_session_name(session_name): - session_uuid = self.register(server, profile_id, profile_name, - session_name=session_name, virgin=False, - **kwargs - ) - self(session_uuid).connected = True - self.update_status(session_uuid=session_uuid, force_update=True, newly_connected=newly_connected) + if not self.has_session_of_session_name(session_name) or re_register: + if not (skip_pubapp_sessions and re.match('.*_stRPUBLISHED_.*', session_name)): + session_uuid = self.register(server, profile_id, profile_name, + session_name=session_name, virgin=False, + **kwargs + ) + self(session_uuid).connected = True + self.update_status(session_uuid=session_uuid, force_update=True, newly_connected=newly_connected) def register(self, server, profile_id, profile_name, session_name=None, - control_backend=_X2goControlSession, - terminal_backend=_X2goTerminalSession, - info_backend=_X2goServerSessionInfo, - list_backend=_X2goServerSessionList, - proxy_backend=_X2goProxy, - settings_backend=_X2goClientSettings, - printing_backend=_X2goClientPrinting, + control_backend=_BACKENDS['X2GoControlSession']['default'], + terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], + info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], + list_backend=_BACKENDS['X2GoServerSessionList']['default'], + proxy_backend=_BACKENDS['X2GoProxy']['default'], + settings_backend=_BACKENDS['X2GoClientSettings']['default'], + printing_backend=_BACKENDS['X2GoClientPrinting']['default'], client_rootdir=os.path.join(_LOCAL_HOME,_X2GO_CLIENT_ROOTDIR), sessions_rootdir=os.path.join(_LOCAL_HOME,_X2GO_SESSIONS_ROOTDIR), ssh_rootdir=os.path.join(_LOCAL_HOME,_X2GO_SSH_ROOTDIR), @@ -398,9 +471,9 @@ known_hosts=None, **kwargs): """\ - Register a new L{X2goSession} instance with this L{X2goSessionRegistry}. + Register a new L{X2GoSession} instance with this L{X2GoSessionRegistry}. - @param server: hostname of X2go server + @param server: hostname of X2Go server @type server: C{str} @param profile_id: profile ID @type profile_id: C{str} @@ -408,64 +481,92 @@ @type profile_name: C{str} @param session_name: session name (if available) @type session_name: C{str} - @param control_backend: X2go control session backend to use - @type control_backend: C{class} - @param terminal_backend: X2go terminal session backend to use - @type terminal_backend: C{class} - @param info_backend: X2go session info backend to use - @type info_backend: C{class} - @param list_backend: X2go session list backend to use - @type list_backend: C{class} - @param proxy_backend: X2go proxy backend to use - @type proxy_backend: C{class} - @param settings_backend: X2go client settings backend to use - @type settings_backend: C{class} - @param printing_backend: X2go client printing backend to use - @type printing_backend: C{class} + @param control_backend: X2Go control session backend to use + @type control_backend: C{str} + @param terminal_backend: X2Go terminal session backend to use + @type terminal_backend: C{str} + @param info_backend: X2Go session info backend to use + @type info_backend: C{str} + @param list_backend: X2Go session list backend to use + @type list_backend: C{str} + @param proxy_backend: X2Go proxy backend to use + @type proxy_backend: C{str} + @param settings_backend: X2Go client settings backend to use + @type settings_backend: C{str} + @param printing_backend: X2Go client printing backend to use + @type printing_backend: C{str} @param client_rootdir: client base dir (default: ~/.x2goclient) @type client_rootdir: C{str} @param sessions_rootdir: sessions base dir (default: ~/.x2go) @type sessions_rootdir: C{str} @param ssh_rootdir: ssh base dir (default: ~/.ssh) @type ssh_rootdir: C{str} - @param keep_controlsession_alive: On last L{X2goSession.disconnect()} keep the associated C{X2goControlSession*} instance alive? + @param keep_controlsession_alive: On last L{X2GoSession.disconnect()} keep the associated C{X2GoControlSession} instance alive? @ŧype keep_controlsession_alive: C{bool} @param add_to_known_hosts: Auto-accept server host validity? @type add_to_known_hosts: C{bool} @param known_hosts: the underlying Paramiko/SSH systems C{known_hosts} file @type known_hosts: C{str} - @param kwargs: all other options will be passed on to the constructor of the to-be-instantiated L{X2goSession} instance + @param kwargs: all other options will be passed on to the constructor of the to-be-instantiated L{X2GoSession} instance @type C{dict} + @return: the session UUID of the newly registered (or re-registered) session + @rtype: C{str} + """ + if profile_id not in self._profile_locks.keys(): + self._profile_locks[profile_id] = threading.Lock() + + self._profile_locks[profile_id].acquire() + control_session = None if profile_id in self.control_sessions.keys(): control_session = self.control_sessions[profile_id] + try: + _params = self.client_instance.session_profiles.to_session_params(profile_id) + + except x2go_exceptions.X2GoProfileException: + _params = utils._convert_SessionProfileOptions_2_SessionParams(_X2GO_SESSIONPROFILE_DEFAULTS) + + for _k in _params.keys(): + if _k in kwargs.keys(): + _params[_k] = kwargs[_k] + + # allow injection of PKey objects (Paramiko's private SSH keys) + if kwargs.has_key('pkey'): + _params['pkey'] = kwargs['pkey'] + if kwargs.has_key('sshproxy_pkey'): + _params['sshproxy_pkey'] = kwargs['sshproxy_pkey'] + # when starting a new session, we will try to use unused registered virgin sessions - # depending on your application layout, there shoul either be one or no such virgin session at all - _virgin_sessions = self.virgin_sessions_of_profile_name(profile_name, return_objects=True) + # depending on your application layout, there should either be one or no such virgin session at all + _virgin_sessions = [ s for s in self.virgin_sessions_of_profile_name(profile_name, return_objects=True) if not s.activated ] if _virgin_sessions and not session_name: - session_uuid = _virgin_sessions[0].get_uuid() - _params = self.client_instance.session_profiles.to_session_params(profile_id) - self(session_uuid).update_params(_params) - self(session_uuid).set_server(server) - self(session_uuid).set_profile_name(profile_name) + self(session_uuid).activated = True self.logger('using already initially-registered yet-unused session %s' % session_uuid, loglevel=log.loglevel_NOTICE) - return session_uuid - session_uuid = self.get_session_of_session_name(session_name) - if session_uuid is not None: - _params = self.client_instance.session_profiles.to_session_params(profile_id) + else: + session_uuid = self.get_session_of_session_name(session_name, match_profile_name=profile_name) + if session_uuid is not None: self.logger('using already registered-by-session-name session %s' % session_uuid, loglevel=log.loglevel_NOTICE) + if session_uuid is not None: + self(session_uuid).activated = True self(session_uuid).update_params(_params) self(session_uuid).set_server(server) self(session_uuid).set_profile_name(profile_name) - self.logger('using already registered-by-session-name session %s' % session_uuid, loglevel=log.loglevel_NOTICE) + self._profile_locks[profile_id].release() return session_uuid - s = session.X2goSession(server=server, control_session=control_session, + try: del _params['server'] + except: pass + try: del _params['profile_name'] + except: pass + try: del _params['profile_id'] + except: pass + + s = session.X2GoSession(server=server, control_session=control_session, profile_id=profile_id, profile_name=profile_name, session_name=session_name, control_backend=control_backend, @@ -481,43 +582,63 @@ keep_controlsession_alive=keep_controlsession_alive, add_to_known_hosts=add_to_known_hosts, known_hosts=known_hosts, - logger=self.logger, **kwargs) + client_instance=self.client_instance, + logger=self.logger, **_params) - session_uuid = s._X2goSession__get_uuid() - self.logger('registering X2go session %s...' % profile_name, log.loglevel_NOTICE) - self.logger('registering X2go session with UUID %s' % session_uuid, log.loglevel_DEBUG) + session_uuid = s._X2GoSession__get_uuid() + self.logger('registering X2Go session %s...' % profile_name, log.loglevel_NOTICE) + self.logger('registering X2Go session with UUID %s' % session_uuid, log.loglevel_DEBUG) self.registry[session_uuid] = s if profile_id not in self.control_sessions.keys(): self.control_sessions[profile_id] = s.get_control_session() + # make sure a new session is a non-master session unless promoted in update_status method + self(session_uuid).unset_master_session() + if control_session is None: + self(session_uuid).do_auto_connect() + + self._profile_locks[profile_id].release() return session_uuid - def has_session_of_session_name(self, session_name): + def has_session_of_session_name(self, session_name, match_profile_name=None): """\ - Detect if we know about an L{X2goSession} of name C{}. + Detect if we know about an L{X2GoSession} of name C{}. @param session_name: name of session to be searched for @type session_name: C{str} + @param match_profile_name: a session's profile_name must match this profile name + @type match_profile_name: C{str} + @return: C{True} if a session of C{} has been found @rtype: C{bool} """ - return bool(self.get_session_of_session_name(session_name)) + return bool(self.get_session_of_session_name(session_name, match_profile_name=match_profile_name)) - def get_session_of_session_name(self, session_name, return_object=False): + def get_session_of_session_name(self, session_name, return_object=False, match_profile_name=None): """\ - Retrieve the L{X2goSession} instance with session name C{}. + Retrieve the L{X2GoSession} instance with session name C{}. @param session_name: name of session to be retrieved @type session_name: C{str} - @param return_object: if C{False} the session UUID hash will be returned, if C{True} the L{X2goSession} instance will be returned + @param return_object: if C{False} the session UUID hash will be returned, if C{True} the L{X2GoSession} instance will be returned @type return_object: C{bool} - @return: L{X2goSession} object or its representing session UUID hash - @rtype: L{X2goSession} instance or C{str} + @param match_profile_name: returned sessions must match this profile name + @type match_profile_name: C{str} + + @return: L{X2GoSession} object or its representing session UUID hash + @rtype: L{X2GoSession} instance or C{str} + + @raise X2GoSessionRegistryException: if there is more than one L{X2GoSession} registered for C{} within + the same L{X2GoClient} instance. This should never happen! """ - found_sessions = [ s for s in self.registered_sessions() if s.session_name == session_name and s.session_name is not None ] + if match_profile_name is None: + reg_sessions = self.registered_sessions() + else: + reg_sessions = self.registered_sessions_of_profile_name(match_profile_name) + found_sessions = [ s for s in reg_sessions if s.session_name == session_name and s.session_name is not None ] if len(found_sessions) == 1: session = found_sessions[0] if return_object: @@ -525,7 +646,7 @@ else: return session.get_uuid() elif len(found_sessions) > 1: - raise X2goSessionRegistryException('there should only be one registered session of name ,,%s\'\'' % session_name) + raise x2go_exceptions.X2GoSessionRegistryException('there should only be one registered session of name ,,%s\'\'' % session_name) else: return None @@ -538,21 +659,21 @@ sessions = [ ts for ts in self.registry.values() if eval('ts.%s' % state) ] if return_profile_names: profile_names = [] - for session in sessions: - if session.profile_name not in profile_names: - profile_names.append(session.profile_name) + for this_session in sessions: + if this_session.profile_name and this_session.profile_name not in profile_names: + profile_names.append(this_session.profile_name) return profile_names elif return_profile_ids: profile_ids = [] - for session in sessions: - if session.profile_id not in profile_ids: - profile_ids.append(session.profile_id) + for this_session in sessions: + if this_session.profile_id and this_session.profile_id not in profile_ids: + profile_ids.append(this_session.profile_id) return profile_ids elif return_session_names: session_names = [] - for session in sessions: - if session.session_name not in session_names: - session_names.append(session.session_name) + for this_session in sessions: + if this_session.session_name and this_session.session_name not in session_names: + session_names.append(this_session.session_name) return session_names elif return_objects: return sessions @@ -561,17 +682,18 @@ def connected_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of sessions that the underlying L{X2goClient} instances is currently connected to. + Retrieve a list of sessions that the underlying L{X2GoClient} instances is currently connected to. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -580,17 +702,18 @@ def associated_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ - Retrieve a list of sessions that are currently associated by an C{X2goTerminalSession*} to the underlying L{X2goClient} instance. + Retrieve a list of sessions that are currently associated by an C{X2GoTerminalSession*} to the underlying L{X2GoClient} instance. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -602,18 +725,18 @@ Retrieve a list of sessions that are currently still in virgin state (not yet connected, associated etc.). If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} - """ return self._sessionsWithState('virgin', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) @@ -622,14 +745,15 @@ Retrieve a list of sessions that are currently in running state. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -641,14 +765,15 @@ Retrieve a list of sessions that are currently in suspended state. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -660,14 +785,15 @@ Retrieve a list of sessions that have terminated recently. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -677,7 +803,7 @@ @property def has_running_sessions(self): """\ - Equals C{True} if the underlying L{X2goClient} instance has any running sessions at hand. + Equals C{True} if the underlying L{X2GoClient} instance has any running sessions at hand. """ return self.running_sessions() and len(self.running_sessions()) > 0 @@ -685,7 +811,7 @@ @property def has_suspended_sessions(self): """\ - Equals C{True} if the underlying L{X2goClient} instance has any suspended sessions at hand. + Equals C{True} if the underlying L{X2GoClient} instance has any suspended sessions at hand. """ return self.suspended_sessions and len(self.suspended_sessions) > 0 @@ -695,14 +821,15 @@ Retrieve a list of all registered sessions. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -714,14 +841,15 @@ Retrieve a list of sessions that are currently _NOT_ in running state. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} @param return_profile_names: return as list of profile names @type return_profile_names: C{bool} @param return_profile_ids: return as list of profile IDs @type return_profile_ids: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) @rtype: C{list} @@ -730,93 +858,119 @@ def connected_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ - For a given session profile name retrieve a list of sessions that are currently connected to the profile's X2go server. + For a given session profile name retrieve a list of sessions that are currently connected to the profile's X2Go server. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. @param profile_name: session profile name @type profile_name: C{str} - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects or session names) @rtype: C{list} """ if return_objects: - return self.connected_sessions() and [ s for s in self.connected_sessions() if s.profile_name == profile_name ] + return self.connected_sessions() and [ s for s in self.connected_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: - return self.connected_sessions() and [ s.session_name for s in self.connected_sessions() if s.profile_name == profile_name ] + return self.connected_sessions() and [ s.session_name for s in self.connected_sessions() if s.get_profile_name() == profile_name ] else: - return self.connected_sessions() and [ s.get_uuid() for s in self.connected_sessions() if s.profile_name == profile_name ] + return self.connected_sessions() and [ s.get_uuid() for s in self.connected_sessions() if s.get_profile_name() == profile_name ] def associated_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ - For a given session profile name retrieve a list of sessions that are currently associated by an C{X2goTerminalSession*} to this L{X2goClient} instance. + For a given session profile name retrieve a list of sessions that are currently associated by an C{X2GoTerminalSession*} to this L{X2GoClient} instance. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. @param profile_name: session profile name @type profile_name: C{str} - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects or session names) @rtype: C{list} """ if return_objects: - return self.associated_sessions() and [ s for s in self.associated_sessions() if s.profile_name == profile_name ] + return self.associated_sessions() and [ s for s in self.associated_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: - return self.associated_sessions() and [ s.session_name for s in self.associated_sessions() if s.profile_name == profile_name ] + return self.associated_sessions() and [ s.session_name for s in self.associated_sessions() if s.get_profile_name() == profile_name ] else: - return self.associated_sessions() and [ s.get_uuid() for s in self.associated_sessions() if s.profile_name == profile_name ] + return self.associated_sessions() and [ s.get_uuid() for s in self.associated_sessions() if s.get_profile_name() == profile_name ] - def registered_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): + def pubapp_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ - For a given session profile name retrieve a list of sessions that are currently registered with this L{X2goClient} instance. + For a given session profile name retrieve a list of sessions that can be providers for published application list. If none of the C{return_*} options is specified a list of session UUID hashes will be returned. @param profile_name: session profile name @type profile_name: C{str} - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects or session names) @rtype: C{list} """ + if return_objects: + return self.associated_sessions_of_profile_name(profile_name) and [ s for s in self.associated_sessions_of_profile_name(profile_name) if s.is_published_applications_provider() ] + elif return_session_names: + return self.associated_sessions_of_profile_name(profile_name) and [ s.session_name for s in self.associated_sessions_of_profile_name(profile_name) if s.is_published_applications_provider() ] + else: + return self.associated_sessions_of_profile_name(profile_name) and [ s.get_uuid() for s in self.associated_sessions_of_profile_name(profile_name) if s.is_published_applications_provider() ] + + def registered_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): + """\ + For a given session profile name retrieve a list of sessions that are currently registered with this L{X2GoClient} instance. + If none of the C{return_*} options is specified a list of session UUID hashes will be returned. + @param profile_name: session profile name + @type profile_name: C{str} + @param return_objects: return as list of L{X2GoSession} instances + @type return_objects: C{bool} + @param return_session_names: return as list of X2Go session names + @type return_session_names: C{bool} + + @return: a session list (as UUID hashes, objects or session names) + @rtype: C{list} + + """ if return_objects: - return self.registered_sessions() and [ s for s in self.registered_sessions() if s.profile_name == profile_name ] + return self.registered_sessions() and [ s for s in self.registered_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: - return self.registered_sessions() and [ s.session_name for s in self.registered_sessions() if s.profile_name == profile_name ] + return self.registered_sessions() and [ s.session_name for s in self.registered_sessions() if s.get_profile_name() == profile_name ] else: - return self.registered_sessions() and [ s.get_uuid() for s in self.registered_sessions() if s.profile_name == profile_name ] + return self.registered_sessions() and [ s.get_uuid() for s in self.registered_sessions() if s.get_profile_name() == profile_name ] def virgin_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ - For a given session profile name retrieve a list of sessions that are registered with this L{X2goClient} instance but have not - yet been started (i.e. sessions that are in virgin state). If none of the C{return_*} options is specified a list of + For a given session profile name retrieve a list of sessions that are registered with this L{X2GoClient} instance but have not + yet been started (i.e. sessions that are in virgin state). If none of the C{return_*} options is specified a list of session UUID hashes will be returned. @param profile_name: session profile name @type profile_name: C{str} - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects or session names) @rtype: C{list} """ if return_objects: - return self.virgin_sessions() and [ s for s in self.virgin_sessions() if s.profile_name == profile_name ] + return self.virgin_sessions() and [ s for s in self.virgin_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: - return self.virgin_sessions() and [ s.session_name for s in self.virgin_sessions() if s.profile_name == profile_name ] + return self.virgin_sessions() and [ s.session_name for s in self.virgin_sessions() if s.get_profile_name() == profile_name ] else: - return self.virgin_sessions() and [ s.get_uuid() for s in self.virgin_sessions() if s.profile_name == profile_name ] + return self.virgin_sessions() and [ s.get_uuid() for s in self.virgin_sessions() if s.get_profile_name() == profile_name ] def running_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ @@ -825,20 +979,21 @@ @param profile_name: session profile name @type profile_name: C{str} - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects or session names) @rtype: C{list} """ if return_objects: - return self.running_sessions() and [ s for s in self.running_sessions() if s.profile_name == profile_name ] + return self.running_sessions() and [ s for s in self.running_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: - return self.running_sessions() and [ s.session_name for s in self.running_sessions() if s.profile_name == profile_name ] + return self.running_sessions() and [ s.session_name for s in self.running_sessions() if s.get_profile_name() == profile_name ] else: - return self.running_sessions() and [ s.get_uuid() for s in self.running_sessions() if s.profile_name == profile_name ] + return self.running_sessions() and [ s.get_uuid() for s in self.running_sessions() if s.get_profile_name() == profile_name ] def suspended_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ @@ -847,29 +1002,31 @@ @param profile_name: session profile name @type profile_name: C{str} - @param return_objects: return as list of L{X2goSession} instances + @param return_objects: return as list of L{X2GoSession} instances @type return_objects: C{bool} - @param return_session_names: return as list of X2go session names + @param return_session_names: return as list of X2Go session names @type return_session_names: C{bool} + @return: a session list (as UUID hashes, objects or session names) @rtype: C{list} """ if return_objects: - return self.suspended_sessions() and [ s for s in self.suspended_sessions() if s.profile_name == profile_name ] + return self.suspended_sessions() and [ s for s in self.suspended_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: - return self.suspended_sessions() and [ s.session_name for s in self.suspended_sessions() if s.profile_name == profile_name ] + return self.suspended_sessions() and [ s.session_name for s in self.suspended_sessions() if s.get_profile_name() == profile_name ] else: - return self.suspended_sessions() and [ s.get_uuid() for s in self.suspended_sessions() if s.profile_name == profile_name ] + return self.suspended_sessions() and [ s.get_uuid() for s in self.suspended_sessions() if s.get_profile_name() == profile_name ] def control_session_of_profile_name(self, profile_name): """\ - For a given session profile name retrieve a the corresponding C{X2goControlSession*} instance. + For a given session profile name retrieve a the corresponding C{X2GoControlSession*} instance. @param profile_name: session profile name @type profile_name: C{str} + @return: contol session instance - @rtype: C{X2goControlSession*} instance + @rtype: C{X2GoControlSession*} instance """ _sessions = self.registered_sessions_of_profile_name(profile_name, return_objects=True) @@ -886,12 +1043,13 @@ """ return [ c for c in self.control_sessions.values() if c.is_connected() ] - def connected_profiles(self, use_paramiko=False): + def connected_profiles(self, use_paramiko=False, return_profile_ids=True, return_profile_names=False): """\ Retrieve a list of all currently connected session profiles. @param use_paramiko: send query directly to the Paramiko/SSH layer @type use_paramiko: C{bool} + @return: list of connected session profiles @rtype: C{list} @@ -899,5 +1057,38 @@ if use_paramiko: return [ p for p in self.control_sessions.keys() if self.control_sessions[p].is_connected() ] else: - return self.connected_sessions(return_profile_ids=True) + return self.connected_sessions(return_profile_ids=return_profile_ids, return_profile_names=return_profile_names) + + def get_master_session(self, profile_name, return_object=True, return_session_name=False): + """\ + Retrieve the master session of a specific profile. + + @param profile_name: the profile name that we query the master session of + @type profile_name: C{str} + @param return_object: return L{X2GoSession} instance + @type return_object: C{bool} + @param return_session_name: return X2Go session name + @type return_session_name: C{bool} + @return: a session list (as UUID hashes, objects, profile names/IDs or session names) + @rtype: C{list} + + """ + if profile_name not in self.connected_profiles(return_profile_names=True): + return None + + if profile_name not in self.master_sessions.keys() or self.master_sessions[profile_name] is None: + return None + + _session = self.master_sessions[profile_name] + + if not _session.is_master_session(): + del self.master_sessions[profile_name] + return None + + if return_object: + return _session + elif return_session_name: + return _session.get_session_name() + else: + return _session.get_uuid() diff -Nru python-x2go-0.1.1.8/x2go/rforward.py python-x2go-0.5.0.6/x2go/rforward.py --- python-x2go-0.1.1.8/x2go/rforward.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/rforward.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,25 +1,23 @@ -#!/usr/bin/env python - -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2go reverse SSH/Paramiko tunneling provides X2go sound, X2go printing and -X2go sshfs for folder sharing and mounting remote devices in X2go terminal +X2Go reverse SSH/Paramiko tunneling provides X2Go sound, X2Go printing and +X2Go sshfs for folder sharing and mounting remote devices in X2Go terminal server sessions. """ @@ -33,27 +31,26 @@ # gevent/greenlet from gevent import select, socket, Timeout -from gevent import sleep as gevent_sleep -# Python X2go modules +# Python X2Go modules import log def x2go_transport_tcp_handler(chan, (origin_addr, origin_port), (server_addr, server_port)): """\ - An X2go customized TCP handler for the Paramiko/SSH C{Transport()} class. + An X2Go customized TCP handler for the Paramiko/SSH C{Transport()} class. - Incoming channels will be put into Paramiko's default accept queue. This corresponds to + Incoming channels will be put into Paramiko's default accept queue. This corresponds to the default behaviour of Paramiko's C{Transport} class. However, additionally this handler function checks the server port of the incoming channel - and detects if there are Paramiko/SSH reverse forwarding tunnels waiting for the incoming - channels. The Paramiko/SSH reverse forwarding tunnels are initiated by an L{X2goSession} instance + and detects if there are Paramiko/SSH reverse forwarding tunnels waiting for the incoming + channels. The Paramiko/SSH reverse forwarding tunnels are initiated by an L{X2GoSession} instance (currently supported: reverse tunneling auf audio data, reverse tunneling of SSH requests). - If the server port of an incoming Paramiko/SSH channel matches the configured port of an L{X2goRevFwTunnel} - instance, this instance gets notified of the incoming channel and a new L{X2goRevFwChannelThread} is - started. This L{X2goRevFwChannelThread} then takes care of the new channel's incoming data stream. + If the server port of an incoming Paramiko/SSH channel matches the configured port of an L{X2GoRevFwTunnel} + instance, this instance gets notified of the incoming channel and a new L{X2GoRevFwChannelThread} is + started. This L{X2GoRevFwChannelThread} then takes care of the new channel's incoming data stream. """ transport = chan.get_transport() @@ -71,10 +68,10 @@ rev_tuns[session_name]['sshfs'][1].notify() -class X2goRevFwTunnel(threading.Thread): +class X2GoRevFwTunnel(threading.Thread): """\ - L{X2goRevFwTunnel} class objects are used to reversely tunnel - X2go audio, X2go printing and X2go folder sharing / device mounting + L{X2GoRevFwTunnel} class objects are used to reversely tunnel + X2Go audio, X2Go printing and X2Go folder sharing / device mounting through Paramiko/SSH. """ @@ -82,32 +79,32 @@ """\ Setup a reverse tunnel through Paramiko/SSH. - After the reverse tunnel has been setup up with L{X2goRevFwTunnel.start()} it waits - for notification from L{X2goRevFwTunnel.notify()} to accept incoming channels. This - notification (L{X2goRevFwTunnel.notify()} gets called from within the transport's - TCP handler function L{x2go_transport_tcp_handler} of the L{X2goSession} instance. + After the reverse tunnel has been setup up with L{X2GoRevFwTunnel.start()} it waits + for notification from L{X2GoRevFwTunnel.notify()} to accept incoming channels. This + notification (L{X2GoRevFwTunnel.notify()} gets called from within the transport's + TCP handler function L{x2go_transport_tcp_handler} of the L{X2GoSession} instance. - @param server_port: the TCP/IP port on the X2go server (starting point of the tunnel), + @param server_port: the TCP/IP port on the X2Go server (starting point of the tunnel), normally some number above 30000 @type server_port: int - @param remote_host: the target address for reversely tunneled traffic. With X2go this should + @param remote_host: the target address for reversely tunneled traffic. With X2Go this should always be set to the localhost (IPv4) address. @type remote_host: str - @param remote_port: the TCP/IP port on the X2go client (end point of the tunnel), + @param remote_port: the TCP/IP port on the X2Go client (end point of the tunnel), normally an application's standard port (22 for SSH, 4713 for pulse audio, etc.) @type remote_port: int - @param ssh_transport: the L{X2goSession}'s Paramiko/SSH transport instance + @param ssh_transport: the L{X2GoSession}'s Paramiko/SSH transport instance @type ssh_transport: C{paramiko.Transport} instance - @param logger: you can pass an L{X2goLogger} object to the - L{X2goRevFwTunnel} constructor - @type logger: L{X2goLogger} instance - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoRevFwTunnel} constructor + @type logger: L{X2GoLogger} instance + @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: int """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -147,7 +144,7 @@ timeout = Timeout(10) timeout.start() try: - self.ssh_transport.cancel_port_forward('', self.server_port) + self.ssh_transport.global_request('cancel-tcpip-forward', (address, port), wait=True) except: pass finally: @@ -156,7 +153,7 @@ def pause(self): """\ Prevent acceptance of new incoming connections through the Paramiko/SSH - reverse forwarding tunnel. Also, any active connection on this L{X2goRevFwTunnel} + reverse forwarding tunnel. Also, any active connection on this L{X2GoRevFwTunnel} instance will be closed immediately, if this method is called. """ @@ -173,19 +170,19 @@ """ if self._accept_channels == False: self._accept_channels = True - self._requested_port = self.ssh_transport.request_port_forward('', self.server_port, handler=x2go_transport_tcp_handler) + self._requested_port = self.ssh_transport.request_port_forward('127.0.0.1', self.server_port, handler=x2go_transport_tcp_handler) self.logger('resumed thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def notify(self): """\ - Notify an L{X2goRevFwTunnel} instance of an incoming Paramiko/SSH channel. + Notify an L{X2GoRevFwTunnel} instance of an incoming Paramiko/SSH channel. If an incoming reverse tunnel channel appropriate for this instance has - been detected, this method gets called from the L{X2goSession}'s transport + been detected, this method gets called from the L{X2GoSession}'s transport TCP handler. The sent notification will trigger a C{thread.Condition()} waiting for notification - in L{X2goRevFwTunnel.run()}. + in L{X2GoRevFwTunnel.run()}. """ self.incoming_channel.acquire() @@ -195,7 +192,7 @@ def stop_thread(self): """\ - Stops this L{X2goRevFwTunnel} thread completely. + Stops this L{X2GoRevFwTunnel} thread completely. """ self.pause() @@ -205,14 +202,14 @@ def _request_port_forwarding(self): try: - self._requested_port = self.ssh_transport.request_port_forward('', self.server_port, handler=x2go_transport_tcp_handler) + self._requested_port = self.ssh_transport.request_port_forward('127.0.0.1', self.server_port, handler=x2go_transport_tcp_handler) except paramiko.SSHException: - # if port forward request fails, we try to tell the server to cancel all foregoing port forward requests on + # if port forward request fails, we try to tell the server to cancel all foregoing port forward requests on # self.server_port self.cancel_port_forward('', self.server_port) gevent.sleep(1) try: - self._requested_port = self.ssh_transport.request_port_forward('', self.server_port, handler=x2go_transport_tcp_handler) + self._requested_port = self.ssh_transport.request_port_forward('127.0.0.1', self.server_port, handler=x2go_transport_tcp_handler) except paramiko.SSHException, e: if self.session_instance: self.session_instance.HOOK_rforward_request_denied(server_port=self.server_port) @@ -221,23 +218,23 @@ def run(self): """\ - This method gets run once an L{X2goRevFwTunnel} has been started with its - L{start()} method. Use L{X2goRevFwTunnel}.stop_thread() to stop the + This method gets run once an L{X2GoRevFwTunnel} has been started with its + L{start()} method. Use L{X2GoRevFwTunnel}.stop_thread() to stop the reverse forwarding tunnel again. You can also temporarily lock the tunnel - down with L{X2goRevFwTunnel.pause()} and L{X2goRevFwTunnel.resume()}). + down with L{X2GoRevFwTunnel.pause()} and L{X2GoRevFwTunnel.resume()}). - L{X2goRevFwTunnel.run()} waits for notifications of an appropriate incoming - Paramiko/SSH channel (issued by L{X2goRevFwTunnel.notify()}). Appropriate in - this context means, that its start point on the X2go server matches the class's + L{X2GoRevFwTunnel.run()} waits for notifications of an appropriate incoming + Paramiko/SSH channel (issued by L{X2GoRevFwTunnel.notify()}). Appropriate in + this context means, that its start point on the X2Go server matches the class's property C{server_port}. - Once a new incoming channel gets announced by the L{notify()} method, a new - L{X2goRevFwChannelThread} instance will be initialized. As a data stream handler, + Once a new incoming channel gets announced by the L{notify()} method, a new + L{X2GoRevFwChannelThread} instance will be initialized. As a data stream handler, the function L{x2go_rev_forward_channel_handler()} will be used. - The channel will last till the connection gets dropped on the X2go server side or - until the tunnel gets paused by an L{X2goRevFwTunnel.pause()} call or stopped via the - L{X2goRevFwTunnel.stop_thread()} method. + The channel will last till the connection gets dropped on the X2Go server side or + until the tunnel gets paused by an L{X2GoRevFwTunnel.pause()} call or stopped via the + L{X2GoRevFwTunnel.stop_thread()} method. """ self._request_port_forwarding() @@ -246,11 +243,11 @@ self.incoming_channel.acquire() - self.logger('waiting for incoming data channel on X2go server port: [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) + self.logger('waiting for incoming data channel on X2Go server port: [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) self.incoming_channel.wait() if self._keepalive: - self.logger('detected incoming data channel on X2go server port: [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) + self.logger('detected incoming data channel on X2Go server port: [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) _chan = self.ssh_transport.accept() self.logger('data channel %s for server port [127.0.0.1]:%s is up' % (_chan, self.server_port), loglevel=log.loglevel_DEBUG) else: @@ -258,14 +255,14 @@ self.incoming_channel.release() if self._accept_channels and self._keepalive: - _new_chan_thread = X2goRevFwChannelThread(_chan, (self.remote_host, self.remote_port), - target=x2go_rev_forward_channel_handler, - kwargs={ + _new_chan_thread = X2GoRevFwChannelThread(_chan, (self.remote_host, self.remote_port), + target=x2go_rev_forward_channel_handler, + kwargs={ 'chan': _chan, 'addr': self.remote_host, 'port': self.remote_port, - 'parent_thread': self, - 'logger': self.logger, + 'parent_thread': self, + 'logger': self.logger, } ) _new_chan_thread.start() @@ -274,17 +271,17 @@ def x2go_rev_forward_channel_handler(chan=None, addr='', port=0, parent_thread=None, logger=None, ): """\ - Handle the data stream of a requested channel that got set up by a L{X2goRevFwTunnel} (Paramiko/SSH + Handle the data stream of a requested channel that got set up by a L{X2GoRevFwTunnel} (Paramiko/SSH reverse forwarding tunnel). The channel (and the corresponding connections) close either ... - - ... if the connecting application closes the connection and thus, drops + - ... if the connecting application closes the connection and thus, drops the channel, or - - ... if the L{X2goRevFwTunnel} parent thread gets paused. The call - of L{X2goRevFwTunnel.pause()} on the instance can be used to shut down all incoming - tunneled SSH connections associated to this L{X2goRevFwTunnel} instance - from within a Python X2go application. + - ... if the L{X2GoRevFwTunnel} parent thread gets paused. The call + of L{X2GoRevFwTunnel.pause()} on the instance can be used to shut down all incoming + tunneled SSH connections associated to this L{X2GoRevFwTunnel} instance + from within a Python X2Go application. @param chan: channel @type chan: C{class} @@ -292,11 +289,11 @@ @type addr: C{str} @param port: bind port @type port: C{int} - @param parent_thread: the calling L{X2goRevFwTunnel} instance - @type parent_thread: L{X2goRevFwTunnel} instance - @param logger: you can pass an L{X2goLogger} object to the - L{X2goRevFwTunnel} constructor - @type logger: L{X2goLogger} instance + @param parent_thread: the calling L{X2GoRevFwTunnel} instance + @type parent_thread: L{X2GoRevFwTunnel} instance + @param logger: you can pass an L{X2GoLogger} object to the + L{X2GoRevFwTunnel} constructor + @type logger: L{X2GoLogger} instance """ fw_socket = socket.socket() @@ -313,27 +310,30 @@ return logger('Connected! Reverse tunnel open %r -> %r -> %r' % (chan.origin_addr, - chan.getpeername(), (addr, port)), + chan.getpeername(), (addr, port)), loglevel=log.loglevel_INFO) while parent_thread._accept_channels: r, w, x = select.select([fw_socket, chan], [], []) - if fw_socket in r: - data = fw_socket.recv(1024) - if len(data) == 0: - break - chan.send(data) - if chan in r: - data = chan.recv(1024) - if len(data) == 0: - break - fw_socket.send(data) + try: + if fw_socket in r: + data = fw_socket.recv(1024) + if len(data) == 0: + break + chan.send(data) + if chan in r: + data = chan.recv(1024) + if len(data) == 0: + break + fw_socket.send(data) + except socket.error, e: + logger('Reverse tunnel %s encoutered socket error: %s' % (chan, str(e)), loglevel=log.loglevel_WARN) chan.close() fw_socket.close() logger('Reverse tunnel %s closed from %r' % (chan, chan.origin_addr,), loglevel=log.loglevel_INFO) -class X2goRevFwChannelThread(threading.Thread): +class X2GoRevFwChannelThread(threading.Thread): """\ Starts a thread for each incoming Paramiko/SSH data channel trough the reverse forwarding tunnel. @@ -343,7 +343,7 @@ """\ Initializes a reverse forwarding channel thread. - @param channel: incoming Paramiko/SSH channel from the L{X2goSession}'s transport + @param channel: incoming Paramiko/SSH channel from the L{X2GoSession}'s transport accept queue @type channel: class @param remote: tuple (addr, port) that specifies the data endpoint of the channel diff -Nru python-x2go-0.1.1.8/x2go/session.py python-x2go-0.5.0.6/x2go/session.py --- python-x2go-0.1.1.8/x2go/session.py 2011-10-12 08:58:32.000000000 +0000 +++ python-x2go-0.5.0.6/x2go/session.py 2017-12-12 06:52:58.000000000 +0000 @@ -1,31 +1,54 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2011 by Mike Gabriel +# Copyright (C) 2010-2016 by Mike Gabriel # -# Python X2go is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# Python X2Go is free software; you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # -# Python X2go is distributed in the hope that it will be useful, +# Python X2Go 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. +# GNU Affero General Public License for more details. # -# You should have received a copy of the GNU General Public License +# You should have received a copy of the GNU Affero General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goSession class - a public API of Python X2go, handling standalone X2go +X2GoSession class - a public API of Python X2Go, handling standalone X2Go sessions. -This class is normally embedded into the context of an L{X2goClient} -instance, but it is also possible to address L{X2goSession}s directly via this +This class is normally embedded into the context of an L{X2GoClient} +instance, but it is also possible to address L{X2GoSession}s directly via this class. +To launch a session manually from the Python interactive shell, perform these +simple steps:: + + $ python + Python 2.6.6 (r266:84292, Dec 26 2010, 22:31:48) + [GCC 4.4.5] on linux2 + Type "help", "copyright", "credits" or "license" for more information. + >>> import x2go + >>> import gevent + Xlib.protocol.request.QueryExtension + >>> s = x2go.session.X2GoSession() + >>> s.set_server('') + >>> s.set_port() + >>> s.connect('', '') + [] (x2gocontrolsession-pylib) NOTICE: connecting to []: + [] (x2gosession-pylib) NOTICE: SSH host key verification for host []: with SSH-RSA fingerprint ,,'' initiated. We are seeing this X2Go server for the first time. + [] (x2gosession-pylib) WARN: HOOK_check_host_dialog: host check requested for []: with SSH-RSA fingerprint: ,,''. Automatically adding host as known host. + True + >>> s.start(cmd="LXDE") + True + >>> while True: gevent.sleep(1) + """ + __NAME__ = 'x2gosession-pylib' import os @@ -33,144 +56,176 @@ import types import uuid import time -import threading import gevent +import re +import threading +import base64 + +# FIXME: we need the list of keys from a potentially used SSH agent. This part of code has to be moved into the control session code +import paramiko -# Python X2go modules +# Python X2Go modules +import defaults import log import utils import session -from x2go_exceptions import * - -from x2go.backends.control import X2goControlSession as _X2goControlSession -from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession -from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo -from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList -from x2go.backends.proxy import X2goProxy as _X2goProxy -from x2go.backends.profiles import X2goSessionProfiles as _X2goSessionProfiles -from x2go.backends.settings import X2goClientSettings as _X2goClientSettings -from x2go.backends.printing import X2goClientPrinting as _X2goClientPrinting +import x2go_exceptions +from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS from defaults import LOCAL_HOME as _LOCAL_HOME from defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR from defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR -from defaults import SUPPORTED_SOUND, SUPPORTED_PRINTING, SUPPORTED_FOLDERSHARING, SUPPORTED_MIMEBOX +from defaults import BACKENDS as _BACKENDS + +from defaults import SUPPORTED_SOUND, SUPPORTED_PRINTING, SUPPORTED_FOLDERSHARING, SUPPORTED_MIMEBOX, SUPPORTED_TELEKINESIS -# options of the paramiko.SSHClient().connect() -_X2GO_SESSION_PARAMS = ('geometry', 'depth', 'link', 'pack', - 'cache_type', 'kblayout', 'kbtype', - 'session_type', 'snd_system', 'snd_port', - 'cmd', - 'rdp_server', 'rdp_options', - 'xdmcp_server', - 'rootdir', 'loglevel', 'profile_name', 'profile_id', - 'print_action', 'print_action_args', - 'convert_encoding', 'client_encoding', 'server_encoding', - 'proxy_options', - 'logger', - 'control_backend', 'terminal_backend', 'proxy_backend', - 'profiles_backend', 'settings_backend', 'printing_backend', +_X2GO_SESSION_PARAMS = ('use_sshproxy', 'sshproxy_reuse_authinfo', + 'profile_id', 'session_name', + 'auto_start_or_resume', 'auto_connect', + 'printing', 'allow_mimebox', + 'mimebox_extensions', 'mimebox_action', + 'allow_share_local_folders', 'share_local_folders', 'restore_shared_local_folders', + 'control_backend', 'terminal_backend', 'info_backend', 'list_backend', 'proxy_backend', 'settings_backend', 'printing_backend', + 'client_rootdir', 'sessions_rootdir', 'ssh_rootdir', + 'keep_controlsession_alive', 'add_to_known_hosts', 'known_hosts', 'forward_sshagent', + 'connected', 'virgin', 'running', 'suspended', 'terminated', 'faulty' + 'client_instance', ) -"""A list of allowed X2go session parameters.""" -_X2GO_SSHPROXY_PARAMS = ('sshproxy_host', 'sshproxy_user', 'sshproxy_password', - 'sshproxy_key_filename', 'sshproxy_pkey', 'sshproxy_tunnel', +"""A list of allowed X2Go pure session parameters (i.e. parameters that are passed on neither to an X2GoControlSession, X2GoSSHProxy nor an X2GoControlSession object.""" +# options of the paramiko.SSHClient().connect() method, any option that is allowed for a terminal session instance +_X2GO_TERMINAL_PARAMS = ('geometry', 'depth', 'link', 'pack', 'dpi', + 'cache_type', 'kbtype', 'kblayout', 'kbvariant', 'clipboard', + 'session_type', 'snd_system', 'snd_port', + 'cmd', 'set_session_title', 'session_title', + 'rdp_server', 'rdp_options', 'applications', + 'xdmcp_server', + 'rootdir', 'loglevel', 'profile_name', 'profile_id', + 'print_action', 'print_action_args', + 'convert_encoding', 'client_encoding', 'server_encoding', + 'proxy_options', 'published_applications', 'published_applications_no_submenus', + 'logger', + 'control_backend', 'terminal_backend', 'proxy_backend', + 'profiles_backend', 'settings_backend', 'printing_backend', ) -"""A list of allowed X2go SSH proxy parameters.""" +"""A list of allowed X2Go terminal session parameters.""" +_X2GO_SSHPROXY_PARAMS = ('sshproxy_host', 'sshproxy_port', 'sshproxy_user', 'sshproxy_password', + 'sshproxy_key_filename', 'sshproxy_pkey', 'sshproxy_passphrase', + 'sshproxy_look_for_keys', 'sshproxy_allow_agent', + 'sshproxy_tunnel', + ) +"""A list of allowed X2Go SSH proxy parameters.""" -class X2goSession(object): +class X2GoSession(object): """\ - Public API class for launching X2go sessions. Recommended is to manage X2go sessions from - within an L{X2goClient} instance. However, Python X2go is designed in a way that it also - allows the management of singel L{X2goSession} instance. + Public API class for launching X2Go sessions. Recommended is to manage X2Go sessions from + within an L{X2GoClient} instance. However, Python X2Go is designed in a way that it also + allows the management of singel L{X2GoSession} instance. - Thus, you can use the L{X2goSession} class to manually set up X2go sessions without - L{X2goClient} context (session registry, session list cache, auto-registration of new + Thus, you can use the L{X2GoSession} class to manually set up X2Go sessions without + L{X2GoClient} context (session registry, session list cache, auto-registration of new sessions etc.). """ - def __init__(self, server=None, control_session=None, + def __init__(self, server=None, port=22, control_session=None, use_sshproxy=False, + sshproxy_reuse_authinfo=False, profile_id=None, profile_name='UNKNOWN', session_name=None, + auto_start_or_resume=False, + auto_connect=False, printing=False, allow_mimebox=False, mimebox_extensions=[], mimebox_action='OPEN', allow_share_local_folders=False, share_local_folders=[], - control_backend=_X2goControlSession, - terminal_backend=_X2goTerminalSession, - info_backend=_X2goServerSessionInfo, - list_backend=_X2goServerSessionList, - proxy_backend=_X2goProxy, - settings_backend=_X2goClientSettings, - printing_backend=_X2goClientPrinting, + restore_shared_local_folders=False, + control_backend=_BACKENDS['X2GoControlSession']['default'], + terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], + info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], + list_backend=_BACKENDS['X2GoServerSessionList']['default'], + proxy_backend=_BACKENDS['X2GoProxy']['default'], + settings_backend=_BACKENDS['X2GoClientSettings']['default'], + printing_backend=_BACKENDS['X2GoClientPrinting']['default'], client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), ssh_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR), keep_controlsession_alive=False, add_to_known_hosts=False, known_hosts=None, + forward_sshagent=False, logger=None, loglevel=log.loglevel_DEFAULT, - connected=False, virgin=True, running=None, suspended=None, terminated=None, faulty=None, + connected=False, activated=False, virgin=True, running=None, suspended=None, terminated=None, faulty=None, client_instance=None, **params): """\ - @param server: hostname of X2go server + @param server: hostname of X2Go server @type server: C{str} - @param control_session: an already initialized C{X2goControlSession*} instance - @type control_session: C{X2goControlSession*} instance - @param use_sshproxy: for communication with X2go server use an SSH proxy host + @param control_session: an already initialized C{X2GoControlSession*} instance + @type control_session: C{X2GoControlSession*} instance + @param use_sshproxy: for communication with X2Go server use an SSH proxy host @type use_sshproxy: C{bool} + @param sshproxy_reuse_authinfo: for proxy authentication re-use the X2Go session's password / key file + @type sshproxy_reuse_authinfo: C{bool} @param profile_id: profile ID @type profile_id: C{str} @param profile_name: profile name @type profile_name: C{str} @param session_name: session name (if available) @type session_name: C{str} - @param printing: enable X2go printing + @param auto_start_or_resume: automatically start a new or resume latest session after connect + @type auto_start_or_resume: C{bool} + @param auto_connect: call a hook method that handles connecting the session profile automatically after a session for this profile has been registered + @type auto_connect: C{bool} + @param printing: enable X2Go printing @type printing: C{bool} - @param allow_mimebox: enable X2go MIME box support + @param allow_mimebox: enable X2Go MIME box support @type allow_mimebox: C{bool} - @param mimebox_extensions: whitelist of allowed X2go MIME box extensions + @param mimebox_extensions: whitelist of allowed X2Go MIME box extensions @type mimebox_extensions: C{list} - @param mimebox_action: action for incoming X2go MIME box files - @type mimebox_action: C{X2goMimeBoxAction*} or C{str} + @param mimebox_action: action for incoming X2Go MIME box files + @type mimebox_action: C{X2GoMimeBoxAction*} or C{str} @param allow_share_local_folders: enable local folder sharing support @type allow_share_local_folders: C{bool} - @param share_local_folders: list of local folders to share with the remote X2go session + @param share_local_folders: list of local folders to share with the remote X2Go session @type share_local_folders: C{list} - @param control_backend: X2go control session backend to use - @type control_backend: C{class} - @param terminal_backend: X2go terminal session backend to use - @type terminal_backend: C{class} - @param info_backend: X2go session info backend to use - @type info_backend: C{class} - @param list_backend: X2go session list backend to use - @type list_backend: C{class} - @param proxy_backend: X2go proxy backend to use - @type proxy_backend: C{class} - @param settings_backend: X2go client settings backend to use - @type settings_backend: C{class} - @param printing_backend: X2go client printing backend to use - @type printing_backend: C{class} + @param restore_shared_local_folders: store actual list of shared local folders after session has been suspended or terminated + @type restore_shared_local_folders: C{bool} + @param control_backend: X2Go control session backend to use + @type control_backend: C{str} + @param terminal_backend: X2Go terminal session backend to use + @type terminal_backend: C{str} + @param info_backend: X2Go session info backend to use + @type info_backend: C{str} + @param list_backend: X2Go session list backend to use + @type list_backend: C{str} + @param proxy_backend: X2Go proxy backend to use + @type proxy_backend: C{str} + @param settings_backend: X2Go client settings backend to use + @type settings_backend: C{str} + @param printing_backend: X2Go client printing backend to use + @type printing_backend: C{str} @param client_rootdir: client base dir (default: ~/.x2goclient) @type client_rootdir: C{str} @param sessions_rootdir: sessions base dir (default: ~/.x2go) @type sessions_rootdir: C{str} @param ssh_rootdir: ssh base dir (default: ~/.ssh) @type ssh_rootdir: C{str} - @param keep_controlsession_alive: On last L{X2goSession.disconnect()} keep the associated C{X2goControlSession*} instance alive? + @param keep_controlsession_alive: On last L{X2GoSession.disconnect()} keep the associated C{X2GoControlSession*} instance alive? @ŧype keep_controlsession_alive: C{bool} @param add_to_known_hosts: Auto-accept server host validity? @type add_to_known_hosts: C{bool} @param known_hosts: the underlying Paramiko/SSH systems C{known_hosts} file @type known_hosts: C{str} + @param forward_sshagent: forward SSH agent authentication requests to the SSH agent on the X2Go client-side + @type forward_sshagent: C{bool} @param connected: manipulate session state »connected« by giving a pre-set value @type connected: C{bool} + @param activated: normal leave this untouched, an activated session is a session that is about to be used + @type activated: C{bool} @param virgin: manipulate session state »virgin« by giving a pre-set value @type virgin: C{bool} @param running: manipulate session state »running« by giving a pre-set value @@ -181,14 +236,14 @@ @type terminated: C{bool} @param faulty: manipulate session state »faulty« by giving a pre-set value @type faulty: C{bool} - @param client_instance: if available, the underlying L{X2goClient} instance - @type client_instance: C{X2goClient} instance + @param client_instance: if available, the underlying L{X2GoClient} instance + @type client_instance: C{X2GoClient} instance @param params: further control session, terminal session and SSH proxy class options @type params: C{dict} """ if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) + self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ @@ -198,6 +253,7 @@ self.uuid = uuid.uuid1() self.connected = connected + self.activated = activated self.virgin = virgin self.running = running self.suspended = suspended @@ -209,36 +265,57 @@ self.profile_name = profile_name self.session_name = session_name self.server = server + self.port = port self._last_status = None - self.locked = False - + self.auto_start_or_resume = auto_start_or_resume + self.auto_connect = auto_connect self.printing = printing self.allow_share_local_folders = allow_share_local_folders self.share_local_folders = share_local_folders + self.restore_shared_local_folders = restore_shared_local_folders self.allow_mimebox = allow_mimebox self.mimebox_extensions = mimebox_extensions self.mimebox_action = mimebox_action - self.control_backend = control_backend - self.terminal_backend = terminal_backend - self.info_backend = info_backend - self.list_backend = list_backend - self.proxy_backend = proxy_backend - self.settings_backend = settings_backend - self.printing_backend = printing_backend + self.control_backend = utils._get_backend_class(control_backend, "X2GoControlSession") + self.terminal_backend = utils._get_backend_class(terminal_backend, "X2GoTerminalSession") + self.info_backend = utils._get_backend_class(info_backend, "X2GoServerSessionInfo") + self.list_backend = utils._get_backend_class(list_backend, "X2GoServerSessionList") + self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") + self.settings_backend = utils._get_backend_class(settings_backend, "X2GoClientSettings") + self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") self.client_rootdir = client_rootdir self.sessions_rootdir = sessions_rootdir self.ssh_rootdir = ssh_rootdir self.control_session = control_session + if params.has_key('published_applications'): + self.published_applications = params['published_applications'] + if self.published_applications: + params['cmd'] = 'PUBLISHED' + else: + self.published_applications = params['published_applications'] = False + + if params.has_key('cmd') and params['cmd'] != 'PUBLISHED': + self.published_applications = params['published_applications'] = False + self.published_applications_menu = None + + if self.session_name: + if not re.match('.*_stRPUBLISHED_.*',self.session_name): + self.published_applications = params['published_applications'] = False + + self.use_sshproxy = use_sshproxy + self.sshproxy_reuse_authinfo = sshproxy_reuse_authinfo + self.control_params = {} self.terminal_params = {} self.sshproxy_params = {} self.update_params(params) - self.shared_folders = [] + self.shared_folders = {} self.session_environment = {} + self.server_features = [] try: del self.control_params['server'] except: pass @@ -246,19 +323,19 @@ self.client_instance = client_instance if self.logger.get_loglevel() & log.loglevel_DEBUG: - self.logger('X2go control session parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) - for p in self.control_params: + self.logger('X2Go control session parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) + for p in [ _p for _p in self.control_params if not _p.endswith('pkey') ]: self.logger(' %s: %s' % (p, self.control_params[p]), log.loglevel_DEBUG) - self.logger('X2go terminal session parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) + self.logger('X2Go terminal session parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) for p in self.terminal_params: self.logger(' %s: %s' % (p,self.terminal_params[p]), log.loglevel_DEBUG) - self.logger('X2go sshproxy parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) + self.logger('X2Go sshproxy parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) for p in self.sshproxy_params: self.logger(' %s: %s' % (p,self.sshproxy_params[p]), loglevel=log.loglevel_DEBUG) self.add_to_known_hosts = add_to_known_hosts self.known_hosts = known_hosts - self.use_sshproxy = use_sshproxy + self.forward_sshagent = forward_sshagent self._current_status = { 'timestamp': time.time(), @@ -274,11 +351,117 @@ self._SUPPORTED_SOUND = SUPPORTED_SOUND self._SUPPORTED_PRINTING = SUPPORTED_PRINTING self._SUPPORTED_MIMEBOX = SUPPORTED_MIMEBOX + self._SUPPORTED_TELEKINESIS = SUPPORTED_TELEKINESIS self._SUPPORTED_FOLDERSHARING = SUPPORTED_FOLDERSHARING + self.master_session = None self.init_control_session() self.terminal_session = None + if self.is_connected(): + self.retrieve_server_features() + + self._progress_status = 0 + self._lock = threading.Lock() + + self._restore_exported_folders = {} + if self.client_instance and self.restore_shared_local_folders: + self._restore_exported_folders = self.client_instance.get_profile_config(self.profile_name, 'export') + + def __str__(self): + return self.__get_uuid() + + def __repr__(self): + result = 'X2GoSession(' + for p in dir(self): + if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue + result += p + '=' + str(self.__dict__[p]) + ',' + result = result.strip(',') + return result + ')' + + def __call__(self): + return self.__get_uuid() + + def __del__(self): + """\ + Class destructor. + + """ + if self.has_control_session() and self.has_terminal_session(): + self.get_control_session().dissociate(self.get_terminal_session()) + + if self.has_control_session(): + if self.keep_controlsession_alive: + # regenerate this session instance for re-usage if this is the last session for a certain session profile + # and keep_controlsession_alive is set to True... + self.virgin = True + self.activated = False + self.connected = self.is_connected() + self.running = None + self.suspended = None + self.terminated = None + self._current_status = { + 'timestamp': time.time(), + 'server': self.server, + 'virgin': self.virgin, + 'connected': self.connected, + 'running': self.running, + 'suspended': self.suspended, + 'terminated': self.terminated, + 'faulty': self.faulty, + } + self._last_status = None + self.session_name = None + + else: + self.get_control_session().__del__() + self.control_session = None + + if self.has_terminal_session(): + self.get_terminal_session().__del__() + self.terminal_session = None + + def get_client_instance(self): + """\ + Return parent L{X2GoClient} instance if avaiable. + + return: L{X2GoClient} instance this session is associated with + rtype: C{obj} + + """ + return self.client_instance + __get_client_instance = get_client_instance + + def HOOK_on_control_session_death(self): + """\ + HOOK method: called if a control session (server connection) has unexpectedly encountered a failure. + + """ + if self.client_instance: + self.client_instance.HOOK_on_control_session_death(profile_name=self.profile_name) + else: + self.logger('HOOK_on_control_session_death: the control session of profile %s has died unexpectedly' % self.profile_name, loglevel=log.loglevel_WARN) + + def HOOK_on_failing_SFTP_client(self): + """\ + HOOK method: called SFTP client support is unavailable for the session. + + """ + if self.client_instance: + self.client_instance.HOOK_on_failing_SFTP_client(profile_name=self.profile_name) + else: + self.logger('HOOK_on_failing_SFTP_client: new session for profile: %s will lack SFTP client support. Check your server setup. Avoid echoing ~/.bashrc files on server.' % self.profile_name, loglevel=log.loglevel_ERROR) + + def HOOK_auto_connect(self): + """\ + HOOK method: called if the session demands to auto connect. + + """ + if self.client_instance: + self.client_instance.HOOK_profile_auto_connect(profile_name=self.profile_name) + else: + self.logger('HOOK_auto_connect: profile ,,%s\'\' wants to auto-connect to the X2Go server.' % self.profile_name, loglevel=log.loglevel_WARN) + def HOOK_session_startup_failed(self): """\ HOOK method: called if the startup of a session failed. @@ -287,7 +470,37 @@ if self.client_instance: self.client_instance.HOOK_session_startup_failed(profile_name=self.profile_name) else: - self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s'' failed.' % self.profile_name, loglevel=log.loglevel_WARN) + self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s\'\' failed.' % self.profile_name, loglevel=log.loglevel_WARN) + + def HOOK_desktop_sharing_denied(self): + """\ + HOOK method: called if the startup of a shadow session was denied by the other user. + + """ + if self.client_instance: + self.client_instance.HOOK_desktop_sharing_denied(profile_name=self.profile_name) + else: + self.logger('HOOK_desktop_sharing_denied: desktop sharing for session profile ,,%s\'\' was denied by the other user.' % self.profile_name, loglevel=log.loglevel_WARN) + + def HOOK_list_desktops_timeout(self): + """\ + HOOK method: called if the x2golistdesktops command generates a timeout due to long execution time. + + """ + if self.client_instance: + self.client_instance.HOOK_list_desktops_timeout(profile_name=self.profile_name) + else: + self.logger('HOOK_list_desktops_timeout: the server-side x2golistdesktops command for session profile %s took too long to return results. This can happen from time to time, please try again.' % self.profile_name, loglevel=log.loglevel_WARN) + + def HOOK_no_such_desktop(self, desktop='UNKNOWN'): + """\ + HOOK method: called if it is tried to connect to a shared desktop that's not available (anymore). + + """ + if self.client_instance: + self.client_instance.HOOK_no_such_desktop(profile_name=self.profile_name, desktop=desktop) + else: + self.logger('HOOK_no_such_desktop: the desktop %s (via session profile %s) is not available for sharing (anymore).' % (desktop, self.profile_name), loglevel=log.loglevel_WARN) def HOOK_rforward_request_denied(self, server_port=0): """\ @@ -302,7 +515,7 @@ else: self.logger('HOOK_rforward_request_denied: TCP port (reverse) forwarding request for session %s to server port %s has been denied by server %s. This is a common issue with SSH, it might help to restart the server\'s SSH daemon.' % (self.session_name, server_port, self.profile_name), loglevel=log.loglevel_WARN) - def HOOK_forwarding_tunnel_setup_failed(self, chain_host='UNKNOWN', chain_port=0): + def HOOK_forwarding_tunnel_setup_failed(self, chain_host='UNKNOWN', chain_port=0, subsystem=None): """\ HOOK method: called if a port forwarding tunnel setup failed. @@ -310,20 +523,64 @@ @type chain_host: C{str} @param chain_port: port of chain host (forwarding tunnel end point) @type chain_port: C{str} + @param subsystem: information on the subsystem that provoked this hook call + @type subsystem: C{str} + + """ + if type(subsystem) in (types.StringType, types.UnicodeType): + _subsystem = '(%s) ' % subsystem + else: + _subsystem = '' + + if subsystem.endswith('Proxy'): + self.faulty = True + + if self.client_instance: + self.client_instance.HOOK_forwarding_tunnel_setup_failed(profile_name=self.profile_name, session_name=self.session_name, chain_host=chain_host, chain_port=chain_port, subsystem=subsystem) + else: + self.logger('HOOK_forwarding_tunnel_setup_failed: Forwarding tunnel request to [%s]:%s for session %s (%s) was denied by remote X2Go/SSH server. Subsystem (%s) startup failed.' % (chain_host, chain_port, self.session_name, self.profile_name, _subsystem), loglevel=log.loglevel_WARN) + + def HOOK_printing_not_available(self): + """\ + HOOK method: called if X2Go client-side printing is not available. + + """ + if self.client_instance: + self.client_instance.HOOK_printing_not_available(profile_name=self.profile_name, session_name=self.session_name) + else: + self.logger('HOOK_printing_not_available: X2Go\'s client-side printing feature is not available with this session (%s) of profile %s.' % (self.session_name, self.profile_name), loglevel=log.loglevel_WARN) + + def HOOK_mimebox_not_available(self): + """\ + HOOK method: called if the X2Go MIME box is not available. """ - # mark session as faulty - self.faulty = True + if self.client_instance: + self.client_instance.HOOK_mimebox_not_available(profile_name=self.profile_name, session_name=self.session_name) + else: + self.logger('HOOK_mimebox_not_available: X2Go\'s MIME box feature is not available with this session (%s) of profile %s.' % (self.session_name, self.profile_name), loglevel=log.loglevel_WARN) + + def HOOK_foldersharing_not_available(self): + """\ + HOOK method: called if X2Go client-side folder-sharing is not available. + """ if self.client_instance: - self.client_instance.HOOK_forwarding_tunnel_setup_failed(profile_name=self.profile_name, session_name=self.session_name, chain_host=chain_host, chain_port=chain_port) + self.client_instance.HOOK_foldersharing_not_available(profile_name=self.profile_name, session_name=self.session_name) else: - self.logger('HOOK_forwarding_tunnel_setup_failed: Forwarding tunnel request to [%s]:%s for session %s (%s) was denied by remote X2go/SSH server. Session startup failed.' % (chain_host, chain_port, self.session_name, self.profile_name), loglevel=log.loglevel_WARN) + self.logger('HOOK_foldersharing_not_available: X2Go\'s client-side folder sharing feature is not available with this session (%s) of profile %s.' % (self.session_name, self.profile_name), loglevel=log.loglevel_WARN) + + def HOOK_sshfs_not_available(self): + """\ + HOOK method: called if the X2Go server denies SSHFS access. - # get rid of the faulty session... - self.terminate() + """ + if self.client_instance: + self.client_instance.HOOK_sshfs_not_available(profile_name=self.profile_name, session_name=self.session_name) + else: + self.logger('HOOK_sshfs_not_available: the remote X2Go server (%s) denies SSHFS access for session %s. This will result in client-side folder sharing, printing and the MIME box feature being unavailable' % (self.profile_name, self.session_name), loglevel=log.loglevel_WARN) - def HOOK_check_host_dialog(self, host, port, fingerprint='no fingerprint', fingerprint_type='RSA'): + def HOOK_check_host_dialog(self, host, port, fingerprint='no fingerprint', fingerprint_type='UNKNOWN'): """\ HOOK method: called if a host check is requested. This hook has to either return C{True} (default) or C{False}. @@ -342,19 +599,22 @@ if self.client_instance: return self.client_instance.HOOK_check_host_dialog(profile_name=self.profile_name, host=host, port=port, fingerprint=fingerprint, fingerprint_type=fingerprint_type) else: - self.logger('HOOK_check_host_dialog: host check requested for [%s]:%s with %s fingerprint: ,,%s.\'\'. Automatically adding host as known host.' % (host, port, fingerprint_type, fingerprint), loglevel=log.loglevel_WARN) + self.logger('HOOK_check_host_dialog: host check requested for [%s]:%s with %s fingerprint: ,,%s\'\'. Automatically adding host as known host.' % (host, port, fingerprint_type, fingerprint), loglevel=log.loglevel_WARN) return True def init_control_session(self): """\ - Initialize a new control session (C{X2goControlSession*}). + Initialize a new control session (C{X2GoControlSession*}). """ + low_latency = self.terminal_params.has_key('link') and self.terminal_params['link'].lower() in ('modem', 'isdn') + if self.control_session is None: - self.logger('initializing X2goControlSession', loglevel=log.loglevel_DEBUG) + self.logger('initializing X2GoControlSession', loglevel=log.loglevel_DEBUG) self.control_session = self.control_backend(profile_name=self.profile_name, add_to_known_hosts=self.add_to_known_hosts, known_hosts=self.known_hosts, + forward_sshagent=self.forward_sshagent, terminal_backend=self.terminal_backend, info_backend=self.info_backend, list_backend=self.list_backend, @@ -362,21 +622,102 @@ client_rootdir=self.client_rootdir, sessions_rootdir=self.sessions_rootdir, ssh_rootdir=self.ssh_rootdir, + low_latency=low_latency, logger=self.logger) + else: + self.control_session.low_latency = low_latency + __init_control_session = init_control_session + + def is_master_session(self): + """\ + Is this session a/the master session of sessions. + + The master session is the session has been launched first for a specific connection, + it also is _the_ session that controls the client-side shared folders. + + If this L{X2GoSession} instance is a standalone instance (without parent L{X2GoClient}) + this method will always return C{True}. + + @return: returns C{True} if this session is a master session + @rtype: C{bool} + + """ + if self.master_session is None and self.client_instance is None: + return True + return bool(self.master_session) + __is_master_session = is_master_session + + def set_master_session(self, wait=0, max_wait=20): + """\ + Declare this as a master session of a connection channel. + + This method gets called by the L{X2GoSessionRegistry} while sessions are starting or resuming and it relies on + an already set-up terminal session. + + @param wait: wait for seconds before sharing local folders via the new master session + of the corresponding session profile. + @type wait: C{int} + @param max_wait: wait for seconds for the terminal session to appear + @type max_wait: C{int} + + """ + self.logger('Using session %s as master session for profile %s.' % (self.get_session_name(), self.get_profile_name()), loglevel=log.loglevel_NOTICE) + self.master_session = True + + # retrieve an up-to-date list of sharable local folders from the client instance + if self.client_instance: + _exports = self.client_instance.get_profile_config(self.profile_name, 'export') + self.share_local_folders = [ sf for sf in _exports.keys() if _exports[sf] ] + + i = 0 + while i < max_wait: + i += 1 + if self.has_terminal_session(): + break + gevent.sleep(1) + + if wait: + gevent.spawn_later(wait, self.share_all_local_folders, update_exported_folders=False) + else: + gevent.spawn(self.share_all_local_folders, update_exported_folders=False) + __set_master_session = set_master_session + + def unset_master_session(self): + """\ + Declare this as a non-master session of a connection channel. + + """ + # unmount shared folders + if self.has_terminal_session(): + self.unshare_all_local_folders(update_exported_folders=False) + self.master_session = False + __unset_master_session = unset_master_session def set_server(self, server): """\ - Modify server name after L{X2goSession} has already been initialized. + Modify server name after L{X2GoSession} has already been initialized. @param server: new server name @type server: C{str} """ self.server = server + __set_server = set_server + + def set_port(self, port): + """\ + Modify server port after L{X2GoSession} has already been initialized. + + @param port: socket port of server to connect to + @type port: C{int} + + """ + self.port = port + __set_port = set_port def set_profile_name(self, profile_name): """\ - Modify session profile name after L{X2goSession} has already been initialized. + Modify session profile name after L{X2GoSession} has already been initialized. @param profile_name: new session profile name @type profile_name: C{str} @@ -384,84 +725,57 @@ """ self.profile_name = profile_name self.control_session.set_profile_name(profile_name) + __set_profile_name = set_profile_name - def __str__(self): - return self.__get_uuid() - - def __repr__(self): - result = 'X2goSession(' - for p in dir(self): - if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue - result += p + '=' + str(self.__dict__[p]) + ', ' - return result + ')' - - def __call__(self): - return self.__get_uuid() - - def __del__(self): + def get_session_profile_option(self, option): """\ - Class destructor. + Retrieve a specific profile parameter for this session. - """ - if self.has_control_session() and self.has_terminal_session(): - self.get_control_session().dissociate(self.get_terminal_session()) + @param option: name of a specific profile option to be queried. + @type option: C{str} - if self.has_control_session(): - if self.keep_controlsession_alive: - # regenerate this session instance for re-usage if this is the last session for a certain session profile - # and keep_controlsession_alive is set to True... - self.virgin = True - self.connected = self.is_connected() - self.running = None - self.suspended = None - self.terminated = None - self._current_status = { - 'timestamp': time.time(), - 'server': self.server, - 'virgin': self.virgin, - 'connected': self.connected, - 'running': self.running, - 'suspended': self.suspended, - 'terminated': self.terminated, - 'faulty': self.faulty, - } - self._last_status = None - self.session_name = None + @return: value for profile option C{