=== removed directory '.bzr-builddeb' === removed file '.bzr-builddeb/default.conf' --- .bzr-builddeb/default.conf 2008-09-17 00:34:09 +0000 +++ .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000 @@ -1,2 +0,0 @@ -[BUILDDEB] -split = True === removed file '.bzrignore' --- .bzrignore 2019-07-27 10:11:45 +0000 +++ .bzrignore 1970-01-01 00:00:00 +0000 @@ -1,17 +0,0 @@ -*.5 -*.8 -*.8mandos -confdir -keydir -statedir -man -plugin-runner -plugins.d/askpass-fifo -plugins.d/mandos-client -plugins.d/password-prompt -plugins.d/splashy -plugins.d/usplash -plugins.d/plymouth -plugin-helpers/mandos-client-iprouteadddel -dracut-module/password-agent -.tramp_history === removed file 'COPYING' --- COPYING 2008-08-15 20:17:32 +0000 +++ COPYING 1970-01-01 00:00:00 +0000 @@ -1,676 +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 -. - === removed file 'DBUS-API' --- DBUS-API 2019-02-10 04:20:26 +0000 +++ DBUS-API 1970-01-01 00:00:00 +0000 @@ -1,155 +0,0 @@ - -*- mode: org; coding: utf-8 -*- - - Mandos Server D-Bus Interface - -This file documents the D-Bus interface to the Mandos server. - -* Bus: System bus - Bus name: "se.recompile.Mandos" - - -* Object Paths: - - | Path | Object | - |-----------------------+-------------------| - | "/" | The Mandos Server | - - (To get a list of paths to client objects, use the standard D-Bus - org.freedesktop.DBus.ObjectManager interface, which the server - object supports.) - - -* Mandos Server Interface: - Interface name: "se.recompile.Mandos" - -** Methods: -*** RemoveClient(o: ObjectPath) → nothing - Removes a client - -** Signals: -*** ClientNotFound(s: KeyID, s: Address) - A client connected from Address using KeyID, but was - rejected because it was not found in the server. The key ID - is represented as a string of hexadecimal digits. The address is - an IPv4 or IPv6 address in its normal string format. - - -* Mandos Client Interface: - Interface name: "se.recompile.Mandos.Client" - -** Methods -*** Approve(b: Approve) → nothing - Approve or deny a connected client waiting for approval. If - denied, a client will not be sent its secret. - -*** CheckedOK() → nothing - Assert that this client has been checked and found to be alive. - This will restart the timeout before disabling this client. See - also the "LastCheckedOK" property. - -** Properties - - Note: Many of these properties directly correspond to a setting in - "clients.conf", in which case they are fully documented in - mandos-clients.conf(5). - - | Name | Type | Access | clients.conf | - |-------------------------+------+------------+---------------------| - | ApprovedByDefault | b | Read/Write | approved_by_default | - | ApprovalDelay (a) | t | Read/Write | approval_delay | - | ApprovalDuration (a) | t | Read/Write | approval_duration | - | ApprovalPending (b) | b | Read | N/A | - | Checker | s | Read/Write | checker | - | CheckerRunning (c) | b | Read/Write | N/A | - | Created (d) | s | Read | N/A | - | Enabled (e) | b | Read/Write | N/A | - | Expires (f) | s | Read | N/A | - | ExtendedTimeout (a) | t | Read/Write | extended_timeout | - | Fingerprint | s | Read | fingerprint | - | KeyID | s | Read | key_id | - | Host | s | Read/Write | host | - | Interval (a) | t | Read/Write | interval | - | LastApprovalRequest (g) | s | Read | N/A | - | LastCheckedOK (h) | s | Read/Write | N/A | - | LastCheckerStatus (i) | n | Read | N/A | - | LastEnabled (j) | s | Read | N/A | - | Name | s | Read | (Section name) | - | Secret (k) | ay | Write | secret (or secfile) | - | Timeout (a) | t | Read/Write | timeout | - - a) Represented as milliseconds. - - b) An approval is currently pending. - - c) Changing this property can either start a new checker or abort a - running one. - - d) The creation time of this client object, as an RFC 3339 string. - - e) Changing this property enables or disables a client. - - f) The date and time this client will be disabled, as an RFC 3339 - string, or an empty string if this is not scheduled. - - g) The date and time of the last approval request, as an RFC 3339 - string, or an empty string if this has not happened. - - h) The date and time a checker was last successful, as an RFC 3339 - string, or an empty string if this has not happened. Setting - this property is equivalent to calling CheckedOK(), i.e. the - current time is set, regardless of the string sent. Please - always use an empty string when setting this property, to allow - for possible future expansion. - - i) The exit status of the last checker, -1 if it did not exit - cleanly, -2 if a checker has not yet returned. - - j) The date and time this client was last enabled, as an RFC 3339 - string, or an empty string if this has not happened. - - k) A raw byte array, not hexadecimal digits. - -** Signals -*** CheckerCompleted(n: Exitcode, x: Waitstatus, s: Command) - A checker (Command) has completed. Exitcode is either the exit - code or -1 for abnormal exit. In any case, the full Waitstatus - (as from wait(2)) is also available. - -*** CheckerStarted(s: Command) - A checker command (Command) has just been started. - -*** GotSecret() - This client has been sent its secret. - -*** NeedApproval(t: Timeout, b: ApprovedByDefault) - This client will be approved or denied in exactly Timeout - milliseconds, depending on ApprovedByDefault. Approve() can now - usefully be called on this client object. - -*** Rejected(s: Reason) - This client was not given its secret for a specified Reason. - -* Copyright - - Copyright © 2010-2019 Teddy Hogeborn - Copyright © 2010-2019 Björn Påhlsson - -** License: - - This file is part of Mandos. - - Mandos 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. - - Mandos 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 Mandos. If not, see . - - -#+STARTUP: showall === removed file 'INSTALL' --- INSTALL 2019-11-03 19:17:57 +0000 +++ INSTALL 1970-01-01 00:00:00 +0000 @@ -1,157 +0,0 @@ --*- org -*- - -* Prerequisites - -** Operating System - - Debian 8.0 "jessie" or Ubuntu 15.10 "Wily Werewolf" (or later). - - This is mostly for the support scripts which make sure that the - client is installed and started in the initial RAM disk environment - and that the initial RAM file system image file is automatically - made unreadable. The server and client programs themselves *could* - be run in other distributions, but they *are* specific to GNU/Linux - systems, and are not written with portabillity to other Unixes in - mind. - -** Libraries - - The following libraries and packages are needed. (It is possible - that it might work with older versions of some of these, but these - versions are confirmed to work. Newer versions are almost - certainly OK.) - -*** Documentation - These are required to build the manual pages for both the server - and client: - - + DocBook 4.5 http://www.docbook.org/ - Note: DocBook 5.0 is not compatible. - + DocBook XSL stylesheets 1.71.0 - http://wiki.docbook.org/DocBookXslStylesheets - - Package names: - docbook docbook-xsl - - To build just the documentation, run the command "make doc". Then - the manual page "mandos.8", for example, can be read by running - "man -l mandos.8". - -*** Mandos Server - + GnuTLS 3.3 https://www.gnutls.org/ - (but not 3.6.0 or later, until 3.6.6, which works) - + Avahi 0.6.16 https://www.avahi.org/ - + Python 3 https://www.python.org/ - Note: Python 2.7 is still supported, if the "mandos", - "mandos-ctl", and "mandos-monitor" files are edited to contain - "#!/usr/bin/python" instead of python3. - + dbus-python 0.82.4 https://dbus.freedesktop.org/doc/dbus-python/ - + PyGObject 3.8 https://wiki.gnome.org/Projects/PyGObject - + pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ - + Urwid 1.0.1 http://urwid.org/ - (Only needed by the "mandos-monitor" tool.) - - Strongly recommended: - + fping 2.4b2-to-ipv6 http://www.fping.org/ - + ssh-keyscan from OpenSSH http://www.openssh.com/ - - Package names: - avahi-daemon python3 python3-dbus python3-gi python3-urwid - pkg-config fping ssh-client - -*** Mandos Client - + GNU C Library 2.17 https://gnu.org/software/libc/ - + GnuTLS 3.3 https://www.gnutls.org/ - (but not 3.6.0 or later, until 3.6.6 which works) - + Avahi 0.6.16 https://www.avahi.org/ - + GnuPG 1.4.9 https://www.gnupg.org/ - + GPGME 1.1.6 https://www.gnupg.org/related_software/gpgme/ - + pkg-config https://www.freedesktop.org/wiki/Software/pkg-config/ - + libnl-route 3 https://www.infradead.org/~tgr/libnl/ - + GLib 2.40 http://www.gtk.org/ - - One of: - + initramfs-tools 0.85i - https://tracker.debian.org/pkg/initramfs-tools - + dracut 044+241 - http://www.kernel.org/pub/linux/utils/boot/dracut/dracut.html - - Strongly recommended: - + OpenSSH http://www.openssh.com/ - - Package names: - initramfs-tools dracut libgnutls-dev gnutls-bin libavahi-core-dev - gnupg libgpgme11-dev pkg-config ssh libnl-route-3-dev - libglib2.0-dev - -* Installing the Mandos server - - 1. Do "make doc". - - 2. On the computer to run as a Mandos server, run the following - command: - For Debian: su - -c 'make install-server' - For Ubuntu: sudo make install-server - - (This creates a configuration without any clients configured; you - need an actually configured client to do that; see below.) - -* Installing the Mandos client. - - 1. Do "make all doc". - - 2. On the computer to run as a Mandos client, run the following - command: - For Debian: su - -c 'make install-client' - For Ubuntu: sudo make install-client - - This will also create an OpenPGP key, which will take some time - and entropy, so be patient. - - 3. Run the following command: - For Debian: su - -c 'mandos-keygen --password' - For Ubuntu: sudo mandos-keygen --password - - When prompted, enter the password/passphrase for the encrypted - root file system on this client computer. The command will - output a section of text, starting with a [section header]. Copy - and append this to the file "/etc/mandos/clients.conf" *on the - server computer*. - - 4. Configure the client to use any special configuration needed for - your local system. Note: This is not necessary if the server is - present on the same wired local network as the client. If you do - make changes to /etc/mandos/plugin-runner.conf, the initrd.img - file must be updated, possibly using the following command: - - # update-initramfs -k all -u - - 5. On the server computer, start the server by running the command - For Debian: su - -c 'invoke-rc.d mandos start' - For Ubuntu: sudo service mandos start - - At this point, it is possible to verify that the correct password - will be received by the client by running the command: - - # /usr/lib/mandos/plugins.d/mandos-client \ - --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt \ - --tls-privkey=/etc/keys/mandos/tls-privkey.pem \ - --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo - - This command should retrieve the password from the server, - decrypt it, and output it to standard output. - - After this, the client computer should be able to reboot without - needing a password entered on the console, as long as it does not - take more than five minutes to reboot. - -* Further customizations - - You may want to tighten or loosen the timeouts in the server - configuration files; see mandos.conf(5) and mandos-clients.conf(5). - If IPsec is not used and SSH is not installed, it is suggested that - a more cryptographically secure checker program is used and - configured, since, without IPsec, ping packets can be faked. - -#+STARTUP: showall === modified file 'Makefile' --- Makefile 2019-09-03 19:06:41 +0000 +++ Makefile 2008-07-29 03:35:39 +0000 @@ -1,605 +1,31 @@ -WARN:=-O -Wall -Wextra -Wdouble-promotion -Wformat=2 -Winit-self \ - -Wmissing-include-dirs -Wswitch-default -Wswitch-enum \ - -Wunused -Wuninitialized -Wstrict-overflow=5 \ - -Wsuggest-attribute=pure -Wsuggest-attribute=const \ - -Wsuggest-attribute=noreturn -Wfloat-equal -Wundef -Wshadow \ - -Wunsafe-loop-optimizations -Wpointer-arith \ - -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings \ - -Wconversion -Wlogical-op -Waggregate-return \ - -Wstrict-prototypes -Wold-style-definition \ - -Wmissing-format-attribute -Wnormalized=nfc -Wpacked \ - -Wredundant-decls -Wnested-externs -Winline -Wvla \ - -Wvolatile-register-var -Woverlength-strings - -#DEBUG:=-ggdb3 -fsanitize=address $(SANITIZE) -## Check which sanitizing options can be used -#SANITIZE:=$(foreach option,$(ALL_SANITIZE_OPTIONS),$(shell \ -# echo 'int main(){}' | $(CC) --language=c $(option) \ -# /dev/stdin -o /dev/null >/dev/null 2>&1 && echo $(option))) -# -ALL_SANITIZE_OPTIONS:=-fsanitize=leak -fsanitize=undefined \ - -fsanitize=shift -fsanitize=integer-divide-by-zero \ - -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null \ - -fsanitize=return -fsanitize=signed-integer-overflow \ - -fsanitize=bounds -fsanitize=alignment \ - -fsanitize=object-size -fsanitize=float-divide-by-zero \ - -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute \ - -fsanitize=returns-nonnull-attribute -fsanitize=bool \ - -fsanitize=enum -fsanitize-address-use-after-scope - -# For info about _FORTIFY_SOURCE, see feature_test_macros(7) -# and . -FORTIFY:=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC -LINK_FORTIFY_LD:=-z relro -z now -LINK_FORTIFY:= - -# If BROKEN_PIE is set, do not build with -pie -ifndef BROKEN_PIE -FORTIFY += -fPIE -LINK_FORTIFY += -pie -endif +WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs -Wswitch-default -Wswitch-enum -Wunused-parameter -Wstrict-aliasing=2 -Wextra -Wfloat-equal -Wundef -Wshadow -Wunsafe-loop-optimizations -Wpointer-arith -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wconversion -Wstrict-prototypes -Wold-style-definition -Wpacked -Wnested-externs -Wunreachable-code -Winline -Wvolatile-register-var +DEBUG=-ggdb3 +# For info about _FORTIFY_SOURCE, see +# +FORTIFY=-D_FORTIFY_SOURCE=2 # -fstack-protector-all #COVERAGE=--coverage -OPTIMIZE:=-Os -fno-strict-aliasing -LANGUAGE:=-std=gnu11 -FEATURES:=-D_FILE_OFFSET_BITS=64 -htmldir:=man -version:=1.8.9 -SED:=sed -PKG_CONFIG?=pkg-config - -USER:=$(firstword $(subst :, ,$(shell getent passwd _mandos \ - || getent passwd nobody || echo 65534))) -GROUP:=$(firstword $(subst :, ,$(shell getent group _mandos \ - || getent group nogroup || echo 65534))) - -LINUXVERSION:=$(shell uname --kernel-release) - -## Use these settings for a traditional /usr/local install -# PREFIX:=$(DESTDIR)/usr/local -# CONFDIR:=$(DESTDIR)/etc/mandos -# KEYDIR:=$(DESTDIR)/etc/mandos/keys -# MANDIR:=$(PREFIX)/man -# INITRAMFSTOOLS:=$(DESTDIR)/etc/initramfs-tools -# DRACUTMODULE:=$(DESTDIR)/usr/lib/dracut/modules.d/90mandos -# STATEDIR:=$(DESTDIR)/var/lib/mandos -# LIBDIR:=$(PREFIX)/lib -## - -## These settings are for a package-type install -PREFIX:=$(DESTDIR)/usr -CONFDIR:=$(DESTDIR)/etc/mandos -KEYDIR:=$(DESTDIR)/etc/keys/mandos -MANDIR:=$(PREFIX)/share/man -INITRAMFSTOOLS:=$(DESTDIR)/usr/share/initramfs-tools -DRACUTMODULE:=$(DESTDIR)/usr/lib/dracut/modules.d/90mandos -STATEDIR:=$(DESTDIR)/var/lib/mandos -LIBDIR:=$(shell \ - for d in \ - "/usr/lib/`dpkg-architecture \ - -qDEB_HOST_MULTIARCH 2>/dev/null`" \ - "`rpm --eval='%{_libdir}' 2>/dev/null`" /usr/lib; do \ - if [ -d "$$d" -a "$$d" = "$${d%/}" ]; then \ - echo "$(DESTDIR)$$d"; \ - break; \ - fi; \ - done) -## - -SYSTEMD:=$(DESTDIR)$(shell $(PKG_CONFIG) systemd \ - --variable=systemdsystemunitdir) -TMPFILES:=$(DESTDIR)$(shell $(PKG_CONFIG) systemd \ - --variable=tmpfilesdir) -SYSUSERS:=$(DESTDIR)$(shell $(PKG_CONFIG) systemd \ - --variable=sysusersdir) - -GNUTLS_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I gnutls) -GNUTLS_LIBS:=$(shell $(PKG_CONFIG) --libs gnutls) -AVAHI_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I avahi-core) -AVAHI_LIBS:=$(shell $(PKG_CONFIG) --libs avahi-core) -GPGME_CFLAGS:=$(shell gpgme-config --cflags; getconf LFS_CFLAGS) -GPGME_LIBS:=$(shell gpgme-config --libs; getconf LFS_LIBS; \ - getconf LFS_LDFLAGS) -LIBNL3_CFLAGS:=$(shell $(PKG_CONFIG) --cflags-only-I libnl-route-3.0) -LIBNL3_LIBS:=$(shell $(PKG_CONFIG) --libs libnl-route-3.0) -GLIB_CFLAGS:=$(shell $(PKG_CONFIG) --cflags glib-2.0) -GLIB_LIBS:=$(shell $(PKG_CONFIG) --libs glib-2.0) +OPTIMIZE=-Os +LANGUAGE=-std=gnu99 # Do not change these two -CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ - $(LANGUAGE) $(FEATURES) -DVERSION='"$(version)"' -LDFLAGS+=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(strip \ - ) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) - -# Commands to format a DocBook document into a manual page -DOCBOOKTOMAN=$(strip cd $(dir $<); xsltproc --nonet --xinclude \ - --param man.charmap.use.subset 0 \ - --param make.year.ranges 1 \ - --param make.single.year.ranges 1 \ - --param man.output.quietly 1 \ - --param man.authors.section.enabled 0 \ - /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ - $(notdir $<); \ - if locale --all 2>/dev/null | grep --regexp='^en_US\.utf8$$' \ - && command -v man >/dev/null; then LANG=en_US.UTF-8 \ - MANWIDTH=80 man --warnings --encoding=UTF-8 --local-file \ - $(notdir $@); fi >/dev/null) - -DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \ - --param make.year.ranges 1 \ - --param make.single.year.ranges 1 \ - --param man.output.quietly 1 \ - --param man.authors.section.enabled 0 \ - --param citerefentry.link 1 \ - --output $@ \ - /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \ - $<; $(HTMLPOST) $@) -# Fix citerefentry links -HTMLPOST:=$(SED) --in-place \ - --expression='s/\(\)\([^<]*\)\(<\/span>(\)\([^)]*\)\()<\/span><\/a>\)/\1\3.\5\2\3\4\5\6/g' - -PLUGINS:=plugins.d/password-prompt plugins.d/mandos-client \ - plugins.d/usplash plugins.d/splashy plugins.d/askpass-fifo \ - plugins.d/plymouth -PLUGIN_HELPERS:=plugin-helpers/mandos-client-iprouteadddel -CPROGS:=plugin-runner dracut-module/password-agent $(PLUGINS) \ - $(PLUGIN_HELPERS) -PROGS:=mandos mandos-keygen mandos-ctl mandos-monitor $(CPROGS) -DOCS:=mandos.8 mandos-keygen.8 mandos-monitor.8 mandos-ctl.8 \ - mandos.conf.5 mandos-clients.conf.5 plugin-runner.8mandos \ - dracut-module/password-agent.8mandos \ - plugins.d/mandos-client.8mandos \ - plugins.d/password-prompt.8mandos plugins.d/usplash.8mandos \ - plugins.d/splashy.8mandos plugins.d/askpass-fifo.8mandos \ - plugins.d/plymouth.8mandos intro.8mandos - -htmldocs:=$(addsuffix .xhtml,$(DOCS)) - -objects:=$(addsuffix .o,$(CPROGS)) - -all: $(PROGS) mandos.lsm - -doc: $(DOCS) - -html: $(htmldocs) - -%.5: %.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -%.5.xhtml: %.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - -%.8: %.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -%.8.xhtml: %.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - -%.8mandos: %.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -%.8mandos.xhtml: %.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - -intro.8mandos: intro.xml common.ent legalnotice.xml - $(DOCBOOKTOMAN) -intro.8mandos.xhtml: intro.xml common.ent legalnotice.xml - $(DOCBOOKTOHTML) - -mandos.8: mandos.xml common.ent mandos-options.xml overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos.8.xhtml: mandos.xml common.ent mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -mandos-keygen.8: mandos-keygen.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos-keygen.8.xhtml: mandos-keygen.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - -mandos-monitor.8: mandos-monitor.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos-monitor.8.xhtml: mandos-monitor.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - -mandos-ctl.8: mandos-ctl.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos-ctl.8.xhtml: mandos-ctl.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - -mandos.conf.5: mandos.conf.xml common.ent mandos-options.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -mandos.conf.5.xhtml: mandos.conf.xml common.ent mandos-options.xml \ - legalnotice.xml - $(DOCBOOKTOHTML) - -plugin-runner.8mandos: plugin-runner.xml common.ent overview.xml \ - legalnotice.xml - $(DOCBOOKTOMAN) -plugin-runner.8mandos.xhtml: plugin-runner.xml common.ent \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -dracut-module/password-agent.8mandos: \ - dracut-module/password-agent.xml common.ent \ - overview.xml legalnotice.xml - $(DOCBOOKTOMAN) -dracut-module/password-agent.8mandos.xhtml: \ - dracut-module/password-agent.xml common.ent \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -plugins.d/mandos-client.8mandos: plugins.d/mandos-client.xml \ - common.ent \ - mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOMAN) -plugins.d/mandos-client.8mandos.xhtml: plugins.d/mandos-client.xml \ - common.ent \ - mandos-options.xml \ - overview.xml legalnotice.xml - $(DOCBOOKTOHTML) - -# Update all these files with version number $(version) -common.ent: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\($$/\1$(version)">/' \ - $@) - -mandos: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@) - -mandos-keygen: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(VERSION="\)[^"]*"$$/\1$(version)"/' \ - $@) - -mandos-ctl: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@) - -mandos-monitor: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@) - -mandos.lsm: Makefile - $(strip $(SED) --in-place \ - --expression='s/^\(Version:\).*/\1\t$(version)/' \ - $@) - $(strip $(SED) --in-place \ - --expression='s/^\(Entered-date:\).*/\1\t$(shell date --rfc-3339=date --reference=Makefile)/' \ - $@) - $(strip $(SED) --in-place \ - --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \ - $@) - -# Need to add the GnuTLS, Avahi and GPGME libraries -plugins.d/mandos-client: plugins.d/mandos-client.c - $(LINK.c) $^ $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(strip\ - ) $(GPGME_CFLAGS) $(GNUTLS_LIBS) $(strip\ - ) $(AVAHI_LIBS) $(GPGME_LIBS) $(LOADLIBES) $(strip\ - ) $(LDLIBS) -o $@ - -# Need to add the libnl-route library -plugin-helpers/mandos-client-iprouteadddel: plugin-helpers/mandos-client-iprouteadddel.c - $(LINK.c) $(LIBNL3_CFLAGS) $^ $(LIBNL3_LIBS) $(strip\ - ) $(LOADLIBES) $(LDLIBS) -o $@ - -# Need to add the GLib and pthread libraries -dracut-module/password-agent: dracut-module/password-agent.c - $(LINK.c) $(GLIB_CFLAGS) $^ $(GLIB_LIBS) -lpthread $(strip\ - ) $(LOADLIBES) $(LDLIBS) -o $@ - -.PHONY : all doc html clean distclean mostlyclean maintainer-clean \ - check run-client run-server install install-html \ - install-server install-client-nokey install-client uninstall \ - uninstall-server uninstall-client purge purge-server \ - purge-client - +CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) $(LANGUAGE) +LDFLAGS=$(COVERAGE) + +PROGS=plugbasedclient plugins.d/mandosclient plugins.d/passprompt + +objects=$(shell for p in $(PROGS); do echo $${p}.o; done) + +all: $(PROGS) + +plugbasedclient: plugbasedclient.o + $(LINK.o) -lgnutls $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +plugins.d/mandosclient: plugins.d/mandosclient.o + $(LINK.o) -lgnutls -lavahi-core -lgpgme $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +plugins.d/passprompt: plugins.d/passprompt.o + $(LINK.o) $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +.PHONY : clean clean: - -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core - -distclean: clean -mostlyclean: clean -maintainer-clean: clean - -rm --force --recursive keydir confdir statedir - -check: all - ./mandos --check - ./mandos-ctl --check - ./mandos-keygen --version - ./plugin-runner --version - ./plugin-helpers/mandos-client-iprouteadddel --version - ./dracut-module/password-agent --test - -# Run the client with a local config and key -run-client: all keydir/seckey.txt keydir/pubkey.txt \ - keydir/tls-privkey.pem keydir/tls-pubkey.pem - @echo '######################################################' - @echo '# The following error messages are harmless and can #' - @echo '# be safely ignored: #' - @echo '## From plugin-runner: #' - @echo '# setgid: Operation not permitted #' - @echo '# setuid: Operation not permitted #' - @echo '## From askpass-fifo: #' - @echo '# mkfifo: Permission denied #' - @echo '## From mandos-client: #' - @echo '# Failed to raise privileges: Operation not permi... #' - @echo '# Warning: network hook "*" exited with status * #' - @echo '# ioctl SIOCSIFFLAGS +IFF_UP: Operation not permi... #' - @echo '# Failed to bring up interface "*": Operation not... #' - @echo '# #' - @echo '# (The messages are caused by not running as root, #' - @echo '# but you should NOT run "make run-client" as root #' - @echo '# unless you also unpacked and compiled Mandos as #' - @echo '# root, which is also NOT recommended.) #' - @echo '######################################################' -# We set GNOME_KEYRING_CONTROL to block pam_gnome_keyring - ./plugin-runner --plugin-dir=plugins.d \ - --plugin-helper-dir=plugin-helpers \ - --config-file=plugin-runner.conf \ - --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--tls-privkey=keydir/tls-privkey.pem,--tls-pubkey=keydir/tls-pubkey.pem,--network-hook-dir=network-hooks.d \ - --env-for=mandos-client:GNOME_KEYRING_CONTROL= \ - $(CLIENTARGS) - -# Used by run-client -keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem: mandos-keygen - install --directory keydir - ./mandos-keygen --dir keydir --force - -# Run the server with a local config -run-server: confdir/mandos.conf confdir/clients.conf statedir - ./mandos --debug --no-dbus --configdir=confdir \ - --statedir=statedir $(SERVERARGS) - -# Used by run-server -confdir/mandos.conf: mandos.conf - install --directory confdir - install --mode=u=rw,go=r $^ $@ -confdir/clients.conf: clients.conf keydir/seckey.txt keydir/tls-pubkey.pem - install --directory confdir - install --mode=u=rw $< $@ -# Add a client password - ./mandos-keygen --dir keydir --password --no-ssh >> $@ -statedir: - install --directory statedir - -install: install-server install-client-nokey - -install-html: html - install --directory $(htmldir) - install --mode=u=rw,go=r --target-directory=$(htmldir) \ - $(htmldocs) - -install-server: doc - install --directory $(CONFDIR) - if install --directory --mode=u=rwx --owner=$(USER) \ - --group=$(GROUP) $(STATEDIR); then \ - :; \ - elif install --directory --mode=u=rwx $(STATEDIR); then \ - chown -- $(USER):$(GROUP) $(STATEDIR) || :; \ - fi - if [ "$(TMPFILES)" != "$(DESTDIR)" \ - -a -d "$(TMPFILES)" ]; then \ - install --mode=u=rw,go=r tmpfiles.d-mandos.conf \ - $(TMPFILES)/mandos.conf; \ - fi - if [ "$(SYSUSERS)" != "$(DESTDIR)" \ - -a -d "$(SYSUSERS)" ]; then \ - install --mode=u=rw,go=r sysusers.d-mandos.conf \ - $(SYSUSERS)/mandos.conf; \ - fi - install --mode=u=rwx,go=rx mandos $(PREFIX)/sbin/mandos - install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ - mandos-ctl - install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ - mandos-monitor - install --mode=u=rw,go=r --target-directory=$(CONFDIR) \ - mandos.conf - install --mode=u=rw --target-directory=$(CONFDIR) \ - clients.conf - install --mode=u=rw,go=r dbus-mandos.conf \ - $(DESTDIR)/etc/dbus-1/system.d/mandos.conf - install --mode=u=rwx,go=rx init.d-mandos \ - $(DESTDIR)/etc/init.d/mandos - if [ "$(SYSTEMD)" != "$(DESTDIR)" -a -d "$(SYSTEMD)" ]; then \ - install --mode=u=rw,go=r mandos.service $(SYSTEMD); \ - fi - install --mode=u=rw,go=r default-mandos \ - $(DESTDIR)/etc/default/mandos - if [ -z $(DESTDIR) ]; then \ - update-rc.d mandos defaults 25 15;\ - fi - gzip --best --to-stdout mandos.8 \ - > $(MANDIR)/man8/mandos.8.gz - gzip --best --to-stdout mandos-monitor.8 \ - > $(MANDIR)/man8/mandos-monitor.8.gz - gzip --best --to-stdout mandos-ctl.8 \ - > $(MANDIR)/man8/mandos-ctl.8.gz - gzip --best --to-stdout mandos.conf.5 \ - > $(MANDIR)/man5/mandos.conf.5.gz - gzip --best --to-stdout mandos-clients.conf.5 \ - > $(MANDIR)/man5/mandos-clients.conf.5.gz - gzip --best --to-stdout intro.8mandos \ - > $(MANDIR)/man8/intro.8mandos.gz - -install-client-nokey: all doc - install --directory $(LIBDIR)/mandos $(CONFDIR) - install --directory --mode=u=rwx $(KEYDIR) \ - $(LIBDIR)/mandos/plugins.d \ - $(LIBDIR)/mandos/plugin-helpers - if [ "$(SYSUSERS)" != "$(DESTDIR)" \ - -a -d "$(SYSUSERS)" ]; then \ - install --mode=u=rw,go=r sysusers.d-mandos.conf \ - $(SYSUSERS)/mandos-client.conf; \ - fi - if [ "$(CONFDIR)" != "$(LIBDIR)/mandos" ]; then \ - install --mode=u=rwx \ - --directory "$(CONFDIR)/plugins.d" \ - "$(CONFDIR)/plugin-helpers"; \ - fi - install --mode=u=rwx,go=rx --directory \ - "$(CONFDIR)/network-hooks.d" - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos plugin-runner - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos \ - mandos-to-cryptroot-unlock - install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ - mandos-keygen - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/password-prompt - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/mandos-client - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/usplash - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/splashy - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/askpass-fifo - install --mode=u=rwxs,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugins.d \ - plugins.d/plymouth - install --mode=u=rwx,go=rx \ - --target-directory=$(LIBDIR)/mandos/plugin-helpers \ - plugin-helpers/mandos-client-iprouteadddel - install initramfs-tools-hook \ - $(INITRAMFSTOOLS)/hooks/mandos - install --mode=u=rw,go=r initramfs-tools-conf \ - $(INITRAMFSTOOLS)/conf.d/mandos-conf - install --mode=u=rw,go=r initramfs-tools-conf-hook \ - $(INITRAMFSTOOLS)/conf-hooks.d/zz-mandos - install initramfs-tools-script \ - $(INITRAMFSTOOLS)/scripts/init-premount/mandos - install initramfs-tools-script-stop \ - $(INITRAMFSTOOLS)/scripts/local-premount/mandos - install --directory $(DRACUTMODULE) - install --mode=u=rw,go=r --target-directory=$(DRACUTMODULE) \ - dracut-module/ask-password-mandos.path \ - dracut-module/ask-password-mandos.service - install --mode=u=rwxs,go=rx \ - --target-directory=$(DRACUTMODULE) \ - dracut-module/module-setup.sh \ - dracut-module/cmdline-mandos.sh \ - dracut-module/password-agent - install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR) - gzip --best --to-stdout mandos-keygen.8 \ - > $(MANDIR)/man8/mandos-keygen.8.gz - gzip --best --to-stdout plugin-runner.8mandos \ - > $(MANDIR)/man8/plugin-runner.8mandos.gz - gzip --best --to-stdout plugins.d/mandos-client.8mandos \ - > $(MANDIR)/man8/mandos-client.8mandos.gz - gzip --best --to-stdout plugins.d/password-prompt.8mandos \ - > $(MANDIR)/man8/password-prompt.8mandos.gz - gzip --best --to-stdout plugins.d/usplash.8mandos \ - > $(MANDIR)/man8/usplash.8mandos.gz - gzip --best --to-stdout plugins.d/splashy.8mandos \ - > $(MANDIR)/man8/splashy.8mandos.gz - gzip --best --to-stdout plugins.d/askpass-fifo.8mandos \ - > $(MANDIR)/man8/askpass-fifo.8mandos.gz - gzip --best --to-stdout plugins.d/plymouth.8mandos \ - > $(MANDIR)/man8/plymouth.8mandos.gz - gzip --best --to-stdout dracut-module/password-agent.8mandos \ - > $(MANDIR)/man8/password-agent.8mandos.gz - -install-client: install-client-nokey -# Post-installation stuff - -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)" - if command -v update-initramfs >/dev/null; then \ - update-initramfs -k all -u; \ - elif command -v dracut >/dev/null; then \ - for initrd in $(DESTDIR)/boot/initr*-$(LINUXVERSION); do \ - if [ -w "$$initrd" ]; then \ - chmod go-r "$$initrd"; \ - dracut --force "$$initrd"; \ - fi; \ - done; \ - fi - echo "Now run mandos-keygen --password --dir $(KEYDIR)" - -uninstall: uninstall-server uninstall-client - -uninstall-server: - -rm --force $(PREFIX)/sbin/mandos \ - $(PREFIX)/sbin/mandos-ctl \ - $(PREFIX)/sbin/mandos-monitor \ - $(MANDIR)/man8/mandos.8.gz \ - $(MANDIR)/man8/mandos-monitor.8.gz \ - $(MANDIR)/man8/mandos-ctl.8.gz \ - $(MANDIR)/man5/mandos.conf.5.gz \ - $(MANDIR)/man5/mandos-clients.conf.5.gz - update-rc.d -f mandos remove - -rmdir $(CONFDIR) - -uninstall-client: -# Refuse to uninstall client if /etc/crypttab is explicitly configured -# to use it. - ! grep --regexp='^ *[^ #].*keyscript=[^,=]*/mandos/' \ - $(DESTDIR)/etc/crypttab - -rm --force $(PREFIX)/sbin/mandos-keygen \ - $(LIBDIR)/mandos/plugin-runner \ - $(LIBDIR)/mandos/plugins.d/password-prompt \ - $(LIBDIR)/mandos/plugins.d/mandos-client \ - $(LIBDIR)/mandos/plugins.d/usplash \ - $(LIBDIR)/mandos/plugins.d/splashy \ - $(LIBDIR)/mandos/plugins.d/askpass-fifo \ - $(LIBDIR)/mandos/plugins.d/plymouth \ - $(INITRAMFSTOOLS)/hooks/mandos \ - $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ - $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ - $(INITRAMFSTOOLS)/scripts/local-premount/mandos \ - $(DRACUTMODULE)/ask-password-mandos.path \ - $(DRACUTMODULE)/ask-password-mandos.service \ - $(DRACUTMODULE)/module-setup.sh \ - $(DRACUTMODULE)/cmdline-mandos.sh \ - $(DRACUTMODULE)/password-agent \ - $(MANDIR)/man8/mandos-keygen.8.gz \ - $(MANDIR)/man8/plugin-runner.8mandos.gz \ - $(MANDIR)/man8/mandos-client.8mandos.gz - $(MANDIR)/man8/password-prompt.8mandos.gz \ - $(MANDIR)/man8/usplash.8mandos.gz \ - $(MANDIR)/man8/splashy.8mandos.gz \ - $(MANDIR)/man8/askpass-fifo.8mandos.gz \ - $(MANDIR)/man8/plymouth.8mandos.gz \ - $(MANDIR)/man8/password-agent.8mandos.gz \ - -rmdir $(LIBDIR)/mandos/plugins.d $(CONFDIR)/plugins.d \ - $(LIBDIR)/mandos $(CONFDIR) $(KEYDIR) $(DRACUTMODULE) - if command -v update-initramfs >/dev/null; then \ - update-initramfs -k all -u; \ - elif command -v dracut >/dev/null; then \ - for initrd in $(DESTDIR)/boot/initr*-$(LINUXVERSION); do \ - test -w "$$initrd" && dracut --force "$$initrd"; \ - done; \ - fi - -purge: purge-server purge-client - -purge-server: uninstall-server - -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \ - $(DESTDIR)/etc/dbus-1/system.d/mandos.conf - $(DESTDIR)/etc/default/mandos \ - $(DESTDIR)/etc/init.d/mandos \ - $(SYSTEMD)/mandos.service \ - $(DESTDIR)/run/mandos.pid \ - $(DESTDIR)/var/run/mandos.pid - -rmdir $(CONFDIR) - -purge-client: uninstall-client - -shred --remove $(KEYDIR)/seckey.txt $(KEYDIR)/tls-privkey.pem - -rm --force $(CONFDIR)/plugin-runner.conf \ - $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt \ - $(KEYDIR)/tls-pubkey.txt $(KEYDIR)/tls-privkey.txt - -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) + -rm -f $(PROGS) $(objects) core === removed file 'NEWS' --- NEWS 2019-09-03 19:06:41 +0000 +++ NEWS 1970-01-01 00:00:00 +0000 @@ -1,570 +0,0 @@ -This NEWS file records noteworthy changes, very tersely. -See the manual for detailed information. - -Version 1.8.9 (2019-09-03) -* No user-visible changes - -Version 1.8.8 (2019-08-18) -* No user-visible changes - -Version 1.8.7 (2019-08-05) -* Client: -** Always compile with LFS (Large File Support) enabled. -* Server -** Improve intro(8mandos) manual page to cover dracut(8) support. - -Version 1.8.6 (2019-08-03) -* Client: -** dracut support: In password-agent, properly ignore deleted and - renamed question files, and also fix memory alignment issue. - -Version 1.8.5 (2019-07-30) -* Client -** Support dracut(8) as well as initramfs-tools(7). -** Minor bug fix: Allow the mandos-keygen --passfile option to use - passfiles with names starting with "-". -** Document known limitation of mandos-keygen --password; it strips - white space from start and end of the password. -* Server -** Bug fix: The server used to fail to restart if the "port" setting - was used. This has been fixed. -** Minor bug fix: Reap zombies left over from checker runs. (Debian - bug #933387) - -Version 1.8.4 (2019-04-09) -* Client -** Fix minor memory leak in plugin-runner. -* Server -** mandos-ctl now has a --debug option to show D-Bus calls. - -Version 1.8.3 (2019-02-11) -* No user-visible changes. - -Version 1.8.2 (2019-02-10) -* Client -** In mandos-keygen, ignore failures to remove files in some cases. - -Version 1.8.1 (2019-02-10) -* Client -** Only generate TLS keys using GnuTLS' certtool, of sufficient - version. Key generation of TLS keys will not happen until a - version of GnuTLS is installed with support for raw public keys. -** Remove any bad keys created by 1.8.0 and openssl. -* Server -** On installation, edit clients.conf and remove the same bad key ID - which was erroneously reported by all 1.8.0 clients. Also do not - trust this key ID in the server. - -Version 1.8.0 (2019-02-10) -* Client -** Use new TLS keys for server communication and identification. - With GnuTLS 3.6 or later, OpenPGP keys are no longer supported. - The client can now use the new "raw public keys" (RFC 7250) API - instead, using GnuTLS 3.6.6. Please note: This *requires* new key - IDs to be added to server's client.conf file. -** New --tls-privkey and --tls-pubkey options to load TLS key files. - If GnuTLS is too old, these options do nothing. -* Server -** Supports either old or new GnuTLS. - The server now supports using GnuTLS 3.6.6 and clients connecting - with "raw public keys" as identification. The server will read - both fingerprints and key IDs from clients.conf file, and will use - either one or the other, depending on what is supported by GnuTLS - on the system. Please note: both are *not* supported at once; if - one type is supported by GnuTLS, all values of the other type from - clients.conf are ignored. - -Version 1.7.20 (2018-08-19) -* Client -** Fix: Adapt to the Debian cryptsetup package 2.0.3 or later. - Important: in that version or later, the plugins "askpass-fifo", - "password-prompt", and "plymouth" will no longer be run, since they - would conflict with what cryptsetup is doing. Other plugins, such - as mandos-client and any user-supplied plugins, will still run. -** Better error message if failing to decrypt secret data -** Check for (and report) any key import failure from GPGME -** Better error message if self-signature verification fails -** Set system clock if not set; required by GnuPG for key import -** When debugging plugin-runner, it will now show starting plugins - -Version 1.7.19 (2018-02-22) -* Client -** Do not print "unlink(...): No such file or directory". -** Bug fixes: Fix file descriptor leaks. -** Bug fix: Don't use leak sanitizer with leaking libraries. - -Version 1.7.18 (2018-02-12) -* Client -** Bug fix: Revert faulty fix for a nonexistent bug in the - plugin-runner - -Version 1.7.17 (2018-02-10) -* Client -** Bug fix: Fix a memory leak in the plugin-runner -** Bug fix: Fix memory leaks in the plymouth plugin - -Version 1.7.16 (2017-08-20) -* Client -** Bug fix: ignore "resumedev" entries in initramfs' cryptroot file -** Bug fix in plymouth plugin: fix memory leak, avoid warning output - -Version 1.7.15 (2017-02-23) -* Server -** Bug fix: Respect the mandos.conf "zeroconf" and "restore" options -* Client -** Bug fix in mandos-keygen: Handle backslashes in passphrases - -Version 1.7.14 (2017-01-25) -* Server -** Use "Requisite" instead of "RequisiteOverridable" in systemd - service file. - -Version 1.7.13 (2016-10-08) -* Client -** Minor bug fix: Don't ask for passphrase or fail when generating - keys using GnuPG 2.1 in a chrooted environment. - -Version 1.7.12 (2016-10-05) -* Client -** Bug fix: Don't crash after exit() when using DH parameters file - -Version 1.7.11 (2016-10-01) -* Client -** Security fix: Don't compile with AddressSanitizer -* Server -** Bug fix: Find GnuTLS library when gnutls28-dev is not installed -** Bug fix: Include "Expires" and "Last Checker Status" in mandos-ctl - verbose output -** New option for mandos-ctl: --dump-json - -Version 1.7.10 (2016-06-23) -* Client -** Security fix: restrict permissions of /etc/mandos/plugin-helpers -* Server -** Bug fix: Make the --interface flag work with Python 2.7 when "cc" - is not installed - -Version 1.7.9 (2016-06-22) -* Client -** Do not include intro(8mandos) man page - -Version 1.7.8 (2016-06-21) -* Client -** Include intro(8mandos) man page -** mandos-keygen: Use ECDSA SSH keys by default -** Bug fix: Work with GnuPG 2 when booting (Debian bug #819982) - by copying /usr/bin/gpg-agent into initramfs -* Server -** Bug fix: Work with GnuPG 2 (don't use --no-use-agent option) -** Bug fix: Make the --interface option work when using Python 2.7 - by trying harder to find SO_BINDTODEVICE - -Version 1.7.7 (2016-03-19) -* Client -** Fix bug in Plymouth client, broken since 1.7.2 - -Version 1.7.6 (2016-03-13) -* Server -** Fix bug where stopping server would time out -** Make server runnable with Python 3 - -Version 1.7.5 (2016-03-08) -* Server -** Fix security restrictions in systemd service file. -** Work around bug where stopping server would time out - -Version 1.7.4 (2016-03-05) -* Client -** Bug fix: Tolerate errors from configure_networking (Debian Bug - #816513) -** Compilation: Only use sanitizing options which work with the - compiler used when building. This should fix compilation with GCC - 4.9 on mips, mipsel, and s390x. -* Server -** Add extra security restrictions in systemd service file. - -Version 1.7.3 (2016-02-29) -* Client -** Bug fix: Remove new type of keyring directory user by GnuPG 2.1. -** Bug fix: Remove "nonnull" attribute from a function argument, which - would otherwise generate a spurious runtime warning. - -Version 1.7.2 (2016-02-28) -* Server -** Stop using python-gnutls library; it was not updated to GnuTLS 3.3. -** Bug fix: Only send D-Bus signal ClientRemoved if using D-Bus. -** Use GnuPG 2 if available. -* Client -** Compile with various sanitizing flags. - -Version 1.7.1 (2015-10-24) -* Client -** Bug fix: Can now really find Mandos server even if the server has - an IPv6 address on a network other than the one which the Mandos - server is on. - -Version 1.7.0 (2015-08-10) -* Server -** Bug fix: Handle local Zeroconf service name collisions better. -** Bug fix: Finally fix "ERROR: Child process vanished" bug. -** Bug fix: Fix systemd service file to start server correctly. -** Bug fix: Be compatible with old 2048-bit DSA keys. -** The D-Bus API now provides the standard D-Bus ObjectManager - interface, and deprecates older functionality. See the DBUS-API - file for the currently recommended API. Note: the original API - still works, but is deprecated. -* Client -** Can now find Mandos server even if the server has an IPv6 address - on a network without IPv6 Router Advertisment (like if the Mandos - client itself is the router, or there is an IPv6 router advertising - a network other than the one which the Mandos server is on.) -** Use a better value than 1024 for the default number of DH bits. - This better value is either provided by a DH parameters file (see - below) or an appropriate number of DH bits is determined based on - the PGP key. -** Bug fix: mandos-keygen now generates correct output for the - "Checker" variable even if the SSH server on the Mandos client has - multiple SSH key types. -** Can now use pre-generated Diffie-Hellman parameters from a file. - -Version 1.6.9 (2014-10-05) -* Server -** Changed to emit standard D-Bus signal when D-Bus properties change. - (The old signal is still emitted too, but marked as deprecated.) - -Version 1.6.8 (2014-08-06) -* Client -** Bug fix: mandos-keygen now generates working SSH checker commands. -* Server -** Bug fix: "mandos-monitor" now really redraws screen on Ctrl-L. -** Now requires Python 2.7. - -Version 1.6.7 (2014-07-17) -* Client -** Bug fix: Now compatible with GPGME 1.5.0. -** Bug fix: Fixed minor memory leaks. -* Server -** "mandos-monitor" now has verbose logging, toggleable with "v". - -Version 1.6.6 (2014-07-13) -* Client -** If client host has an SSH server, "mandos-keygen --password" now - outputs "checker" option which uses "ssh-keyscan"; this is more - secure than the default "fping" checker. -** Bug fix: allow "." in network hook names, to match documentation. -** Better error messages. -* Server -** New --no-zeroconf option. -** Bug fix: Fix --servicename option, broken since 1.6.4. -** Bug fix: Fix --socket option work for --socket=0. - -Version 1.6.5 (2014-05-11) -* Client -** Work around bug in GnuPG -** Give better error messages when run without sufficient privileges -** Only warn if workaround for Debian bug #633582 was necessary and - failed, not if it failed and was unnecessary. - -Version 1.6.4 (2014-02-16) -* Server -** Very minor fix to self-test code. - -Version 1.6.3 (2014-01-21) -* Server -** Add systemd support. -** For PID file, fall back to /var/run if /run does not exist. -* Client -** Moved files from /usr/lib/mandos to whatever the architecture - specifies, like /usr/lib/x86_64-linux-gnu/mandos or - /usr/lib64/mandos. - -Version 1.6.2 (2013-10-24) -* Server -** PID file moved from /var/run to /run. -** Bug fix: Handle long secrets when saving client state. -** Bug fix: Use more magic in the GnuTLS priority string to handle - both old DSA/ELG 2048-bit keys and new RSA/RSA 4096-bit keys. -* Client -** mandos-keygen: Bug fix: now generate RSA keys which GnuTLS can use. - Bug fix: Output passphrase prompts even when - redirecting standard output. - -Version 1.6.1 (2013-10-13) -* Server -** All client options for time intervals now also take an RFC 3339 - duration. The same for all options to mandos-ctl. -** Bug fix: Handle fast checkers (like ":") correctly. -** Bug fix: Don't print output from checkers when running in - foreground. -** Bug fix: Do not fail when client is removed from clients.conf but - saved settings remain. -** Bug fix: mandos-monitor now displays standout (reverse video) again - using new version of Urwid. -** Bug fix: Make boolean options work from the config file again. -** Bug fix: Make --no-ipv6 work again. -** New default priority string to be slightly more compatible with - older versions of GnuTLS. -* Client -** Bug fix: Fix bashism in mandos-keygen. -** Default key and subkey types are now RSA and RSA, respectively. - Also, new default key size is 4096 bits. - -Version 1.6.0 (2012-06-18) -* Server -** Takes new --foreground option -** Init script supports new "status" action. -* Client -** Now uses all interfaces by default; the --interface option can - still be used to restrict it, and the argument to --interface (as - well as the $DEVICE environment variable for the network hooks) is - now a comma-separated list of interfaces to use. - -Version 1.5.5 (2012-06-01) -* Server -** Server takes new --socket option - -Version 1.5.4 (2012-05-20) -* Server -** Bug fix: Regression fix: Make non-zero approval timeout values work. -** Bug fix: Regression fix: Allow changing the Timeout D-Bus property. -** Fall back to not bind to an interface if an invalid interface name - is given. -** Removed support for undocumented feature of using plain "%%s" in - "checker" client option. -** Old D-Bus interface are now marked as deprecated. -** mandos-monitor: Bug fix: show approval timers correctly. -** mandos-ctl: Show "Extended Timeout" correctly, not as milliseconds. - -Version 1.5.3 (2012-01-15) -* Server -** Add D-Bus property se.recompile.Client.LastCheckerStatus and use it - in mandos-monitor. -* Client -** Fix bugs in the example "bridge" network hook. - -Version 1.5.2 (2012-01-08) -* Server -** Removed D-Bus signal se.recompile.Mandos.NewRequest() added in - 1.5.0. It was buggy and was of questionable utility. - -Version 1.5.1 (2012-01-01) -* Server -** Include intro(8mandos) manual page, missing since migration from - README file in version 1.4.0. - -Version 1.5.0 (2012-01-01) -* Client -** Network hooks. The Mandos client can now run custom scripts to take - up a network interface before the client is run. Three example - scripts are provided: "wireless", "openvpn", and "bridge". - To facilitate this, the client now prefers network interfaces which - are up (if any) over all other interfaces. -* Server -** Persistent state. Client state is now saved between server - restarts. -** clients.conf file can now contain "enabled" setting for clients. -** Bug fix: Fix rare crash bug. -** Bug fix: Send corrent D-Bus type in PropertyChanged for - "ApprovalDelay", "ApprovalDuration", "Timeout", and - "ExtendedTimeout". -** mandos-ctl: Bare numbers as arguments are taken to be milliseconds. -** Bug fix: mandos-ctl --secret option now works. -** New D-Bus signal: se.recompile.Mandos.NewRequest(s). - -Version 1.4.1 (2011-10-15) -* Server -** Make D-Bus properties settable again, and handle checkers - for disabled clients correctly. -* Miscellaneous fixes to "pedantic" Lintian warnings - -Version 1.4.0 (2011-10-09) -* README file migrated to manual page intro(8mandos). -* Client: -** Fixed warning about "rmdir: Directory not empty". -* Server: -** Default values changed: timeout 5 minutes, interval 2 minutes. -** Clients gets an expiration extension when receiving a password, - controlled by new "extended_timeout" setting. -** New domain name: "fukt.bsnet.se" changes to "recompile.se". This - also affects the D-Bus bus and interface names (old names still - work). Users should start using the new names immediately. -** New D-Bus Client object properties "Expires" and "ExtendedTimeout"; - see DBUS-API for details. - -Version 1.3.1 (2011-07-27) -* Client: -** Client now retries all Mandos servers periodically. -** Work around Debian bug #633582 - fixes "Permission denied" problem. - -Version 1.3.0 (2011-03-08) -* Server: -** Updated for Python 2.6. -* Client: -** Bug fix: Make the password-prompt plugin not conflict with - Plymouth. -** Bug fix: Bug fix: update initramfs also when purging package. - -Version 1.2.3 (2010-10-11) -* Server: -** Bug fix: Expose D-Bus API also in non-debug mode. - -Version 1.2.2 (2010-10-07) -* Client: -** splashy: Minor fix to compile with non-Linux kernels. - -Version 1.2.1 (2010-10-02) -* Server: -** mandos-monitor(8): Documentation bug fix: Key for removing client - is "R", not "r". - -Version 1.2 (2010-09-28) -* Client: -** New "plymouth" plugin to ask for a password using the Plymouth - graphical boot system. -** The Mandos client now automatically chooses a network interface if - the DEVICE setting in /etc/initramfs-tools/initramfs.conf is set to - the empty string. This is also the new default instead of "eth0". -** The Mandos client --connect option now loops indefinitely until a - password is received from the specified server. -** Bug fix: Quote directory correctly in mandos-keygen with --password -** Bug fix: don't use "echo -e" in mandos-keygen; unsupported by dash. -* Server: -** Terminology change: clients are now "ENABLED" or "DISABLED", not - "valid" or "invalid". -** New D-Bus API; see the file "DBUS-API". -** New control utilities using the new D-Bus API: - + mandos-ctl A command-line based utility - + mandos-monitor A text-based GUI interface -** New feature: manual interactive approval or denying of clients on a - case-by-case basis. -** New --debuglevel option to control logging -** Will not write PID file if --debug is passed -** Bug fix: Avoid race conditions with short "interval" values or - fast checkers. -** Bug fix: Don't try to bind to a network interface when none is - specified - -Version 1.0.14 (2009-10-25) -Enable building without -pie and -fPIE if BROKEN_PIE is set. - -Version 1.0.13 (2009-10-22) -* Client -** Security bug fix: If Mandos server is also installed, do not copy - its config files (with encrypted passwords) into the initrd.img-* - files. - -Version 1.0.12 (2009-09-17) -* Client -** Bug fix: Allow network interface renaming by "udev" by taking down - the network interface after using it. -** Bug fix: User-supplied plugins are now installed correctly. -** Bug fix: If usplash was used but the password was instead provided - by the Mandos server, the usplash daemon used to ignore the first - command passed to it. This has been fixed. -** Bug fix: Make the "--userid" and "--groupid" options in - "plugin-runner.conf" work. -* Server -** Bug fix: Fix the LSB header in the init.d script to make dependency - based booting work. -** A client receiving its password now also counts as if a checker was - run successfully (i.e. the timeout timer is reset). - -Version 1.0.11 (2009-05-23) -* Client -** Bug fix: Use "pkg-config" instead of old "libgnutls-config". - -Version 1.0.10 (2009-05-17) -* Client -** Security bug fix: Fix permissions on initrd.img-*.bak files when - upgrading from older versions. - -Version 1.0.9 (2009-05-17) -* Client -** Security bug fix: Fix permissions on initrd.img file when - installing new linux-image-* packages calling mkinitramfs-kpkg (all - version lower than 2.6.28-1-* does this). - -Version 1.0.8 (2009-02-25) -* Client -** Bug fix: Fix missing quote characters in initramfs-tools-hook. - -Version 1.0.7 (2009-02-24) -* Client -** Bug fix: Do not depend on GNU awk. - -Version 1.0.6 (2009-02-13) -* Server -** Fix bug where server would stop responding, with a zombie checker -** Support for disabling IPv6 (only for advanced users) -** Fix bug which made server not change group ID - -* Client -** Bug fix: Fix permission for /lib64 (on relevant architechtures). -** Add support for IPv4 addresses. -** Add support in mandos-client for not bringing up a network - interface by specifying an empty string to "--interface". -** Make password prompt on boot not be mangled by kernel log messages - about network interface. -** Get network interface from initramfs.conf and/or from kernel - command line. -** If set by "ip=" kernel command line, configure network on boot. -** Support connecting directly using "mandos=connect" kernel command. - line option, provided network is configured using "ip=". -** Fix bug which made plugin-runner and mandos-client not change group - ID. -** Fix bug where the "--options-for" option of plugin-runner would - truncate the value at the first colon character. -** Fix bug where plugin-runner would not go to fallback if all plugins - failed. -** Fix bug where mandos-client would not clean temporary directory on - a signal or on certain file systems. -** Bug fix: remove bashism in /bin/sh script "mandos-keygen". - -Version 1.0.5 (2009-01-17) -* Client -** Fix small memory leak in plugin-runner. - -Version 1.0.4 (2009-01-15) -* Server -** Only find matched user/group pairs when searching for suitable - nonprivileged user/group to switch to. - -* Client -** New kernel parameter "mandos=off" makes client not run at boot. -** Fix linking errors and compilation warnings on AMD64. -** Parse numbers in command line options better. -** The splashy and usplash plugins are more robust while traversing - /proc, and will not abort if a process suddenly disappears. - -Version 1.0.3 (2009-01-06) -* Server -** Now tries to change to user and group "_mandos" before falling back - to trying the old values "mandos", "nobody:nogroup", and "65534". -** Now does not abort on startup even if no clients are defined in - clients.conf. - -* Client -** Plugins named "*.dpkg-bak" are now ignored. -** Hopefully fixed compilation failure on some architectures where the - C compiler does not recognize the "-z" option as a linker option. - -Version 1.0.2 (2008-10-17) -* mandos-keygen now signs the encrypted key blobs. This signature is - not currently verified by mandos-client, but this may change in the - future. - -Version 1.0.1 (2008-10-07) -* Server -** Expand environment variables and ~user in clients.conf's "secfile" - The "secfile" option in /etc/mandos/clients.conf now expands - "~user/foo" and "$ENVVAR" strings. - -* Client (plugin-runner, plugins, etc.) -** Manual pages for the usplash, splashy, and askpass-fifo plugins. - All plugins now have man pages. -** More secure compilation and linking flags. - All programs are now compiled with "-fstack-protector-all -fPIE - -pie", and linked using "-z relro -pie" for additional security. - -* There is now a "NEWS" file (this one), giving a history of - noteworthy changes. === removed file 'README' --- README 2016-03-23 07:11:22 +0000 +++ README 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ -Please see: https://www.recompile.se/mandos/man/intro.8mandos - -This information previously in this file has been moved to the -intro(8mandos) manual page. Go to the above URL, or install the -Mandos server and run this command: - - man 8mandos intro - -In short, this is the Mandos system; it allows computers to have -encrypted root file systems and at the same time be capable of remote -and/or unattended reboots. === modified file 'TODO' --- TODO 2019-04-09 19:41:53 +0000 +++ TODO 2008-07-29 03:35:39 +0000 @@ -1,124 +1,33 @@ --*- org -*- - -* Testing -** python-nemu - -* mandos-applet - -* mandos-client -** TODO A --server option which only adds to the server list. - (Unlike --connect, which implicitly disables zeroconf.) -** TODO [#B] Use capabilities instead of seteuid(). - https://forums.grsecurity.net/viewtopic.php?f=7&t=2522 -** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() -** TODO [#C] Make start_mandos_communication() take "struct server". -** TODO [#C] --interfaces=regex,eth*,noregex (bridge-utils-interfaces(5)) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. - -* splashy -** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* usplash (Deprecated) -** TODO [#B] Make it work again -** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* askpass-fifo -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* password-prompt -** TODO [#B] lock stdin (with flock()?) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL - -* plymouth -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. - -* TODO [#B] passdev - -* plugin-runner -** TODO handle printing for errors for plugins -*** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" -** TODO [#C] use same file name rules as run-parts(8) -** kernel command line option for debug info -** TODO [#A] Restart plugins which exit with EX_TEMPFAIL - -* mandos (server) -** TODO [#B] --notify-command - This would allow the mandos.service to use - --notify-command="systemd-notify --pid --ready" -** TODO [#B] python-systemd -*** import systemd.daemon; systemd.daemon.notify() -** TODO [#B] Log level :BUGS: -*** TODO /etc/mandos/clients.d/*.conf - Watch this directory and add/remove/update clients? -** TODO [#C] config for TXT record -** TODO Log level dbus option - SetLogLevel D-Bus call -** TODO [#C] DBusServiceObjectUsingSuper -** TODO [#B] Global enable/disable flag -** TODO [#B] By-client countdown on number of secrets given -** D-Bus Client method NeedsPassword(50) - Timeout, default disapprove - + SetPass(u"gazonk", True) -> Approval, persistent - + Approve(False) -> Close client connection immediately -** TODO [#C] python-parsedatetime -** TODO Separate logging logic to own object -** TODO [#B] Limit approval_delay to max gnutls/tls timeout value -** TODO [#B] break the wait on approval_delay if connection dies -** TODO Generate Client.runtime_expansions from client options + extra -** TODO Allow %%(checker)s as a runtime expansion -** TODO D-Bus AddClient() method on server object -** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. :2: -** TODO Save state periodically to recover better from hard shutdowns -** TODO CheckerCompleted method, deprecate CheckedOK -** TODO Secret Service API? - https://standards.freedesktop.org/secret-service/ -** TODO Remove D-Bus interfaces with old domain name :2: -** TODO Remove old string_to_delta format :2: -** TODO http://0pointer.de/blog/projects/stateless.html -*** File in /usr/lib/sysusers.d to create user+group "_mandos" -** TODO Error handling on error parsing config files -** TODO init.d script error handling -** TODO D-Bus server properties; address, port, interface, etc. :2: -** Python 3 :2: -*** TODO [#C] In Python 3.3, use shlex.quote() instead of re.escape() - -* mandos-ctl -** TODO Remove old string_to_delta format :2: - -* TODO mandos-dispatch - Listens for specified D-Bus signals and spawns shell commands with - arguments. - -* mandos-monitor -** TODO --servicename :BUGS: -** TODO help should be toggleable -** Urwid client data displayer - Better view of client data in the listing -*** Properties popup -** Print a nice "We are sorry" message, save stack trace to log. - -* mandos-keygen -** TODO "--secfile" option - Using the "secfile" option instead of "secret" -** TODO [#B] "--test" option - For testing decryption before rebooting. - -* Package -** /usr/share/initramfs-tools/hooks/mandos -*** TODO [#C] use same file name rules as run-parts(8) -*** TODO [#C] Do not install in initrd.img if configured not to. - Use "/etc/initramfs-tools/hooksconf.d/mandos"? -** TODO [#C] $(pkg-config --variable=completionsdir bash-completion) - From XML sources directly? - -* Side Stuff -** TODO Locate which package moves the other bin/sh when busybox is deactivated -** TODO contact owner of package, and ask them to have that shell static in position regardless of busybox - -* [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] - - -#+STARTUP: showall +[Mandos client] +configuration for OpenPGP key dir +header files/symbols tally +check exit codes of all system calls +IPv4 support +protocol version header +use strsep instead of strtok? + +[Pluginbasedclient] +disable certain plugins +header files/symbols tally +check exit codes of all system calls +change uid to nobody:nogroup + other drop privs stuff? +pass things in environment, like device name, etc + Does cryptsetup already do this? +Configurable plugin dir +use strsep instead of strtok? + +[Server] +config for: + TXT record +protocol version header +Run-time communication with server + probably using D-Bus + +[Mandos-tools/utilities] + List clients + Enable client + Disable client + +[Installer] +... === removed file 'bugs.xml' --- bugs.xml 2016-03-05 21:42:56 +0000 +++ bugs.xml 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ - - - - Please report bugs to the Mandos development mailing list: - mandos-dev@recompile.se (subscription required). - Note that this list is public. The developers can be reached - privately at mandos@recompile.se (OpenPGP key - fingerprint 153A 37F1 0BBA 0435 987F 2C4A 7223 2973 CA34 - C2C4 for encrypted mail). - === added file 'ca.pem' --- ca.pem 1970-01-01 00:00:00 +0000 +++ ca.pem 2007-10-20 21:38:25 +0000 @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIHEzCCBPugAwIBAgIJAMCSQPxm3Tm7MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD +VQQGEwJTRTELMAkGA1UECBMCQkwxEDAOBgNVBAcTB1Jvbm5lYnkxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEWMBQGA1UEAxMNQnJheGVuIHVuaXRl +ZDEjMCEGCSqGSIb3DQEJARYUYmVsb3JuQGZ1a3QuYnNuZXQuc2UwHhcNMDcxMDE1 +MTczNDQ1WhcNMTcxMDEyMTczNDQ1WjCBjDELMAkGA1UEBhMCU0UxCzAJBgNVBAgT +AkJMMRAwDgYDVQQHEwdSb25uZWJ5MSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz +IFB0eSBMdGQxFjAUBgNVBAMTDUJyYXhlbiB1bml0ZWQxIzAhBgkqhkiG9w0BCQEW +FGJlbG9ybkBmdWt0LmJzbmV0LnNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAxrMpGUiDUI0X3XgYovgRGaKaSIoCL7RlNVRHxWLEaNIEmHuhQv40fksa +ndO7KcbxBRTMvF/XNYyEEyks+D3jQrBmWBjsOSdZkLhP64HLnx2eYLd1HEAXIhB8 +wip180m9tfT6XeXX6cOh5AowMOGHv364xcZXJvYJgxVuMW1vlh9F79N1bnnR2rJ6 +4nmebJ91QW7ecSwA6h0fnx6GF3d5PfEC24/Fys/1NbdYiy8EYsP+lIEHQC4oD0Za +kjclNWT3O1I8lnLdm4F1KdgDIva7aCyoAI4rLsxFxnnaqQcPo+8gGhusfnbOSymd +m2tdz91x3Z2rSoo/cgc2TaPYKYuiJScsw1FftYGOZLiXbAedCqmoEC9XdMlZrbPn +AWhjSvbFY8B2LCoy23OFpV2h+QrnKXOzsxyfxexwkjytIn9Msy5DlcCQz93vO/c9 +CGN/GpXJf5ebNk4oTE7pXNMM27shvubtXa6BL7A+S7yB8xmQ3NA+CABsQsKHcgVy +2+SLD65Ms6OpwXn9BPlT1aJxbTiCXlSIQ7OIDPE1okwjxLTHc9dygoYuqjLG9Dkx +WOqZWNxyj3kU40mX96qbOhJG5Zgq9J9fUT4ZJETO754YIFGKltlT6RSqeLrNOuLg +9cYNGOLqoCVIyUi5HiTR7iIukNDeZy4YN6BiQH+TnYjC5+FwZbsCAwEAAaOCAXQw +ggFwMB0GA1UdDgQWBBT4/xxxSVgFPLkDVNc8Ocdvodu3JTCBwQYDVR0jBIG5MIG2 +gBT4/xxxSVgFPLkDVNc8Ocdvodu3JaGBkqSBjzCBjDELMAkGA1UEBhMCU0UxCzAJ +BgNVBAgTAkJMMRAwDgYDVQQHEwdSb25uZWJ5MSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMTDUJyYXhlbiB1bml0ZWQxIzAhBgkqhkiG +9w0BCQEWFGJlbG9ybkBmdWt0LmJzbmV0LnNlggkAwJJA/GbdObswDwYDVR0TAQH/ +BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQYwCQYDVR0SBAIwADArBglghkgBhvhC +AQ0EHhYcVGlueUNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAfBgNVHREEGDAWgRRi +ZWxvcm5AZnVrdC5ic25ldC5zZTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEF +BQADggIBAD/AJL0dKb4CcVEBzYsKV5JAxZQBTFYXm4TlMzbNJnl6C3FH636xSfvE +qWWPrIplbmNn0tyXNf1dvb2Xy4QVFTo5lnp6PKwhqpV5JK7+4/LiDZj5R4VNl36B +RUleXSSGV/+PJ1hRC9J/s+oziK1025hPpDB+n13sFrBv9DyqP/3GDSVoi13Mldf1 +vTWRnvugi7XlMhFdemB1Vz0eyIde0FtFpPrd3HeFrXQX8617XfcEYJmCU2aDT1fy +77RHFG+3dL286UcK2D85bqNCntP9W3zP6rwerkO1pGmT2cBn9AO6Qt2MI7klTJmw +SRqbeBcF+gaBRGY8iZHcqow7vqTvo65p/ZH9AUqMQh41W40FbepUA3354xGmwAlB +LBXRuVjaDvKyargh936dOyzIDr+fZtr6BEthmt6BghgOZlezAV/P60P56DlFvpK8 +4wHyT5KI9LOqbg+W2ToB/GdRZz4p7K90JljrgVlnQhYIQ1YT/5OzPu6Uq5+4Q4hc +ORb5hRCWHwre95kgEzUPqrteqV/g99XaOgqpK6KhY/dpmb7Z5I9f6d61XYYFfxYm +iuNJiTLCQlLyfXFSRlJubWD8hNHo2HNRYdiWF3cttuGL9aC0uSIk4xq/Oi/guqIn +sZlrO3I4MaiUnUXntMLU4zh0o/RPqhoT3lY1VubwgmpkOw8p7ggE +-----END CERTIFICATE----- === added file 'cert.pem' --- cert.pem 1970-01-01 00:00:00 +0000 +++ cert.pem 2007-10-20 21:38:25 +0000 @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIG8zCCBNugAwIBAgIBATANBgkqhkiG9w0BAQUFADCBjDELMAkGA1UEBhMCU0Ux +CzAJBgNVBAgTAkJMMRAwDgYDVQQHEwdSb25uZWJ5MSEwHwYDVQQKExhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMTDUJyYXhlbiB1bml0ZWQxIzAhBgkq +hkiG9w0BCQEWFGJlbG9ybkBmdWt0LmJzbmV0LnNlMB4XDTA3MTAxNTE3MzczMVoX +DTA4MTAxNDE3MzczMVowdTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkJMMRAwDgYD +VQQHEwdSb25uZWJ5MREwDwYDVQQKEwhHbnVzdHVmZjEPMA0GA1UEAxMGQnJheGVu +MSMwIQYJKoZIhvcNAQkBFhRiZWxvcm5AZnVrdC5ic25ldC5zZTCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAK4j59iJGfir5jxl4gCeLyhXED4YGd42o/ll +XdWtYO9IHtrCZknnRIjjviQ0PJk6JQpIGkNrdW5uRSQcTKshcEOL8cprUHe+Mpi8 +34i7bOaPlDdJYoMatkHEvq92weXFfKa8yG5UtmAbiuo388JbnVgU7HAqq1ipi+dS +MoJU0FR3qxKC3eioQQdq+QB257BeZNDq3SrWTqZXw7pu4DmiOZJhpMAV5CmjQKt4 +ZC0EdByGct0tp3X3swpBY+0a+wlFMKVe1WGy9dfhkNRVhUCXNKOCuaaIMsn8iuCc +NlkIw4GVhvJj9ALfRZS5VzRaKIhOnFub4+PxYClt1ghfTBhk1aQjiSl1JT+qtgpv +Sqd34OnYtLRp+L5EcLQSPoykG2TjsUxnYXtBiPoZuIk7v5alQlbT20DmuWhpCMm4 +fw1W0Q6eXL2APtrjAt7HS79qDopySL4st8nTkY3fzmjW8Vpg1Y4HyN6aY8tR9OIj +SwZhCv+NoixbZToUnLXmoRtQnO6rSp7qLuJCPC1gbDc1TR4mclr5Vumb8ag1iQTi +i0SvhqY8u1sDMGlL9aZW0WaykfoC64cUdBKm9R6dt08Y0xAevSaxdcsmK9kCfCJO +o6nJ2pQ9+WdSjYEhq5DLBGhpfhzs+U1ZL0HYmTfyVjmbWuDZaQ1Yht4vHf1TYwAp +FjGJGt/7AgMBAAGjggF0MIIBcDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIG +QDArBglghkgBhvhCAQ0EHhYcVGlueUNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd +BgNVHQ4EFgQUoq4JMCRt6ToFRTrLNUFd4xQ0hGUwgcEGA1UdIwSBuTCBtoAU+P8c +cUlYBTy5A1TXPDnHb6HbtyWhgZKkgY8wgYwxCzAJBgNVBAYTAlNFMQswCQYDVQQI +EwJCTDEQMA4GA1UEBxMHUm9ubmVieTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRYwFAYDVQQDEw1CcmF4ZW4gdW5pdGVkMSMwIQYJKoZIhvcNAQkB +FhRiZWxvcm5AZnVrdC5ic25ldC5zZYIJAMCSQPxm3Tm7MB8GA1UdEgQYMBaBFGJl +bG9ybkBmdWt0LmJzbmV0LnNlMB8GA1UdEQQYMBaBFGJlbG9ybkBmdWt0LmJzbmV0 +LnNlMA0GCSqGSIb3DQEBBQUAA4ICAQBC5Hz334l9o0RnjjjzZZyfYpEuAK7MJr6g +r7hPqZzAAXL7i4w5AEutthIxo+JGGL0P4xv2Swc6uwodU0GE4/6DUEBQrjaDhr/F +Uw3GaXYVopdDa/kYWAXF6lP+hkNhb1hI5onLMtRpoICLpfePNALZn3lMaBPI2efC +EvViu3dpjJKKNm/HlXOl/wgrrcLwHKlbPSozaiXe2qxik4fr15cXtm4/nXetZq1g +8NAOcZpdurZNEieMhtFSvLbQ3X/yNYKLgukz/zVCzL0IXLQXA4B2URBbzQJtrjOD +Agq5mAXhrlGuEEvaMJeQP+VrfsIZU2fFKvr8LVQO37GprQizPFBC8FDkrE4XrpET +A6ztDijyWRxT5x+4MbZrfL3GE9ZO57HZaAdXil1cjxXMlFqtJ6yoLNil3v+mgp9G +yJeEz6L3jYRxVZqXYy7VMh+rJ7Mdr31g9pT8TT0/LGoCBorCTcUYIBP3BE3F85Nk +JCPR5GG7Mfk3s8VTxTFAMZGKgst+i+YiMQSLkXIHNph5Fi18qIacpD9kkCXVj+JU +UW/fYcTd4vQdR2HhyJdPddtHkprMN3ZlICEug3junMOJb0PWByqfuaihkPYIZ2xw +puEkrP8cMdTXiUXep2RHTwByqy3/EQMC3ZWRiMIHZRIkl+o5bklOHwadUmzrJ1vL +QjmtPc7y1g== +-----END CERTIFICATE----- === added file 'client-cert.pem' --- client-cert.pem 1970-01-01 00:00:00 +0000 +++ client-cert.pem 2007-10-20 21:38:25 +0000 @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIHBzCCBO+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBjDELMAkGA1UEBhMCU0Ux +CzAJBgNVBAgTAkJMMRAwDgYDVQQHEwdSb25uZWJ5MSEwHwYDVQQKExhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMTDUJyYXhlbiB1bml0ZWQxIzAhBgkq +hkiG9w0BCQEWFGJlbG9ybkBmdWt0LmJzbmV0LnNlMB4XDTA3MTAxNTIwNDkwNloX +DTA4MTAxNDIwNDkwNlowfDELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkJMMRAwDgYD +VQQHEwdSb25uZWJ5MREwDwYDVQQKEwhnbnVzdHVmZjEWMBQGA1UEAxQNYnJheGVu +X2NsaWVudDEjMCEGCSqGSIb3DQEJARYUYmVsb3JuQGZ1a3QuYnNuZXQuc2UwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDWAwsXT4jeKCKg7/4g8KTmwEZ6 +88zfaJeqpzpkpc25J47Qmr5Mf6NiKUIAqjD8KycApG1cwVoLEvxvQOip2lSzL4ex +ipTpZO45O6N5blKG4XKqJRE2VE5N1j2Z+jJmfFEaHqMDYb9+dlnSZ2c0c1d2qcgC +HgOR5iZHWQCGtEK3y9HfWsFpULuzfYMIpYZjPic+kAuisT5cl/wIDVI/MbpB3/vk +9vMpRCFRjvbOcN7rEW4UihuVSFmzFNk99QR8e3Li1HtqbXmmtJps9u16ugGQO4ED +4gk65i5TU15sZJgFLXBuFuZFYH8WF2CIu6o4oylCLVjFRZHl8jKwZdPtNUZ01cJk +oj+M9Yc9m6F1dLafLThhvSLMuRfUvHY9bFAkF6QkkgIjY1mN1VTm6XxLN991DJd7 +67IfpFrBaKi47C4abBc4lZYfBlmXdkpOt3UcYxh3YT+KTUK/7S/arj2iEypTic4x +PVDsA4DbnHJ1+6ZzyMAM7ACmZnWhq7gXhjT8z3eEyaqaZVOracG1DFln781Mm6b9 +Eb3qJfczKR/QXiMsF4gwwH+Dclkd/THjjv/SPQYCiWm2Fo8IXG/hdZzBx4dWoSMZ +kgQM5GDLQU80/jAvDni6n97I48fcxjL6YnEgQeoasKoayGaOmnhEs/eytpcGocNi +3pJUpD0coYyAemeL5wIDAQABo4IBgTCCAX0wCQYDVR0TBAIwADARBglghkgBhvhC +AQEEBAMCBLAwKwYJYIZIAYb4QgENBB4WHFRpbnlDQSBHZW5lcmF0ZWQgQ2VydGlm +aWNhdGUwHQYDVR0OBBYEFK9FlbSWMi9o063eYNprvQ4Msg/FMIHBBgNVHSMEgbkw +gbaAFPj/HHFJWAU8uQNU1zw5x2+h27cloYGSpIGPMIGMMQswCQYDVQQGEwJTRTEL +MAkGA1UECBMCQkwxEDAOBgNVBAcTB1Jvbm5lYnkxITAfBgNVBAoTGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDEWMBQGA1UEAxMNQnJheGVuIHVuaXRlZDEjMCEGCSqG +SIb3DQEJARYUYmVsb3JuQGZ1a3QuYnNuZXQuc2WCCQDAkkD8Zt05uzAfBgNVHRIE +GDAWgRRiZWxvcm5AZnVrdC5ic25ldC5zZTAfBgNVHREEGDAWgRRiZWxvcm5AZnVr +dC5ic25ldC5zZTALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggIBAINB3d3z +FDRV5/+vov3KhyKhhDYdhO2IqkSXARRQffrd94/F/8YcrOgJJrvnz6THbQIzuSM7 +sSE2Y2JqyWfaA0J8CQm9JrOgVTE9I74/IZONC3jSBrt/Q9dG74otaeaHNOTaH4lU +nAH5Cv6CgMFi58n+yQLxrYBAimWx8x1u40OxUXM+AbpAgDo3uXNt62SdrWAldwh3 +9Eqog3qUcN7R1VGx535VXJGg3e8yZFRPnFLiYuO1afjO/tlOyYBG0wvCfmNMSpcx ++/ycIXzTH5kvIcPoa8oCaAuCMgtsMZ2SoBJ37xgwb1qKjfVAOfju1qVYgI5jqQLp +sNsS4FqyS7jpKoUxbcShvOui5GUunXgxZ/VMd7rqSqbC8ozKXvOEYaJWf9lY7DCR +RdFR03CmWZ8EDO2JM1sKRni9gVz7wflSPjI8vDw9EbQzrW0Du64i4aBC2/kuJq9g +7rEQrjMRh8/kQigCP1BUk1l+r/gPO8UeIBc7tTls0FsPIkKQq7cPNQgNiNIIV8v2 +iLW1bZmT2vswpiyqbz7LtGzYj+x1Ce+7U9iXgY08QgbiqiXl/eNw/xNVkFaMMDc2 +4+APQMF790OaZvWtme9XzePsy16wLgF4G/Xh1M8RfG3z7vXXm4Vl38iErZkXTmMl +63Z87xA2LHvcoMEpP/nJHnvts3X2o3u7FFG9 +-----END CERTIFICATE----- === added file 'client-key.pem' --- client-key.pem 1970-01-01 00:00:00 +0000 +++ client-key.pem 2007-10-20 21:38:25 +0000 @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKgIBAAKCAgEA1gMLF0+I3igioO/+IPCk5sBGevPM32iXqqc6ZKXNuSeO0Jq+ +TH+jYilCAKow/CsnAKRtXMFaCxL8b0DoqdpUsy+HsYqU6WTuOTujeW5ShuFyqiUR +NlROTdY9mfoyZnxRGh6jA2G/fnZZ0mdnNHNXdqnIAh4DkeYmR1kAhrRCt8vR31rB +aVC7s32DCKWGYz4nPpALorE+XJf8CA1SPzG6Qd/75PbzKUQhUY72znDe6xFuFIob +lUhZsxTZPfUEfHty4tR7am15prSabPbteroBkDuBA+IJOuYuU1NebGSYBS1wbhbm +RWB/FhdgiLuqOKMpQi1YxUWR5fIysGXT7TVGdNXCZKI/jPWHPZuhdXS2ny04Yb0i +zLkX1Lx2PWxQJBekJJICI2NZjdVU5ul8SzffdQyXe+uyH6RawWiouOwuGmwXOJWW +HwZZl3ZKTrd1HGMYd2E/ik1Cv+0v2q49ohMqU4nOMT1Q7AOA25xydfumc8jADOwA +pmZ1oau4F4Y0/M93hMmqmmVTq2nBtQxZZ+/NTJum/RG96iX3Mykf0F4jLBeIMMB/ +g3JZHf0x447/0j0GAolpthaPCFxv4XWcwceHVqEjGZIEDORgy0FPNP4wLw54up/e +yOPH3MYy+mJxIEHqGrCqGshmjpp4RLP3sraXBqHDYt6SVKQ9HKGMgHpni+cCAwEA +AQKCAgBha4c7+EecoXaJ/lWXlxPpurMauyqStGD+HRvWvycz1s8LJLXlyuCMCa3y +8YZU9CvP/gmOhLHBgsYIuupuj2WpH8TMTAJXcEuFICHdYBwPLEdvLmp0adIvWow2 +MI+K2aJtmm6oVnG+Vo+y2MFBPhQdf1H9rL4BR1w7dEdqClqoog6Kdxy+HTMklMj2 +Qas4OA3TS+0QBVEXA1SGMdIz1CYuYJCg/M1aBpqILuUounavWQLcNLYzsXirrZzq +uENvix6UJRd9LhKHkYUOfyVBjbSyfHPRWa7L8gY6hiPggbY1/SZF5wSxpiiT3NZj +x9HH8HYSmuPjATVWEHeElwXu4CaOqax6OCMUwrIHWXUq/ydWjDXk/vK6PU1d2Fvh +Y274QkBHwrDRZ7HiDLZDO6VVJx9O0WKajkLqidVMFzSZem+rxlIW8KwJUto8ObEi +DK6VfkD0f1VfhWbVyb4dzgmloseHc9S/31ZBY4MfESpgVOh3rg12eXl4BVTRzDlf +Qq0Z9hKuBaURt/CNv+ALpAp+u6mvzmNuSnjuXOkD3bmYwPZljykf1A3Cr8eyPK1E +4PbLZN6Te7pbbjDvj+k23dzk1HUZ+I+5ykJi1v7tsQIiUOxFVJRMEmsfG+rMmKp6 ++9fsgcqK0wpnZUJ325ghjaaGR4i4AUMYVHn+mTmbhCDis97Q2QKCAQEA71FMbFGt +pBzYOCn9hMrBDIAU2tQNkmukeg7HremIyh5zm3Lt9YdSNGM/D1WqN3XdsdVczLoy +l89jUTyoqoFQkackTkPxftUd6655CuOqSGFhr8xVk7SSvC9ZbwV69WgVJkYfrYv5 +3gXPAmBgc3S8S400ik5AtKh3XIQMsWwKPdpHKQZvbpyw851IoRQEzUhiT/Bi3Uug +PzZHyFNmJMrPqSaot73zwkmkPK3KOCgAxStB/Ab2JNx8yjfL2UxIqunU5TaO65Wp +2Be2mSPwYe0FZ8oE4HPKmOWJC7jHgT88OzdaeT9jBTOecGbvm/Wz2Rr83wUkMZlf ++/sF2XOrIsCSjQKCAQEA5O4oDf+t0vvEonTC9YE5bQ/rkSz4SKYI23nnI+u19C4E +ntsHzrU9VMrnZV/Zc+j+z5hObgN179ePTvHLSjhvfo1zONGznl05YZRUE0dLf9bw +ns3tNjxYc86BtYGGJfX8WqgsTLCTotrRY8hk2D2NupzB21WfaalWi2pZEsEq5vk4 +gqh7IwraS7x56GQTQIPfdQmv9XNfDh7VO4bcaqAI1n+KIpJTFVcEHnu6RpoQyE12 +F2oEQOo/S4nY2N+MZioavO46o9r4p/3V8CyuXQghkal2GHZeCkTpMto1BayHYOKc +ZwxxLV/F/Nhr2BqJwiUojvbDcjcDeZVysjHQfNI1QwKCAQEAwwbd4OgvOa7IBf0y +PSV+bVFzrWFiLhDK2S1yTKgkcZKfY+8lPRIqS8cVfMmzDb9gC6x1E+IpmM3JgkqM +qWb44bn0PFPiWhoTaB2nRtiBzLqPgVRj6Rse+X0cxP0SVyubELXU2vlXhzf0m1sv +PufDC07nok5jLNadbyetsGj7b6ySkTxNUzcefWmP5rUJtMFoXPzplK4syVbS6M4O +T613zcFTfWmvXIXm8gwu39S5y/SUsW566U9F4wXVeiBQl/g3JxRvJE2zPAcXJ3XC +UAt4fDyF/ORgFnn4VTUgYJPH4foaIPUnHPYUCEXavp5dEnCL4rOt6z2ymwbWnX9F +1+xXgQKCAQEAr1XAE8ipRxhRJ+OplgKdCuzQjOYWWv3fdslMwHQ9bYD5RPmYZzbk +fFbTFw9sKpxe3HxYRWYdI87DEcGa44OJ0TFg+DmUCkx4MEY8hm9qYcUrkVVCwvFB +BaE6Mtu69MQLvRtkom/zAx34lSXcJsouDKkWyHgxmel6QVj4U6bixvhF0bxcYyBi +xPLbo6NSI21c7fS3cZQlT1lKE0dc9cUQP8h68bOEMqnsm7RDnONOyzqYoaIvE9DV +HXO8Q44jp/PDesQy4WBKZc/B1StDeDlMDJXvvxiZOeBBgxMg3PGg1hF5nGspG4lo +yBixsFfS/oEbKTPRyV5dKPfPWq9QcOlGpQKCAQEAwe2qtx7tzI7cdx1JLSHNZyPp +h9/WTjYKVg3Sl6dXfDsNTqIUkRrwpB1tEZXs1PIq5641ev4LmRxGDPLJWIwNZ1LN +slR12towc6LTY/05OIBHrTIAW8fGs2E4YcqYcLWsWuzU9BsJLQeBdUth9zF6qZoK +USN6a3326dx8lhUcQ4zJ9c/DtK+99WAXtt0Cki7aICvLbcsmRbKPHpJyzRpDI7uH +Ex/UgjSR1w5L2Seou3e6ljxYGAecqa86HIkHVleDIrIYl6GlKDBKSVzfmLh/97mi +o3oTNqRal934xYS6ZTId/ZkRL5c78U/NJlK1H5gYzfL65mJ1+c2N3cR3sy4jtQ== +-----END RSA PRIVATE KEY----- === modified file 'clients.conf' --- clients.conf 2019-02-09 23:34:15 +0000 +++ clients.conf 2008-07-29 03:35:39 +0000 @@ -1,93 +1,33 @@ -# Default settings for all clients. These values are the default -# values, so uncomment and change them if you want different ones. [DEFAULT] - -# How long until a client is disabled and not be allowed to get the -# data this server holds. -;timeout = PT5M - -# How often to run the checker to confirm that a client is still up. -# Note: a new checker will not be started if an old one is still -# running. The server will wait for a checker to complete until the -# above "timeout" occurs, at which time the client will be disabled, -# and any running checker killed. -;interval = PT2M - -# Extended timeout is an added timeout that is given once after a -# password has been sent sucessfully to a client. This allows for -# additional delays caused by file system checks and quota checks. -;extended_timeout = PT15M - -# What command to run as "the checker". -;checker = fping -q -- %%(host)s - -# Whether to approve a client by default after the approval delay. -;approved_by_default = True - -# How long to wait for approval. -;approval_delay = PT0S - -# How long one approval will last. -;approval_duration = PT1S - -# Whether this client is enabled by default -;enabled = True - - -;#### -;# Example client -;[foo] -; -;# TLS public key ID -;key_id = f33fcbed11ed5e03073f6a55b86ffe92af0e24c045fb6e3b40547b3dc0c030ed -; -;# OpenPGP key fingerprint -;fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 -; -;# This is base64-encoded binary data. It will be decoded and sent to -;# the client matching the above fingerprint. This should, of course, -;# be OpenPGP encrypted data, decryptable only by the client. -;secret = -; hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234 -; REJMVv7lBSrPE2132Lmd2gqF1HeLKDJRSVxJpt6xoWOChGHg+TMyXDxK+N -; Xl89vGvdU1XfhKkVm9MDLOgT5ECDPysDGHFPDhqHOSu3Kaw2DWMV/iH9vz -; 3Z20erVNbdcvyBnuojcoWO/6yfB5EQO0BXp7kcyy00USA3CjD5FGZdoQGI -; Tb8A/ar0tVA5crSQmaSotm6KmNLhrFnZ5BxX+TiE+eTUTqSloWRY6VAvqW -; QHC7OASxK5E6RXPBuFH5IohUA2Qbk5AHt99pYvsIPX88j2rWauOokoiKZo -; t/9leJ8VxO5l3wf/U64IH8bkPIoWmWZfd/nqh4uwGNbCgKMyT+AnvH7kMJ -; 3i7DivfWl2mKLV0PyPHUNva0VQxX6yYjcOhj1R6fCr/at8/NSLe2OhLchz -; dC+Ls9h+kvJXgF8Sisv+Wk/1RadPLFmraRlqvJwt6Ww21LpiXqXHV2mIgq -; WnR98YgSvUi3TJHrUQiNc9YyBzuRo0AjgG2C9qiE3FM+Y28+iQ/sR3+bFs -; zYuZKVTObqiIslwXu7imO0cvvFRgJF/6u3HNFQ4LUTGhiM3FQmC6NNlF3/ -; vJM2hwRDMcJqDd54Twx90Wh+tYz0z7QMsK4ANXWHHWHR0JchnLWmenzbtW -; 5MHdW9AYsNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm -; 4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O -; QlnHIvPzEArRQLo= -; -;# Host name; used only by the checker, not used by the server itself. -;host = foo.example.org -;#### - -;#### -;# Another example client, named "bar". -;[bar] -;# The key ID is not space or case sensitive -;key_id = F33FCBED11ED5E03073F6A55B86FFE92 AF0E24C045FB6E3B40547B3DC0C030ED -; -;# The fingerprint is not space or case sensitive -;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 -; -;# If "secret" is not specified, a file can be read for the data. -;secfile = /etc/keys/mandos/bar-secret.bin -; -;# An IP address for host is also fine, if the checker accepts it. -;host = 192.0.2.3 -; -;# Parameters from the [DEFAULT] section can be overridden per client. -;interval = PT1M -; -;# This client requires manual approval before it receives its secret. -;approved_by_default = False -;# Require approval within 30 seconds. -;approval_delay = PT30S -;#### +timeout = 1h +interval = 5m +checker = fping -q -- %%(fqdn)s + +# Example +[foo] +fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 +secret = Base+64+encoded+OpenPGP+encrypted+data/= +# secfile = /etc/mandos/foo-secret.txt.asc +fqdn = foo.example.org +checker = fping -q -- %%(fqdn)s +timeout = 10m + +[braxen_client] +fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 +secret = + hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234REJMVv + 7lBSrPE2132Lmd2gqF1HeLKDJRSVxJpt6xoWOChGHg+TMyXDxK+NXl89vGvdU1Xf + hKkVm9MDLOgT5ECDPysDGHFPDhqHOSu3Kaw2DWMV/iH9vz3Z20erVNbdcvyBnuoj + coWO/6yfB5EQO0BXp7kcyy00USA3CjD5FGZdoQGITb8A/ar0tVA5crSQmaSotm6K + mNLhrFnZ5BxX+TiE+eTUTqSloWRY6VAvqWQHC7OASxK5E6RXPBuFH5IohUA2Qbk5 + AHt99pYvsIPX88j2rWauOokoiKZot/9leJ8VxO5l3wf/U64IH8bkPIoWmWZfd/nq + h4uwGNbCgKMyT+AnvH7kMJ3i7DivfWl2mKLV0PyPHUNva0VQxX6yYjcOhj1R6fCr + /at8/NSLe2OhLchzdC+Ls9h+kvJXgF8Sisv+Wk/1RadPLFmraRlqvJwt6Ww21Lpi + XqXHV2mIgqWnR98YgSvUi3TJHrUQiNc9YyBzuRo0AjgG2C9qiE3FM+Y28+iQ/sR3 + +bFszYuZKVTObqiIslwXu7imO0cvvFRgJF/6u3HNFQ4LUTGhiM3FQmC6NNlF3/vJ + M2hwRDMcJqDd54Twx90Wh+tYz0z7QMsK4ANXWHHWHR0JchnLWmenzbtW5MHdW9AY + sNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm4T2zw4dxS5NswX + WU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2OQlnHIvPzEArRQLo= + =iHhv +fqdn = localhost +interval = 5m === removed file 'common.ent' --- common.ent 2019-09-03 19:06:41 +0000 +++ common.ent 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ - - - === added file 'crl.pem' --- crl.pem 1970-01-01 00:00:00 +0000 +++ crl.pem 2007-10-20 21:38:25 +0000 @@ -0,0 +1,18 @@ +-----BEGIN X509 CRL----- +MIIC0zCBvDANBgkqhkiG9w0BAQUFADCBjDELMAkGA1UEBhMCU0UxCzAJBgNVBAgT +AkJMMRAwDgYDVQQHEwdSb25uZWJ5MSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz +IFB0eSBMdGQxFjAUBgNVBAMTDUJyYXhlbiB1bml0ZWQxIzAhBgkqhkiG9w0BCQEW +FGJlbG9ybkBmdWt0LmJzbmV0LnNlFw0wNzEwMTUxNzQ2MTlaFw0wNzExMTQxNzQ2 +MTlaMA0GCSqGSIb3DQEBBQUAA4ICAQCYTCUYRfx8+lTNXsVMhLT/890agPGj7BQw +NhwHTZuEudPTxBtOLPf0za4z7eGTD9ggu7SayQWEOV4bfjv1yiOLzf6vEHdzv1Ee +mlLhYgDMIhACrQmfKAmjoabsaK+VccBJW/R1oNW5Z9sWjFP91+3T7lfp8pPvWAlf ++9mJaaysd1yguY0OITAIWEL2lLlGtd85RYLvJe2nWZ6GrH5mIEYA7IQrnPgcU3ij +eAEn88I7EofUHfn1TMpMDJgMKm/edvEerLKb62AblcGLfo4gOBQWcvCAWLPzqxhE +wKag1xL8ucG6250yfkYBf3KEMLZAU8py5MwaIqMVOQzz3gsQ7dE87xR5GndLvPvr +149RbKDSZDdPDOEmCqlmb/Sxppm7jsNwqAphrZlNsBgLTrxih7Ex1cFph4jA5AZ5 +Hgqpftb94CdauOPz/AVu5aeXIwG0dpxhN0dtemwhHIxslFwDtuFWwcmP3upDZUOM +Q4ZZBp2A1lhDW86w3law8E2TCuDFmwNtqp5zOMtOwAJF2RK2DquaaxKQG0JRhdTi +f3mzjTTRPXomBgf7U1W25RFMO91uslCijAr/ELAa9SkFYWArnbfGabDsoy3OGL8D +fjuaz2eIdwvASyPwUkGlfeFBRDNIRCZ3szsvtThMFWUxKvKTRBTJf4AFLwx3XWOS +R6IxSk3t0A== +-----END X509 CRL----- === removed file 'dbus-mandos.conf' --- dbus-mandos.conf 2011-10-02 19:18:24 +0000 +++ dbus-mandos.conf 1970-01-01 00:00:00 +0000 @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - === removed directory 'debian' === removed file 'debian/changelog' --- debian/changelog 2019-09-04 21:17:42 +0000 +++ debian/changelog 1970-01-01 00:00:00 +0000 @@ -1,928 +0,0 @@ -mandos (1.8.9-2) unstable; urgency=medium - - * Fix failing autopkgtest. - * debian/tests/control (mandos-check/Restrictions): Add "allow-stderr". - - -- Teddy Hogeborn Wed, 04 Sep 2019 23:14:06 +0200 - -mandos (1.8.9-1) unstable; urgency=medium - - * New upstream release. - * Fix "Python2 removal in sid/bullseye" by using Python 3 instead - (Closes: #936987) - * debian/control (Build-Depends, Build-Depends-Indep): Move "systemd" - from indep to regular build-depends. - (Build-Depends-Indep, Package: mandos/Depends): Depend on Python 3 and - Python 3 modules instead of Python 2. - - -- Teddy Hogeborn Tue, 03 Sep 2019 20:58:27 +0200 - -mandos (1.8.8-1) unstable; urgency=medium - - * New upstream release. - * debian/po/de.po: New; Fix "[INTL:de] Initial German debconf - translation" by including the contributed translation (Closes: - #934373) - * debian/po/fr.po: New; Fix "[INTL:fr] French debconf templates - translation" by including the contributed translation (Closes: - #934888) - * debian/po/sv.po: New Swedish translation. - * debian/mandos.postinst: Only reload D-Bus daemon if new user was - created. - * debian/mandos.dirs (usr/lib/sysusers.d): New. - * debian/mandos-client.dirs (usr/lib/sysusers.d): - '' - - - -- Teddy Hogeborn Sun, 18 Aug 2019 22:01:13 +0200 - -mandos (1.8.7-1) unstable; urgency=medium - - * New upstream release. - * debian/upstream/metadata: New. - * debian/mandos-client.postrm: Use the same logic as the - update_initramfs function in debian/mandos-client.postinst. - * debian/mandos-client.templates (mandos-client/key_id): Line which - should not be wrapped should be prefixed by a space. - * debian/mandos.templates (mandos/key_id): - '' - - * debian/po/en_US.po: New "translation" from ASCII to UTF-8. - * debian/po/templates.pot: Updated. - * debian/source/lintian-overrides - (package-uses-old-debhelper-compat-version): New; set to "10". - * debian/mandos-client.lintian-overrides - (maintainer-script-supports-ancient-package-version): New. - debian/mandos.lintian-overrides - (maintainer-script-supports-ancient-package-version): - '' - - - -- Teddy Hogeborn Mon, 05 Aug 2019 23:22:00 +0200 - -mandos (1.8.6-1) unstable; urgency=medium - - * New upstream release. - * Fix "mandos FTCBFS: hard codes build architecture pkg-config" - by making pkg-config overridable (Closes: #933701) - * debian/mandos.postinst (configure): After creating (or renaming) user - & group, reload D-Bus daemon (if present). - - -- Teddy Hogeborn Sat, 03 Aug 2019 14:51:01 +0200 - -mandos (1.8.5-1) unstable; urgency=medium - - * New upstream release. - * Fix "does not reap children" by reaping children (Closes: #933387) - * debian/mandos-client.README.Debian: Use new-style interface name. - * debian/tests/control: New file; implements autopkgtest support. - * debian/mandos-client.lintian-overrides - (manpage-has-errors-from-man): Remove; unnecessary. - * debian/mandos.lintian-overrides - (init.d-script-needs-depends-on-lsb-base): - '' - - * debian/mandos-client.postinst (update_initramfs): Upstream now - supports dracut(8), so update commands here to and run the correct - command to update initramfs. - * debian/control (Build-Depends): Add GLib -dev package. - (mandos-client/Depends): Add dracut(8) as an alternative dependency to - initramfs-tools. - (mandos-client/Conflicts): New; set to "dracut-config-generic". - (debian/mandos-client.README.Debian): Update for dracut(8) support. - * debian/mandos-client.templates: Reflowed by debconf-gettextize(1). - * debian/mandos.templates: - '' - - * debian/po/POTFILES.in: New. - * debian/po/templates.pot: - '' - - * debian/source/lintian-overrides: New. - * debian/control (Standards-Version): Update to "4.4.0". - - -- Teddy Hogeborn Tue, 30 Jul 2019 20:41:29 +0200 - -mandos (1.8.4-1) unstable; urgency=medium - - * Fix "dirs in initrd are not accessible by mandos plugin-runner" by - making sure UMASK is set, no matter what other packages have installed - in "/usr/share/initramfs-tools/conf-hooks.d". (Closes: #926641) - * Fix "LeakSanitizer: detected memory leaks, fails to decrypt" - by fixing memory leak in plugin-runner. (Closes: #926643) - * debian/mandos-client.dirs: Add - "usr/share/initramfs-tools/conf-hooks.d", needed by fix for #926641. - - -- Teddy Hogeborn Tue, 09 Apr 2019 22:05:39 +0200 - -mandos (1.8.3-3) unstable; urgency=medium - - * Fix "src:mandos: modifies d/control during build" by not doing that - anymore. (Closes: #922202) - * debian/rules (override_dh_shlibdeps-arch): Commented out. - - -- Teddy Hogeborn Wed, 13 Feb 2019 09:52:39 +0100 - -mandos (1.8.3-2) unstable; urgency=medium - - * debian/rules (override_dh_shlibdeps-arch): New; conditionally edit - debian/control before running dh_shlibdeps. - - -- Teddy Hogeborn Mon, 11 Feb 2019 12:49:57 +0100 - -mandos (1.8.3-1) unstable; urgency=medium - - * New upstream release. - * debian/watch: Make the ".orig" file name suffix non-optional; - otherwise uscan thinks that ".orig" is part of the version number. - * debian/control (Build-Depends): Changed GnuTLS dependencies; move - 3.6.6 alternative to first in list, and remove dependencies on the - virtual package "gnutls-dev", since we need the version restrictions. - (Package: mandos/Depends): Remove dependency on libgnutls28-dev - package. - (Package: mandos/Suggests): New; set to "libc6-dev, c-compiler". (Used - to find value of "SO_BINDTODEVICE"). - (Package: mandos-client/Depends): Don't depend on openssl anymore; - instead depend on either a gnutls-bin (>= 3.6.6) (in which case TLS - key generation will work), or on libgnutls30 (<< 3.6.0) (in which case - TLS key generation will not be needed). - - -- Teddy Hogeborn Mon, 11 Feb 2019 07:30:32 +0100 - -mandos (1.8.2-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos-client.postinst (create_keys): Ignore failure to remove - bad keys. - - -- Teddy Hogeborn Sun, 10 Feb 2019 11:44:56 +0100 - -mandos (1.8.1-1) unstable; urgency=high - - * New upstream release. - * debian/mandos-client.postinst (create_keys): Remove any bad keys - created by 1.8.0-1. Only create TLS keys if certtool succeeds. - * debian/mandos.postinst (configure): Remove any bad keys from - clients.conf, and inform the user if any were found. - * debian/mandos.templates (mandos/removed_bad_key_ids): New message. - - -- Teddy Hogeborn Sun, 10 Feb 2019 10:00:21 +0100 - -mandos (1.8.0-1) unstable; urgency=medium - - * New upstream release. - * Fix "(tries to) use GnuTLS OpenPGP support" by using raw public keys - when available (Closes: #879538) - * Fix "mandos : Depends: libgnutls30 (< 3.6.0) but 3.6.5-2 is to be - installed" by now also allowing GnuTLS >= 3.6.6 (Closes: #916673) - * debian/control (Standards-Version): Update to "4.3.0". - (Package: mandos-client/Depends): Change from "cryptsetup" to - "cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs". Add "debconf (>= - 1.5.5) | debconf-2.0". - (Source: mandos/Build-Depends): Also allow libgnutls30 (>= 3.6.6). - (Package: mandos/Depends): - '' - and add debconf (>= 1.5.5) | - debconf-2.0". - (Package: mandos/Description): Alter description to match new design. - (Package: mandos-client/Description): - '' - - (Package: mandos-client/Depends): Move "gnutls-bin | openssl" to here - from "Recommends". - * debian/mandos-client.README.Debian: Add --tls-privkey and --tls-pubkey - options to test command. - * debian/mandos-client.postinst (create_key): Renamed to "create_keys" - - all callers changed - and also create TLS key files. Show notice if - new TLS key files were created. - * debian/mandos-client.postrm (purge): Also remove TLS key files. - * debian/mandos-client.lintian-overrides: Override warnings. - * debian/mandos-client.templates: New. - * debian/mandos.lintian-overrides: Override warnings. - * debian/mandos.postinst (configure): If GnuTLS 3.6.6 or later is - detected, show an important notice (once) about the new key_id option - required in clients.conf. - * debian/mandos.templates: New. - * debian/copyright: Update copyright year to 2019. - - -- Teddy Hogeborn Sun, 10 Feb 2019 05:52:49 +0100 - -mandos (1.7.20-1) unstable; urgency=medium - - * New upstream release. - * Fix "[tethys] mandos-client: Mandos client fails while booting but - works from chroot into unpacked initramfs" by setting system clock if - necessary (Closes: #894495) - * Fix "initramfs boot script assumes internal cryptsetup implementation - details and is now broken" by only using documented - interfaces (Closes: #904899) - * debian/mandos-client.dirs: Add - "usr/share/initramfs-tools/scripts/local-premount" and - "usr/share/initramfs-tools/conf.d", and remove - "usr/share/initramfs-tools/conf-hooks.d". - * debian/control (mandos-client/Depends): Add "(>= 0.99)" to dependency - on "initramfs-tools". - * debian/control (Source: mandos/Rules-Requires-Root): New; set to - "binary-targets". - (Standards-Version): Update to "4.2.0". - - -- Teddy Hogeborn Sun, 19 Aug 2018 22:14:04 +0200 - -mandos (1.7.19-1) unstable; urgency=medium - - * New upstream release. - * Fix "fails with "LeakSanitizer has encountered a fatal error"" by not - using LeakSanitizer in affected binary (Closes: #886595) - - -- Teddy Hogeborn Thu, 22 Feb 2018 19:47:59 +0100 - -mandos (1.7.18-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Mon, 12 Feb 2018 16:00:11 +0100 - -mandos (1.7.17-1) unstable; urgency=medium - - * New upstream release. - * Fix "fails with "LeakSanitizer has encountered a fatal error"" - by fixing memory leak in plugin-runner (Closes: #886595) - * debian/control (Build-Depends): Also depend on "libgnutls28-dev (<< - 3.6.0) | libgnutls30 (<< 3.6.0)". - (Package: mandos/Depends): - '' - - * debian/compat: Change to "10". - * debian/watch (version): Change to "4". - (opts/pgpsigurlmangle): Remove. - (opts/pgpmode): New; set to "auto". - (URL): Change to "https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@ - @ANY_VERSION@(?:\.orig)?@ARCHIVE_EXT@". - * debian/copyright: Update copyright year to 2018. - * debian/rules: Support the "noopt" and "parallel" DEB_BUILD_OPTIONS. - (override_dh_fixperms-arch): Use the DEB_HOST_MULTIARCH - variable directly instead of shelling out to "dpkg-architecture". - * debian/control (Standards-Version): Update to "4.1.3". - (Build-Depends): Change version of debhelper dependency to ">= 10". - * debian/mandos.lintian-overrides - (init.d-script-needs-depends-on-lsb-base): Change line number to "46". - - -- Teddy Hogeborn Sat, 10 Feb 2018 19:09:50 +0100 - -mandos (1.7.16-1) unstable; urgency=medium - - * New upstream release. - * debian/copyright (License): Use program name explicitly. - (Format): Use https in URL. - * debian/control (Priority): Change from "extra" to "optional". - (Standards-Version): Update to "4.0.1". - - -- Teddy Hogeborn Sun, 20 Aug 2017 21:05:26 +0200 - -mandos (1.7.15-1) unstable; urgency=medium - - * New upstream release. - * Upstream release fixes "Seems not to be honoring zeroconf option at - mandos.conf" (Closes: #855589) - * debian/mandos.lintian-overrides (mandos): Add new line - "init.d-script-needs-depends-on-lsb-base etc/init.d/mandos (line 49)". - * debian/copyright: Update copyright year to 2017. - - -- Teddy Hogeborn Thu, 23 Feb 2017 21:29:36 +0100 - -mandos (1.7.14-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos-client.postinst (create_key): Stop GPG agent after - running mandos-keygen. - * debian/control (Package: mandos/Depends): Add "systemd-sysv | lsb-base - (>= 3.0-6)", change "gnupg" to "gnupg2 | gnupg", and change - "libgpgme11-dev" to "libgpgme-dev | libgpgme11-dev". - - -- Teddy Hogeborn Wed, 25 Jan 2017 20:36:03 +0100 - -mandos (1.7.13-1) unstable; urgency=medium - - * New upstream release. - * Fix "fails to install noninteractively" by using the "%no-protection" - statement in the GnuPG batch parameter file. (Closes: #840001) - - -- Teddy Hogeborn Sat, 08 Oct 2016 06:31:07 +0200 - -mandos (1.7.12-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Wed, 05 Oct 2016 22:06:55 +0200 - -mandos (1.7.11-1) unstable; urgency=high - - * New upstream release. - * debian/control (Source: mandos/Vcs-Bzr): Change to use HTTPS. - (Vcs-Browser): - '' - - - -- Teddy Hogeborn Sat, 01 Oct 2016 16:20:48 +0200 - -mandos (1.7.10-1) unstable; urgency=high - - * New upstream release. - * debian/rules (override_dh_fixperms-arch): Also exclude - "etc/mandos/plugin-helpers" from changes by dh_fixperms. - * debian/mandos-client.postinst: Fix the permissions of - "/etc/mandos/plugin-helpers" for those systems which had a fresh - install of an older version. - - -- Teddy Hogeborn Thu, 23 Jun 2016 22:00:29 +0200 - -mandos (1.7.9-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Wed, 22 Jun 2016 07:30:12 +0200 - -mandos (1.7.8-1) unstable; urgency=medium - - * New upstream release. - * Fix "bad gpgme_op_decrypt: GPGME: Decryption failed." by copying - /usr/bin/gpg-agent into initramfs (Closes: #819982) - * debian/control (Homepage): Change URL to use HTTPS. - (Standards-Version): Update to 3.9.8. - * debian/copyright (Source): Change URL to HTTPS. - * debian/mandos-client.README.Debian: Change wording to match updated - capabilities. - - -- Teddy Hogeborn Tue, 21 Jun 2016 21:36:10 +0200 - -mandos (1.7.7-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos-client.postinst (configure): If older version, fix - permissions on plugin helper directory. Also fix permissions on - plugin helper local override directory (/etc/mandos/plugin-helpers), - but only if not listed by "dpkg-statoverride". - * debian/rules (override_dh_fixperms-arch): Exclude plugin helper - directory from dh_fixperms. - * debian/mandos.postinst (configure): Fix state directory permissions, - but only if not listed by "dpkg-statoverride". - * debian/mandos-client.lintian-overrides: Do not warn about permissions - on plugin helper directory. - * debian/mandos.dirs (usr/lib/tmpfiles.d): Added. - - -- Teddy Hogeborn Sat, 19 Mar 2016 22:58:49 +0100 - -mandos (1.7.6-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Source: mandos/Build-Depends-Indep): Remove - "python-avahi". - (Source: mandos/Build-Depends-Indep): Change "python-gi | - python-gobject" to "python-gi"; i.e. remove "python-gobject". - - -- Teddy Hogeborn Sun, 13 Mar 2016 22:58:23 +0100 - -mandos (1.7.5-1) unstable; urgency=high - - * New upstream release. - * debian/mandos.postinst (configure): If old version was 1.7.4-1 or - 1.7.4-1~bpo8+1, fix situation where clients.pickle file is owned by - root. - - -- Teddy Hogeborn Tue, 08 Mar 2016 01:09:55 +0100 - -mandos (1.7.4-1) unstable; urgency=medium - - * New upstream release. - * initramfs-tools-script: Fix "Call to configure_network in initramfs - script broken due to set -e" by surrounding call by "set +x" and "set - -e" (Closes: #816513) - * debian/control: (Source: mandos/Build-Depends-Indep): Change - "python-gobject | python-gi" to "python-gi | python-gobject" - (Package: mandos/Depends): - '' - - - -- Teddy Hogeborn Sat, 05 Mar 2016 23:10:07 +0100 - -mandos (1.7.3-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Mon, 29 Feb 2016 22:26:38 +0100 - -mandos (1.7.2-1) unstable; urgency=medium - - * New upstream release. - * Fix "Uses unneeded and obsolete version specific python packages" - by removing version-specific dependencies (Closes: #811159) - * debian/control (Source: mandos/Build-Depends): Add (>= 3.3.0) to - "libgnutls28-dev" and "gnutls-dev". - (Source: mandos/Build-Depends-Indep): Remove "python2.7-gnutls", - "python2.7", "python2.7-dbus", "python2.7-avahi", and - "python2.7-gobject"; replace with "python (>= 2.7), python (<< 3)", - "python-dbus", "python-avahi", "python-gobject | python-gi". - (Package: mandos/Depends): Remove "python-gnutls" and - "python2.7-gnutls", add "libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= - 3.3.0)". Add "python (<< 3)". Remove "python2.7-dbus", - "python2.7-avahi", "python2.7-gobject", and "python2.7-urwid". - Replace "python-gobject" with "python-gobject | python-gi" and "gnupg - (<< 2)" with "gnupg". - (Package: mandos-client/Depends): Replace - "gnupg (<< 2)" with "gnupg". - (Source: mandos/Standards-Version): Change to 3.9.7. - * debian/copyright (Copyright): Update copyright year. - - -- Teddy Hogeborn Sun, 28 Feb 2016 16:09:01 +0100 - -mandos (1.7.1-2) unstable; urgency=medium - - * debian/control (Package: mandos/Depends): Fix "Please drop versioned - dependency on initscripts package" by removing initscripts dependency - (Closes: #804967) - * debian/rules (override_dh_fixperms) Fix "FTBFS when built with - dpkg-buildpackage -A (No such file or directory)" by splitting into - "override_dh_fixperms-arch" and "override_dh_fixperms-indep". - (Closes: #806073) - - -- Teddy Hogeborn Sat, 05 Dec 2015 02:27:40 +0100 - -mandos (1.7.1-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Sat, 24 Oct 2015 19:43:40 +0200 - -mandos (1.7.0-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Standards-Version): Updated to "3.9.6". - (Build-Depends): Add "libnl-route-3-dev". - (Package: mandos-client/Recommends): Added "gnutls-bin | openssl" for - the generating of DH parameters. - * debian/mandos-client.README.Debian: Update example command line to use - new MANDOSPLUGINHELPERDIR environment variable. Also document the new - dhparams.pem file. - * debian/mandos-client.postinst: Create DH parameters file. - * debian/mandos.prerm: Don't run init script, use only invoke-rc.d. - * debian/mandos-client.postinst: Don't use absolute paths to commands. - * debian/mandos-client.postrm: Don't use absolute paths to commands. - Also remove dhparams.pem file. - * debian/copyright (Copyright): Update copyright year. - * Upstream changed systemd service file to implicitly be of - "Type=dbus". (Closes: #786845) - - -- Teddy Hogeborn Mon, 10 Aug 2015 22:00:29 +0200 - -mandos (1.6.9-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Build-Depends): Fix "still uses GnutLS 2.x" by - changing from "libgnutls-dev" to "libgnutls28-dev | gnutls-dev" - (Closes: #762349) - - -- Teddy Hogeborn Sun, 05 Oct 2014 22:05:06 +0200 - -mandos (1.6.8-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Source: mandos/Build-Depends-Indep): Since upstream - now requires Python 2.7, depend on exactly the python2.7 package and - all the Python 2.7 versions of the python modules. - (Package: mandos/Depends): - '' - but still depend on python (>=2.7) - and the generic versions of the Python modules; this is for mandos-ctl - and mandos-monitor, both of which are compatible with Python 3, and - use #!/usr/bin/python. - - -- Teddy Hogeborn Wed, 06 Aug 2014 22:55:24 +0200 - -mandos (1.6.7-1) unstable; urgency=medium - - * New upstream release. - - -- Teddy Hogeborn Thu, 17 Jul 2014 05:22:45 +0200 - -mandos (1.6.6-1) unstable; urgency=medium - - * New upstream release. - * debian/mandos.postinst: Fix typo in comment. - * debian/control (mandos/Recommends): Changed to "ssh-client | fping". - (mandos-client/Recommends): New; set to "ssh". - - -- Teddy Hogeborn Sun, 13 Jul 2014 22:49:21 +0200 - -mandos (1.6.5-3) unstable; urgency=medium - - * debian/control (mandos-client/Depends): Add "dpkg-dev (>=1.16.0)"; - initramfs-tools-hook runs "dpkg-architecture -qDEB_HOST_MULTIARCH". - (Closes: #750221) - - -- Teddy Hogeborn Fri, 06 Jun 2014 04:27:15 +0200 - -mandos (1.6.5-2) unstable; urgency=medium - - * debian/rules (override_dh_auto_test-arch): New; does nothing. Fixes - FTBFS for build-indep. - - -- Teddy Hogeborn Tue, 13 May 2014 08:08:31 +0200 - -mandos (1.6.5-1) unstable; urgency=medium - - * New upstream release. - * debian/copyright: Change year to "2014". - * debian/control (Build-Depends, Build-Depends-Indep): Moved build - dependencies of "mandos" package to "Build-Depends-Indep". - * debian/upstream/signing-key.asc: New; upstream source public key. - * debian/control (Standards-Version): Updated to "3.9.5". - * debian/control (mandos/Depends): Remove the dependency on - "avahi-daemon (>= 0.6.31-3) | systemd-sysv". It is unnecessary - since we have a workaround in debian/mandos.postinst anyway. - - -- Teddy Hogeborn Sun, 11 May 2014 22:16:33 +0200 - -mandos (1.6.4-1) unstable; urgency=medium - - * New upstream release. - * debian/control (Build-Depends): Add Python dependencies to - successfully run self-tests. - * debian/copyright: GPLv3 now has its own license file - use it. - * debian/watch: Set PGP signature URL. - - -- Teddy Hogeborn Sun, 16 Feb 2014 14:09:25 +0100 - -mandos (1.6.3-1) unstable; urgency=low - - * New upstream release. - * debian/control (Build-Depends): Added "systemd". - * debian/mandos.dirs (lib/systemd/system): New. - * debian/mandos-client.README.Debian: Refer to architecture libdir. - * debian/control (mandos/Depends): Add "avahi-daemon (>= 0.6.31-3) | - systemd-sysv". - * debian/mandos.postinst: If avahi-daemon is version 0.6.31-2 or older, - edit /etc/init.d script headers Required-Start - and Required-Stop to have "avahi" instead of - "avahi-daemon", before insserv(8) sees it. - * debian/mandos-client.lintian-overrides: Libdir changes. - * debian/rules (override_dh_fixperms): - '' - - - -- Teddy Hogeborn Tue, 21 Jan 2014 22:01:30 +0100 - -mandos (1.6.2-1) unstable; urgency=low - - * New upstream release. - * debian/compat: Changed to "9". - * debian/control (Build-Depends): Changed debhelper version to (>= 9). - (Standards-Version): Updated to "3.9.4". - (DM-Upload-Allowed): Removed. - (mandos/Depends): Add "initscripts (>= 2.88dsf-13.3)" to be able to - use the "/run" directory (for mandos.pid). - * debian/copyright (Copyright): Update year. - * Fix "Mandos/gnutls fails to establish connection, "an algorithm that - is not enabled was negotiated"" fixed by upstream. (Closes: #702120) - - -- Teddy Hogeborn Thu, 24 Oct 2013 22:33:40 +0200 - -mandos (1.6.1-1) unstable; urgency=low - - * New upstream release. - * debian/control (mandos/Depends): No longer depends on - python-gnupginterface, but does - depend on gnupg (<< 2). - (Build-Depends): Depend on debhelper 8.9.7 for using "override-*-arch" - and "override-*-indep" targets in debian/rules. - * debian/mandos-client.README: Update Linux documentation link. - * debian/rules: Completely rewritten to use debhelper v7. - * initramfs-tools-hook: Bug fix: Make sure the right version of GnuPG is - copied into the initramfs image. Always assume that GPGME is used to - avoid searching for it since the path might not be /usr/lib. Thanks - to Félix Sipma for the initial bug report, - and also thanks to Dick Middleton for some more - debugging. (Closes: #721903) - * Fix "bashism in /bin/sh script" fixed by upstream. (Closes: #690639) - - -- Teddy Hogeborn Sun, 13 Oct 2013 19:03:23 +0200 - -mandos (1.6.0-1) unstable; urgency=low - - * New upstream release. - * debian/copyright (Copyright): Join the two lines to a single line. - * debian/mandos-client.README.Debian: Update to refer to the new - location of the example network hooks, and the new feature of using - all network interfaces. - * debian/mandos-client.docs (network-hooks.d): Removed. - * debian/mandos-client.examples (network-hooks.d): New. - * debian/rules (binary-common): Added "dh_installexamples". - (binary-common/dh_fixperms): Exclude new location of - "network-hooks.d". - - -- Teddy Hogeborn Mon, 18 Jun 2012 00:15:23 +0200 - -mandos (1.5.5-1) unstable; urgency=low - - * New upstream release. - * debian/copyright (Format): Updated to - "http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/". - * debian/control (Build-Depends): Removed "man, locales-all". - - -- Teddy Hogeborn Fri, 01 Jun 2012 20:30:41 +0200 - -mandos (1.5.4-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 20 May 2012 15:38:34 +0200 - -mandos (1.5.3-1.2) unstable; urgency=low - - * Non-maintainer upload. - * Set Architecture to linux-any. (Closes: #647670) - - -- Robert Millan Sun, 22 Apr 2012 16:22:01 +0200 - -mandos (1.5.3-1.1) unstable; urgency=low - - * Non-maintainer upload. - * Fix "mandos FTBFS on buildds": add build-dependency on locales-all and - pass LC_ALL to dh_auto_build to make sure we have and use the en_US.UTF-8 - locale for manpage creation. - (Closes: #656178) - - -- gregor herrmann Tue, 31 Jan 2012 17:56:05 +0100 - -mandos (1.5.3-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 15 Jan 2012 22:05:54 +0100 - -mandos (1.5.2-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 08 Jan 2012 11:17:20 +0100 - -mandos (1.5.1-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 01 Jan 2012 21:53:31 +0100 - -mandos (1.5.0-1) unstable; urgency=low - - * New upstream release. - * debian/control (mandos-client/Depends): Added "initramfs-tools". - * debian/mandos-client.README.Debian: Corrected mail address and adjust - wording. - * debian/rules (binary-common): Exclude new nework-hooks.d directory - from dh_fixperms. - * debian/mandos-client.README.Debian: Document network hook facility. - * debian/mandos-client.docs (network-hooks.d): Added. - * debian/mandos.dirs (var/lib/mandos): Added. - * debian/mandos.postinst: Fix ownership of /var/lib/mandos. - * debian/control (mandos/Depends): Added "python-gnupginterface". - - -- Teddy Hogeborn Sun, 01 Jan 2012 05:58:11 +0100 - -mandos (1.4.1-1) unstable; urgency=low - - * New upstream release. - * debian/control (Build-Depends): Added "man". - * debian/control (Conflicts): Changed to "Breaks:". - * debian/copyright: Updated format. - * debian/mandos-client.postinst: Use "set -e" instead of "#!/bin/sh -e". - * debian/mandos-client.postrm: - '' - - * debian/mandos.postinst: - '' - - * debian/mandos.prerm: Consistent magic. - - -- Björn Påhlsson Sat, 15 Oct 2011 18:18:52 +0200 - -mandos (1.4.0-1) unstable; urgency=low - - * New upstream release. - * Fix "FTBFS with binutils-gold": Added "-Xlinker --as-needed" to - LDFLAGS in Makefile. (Closes: #632145) - * Fix "/run transition: uses obsolete /dev/.initramfs": Try both old and - new PID file locations. (Closes: #643554) - * debian/source/local-options: New; contains "--single-debian-patch". - * debian/control (Standards-Version): Upgraded to "3.9.2". - (DM-Upload-Allowed): New; set to "yes". - * debian/control: Changed domain from "fukt.bsnet.se" to "recompile.se". - * debian/copyright: - '' - - * debian/mandos-client.README.Debian: - '' - - * debian/mandos.README.Debian: - '' - - * debian/watch: - '' - - * debian/control (mandos/Description): Fix language to placate lintian. - - -- Teddy Hogeborn Sun, 09 Oct 2011 19:15:08 +0200 - -mandos (1.3.1-1) unstable; urgency=low - - * New upstream release. - * Conflict with correct version of dropbear. - * New version uses argparse; depend on python (<=2.7) | python-argparse. - - -- Teddy Hogeborn Wed, 27 Jul 2011 19:47:17 +0200 - -mandos (1.3.0-1) unstable; urgency=low - - * New upstream release. - * debian/control (mandos): Depend on Python 2.6, remove dependency on - python-multiprocessing. - (mandos-client): Conflict with dropbear (<< 0.52-5). - * debian/mandos-client.postrm (purge): Bug fix: update initramfs also on - purge. - * debian/mandos-client.lintian-overrides: Added plugins.d/plymouth. - - -- Teddy Hogeborn Tue, 08 Mar 2011 20:22:57 +0100 - -mandos (1.2.3-1) experimental; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Mon, 11 Oct 2010 19:37:31 +0200 - -mandos (1.2.2-1) experimental; urgency=low - - * New upstream release. - * plugins.d/splashy.c: Only use ELIBBAD if defined. (Closes: #599256) - - -- Teddy Hogeborn Thu, 07 Oct 2010 20:27:54 +0200 - -mandos (1.2.1-3) experimental; urgency=low - - * debian/changelog: Include entry for NMU of version 1.0.14-1.1. - - -- Teddy Hogeborn Tue, 05 Oct 2010 20:58:38 +0200 - -mandos (1.2.1-2) unstable; urgency=low - - * debian/source/format: New; contains "3.0 (quilt)". Really. - - -- Björn Påhlsson Sat, 02 Oct 2010 19:46:59 +0200 - -mandos (1.2.1-1) unstable; urgency=low - - * New upstream release. - * debian/source/format: New; contains "3.0 (quilt)". - - -- Björn Påhlsson Sat, 02 Oct 2010 19:03:58 +0200 - -mandos (1.2-1) unstable; urgency=low - - * New upstream release. - * Makefile (LINK_FORTIFY_LD): Remove "-fPIE". (Closes: #557076) - * debian/control: Add gnupg dependency to "mandos-client" and removed it - from "mandos". Added dependency on "python-urwid" "mandos" since the - new "mandos-monitor" utility needs it, and on "python (>=2.6) | - python-multiprocessing" since the Mandos server now uses it. - * debian/rules: Set BROKEN_PIE on mips and mipsel if a known buggy - version of binutils is used. - * debian/mandos.docs: Also install "/usr/share/doc/mandos/DBUS-API". - * debian/mandos.dirs: Added "etc/dbus-1/system.d". - * debian/mandos-client.README.Debian: Update info about DEVICE setting - of initramfs.conf. - * debian/mandos-client.README.Debian: Remove warning about --connect not - looping, since it now does. - - -- Teddy Hogeborn Tue, 28 Sep 2010 20:46:11 +0200 - -mandos (1.0.14-1.1) unstable; urgency=low - - * Non-maintainer upload. - * Rebuild against libavahi-core-dev (>= 0.6.26-1). - - -- Michael Biebl Mon, 12 Jul 2010 16:34:34 +0200 - -mandos (1.0.14-1) unstable; urgency=low (HIGH on mips and mipsel) - - * New upstream release. - * debian/rules: Build with BROKEN_PIE set on mips and mipsel - architectures - fixes FTBFS there. - - -- Teddy Hogeborn Sun, 25 Oct 2009 20:10:09 +0100 - -mandos (1.0.13-1) unstable; urgency=high - - * New upstream release. - * Do not copy unnecessary files to initrd (Closes: #551907) - - -- Teddy Hogeborn Thu, 22 Oct 2009 00:53:21 +0200 - -mandos (1.0.12-1) unstable; urgency=low - - * New upstream release. - * init.d-mandos: Correct dependencies (Closes: #546928) - * debian/control (Standards-Version): Changed to "3.8.3". - * debian/mandos-client.README.Debian: Improved wording and formatting. - Updated location of nfsroot.txt. - * debian/mandos.README.Debian: Improved wording and formatting. - * debian/mandos-client.postinst (configure): Don't look for user and - group with the old name if upgrading from a new enough version. - * debian/mandos.postinst (configure): - '' - - * debian/mandos-client.README.Debian: Added text about non-usability of - pseudo-network interfaces. - - -- Teddy Hogeborn Thu, 17 Sep 2009 15:03:59 +0200 - -mandos (1.0.11-1) unstable; urgency=low - - * debian/control (Standards-Version): Changed to "3.8.1". - * Makefile (GNUTLS_CFLAGS, GNUTLS_CFLAGS): Use "pkg-config" instead of - the old "libgnutls-config" script. (Closes: #529836) - - -- Teddy Hogeborn Sat, 23 May 2009 07:12:20 +0200 - -mandos (1.0.10-1) unstable; urgency=low - - * New upstream release. - * debian/mandos-client.postinst (update_initramfs): Fix permissions of - old initrd.img-*.bak files. - - -- Teddy Hogeborn Sun, 17 May 2009 04:56:35 +0200 - -mandos (1.0.9-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sun, 17 May 2009 02:59:45 +0200 - -mandos (1.0.8-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Wed, 25 Feb 2009 02:26:57 +0100 - -mandos (1.0.7-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Tue, 24 Feb 2009 12:58:06 +0100 - -mandos (1.0.6-1) unstable; urgency=low - - * New upstream release. - * debian/mandos-client.postinst: Converted to Bourne shell. Also - minor message change. - * debian/mandos-client.postrm: Minor message change. - * debian/mandos.postinst: Converted to Bourne shell. Also minor - message change. - * debian/mandos.prerm: Minor message change. - * debian/rules (install-indep): Removed "--no-start" from - dh_installinit. - * debian/mandos-client.lintian-overrides: Remove obsolete override for - unbreakable line in plugin-runner manual page. - * debian/control (mandos/Depends): Added "python-gobject". - * debian/mandos-client.dirs: Change - "usr/share/initramfs-tools/scripts/local-top" to - "usr/share/initramfs-tools/scripts/init-premount". - * debian/mandos-client.README.Debian: Add reference to initramfs.conf - and nfsroot.txt. New section about the new non-local connection - feature. - - -- Teddy Hogeborn Fri, 13 Feb 2009 09:27:25 +0100 - -mandos (1.0.5-1) unstable; urgency=low - - * New upstream release. - - -- Teddy Hogeborn Sat, 17 Jan 2009 02:26:00 +0100 - -mandos (1.0.4-1) unstable; urgency=low - - * New upstream release. - * debian/watch: New file. - * debian/mandos-client.README.Debian: Document new "mandos=off" kernel - parameter. - - -- Teddy Hogeborn Thu, 15 Jan 2009 05:49:22 +0100 - -mandos (1.0.3-2) unstable; urgency=low - - * Removed some now-unused debconf files. - * Changed postinst scripts to not source debconf/confmodule. - * Removed po-debconf from build-depends. - - -- Teddy Hogeborn Tue, 06 Jan 2009 21:28:20 +0100 - -mandos (1.0.3-1) unstable; urgency=low - - * New upstream release. - * Add -Xlinker to linker flags to fix FTBFS for some architectures. - Thanks to Thiemo Seufer for the report and - fix. (Closes: #509398) - * Remove debconf use altogether, thereby stopping debconf abuse. Thanks - to Christian Perrier . (Closes: #509653) - * Add NEWS file to /usr/share/doc directories. - * Use and create "_mandos" user+group. Rename old user+group created by - older versions of this package. - * Fix manual pages by adding build-depend on "docbook-xml". - - -- Teddy Hogeborn Tue, 06 Jan 2009 01:21:20 +0100 - -mandos (1.0.2-1) unstable; urgency=low - - * New upstream release. - * debian/copyright: Rewritten to conform to - . - - -- Teddy Hogeborn Fri, 17 Oct 2008 20:42:12 +0200 - -mandos (1.0.1-1) unstable; urgency=low - - * New upstream release. - * Separate /usr/share/doc/mandos-client/README.Debian into sections with - headlines. Add instructions on how to test the server and verify the - password. - - -- Teddy Hogeborn Tue, 07 Oct 2008 23:07:23 +0200 - -mandos (1.0-2) unstable; urgency=low - - * Added comments in debian/*.lintian-overrides files. Added Debian - revison number to version number. - - -- Teddy Hogeborn Wed, 01 Oct 2008 17:23:35 +0200 - -mandos (1.0-1) unstable; urgency=low - - * Initial Release. (Closes: #500727). - - -- Teddy Hogeborn Tue, 30 Sep 2008 21:58:43 +0200 === removed file 'debian/compat' --- debian/compat 2018-02-06 20:03:50 +0000 +++ debian/compat 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -10 === removed file 'debian/control' --- debian/control 2019-10-20 03:39:15 +0000 +++ debian/control 1970-01-01 00:00:00 +0000 @@ -1,70 +0,0 @@ -Source: mandos -Section: admin -Priority: optional -Maintainer: Mandos Maintainers -Uploaders: Teddy Hogeborn , - Björn Påhlsson -Build-Depends: debhelper (>= 10), docbook-xml, docbook-xsl, - libavahi-core-dev, libgpgme-dev | libgpgme11-dev, - libglib2.0-dev (>=2.40), libgnutls28-dev (>= 3.3.0), - libgnutls28-dev (>= 3.6.6) | libgnutls28-dev (<< 3.6.0), - xsltproc, pkg-config, libnl-route-3-dev, systemd -Build-Depends-Indep: python3 (>= 3), python3-dbus, python3-gi, - po-debconf -Standards-Version: 4.4.1 -Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk -Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files -Homepage: https://www.recompile.se/mandos -Rules-Requires-Root: binary-targets - -Package: mandos -Architecture: all -Depends: ${misc:Depends}, python3 (>= 3), libgnutls30 (>= 3.3.0), - libgnutls30 (>= 3.6.6) | libgnutls30 (<< 3.6.0), - python3-dbus, python3-gi, avahi-daemon, adduser, - python3-urwid, gnupg2 | gnupg, - systemd-sysv | lsb-base (>= 3.0-6), - debconf (>= 1.5.5) | debconf-2.0 -Recommends: ssh-client | fping -Suggests: libc6-dev | libc-dev, c-compiler -Description: server giving encrypted passwords to Mandos clients - This is the server part of the Mandos system, which allows - computers to have encrypted root file systems and at the - same time be capable of remote and/or unattended reboots. - . - The computers run a small client program in the initial RAM - disk environment which will communicate with a server over a - network. All network communication is encrypted using TLS. - The clients are identified by the server using a TLS public - key; each client has one unique to it. The server sends the - clients an encrypted password. The encrypted password is - decrypted by the clients using an OpenPGP key, and the - password is then used to unlock the root file system, - whereupon the computers can continue booting normally. - -Package: mandos-client -Architecture: linux-any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, - cryptsetup (<< 2:2.0.3-1) | cryptsetup-initramfs, - initramfs-tools (>= 0.99) | dracut (>= 044+241-3), - dpkg-dev (>=1.16.0), - gnutls-bin (>= 3.6.6) | libgnutls30 (<< 3.6.0), - debconf (>= 1.5.5) | debconf-2.0 -Recommends: ssh -Breaks: dropbear (<= 0.53.1-1) -Enhances: cryptsetup -Conflicts: dracut-config-generic -Description: do unattended reboots with an encrypted root file system - This is the client part of the Mandos system, which allows - computers to have encrypted root file systems and at the - same time be capable of remote and/or unattended reboots. - . - The computers run a small client program in the initial RAM - disk environment which will communicate with a server over a - network. All network communication is encrypted using TLS. - The clients are identified by the server using a TLS public - key; each client has one unique to it. The server sends the - clients an encrypted password. The encrypted password is - decrypted by the clients using an OpenPGP key, and the - password is then used to unlock the root file system, - whereupon the computers can continue booting normally. === removed file 'debian/copyright' --- debian/copyright 2019-02-10 04:20:26 +0000 +++ debian/copyright 1970-01-01 00:00:00 +0000 @@ -1,26 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Mandos -Upstream-Contact: Mandos -Source: - -Files: * -Copyright: Copyright © 2008-2019 Teddy Hogeborn - Copyright © 2008-2019 Björn Påhlsson -License: GPL-3+ - This file is part of Mandos. - . - Mandos 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. - . - Mandos 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 Mandos. If not, see . - . - On Debian systems, the complete text of the GNU General Public - License can be found in "/usr/share/common-licenses/GPL-3". === removed file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2019-07-27 10:11:45 +0000 +++ debian/mandos-client.README.Debian 1970-01-01 00:00:00 +0000 @@ -1,119 +0,0 @@ -This file documents the next steps to take after installation of the -Debian package, and also contain some notes specific to the Debian -packaging which are not also in the manual. - -* Adding a Client Password to the Server - - The server must be given a password to give back to the client on - boot time. This password must be a one which can be used to unlock - the root file system device. On the *client*, run this command: - - mandos-keygen --password - - It will prompt for a password and output a config file section. - This output should be copied to the Mandos server and added to the - file "/etc/mandos/clients.conf" there. - -* Testing that it Works (Without Rebooting) - - After the server has been started with this client's key added, it - is possible to verify that the correct password will be received by - this client by running the command, on the client: - - MANDOSPLUGINHELPERDIR=/usr/lib/$(dpkg-architecture \ - -qDEB_HOST_MULTIARCH)/mandos/plugin-helpers \ - /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH \ - )/mandos/plugins.d/mandos-client \ - --pubkey=/etc/keys/mandos/pubkey.txt \ - --seckey=/etc/keys/mandos/seckey.txt \ - --tls-privkey=/etc/keys/mandos/tls-privkey.pem \ - --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo - - This command should retrieve the password from the server, decrypt - it, and output it to standard output. There it can be verified to - be the correct password, before rebooting. - -* Emergency Escape - - If it ever should be necessary, the Mandos client can be temporarily - prevented from running at startup by passing the parameter - "mandos=off" to the kernel. - -* Specifying a Client Network Interface - - At boot time the network interfaces to use will by default be - automatically detected. If this should result in incorrect - interfaces, edit the DEVICE setting in the - "/etc/initramfs-tools/initramfs.conf" file. (The default setting is - empty, meaning it will autodetect the interfaces.) *If* the DEVICE - setting is changed, it will be necessary to update the initrd image - by running this command: - - (For initramfs-tools:) - update-initramfs -k all -u - - (For dracut:) - dpkg-reconfigure dracut - - The device can also be overridden at boot time on the Linux kernel - command line using the sixth colon-separated field of the "ip=" - option; for exact syntax, read the documentation in the file - "/usr/share/doc/linux-doc-*/Documentation/filesystems/nfs/nfsroot.txt", - available in the "linux-doc-*" package. - - Note that since the network interfaces are used in the initial RAM - disk environment, the network interfaces *must* exist at that stage. - Thus, an interface can *not* be a pseudo-interface such as "br0" or - "tun0"; instead, only real interfaces (such as "enp1s0" or "eth0") - can be used. This can be overcome by writing a "network hook" - program to create an interface (see mandos-client(8mandos)) and - placing it in "/etc/mandos/network-hooks.d", from where it will be - copied into the initial RAM disk. Example network hook scripts can - be found in "/usr/share/doc/mandos-client/examples/network-hooks.d". - -* User-Supplied Plugins - - Any plugins found in "/etc/mandos/plugins.d" will override and add - to the normal Mandos plugins. When adding or changing plugins, do - not forget to update the initital RAM disk image: - - (For initramfs-tools:) - update-initramfs -k all -u - - (For dracut:) - dpkg-reconfigure dracut - -* Do *NOT* Edit "/etc/crypttab" - - It is NOT necessary to edit "/etc/crypttab" to specify - "/usr/lib/mandos/plugin-runner" as a keyscript for the root file - system; if no keyscript is given for the root file system, the - Mandos client will be the new default way for getting a password for - the root file system when booting. - -* Non-local Connection (Not Using ZeroConf) - - If the "ip=" kernel command line option is used to specify a - complete IP address and device name, as noted above, it then becomes - possible to specify a specific IP address and port to connect to, - instead of using ZeroConf. The syntax for doing this is - "mandos=connect::" on the kernel command - line. - - For very advanced users, it is possible to specify simply - "mandos=connect" on the kernel command line to make the system only - set up the network (using the data in the "ip=" option) and not pass - any extra "--connect" options to mandos-client at boot. For this to - work, "--options-for=mandos-client:--connect=
:" needs - to be manually added to the file "/etc/mandos/plugin-runner.conf". - -* Diffie-Hellman Parameters - - On installation, a file with Diffie-Hellman parameters, - /etc/keys/mandos/dhparams.pem, will be generated and automatically - installed into the initital RAM disk image and also used by the - Mandos Client on boot. If different parameters are needed for - policy or other reasons, simply replace the existing dhparams.pem - file and update the initital RAM disk image. - - -- Teddy Hogeborn , Mon, 15 Jul 2019 16:47:02 +0200 === removed file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2019-08-18 04:14:31 +0000 +++ debian/mandos-client.dirs 1970-01-01 00:00:00 +0000 @@ -1,8 +0,0 @@ -usr/share/man/man8 -usr/sbin -usr/share/initramfs-tools/hooks -usr/share/initramfs-tools/conf.d -usr/share/initramfs-tools/conf-hooks.d -usr/share/initramfs-tools/scripts/init-premount -usr/share/initramfs-tools/scripts/local-premount -usr/lib/sysusers.d === removed file 'debian/mandos-client.docs' --- debian/mandos-client.docs 2012-06-01 21:48:12 +0000 +++ debian/mandos-client.docs 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ -NEWS -README -TODO === removed file 'debian/mandos-client.examples' --- debian/mandos-client.examples 2012-06-01 21:48:12 +0000 +++ debian/mandos-client.examples 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -network-hooks.d === removed file 'debian/mandos-client.links' --- debian/mandos-client.links 2008-09-19 13:50:22 +0000 +++ debian/mandos-client.links 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -usr/share/man/man8/plugin-runner.8mandos.gz usr/share/man/man5/plugin-runner.conf.5mandos.gz === removed file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 2019-08-05 21:14:05 +0000 +++ debian/mandos-client.lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,44 +0,0 @@ -# This directory contains secret client key files. -# -mandos-client binary: non-standard-dir-perm etc/keys/mandos/ 0700 != 0755 - -# The directory /usr/lib//mandos/plugins.d contains setuid -# binaries which are not meant to be run outside an initial RAM disk -# environment (except for test purposes). It would be insecure to -# allow anyone to run them. -# -mandos-client binary: non-standard-dir-perm usr/lib/*/mandos/plugins.d/ 0700 != 0755 -# Likewise for helper executables for plugins -mandos-client binary: non-standard-dir-perm usr/lib/*/mandos/plugin-helpers/ 0700 != 0755 - -# These binaries must be setuid root, since they need root powers, but -# are started by plugin-runner(8mandos), which runs all plugins as -# user/group "_mandos". These binaries are not run in a running -# system, but in an initial RAM disk environment. Here they are -# protected from non-root access by the directory permissions, above. -# -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/mandos-client 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/askpass-fifo 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/splashy 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/usplash 4755 root/root -mandos-client binary: setuid-binary usr/lib/*/mandos/plugins.d/plymouth 4755 root/root - -# The directory /etc/mandos/plugins.d can be used by local system -# administrators to place plugins in, overriding and complementing -# /usr/lib//mandos/plugins.d, and must be likewise protected. -# -mandos-client binary: non-standard-dir-perm etc/mandos/plugins.d/ 0700 != 0755 -# Likewise for plugin-helpers directory -mandos-client binary: non-standard-dir-perm etc/mandos/plugin-helpers/ 0700 != 0755 - -# The debconf templates is only used for displaying information -# detected in the postinst, not for saving answers to questions, so we -# don't need a .config file. -mandos-client binary: no-debconf-config - -# The notice displayed from the postinst script really is critical -mandos-client binary: postinst-uses-db-input - -# These are very important to work around bugs or changes in the old -# versions, and there is no pressing need to remove them. -mandos-client binary: maintainer-script-supports-ancient-package-version * === removed file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 2019-07-27 10:11:45 +0000 +++ debian/mandos-client.postinst 1970-01-01 00:00:00 +0000 @@ -1,204 +0,0 @@ -#!/bin/sh -# This script can be called in the following ways: -# -# After the package was installed: -# configure -# -# -# If prerm fails during upgrade or fails on failed upgrade: -# abort-upgrade -# -# If prerm fails during deconfiguration of a package: -# abort-deconfigure in-favour -# removing -# -# If prerm fails during replacement due to conflict: -# abort-remove in-favour - -. /usr/share/debconf/confmodule - -set -e - -# Update the initial RAM file system image -update_initramfs() -{ - if command -v update-initramfs >/dev/null; then - update-initramfs -k all -u - elif command -v dracut >/dev/null; then - dracut_version="`dpkg-query --showformat='${Version}' --show dracut`" - if dpkg --compare-versions "$dracut_version" lt 043-1 \ - && bash -c '. /etc/dracut.conf; . /etc/dracut.conf.d/*; [ "$hostonly" != yes ]'; then - echo 'Dracut is not configured to use hostonly mode!' >&2 - return 1 - fi - # Logic taken from dracut.postinst - for kernel in /boot/vmlinu[xz]-*; do - kversion="${kernel#/boot/vmlinu[xz]-}" - # Dracut preserves old permissions of initramfs image - # files, so we adjust permissions before creating new - # initramfs image containing secret keys. - chmod go-r /boot/initrd.img-"$kversion" - if [ "$kversion" != "*" ]; then - /etc/kernel/postinst.d/dracut "$kversion" - fi - done - fi - - if dpkg --compare-versions "$2" lt-nl "1.0.10-1"; then - # Make old initrd.img files unreadable too, in case they were - # created with mandos-client 1.0.8 or older. - find /boot -maxdepth 1 -type f -name "initrd.img-*.bak" \ - -print0 | xargs --null --no-run-if-empty chmod o-r - fi -} - -# Add user and group -add_mandos_user(){ - # Rename old "mandos" user and group - if dpkg --compare-versions "$2" lt "1.0.3-1"; then - case "`getent passwd mandos`" in - *:Mandos\ password\ system,,,:/nonexistent:/bin/false) - usermod --login _mandos mandos - groupmod --new-name _mandos mandos - return - ;; - esac - fi - # Create new user and group - if ! getent passwd _mandos >/dev/null; then - adduser --system --force-badname --quiet --home /nonexistent \ - --no-create-home --group --disabled-password \ - --gecos "Mandos password system" _mandos - fi -} - -# Create client key pairs -create_keys(){ - # If the OpenPGP key files do not exist, generate all keys using - # mandos-keygen - if ! [ -r /etc/keys/mandos/pubkey.txt \ - -a -r /etc/keys/mandos/seckey.txt ]; then - mandos-keygen - gpg-connect-agent KILLAGENT /bye || : - return 0 - fi - - # Remove any bad TLS keys by 1.8.0-1 - if dpkg --compare-versions "$2" eq "1.8.0-1" \ - || dpkg --compare-versions "$2" eq "1.8.0-1~bpo9+1"; then - # Is the key bad? - if ! certtool --password='' \ - --load-privkey=/etc/keys/mandos/tls-privkey.pem \ - --outfile=/dev/null --pubkey-info --no-text \ - 2>/dev/null; then - shred --remove -- /etc/keys/mandos/tls-privkey.pem \ - 2>/dev/null || : - rm --force -- /etc/keys/mandos/tls-pubkey.pem - fi - fi - - # If the TLS keys already exists, do nothing - if [ -r /etc/keys/mandos/tls-privkey.pem \ - -a -r /etc/keys/mandos/tls-pubkey.pem ]; then - return 0 - fi - - # Try to create the TLS keys - - TLS_PRIVKEYTMP="`mktemp -t mandos-client-privkey.XXXXXXXXXX`" - - if certtool --generate-privkey --password='' \ - --outfile "$TLS_PRIVKEYTMP" --sec-param ultra \ - --key-type=ed25519 --pkcs8 --no-text 2>/dev/null; then - - local umask=$(umask) - umask 077 - cp --archive "$TLS_PRIVKEYTMP" /etc/keys/mandos/tls-privkey.pem - shred --remove -- "$TLS_PRIVKEYTMP" 2>/dev/null || : - - # First try certtool from GnuTLS - if ! certtool --password='' \ - --load-privkey=/etc/keys/mandos/tls-privkey.pem \ - --outfile=/etc/keys/mandos/tls-pubkey.pem --pubkey-info \ - --no-text 2>/dev/null; then - # Otherwise try OpenSSL - if ! openssl pkey -in /etc/keys/mandos/tls-privkey.pem \ - -out /etc/keys/mandos/tls-pubkey.pem -pubout; then - rm --force /etc/keys/mandos/tls-pubkey.pem - # None of the commands succeded; give up - umask $umask - return 1 - fi - fi - umask $umask - - key_id=$(mandos-keygen --passfile=/dev/null \ - | grep --regexp="^key_id[ =]") - - db_version 2.0 - db_fset mandos-client/key_id seen false - db_reset mandos-client/key_id - db_subst mandos-client/key_id key_id $key_id - db_input critical mandos-client/key_id || true - db_go - db_stop - else - shred --remove -- "$TLS_PRIVKEYTMP" 2>/dev/null || : - fi -} - -create_dh_params(){ - if [ -r /etc/keys/mandos/dhparams.pem ]; then - return 0 - fi - # Create a Diffe-Hellman parameters file - DHFILE="`mktemp -t mandos-client-dh-parameters.XXXXXXXXXX.pem`" - # First try certtool from GnuTLS - if ! certtool --generate-dh-params --sec-param high \ - --outfile "$DHFILE"; then - # Otherwise try OpenSSL - if ! openssl genpkey -genparam -algorithm DH -out "$DHFILE" \ - -pkeyopt dh_paramgen_prime_len:3072; then - # None of the commands succeded; give up - rm -- "$DHFILE" - return 1 - fi - fi - sed --in-place --expression='0,/^-----BEGIN DH PARAMETERS-----$/d' \ - "$DHFILE" - sed --in-place --expression='1i-----BEGIN DH PARAMETERS-----' \ - "$DHFILE" - cp --archive "$DHFILE" /etc/keys/mandos/dhparams.pem - rm -- "$DHFILE" -} - -case "$1" in - configure) - add_mandos_user "$@" - create_keys "$@" - create_dh_params "$@" || : - update_initramfs "$@" - if dpkg --compare-versions "$2" lt-nl "1.7.10-1"; then - PLUGINHELPERDIR=/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-helpers - if ! dpkg-statoverride --list "$PLUGINHELPERDIR" \ - >/dev/null 2>&1; then - chmod u=rwx,go= -- "$PLUGINHELPERDIR" - fi - if ! dpkg-statoverride --list /etc/mandos/plugin-helpers \ - >/dev/null 2>&1; then - chmod u=rwx,go= -- /etc/mandos/plugin-helpers - fi - fi - ;; - abort-upgrade|abort-deconfigure|abort-remove) - ;; - - *) - echo "$0 called with unknown argument '$1'" 1>&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 === removed file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 2019-08-05 14:31:51 +0000 +++ debian/mandos-client.postrm 1970-01-01 00:00:00 +0000 @@ -1,73 +0,0 @@ -#!/bin/sh -# This script can be called in the following ways: -# -# After the package was removed: -# remove -# -# After the package was purged: -# purge -# -# After the package was upgraded: -# upgrade -# if that fails: -# failed-upgrade -# -# -# After all of the packages files have been replaced: -# disappear -# -# -# If preinst fails during install: -# abort-install -# -# If preinst fails during upgrade of removed package: -# abort-install -# -# If preinst fails during upgrade: -# abort-upgrade - -set -e - -# Update the initial RAM file system image -update_initramfs() -{ - if command -v update-initramfs >/dev/null; then - update-initramfs -k all -u - elif command -v dracut >/dev/null; then - # Logic taken from dracut.postinst - for kernel in /boot/vmlinu[xz]-*; do - kversion="${kernel#/boot/vmlinu[xz]-}" - if [ "$kversion" != "*" ]; then - /etc/kernel/postinst.d/dracut "$kversion" - fi - done - fi -} - -case "$1" in - remove) - update_initramfs - ;; - - purge) - shred --remove /etc/keys/mandos/seckey.txt 2>/dev/null || : - rm --force /etc/mandos/plugin-runner.conf \ - /etc/keys/mandos/pubkey.txt \ - /etc/keys/mandos/seckey.txt \ - /etc/keys/mandos/tls-privkey.pem \ - /etc/keys/mandos/tls-pubkey.pem \ - /etc/keys/mandos/dhparams.pem 2>/dev/null - update_initramfs - ;; - upgrade|failed-upgrade|disappear|abort-install|abort-upgrade) - ;; - - *) - echo "$0 called with unknown argument '$1'" 1>&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 === removed file 'debian/mandos-client.templates' --- debian/mandos-client.templates 2019-08-05 21:00:35 +0000 +++ debian/mandos-client.templates 1970-01-01 00:00:00 +0000 @@ -1,19 +0,0 @@ -Template: mandos-client/key_id -Type: note -_description: New client option "${key_id}" is REQUIRED on server - A new "key_id" client option is REQUIRED in the server's clients.conf - file, otherwise this computer most likely will not reboot unattended. - This option: - . - ${key_id} - . - must be added (all on one line!) on the Mandos server host, in the file - /etc/mandos/clients.conf, right before the "fingerprint" option for this - Mandos client. You must edit that file on that server and add this - option. - . - With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as - TLS session keys. A new TLS key pair has been generated and will be used - as identification, but the key ID of the public key needs to be added to - the server, since this will now be used to identify the client to the - server. === removed file 'debian/mandos.README.Debian' --- debian/mandos.README.Debian 2011-10-05 16:00:56 +0000 +++ debian/mandos.README.Debian 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -The Mandos server is useless without at least one configured client in -/etc/mandos/clients.conf. To create one, install the "mandos-client" -package on a client computer, and, on the client, run the command - - # mandos-keygen --password - -to get a config file stanza. Append the output of that command to the -file "/etc/mandos/clients.conf" on the Mandos server computer. - - -- Teddy Hogeborn , Wed, 5 Oct 2011 17:51:22 +0200 === removed file 'debian/mandos.dirs' --- debian/mandos.dirs 2019-08-18 00:23:21 +0000 +++ debian/mandos.dirs 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -usr/share/man/man5 -usr/share/man/man8 -etc/init.d -etc/default -etc/dbus-1/system.d -usr/sbin -var/lib/mandos -lib/systemd/system -usr/lib/tmpfiles.d -usr/lib/sysusers.d === removed file 'debian/mandos.docs' --- debian/mandos.docs 2010-09-12 03:00:40 +0000 +++ debian/mandos.docs 1970-01-01 00:00:00 +0000 @@ -1,4 +0,0 @@ -NEWS -README -TODO -DBUS-API === removed file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 2019-08-05 21:14:05 +0000 +++ debian/mandos.lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,16 +0,0 @@ -# This config file will normally have encrypted secret client keys in -# it, so it must be kept unreadable for non-root users. -# -mandos binary: non-standard-file-perm etc/mandos/clients.conf 0600 != 0644 - -# The debconf templates is only used for displaying information -# detected in the postinst, not for saving answers to questions, so we -# don't need a .config file. -mandos binary: no-debconf-config - -# The notice displayed from the postinst script really is critical -mandos binary: postinst-uses-db-input - -# These are very important to work around bugs or changes in the old -# versions, and there is no pressing need to remove them. -mandos binary: maintainer-script-supports-ancient-package-version * === removed file 'debian/mandos.postinst' --- debian/mandos.postinst 2019-08-18 00:05:36 +0000 +++ debian/mandos.postinst 1970-01-01 00:00:00 +0000 @@ -1,118 +0,0 @@ -#!/bin/sh -# This script can be called in the following ways: -# -# After the package was installed: -# configure -# -# -# If prerm fails during upgrade or fails on failed upgrade: -# abort-upgrade -# -# If prerm fails during deconfiguration of a package: -# abort-deconfigure in-favour -# removing -# -# If prerm fails during replacement due to conflict: -# abort-remove in-favour - -. /usr/share/debconf/confmodule - -set -e - -case "$1" in - configure) - # Rename old "mandos" user and group - if dpkg --compare-versions "$2" lt "1.0.3-1"; then - case "`getent passwd mandos`" in - *:Mandos\ password\ system,,,:/nonexistent:/bin/false) - usermod --login _mandos mandos - groupmod --new-name _mandos mandos - # Reload D-Bus daemon to be aware of the _mandos - # user & group - if [ -x /etc/init.d/dbus ]; then - invoke-rc.d dbus force-reload || : - fi - ;; - esac - fi - # Create new user and group - if ! getent passwd _mandos >/dev/null; then - adduser --system --force-badname --quiet \ - --home /nonexistent --no-create-home --group \ - --disabled-password --gecos "Mandos password system" \ - _mandos - # Reload D-Bus daemon to be aware of the _mandos user & - # group - if [ -x /etc/init.d/dbus ]; then - invoke-rc.d dbus force-reload || : - fi - elif dpkg --compare-versions "$2" eq 1.7.4-1 \ - || dpkg --compare-versions "$2" eq "1.7.4-1~bpo8+1" - then - start=no - if ! [ -f /var/lib/mandos/clients.pickle ]; then - invoke-rc.d mandos stop - start=yes - fi - chown _mandos:_mandos /var/lib/mandos/clients.pickle \ - 2>/dev/null || : - if [ "$start" = yes ]; then - invoke-rc.d mandos start - fi - fi - if ! dpkg-statoverride --list "/var/lib/mandos" >/dev/null \ - 2>&1; then - chown _mandos:_mandos /var/lib/mandos - chmod u=rwx,go= /var/lib/mandos - fi - - if dpkg --compare-versions "$2" eq "1.8.0-1" \ - || dpkg --compare-versions "$2" eq "1.8.0-1~bpo9+1"; then - if grep --quiet --regexp='^[[:space:]]*key_id[[:space:]]*=[[:space:]]*[Ee]3[Bb]0[Cc]44298[Ff][Cc]1[Cc]149[Aa][Ff][Bb][Ff]4[Cc]8996[Ff][Bb]92427[Aa][Ee]41[Ee]4649[Bb]934[Cc][Aa]495991[Bb]7852[Bb]855[[:space:]]*$' /etc/mandos/clients.conf; then - sed --in-place \ - --expression='/^[[:space:]]*key_id[[:space:]]*=[[:space:]]*[Ee]3[Bb]0[Cc]44298[Ff][Cc]1[Cc]149[Aa][Ff][Bb][Ff]4[Cc]8996[Ff][Bb]92427[Aa][Ee]41[Ee]4649[Bb]934[Cc][Aa]495991[Bb]7852[Bb]855[[:space:]]*$/d' \ - /etc/mandos/clients.conf - invoke-rc.d mandos restart - db_version 2.0 - db_fset mandos/removed_bad_key_ids seen false - db_reset mandos/removed_bad_key_ids - db_input critical mandos/removed_bad_key_ids || true - db_go - db_stop - fi - fi - - gnutls_version=$(dpkg-query --showformat='${Version}' \ - --show libgnutls30 \ - 2>/dev/null || :) - if [ -n "$gnutls_version" ] \ - && dpkg --compare-versions $gnutls_version ge 3.6.6; then - db_version 2.0 - db_input critical mandos/key_id || true - db_go - db_stop - fi - ;; - - abort-upgrade|abort-deconfigure|abort-remove) - ;; - - *) - echo "$0 called with unknown argument '$1'" 1>&2 - exit 1 - ;; -esac - -# Avahi version 0.6.31-2 and older provides "avahi" (instead of -# "avahi-daemon") in its /etc/init.d script header. To make -# insserv(8) happy, we edit our /etc/init.d script header to contain -# the correct string before the code added by dh_installinit calls -# update.rc-d, which calls insserv. -avahi_version="`dpkg-query --showformat='${Version}' --show avahi-daemon`" -if dpkg --compare-versions "$avahi_version" le 0.6.31-2; then - sed --in-place --expression='/^### BEGIN INIT INFO$/,/^### END INIT INFO$/s/^\(# Required-\(Stop\|Start\):.*avahi\)-daemon\>/\1/g' /etc/init.d/mandos -fi - -#DEBHELPER# - -exit 0 === removed file 'debian/mandos.prerm' --- debian/mandos.prerm 2015-07-12 01:57:54 +0000 +++ debian/mandos.prerm 1970-01-01 00:00:00 +0000 @@ -1,32 +0,0 @@ -#!/bin/sh -# prerm script for mandos -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * 'remove' -# * 'upgrade' -# * 'failed-upgrade' -# * 'remove' 'in-favour' -# * 'deconfigure' 'in-favour' -# 'removing' -# -# for details, see /usr/share/doc/packaging-manual/ - -case "$1" in - remove|deconfigure) - invoke-rc.d mandos stop || : - ;; - upgrade|failed-upgrade) - ;; - *) - echo "prerm called with unknown argument '$1'" >&2 - exit 0 - ;; -esac - -#DEBHELPER# - -exit 0 === removed file 'debian/mandos.templates' --- debian/mandos.templates 2019-08-05 21:00:35 +0000 +++ debian/mandos.templates 1970-01-01 00:00:00 +0000 @@ -1,29 +0,0 @@ -Template: mandos/key_id -Type: note -_Description: New client option "key_id" is REQUIRED on server - A new "key_id" client option is REQUIRED in the clients.conf file, - otherwise the client most likely will not reboot unattended. This option: - . - key_id = - . - must be added in the file /etc/mandos/clients.conf, right before the - "fingerprint" option, for each Mandos client. You must edit that file and - add this option for all clients. To see the correct key ID for each - client, run this command (on each client): - . - mandos-keygen -F/dev/null|grep ^key_id - . - Note: the clients must all also be using GnuTLS 3.6.6 or later; the server - cannot serve passwords for both old and new clients! - . - Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP - keys as TLS session keys. A new TLS key pair will be generated on each - client and will be used as identification, but the key ID of the public - key needs to be added to this server, since this will now be used to - identify the client to the server. - -Template: mandos/removed_bad_key_ids -Type: note -_Description: Bad key IDs have been removed from clients.conf - Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been - removed from /etc/mandos/clients.conf === removed directory 'debian/po' === removed file 'debian/po/POTFILES.in' --- debian/po/POTFILES.in 2019-07-27 19:28:14 +0000 +++ debian/po/POTFILES.in 1970-01-01 00:00:00 +0000 @@ -1,2 +0,0 @@ -[type: gettext/rfc822deb] mandos.templates -[type: gettext/rfc822deb] mandos-client.templates === removed file 'debian/po/de.po' --- debian/po/de.po 2019-08-16 19:28:16 +0000 +++ debian/po/de.po 1970-01-01 00:00:00 +0000 @@ -1,155 +0,0 @@ -# German debconf translation of mandos. -# This file is distributed under the same license as the mandos package. -# Copyright (C) 2008-2019 Teddy Hogeborn and Björn Påhlsson -# Copyright (C) of this file 2019 Chris Leick . -# -msgid "" -msgstr "" -"Project-Id-Version: mandos 1.8.7-1\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-08-10 12:06+0100\n" -"Last-Translator: Chris Leick \n" -"Language-Team: German \n" -"Language: de\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Auf diesem Server ist die Client-Option »key_id« ERFORDERLICH" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"In der Datei clients.conf ist eine neue Client-Option »key_id« ERFORDERLICH, " -"andernfalls werden die Clients höchstwahrscheinlich nicht unbeaufsichtigt neu " -"starten. Die Option" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"\"fingerprint\" option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" -msgstr "" -"muss der Datei /etc/mandos/clients.conf kurz vor der Option »fingerprint« " -"auf jedem Mandos-Client hinzugefügt werden. Sie müssen diese Datei bearbeiten " -"und diese Option auf allen Clients hinzufügen. Um die korrekte " -"Schlüsselkennung für jeden Client anzusehen, führen Sie (auf jedem Client) " -"diesen Befehl aus:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Hinweis: Die Clients müssen außerdem alle GnuTLS 3.6.6 oder neuer nutzen; der " -"Server kann keine Passwörter für sowohl alte als auch neue Clients anbieten!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server." -msgstr "" -"Begründung: Mit GnuTLS 3.6.6 wurde erzwungen, dass Mandos die Benutzung von " -"OpenPGP als TLS-Sitzungsschlüssel stoppt. Auf jedem Client wird ein neues " -"TLS-Schlüsselpaar erzeugt und zur Identifizierung benutzt, aber der " -"öffentliche Schlüssel muss auf diesem Server hinzugefügt werden, da dies nun " -"zur Identifizierung des Clients auf dem Server verwendet wird." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Falsche Schlüsselkennungen wurden aus der clients.conf entfernt." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Falsche Schlüsselkennungen, die durch einen Fehler im Mandos-Client 1.8.0 " -"erzeugt wurden, wurden aus /etc/mandos/clients.conf entfernt." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Auf dem Server ist die neue Client-Option »${key_id}« ERFORDERLICH." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the server's clients.conf " -"file, otherwise this computer most likely will not reboot unattended. This " -"option:" -msgstr "" -"In der Datei clients.conf des Servers ist eine neue Client-Option »key_id« " -"ERFORDERLICH, andernfalls wird dieser Rechner höchstwahrscheinlich nicht " -"unbeaufsichtigt neu starten. Die Option " - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"must be added (all on one line!) on the Mandos server host, in the file /etc/" -"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " -"client. You must edit that file on that server and add this option." -msgstr "" -"muss (in einer einzigen Zeile!) der Datei /etc/mandos/clients.conf auf dem " -"Mandos-Server kurz vor der Option »fingerprint« für diesen Mandos-Client " -"hinzugefügt werden. Sie müssen diese Datei auf diesem Server bearbeiten und " -"diese Option hinzufügen." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." -msgstr "" -"Mit GnuTLS 3.6.6 wurde erzwungen, dass Mandos die Benutzung von OpenPGP als " -"TLS-Sitzungsschlüssel stoppt. Ein neues TLS-Schlüsselpaar wurde erzeugt und " -"wird zur Identifizierung benutzt, aber die Schlüsselkennung des öffentlichen " -"Schlüssels muss auf diesem Server hinzugefügt werden, da dies nun " -"zur Identifizierung des Clients auf dem Server verwendet wird." === removed file 'debian/po/en_US.po' --- debian/po/en_US.po 2019-08-05 21:00:35 +0000 +++ debian/po/en_US.po 1970-01-01 00:00:00 +0000 @@ -1,150 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-08-05 22:59+0200\n" -"Last-Translator: Teddy Hogeborn \n" -"Language-Team: English\n" -"Language: en_US\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "New client option “key_id” is REQUIRED on server" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"A new “key_id” client option is REQUIRED in the clients.conf file, otherwise " -"the client most likely will not reboot unattended. This option:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"\"fingerprint\" option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" -msgstr "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"“fingerprint” option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server." -msgstr "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server. " - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Bad key IDs have been removed from clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "New client option “${key_id}” is REQUIRED on server" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the server's clients.conf " -"file, otherwise this computer most likely will not reboot unattended. This " -"option:" -msgstr "" -"A new “key_id” client option is REQUIRED in the server’s clients.conf file, " -"otherwise this computer most likely will not reboot unattended. This option:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"must be added (all on one line!) on the Mandos server host, in the file /etc/" -"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " -"client. You must edit that file on that server and add this option." -msgstr "" -"must be added (all on one line!) on the Mandos server host, in the file /" -"etc/ mandos/clients.conf, right before the “fingerprint” option for this " -"Mandos client. You must edit that file on that server and add this option." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." -msgstr "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." === removed file 'debian/po/fr.po' --- debian/po/fr.po 2019-08-16 19:32:47 +0000 +++ debian/po/fr.po 1970-01-01 00:00:00 +0000 @@ -1,156 +0,0 @@ -# Translation of mandos debconf templates to French -# Copyright (C) 2019, French l10n team -# This file is distributed under the same license as the mandos package. -# Grégoire Scano , 2019. -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-07-27 21:06+0200\n" -"PO-Revision-Date: 2019-08-11 15:58+0800\n" -"Last-Translator: Grégoire Scano \n" -"Language-Team: French \n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "La nouvelle option de client « key_id » est NÉCESSAIRE sur le serveur" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"Une nouvelle option de client « key_id » est NÉCESSAIRE dans le fichier " -"clients.conf, autrement le client ne redémarrera probablement pas de lui-" -"même. Cette option :" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "key_id = " -msgstr "key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"\"fingerprint\" option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" -msgstr "" -"doit être ajoutée dans le fichier /etc/mandos/clients.conf, juste avant " -"l'option « fingerprint », pour chaque client Mandos. Vous devez éditer ce " -"fichier et ajouter cette option pour tous les clients. Pour voir " -"l'identifiant de clef correct pour chaque client, exécutez la commande (sur " -"chaque client) :" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "mandos-keygen -F/dev/null|grep ^key_id" -msgstr "mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Note : les clients doivent également tous utiliser GnuTLS 3.6.6 ou " -"ultérieur ; le serveur ne peut pas servir des mots de passe pour des clients " -"anciens et récents en même temps !" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server." -msgstr "" -"Explication : avec GnuTLS 3.6.6, Mandos a été contraint d'arrêter d'utiliser " -"des clefs OpenPGP comme clefs de session TLS. Une nouvelle paire de clefs " -"TLS sera générée pour chaque client et sera utilisée pour l'identification, " -"mais l'identifiant de la clef publique doit être ajouté à ce serveur, " -"puisqu'il sera utilisé pour identifier le client auprès du serveur." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Les identifiants de clef incorrects ont été supprimés de clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Les identifiants de clef incorrects, créés par un bogue dans le client " -"Mandos 1.8.0, ont été supprimés de /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "" -"La nouvelle option de client « ${key_id} » est NÉCESSAIRE sur le serveur" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the server's clients.conf " -"file, otherwise this computer most likely will not reboot unattended. This " -"option:" -msgstr "" -"Une nouvelle option de client « key_id » est NÉCESSAIRE dans le fichier " -"clients.conf du serveur, autrement cette machine ne pourra pas redémarrer " -"d'elle-même. Cette option :" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "${key_id}" -msgstr "${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"must be added (all on one line!) on the Mandos server host, in the file /etc/" -"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " -"client. You must edit that file on that server and add this option." -msgstr "" -"doit être ajoutée (tout sur une seule ligne !) sur le serveur Mandos hôte, " -"dans le fichier /etc/mandos/clients.conf, juste avant l'option " -"« fingerprint » de ce client Mandos. Vous devez éditer ce fichier sur ce " -"serveur et ajouter cette option." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." -msgstr "" -"Avec GnuTLS 3.6.6, Mandos a été contraint d'arrêter d'utiliser des clefs " -"OpenPGP comme clefs de session TLS. Une nouvelle paire de clefs TLS a été " -"générée et sera utilisée pour l'identification, mais l'identifiant de la " -"clef publique doit être ajouté au serveur, puisqu'il sera utilisé pour " -"identifier le client auprès du serveur." === removed file 'debian/po/pt.po' --- debian/po/pt.po 2019-10-19 17:37:00 +0000 +++ debian/po/pt.po 1970-01-01 00:00:00 +0000 @@ -1,158 +0,0 @@ -# Translation of mandos debconf messages to European Portuguese -# Copyright (C) 2019 THE mandos'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# -# Américo Monteiro , 2019. -msgid "" -msgstr "" -"Project-Id-Version: mandos 1.8.9-2\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-10-18 18:45+0000\n" -"Last-Translator: Américo Monteiro \n" -"Language-Team: Portuguese <>\n" -"Language: pt\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Lokalize 2.0\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Nova opção \"key_id\" de cliente é NECESSÁRIA no servidor" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"Uma nova opção de cliente \"key_id\" é NECESSÁRIA no ficheiro clients.conf, " -"caso contrário o mais provável é o cliente não conseguir reinicicar sozinho. " -"Esta opção:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"\"fingerprint\" option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" -msgstr "" -"tem de ser adicionada ao ficheiro /etc/mandos/clients.conf, logo antes " -"da opção \"fingerprint\", para cada cliente Mandos. Você tem de editar esse " -"ficheiro e adicionar esta opção para todos os clientes. Para ver a key ID " -"para cada cliente, corra este comando (em cada cliente):" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Note: os clientes têm de também usar GnuTLS 3.6.6 ou posterior; o servidor " -"não consegue servir palavras passe para ambos clientes antigos e novos!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server." -msgstr "" -"Razão: Com GnuTLS 3.6.6, o Mandos foi forçado a parar de usar chaves OpenPGP " -"como chaves de sessão TLS. Será gerado um novo par de chaves TLS em cada " -"cliente e será usado como identificação, mas o ID de chave da chave pública " -"precisa de ser adicionada a este servidor, pois esta irá agora ser usada " -"para identificar o cliente no servidor." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "IDs de chave errados foram removidos de clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"IDs de chave errados, que foram criados por um bug no cliente Mandos 1.8.0, " -"foram removidos de /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Nova opção \"${key_id}\" de cliente é NECESSÁRIA no servidor" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the server's clients.conf " -"file, otherwise this computer most likely will not reboot unattended. This " -"option:" -msgstr "" -"Uma nova opção \"key_id\" de cliente é NECESSÁRIA no ficheiro clients.conf " -"do servidor, caso contrário, é bem provável que este computador não consiga " -"reiniciar sozinho. Esta opção:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"must be added (all on one line!) on the Mandos server host, in the file /etc/" -"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " -"client. You must edit that file on that server and add this option." -msgstr "" -"tem de ser adicionada (toda numa linha) na máquina servidor do Mandos, no " -"ficheiro /etc/mandos/clients.conf, logo antes da opção \"fingerprint\" para " -"este cliente Mandos. Você tem de editar esse ficheiro nesse servidor e " -"adicionar esta opção." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." -msgstr "" -"Com GnuTLS 3.6.6, o Mandos foi forçado a parar de usar chaves OpenPGP " -"como chaves de sessão TLS. Foi gerado um novo par de chaves TLS e será " -"usado como identificação, mas o ID de chave da chave pública precisa de ser " -"adicionada ao servidor, pois esta irá agora ser usada para identificar o " -"cliente no servidor." - - === removed file 'debian/po/sv.po' --- debian/po/sv.po 2019-08-16 20:47:52 +0000 +++ debian/po/sv.po 1970-01-01 00:00:00 +0000 @@ -1,156 +0,0 @@ -# Translation of mandos debconf templates to Swedish -# Copyright (C) 2019, Mandos Maintainers -# This file is distributed under the same license as the mandos package. -# Teddy Hogeborn , 2019. -# -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: 2019-08-16 22:45+0200\n" -"Last-Translator: Teddy Hogeborn \n" -"Language-Team: Swedish \n" -"Language: sv\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "Ny klientinställning ”key_id” KRÄVS på servern" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" -"En ny klientinställning, ”key_id”, KRÄVS i filen clients.conf, annars\n" -"kommer klienten antagligen inte att starta upp av sig själv. Denna\n" -"inställning:" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr " key_id = " - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"\"fingerprint\" option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" -msgstr "" -"måste läggas till i filen /etc/mandos/clients.conf, precis ovanför\n" -"inställningen ”fingerprint”, för varje Mandosklient. Du måste ändra i\n" -"den filen och lägga till den inställningen för alla klienter. För att\n" -"se det korrekta nyckel-IDt för varje klient, kör följande kommando (på\n" -"varje klient):" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr " mandos-keygen -F/dev/null|grep ^key_id" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" -"Observera: Alla klienter måste också använda GnuTLS 3.6.6 eller nyare;\n" -"servern kan inte ge lösenord till både nya och gamla klienter!" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server." -msgstr "" -"Förklaring: Med GnuTLS 3.6.6 så har Mandos nödgats att sluta använda\n" -"OpenPGP-nycklar som TLS-sessionsnycklar. Ett nytt TLS-nyckelpar\n" -"kommer att genereras på varje klient och kommer att användas för\n" -"identifiering, men nyckel-IDt för den publika nyckeln måste läggas\n" -"till på denna server, då denna numera kommer att användas för att\n" -"identifiera klienten för servern." - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "Dåliga nyckel-IDn har tagits bort från clients.conf" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" -"Dåliga nyckel-IDn, som skapats av en bugg i Mandosklienten 1.8.0, har\n" -"tagits bort från /etc/mandos/clients.conf" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "Ny klientinställning ”${key_id}” KRÄVS på servern" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the server's clients.conf " -"file, otherwise this computer most likely will not reboot unattended. This " -"option:" -msgstr "" -"En ny klientinställning, ”key_id”, KRÄVS i serverns clients.conf-fil,\n" -"annars kommer denna dator antagligen inte att starta upp av sig själv.\n" -"Denna inställning:" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr " ${key_id}" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"must be added (all on one line!) on the Mandos server host, in the file /etc/" -"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " -"client. You must edit that file on that server and add this option." -msgstr "" -"måste läggas till (allt på en rad!) på Mandosservervärddatorn, i filen\n" -"/etc/mandos/clients.conf, precis ovanför inställningen ”fingerprint”,\n" -"för denna Mandosklient. Du måste ändra i den filen och lägga till den\n" -"inställningen." - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." -msgstr "" -"Med GnuTLS 3.6.6 så har Mandos nödgats att sluta använda\n" -"OpenPGP-nycklar som TLS-sessionsnycklar. Ett nytt TLS-nyckelpar har\n" -"genererats och kommer att användas för identifiering, men nyckel-IDt\n" -"för den publika nyckeln måste läggas till på servern, då detta numera\n" -"kommer att användas för att identifiera klienten för servern." === removed file 'debian/po/templates.pot' --- debian/po/templates.pot 2019-08-05 21:00:35 +0000 +++ debian/po/templates.pot 1970-01-01 00:00:00 +0000 @@ -1,127 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the mandos package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: mandos\n" -"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" -"POT-Creation-Date: 2019-08-05 22:57+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "New client option \"key_id\" is REQUIRED on server" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the clients.conf file, " -"otherwise the client most likely will not reboot unattended. This option:" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " key_id = " -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"must be added in the file /etc/mandos/clients.conf, right before the " -"\"fingerprint\" option, for each Mandos client. You must edit that file and " -"add this option for all clients. To see the correct key ID for each client, " -"run this command (on each client):" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid " mandos-keygen -F/dev/null|grep ^key_id" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " -"cannot serve passwords for both old and new clients!" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:1001 -msgid "" -"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " -"keys as TLS session keys. A new TLS key pair will be generated on each " -"client and will be used as identification, but the key ID of the public key " -"needs to be added to this server, since this will now be used to identify " -"the client to the server." -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "Bad key IDs have been removed from clients.conf" -msgstr "" - -#. Type: note -#. Description -#: ../mandos.templates:2001 -msgid "" -"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " -"removed from /etc/mandos/clients.conf" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "New client option \"${key_id}\" is REQUIRED on server" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"A new \"key_id\" client option is REQUIRED in the server's clients.conf " -"file, otherwise this computer most likely will not reboot unattended. This " -"option:" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid " ${key_id}" -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"must be added (all on one line!) on the Mandos server host, in the file /etc/" -"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " -"client. You must edit that file on that server and add this option." -msgstr "" - -#. Type: note -#. description -#: ../mandos-client.templates:1001 -msgid "" -"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " -"session keys. A new TLS key pair has been generated and will be used as " -"identification, but the key ID of the public key needs to be added to the " -"server, since this will now be used to identify the client to the server." -msgstr "" === removed file 'debian/rules' --- debian/rules 2019-04-09 22:31:23 +0000 +++ debian/rules 1970-01-01 00:00:00 +0000 @@ -1,61 +0,0 @@ -#!/usr/bin/make -f - -ifeq (,$(filter noopt,$(DEB_BUILD_OPTIONS))) - MAKEFLAGS += OPTIMIZE=-O0 -endif - -ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) - MAKEFLAGS += -j$(NUMJOBS) -endif - -%: - dh $@ - -override_dh_auto_build-arch: - LC_ALL=en_US.utf8 dh_auto_build -- all doc - -override_dh_auto_build-indep: - LC_ALL=en_US.utf8 dh_auto_build -- doc - -override_dh_installinit-indep: - dh_installinit --onlyscripts \ - --update-rcd-params="defaults 25 15" - -override_dh_auto_install-indep: - $(MAKE) DESTDIR=$(CURDIR)/debian/mandos install-server - -override_dh_auto_install-arch: - $(MAKE) DESTDIR=$(CURDIR)/debian/mandos-client \ - install-client-nokey - -override_dh_fixperms-arch: - dh_fixperms --exclude etc/keys/mandos \ - --exclude etc/mandos/plugins.d \ - --exclude etc/mandos/plugin-helpers \ - --exclude usr/lib/$(DEB_HOST_MULTIARCH)/mandos/plugins.d \ - --exclude usr/lib/$(DEB_HOST_MULTIARCH)/mandos/plugin-helpers \ - --exclude usr/share/doc/mandos-client/examples/network-hooks.d - chmod --recursive g-w -- \ - "$(CURDIR)/debian/mandos-client/usr/share/doc/mandos-client/examples/network-hooks.d" - -override_dh_fixperms-indep: - dh_fixperms --exclude etc/mandos/clients.conf - -override_dh_auto_test-arch: ; - -#bpo## dpkg-shlibdeps sees the "libgnutls28-dev (>= 3.6.6) | -#bpo## libgnutls28-dev (<< 3.6.0)," in the build-dependencies not as two -#bpo## alternatives, but as an absolute dependency on libgnutls30 >= 3.6.6. -#bpo## So we have to do this ugly hack to hide this build dependency if we -#bpo## compiled with libgnutls30 << 3.6.0. -#bpo#override_dh_shlibdeps-arch: -#bpo# -gnutls_version=$$(dpkg-query --showformat='$${Version}' \ -#bpo# --show libgnutls30); \ -#bpo# dpkg --compare-versions $$gnutls_version lt 3.6.0 \ -#bpo# && { cp --archive debian/control debian/control.orig; sed --in-place --expression='s/libgnutls28-dev (>= 3\.6\.6) |//' debian/control; } -#bpo# dh_shlibdeps -#bpo# -gnutls_version=$$(dpkg-query --showformat='$${Version}' \ -#bpo# --show libgnutls30); \ -#bpo# dpkg --compare-versions $$gnutls_version lt 3.6.0 \ -#bpo# && mv debian/control.orig debian/control === removed directory 'debian/source' === removed file 'debian/source/format' --- debian/source/format 2010-10-02 17:41:05 +0000 +++ debian/source/format 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -3.0 (quilt) === removed file 'debian/source/lintian-overrides' --- debian/source/lintian-overrides 2019-08-05 21:03:31 +0000 +++ debian/source/lintian-overrides 1970-01-01 00:00:00 +0000 @@ -1,7 +0,0 @@ -# We are both upstream and Debian maintainer for this package, so the -# .asc signature can not exist until after the orig.tar.gz has been -# built as part of the Debian package build. -mandos source: orig-tarball-missing-upstream-signature mandos_*.tar.gz - -# We want to backport to stretch for as long as reasonably practical -mandos source: package-uses-old-debhelper-compat-version 10 === removed file 'debian/source/local-options' --- debian/source/local-options 2011-10-08 21:13:46 +0000 +++ debian/source/local-options 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ ---single-debian-patch === removed directory 'debian/tests' === removed file 'debian/tests/control' --- debian/tests/control 2019-09-04 05:31:20 +0000 +++ debian/tests/control 1970-01-01 00:00:00 +0000 @@ -1,33 +0,0 @@ -Test-Command: /usr/sbin/mandos --check -Restrictions: superficial, allow-stderr -Features: test-name=mandos-check -Depends: mandos - -Test-Command: /usr/sbin/mandos-ctl --check --verbose -Restrictions: allow-stderr -Features: test-name=mandos-ctl -Depends: mandos - -Test-Command: /usr/sbin/mandos-keygen --version -Restrictions: superficial -Features: test-name=mandos-keygen-version -Depends: mandos-client - -Test-Command: /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-runner --version -Restrictions: needs-root, superficial -Features: test-name=plugin-runner-version -Depends: mandos-client - -Test-Command: /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)/mandos/plugin-helpers/mandos-client-iprouteadddel --version -Restrictions: needs-root, superficial -Features: test-name=mandos-client-iprouteadddel-version -Depends: mandos-client - -Test-Command: /usr/lib/dracut/modules.d/90mandos/password-agent --test --verbose -Features: test-name=password-agent -Depends: mandos-client - -Test-Command: /usr/lib/dracut/modules.d/90mandos/password-agent --test --verbose -p /task-creators/start_mandos_client/suid -Restrictions: needs-root -Features: test-name=password-agent-suid -Depends: mandos-client === removed directory 'debian/upstream' === removed file 'debian/upstream/metadata' --- debian/upstream/metadata 2019-08-04 12:39:39 +0000 +++ debian/upstream/metadata 1970-01-01 00:00:00 +0000 @@ -1,13 +0,0 @@ -# -*- yaml -*- ---- -Bug-Submit: mailto:mandos-dev@recompile.se -Changelog: https://bzr.recompile.se/loggerhead/mandos/trunk/view/head:/NEWS -Contact: mandos@recompile.se -Documentation: https://www.recompile.se/mandos/man/intro.8mandos -FAQ: https://www.recompile.se/mandos/man/intro.8mandos#faq -Name: Mandos -Other-References: https://www.recompile.se/mandos -Registration: https://mail.recompile.se/cgi-bin/mailman/listinfo/mandos-dev -Repository: https://ftp.recompile.se/pub/mandos/trunk -Repository-Browse: https://bzr.recompile.se/loggerhead/mandos/trunk/files -Security-Contact: mandos@recompile.se === removed file 'debian/upstream/signing-key.asc' --- debian/upstream/signing-key.asc 2014-03-28 22:32:21 +0000 +++ debian/upstream/signing-key.asc 1970-01-01 00:00:00 +0000 @@ -1,52 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.12 (GNU/Linux) - -mQINBFJQOFYBEACoWsEGlOxVWFUAxOxdd3GDLaqEKkKihJwLp102Ks7JKMd9friR -7+OZuo3U0gdqLU9q1jPJn36J1QbaUTOvcaKtZp+QpUoYJ2OaGtlOY5ML8LSoC0rZ -MIzGYTtvriwpU/YplLNGPl/90KsB2VqjrY1l1he5M8zziWDlPdJxwg8GFvmPWoif -6oo+1iCswL5IdQ6c5MVO53zYu0cgyUSazLsVD5Xzy59lefgtaDydahJpPycf5aEQ -DAoC9fZt2mgG3FLIUCZdXIhZdOJGCMdjLThBnJXYgGbG4rbGLNlI4W/uA5aqa4ME -WYSAcCyX3ucKY/LkXRtC+z5s05e7tZ3Z+uAJy1eDsbhDXgZERye7a/zPWx1tAlzQ -E80Oltjh1uXWjQORyx99a0jK87zjm49YjhYw1ZN6Z0HfSaws4Yj2QOzp9t4B3l7f -DIUYoWBfHW7mseQeQ+t3TwQU5gjFCNu7oDeATqi5A5MksXN0+BcksterbGRBhEyp -CybIEyrZE033jIs407Ool4Kv10cnjc8oy609BXex/dxwcvVr2vQHle4NPUZd+Xhg -zC+9Z4jFwE0M/EPvtyieA/DWQse+TZ5itDGMYDub/GJfv1U61ANOgPIbTEF7iSa9 -5nWmq7zyUy/txmABka842Kt0Vp6ayoKcF8EIXCaDrVfPnXj+JlKf3c2u6wARAQAB -tCxNYW5kb3MgTWFpbnRhaW5lciBUZWFtIDxtYW5kb3NAcmVjb21waWxlLnNlPokC -PQQTAQgAJwUCUlA4VgIbAwUJCWYBgAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAK -CRByIylzyjTCxETXEACi56jCV9lJNSBbTp0Iet4X/i7Mx0Z8UkFFa3l7o0i4jFQj -CIBrWECDlcxqZziii2dgh7L0ma93vB3rfjfCWeYLcEQw43MFBsd4dHuobrLXTcqU -7n0Zmc8BsXwk5B25CnEYgvlbWX9BCYtxHGRcZzQrqOFjCMKatq0EIIVuWaz5yuCU -V2rEgnr+veTd/rBOE9ez6Ju6xH11Teob5G7pMM0YPKHtZG/J3rvWPw4BDM6Tc60B -G0sTDZNgkrGWxuB8YaLIwVWzliQK/17Jv/0alajyA3cWLWMkcK9Yhi/einzdMRoD -IjnbtoHcSC9g6i0VGelwnMpHlFTwXriXEBSttULarK3iKE4tOv9nxMAEwicqlw14 -X96PPgz6ogtJG7FiwZfy1CQ81Uby+YuIhHZx3ZEyR1TFq70e98EgCuvjMZWMIhSy -gB5Vssfq7c2lXQjltV3ujhK1PD+7/iHlL7t4QRxPDN8fbMS2VPfAdtnWS1K48d5J -D/jP1LrGWS81HIaX8GFVLVw+jSQEu9cn3TFiZxK/4MMsITmlouJdtZWmQ/otMSMl -wiCCZp3dGpRXMmaqR1N8V0nMKshM8mci7bD92ubd/t6cR/G6l+VIp45WyFONvtde -F3ccfmfrKJuroMDfHxPxMf58EroAuWwzJKCPRH4JmwDvSSQoIIAGNL1lOKp6wrkC -DQRSUDhWARAAzN7pbpAu7XLNPODotV/N+JaCFvNAIqTcr9PrbhxiKFCDs9/IExwP -sGENL9GZd1DfoGEgxQ8j3l8VGw9VSeUoN7uMY2NwwbXTilAFkn/S8xnr2zQDRZ+n -EeFSq9MMFxj4Kt1TqVYDbO8vmFfOT3gRCRHeJ+pn4yJeSPau/ndNrmbQ1/Z6vaUG -yfo931ottx7SXZwkHA6jJVFT9rbHTyx9tzOqMKDJiMrx8qKaHpE9B45oHNR0WJJJ -75zoDVuOZ6wAxXZuqBFu2lKPqDTZeawfzcu5qplrm1RPgSOjz6w1A41HBLqGe+v9 -7Twx5wfNMgnKC0V2wUe0xR6hQHQlyZoCwcGyrasiu+v/joZ1p66SSWKjs+LGjoe4 -Lwh6VjZU2x+irVBjcgIoRWf3k4JAef0nGYsm0cFjAnwXac+/CYxVt+7Y4+HG8wPB -oUNZkW+bvdHQouxEClxnccIEgX/AkriJTYDQ4q3tkl2HVE/51R4pdQVLer2a5ov+ -Jwk44DdqzYstMsvqu+iD48hXzADg6HFvofkpct15h463pMaJf99uVVM9ZDNQ6B34 -l3tPX9ZDkIDl6n9dE2Jkaxx1yNVhPXpeMf4EzL+CEdUErVStB66lUkw+tNkuZyVT -ZTQel9h0196+CNSiqAaL8+ZZdbjKKfzlcB4Qnd897XzMfsFQ7mzJ9QsAEQEAAYkC -JQQYAQgADwUCUlA4VgIbDAUJCWYBgAAKCRByIylzyjTCxFmlEACBTOg5NqX63d8D -mwk4smlFPppQBIduxZaMG9HsLcPi3VKTG9Zg6WI6rEdr/4MnoINsudLsEbrQLgRH -2q1Zs+HqIIP5H2/sYHmswyokYB10zKB6gNUUg/GSlcAcrelsHVKx5B8kccWGT5gk -Wo/X0BGMUTOvQ6lJ6YNo1idcQ2ZjsyfZoz3G8JS7/EXN//jAZf+017yj8WsAS7hw -JRFMy7VET4g00JcBoNOAMP7PkozimZ2OwwsggJSYWkR1RaU2tKR1VmDF8R6UxuEd -BJzwFmz+wNC1Kq+FoSaRNsrKEmzLnfV9unDnF2z7Lc4LqOysXdzOk9zTBPur0gd2 -Lh5H/g5rTAMQBARqXfvIwiTtrBGgil8JW8e4Bc0LQUuHAE7x9gMRil+OtkQrCRk9 -0LWXVS+K0tvvruE4EDtCGiS5046+BEI3aYsp4hNzjHADq0TJeCYjNg9kY0CjxcEq -cfuMoUbQ0MkARGuBbykCdlylfTrkxrj/dPhr49lctY3H+Pj6F4fMDM4TP6UTGA8k -993RRNYhkDWSxIp6G7RJpBZobHN+eHQ3r8A4tWdYb4Fvd2lvwEDjUFT9uD6WAff4 -8A1hM2uSy91UYBOPrIjqYdRFKJc9rThYdXH2T6SiRMYtZMrEKhqPffB/i9mqVBlD -6vKRsaQikZujRdP9Dkf0mLmJ7LANWw== -=9Noe ------END PGP PUBLIC KEY BLOCK----- === removed file 'debian/watch' --- debian/watch 2019-02-11 05:15:24 +0000 +++ debian/watch 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ -version=4 -opts=pgpmode=auto \ - https://ftp.recompile.se/pub/@PACKAGE@/@PACKAGE@@ANY_VERSION@\.orig@ARCHIVE_EXT@ === removed file 'default-mandos' --- default-mandos 2008-09-17 00:34:09 +0000 +++ default-mandos 1970-01-01 00:00:00 +0000 @@ -1,7 +0,0 @@ -# Directory where configuration files are located. Default is -# "/etc/mandos". -# -#CONFIGDIR=/etc/mandos - -# Additional options that are passed to the Daemon. -DAEMON_ARGS="" === removed directory 'dracut-module' === removed file 'dracut-module/ask-password-mandos.path' --- dracut-module/ask-password-mandos.path 2019-07-27 10:11:45 +0000 +++ dracut-module/ask-password-mandos.path 1970-01-01 00:00:00 +0000 @@ -1,47 +0,0 @@ -# -*- systemd -*- -# -# Copyright © 2019 Teddy Hogeborn -# Copyright © 2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# -# This systemd.path(5) unit will wait until there are any password -# questions present, represented by files named "ask.*" in the -# /run/systemd/ask-password directory, and then start the -# "ask-password-mandos.service" systemd.service(5) unit. - -# This file should be installed in the root file system as -# "/usr/lib/dracut/modules.d/90mandos/ask-password-mandos.path" and -# will be installed in the initramfs image file as -# "/lib/systemd/system/ask-password-mandos.path", and symlinked to -# "/lib/systemd/system//sysinit.target.wants/ask-password-mandos.path" -# by dracut when dracut creates the initramfs image file. - -[Unit] -Description=Forward Password Requests to remote Mandos server -Documentation=man:intro(8mandos) man:password-agent(8mandos) man:mandos-client(8mandos) -DefaultDependencies=no -Conflicts=shutdown.target -Before=basic.target shutdown.target -ConditionKernelCommandLine=!mandos=off -ConditionFileIsExecutable=/lib/mandos/password-agent -ConditionPathIsMountPoint=!/sysroot - -[Path] -PathExistsGlob=/run/systemd/ask-password/ask.* -MakeDirectory=yes === removed file 'dracut-module/ask-password-mandos.service' --- dracut-module/ask-password-mandos.service 2019-07-27 10:11:45 +0000 +++ dracut-module/ask-password-mandos.service 1970-01-01 00:00:00 +0000 @@ -1,51 +0,0 @@ -# -*- systemd -*- -# -# Copyright © 2019 Teddy Hogeborn -# Copyright © 2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# -# This systemd.service(5) unit file will start the Mandos -# password-agent(8mandos) program, which will in turn run -# mandos-client(8mandos) to get a password and send the password to -# any and all active password questions using the systemd “Password -# Agent” mechanism. - -# This file should be installed in the root file system as -# "/usr/lib/dracut/modules.d/90mandos/ask-password-mandos.service" and -# will be installed in the initramfs image file as -# "/lib/systemd/system/ask-password-mandos.service" by dracut when -# dracut creates the initramfs image file. - -[Unit] -Description=Forward Password Requests to remote Mandos server -Documentation=man:intro(8mandos) man:password-agent(8mandos) man:mandos-client(8mandos) -DefaultDependencies=no -Conflicts=shutdown.target -Before=shutdown.target -ConditionKernelCommandLine=!mandos=off -ConditionFileIsExecutable=/lib/mandos/password-agent -ConditionFileIsExecutable=/lib/mandos/mandos-client -ConditionFileNotEmpty=/etc/mandos/keys/pubkey.txt -ConditionFileNotEmpty=/etc/mandos/keys/seckey.txt -ConditionFileNotEmpty=/etc/mandos/keys/tls-pubkey.pem -ConditionFileNotEmpty=/etc/mandos/keys/tls-privkey.pem -ConditionPathIsMountPoint=!/sysroot - -[Service] -ExecStart=/lib/mandos/password-agent -- /lib/mandos/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem === removed file 'dracut-module/cmdline-mandos.sh' --- dracut-module/cmdline-mandos.sh 2019-07-27 10:11:45 +0000 +++ dracut-module/cmdline-mandos.sh 1970-01-01 00:00:00 +0000 @@ -1,74 +0,0 @@ -#!/bin/sh -# -# This file should be present in the root file system directory -# /usr/lib/dracut/modules.d/90mandos. When dracut creates the -# initramfs image, dracut will run the "module-setup.sh" file in the -# same directory, which (when *not* using the "systemd" dracut module) -# will copy this file ("cmdline-mandos.sh") into the initramfs as -# "/lib/dracut/hooks/cmdline/20-cmdline-mandos.sh". -# -# Despite the above #!/bin/sh line and the executable flag, this file -# is not executed; this file is sourced by the /init script in the -# initramfs image created by dracut. - -if getargbool 1 mandos && [ -e /lib/dracut-crypt-lib.sh ]; then - cat >> /lib/dracut-crypt-lib.sh <<- "EOF" - ask_for_password(){ - local cmd; local prompt; local tries=3 - local ply_cmd; local ply_prompt; local ply_tries=3 - local tty_cmd; local tty_prompt; local tty_tries=3 - local ret - - while [ $# -gt 0 ]; do - case "$1" in - --cmd) ply_cmd="$2"; tty_cmd="$2"; shift;; - --ply-cmd) ply_cmd="$2"; shift;; - --tty-cmd) tty_cmd="$2"; shift;; - --prompt) ply_prompt="$2"; tty_prompt="$2"; shift;; - --ply-prompt) ply_prompt="$2"; shift;; - --tty-prompt) tty_prompt="$2"; shift;; - --tries) ply_tries="$2"; tty_tries="$2"; shift;; - --ply-tries) ply_tries="$2"; shift;; - --tty-tries) tty_tries="$2"; shift;; - --tty-echo-off) tty_echo_off=yes;; - -*) :;; - esac - shift - done - if [ -z "$ply_cmd" ]; then - ply_cmd="$tty_cmd" - fi - # Extract device and luksname from $ply_cmd - set -- $ply_cmd - shift - for arg in "$@"; do - case "$arg" in - -*) :;; - *) - if [ -z "$device" ]; then - device="$arg" - else - luksname="$arg" - break - fi - ;; - esac - done - { flock -s 9; - if [ -z "$ply_prompt" ]; then - if [ -z "$tty_prompt" ]; then - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - else - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --options-for=password-prompt:--prompt="${tty_prompt}" --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - fi - else - if [ -z "$tty_prompt" ]; then - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --options-for=plymouth:--prompt="${ply_prompt}" --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - else - CRYPTTAB_SOURCE="$device" cryptsource="$device" CRYPTTAB_NAME="$luksname" crypttarget="$luksname" /lib/mandos/plugin-runner --options-for=password-prompt:--prompt="${tty_prompt}" --options-for=plymouth:--prompt="${ply_prompt}" --config-file=/etc/mandos/plugin-runner.conf | $ply_cmd - fi - fi - } 9>/.console_lock - } - EOF -fi === removed file 'dracut-module/module-setup.sh' --- dracut-module/module-setup.sh 2019-07-27 10:11:45 +0000 +++ dracut-module/module-setup.sh 1970-01-01 00:00:00 +0000 @@ -1,253 +0,0 @@ -#!/bin/sh -# -# This file should be present in the root file system directory -# /usr/lib/dracut/modules.d/90mandos. When dracut creates the -# initramfs image, dracut will source this file and run the shell -# functions defined in this file: "install", "check", "depends", -# "cmdline", and "installkernel". -# -# Despite the above #!/bin/sh line and the executable flag, this file -# is not executed; this file is sourced by dracut when creating the -# initramfs image file. - -mandos_libdir(){ - for dir in /usr/lib \ - "/usr/lib/`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`" \ - "`rpm --eval='%{_libdir}' 2>/dev/null`" /usr/local/lib; do - if [ -d "$dir"/mandos ]; then - echo "$dir"/mandos - return - fi - done - # Mandos not found - return 1 -} - -mandos_keydir(){ - for dir in /etc/keys/mandos /etc/mandos/keys; do - if [ -d "$dir" ]; then - echo "$dir" - return - fi - done - # Mandos key directory not found - return 1 -} - -check(){ - if [ "${hostonly:-no}" = "no" ]; then - dwarning "Mandos: Dracut not in hostonly mode" - return 1 - fi - - local libdir=`mandos_libdir` - if [ -z "$libdir" ]; then - dwarning "Mandos lib directory not found" - return 1 - fi - - local keydir=`mandos_keydir` - if [ -z "$keydir" ]; then - dwarning "Mandos key directory not found" - return 1 - fi -} - -install(){ - chmod go+w,+t "$initdir"/tmp - local libdir=`mandos_libdir` - local keydir=`mandos_keydir` - set `{ getent passwd _mandos \ - || getent passwd nobody \ - || echo ::65534:65534:::; } \ - | cut --delimiter=: --fields=3,4 --only-delimited \ - --output-delimiter=" "` - local mandos_user="$1" - local mandos_group="$2" - inst "${libdir}" /lib/mandos - if dracut_module_included "systemd"; then - plugindir=/lib/mandos - inst "${libdir}/plugins.d/mandos-client" \ - "${plugindir}/mandos-client" - chmod u-s "${initdir}/${plugindir}/mandos-client" - inst "${moddir}/ask-password-mandos.service" \ - "${systemdsystemunitdir}/ask-password-mandos.service" - if [ ${mandos_user} != 65534 ]; then - sed --in-place \ - --expression="s,^ExecStart=/lib/mandos/password-agent ,&--user=${mandos_user} ," \ - "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" - fi - if [ ${mandos_group} != 65534 ]; then - sed --in-place \ - --expression="s,^ExecStart=/lib/mandos/password-agent ,&--group=${mandos_group} ," \ - "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" - fi - else - inst_hook cmdline 20 "$moddir"/cmdline-mandos.sh - plugindir=/lib/mandos/plugins.d - inst "${libdir}/plugin-runner" /lib/mandos/plugin-runner - inst /etc/mandos/plugin-runner.conf - sed --in-place \ - --expression='1i--options-for=mandos-client:--pubkey=/etc/mandos/keys/pubkey.txt,--seckey=/etc/mandos/keys/seckey.txt,--tls-pubkey=/etc/mandos/keys/tls-pubkey.pem,--tls-privkey=/etc/mandos/keys/tls-privkey.pem' \ - "${initdir}/etc/mandos/plugin-runner.conf" - if [ ${mandos_user} != 65534 ]; then - sed --in-place --expression="1i--userid=${mandos_user}" \ - "${initdir}/etc/mandos/plugin-runner.conf" - fi - if [ ${mandos_group} != 65534 ]; then - sed --in-place \ - --expression="1i--groupid=${mandos_group}" \ - "${initdir}/etc/mandos/plugin-runner.conf" - fi - inst "${libdir}/plugins.d" "$plugindir" - chown ${mandos_user}:${mandos_group} "${initdir}/${plugindir}" - # Copy the packaged plugins - for file in "$libdir"/plugins.d/*; do - base="`basename \"$file\"`" - # Is this plugin overridden? - if [ -e "/etc/mandos/plugins.d/$base" ]; then - continue - fi - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") dwarning "Mandos client plugin directory is empty." >&2 ;; - askpass-fifo) : ;; # Ignore packaged for dracut - *) inst "${file}" "${plugindir}/${base}" ;; - esac - done - # Copy any user-supplied plugins - for file in /etc/mandos/plugins.d/*; do - base="`basename \"$file\"`" - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) inst "$file" "${plugindir}/${base}" ;; - esac - done - # Copy any user-supplied plugin helpers - for file in /etc/mandos/plugin-helpers/*; do - base="`basename \"$file\"`" - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) inst "$file" "/lib/mandos/plugin-helpers/$base";; - esac - done - fi - # Copy network hooks - for hook in /etc/mandos/network-hooks.d/*; do - basename=`basename "$hook"` - case "$basename" in - "*") continue ;; - *[!A-Za-z0-9_.-]*) continue ;; - *) test -d "$hook" || inst "$hook" "/lib/mandos/network-hooks.d/$basename" ;; - esac - if [ -x "$hook" ]; then - # Copy any files needed by the network hook - MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=files \ - VERBOSITY=0 "$hook" files | while read file target; do - if [ ! -e "${file}" ]; then - dwarning "WARNING: file ${file} not found, requested by Mandos network hook '${basename}'" >&2 - fi - if [ -z "${target}" ]; then - inst "$file" - else - inst "$file" "$target" - fi - done - fi - done - # Copy the packaged plugin helpers - for file in "$libdir"/plugin-helpers/*; do - base="`basename \"$file\"`" - # Is this plugin overridden? - if [ -e "/etc/mandos/plugin-helpers/$base" ]; then - continue - fi - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) inst "$file" "/lib/mandos/plugin-helpers/$base";; - esac - done - local gpg=/usr/bin/gpg - if [ -e /usr/bin/gpgconf ]; then - inst /usr/bin/gpgconf - gpg="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg:[^:]*://p'`" - gpgagent="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg-agent:[^:]*://p'`" - # Newer versions of GnuPG 2 requires the gpg-agent binary - if [ -e "$gpgagent" ]; then - inst "$gpgagent" - fi - fi - inst "$gpg" - if dracut_module_included "systemd"; then - inst "${moddir}/password-agent" /lib/mandos/password-agent - inst "${moddir}/ask-password-mandos.path" \ - "${systemdsystemunitdir}/ask-password-mandos.path" - ln_r "${systemdsystemunitdir}/ask-password-mandos.path" \ - "${systemdsystemunitdir}/sysinit.target.wants/ask-password-mandos.path" - fi - # Key files - for file in "$keydir"/*; do - if [ -d "$file" ]; then - continue - fi - case "$file" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) - inst "$file" "/etc/mandos/keys/`basename \"$file\"`" - chown ${mandos_user}:${mandos_group} \ - "${initdir}/etc/mandos/keys/`basename \"$file\"`" - if [ `basename "$file"` = dhparams.pem ]; then - # Use Diffie-Hellman parameters file - if dracut_module_included "systemd"; then - sed --in-place \ - --expression='/^ExecStart/s/$/ --dh-params=\/etc\/mandos\/keys\/dhparams.pem/' \ - "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" - else - sed --in-place \ - --expression="1i--options-for=mandos-client:--dh-params=/etc/mandos/keys/dhparams.pem" \ - "${initdir}/etc/mandos/plugin-runner.conf" - fi - fi - ;; - esac - done -} - -installkernel(){ - instmods =drivers/net - hostonly='' instmods ipv6 - # Copy any kernel modules needed by network hooks - for hook in /etc/mandos/network-hooks.d/*; do - basename=`basename "$hook"` - case "$basename" in - "*") continue ;; - *[!A-Za-z0-9_.-]*) continue ;; - esac - if [ -x "$hook" ]; then - # Copy and load any modules needed by the network hook - MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=modules \ - VERBOSITY=0 "$hook" modules | while read module; do - if [ -z "${target}" ]; then - instmods "$module" - fi - done - fi - done -} - -depends(){ - echo crypt -} - -cmdline(){ - : -} === removed file 'dracut-module/password-agent.c' --- dracut-module/password-agent.c 2019-10-20 01:48:38 +0000 +++ dracut-module/password-agent.c 1970-01-01 00:00:00 +0000 @@ -1,8112 +0,0 @@ -/* -*- mode: c; coding: utf-8; after-save-hook: (lambda () (let* ((find-build-directory (lambda (try-directory &optional base-directory) (let ((base-directory (or base-directory try-directory))) (cond ((equal try-directory "/") base-directory) ((file-readable-p (concat (file-name-as-directory try-directory) "Makefile")) try-directory) ((funcall find-build-directory (directory-file-name (file-name-directory try-directory)) base-directory)))))) (build-directory (funcall find-build-directory (buffer-file-name))) (local-build-directory (if (fboundp 'file-local-name) (file-local-name build-directory) (or (file-remote-p build-directory 'localname) build-directory))) (command (file-relative-name (file-name-sans-extension (buffer-file-name)) build-directory))) (pcase (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (let ((qbdir (shell-quote-argument local-build-directory)) (qcmd (shell-quote-argument command))) (format "cd %s && CFLAGS=-Werror make --silent %s && %s --test --verbose" qbdir qcmd qcmd)) nil "*Test*")) (0 (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)))) (_ (with-current-buffer "*Test*" (compilation-mode) (cd-absolute build-directory)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); -*- */ -/* - * Mandos password agent - Simple password agent to run Mandos client - * - * Copyright © 2019 Teddy Hogeborn - * Copyright © 2019 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos 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 Mandos. If not, see . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE -#include /* uintmax_t, PRIuMAX, PRIdMAX, - intmax_t, uint32_t, SCNx32, - SCNuMAX, SCNxMAX */ -#include /* size_t */ -#include /* pid_t, uid_t, gid_t, getuid(), - getpid() */ -#include /* bool, true, false */ -#include /* struct sigaction, sigset_t, - sigemptyset(), sigaddset(), - SIGCHLD, pthread_sigmask(), - SIG_BLOCK, SIG_SETMASK, SA_RESTART, - SA_NOCLDSTOP, sigfillset(), kill(), - SIGTERM, sigdelset(), SIGKILL, - NSIG, sigismember(), SA_ONSTACK, - SIG_DFL, SIG_IGN, SIGINT, SIGQUIT, - SIGHUP, SIGSTOP, SIG_UNBLOCK */ -#include /* EXIT_SUCCESS, EXIT_FAILURE, - malloc(), free(), strtoumax(), - realloc(), setenv(), calloc(), - mkdtemp(), mkostemp() */ -#include /* not, or, and, xor */ -#include /* error() */ -#include /* EX_USAGE, EX_OSERR, EX_OSFILE */ -#include /* errno, error_t, EACCES, - ENAMETOOLONG, ENOENT, ENOTDIR, - EEXIST, ECHILD, EPERM, ENOMEM, - EAGAIN, EINTR, ENOBUFS, EADDRINUSE, - ECONNREFUSED, ECONNRESET, - ETOOMANYREFS, EMSGSIZE, EBADF, - EINVAL */ -#include /* strdup(), memcpy(), - explicit_bzero(), memset(), - strcmp(), strlen(), strncpy(), - memcmp(), basename() */ -#include /* argz_create(), argz_count(), - argz_extract(), argz_next(), - argz_add() */ -#include /* epoll_create1(), EPOLL_CLOEXEC, - epoll_ctl(), EPOLL_CTL_ADD, - struct epoll_event, EPOLLIN, - EPOLLRDHUP, EPOLLOUT, - epoll_pwait() */ -#include /* struct timespec, clock_gettime(), - CLOCK_MONOTONIC */ -#include /* struct argp_option, OPTION_HIDDEN, - OPTION_ALIAS, struct argp_state, - ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS, - struct argp, argp_parse(), - ARGP_NO_EXIT */ -#include /* uid_t, gid_t, close(), pipe2(), - fork(), _exit(), dup2(), - STDOUT_FILENO, setresgid(), - setresuid(), execv(), ssize_t, - read(), dup3(), getuid(), dup(), - STDERR_FILENO, pause(), write(), - rmdir(), unlink(), getpid() */ -#include /* munlock(), mlock() */ -#include /* O_CLOEXEC, O_NONBLOCK, fcntl(), - F_GETFD, F_GETFL, FD_CLOEXEC, - open(), O_WRONLY, O_NOCTTY, - O_RDONLY, O_NOFOLLOW */ -#include /* waitpid(), WNOHANG, WIFEXITED(), - WEXITSTATUS() */ -#include /* PIPE_BUF, NAME_MAX, INT_MAX */ -#include /* inotify_init1(), IN_NONBLOCK, - IN_CLOEXEC, inotify_add_watch(), - IN_CLOSE_WRITE, IN_MOVED_TO, - IN_MOVED_FROM, IN_DELETE, - IN_EXCL_UNLINK, IN_ONLYDIR, - struct inotify_event */ -#include /* fnmatch(), FNM_FILE_NAME */ -#include /* asprintf(), FILE, fopen(), - getline(), sscanf(), feof(), - ferror(), fclose(), stderr, - rename(), fdopen(), fprintf(), - fscanf() */ -#include /* GKeyFile, g_key_file_free(), g_key_file_new(), - GError, g_key_file_load_from_file(), - G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT, - g_key_file_get_string(), guint64, - g_key_file_get_uint64(), - G_KEY_FILE_ERROR_KEY_NOT_FOUND, gconstpointer, - g_assert_true(), g_assert_nonnull(), - g_assert_null(), g_assert_false(), - g_assert_cmpint(), g_assert_cmpuint(), - g_test_skip(), g_assert_cmpstr(), - g_test_init(), g_test_add(), g_test_run(), - GOptionContext, g_option_context_new(), - g_option_context_set_help_enabled(), FALSE, - g_option_context_set_ignore_unknown_options(), - gboolean, GOptionEntry, G_OPTION_ARG_NONE, - g_option_context_add_main_entries(), - g_option_context_parse(), - g_option_context_free(), g_error() */ -#include /* struct sockaddr_un, SUN_LEN */ -#include /* AF_LOCAL, socket(), PF_LOCAL, - SOCK_DGRAM, SOCK_NONBLOCK, - SOCK_CLOEXEC, connect(), - struct sockaddr, socklen_t, - shutdown(), SHUT_RD, send(), - MSG_NOSIGNAL, bind(), recv(), - socketpair() */ -#include /* globfree(), glob_t, glob(), - GLOB_ERR, GLOB_NOSORT, GLOB_MARK, - GLOB_ABORTED, GLOB_NOMATCH, - GLOB_NOSPACE */ - -/* End of includes */ - -/* Start of declarations of private types and functions */ - -/* microseconds of CLOCK_MONOTONIC absolute time; 0 means unset */ -typedef uintmax_t mono_microsecs; - -/* "task_queue" - A queue of tasks to be run */ -typedef struct { - struct task_struct *tasks; /* Tasks in this queue */ - size_t length; /* Number of tasks */ - /* Memory allocated for "tasks", in bytes */ - size_t allocated; - /* Time when this queue should be run, at the latest */ - mono_microsecs next_run; -} __attribute__((designated_init)) task_queue; - -/* "task_func" - A function type for task functions - - I.e. functions for the code which runs when a task is run, all have - this type */ -typedef void (task_func) (const struct task_struct, - task_queue *const) - __attribute__((nonnull)); - -/* "buffer" - A data buffer for a growing array of bytes - - Used for the "password" variable */ -typedef struct { - char *data; - size_t length; - size_t allocated; -} __attribute__((designated_init)) buffer; - -/* "string_set" - A set type which can contain strings - - Used by the "cancelled_filenames" variable */ -typedef struct { - char *argz; /* Do not access these except in */ - size_t argz_len; /* the string_set_* functions */ -} __attribute__((designated_init)) string_set; - -/* "task_context" - local variables for tasks - - This data structure distinguishes between different tasks which are - using the same function. This data structure is passed to every - task function when each task is run. - - Note that not every task uses every struct member. */ -typedef struct task_struct { - task_func *const func; /* The function run by this task */ - char *const question_filename; /* The question file */ - const pid_t pid; /* Mandos client process ID */ - const int epoll_fd; /* The epoll set file descriptor */ - bool *const quit_now; /* Set to true on fatal errors */ - const int fd; /* General purpose file descriptor */ - bool *const mandos_client_exited; /* Set true when client exits */ - buffer *const password; /* As read from client process */ - bool *const password_is_read; /* "password" is done growing */ - char *filename; /* General purpose file name */ - /* A set of strings of all the file names of questions which have - been cancelled for any reason; tasks pertaining to these question - files should not be run */ - string_set *const cancelled_filenames; - const mono_microsecs notafter; /* "NotAfter" from question file */ - /* Updated before each queue run; is compared with queue.next_run */ - const mono_microsecs *const current_time; -} __attribute__((designated_init)) task_context; - -/* Declare all our functions here so we can define them in any order - below. Note: test functions are *not* declared here, they are - declared in the test section. */ -__attribute__((warn_unused_result)) -static bool should_only_run_tests(int *, char **[]); -__attribute__((warn_unused_result, cold)) -static bool run_tests(int, char *[]); -static void handle_sigchld(__attribute__((unused)) int sig){} -__attribute__((warn_unused_result, malloc)) -task_queue *create_queue(void); -__attribute__((nonnull, warn_unused_result)) -bool add_to_queue(task_queue *const, const task_context); -__attribute__((nonnull)) -void cleanup_task(const task_context *const); -__attribute__((nonnull)) -void cleanup_queue(task_queue *const *const); -__attribute__((pure, nonnull, warn_unused_result)) -bool queue_has_question(const task_queue *const); -__attribute__((nonnull)) -void cleanup_close(const int *const); -__attribute__((nonnull)) -void cleanup_string(char *const *const); -__attribute__((nonnull)) -void cleanup_buffer(buffer *const); -__attribute__((pure, nonnull, warn_unused_result)) -bool string_set_contains(const string_set, const char *const); -__attribute__((nonnull, warn_unused_result)) -bool string_set_add(string_set *const, const char *const); -__attribute__((nonnull)) -void string_set_clear(string_set *); -void string_set_swap(string_set *const, string_set *const); -__attribute__((nonnull, warn_unused_result)) -bool start_mandos_client(task_queue *const, const int, bool *const, - bool *const, buffer *const, bool *const, - const struct sigaction *const, - const sigset_t, const char *const, - const uid_t, const gid_t, - const char *const *const); -__attribute__((nonnull)) -task_func wait_for_mandos_client_exit; -__attribute__((nonnull)) -task_func read_mandos_client_output; -__attribute__((warn_unused_result)) -bool add_inotify_dir_watch(task_queue *const, const int, bool *const, - buffer *const, const char *const, - string_set *, const mono_microsecs *const, - bool *const, bool *const); -__attribute__((nonnull)) -task_func read_inotify_event; -__attribute__((nonnull)) -task_func open_and_parse_question; -__attribute__((nonnull)) -task_func cancel_old_question; -__attribute__((nonnull)) -task_func connect_question_socket; -__attribute__((nonnull)) -task_func send_password_to_socket; -__attribute__((warn_unused_result)) -bool add_existing_questions(task_queue *const, const int, - buffer *const, string_set *, - const mono_microsecs *const, - bool *const, bool *const, - const char *const); -__attribute__((nonnull, warn_unused_result)) -bool wait_for_event(const int, const mono_microsecs, - const mono_microsecs); -bool run_queue(task_queue **const, string_set *const, bool *const); -bool clear_all_fds_from_epoll_set(const int); -mono_microsecs get_current_time(void); -__attribute__((nonnull, warn_unused_result)) -bool setup_signal_handler(struct sigaction *const); -__attribute__((nonnull)) -bool restore_signal_handler(const struct sigaction *const); -__attribute__((nonnull, warn_unused_result)) -bool block_sigchld(sigset_t *const); -__attribute__((nonnull)) -bool restore_sigmask(const sigset_t *const); -__attribute__((nonnull)) -bool parse_arguments(int, char *[], const bool, char **, char **, - uid_t *const , gid_t *const, char **, size_t *); - -/* End of declarations of private types and functions */ - -/* Start of "main" section; this section LACKS TESTS! - - Code here should be as simple as possible. */ - -/* These are required to be global by Argp */ -const char *argp_program_version = "password-agent " VERSION; -const char *argp_program_bug_address = ""; - -int main(int argc, char *argv[]){ - - /* If the --test option is passed, skip all normal operations and - instead only run the run_tests() function, which also does all - its own option parsing, so we don't have to do anything here. */ - if(should_only_run_tests(&argc, &argv)){ - if(run_tests(argc, argv)){ - return EXIT_SUCCESS; /* All tests successful */ - } - return EXIT_FAILURE; /* Some test(s) failed */ - } - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - - uid_t user = 0; - gid_t group = 0; - - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - if(not parse_arguments(argc, argv, true, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)){ - /* This should never happen, since "true" is passed as the third - argument to parse_arguments() above, which should make - argp_parse() call exit() if any parsing error occurs. */ - error(EX_USAGE, errno, "Failed to parse arguments"); - } - - const char default_agent_directory[] = "/run/systemd/ask-password"; - const char default_helper_directory[] - = "/lib/mandos/plugin-helpers"; - const char *const default_argv[] - = {"/lib/mandos/plugins.d/mandos-client", NULL }; - - /* Set variables to default values if unset */ - if(agent_directory == NULL){ - agent_directory = strdup(default_agent_directory); - if(agent_directory == NULL){ - error(EX_OSERR, errno, "Failed strdup()"); - } - } - if(helper_directory == NULL){ - helper_directory = strdup(default_helper_directory); - if(helper_directory == NULL){ - error(EX_OSERR, errno, "Failed strdup()"); - } - } - if(user == 0){ - user = 65534; /* nobody */ - } - if(group == 0){ - group = 65534; /* nogroup */ - } - /* If parse_opt did not create an argz vector, create one with - default values */ - if(mandos_argz == NULL){ -#ifdef __GNUC__ -#pragma GCC diagnostic push - /* argz_create() takes a non-const argv for some unknown reason - - argz_create() isn't modifying the strings, just copying them. - Therefore, this cast to non-const should be safe. */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - errno = argz_create((char *const *)default_argv, &mandos_argz, - &mandos_argz_length); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - if(errno != 0){ - error(EX_OSERR, errno, "Failed argz_create()"); - } - } - /* Use argz vector to create a normal argv, usable by execv() */ - - char **mandos_argv = malloc((argz_count(mandos_argz, - mandos_argz_length) - + 1) * sizeof(char *)); - if(mandos_argv == NULL){ - error_t saved_errno = errno; - free(mandos_argz); - error(EX_OSERR, saved_errno, "Failed malloc()"); - } - argz_extract(mandos_argz, mandos_argz_length, mandos_argv); - - sigset_t orig_sigmask; - if(not block_sigchld(&orig_sigmask)){ - return EX_OSERR; - } - - struct sigaction old_sigchld_action; - if(not setup_signal_handler(&old_sigchld_action)){ - return EX_OSERR; - } - - mono_microsecs current_time = 0; - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if(epoll_fd < 0){ - error(EX_OSERR, errno, "Failed to create epoll set fd"); - } - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - if(queue == NULL){ - error(EX_OSERR, errno, "Failed to create task queue"); - } - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - - /* Add tasks to queue */ - if(not start_mandos_client(queue, epoll_fd, &mandos_client_exited, - &quit_now, &password, &password_is_read, - &old_sigchld_action, orig_sigmask, - helper_directory, user, group, - (const char *const *)mandos_argv)){ - return EX_OSERR; /* Error has already been printed */ - } - /* These variables were only for start_mandos_client() and are not - needed anymore */ - free(mandos_argv); - free(mandos_argz); - mandos_argz = NULL; - if(not add_inotify_dir_watch(queue, epoll_fd, &quit_now, &password, - agent_directory, &cancelled_filenames, - ¤t_time, &mandos_client_exited, - &password_is_read)){ - switch(errno){ /* Error has already been printed */ - case EACCES: - case ENAMETOOLONG: - case ENOENT: - case ENOTDIR: - return EX_OSFILE; - default: - return EX_OSERR; - } - } - if(not add_existing_questions(queue, epoll_fd, &password, - &cancelled_filenames, ¤t_time, - &mandos_client_exited, - &password_is_read, agent_directory)){ - return EXIT_FAILURE; /* Error has already been printed */ - } - - /* Run queue */ - do { - current_time = get_current_time(); - if(not wait_for_event(epoll_fd, queue->next_run, current_time)){ - const error_t saved_errno = errno; - error(EXIT_FAILURE, saved_errno, "Failure while waiting for" - " events"); - } - - current_time = get_current_time(); - if(not run_queue(&queue, &cancelled_filenames, &quit_now)){ - const error_t saved_errno = errno; - error(EXIT_FAILURE, saved_errno, "Failure while running queue"); - } - - /* When no tasks about questions are left in the queue, break out - of the loop (and implicitly exit the program) */ - } while(queue_has_question(queue)); - - restore_signal_handler(&old_sigchld_action); - restore_sigmask(&orig_sigmask); - - return EXIT_SUCCESS; -} - -__attribute__((warn_unused_result)) -mono_microsecs get_current_time(void){ - struct timespec currtime; - if(clock_gettime(CLOCK_MONOTONIC, &currtime) != 0){ - error(0, errno, "Failed to get current time"); - return 0; - } - return ((mono_microsecs)currtime.tv_sec * 1000000) /* seconds */ - + ((mono_microsecs)currtime.tv_nsec / 1000); /* nanoseconds */ -} - -/* End of "main" section */ - -/* Start of regular code section; ALL this code has tests */ - -__attribute__((nonnull)) -bool parse_arguments(int argc, char *argv[], const bool exit_failure, - char **agent_directory, char **helper_directory, - uid_t *const user, gid_t *const group, - char **mandos_argz, size_t *mandos_argz_length){ - - const struct argp_option options[] = { - { .name="agent-directory",.key='d', .arg="DIRECTORY", - .doc="Systemd password agent directory" }, - { .name="helper-directory",.key=128, .arg="DIRECTORY", - .doc="Mandos Client password helper directory" }, - { .name="plugin-helper-dir", .key=129, /* From plugin-runner */ - .flags=OPTION_HIDDEN | OPTION_ALIAS }, - { .name="user", .key='u', .arg="USERID", - .doc="User ID the Mandos Client will use as its unprivileged" - " user" }, - { .name="userid", .key=130, /* From plugin--runner */ - .flags=OPTION_HIDDEN | OPTION_ALIAS }, - { .name="group", .key='g', .arg="GROUPID", - .doc="Group ID the Mandos Client will use as its unprivileged" - " group" }, - { .name="groupid", .key=131, /* From plugin--runner */ - .flags=OPTION_HIDDEN | OPTION_ALIAS }, - { .name="test", .key=255, /* See should_only_run_tests() */ - .doc="Skip normal operation, and only run self-tests. See" - " --test --help.", .group=10, }, - { NULL }, - }; - - __attribute__((nonnull(3))) - error_t parse_opt(int key, char *arg, struct argp_state *state){ - errno = 0; - switch(key){ - case 'd': /* --agent-directory */ - *agent_directory = strdup(arg); - break; - case 128: /* --helper-directory */ - case 129: /* --plugin-helper-dir */ - *helper_directory = strdup(arg); - break; - case 'u': /* --user */ - case 130: /* --userid */ - { - char *tmp; - uintmax_t tmp_id = 0; - errno = 0; - tmp_id = (uid_t)strtoumax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (uid_t)tmp_id or (uid_t)tmp_id == 0){ - return ARGP_ERR_UNKNOWN; - } - *user = (uid_t)tmp_id; - errno = 0; - break; - } - case 'g': /* --group */ - case 131: /* --groupid */ - { - char *tmp; - uintmax_t tmp_id = 0; - errno = 0; - tmp_id = (uid_t)strtoumax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (gid_t)tmp_id or (gid_t)tmp_id == 0){ - return ARGP_ERR_UNKNOWN; - } - *group = (gid_t)tmp_id; - errno = 0; - break; - } - case ARGP_KEY_ARGS: - /* Copy arguments into argz vector */ - return argz_create(state->argv + state->next, mandos_argz, - mandos_argz_length); - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - const struct argp argp = { - .options=options, - .parser=parse_opt, - .args_doc="[MANDOS_CLIENT [OPTION...]]\n--test", - .doc = "Mandos password agent -- runs Mandos client as a" - " systemd password agent", - }; - - errno = argp_parse(&argp, argc, argv, - exit_failure ? 0 : ARGP_NO_EXIT, NULL, NULL); - - return errno == 0; -} - -__attribute__((nonnull, warn_unused_result)) -bool block_sigchld(sigset_t *const orig_sigmask){ - sigset_t sigchld_sigmask; - if(sigemptyset(&sigchld_sigmask) < 0){ - error(0, errno, "Failed to empty signal set"); - return false; - } - if(sigaddset(&sigchld_sigmask, SIGCHLD) < 0){ - error(0, errno, "Failed to add SIGCHLD to signal set"); - return false; - } - if(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, orig_sigmask) != 0){ - error(0, errno, "Failed to block SIGCHLD signal"); - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result, const)) -bool restore_sigmask(const sigset_t *const orig_sigmask){ - if(pthread_sigmask(SIG_SETMASK, orig_sigmask, NULL) != 0){ - error(0, errno, "Failed to restore blocked signals"); - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool setup_signal_handler(struct sigaction *const old_sigchld_action){ - struct sigaction sigchld_action = { - .sa_handler=handle_sigchld, - .sa_flags=SA_RESTART | SA_NOCLDSTOP, - }; - /* Set all signals in "sa_mask" struct member; this makes all - signals automatically blocked during signal handler */ - if(sigfillset(&sigchld_action.sa_mask) != 0){ - error(0, errno, "Failed to do sigfillset()"); - return false; - } - if(sigaction(SIGCHLD, &sigchld_action, old_sigchld_action) != 0){ - error(0, errno, "Failed to set SIGCHLD signal handler"); - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool restore_signal_handler(const struct sigaction *const - old_sigchld_action){ - if(sigaction(SIGCHLD, old_sigchld_action, NULL) != 0){ - error(0, errno, "Failed to restore signal handler"); - return false; - } - return true; -} - -__attribute__((warn_unused_result, malloc)) -task_queue *create_queue(void){ - task_queue *queue = malloc(sizeof(task_queue)); - if(queue){ - queue->tasks = NULL; - queue->length = 0; - queue->allocated = 0; - queue->next_run = 0; - } - return queue; -} - -__attribute__((nonnull, warn_unused_result)) -bool add_to_queue(task_queue *const queue, const task_context task){ - const size_t needed_size = sizeof(task_context)*(queue->length + 1); - if(needed_size > (queue->allocated)){ - task_context *const new_tasks = realloc(queue->tasks, - needed_size); - if(new_tasks == NULL){ - error(0, errno, "Failed to allocate %" PRIuMAX - " bytes for queue->tasks", (uintmax_t)needed_size); - return false; - } - queue->tasks = new_tasks; - queue->allocated = needed_size; - } - /* Using memcpy here is necessary because doing */ - /* queue->tasks[queue->length++] = task; */ - /* would violate const-ness of task members */ - memcpy(&(queue->tasks[queue->length++]), &task, - sizeof(task_context)); - return true; -} - -__attribute__((nonnull)) -void cleanup_task(const task_context *const task){ - const error_t saved_errno = errno; - /* free and close all task data */ - free(task->question_filename); - if(task->filename != task->question_filename){ - free(task->filename); - } - if(task->pid > 0){ - kill(task->pid, SIGTERM); - } - if(task->fd > 0){ - close(task->fd); - } - errno = saved_errno; -} - -__attribute__((nonnull)) -void free_queue(task_queue *const queue){ - free(queue->tasks); - free(queue); -} - -__attribute__((nonnull)) -void cleanup_queue(task_queue *const *const queue){ - if(*queue == NULL){ - return; - } - for(size_t i = 0; i < (*queue)->length; i++){ - const task_context *const task = ((*queue)->tasks)+i; - cleanup_task(task); - } - free_queue(*queue); -} - -__attribute__((pure, nonnull, warn_unused_result)) -bool queue_has_question(const task_queue *const queue){ - for(size_t i=0; i < queue->length; i++){ - if(queue->tasks[i].question_filename != NULL){ - return true; - } - } - return false; -} - -__attribute__((nonnull)) -void cleanup_close(const int *const fd){ - const error_t saved_errno = errno; - close(*fd); - errno = saved_errno; -} - -__attribute__((nonnull)) -void cleanup_string(char *const *const ptr){ - free(*ptr); -} - -__attribute__((nonnull)) -void cleanup_buffer(buffer *buf){ - if(buf->allocated > 0){ -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25) - explicit_bzero(buf->data, buf->allocated); -#else - memset(buf->data, '\0', buf->allocated); -#endif - } - if(buf->data != NULL){ - if(munlock(buf->data, buf->allocated) != 0){ - error(0, errno, "Failed to unlock memory of old buffer"); - } - free(buf->data); - buf->data = NULL; - } - buf->length = 0; - buf->allocated = 0; -} - -__attribute__((pure, nonnull, warn_unused_result)) -bool string_set_contains(const string_set set, const char *const str){ - for(const char *s = set.argz; s != NULL and set.argz_len > 0; - s = argz_next(set.argz, set.argz_len, s)){ - if(strcmp(s, str) == 0){ - return true; - } - } - return false; -} - -__attribute__((nonnull, warn_unused_result)) -bool string_set_add(string_set *const set, const char *const str){ - if(string_set_contains(*set, str)){ - return true; - } - error_t error = argz_add(&set->argz, &set->argz_len, str); - if(error == 0){ - return true; - } - errno = error; - return false; -} - -__attribute__((nonnull)) -void string_set_clear(string_set *set){ - free(set->argz); - set->argz = NULL; - set->argz_len = 0; -} - -__attribute__((nonnull)) -void string_set_swap(string_set *const set1, string_set *const set2){ - /* Swap contents of two string sets */ - { - char *const tmp_argz = set1->argz; - set1->argz = set2->argz; - set2->argz = tmp_argz; - } - { - const size_t tmp_argz_len = set1->argz_len; - set1->argz_len = set2->argz_len; - set2->argz_len = tmp_argz_len; - } -} - -__attribute__((nonnull, warn_unused_result)) -bool start_mandos_client(task_queue *const queue, - const int epoll_fd, - bool *const mandos_client_exited, - bool *const quit_now, buffer *const password, - bool *const password_is_read, - const struct sigaction *const - old_sigchld_action, const sigset_t sigmask, - const char *const helper_directory, - const uid_t user, const gid_t group, - const char *const *const argv){ - int pipefds[2]; - if(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK) != 0){ - error(0, errno, "Failed to pipe2(..., O_CLOEXEC | O_NONBLOCK)"); - return false; - } - - const pid_t pid = fork(); - if(pid == 0){ - if(not restore_signal_handler(old_sigchld_action)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&sigmask)){ - _exit(EXIT_FAILURE); - } - if(close(pipefds[0]) != 0){ - error(0, errno, "Failed to close() parent pipe fd"); - _exit(EXIT_FAILURE); - } - if(dup2(pipefds[1], STDOUT_FILENO) == -1){ - error(0, errno, "Failed to dup2() pipe fd to stdout"); - _exit(EXIT_FAILURE); - } - if(close(pipefds[1]) != 0){ - error(0, errno, "Failed to close() old child pipe fd"); - _exit(EXIT_FAILURE); - } - if(setenv("MANDOSPLUGINHELPERDIR", helper_directory, 1) != 0){ - error(0, errno, "Failed to setenv(\"MANDOSPLUGINHELPERDIR\"," - " \"%s\", 1)", helper_directory); - _exit(EXIT_FAILURE); - } - if(group != 0 and setresgid(group, 0, 0) == -1){ - error(0, errno, "Failed to setresgid(-1, %" PRIuMAX ", %" - PRIuMAX")", (uintmax_t)group, (uintmax_t)group); - _exit(EXIT_FAILURE); - } - if(user != 0 and setresuid(user, 0, 0) == -1){ - error(0, errno, "Failed to setresuid(-1, %" PRIuMAX ", %" - PRIuMAX")", (uintmax_t)user, (uintmax_t)user); - _exit(EXIT_FAILURE); - } -#ifdef __GNUC__ -#pragma GCC diagnostic push - /* For historical reasons, the "argv" argument to execv() is not - const, but it is safe to override this. */ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - execv(argv[0], (char **)argv); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - error(0, errno, "execv(\"%s\", ...) failed", argv[0]); - _exit(EXIT_FAILURE); - } - close(pipefds[1]); - - if(not add_to_queue(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=mandos_client_exited, - .quit_now=quit_now, - })){ - error(0, errno, "Failed to add wait_for_mandos_client to queue"); - close(pipefds[0]); - return false; - } - - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipefds[0], - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to add file descriptor to epoll set"); - close(pipefds[0]); - return false; - } - - return add_to_queue(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=quit_now, - .password=password, - .password_is_read=password_is_read, - }); -} - -__attribute__((nonnull)) -void wait_for_mandos_client_exit(const task_context task, - task_queue *const queue){ - const pid_t pid = task.pid; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const quit_now = task.quit_now; - - int status; - switch(waitpid(pid, &status, WNOHANG)){ - case 0: /* Not exited yet */ - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add myself to queue"); - *quit_now = true; - } - break; - case -1: /* Error */ - error(0, errno, "waitpid(%" PRIdMAX ") failed", (intmax_t)pid); - if(errno != ECHILD){ - kill(pid, SIGTERM); - } - *quit_now = true; - break; - default: /* Has exited */ - *mandos_client_exited = true; - if((not WIFEXITED(status)) - or (WEXITSTATUS(status) != EXIT_SUCCESS)){ - error(0, 0, "Mandos client failed or was killed"); - *quit_now = true; - } - } -} - -__attribute__((nonnull)) -void read_mandos_client_output(const task_context task, - task_queue *const queue){ - buffer *const password = task.password; - bool *const quit_now = task.quit_now; - bool *const password_is_read = task.password_is_read; - const int fd = task.fd; - const int epoll_fd = task.epoll_fd; - - const size_t new_potential_size = (password->length + PIPE_BUF); - if(password->allocated < new_potential_size){ - char *const new_buffer = calloc(new_potential_size, 1); - if(new_buffer == NULL){ - error(0, errno, "Failed to allocate %" PRIuMAX - " bytes for password", (uintmax_t)new_potential_size); - *quit_now = true; - close(fd); - return; - } - if(mlock(new_buffer, new_potential_size) != 0){ - /* Warn but do not treat as fatal error */ - if(errno != EPERM and errno != ENOMEM){ - error(0, errno, "Failed to lock memory for password"); - } - } - if(password->length > 0){ - memcpy(new_buffer, password->data, password->length); -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25) - explicit_bzero(password->data, password->allocated); -#else - memset(password->data, '\0', password->allocated); -#endif - } - if(password->data != NULL){ - if(munlock(password->data, password->allocated) != 0){ - error(0, errno, "Failed to unlock memory of old buffer"); - } - free(password->data); - } - password->data = new_buffer; - password->allocated = new_potential_size; - } - - const ssize_t read_length = read(fd, password->data - + password->length, PIPE_BUF); - - if(read_length == 0){ /* EOF */ - *password_is_read = true; - close(fd); - return; - } - if(read_length < 0 and errno != EAGAIN){ /* Actual error */ - error(0, errno, "Failed to read password from Mandos client"); - *quit_now = true; - close(fd); - return; - } - if(read_length > 0){ /* Data has been read */ - password->length += (size_t)read_length; - } - - /* Either data was read, or EAGAIN was indicated, meaning no data - available yet */ - - /* Re-add the fd to the epoll set */ - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to re-add file descriptor to epoll set"); - *quit_now = true; - close(fd); - return; - } - - /* Re-add myself to the queue */ - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add myself to queue"); - *quit_now = true; - close(fd); - } -} - -__attribute__((nonnull, warn_unused_result)) -bool add_inotify_dir_watch(task_queue *const queue, - const int epoll_fd, bool *const quit_now, - buffer *const password, - const char *const dir, - string_set *cancelled_filenames, - const mono_microsecs *const current_time, - bool *const mandos_client_exited, - bool *const password_is_read){ - const int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); - if(fd == -1){ - error(0, errno, "Failed to create inotify instance"); - return false; - } - - if(inotify_add_watch(fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO - | IN_MOVED_FROM| IN_DELETE | IN_EXCL_UNLINK - | IN_ONLYDIR) - == -1){ - error(0, errno, "Failed to create inotify watch on %s", dir); - return false; - } - - /* Add the inotify fd to the epoll set */ - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to add file descriptor to epoll set"); - close(fd); - return false; - } - - const task_context read_inotify_event_task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .quit_now=quit_now, - .password=password, - .fd=fd, - .filename=strdup(dir), - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - }; - if(read_inotify_event_task.filename == NULL){ - error(0, errno, "Failed to strdup(\"%s\")", dir); - close(fd); - return false; - } - - return add_to_queue(queue, read_inotify_event_task); -} - -__attribute__((nonnull)) -void read_inotify_event(const task_context task, - task_queue *const queue){ - const int fd = task.fd; - const int epoll_fd = task.epoll_fd; - char *const filename = task.filename; - bool *quit_now = task.quit_now; - buffer *const password = task.password; - string_set *const cancelled_filenames = task.cancelled_filenames; - const mono_microsecs *const current_time = task.current_time; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const ssize_t read_length = read(fd, ievent, ievent_size); - if(read_length == 0){ /* EOF */ - error(0, 0, "Got EOF from inotify fd for directory %s", filename); - *quit_now = true; - cleanup_task(&task); - return; - } - if(read_length < 0 and errno != EAGAIN){ /* Actual error */ - error(0, errno, "Failed to read from inotify fd for directory %s", - filename); - *quit_now = true; - cleanup_task(&task); - return; - } - if(read_length > 0 /* Data has been read */ - and fnmatch("ask.*", ievent->name, FNM_FILE_NAME) == 0){ - char *question_filename = NULL; - const ssize_t question_filename_length - = asprintf(&question_filename, "%s/%s", filename, ievent->name); - if(question_filename_length < 0){ - error(0, errno, "Failed to create file name from directory name" - " %s and file name %s", filename, ievent->name); - } else { - if(ievent->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)){ - if(not add_to_queue(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .question_filename=question_filename, - .filename=question_filename, - .password=password, - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - })){ - error(0, errno, "Failed to add open_and_parse_question task" - " for file name %s to queue", filename); - } else { - /* Force the added task (open_and_parse_question) to run - immediately */ - queue->next_run = 1; - } - } else if(ievent->mask & (IN_MOVED_FROM | IN_DELETE)){ - if(not string_set_add(cancelled_filenames, - question_filename)){ - error(0, errno, "Could not add question %s to" - " cancelled_questions", question_filename); - *quit_now = true; - free(question_filename); - cleanup_task(&task); - return; - } - free(question_filename); - } - } - } - - /* Either data was read, or EAGAIN was indicated, meaning no data - available yet */ - - /* Re-add myself to the queue */ - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to re-add read_inotify_event(%s) to" - " queue", filename); - *quit_now = true; - cleanup_task(&task); - return; - } - - /* Re-add the fd to the epoll set */ - const int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }); - if(ret != 0 and errno != EEXIST){ - error(0, errno, "Failed to re-add inotify file descriptor %d for" - " directory %s to epoll set", fd, filename); - /* Force the added task (read_inotify_event) to run again, at most - one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } -} - -__attribute__((nonnull)) -void open_and_parse_question(const task_context task, - task_queue *const queue){ - __attribute__((cleanup(cleanup_string))) - char *question_filename = task.question_filename; - const int epoll_fd = task.epoll_fd; - buffer *const password = task.password; - string_set *const cancelled_filenames = task.cancelled_filenames; - const mono_microsecs *const current_time = task.current_time; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - - /* We use the GLib "Key-value file parser" functions to parse the - question file. See for specification of contents */ - __attribute__((nonnull)) - void cleanup_g_key_file(GKeyFile **key_file){ - if(*key_file != NULL){ - g_key_file_free(*key_file); - } - } - - __attribute__((cleanup(cleanup_g_key_file))) - GKeyFile *key_file = g_key_file_new(); - if(key_file == NULL){ - error(0, errno, "Failed g_key_file_new() for \"%s\"", - question_filename); - return; - } - GError *glib_error = NULL; - if(g_key_file_load_from_file(key_file, question_filename, - G_KEY_FILE_NONE, &glib_error) != TRUE){ - /* If a file was removed, we should ignore it, so */ - /* only show error message if file actually existed */ - if(glib_error->code != G_FILE_ERROR_NOENT){ - error(0, 0, "Failed to load question data from file \"%s\": %s", - question_filename, glib_error->message); - } - return; - } - - __attribute__((cleanup(cleanup_string))) - char *socket_name = g_key_file_get_string(key_file, "Ask", - "Socket", - &glib_error); - if(socket_name == NULL){ - error(0, 0, "Question file \"%s\" did not contain \"Socket\": %s", - question_filename, glib_error->message); - return; - } - - if(strlen(socket_name) == 0){ - error(0, 0, "Question file \"%s\" had empty \"Socket\" value", - question_filename); - return; - } - - const guint64 pid = g_key_file_get_uint64(key_file, "Ask", "PID", - &glib_error); - if(glib_error != NULL){ - error(0, 0, "Question file \"%s\" contained bad \"PID\": %s", - question_filename, glib_error->message); - return; - } - - if((pid != (guint64)((pid_t)pid)) - or (kill((pid_t)pid, 0) != 0)){ - error(0, 0, "PID %" PRIuMAX " in question file \"%s\" is bad or" - " does not exist", (uintmax_t)pid, question_filename); - return; - } - - guint64 notafter = g_key_file_get_uint64(key_file, "Ask", - "NotAfter", &glib_error); - if(glib_error != NULL){ - if(glib_error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND){ - error(0, 0, "Question file \"%s\" contained bad \"NotAfter\":" - " %s", question_filename, glib_error->message); - } - notafter = 0; - } - if(notafter != 0){ - if(queue->next_run == 0 or (queue->next_run > notafter)){ - queue->next_run = notafter; - } - if(*current_time >= notafter){ - return; - } - } - - const task_context connect_question_socket_task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=password, - .filename=strdup(socket_name), - .cancelled_filenames=task.cancelled_filenames, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - .current_time=current_time, - }; - if(connect_question_socket_task.question_filename == NULL - or connect_question_socket_task.filename == NULL - or not add_to_queue(queue, connect_question_socket_task)){ - error(0, errno, "Failed to add connect_question_socket for socket" - " %s (from \"%s\") to queue", socket_name, - question_filename); - cleanup_task(&connect_question_socket_task); - return; - } - /* Force the added task (connect_question_socket) to run - immediately */ - queue->next_run = 1; - - if(notafter > 0){ - char *const dup_filename = strdup(question_filename); - const task_context cancel_old_question_task = { - .func=cancel_old_question, - .question_filename=dup_filename, - .notafter=notafter, - .filename=dup_filename, - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - }; - if(cancel_old_question_task.question_filename == NULL - or not add_to_queue(queue, cancel_old_question_task)){ - error(0, errno, "Failed to add cancel_old_question for file " - "\"%s\" to queue", question_filename); - cleanup_task(&cancel_old_question_task); - return; - } - } -} - -__attribute__((nonnull)) -void cancel_old_question(const task_context task, - task_queue *const queue){ - char *const question_filename = task.question_filename; - string_set *const cancelled_filenames = task.cancelled_filenames; - const mono_microsecs notafter = task.notafter; - const mono_microsecs *const current_time = task.current_time; - - if(*current_time >= notafter){ - if(not string_set_add(cancelled_filenames, question_filename)){ - error(0, errno, "Failed to cancel question for file %s", - question_filename); - } - cleanup_task(&task); - return; - } - - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add cancel_old_question for file " - "%s to queue", question_filename); - cleanup_task(&task); - return; - } - - if((queue->next_run == 0) or (queue->next_run > notafter)){ - queue->next_run = notafter; - } -} - -__attribute__((nonnull)) -void connect_question_socket(const task_context task, - task_queue *const queue){ - char *const question_filename = task.question_filename; - char *const filename = task.filename; - const int epoll_fd = task.epoll_fd; - buffer *const password = task.password; - string_set *const cancelled_filenames = task.cancelled_filenames; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - const mono_microsecs *const current_time = task.current_time; - - struct sockaddr_un sock_name = { .sun_family=AF_LOCAL }; - - if(sizeof(sock_name.sun_path) <= strlen(filename)){ - error(0, 0, "Socket filename is larger than" - " sizeof(sockaddr_un.sun_path); %" PRIuMAX ": \"%s\"", - (uintmax_t)sizeof(sock_name.sun_path), filename); - if(not string_set_add(cancelled_filenames, question_filename)){ - error(0, errno, "Failed to cancel question for file %s", - question_filename); - } - cleanup_task(&task); - return; - } - - const int fd = socket(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if(fd < 0){ - error(0, errno, - "Failed to create socket(PF_LOCAL, SOCK_DGRAM, 0)"); - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add connect_question_socket for file" - " \"%s\" and socket \"%s\" to queue", question_filename, - filename); - cleanup_task(&task); - } else { - /* Force the added task (connect_question_socket) to run - immediately */ - queue->next_run = 1; - } - return; - } - - strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path)); - if(connect(fd, (struct sockaddr *)&sock_name, - (socklen_t)SUN_LEN(&sock_name)) != 0){ - error(0, errno, "Failed to connect socket to \"%s\"", filename); - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add connect_question_socket for file" - " \"%s\" and socket \"%s\" to queue", question_filename, - filename); - cleanup_task(&task); - } else { - /* Force the added task (connect_question_socket) to run again, - at most one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } - return; - } - - /* Not necessary, but we can try, and merely warn on failure */ - if(shutdown(fd, SHUT_RD) != 0){ - error(0, errno, "Failed to shutdown reading from socket \"%s\"", - filename); - } - - /* Add the fd to the epoll set */ - if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event){ .events=EPOLLOUT }) - != 0){ - error(0, errno, "Failed to add inotify file descriptor %d for" - " socket %s to epoll set", fd, filename); - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add connect_question_socket for file" - " \"%s\" and socket \"%s\" to queue", question_filename, - filename); - cleanup_task(&task); - } else { - /* Force the added task (connect_question_socket) to run again, - at most one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } - return; - } - - /* add task send_password_to_socket to queue */ - const task_context send_password_to_socket_task = { - .func=send_password_to_socket, - .question_filename=question_filename, - .filename=filename, - .epoll_fd=epoll_fd, - .fd=fd, - .password=password, - .cancelled_filenames=cancelled_filenames, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - .current_time=current_time, - }; - - if(not add_to_queue(queue, send_password_to_socket_task)){ - error(0, errno, "Failed to add send_password_to_socket for" - " file \"%s\" and socket \"%s\" to queue", - question_filename, filename); - cleanup_task(&send_password_to_socket_task); - } -} - -__attribute__((nonnull)) -void send_password_to_socket(const task_context task, - task_queue *const queue){ - char *const question_filename=task.question_filename; - char *const filename=task.filename; - const int epoll_fd=task.epoll_fd; - const int fd=task.fd; - buffer *const password=task.password; - string_set *const cancelled_filenames=task.cancelled_filenames; - bool *const mandos_client_exited = task.mandos_client_exited; - bool *const password_is_read = task.password_is_read; - const mono_microsecs *const current_time = task.current_time; - - if(*mandos_client_exited and *password_is_read){ - - const size_t send_buffer_length = password->length + 2; - char *send_buffer = malloc(send_buffer_length); - if(send_buffer == NULL){ - error(0, errno, "Failed to allocate send_buffer"); - } else { - if(mlock(send_buffer, send_buffer_length) != 0){ - /* Warn but do not treat as fatal error */ - if(errno != EPERM and errno != ENOMEM){ - error(0, errno, "Failed to lock memory for password" - " buffer"); - } - } - /* “[…] send a single datagram to the socket consisting of the - password string either prefixed with "+" or with "-" - depending on whether the password entry was successful or - not. You may but don't have to include a final NUL byte in - your message. - - — (Wed 08 Oct 2014 02:14:28 AM UTC) - */ - send_buffer[0] = '+'; /* Prefix with "+" */ - /* Always add an extra NUL */ - send_buffer[password->length + 1] = '\0'; - if(password->length > 0){ - memcpy(send_buffer + 1, password->data, password->length); - } - errno = 0; - ssize_t ssret = send(fd, send_buffer, send_buffer_length, - MSG_NOSIGNAL); - const error_t saved_errno = errno; -#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 25) - explicit_bzero(send_buffer, send_buffer_length); -#else - memset(send_buffer, '\0', send_buffer_length); -#endif - if(munlock(send_buffer, send_buffer_length) != 0){ - error(0, errno, "Failed to unlock memory of send buffer"); - } - free(send_buffer); - if(ssret < 0 or ssret < (ssize_t)send_buffer_length){ - switch(saved_errno){ - case EINTR: - case ENOBUFS: - case ENOMEM: - case EADDRINUSE: - case ECONNREFUSED: - case ECONNRESET: - case ENOENT: - case ETOOMANYREFS: - case EAGAIN: - /* Retry, below */ - break; - case EMSGSIZE: - error(0, 0, "Password of size %" PRIuMAX " is too big", - (uintmax_t)password->length); -#if __GNUC__ < 7 - /* FALLTHROUGH */ -#else - __attribute__((fallthrough)); -#endif - case 0: - if(ssret >= 0 and ssret < (ssize_t)send_buffer_length){ - error(0, 0, "Password only partially sent to socket"); - } -#if __GNUC__ < 7 - /* FALLTHROUGH */ -#else - __attribute__((fallthrough)); -#endif - default: - error(0, saved_errno, "Failed to send() to socket %s", - filename); - if(not string_set_add(cancelled_filenames, - question_filename)){ - error(0, errno, "Failed to cancel question for file %s", - question_filename); - } - cleanup_task(&task); - return; - } - } else { - /* Success */ - cleanup_task(&task); - return; - } - } - } - - /* We failed or are not ready yet; retry later */ - - if(not add_to_queue(queue, task)){ - error(0, errno, "Failed to add send_password_to_socket for" - " file %s and socket %s to queue", question_filename, - filename); - cleanup_task(&task); - } - - /* Add the fd to the epoll set */ - if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, - &(struct epoll_event){ .events=EPOLLOUT }) - != 0){ - error(0, errno, "Failed to add socket file descriptor %d for" - " socket %s to epoll set", fd, filename); - /* Force the added task (send_password_to_socket) to run again, at - most one second from now */ - if((queue->next_run == 0) - or (queue->next_run > (*current_time + 1000000))){ - queue->next_run = *current_time + 1000000; - } - } -} - -__attribute__((warn_unused_result)) -bool add_existing_questions(task_queue *const queue, - const int epoll_fd, - buffer *const password, - string_set *cancelled_filenames, - const mono_microsecs *const current_time, - bool *const mandos_client_exited, - bool *const password_is_read, - const char *const dirname){ - __attribute__((cleanup(cleanup_string))) - char *dir_pattern = NULL; - const int ret = asprintf(&dir_pattern, "%s/ask.*", dirname); - if(ret < 0 or dir_pattern == NULL){ - error(0, errno, "Could not create glob pattern for directory %s", - dirname); - return false; - } - __attribute__((cleanup(globfree))) - glob_t question_filenames = {}; - switch(glob(dir_pattern, GLOB_ERR | GLOB_NOSORT | GLOB_MARK, - NULL, &question_filenames)){ - case GLOB_ABORTED: - default: - error(0, errno, "Failed to open directory %s", dirname); - return false; - case GLOB_NOMATCH: - error(0, errno, "There are no question files in %s", dirname); - return false; - case GLOB_NOSPACE: - error(0, errno, "Could not allocate memory for question file" - " names in %s", dirname); -#if __GNUC__ < 7 - /* FALLTHROUGH */ -#else - __attribute__((fallthrough)); -#endif - case 0: - for(size_t i = 0; i < question_filenames.gl_pathc; i++){ - char *const question_filename = strdup(question_filenames - .gl_pathv[i]); - const task_context task = { - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .question_filename=question_filename, - .filename=question_filename, - .password=password, - .cancelled_filenames=cancelled_filenames, - .current_time=current_time, - .mandos_client_exited=mandos_client_exited, - .password_is_read=password_is_read, - }; - - if(question_filename == NULL - or not add_to_queue(queue, task)){ - error(0, errno, "Failed to add open_and_parse_question for" - " file %s to queue", - question_filenames.gl_pathv[i]); - free(question_filename); - } else { - queue->next_run = 1; - } - } - return true; - } -} - -__attribute__((nonnull, warn_unused_result)) -bool wait_for_event(const int epoll_fd, - const mono_microsecs queue_next_run, - const mono_microsecs current_time){ - __attribute__((const)) - int milliseconds_to_wait(const mono_microsecs currtime, - const mono_microsecs nextrun){ - if(currtime >= nextrun){ - return 0; - } - const uintmax_t wait_time_ms = (nextrun - currtime) / 1000; - if(wait_time_ms > (uintmax_t)INT_MAX){ - return INT_MAX; - } - return (int)wait_time_ms; - } - - const int wait_time_ms = milliseconds_to_wait(current_time, - queue_next_run); - - /* Prepare unblocking of SIGCHLD during epoll_pwait */ - sigset_t temporary_unblocked_sigmask; - /* Get current signal mask */ - if(pthread_sigmask(-1, NULL, &temporary_unblocked_sigmask) != 0){ - return false; - } - /* Remove SIGCHLD from the signal mask */ - if(sigdelset(&temporary_unblocked_sigmask, SIGCHLD) != 0){ - return false; - } - struct epoll_event events[8]; /* Ignored */ - int ret = epoll_pwait(epoll_fd, events, - sizeof(events) / sizeof(struct epoll_event), - queue_next_run == 0 ? -1 : (int)wait_time_ms, - &temporary_unblocked_sigmask); - if(ret < 0 and errno != EINTR){ - error(0, errno, "Failed epoll_pwait(epfd=%d, ..., timeout=%d," - " ...", epoll_fd, - queue_next_run == 0 ? -1 : (int)wait_time_ms); - return false; - } - return clear_all_fds_from_epoll_set(epoll_fd); -} - -bool clear_all_fds_from_epoll_set(const int epoll_fd){ - /* Create a new empty epoll set */ - __attribute__((cleanup(cleanup_close))) - const int new_epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if(new_epoll_fd < 0){ - return false; - } - /* dup3() the new epoll set fd over the old one, replacing it */ - if(dup3(new_epoll_fd, epoll_fd, O_CLOEXEC) < 0){ - return false; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool run_queue(task_queue **const queue, - string_set *const cancelled_filenames, - bool *const quit_now){ - - task_queue *new_queue = create_queue(); - if(new_queue == NULL){ - return false; - } - - __attribute__((cleanup(string_set_clear))) - string_set old_cancelled_filenames = {}; - string_set_swap(cancelled_filenames, &old_cancelled_filenames); - - /* Declare i outside the for loop, since we might need i after the - loop in case we aborted in the middle */ - size_t i; - for(i=0; i < (*queue)->length and not *quit_now; i++){ - task_context *const task = &((*queue)->tasks[i]); - const char *const question_filename = task->question_filename; - /* Skip any task referencing a cancelled question filename */ - if(question_filename != NULL - and string_set_contains(old_cancelled_filenames, - question_filename)){ - cleanup_task(task); - continue; - } - task->func(*task, new_queue); - } - - if(*quit_now){ - /* we might be in the middle of the queue, so clean up any - remaining tasks in the current queue */ - for(; i < (*queue)->length; i++){ - cleanup_task(&((*queue)->tasks[i])); - } - free_queue(*queue); - *queue = new_queue; - new_queue = NULL; - return false; - } - free_queue(*queue); - *queue = new_queue; - new_queue = NULL; - - return true; -} - -/* End of regular code section */ - -/* Start of tests section; here are the tests for the above code */ - -/* This "fixture" data structure is used by the test setup and - teardown functions */ -typedef struct { - struct sigaction orig_sigaction; - sigset_t orig_sigmask; -} test_fixture; - -static void test_setup(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - g_assert_true(setup_signal_handler(&fixture->orig_sigaction)); - g_assert_true(block_sigchld(&fixture->orig_sigmask)); -} - -static void test_teardown(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - g_assert_true(restore_signal_handler(&fixture->orig_sigaction)); - g_assert_true(restore_sigmask(&fixture->orig_sigmask)); -} - -/* Utility function used by tests to search queue for matching task */ -__attribute__((pure, nonnull, warn_unused_result)) -static task_context *find_matching_task(const task_queue *const queue, - const task_context task){ - /* The argument "task" structure is a pattern to match; 0 in any - member means any value matches, otherwise the value must match. - The filename strings are compared by strcmp(), not by pointer. */ - for(size_t i = 0; i < queue->length; i++){ - task_context *const current_task = queue->tasks+i; - /* Check all members of task_context, if set to a non-zero value. - If a member does not match, continue to next task in queue */ - - /* task_func *const func */ - if(task.func != NULL and current_task->func != task.func){ - continue; - } - /* char *const question_filename; */ - if(task.question_filename != NULL - and (current_task->question_filename == NULL - or strcmp(current_task->question_filename, - task.question_filename) != 0)){ - continue; - } - /* const pid_t pid; */ - if(task.pid != 0 and current_task->pid != task.pid){ - continue; - } - /* const int epoll_fd; */ - if(task.epoll_fd != 0 - and current_task->epoll_fd != task.epoll_fd){ - continue; - } - /* bool *const quit_now; */ - if(task.quit_now != NULL - and current_task->quit_now != task.quit_now){ - continue; - } - /* const int fd; */ - if(task.fd != 0 and current_task->fd != task.fd){ - continue; - } - /* bool *const mandos_client_exited; */ - if(task.mandos_client_exited != NULL - and current_task->mandos_client_exited - != task.mandos_client_exited){ - continue; - } - /* buffer *const password; */ - if(task.password != NULL - and current_task->password != task.password){ - continue; - } - /* bool *const password_is_read; */ - if(task.password_is_read != NULL - and current_task->password_is_read != task.password_is_read){ - continue; - } - /* char *filename; */ - if(task.filename != NULL - and (current_task->filename == NULL - or strcmp(current_task->filename, task.filename) != 0)){ - continue; - } - /* string_set *const cancelled_filenames; */ - if(task.cancelled_filenames != NULL - and current_task->cancelled_filenames - != task.cancelled_filenames){ - continue; - } - /* const mono_microsecs notafter; */ - if(task.notafter != 0 - and current_task->notafter != task.notafter){ - continue; - } - /* const mono_microsecs *const current_time; */ - if(task.current_time != NULL - and current_task->current_time != task.current_time){ - continue; - } - /* Current task matches all members; return it */ - return current_task; - } - /* No task in queue matches passed pattern task */ - return NULL; -} - -static void test_create_queue(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *const queue = create_queue(); - g_assert_nonnull(queue); - g_assert_null(queue->tasks); - g_assert_true(queue->length == 0); - g_assert_true(queue->next_run == 0); -} - -static task_func dummy_func; - -static void test_add_to_queue(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(add_to_queue(queue, - (task_context){ .func=dummy_func })); - g_assert_true(queue->length == 1); - g_assert_nonnull(queue->tasks); - g_assert_true(queue->tasks[0].func == dummy_func); -} - -static void dummy_func(__attribute__((unused)) - const task_context task, - __attribute__((unused)) - task_queue *const queue){ -} - -static void test_queue_has_question_empty(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_false(queue_has_question(queue)); -} - -static void test_queue_has_question_false(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(add_to_queue(queue, - (task_context){ .func=dummy_func })); - g_assert_false(queue_has_question(queue)); -} - -static void test_queue_has_question_true(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - char *const question_filename - = strdup("/nonexistent/question_filename"); - g_assert_nonnull(question_filename); - task_context task = { - .func=dummy_func, - .question_filename=question_filename, - }; - g_assert_true(add_to_queue(queue, task)); - g_assert_true(queue_has_question(queue)); -} - -static void test_queue_has_question_false2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - task_context task = { .func=dummy_func }; - g_assert_true(add_to_queue(queue, task)); - g_assert_true(add_to_queue(queue, task)); - g_assert_cmpint((int)queue->length, ==, 2); - g_assert_false(queue_has_question(queue)); -} - -static void test_queue_has_question_true2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - task_context task1 = { .func=dummy_func }; - g_assert_true(add_to_queue(queue, task1)); - char *const question_filename - = strdup("/nonexistent/question_filename"); - g_assert_nonnull(question_filename); - task_context task2 = { - .func=dummy_func, - .question_filename=question_filename, - }; - g_assert_true(add_to_queue(queue, task2)); - g_assert_cmpint((int)queue->length, ==, 2); - g_assert_true(queue_has_question(queue)); -} - -static void test_cleanup_buffer(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - buffer buf = {}; - - const size_t buffersize = 10; - - buf.data = malloc(buffersize); - g_assert_nonnull(buf.data); - if(mlock(buf.data, buffersize) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - cleanup_buffer(&buf); - g_assert_null(buf.data); -} - -static -void test_string_set_new_set_contains_nothing(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set = {}; - g_assert_false(string_set_contains(set, "")); /* Empty string */ - g_assert_false(string_set_contains(set, "test_string")); -} - -static void -test_string_set_with_added_string_contains_it(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set = {}; - g_assert_true(string_set_add(&set, "test_string")); - g_assert_true(string_set_contains(set, "test_string")); -} - -static void -test_string_set_cleared_does_not_contain_str(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set = {}; - g_assert_true(string_set_add(&set, "test_string")); - string_set_clear(&set); - g_assert_false(string_set_contains(set, "test_string")); -} - -static -void test_string_set_swap_one_with_empty(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set1 = {}; - __attribute__((cleanup(string_set_clear))) - string_set set2 = {}; - g_assert_true(string_set_add(&set1, "test_string1")); - string_set_swap(&set1, &set2); - g_assert_false(string_set_contains(set1, "test_string1")); - g_assert_true(string_set_contains(set2, "test_string1")); -} - -static -void test_string_set_swap_empty_with_one(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set1 = {}; - __attribute__((cleanup(string_set_clear))) - string_set set2 = {}; - g_assert_true(string_set_add(&set2, "test_string2")); - string_set_swap(&set1, &set2); - g_assert_true(string_set_contains(set1, "test_string2")); - g_assert_false(string_set_contains(set2, "test_string2")); -} - -static void test_string_set_swap_one_with_one(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(string_set_clear))) - string_set set1 = {}; - __attribute__((cleanup(string_set_clear))) - string_set set2 = {}; - g_assert_true(string_set_add(&set1, "test_string1")); - g_assert_true(string_set_add(&set2, "test_string2")); - string_set_swap(&set1, &set2); - g_assert_false(string_set_contains(set1, "test_string1")); - g_assert_true(string_set_contains(set1, "test_string2")); - g_assert_false(string_set_contains(set2, "test_string2")); - g_assert_true(string_set_contains(set2, "test_string1")); -} - -static bool fd_has_cloexec_and_nonblock(const int); - -static bool epoll_set_contains(int, int, uint32_t); - -static void test_start_mandos_client(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/bin/true", NULL }; - - g_assert_true(start_mandos_client(queue, epoll_fd, - &mandos_client_exited, &quit_now, - &password, &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, argv)); - - g_assert_cmpuint((unsigned int)queue->length, >=, 2); - - const task_context *const added_wait_task - = find_matching_task(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }); - g_assert_nonnull(added_wait_task); - g_assert_cmpint(added_wait_task->pid, >, 0); - g_assert_cmpint(kill(added_wait_task->pid, SIGKILL), ==, 0); - waitpid(added_wait_task->pid, NULL, 0); - - const task_context *const added_read_task - = find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }); - g_assert_nonnull(added_read_task); - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - g_assert_true(epoll_set_contains(epoll_fd, added_read_task->fd, - EPOLLIN | EPOLLRDHUP)); -} - -static bool fd_has_cloexec_and_nonblock(const int fd){ - const int socket_fd_flags = fcntl(fd, F_GETFD, 0); - const int socket_file_flags = fcntl(fd, F_GETFL, 0); - return ((socket_fd_flags >= 0) - and (socket_fd_flags & FD_CLOEXEC) - and (socket_file_flags >= 0) - and (socket_file_flags & O_NONBLOCK)); -} - -__attribute__((const)) -bool is_privileged(void){ - uid_t user = getuid() + 1; - if(user == 0){ /* Overflow check */ - user++; - } - gid_t group = getuid() + 1; - if(group == 0){ /* Overflow check */ - group++; - } - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(setresgid((uid_t)-1, group, group) == -1){ - if(errno != EPERM){ - error(EXIT_FAILURE, errno, "Failed to setresgid(-1, %" PRIuMAX - ", %" PRIuMAX")", (uintmax_t)group, (uintmax_t)group); - } - exit(EXIT_FAILURE); - } - if(setresuid((uid_t)-1, user, user) == -1){ - if(errno != EPERM){ - error(EXIT_FAILURE, errno, "Failed to setresuid(-1, %" PRIuMAX - ", %" PRIuMAX")", (uintmax_t)user, (uintmax_t)user); - } - exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - int status; - waitpid(pid, &status, 0); - if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){ - return true; - } - return false; -} - -static bool epoll_set_contains(int epoll_fd, int fd, uint32_t events){ - /* Only scan for events in this eventmask */ - const uint32_t eventmask = EPOLLIN | EPOLLOUT | EPOLLRDHUP; - __attribute__((cleanup(cleanup_string))) - char *fdinfo_name = NULL; - int ret = asprintf(&fdinfo_name, "/proc/self/fdinfo/%d", epoll_fd); - g_assert_cmpint(ret, >, 0); - g_assert_nonnull(fdinfo_name); - - FILE *fdinfo = fopen(fdinfo_name, "r"); - g_assert_nonnull(fdinfo); - uint32_t reported_events; - buffer line = {}; - int found_fd = -1; - - do { - if(getline(&line.data, &line.allocated, fdinfo) < 0){ - break; - } - /* See proc(5) for format of /proc/PID/fdinfo/FD for epoll fd's */ - if(sscanf(line.data, "tfd: %d events: %" SCNx32 " ", - &found_fd, &reported_events) == 2){ - if(found_fd == fd){ - break; - } - } - } while(not feof(fdinfo) and not ferror(fdinfo)); - g_assert_cmpint(fclose(fdinfo), ==, 0); - free(line.data); - if(found_fd != fd){ - return false; - } - - if(events == 0){ - /* Don't check events if none are given */ - return true; - } - return (reported_events & eventmask) == (events & eventmask); -} - -static void test_start_mandos_client_execv(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - const char helper_directory[] = "/nonexistent"; - /* Can't execv("/", ...), so this should fail */ - const char *const argv[] = { "/", NULL }; - - { - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - dup2(devnull_fd, STDERR_FILENO); - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, - &password, - (bool[]){false}, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, - argv); - dup2(real_stderr_fd, STDERR_FILENO); - g_assert_true(success); - } - g_assert_cmpuint((unsigned int)queue->length, ==, 2); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - - { - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - dup2(devnull_fd, STDERR_FILENO); - const bool success = run_queue(&queue, &cancelled_filenames, - &quit_now); - dup2(real_stderr_fd, STDERR_FILENO); - if(not success){ - break; - } - } - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); -} - -static void test_start_mandos_client_suid_euid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--user", NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((uid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, 0); -} - -static void test_start_mandos_client_suid_egid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--group", NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((gid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, 0); -} - -static void test_start_mandos_client_suid_ruid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--user", "--real", - NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((uid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, user); -} - -static void test_start_mandos_client_suid_rgid(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - if(not is_privileged()){ - g_test_skip("Not privileged"); - return; - } - - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/usr/bin/id", "--group", "--real", - NULL }; - uid_t user = 1000; - gid_t group = 1001; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, user, - group, argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_nonnull(password.data); - - uintmax_t id; - g_assert_cmpint(sscanf(password.data, "%" SCNuMAX "\n", &id), - ==, 1); - g_assert_true((gid_t)id == id); - - g_assert_cmpuint((unsigned int)id, ==, group); -} - -static void test_start_mandos_client_read(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char dummy_test_password[] = "dummy test password"; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/bin/echo", "-n", dummy_test_password, - NULL }; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, - argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_cmpint((int)password.length, ==, - sizeof(dummy_test_password)-1); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(dummy_test_password, password.data, - sizeof(dummy_test_password)-1), ==, 0); -} - -static -void test_start_mandos_client_helper_directory(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - const char *const argv[] = { "/bin/sh", "-c", - "echo -n ${MANDOSPLUGINHELPERDIR}", NULL }; - - const bool success = start_mandos_client(queue, epoll_fd, - &mandos_client_exited, - &quit_now, &password, - &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, - argv); - g_assert_true(success); - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while(((queue->length) > 0) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(mandos_client_exited); - - g_assert_true(password_is_read); - g_assert_cmpint((int)password.length, ==, - sizeof(helper_directory)-1); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(helper_directory, password.data, - sizeof(helper_directory)-1), ==, 0); -} - -__attribute__((nonnull, warn_unused_result)) -static bool proc_status_sigblk_to_sigset(const char *const, - sigset_t *const); - -static void test_start_mandos_client_sigmask(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - bool password_is_read = false; - const char helper_directory[] = "/nonexistent"; - /* see proc(5) for format of /proc/self/status */ - const char *const argv[] = { "/usr/bin/awk", - "$1==\"SigBlk:\"{ print $2 }", "/proc/self/status", NULL }; - - g_assert_true(start_mandos_client(queue, epoll_fd, - &mandos_client_exited, &quit_now, - &password, &password_is_read, - &fixture->orig_sigaction, - fixture->orig_sigmask, - helper_directory, 0, 0, argv)); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - queue->next_run = 0; - string_set cancelled_filenames = {}; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not (mandos_client_exited and password_is_read)) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - g_assert_true(mandos_client_exited); - g_assert_true(password_is_read); - - sigset_t parsed_sigmask; - g_assert_true(proc_status_sigblk_to_sigset(password.data, - &parsed_sigmask)); - - for(int signum = 1; signum < NSIG; signum++){ - const bool has_signal = sigismember(&parsed_sigmask, signum); - if(sigismember(&fixture->orig_sigmask, signum)){ - g_assert_true(has_signal); - } else { - g_assert_false(has_signal); - } - } -} - -__attribute__((nonnull, warn_unused_result)) -static bool proc_status_sigblk_to_sigset(const char *const sigblk, - sigset_t *const sigmask){ - /* parse /proc/PID/status SigBlk value and convert to a sigset_t */ - uintmax_t scanned_sigmask; - if(sscanf(sigblk, "%" SCNxMAX " ", &scanned_sigmask) != 1){ - return false; - } - if(sigemptyset(sigmask) != 0){ - return false; - } - for(int signum = 1; signum < NSIG; signum++){ - if(scanned_sigmask & ((uintmax_t)1 << (signum-1))){ - if(sigaddset(sigmask, signum) != 0){ - return false; - } - } - } - return true; -} - -static void run_task_with_stderr_to_dev_null(const task_context task, - task_queue *const queue); - -static -void test_wait_for_mandos_client_exit_badpid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - - bool mandos_client_exited = false; - bool quit_now = false; - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const task_context task = { - .func=wait_for_mandos_client_exit, - .pid=1, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }; - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_false(mandos_client_exited); - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void run_task_with_stderr_to_dev_null(const task_context task, - task_queue *const queue){ - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - - stderr = devnull; - task.func(task, queue); - stderr = real_stderr; - - g_assert_cmpint(fclose(devnull), ==, 0); -} - -static -void test_wait_for_mandos_client_exit_noexit(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_eternal_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - while(true){ - pause(); - } - } - return pid; - } - pid_t pid = create_eternal_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const task_context task = { - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(mandos_client_exited); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=task.pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - })); -} - -static -void test_wait_for_mandos_client_exit_success(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_successful_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - return pid; - } - const pid_t pid = create_successful_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const task_context initial_task = { - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - }; - g_assert_true(add_to_queue(queue, initial_task)); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - do { - queue->next_run = 0; - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - g_assert_true(run_queue(&queue, (string_set[]){{}}, &quit_now)); - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not mandos_client_exited) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(mandos_client_exited); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static -void test_wait_for_mandos_client_exit_failure(test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_failing_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - exit(EXIT_FAILURE); - } - return pid; - } - const pid_t pid = create_failing_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(add_to_queue(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - })); - - g_assert_true(sigismember(&fixture->orig_sigmask, SIGCHLD) == 0); - - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - dup2(devnull_fd, STDERR_FILENO); - const bool success = run_queue(&queue, &cancelled_filenames, - &quit_now); - dup2(real_stderr_fd, STDERR_FILENO); - if(not success){ - break; - } - - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not mandos_client_exited) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(quit_now); - g_assert_true(mandos_client_exited); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static -void test_wait_for_mandos_client_exit_killed(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - bool mandos_client_exited = false; - bool quit_now = false; - - pid_t create_killed_process(void){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - while(true){ - pause(); - } - } - kill(pid, SIGKILL); - return pid; - } - const pid_t pid = create_killed_process(); - g_assert_true(pid != -1); - - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - g_assert_true(add_to_queue(queue, (task_context){ - .func=wait_for_mandos_client_exit, - .pid=pid, - .mandos_client_exited=&mandos_client_exited, - .quit_now=&quit_now, - })); - - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC, O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - - struct timespec starttime, currtime; - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &starttime) == 0); - do { - g_assert_true(wait_for_event(epoll_fd, queue->next_run, 0)); - dup2(devnull_fd, STDERR_FILENO); - const bool success = run_queue(&queue, &cancelled_filenames, - &quit_now); - dup2(real_stderr_fd, STDERR_FILENO); - if(not success){ - break; - } - - g_assert_true(clock_gettime(CLOCK_MONOTONIC, &currtime) == 0); - } while((not mandos_client_exited) - and (not quit_now) - and ((currtime.tv_sec - starttime.tv_sec) < 10)); - - g_assert_true(mandos_client_exited); - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static bool epoll_set_does_not_contain(int, int); - -static -void test_read_mandos_client_output_readerror(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - /* Reading /proc/self/mem from offset 0 will always give EIO */ - const int fd = open("/proc/self/mem", - O_RDONLY | O_CLOEXEC | O_NOCTTY); - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=fd, - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, 0); - g_assert_true(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, fd)); - - g_assert_cmpint(close(fd), ==, -1); -} - -static bool epoll_set_does_not_contain(int epoll_fd, int fd){ - return not epoll_set_contains(epoll_fd, fd, 0); -} - -static -void test_read_mandos_client_output_nodata(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, 0); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static void test_read_mandos_client_output_eof(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - g_assert_true(password_is_read); - g_assert_cmpint((int)password.length, ==, 0); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0])); - - g_assert_cmpint(close(pipefds[0]), ==, -1); -} - -static -void test_read_mandos_client_output_once(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - const char dummy_test_password[] = "dummy test password"; - /* Start with a pre-allocated buffer */ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(sizeof(dummy_test_password)), - .length=0, - .allocated=sizeof(dummy_test_password), - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF); - g_assert_cmpint((int)write(pipefds[1], dummy_test_password, - sizeof(dummy_test_password)), - ==, (int)sizeof(dummy_test_password)); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, - (int)sizeof(dummy_test_password)); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(password.data, dummy_test_password, - sizeof(dummy_test_password)), ==, 0); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static -void test_read_mandos_client_output_malloc(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - const char dummy_test_password[] = "dummy test password"; - /* Start with an empty buffer */ - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF); - g_assert_cmpint((int)write(pipefds[1], dummy_test_password, - sizeof(dummy_test_password)), - ==, (int)sizeof(dummy_test_password)); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, - (int)sizeof(dummy_test_password)); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(password.data, dummy_test_password, - sizeof(dummy_test_password)), ==, 0); - - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static -void test_read_mandos_client_output_append(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - const char dummy_test_password[] = "dummy test password"; - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(PIPE_BUF), - .length=PIPE_BUF, - .allocated=PIPE_BUF, - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - - memset(password.data, 'x', PIPE_BUF); - char password_expected[PIPE_BUF]; - memcpy(password_expected, password.data, PIPE_BUF); - - bool password_is_read = false; - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - g_assert_true(sizeof(dummy_test_password) <= PIPE_BUF); - g_assert_cmpint((int)write(pipefds[1], dummy_test_password, - sizeof(dummy_test_password)), - ==, (int)sizeof(dummy_test_password)); - - task_context task = { - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - }; - task.func(task, queue); - - g_assert_false(password_is_read); - g_assert_cmpint((int)password.length, ==, - PIPE_BUF + sizeof(dummy_test_password)); - g_assert_nonnull(password.data); - g_assert_cmpint(memcmp(password_expected, password.data, PIPE_BUF), - ==, 0); - g_assert_cmpint(memcmp(password.data + PIPE_BUF, - dummy_test_password, - sizeof(dummy_test_password)), ==, 0); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_mandos_client_output, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .password=&password, - .password_is_read=&password_is_read, - .quit_now=&quit_now, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); -} - -static char *make_temporary_directory(void); - -static void test_add_inotify_dir_watch(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .quit_now=&quit_now, - .password=&password, - .filename=tempdir, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - g_assert_true(epoll_set_contains(added_read_task->epoll_fd, - added_read_task->fd, - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_temporary_directory(void){ - char *name = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(name); - char *result = mkdtemp(name); - if(result == NULL){ - free(name); - } - return result; -} - -static void test_add_inotify_dir_watch_fail(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - const char nonexistent_dir[] = "/nonexistent"; - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, nonexistent_dir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void test_add_inotify_dir_watch_nondir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - const char not_a_directory[] = "/dev/tty"; - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - g_assert_false(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, not_a_directory, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void test_add_inotify_dir_watch_EAGAIN(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - g_assert_cmpint(read(added_read_task->fd, ievent, ievent_size), ==, - -1); - g_assert_cmpint(errno, ==, EAGAIN); - - free(ievent); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_temporary_file_in_directory(const char - *const dir); - -static -void test_add_inotify_dir_watch_IN_CLOSE_WRITE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - __attribute__((cleanup(cleanup_string))) - char *filename = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(filename); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = 0; - read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_CLOSE_WRITE); - g_assert_cmpstr(ievent->name, ==, basename(filename)); - - free(ievent); - - g_assert_cmpint(unlink(filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_temporary_prefixed_file_in_directory(const char - *const prefix, - const char - *const dir){ - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%sXXXXXX", dir, prefix), - >, 0); - g_assert_nonnull(filename); - const int fd = mkostemp(filename, O_CLOEXEC); - g_assert_cmpint(fd, >=, 0); - g_assert_cmpint(close(fd), ==, 0); - return filename; -} - -static char *make_temporary_file_in_directory(const char - *const dir){ - return make_temporary_prefixed_file_in_directory("temp", dir); -} - -static -void test_add_inotify_dir_watch_IN_MOVED_TO(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *watchdir = make_temporary_directory(); - g_assert_nonnull(watchdir); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, watchdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - char *sourcedir = make_temporary_directory(); - g_assert_nonnull(sourcedir); - - __attribute__((cleanup(cleanup_string))) - char *filename = make_temporary_file_in_directory(sourcedir); - g_assert_nonnull(filename); - - __attribute__((cleanup(cleanup_string))) - char *targetfilename = NULL; - g_assert_cmpint(asprintf(&targetfilename, "%s/%s", watchdir, - basename(filename)), >, 0); - g_assert_nonnull(targetfilename); - - g_assert_cmpint(rename(filename, targetfilename), ==, 0); - g_assert_cmpint(rmdir(sourcedir), ==, 0); - free(sourcedir); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_MOVED_TO); - g_assert_cmpstr(ievent->name, ==, basename(targetfilename)); - - free(ievent); - - g_assert_cmpint(unlink(targetfilename), ==, 0); - g_assert_cmpint(rmdir(watchdir), ==, 0); -} - -static -void test_add_inotify_dir_watch_IN_MOVED_FROM(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfilename); - - __attribute__((cleanup(cleanup_string))) - char *targetdir = make_temporary_directory(); - g_assert_nonnull(targetdir); - - __attribute__((cleanup(cleanup_string))) - char *targetfilename = NULL; - g_assert_cmpint(asprintf(&targetfilename, "%s/%s", targetdir, - basename(tempfilename)), >, 0); - g_assert_nonnull(targetfilename); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - - g_assert_cmpint(rename(tempfilename, targetfilename), ==, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_MOVED_FROM); - g_assert_cmpstr(ievent->name, ==, basename(tempfilename)); - - free(ievent); - - g_assert_cmpint(unlink(targetfilename), ==, 0); - g_assert_cmpint(rmdir(targetdir), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_add_inotify_dir_watch_IN_DELETE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - __attribute__((cleanup(cleanup_string))) - char *tempfile = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfile); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - g_assert_cmpint(unlink(tempfile), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = 0; - read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_DELETE); - g_assert_cmpstr(ievent->name, ==, basename(tempfile)); - - free(ievent); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_add_inotify_dir_watch_IN_EXCL_UNLINK(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - __attribute__((cleanup(cleanup_string))) - char *tempfile = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfile); - int tempfile_fd = open(tempfile, O_WRONLY | O_CLOEXEC | O_NOCTTY - | O_NOFOLLOW); - g_assert_cmpint(tempfile_fd, >, 2); - - g_assert_true(add_inotify_dir_watch(queue, epoll_fd, &quit_now, - &password, tempdir, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read)); - g_assert_cmpint(unlink(tempfile), ==, 0); - - g_assert_cmpuint((unsigned int)queue->length, >, 0); - - const task_context *const added_read_task - = find_matching_task(queue, - (task_context){ .func=read_inotify_event }); - g_assert_nonnull(added_read_task); - - g_assert_cmpint(added_read_task->fd, >, 2); - g_assert_true(fd_has_cloexec_and_nonblock(added_read_task->fd)); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - struct inotify_event *ievent = malloc(ievent_size); - g_assert_nonnull(ievent); - - ssize_t read_size = 0; - read_size = read(added_read_task->fd, ievent, ievent_size); - - g_assert_cmpint((int)read_size, >, 0); - g_assert_true(ievent->mask & IN_DELETE); - g_assert_cmpstr(ievent->name, ==, basename(tempfile)); - - g_assert_cmpint(close(tempfile_fd), ==, 0); - - /* IN_EXCL_UNLINK should make the closing of the previously unlinked - file not appear as an ievent, so we should not see it now. */ - read_size = read(added_read_task->fd, ievent, ievent_size); - g_assert_cmpint((int)read_size, ==, -1); - g_assert_true(errno == EAGAIN); - - free(ievent); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void test_read_inotify_event_readerror(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - /* Reading /proc/self/mem from offset 0 will always result in EIO */ - const int fd = open("/proc/self/mem", - O_RDONLY | O_CLOEXEC | O_NOCTTY); - - bool quit_now = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=fd, - .quit_now=&quit_now, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - }; - g_assert_nonnull(task.filename); - run_task_with_stderr_to_dev_null(task, queue); - g_assert_true(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, fd)); - - g_assert_cmpint(close(fd), ==, -1); -} - -static void test_read_inotify_event_bad_epoll(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - const mono_microsecs current_time = 17; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - const int epoll_fd = pipefds[0]; /* This will obviously fail */ - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - g_assert_nonnull(task.filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_nonnull(find_matching_task(queue, task)); - g_assert_true(queue->next_run == 1000000 + current_time); - - g_assert_cmpint(close(pipefds[0]), ==, 0); - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static void test_read_inotify_event_nodata(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - g_assert_nonnull(task.filename); - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpint(close(pipefds[1]), ==, 0); -} - -static void test_read_inotify_event_eof(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_true(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, pipefds[0])); - - g_assert_cmpint(close(pipefds[0]), ==, -1); -} - -static -void test_read_inotify_event_IN_CLOSE_WRITE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_CLOSE_WRITE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run != 0); - g_assert_cmpuint((unsigned int)queue->length, >=, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpuint((unsigned int)queue->length, >=, 2); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=filename, - .question_filename=filename, - .password=&password, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); -} - -static -void test_read_inotify_event_IN_MOVED_TO(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_MOVED_TO; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run != 0); - g_assert_cmpuint((unsigned int)queue->length, >=, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - g_assert_cmpuint((unsigned int)queue->length, >=, 2); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=filename, - .question_filename=filename, - .password=&password, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); -} - -static -void test_read_inotify_event_IN_MOVED_FROM(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_MOVED_FROM; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_true(string_set_contains(*task.cancelled_filenames, - filename)); -} - -static void test_read_inotify_event_IN_DELETE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ask.dummy_file_name"; - ievent->mask = IN_DELETE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_true(string_set_contains(*task.cancelled_filenames, - filename)); -} - -static void -test_read_inotify_event_IN_CLOSE_WRITE_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_CLOSE_WRITE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); -} - -static void -test_read_inotify_event_IN_MOVED_TO_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_MOVED_TO; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames = &(string_set){}, - .notafter=0, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=task.cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); -} - -static void -test_read_inotify_event_IN_MOVED_FROM_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_MOVED_FROM; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_false(string_set_contains(cancelled_filenames, filename)); -} - -static -void test_read_inotify_event_IN_DELETE_badname(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - - /* "sufficient to read at least one event." - inotify(7) */ - const size_t ievent_max_size = (sizeof(struct inotify_event) - + NAME_MAX + 1); - g_assert_cmpint(ievent_max_size, <=, PIPE_BUF); - struct { - struct inotify_event event; - char name_buffer[NAME_MAX + 1]; - } ievent_buffer; - struct inotify_event *const ievent = &ievent_buffer.event; - - const char dummy_file_name[] = "ignored.dummy_file_name"; - ievent->mask = IN_DELETE; - ievent->len = sizeof(dummy_file_name); - memcpy(ievent->name, dummy_file_name, sizeof(dummy_file_name)); - const size_t ievent_size = (sizeof(struct inotify_event) - + sizeof(dummy_file_name)); - g_assert_cmpint(write(pipefds[1], (char *)ievent, ievent_size), - ==, ievent_size); - g_assert_cmpint(close(pipefds[1]), ==, 0); - - bool quit_now = false; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - task_context task = { - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=strdup("/nonexistent"), - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_false(quit_now); - g_assert_true(queue->next_run == 0); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=read_inotify_event, - .epoll_fd=epoll_fd, - .fd=pipefds[0], - .quit_now=&quit_now, - .password=&password, - .filename=task.filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(epoll_set_contains(epoll_fd, pipefds[0], - EPOLLIN | EPOLLRDHUP)); - - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", task.filename, - dummy_file_name), >, 0); - g_assert_nonnull(filename); - g_assert_false(string_set_contains(cancelled_filenames, filename)); -} - -static -void test_open_and_parse_question_ENOENT(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - char *const filename = strdup("/nonexistent"); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void test_open_and_parse_question_EIO(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - char *filename = strdup("/proc/self/mem"); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static void -test_open_and_parse_question_parse_error(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int tempfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(tempfile, >, 0); - const char bad_data[] = "this is bad syntax\n"; - g_assert_cmpint(write(tempfile, bad_data, sizeof(bad_data)), - ==, sizeof(bad_data)); - g_assert_cmpint(close(tempfile), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_nosocket(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nPID=1\n"), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_badsocket(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=\nPID=1\n"), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_nopid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\n"), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void test_open_and_parse_question_badpid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=\n"), - >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=(mono_microsecs[]){0}, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_noexist_pid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - /* Find value of sysctl kernel.pid_max */ - uintmax_t pid_max = 0; - FILE *sysctl_pid_max = fopen("/proc/sys/kernel/pid_max", "r"); - g_assert_nonnull(sysctl_pid_max); - g_assert_cmpint(fscanf(sysctl_pid_max, "%" PRIuMAX, &pid_max), - ==, 1); - g_assert_cmpint(fclose(sysctl_pid_max), ==, 0); - - pid_t nonexisting_pid = ((pid_t)pid_max)+1; - g_assert_true(nonexisting_pid > 0); /* Overflow check */ - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX"\n", (uintmax_t)nonexisting_pid), - >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const question_filename = strdup(tempfilename); - g_assert_nonnull(question_filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=question_filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=question_filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_no_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX "\n", (uintmax_t)getpid()), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - __attribute__((cleanup(cleanup_string))) - char *socket_filename = strdup("/nonexistent"); - g_assert_nonnull(socket_filename); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=connect_question_socket, - .question_filename=tempfilename, - .filename=socket_filename, - .epoll_fd=epoll_fd, - .password=&password, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(queue->next_run != 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_bad_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - const mono_microsecs current_time = 0; - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX "\nNotAfter=\n", - (uintmax_t)getpid()), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - run_task_with_stderr_to_dev_null(task, queue); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - __attribute__((cleanup(cleanup_string))) - char *socket_filename = strdup("/nonexistent"); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=connect_question_socket, - .question_filename=tempfilename, - .filename=socket_filename, - .epoll_fd=epoll_fd, - .password=&password, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - g_assert_true(queue->next_run != 0); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static -void assert_open_and_parse_question_with_notafter(const mono_microsecs - current_time, - const mono_microsecs - notafter, - const mono_microsecs - next_queue_run){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = next_queue_run; - - __attribute__((cleanup(cleanup_string))) - char *tempfilename = strdup("/tmp/mandosXXXXXX"); - g_assert_nonnull(tempfilename); - int questionfile = mkostemp(tempfilename, O_CLOEXEC); - g_assert_cmpint(questionfile, >, 0); - FILE *qf = fdopen(questionfile, "w"); - g_assert_cmpint(fprintf(qf, "[Ask]\nSocket=/nonexistent\nPID=%" - PRIuMAX "\nNotAfter=%" PRIuMAX "\n", - (uintmax_t)getpid(), notafter), >, 0); - g_assert_cmpint(fclose(qf), ==, 0); - - char *const filename = strdup(tempfilename); - g_assert_nonnull(filename); - task_context task = { - .func=open_and_parse_question, - .question_filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }; - task.func(task, queue); - - if(queue->length >= 1){ - __attribute__((cleanup(cleanup_string))) - char *socket_filename = strdup("/nonexistent"); - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=connect_question_socket, - .filename=socket_filename, - .epoll_fd=epoll_fd, - .password=&password, - .current_time=¤t_time, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - g_assert_true(queue->next_run != 0); - } - - if(notafter == 0){ - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - } else if(current_time >= notafter) { - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - } else { - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=cancel_old_question, - .question_filename=tempfilename, - .filename=tempfilename, - .notafter=notafter, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - })); - } - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(tempfilename), ==, 0); -} - -static void -test_open_and_parse_question_notafter_0(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 0, 0); -} - -static void -test_open_and_parse_question_notafter_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 1, 0); -} - -static void -test_open_and_parse_question_notafter_1_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 1, 1); -} - -static void -test_open_and_parse_question_notafter_1_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(0, 1, 2); -} - -static void -test_open_and_parse_question_equal_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(1, 1, 0); -} - -static void -test_open_and_parse_question_late_notafter(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* current_time, notafter, next_queue_run */ - assert_open_and_parse_question_with_notafter(2, 1, 0); -} - -static void assert_cancel_old_question_param(const mono_microsecs - next_queue_run, - const mono_microsecs - notafter, - const mono_microsecs - current_time, - const mono_microsecs - next_set_to){ - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = next_queue_run; - - char *const question_filename = strdup("/nonexistent"); - g_assert_nonnull(question_filename); - task_context task = { - .func=cancel_old_question, - .question_filename=question_filename, - .filename=question_filename, - .notafter=notafter, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - }; - task.func(task, queue); - - if(current_time >= notafter){ - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(string_set_contains(cancelled_filenames, - "/nonexistent")); - } else { - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=cancel_old_question, - .question_filename=question_filename, - .filename=question_filename, - .notafter=notafter, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - })); - - g_assert_false(string_set_contains(cancelled_filenames, - question_filename)); - } - g_assert_cmpuint((unsigned int)queue->next_run, ==, - (unsigned int)next_set_to); -} - -static void test_cancel_old_question_0_1_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run unset, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(0, 1, 2, 0); -} - -static void test_cancel_old_question_0_2_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is 0, meaning unset, and notafter is 2, - and current_time is not yet notafter or greater, - update value of next_queue_run to value of notafter */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(0, 2, 1, 2); -} - -static void test_cancel_old_question_1_2_3(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run 1, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(1, 2, 3, 1); -} - -static void test_cancel_old_question_1_3_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is set, - and current_time is not yet notafter or greater, - and notafter is larger than next_queue_run - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(1, 3, 2, 1); -} - -static void test_cancel_old_question_2_1_3(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run 2, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(2, 1, 3, 2); -} - -static void test_cancel_old_question_2_3_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is set, - and current_time is not yet notafter or greater, - and notafter is larger than next_queue_run - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(2, 3, 1, 2); -} - -static void test_cancel_old_question_3_1_2(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* next_queue_run 3, - cancellation should happen because time has come, - next_queue_run should be unchanged */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(3, 1, 2, 3); -} - -static void test_cancel_old_question_3_2_1(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* If next_queue_run is set, - and current_time is not yet notafter or greater, - and notafter is smaller than next_queue_run - update value of next_queue_run to value of notafter */ - /* next_queue_run, notafter, current_time, next_set_to */ - assert_cancel_old_question_param(3, 2, 1, 2); -} - -static void -test_connect_question_socket_name_too_long(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const char question_filename[] = "/nonexistent/question"; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - struct sockaddr_un unix_socket = { .sun_family=AF_LOCAL }; - char socket_name[sizeof(unix_socket.sun_path)]; - memset(socket_name, 'x', sizeof(socket_name)); - socket_name[sizeof(socket_name)-1] = '\0'; - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_true(string_set_contains(cancelled_filenames, - question_filename)); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(queue->next_run == 0); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_connect_question_socket_connect_fail(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const char question_filename[] = "/nonexistent/question"; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 3; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - char socket_name[] = "nonexistent"; - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=filename, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_nonnull(find_matching_task(queue, task)); - - g_assert_false(string_set_contains(cancelled_filenames, - question_filename)); - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - g_assert_true(queue->next_run == 1000000 + current_time); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_connect_question_socket_bad_epoll(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 5; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_close))) - const int sock_fd = socket(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - g_assert_cmpint(sock_fd, >=, 0); - struct sockaddr_un sock_name = { .sun_family=AF_LOCAL }; - const char socket_name[] = "socket_name"; - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - g_assert_cmpint((int)strlen(filename), <, - (int)sizeof(sock_name.sun_path)); - strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path)); - sock_name.sun_path[sizeof(sock_name.sun_path)-1] = '\0'; - g_assert_cmpint((int)bind(sock_fd, (struct sockaddr *)&sock_name, - (socklen_t)SUN_LEN(&sock_name)), >=, 0); - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=(buffer[]){{}}, - .filename=strdup(filename), - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - const task_context *const added_task - = find_matching_task(queue, task); - g_assert_nonnull(added_task); - g_assert_true(queue->next_run == 1000000 + current_time); - - g_assert_cmpint(unlink(filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static -void test_connect_question_socket_usable(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool mandos_client_exited = false; - bool password_is_read = false; - const mono_microsecs current_time = 0; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_close))) - const int sock_fd = socket(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - g_assert_cmpint(sock_fd, >=, 0); - struct sockaddr_un sock_name = { .sun_family=AF_LOCAL }; - const char socket_name[] = "socket_name"; - __attribute__((cleanup(cleanup_string))) - char *filename = NULL; - g_assert_cmpint(asprintf(&filename, "%s/%s", tempdir, socket_name), - >, 0); - g_assert_nonnull(filename); - g_assert_cmpint((int)strlen(filename), <, - (int)sizeof(sock_name.sun_path)); - strncpy(sock_name.sun_path, filename, sizeof(sock_name.sun_path)); - sock_name.sun_path[sizeof(sock_name.sun_path)-1] = '\0'; - g_assert_cmpint((int)bind(sock_fd, (struct sockaddr *)&sock_name, - (socklen_t)SUN_LEN(&sock_name)), >=, 0); - task_context task = { - .func=connect_question_socket, - .question_filename=strdup(question_filename), - .epoll_fd=epoll_fd, - .password=&password, - .filename=strdup(filename), - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - const task_context *const added_task - = find_matching_task(queue, (task_context){ - .func=send_password_to_socket, - .question_filename=question_filename, - .filename=filename, - .epoll_fd=epoll_fd, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - .current_time=¤t_time, - }); - g_assert_nonnull(added_task); - g_assert_cmpint(added_task->fd, >, 0); - - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); - - const int fd = added_task->fd; - g_assert_cmpint(fd, >, 0); - g_assert_true(fd_has_cloexec_and_nonblock(fd)); - - /* write to fd */ - char write_data[PIPE_BUF]; - { - /* Construct test password buffer */ - /* Start with + since that is what the real procotol uses */ - write_data[0] = '+'; - /* Set a special character at string end just to mark the end */ - write_data[sizeof(write_data)-2] = 'y'; - /* Set NUL at buffer end, as suggested by the protocol */ - write_data[sizeof(write_data)-1] = '\0'; - /* Fill rest of password with 'x' */ - memset(write_data+1, 'x', sizeof(write_data)-3); - g_assert_cmpint((int)send(fd, write_data, sizeof(write_data), - MSG_NOSIGNAL), ==, sizeof(write_data)); - } - - /* read from sock_fd */ - char read_data[sizeof(write_data)]; - g_assert_cmpint((int)read(sock_fd, read_data, sizeof(read_data)), - ==, sizeof(read_data)); - - g_assert_true(memcmp(write_data, read_data, sizeof(write_data)) - == 0); - - /* writing to sock_fd should fail */ - g_assert_cmpint(send(sock_fd, write_data, sizeof(write_data), - MSG_NOSIGNAL), <, 0); - - /* reading from fd should fail */ - g_assert_cmpint((int)recv(fd, read_data, sizeof(read_data), - MSG_NOSIGNAL), <, 0); - - g_assert_cmpint(unlink(filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void -test_send_password_to_socket_client_not_exited(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - bool password_is_read = true; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=&password_is_read, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task - = find_matching_task(queue, task); - g_assert_nonnull(added_task); - g_assert_cmpuint((unsigned int)password.length, ==, 0); - g_assert_true(password_is_read); - - g_assert_cmpint(added_task->fd, >, 0); - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); -} - -static void -test_send_password_to_socket_password_not_read(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - buffer password = {}; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){false}, - .password_is_read=(bool[]){false}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task = find_matching_task(queue, - task); - g_assert_nonnull(added_task); - g_assert_cmpuint((unsigned int)password.length, ==, 0); - g_assert_true(queue->next_run == 0); - - g_assert_cmpint(added_task->fd, >, 0); - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); -} - -static -void test_send_password_to_socket_EMSGSIZE(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - const char question_filename[] = "/nonexistent/question"; - char *const filename = strdup("/nonexistent/socket"); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const size_t oversized = 1024*1024; /* Limit seems to be 212960 */ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(oversized), - .length=oversized, - .allocated=oversized, - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - /* Construct test password buffer */ - /* Start with + since that is what the real procotol uses */ - password.data[0] = '+'; - /* Set a special character at string end just to mark the end */ - password.data[oversized-3] = 'y'; - /* Set NUL at buffer end, as suggested by the protocol */ - password.data[oversized-2] = '\0'; - /* Fill rest of password with 'x' */ - memset(password.data+1, 'x', oversized-3); - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - __attribute__((cleanup(cleanup_close))) - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=filename, - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - g_assert_true(string_set_contains(cancelled_filenames, - question_filename)); -} - -static void test_send_password_to_socket_retry(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - /* Close the server side socket to force ECONNRESET on client */ - g_assert_cmpint(close(read_socket), ==, 0); - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=(mono_microsecs[]){0}, - }; - g_assert_nonnull(task.question_filename); - - task.func(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task = find_matching_task(queue, - task); - g_assert_nonnull(added_task); - g_assert_cmpuint((unsigned int)password.length, ==, 0); - - g_assert_true(epoll_set_contains(epoll_fd, added_task->fd, - EPOLLOUT)); -} - -static -void test_send_password_to_socket_bad_epoll(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - __attribute__((cleanup(cleanup_string))) - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - __attribute__((cleanup(cleanup_string))) - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - - const mono_microsecs current_time = 11; - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - /* Close the server side socket to force ECONNRESET on client */ - g_assert_cmpint(close(read_socket), ==, 0); - task_context task = { - .func=send_password_to_socket, - .question_filename=strdup(question_filename), - .filename=strdup(filename), - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=¤t_time, - }; - g_assert_nonnull(task.question_filename); - - run_task_with_stderr_to_dev_null(task, queue); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - const task_context *const added_task = find_matching_task(queue, - task); - g_assert_nonnull(added_task); - g_assert_true(queue->next_run == current_time + 1000000); - g_assert_cmpuint((unsigned int)password.length, ==, 0); -} - -static void assert_send_password_to_socket_password(buffer password){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - char *const question_filename = strdup("/nonexistent/question"); - g_assert_nonnull(question_filename); - char *const filename = strdup("/nonexistent/socket"); - g_assert_nonnull(filename); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - int socketfds[2]; - g_assert_cmpint(socketpair(PF_LOCAL, SOCK_DGRAM - | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - socketfds), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_socket = socketfds[0]; - const int write_socket = socketfds[1]; - task_context task = { - .func=send_password_to_socket, - .question_filename=question_filename, - .filename=filename, - .epoll_fd=epoll_fd, - .fd=write_socket, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .mandos_client_exited=(bool[]){true}, - .password_is_read=(bool[]){true}, - .current_time=(mono_microsecs[]){0}, - }; - - char *expected_written_data = malloc(password.length + 2); - g_assert_nonnull(expected_written_data); - expected_written_data[0] = '+'; - expected_written_data[password.length + 1] = '\0'; - if(password.length > 0){ - g_assert_nonnull(password.data); - memcpy(expected_written_data + 1, password.data, password.length); - } - - task.func(task, queue); - - char buf[PIPE_BUF]; - g_assert_cmpint((int)read(read_socket, buf, PIPE_BUF), ==, - (int)(password.length + 2)); - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_true(memcmp(expected_written_data, buf, - password.length + 2) == 0); - - g_assert_true(epoll_set_does_not_contain(epoll_fd, write_socket)); - - free(expected_written_data); -} - -static void -test_send_password_to_socket_null_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_empty_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(1), /* because malloc(0) may return NULL */ - .length=0, - .allocated=0, /* deliberate lie */ - }; - g_assert_nonnull(password.data); - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_empty_str_pass(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=strdup(""), - .length=0, - .allocated=1, - }; - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_text_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - const char dummy_test_password[] = "dummy test password"; - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data = strdup(dummy_test_password), - .length = strlen(dummy_test_password), - .allocated = sizeof(dummy_test_password), - }; - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_binary_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(255), - .length=255, - .allocated=255, - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) != 0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - char c = 1; /* Start at 1, avoiding NUL */ - for(int i=0; i < 255; i++){ - password.data[i] = c++; - } - assert_send_password_to_socket_password(password); -} - -static void -test_send_password_to_socket_nuls_in_password(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char test_password[] = {'\0', 'a', '\0', 'b', '\0', 'c', '\0'}; - __attribute__((cleanup(cleanup_buffer))) - buffer password = { - .data=malloc(sizeof(test_password)), - .length=sizeof(test_password), - .allocated=sizeof(test_password), - }; - g_assert_nonnull(password.data); - if(mlock(password.data, password.allocated) !=0){ - g_assert_true(errno == EPERM or errno == ENOMEM); - } - memcpy(password.data, test_password, password.allocated); - assert_send_password_to_socket_password(password); -} - -static bool assert_add_existing_questions_to_devnull(task_queue - *const, - const int, - buffer *const, - string_set *, - const - mono_microsecs - *const, - bool *const, - bool *const, - const char - *const); - -static void test_add_existing_questions_ENOENT(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - - g_assert_false(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - (buffer[]){{}}, /* password */ - &cancelled_filenames, - (mono_microsecs[]){0}, /* current_time */ - (bool[]){false}, /* mandos_client_exited */ - (bool[]){false}, /* password_is_read */ - "/nonexistent")); /* dirname */ - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); -} - -static -bool assert_add_existing_questions_to_devnull(task_queue - *const queue, - const int - epoll_fd, - buffer *const - password, - string_set - *cancelled_filenames, - const mono_microsecs - *const current_time, - bool *const - mandos_client_exited, - bool *const - password_is_read, - const char *const - dirname){ - __attribute__((cleanup(cleanup_close))) - const int devnull_fd = open("/dev/null", - O_WRONLY | O_CLOEXEC | O_NOCTTY); - g_assert_cmpint(devnull_fd, >=, 0); - __attribute__((cleanup(cleanup_close))) - const int real_stderr_fd = dup(STDERR_FILENO); - g_assert_cmpint(real_stderr_fd, >=, 0); - dup2(devnull_fd, STDERR_FILENO); - const bool ret = add_existing_questions(queue, epoll_fd, password, - cancelled_filenames, - current_time, - mandos_client_exited, - password_is_read, dirname); - dup2(real_stderr_fd, STDERR_FILENO); - return ret; -} - -static -void test_add_existing_questions_no_questions(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - - g_assert_false(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - (buffer[]){{}}, /* password */ - &cancelled_filenames, - (mono_microsecs[]){0}, /* current_time */ - (bool[]){false}, /* mandos_client_exited */ - (bool[]){false}, /* password_is_read */ - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_question_file_in_directory(const char *const); - -static -void test_add_existing_questions_one_question(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *question_filename - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename); - - g_assert_true(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - &password, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read, - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=question_filename, - .question_filename=question_filename, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(question_filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static char *make_question_file_in_directory(const char - *const dir){ - return make_temporary_prefixed_file_in_directory("ask.", dir); -} - -static -void test_add_existing_questions_two_questions(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *question_filename1 - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename1); - __attribute__((cleanup(cleanup_string))) - char *question_filename2 - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename2); - - g_assert_true(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - &password, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read, - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 2); - - g_assert_true(queue->next_run == 1); - - __attribute__((cleanup(string_set_clear))) - string_set seen_questions = {}; - - bool queue_contains_question_opener(char *const question_filename){ - return(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .question_filename=question_filename, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - }) != NULL); - } - - g_assert_true(queue_contains_question_opener(question_filename1)); - g_assert_true(queue_contains_question_opener(question_filename2)); - - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(question_filename1), ==, 0); - g_assert_cmpint(unlink(question_filename2), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void -test_add_existing_questions_non_questions(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *question_filename1 - = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(question_filename1); - __attribute__((cleanup(cleanup_string))) - char *question_filename2 - = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(question_filename2); - - g_assert_false(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - (buffer[]){{}}, /* password */ - &cancelled_filenames, - (mono_microsecs[]){0}, /* current_time */ - (bool[]){false}, /* mandos_client_exited */ - (bool[]){false}, /* password_is_read */ - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 0); - - g_assert_cmpint(unlink(question_filename1), ==, 0); - g_assert_cmpint(unlink(question_filename2), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void -test_add_existing_questions_both_types(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - __attribute__((cleanup(cleanup_buffer))) - buffer password = {}; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - const mono_microsecs current_time = 0; - bool mandos_client_exited = false; - bool password_is_read = false; - __attribute__((cleanup(cleanup_string))) - char *tempdir = make_temporary_directory(); - g_assert_nonnull(tempdir); - __attribute__((cleanup(cleanup_string))) - char *tempfilename1 = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfilename1); - __attribute__((cleanup(cleanup_string))) - char *tempfilename2 = make_temporary_file_in_directory(tempdir); - g_assert_nonnull(tempfilename2); - __attribute__((cleanup(cleanup_string))) - char *question_filename - = make_question_file_in_directory(tempdir); - g_assert_nonnull(question_filename); - - g_assert_true(assert_add_existing_questions_to_devnull - (queue, - epoll_fd, - &password, - &cancelled_filenames, - ¤t_time, - &mandos_client_exited, - &password_is_read, - tempdir)); - - g_assert_cmpuint((unsigned int)queue->length, ==, 1); - - g_assert_nonnull(find_matching_task(queue, (task_context){ - .func=open_and_parse_question, - .epoll_fd=epoll_fd, - .filename=question_filename, - .question_filename=question_filename, - .password=&password, - .cancelled_filenames=&cancelled_filenames, - .current_time=¤t_time, - .mandos_client_exited=&mandos_client_exited, - .password_is_read=&password_is_read, - })); - - g_assert_true(queue->next_run == 1); - - g_assert_cmpint(unlink(tempfilename1), ==, 0); - g_assert_cmpint(unlink(tempfilename2), ==, 0); - g_assert_cmpint(unlink(question_filename), ==, 0); - g_assert_cmpint(rmdir(tempdir), ==, 0); -} - -static void test_wait_for_event_timeout(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - g_assert_true(wait_for_event(epoll_fd, 1, 0)); -} - -static void test_wait_for_event_event(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_pipe = pipefds[0]; - __attribute__((cleanup(cleanup_close))) - const int write_pipe = pipefds[1]; - g_assert_cmpint(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, read_pipe, - &(struct epoll_event) - { .events=EPOLLIN | EPOLLRDHUP }), ==, 0); - g_assert_cmpint((int)write(write_pipe, "x", 1), ==, 1); - - g_assert_true(wait_for_event(epoll_fd, 0, 0)); -} - -static void test_wait_for_event_sigchld(test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - const pid_t pid = fork(); - if(pid == 0){ /* Child */ - if(not restore_signal_handler(&fixture->orig_sigaction)){ - _exit(EXIT_FAILURE); - } - if(not restore_sigmask(&fixture->orig_sigmask)){ - _exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - g_assert_true(pid != -1); - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - g_assert_cmpint(epoll_fd, >=, 0); - - g_assert_true(wait_for_event(epoll_fd, 0, 0)); - - int status; - g_assert_true(waitpid(pid, &status, 0) == pid); - g_assert_true(WIFEXITED(status)); - g_assert_cmpint(WEXITSTATUS(status), ==, EXIT_SUCCESS); -} - -static void test_run_queue_zeroes_next_run(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = 1; - __attribute__((cleanup(cleanup_close))) - const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)queue->next_run, ==, 0); -} - -static -void test_run_queue_clears_cancelled_filenames(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - const char question_filename[] = "/nonexistent/question_filename"; - g_assert_true(string_set_add(&cancelled_filenames, - question_filename)); - - g_assert_true(add_to_queue(queue, - (task_context){ .func=dummy_func })); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); - g_assert_false(string_set_contains(cancelled_filenames, - question_filename)); -} - -static -void test_run_queue_skips_cancelled_filenames(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_pipe = pipefds[0]; - g_assert_cmpint(close(pipefds[1]), ==, 0); - const char question_filename[] = "/nonexistent/question_filename"; - g_assert_true(string_set_add(&cancelled_filenames, - question_filename)); - __attribute__((nonnull)) - void quit_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - g_assert_nonnull(task.quit_now); - *task.quit_now = true; - } - task_context task = { - .func=quit_func, - .question_filename=strdup(question_filename), - .quit_now=&quit_now, - .fd=read_pipe, - }; - g_assert_nonnull(task.question_filename); - - g_assert_true(add_to_queue(queue, task)); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - - /* read_pipe should be closed already */ - errno = 0; - bool read_pipe_closed = (close(read_pipe) == -1); - read_pipe_closed &= (errno == EBADF); - g_assert_true(read_pipe_closed); -} - -static void test_run_queue_one_task(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - - __attribute__((nonnull)) - void next_run_func(__attribute__((unused)) - const task_context task, - task_queue *const q){ - q->next_run = 1; - } - - task_context task = { - .func=next_run_func, - }; - g_assert_true(add_to_queue(queue, task)); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_cmpuint((unsigned int)(queue->next_run), ==, 1); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_run_queue_two_tasks(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - queue->next_run = 1; - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - bool mandos_client_exited = false; - - __attribute__((nonnull)) - void next_run_func(__attribute__((unused)) - const task_context task, - task_queue *const q){ - q->next_run = 1; - } - - __attribute__((nonnull)) - void exited_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.mandos_client_exited = true; - } - - task_context task1 = { - .func=next_run_func, - }; - g_assert_true(add_to_queue(queue, task1)); - - task_context task2 = { - .func=exited_func, - .mandos_client_exited=&mandos_client_exited, - }; - g_assert_true(add_to_queue(queue, task2)); - - g_assert_true(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_false(quit_now); - g_assert_cmpuint((unsigned int)(queue->next_run), ==, 1); - g_assert_true(mandos_client_exited); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_run_queue_two_tasks_quit(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - bool quit_now = false; - bool mandos_client_exited = false; - bool password_is_read = false; - - __attribute__((nonnull)) - void set_exited_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.mandos_client_exited = true; - *task.quit_now = true; - } - task_context task1 = { - .func=set_exited_func, - .quit_now=&quit_now, - .mandos_client_exited=&mandos_client_exited, - }; - g_assert_true(add_to_queue(queue, task1)); - - __attribute__((nonnull)) - void set_read_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.quit_now = true; - *task.password_is_read = true; - } - task_context task2 = { - .func=set_read_func, - .quit_now=&quit_now, - .password_is_read=&password_is_read, - }; - g_assert_true(add_to_queue(queue, task2)); - - g_assert_false(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(quit_now); - g_assert_true(mandos_client_exited xor password_is_read); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_run_queue_two_tasks_cleanup(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - __attribute__((cleanup(cleanup_queue))) - task_queue *queue = create_queue(); - g_assert_nonnull(queue); - __attribute__((cleanup(string_set_clear))) - string_set cancelled_filenames = {}; - int pipefds[2]; - g_assert_cmpint(pipe2(pipefds, O_CLOEXEC | O_NONBLOCK), ==, 0); - __attribute__((cleanup(cleanup_close))) - const int read_pipe = pipefds[0]; - __attribute__((cleanup(cleanup_close))) - const int write_pipe = pipefds[1]; - bool quit_now = false; - - __attribute__((nonnull)) - void read_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.quit_now = true; - } - task_context task1 = { - .func=read_func, - .quit_now=&quit_now, - .fd=read_pipe, - }; - g_assert_true(add_to_queue(queue, task1)); - - __attribute__((nonnull)) - void write_func(const task_context task, - __attribute__((unused)) task_queue *const q){ - *task.quit_now = true; - } - task_context task2 = { - .func=write_func, - .quit_now=&quit_now, - .fd=write_pipe, - }; - g_assert_true(add_to_queue(queue, task2)); - - g_assert_false(run_queue(&queue, &cancelled_filenames, &quit_now)); - g_assert_true(quit_now); - - /* Either read_pipe or write_pipe should be closed already */ - errno = 0; - bool close_read_pipe = (close(read_pipe) == -1); - close_read_pipe &= (errno == EBADF); - errno = 0; - bool close_write_pipe = (close(write_pipe) == -1); - close_write_pipe &= (errno == EBADF); - g_assert_true(close_read_pipe xor close_write_pipe); - g_assert_cmpuint((unsigned int)(queue->length), ==, 0); -} - -static void test_setup_signal_handler(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save current SIGCHLD action, whatever it is */ - struct sigaction expected_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, &expected_sigchld_action), - ==, 0); - - /* Act; i.e. run the setup_signal_handler() function */ - struct sigaction actual_old_sigchld_action; - g_assert_true(setup_signal_handler(&actual_old_sigchld_action)); - - /* Check that the function correctly set "actual_old_sigchld_action" - to the same values as the previously saved - "expected_sigchld_action" */ - /* Check member sa_handler */ - g_assert_true(actual_old_sigchld_action.sa_handler - == expected_sigchld_action.sa_handler); - /* Check member sa_mask */ - for(int signum = 1; signum < NSIG; signum++){ - const int expected_old_block_state - = sigismember(&expected_sigchld_action.sa_mask, signum); - g_assert_cmpint(expected_old_block_state, >=, 0); - const int actual_old_block_state - = sigismember(&actual_old_sigchld_action.sa_mask, signum); - g_assert_cmpint(actual_old_block_state, >=, 0); - g_assert_cmpint(actual_old_block_state, - ==, expected_old_block_state); - } - /* Check member sa_flags */ - g_assert_true((actual_old_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)) - == (expected_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))); - - /* Retrieve the current signal handler for SIGCHLD as set by - setup_signal_handler() */ - struct sigaction actual_new_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, - &actual_new_sigchld_action), ==, 0); - /* Check that the signal handler (member sa_handler) is correctly - set to the "handle_sigchld" function */ - g_assert_true(actual_new_sigchld_action.sa_handler != SIG_DFL); - g_assert_true(actual_new_sigchld_action.sa_handler != SIG_IGN); - g_assert_true(actual_new_sigchld_action.sa_handler - == handle_sigchld); - /* Check (in member sa_mask) that at least a handful of signals are - actually blocked during the signal handler */ - for(int signum = 1; signum < NSIG; signum++){ - int actual_new_block_state; - switch(signum){ - case SIGTERM: - case SIGINT: - case SIGQUIT: - case SIGHUP: - actual_new_block_state - = sigismember(&actual_new_sigchld_action.sa_mask, signum); - g_assert_cmpint(actual_new_block_state, ==, 1); - continue; - case SIGKILL: /* non-blockable */ - case SIGSTOP: /* non-blockable */ - case SIGCHLD: /* always blocked */ - default: - continue; - } - } - /* Check member sa_flags */ - g_assert_true((actual_new_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)) - == (SA_NOCLDSTOP | SA_RESTART)); - - /* Restore signal handler */ - g_assert_cmpint(sigaction(SIGCHLD, &expected_sigchld_action, NULL), - ==, 0); -} - -static void test_restore_signal_handler(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save current SIGCHLD action, whatever it is */ - struct sigaction expected_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, &expected_sigchld_action), - ==, 0); - /* Since we haven't established a signal handler yet, there should - not be one established. But another test may have relied on - restore_signal_handler() to restore the signal handler, and if - restore_signal_handler() is buggy (which we should be prepared - for in this test) the signal handler may not have been restored - properly; check for this: */ - g_assert_true(expected_sigchld_action.sa_handler != handle_sigchld); - - /* Establish a signal handler */ - struct sigaction sigchld_action = { - .sa_handler=handle_sigchld, - .sa_flags=SA_RESTART | SA_NOCLDSTOP, - }; - g_assert_cmpint(sigfillset(&sigchld_action.sa_mask), ==, 0); - g_assert_cmpint(sigaction(SIGCHLD, &sigchld_action, NULL), ==, 0); - - /* Act; i.e. run the restore_signal_handler() function */ - g_assert_true(restore_signal_handler(&expected_sigchld_action)); - - /* Retrieve the restored signal handler data */ - struct sigaction actual_restored_sigchld_action; - g_assert_cmpint(sigaction(SIGCHLD, NULL, - &actual_restored_sigchld_action), ==, 0); - - /* Check that the function correctly restored the signal action, as - saved in "actual_restored_sigchld_action", to the same values as - the previously saved "expected_sigchld_action" */ - /* Check member sa_handler */ - g_assert_true(actual_restored_sigchld_action.sa_handler - == expected_sigchld_action.sa_handler); - /* Check member sa_mask */ - for(int signum = 1; signum < NSIG; signum++){ - const int expected_old_block_state - = sigismember(&expected_sigchld_action.sa_mask, signum); - g_assert_cmpint(expected_old_block_state, >=, 0); - const int actual_restored_block_state - = sigismember(&actual_restored_sigchld_action.sa_mask, signum); - g_assert_cmpint(actual_restored_block_state, >=, 0); - g_assert_cmpint(actual_restored_block_state, - ==, expected_old_block_state); - } - /* Check member sa_flags */ - g_assert_true((actual_restored_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART)) - == (expected_sigchld_action.sa_flags - & (SA_NOCLDSTOP | SA_ONSTACK | SA_RESTART))); -} - -static void test_block_sigchld(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save original signal mask */ - sigset_t expected_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &expected_sigmask), - ==, 0); - - /* Make sure SIGCHLD is unblocked for this test */ - sigset_t sigchld_sigmask; - g_assert_cmpint(sigemptyset(&sigchld_sigmask), ==, 0); - g_assert_cmpint(sigaddset(&sigchld_sigmask, SIGCHLD), ==, 0); - g_assert_cmpint(pthread_sigmask(SIG_UNBLOCK, &sigchld_sigmask, - NULL), ==, 0); - - /* Act; i.e. run the block_sigchld() function */ - sigset_t actual_old_sigmask; - g_assert_true(block_sigchld(&actual_old_sigmask)); - - /* Check the actual_old_sigmask; it should be the same as the - previously saved signal mask "expected_sigmask". */ - for(int signum = 1; signum < NSIG; signum++){ - const int expected_old_block_state - = sigismember(&expected_sigmask, signum); - g_assert_cmpint(expected_old_block_state, >=, 0); - const int actual_old_block_state - = sigismember(&actual_old_sigmask, signum); - g_assert_cmpint(actual_old_block_state, >=, 0); - g_assert_cmpint(actual_old_block_state, - ==, expected_old_block_state); - } - - /* Retrieve the newly set signal mask */ - sigset_t actual_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &actual_sigmask), ==, 0); - - /* SIGCHLD should be blocked */ - g_assert_cmpint(sigismember(&actual_sigmask, SIGCHLD), ==, 1); - - /* Restore signal mask */ - g_assert_cmpint(pthread_sigmask(SIG_SETMASK, &expected_sigmask, - NULL), ==, 0); -} - -static void test_restore_sigmask(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - /* Save original signal mask */ - sigset_t orig_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &orig_sigmask), ==, 0); - - /* Make sure SIGCHLD is blocked for this test */ - sigset_t sigchld_sigmask; - g_assert_cmpint(sigemptyset(&sigchld_sigmask), ==, 0); - g_assert_cmpint(sigaddset(&sigchld_sigmask, SIGCHLD), ==, 0); - g_assert_cmpint(pthread_sigmask(SIG_BLOCK, &sigchld_sigmask, - NULL), ==, 0); - - /* Act; i.e. run the restore_sigmask() function */ - g_assert_true(restore_sigmask(&orig_sigmask)); - - /* Retrieve the newly restored signal mask */ - sigset_t restored_sigmask; - g_assert_cmpint(pthread_sigmask(-1, NULL, &restored_sigmask), - ==, 0); - - /* Check the restored_sigmask; it should be the same as the - previously saved signal mask "orig_sigmask". */ - for(int signum = 1; signum < NSIG; signum++){ - const int orig_block_state = sigismember(&orig_sigmask, signum); - g_assert_cmpint(orig_block_state, >=, 0); - const int restored_block_state = sigismember(&restored_sigmask, - signum); - g_assert_cmpint(restored_block_state, >=, 0); - g_assert_cmpint(restored_block_state, ==, orig_block_state); - } - - /* Restore signal mask */ - g_assert_cmpint(pthread_sigmask(SIG_SETMASK, &orig_sigmask, - NULL), ==, 0); -} - -static void test_parse_arguments_noargs(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -__attribute__((nonnull)) -static bool parse_arguments_devnull(int argc, char *argv[], - const bool exit_failure, - char **agent_directory, - char **helper_directory, - uid_t *const user, - gid_t *const group, - char **mandos_argz, - size_t *mandos_argz_length){ - - FILE *real_stderr = stderr; - FILE *devnull = fopen("/dev/null", "we"); - g_assert_nonnull(devnull); - stderr = devnull; - - const bool ret = parse_arguments(argc, argv, exit_failure, - agent_directory, - helper_directory, user, group, - mandos_argz, mandos_argz_length); - const error_t saved_errno = errno; - - stderr = real_stderr; - g_assert_cmpint(fclose(devnull), ==, 0); - - errno = saved_errno; - - return ret; -} - -static void test_parse_arguments_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--invalid"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_true(errno == EINVAL); - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_long_dir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--agent-directory"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_short_dir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("-d"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_helper_directory(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--helper-directory"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(helper_directory, ==, "/tmp"); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_plugin_helper_dir(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--plugin-helper-dir"), - strdup("/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(helper_directory, ==, "/tmp"); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_user(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--user"), - strdup("1000"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)user, ==, 1000); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_user_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--user"), - strdup("invalid"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)user, ==, 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_user_zero_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--user"), - strdup("0"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)user, ==, 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_group(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--group"), - strdup("1000"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_cmpuint((unsigned int)group, ==, 1000); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_group_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--group"), - strdup("invalid"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static -void test_parse_arguments_group_zero_invalid(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--group"), - strdup("0"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_false(parse_arguments_devnull(argc, argv, false, - &agent_directory, - &helper_directory, &user, - &group, &mandos_argz, - &mandos_argz_length)); - - g_assert_null(helper_directory); - g_assert_null(agent_directory); - g_assert_cmpuint((unsigned int)group, ==, 0); - g_assert_true(group == 0); - g_assert_null(mandos_argz); - g_assert_true(mandos_argz_length == 0); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_mandos_noargs(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer - user_data){ - char *argv[] = { - strdup("prgname"), - strdup("mandos-client"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - g_assert_cmpstr(mandos_argz, ==, "mandos-client"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 1); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_mandos_args(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("mandos-client"), - strdup("one"), - strdup("two"), - strdup("three"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_null(agent_directory); - g_assert_null(helper_directory); - g_assert_true(user == 0); - g_assert_true(group == 0); - char *marg = mandos_argz; - g_assert_cmpstr(marg, ==, "mandos-client"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "one"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "two"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "three"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 4); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_all_args(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("--agent-directory"), - strdup("/tmp"), - strdup("--helper-directory"), - strdup("/var/tmp"), - strdup("--user"), - strdup("1"), - strdup("--group"), - strdup("2"), - strdup("mandos-client"), - strdup("one"), - strdup("two"), - strdup("three"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_cmpstr(helper_directory, ==, "/var/tmp"); - g_assert_true(user == 1); - g_assert_true(group == 2); - char *marg = mandos_argz; - g_assert_cmpstr(marg, ==, "mandos-client"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "one"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "two"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "three"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 4); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -static void test_parse_arguments_mixed(__attribute__((unused)) - test_fixture *fixture, - __attribute__((unused)) - gconstpointer user_data){ - char *argv[] = { - strdup("prgname"), - strdup("mandos-client"), - strdup("--user"), - strdup("1"), - strdup("one"), - strdup("--agent-directory"), - strdup("/tmp"), - strdup("two"), - strdup("three"), - strdup("--helper-directory=/var/tmp"), - NULL }; - const int argc = (sizeof(argv) / sizeof(char *)) - 1; - - __attribute__((cleanup(cleanup_string))) - char *agent_directory = NULL; - __attribute__((cleanup(cleanup_string))) - char *helper_directory = NULL; - uid_t user = 0; - gid_t group = 0; - __attribute__((cleanup(cleanup_string))) - char *mandos_argz = NULL; - size_t mandos_argz_length = 0; - - g_assert_true(parse_arguments(argc, argv, false, &agent_directory, - &helper_directory, &user, &group, - &mandos_argz, &mandos_argz_length)); - - g_assert_cmpstr(agent_directory, ==, "/tmp"); - g_assert_cmpstr(helper_directory, ==, "/var/tmp"); - g_assert_true(user == 1); - g_assert_true(group == 0); - char *marg = mandos_argz; - g_assert_cmpstr(marg, ==, "mandos-client"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "one"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "two"); - marg = argz_next(mandos_argz, mandos_argz_length, marg); - g_assert_cmpstr(marg, ==, "three"); - g_assert_cmpuint((unsigned int)argz_count(mandos_argz, - mandos_argz_length), - ==, 4); - - for(char **arg = argv; *arg != NULL; arg++){ - free(*arg); - } -} - -/* End of tests section */ - -/* Test boilerplate section; New tests should be added to the test - suite definition here, in the "run_tests" function. - - Finally, this section also contains the should_only_run_tests() - function used by main() for deciding if tests should be run or to - start normally. */ - -__attribute__((cold)) -static bool run_tests(int argc, char *argv[]){ - g_test_init(&argc, &argv, NULL); - - /* A macro to add a test with no setup or teardown functions */ -#define test_add(testpath, testfunc) \ - do { \ - g_test_add((testpath), test_fixture, NULL, NULL, \ - (testfunc), NULL); \ - } while(false) - - /* Test the signal-related functions first, since some other tests - depend on these functions in their setups and teardowns */ - test_add("/signal-handling/setup", test_setup_signal_handler); - test_add("/signal-handling/restore", test_restore_signal_handler); - test_add("/signal-handling/block", test_block_sigchld); - test_add("/signal-handling/restore-sigmask", test_restore_sigmask); - - /* Regular non-signal-related tests; these use no setups or - teardowns */ - test_add("/parse_arguments/noargs", test_parse_arguments_noargs); - test_add("/parse_arguments/invalid", test_parse_arguments_invalid); - test_add("/parse_arguments/long-dir", - test_parse_arguments_long_dir); - test_add("/parse_arguments/short-dir", - test_parse_arguments_short_dir); - test_add("/parse_arguments/helper-directory", - test_parse_arguments_helper_directory); - test_add("/parse_arguments/plugin-helper-dir", - test_parse_arguments_plugin_helper_dir); - test_add("/parse_arguments/user", test_parse_arguments_user); - test_add("/parse_arguments/user-invalid", - test_parse_arguments_user_invalid); - test_add("/parse_arguments/user-zero-invalid", - test_parse_arguments_user_zero_invalid); - test_add("/parse_arguments/group", test_parse_arguments_group); - test_add("/parse_arguments/group-invalid", - test_parse_arguments_group_invalid); - test_add("/parse_arguments/group-zero-invalid", - test_parse_arguments_group_zero_invalid); - test_add("/parse_arguments/mandos-noargs", - test_parse_arguments_mandos_noargs); - test_add("/parse_arguments/mandos-args", - test_parse_arguments_mandos_args); - test_add("/parse_arguments/all-args", - test_parse_arguments_all_args); - test_add("/parse_arguments/mixed", test_parse_arguments_mixed); - test_add("/queue/create", test_create_queue); - test_add("/queue/add", test_add_to_queue); - test_add("/queue/has_question/empty", - test_queue_has_question_empty); - test_add("/queue/has_question/false", - test_queue_has_question_false); - test_add("/queue/has_question/true", test_queue_has_question_true); - test_add("/queue/has_question/false2", - test_queue_has_question_false2); - test_add("/queue/has_question/true2", - test_queue_has_question_true2); - test_add("/buffer/cleanup", test_cleanup_buffer); - test_add("/string_set/net-set-contains-nothing", - test_string_set_new_set_contains_nothing); - test_add("/string_set/with-added-string-contains-it", - test_string_set_with_added_string_contains_it); - test_add("/string_set/cleared-does-not-contain-string", - test_string_set_cleared_does_not_contain_str); - test_add("/string_set/swap/one-with-empty", - test_string_set_swap_one_with_empty); - test_add("/string_set/swap/empty-with-one", - test_string_set_swap_empty_with_one); - test_add("/string_set/swap/one-with-one", - test_string_set_swap_one_with_one); - - /* A macro to add a test using the setup and teardown functions */ -#define test_add_st(path, func) \ - do { \ - g_test_add((path), test_fixture, NULL, test_setup, (func), \ - test_teardown); \ - } while(false) - - /* Signal-related tests; these use setups and teardowns which - establish, during each test run, a signal handler for, and a - signal mask blocking, the SIGCHLD signal, just like main() */ - test_add_st("/wait_for_event/timeout", test_wait_for_event_timeout); - test_add_st("/wait_for_event/event", test_wait_for_event_event); - test_add_st("/wait_for_event/sigchld", test_wait_for_event_sigchld); - test_add_st("/run_queue/zeroes-next-run", - test_run_queue_zeroes_next_run); - test_add_st("/run_queue/clears-cancelled_filenames", - test_run_queue_clears_cancelled_filenames); - test_add_st("/run_queue/skips-cancelled-filenames", - test_run_queue_skips_cancelled_filenames); - test_add_st("/run_queue/one-task", test_run_queue_one_task); - test_add_st("/run_queue/two-tasks", test_run_queue_two_tasks); - test_add_st("/run_queue/two-tasks/quit", - test_run_queue_two_tasks_quit); - test_add_st("/run_queue/two-tasks-cleanup", - test_run_queue_two_tasks_cleanup); - test_add_st("/task-creators/start_mandos_client", - test_start_mandos_client); - test_add_st("/task-creators/start_mandos_client/execv", - test_start_mandos_client_execv); - test_add_st("/task-creators/start_mandos_client/suid/euid", - test_start_mandos_client_suid_euid); - test_add_st("/task-creators/start_mandos_client/suid/egid", - test_start_mandos_client_suid_egid); - test_add_st("/task-creators/start_mandos_client/suid/ruid", - test_start_mandos_client_suid_ruid); - test_add_st("/task-creators/start_mandos_client/suid/rgid", - test_start_mandos_client_suid_rgid); - test_add_st("/task-creators/start_mandos_client/read", - test_start_mandos_client_read); - test_add_st("/task-creators/start_mandos_client/helper-directory", - test_start_mandos_client_helper_directory); - test_add_st("/task-creators/start_mandos_client/sigmask", - test_start_mandos_client_sigmask); - test_add_st("/task/wait_for_mandos_client_exit/badpid", - test_wait_for_mandos_client_exit_badpid); - test_add_st("/task/wait_for_mandos_client_exit/noexit", - test_wait_for_mandos_client_exit_noexit); - test_add_st("/task/wait_for_mandos_client_exit/success", - test_wait_for_mandos_client_exit_success); - test_add_st("/task/wait_for_mandos_client_exit/failure", - test_wait_for_mandos_client_exit_failure); - test_add_st("/task/wait_for_mandos_client_exit/killed", - test_wait_for_mandos_client_exit_killed); - test_add_st("/task/read_mandos_client_output/readerror", - test_read_mandos_client_output_readerror); - test_add_st("/task/read_mandos_client_output/nodata", - test_read_mandos_client_output_nodata); - test_add_st("/task/read_mandos_client_output/eof", - test_read_mandos_client_output_eof); - test_add_st("/task/read_mandos_client_output/once", - test_read_mandos_client_output_once); - test_add_st("/task/read_mandos_client_output/malloc", - test_read_mandos_client_output_malloc); - test_add_st("/task/read_mandos_client_output/append", - test_read_mandos_client_output_append); - test_add_st("/task-creators/add_inotify_dir_watch", - test_add_inotify_dir_watch); - test_add_st("/task-creators/add_inotify_dir_watch/fail", - test_add_inotify_dir_watch_fail); - test_add_st("/task-creators/add_inotify_dir_watch/not-a-directory", - test_add_inotify_dir_watch_nondir); - test_add_st("/task-creators/add_inotify_dir_watch/EAGAIN", - test_add_inotify_dir_watch_EAGAIN); - test_add_st("/task-creators/add_inotify_dir_watch/IN_CLOSE_WRITE", - test_add_inotify_dir_watch_IN_CLOSE_WRITE); - test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_TO", - test_add_inotify_dir_watch_IN_MOVED_TO); - test_add_st("/task-creators/add_inotify_dir_watch/IN_MOVED_FROM", - test_add_inotify_dir_watch_IN_MOVED_FROM); - test_add_st("/task-creators/add_inotify_dir_watch/IN_EXCL_UNLINK", - test_add_inotify_dir_watch_IN_EXCL_UNLINK); - test_add_st("/task-creators/add_inotify_dir_watch/IN_DELETE", - test_add_inotify_dir_watch_IN_DELETE); - test_add_st("/task/read_inotify_event/readerror", - test_read_inotify_event_readerror); - test_add_st("/task/read_inotify_event/bad-epoll", - test_read_inotify_event_bad_epoll); - test_add_st("/task/read_inotify_event/nodata", - test_read_inotify_event_nodata); - test_add_st("/task/read_inotify_event/eof", - test_read_inotify_event_eof); - test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE", - test_read_inotify_event_IN_CLOSE_WRITE); - test_add_st("/task/read_inotify_event/IN_MOVED_TO", - test_read_inotify_event_IN_MOVED_TO); - test_add_st("/task/read_inotify_event/IN_MOVED_FROM", - test_read_inotify_event_IN_MOVED_FROM); - test_add_st("/task/read_inotify_event/IN_DELETE", - test_read_inotify_event_IN_DELETE); - test_add_st("/task/read_inotify_event/IN_CLOSE_WRITE/badname", - test_read_inotify_event_IN_CLOSE_WRITE_badname); - test_add_st("/task/read_inotify_event/IN_MOVED_TO/badname", - test_read_inotify_event_IN_MOVED_TO_badname); - test_add_st("/task/read_inotify_event/IN_MOVED_FROM/badname", - test_read_inotify_event_IN_MOVED_FROM_badname); - test_add_st("/task/read_inotify_event/IN_DELETE/badname", - test_read_inotify_event_IN_DELETE_badname); - test_add_st("/task/open_and_parse_question/ENOENT", - test_open_and_parse_question_ENOENT); - test_add_st("/task/open_and_parse_question/EIO", - test_open_and_parse_question_EIO); - test_add_st("/task/open_and_parse_question/parse-error", - test_open_and_parse_question_parse_error); - test_add_st("/task/open_and_parse_question/nosocket", - test_open_and_parse_question_nosocket); - test_add_st("/task/open_and_parse_question/badsocket", - test_open_and_parse_question_badsocket); - test_add_st("/task/open_and_parse_question/nopid", - test_open_and_parse_question_nopid); - test_add_st("/task/open_and_parse_question/badpid", - test_open_and_parse_question_badpid); - test_add_st("/task/open_and_parse_question/noexist_pid", - test_open_and_parse_question_noexist_pid); - test_add_st("/task/open_and_parse_question/no-notafter", - test_open_and_parse_question_no_notafter); - test_add_st("/task/open_and_parse_question/bad-notafter", - test_open_and_parse_question_bad_notafter); - test_add_st("/task/open_and_parse_question/notafter-0", - test_open_and_parse_question_notafter_0); - test_add_st("/task/open_and_parse_question/notafter-1", - test_open_and_parse_question_notafter_1); - test_add_st("/task/open_and_parse_question/notafter-1-1", - test_open_and_parse_question_notafter_1_1); - test_add_st("/task/open_and_parse_question/notafter-1-2", - test_open_and_parse_question_notafter_1_2); - test_add_st("/task/open_and_parse_question/equal-notafter", - test_open_and_parse_question_equal_notafter); - test_add_st("/task/open_and_parse_question/late-notafter", - test_open_and_parse_question_late_notafter); - test_add_st("/task/cancel_old_question/0-1-2", - test_cancel_old_question_0_1_2); - test_add_st("/task/cancel_old_question/0-2-1", - test_cancel_old_question_0_2_1); - test_add_st("/task/cancel_old_question/1-2-3", - test_cancel_old_question_1_2_3); - test_add_st("/task/cancel_old_question/1-3-2", - test_cancel_old_question_1_3_2); - test_add_st("/task/cancel_old_question/2-1-3", - test_cancel_old_question_2_1_3); - test_add_st("/task/cancel_old_question/2-3-1", - test_cancel_old_question_2_3_1); - test_add_st("/task/cancel_old_question/3-1-2", - test_cancel_old_question_3_1_2); - test_add_st("/task/cancel_old_question/3-2-1", - test_cancel_old_question_3_2_1); - test_add_st("/task/connect_question_socket/name-too-long", - test_connect_question_socket_name_too_long); - test_add_st("/task/connect_question_socket/connect-fail", - test_connect_question_socket_connect_fail); - test_add_st("/task/connect_question_socket/bad-epoll", - test_connect_question_socket_bad_epoll); - test_add_st("/task/connect_question_socket/usable", - test_connect_question_socket_usable); - test_add_st("/task/send_password_to_socket/client-not-exited", - test_send_password_to_socket_client_not_exited); - test_add_st("/task/send_password_to_socket/password-not-read", - test_send_password_to_socket_password_not_read); - test_add_st("/task/send_password_to_socket/EMSGSIZE", - test_send_password_to_socket_EMSGSIZE); - test_add_st("/task/send_password_to_socket/retry", - test_send_password_to_socket_retry); - test_add_st("/task/send_password_to_socket/bad-epoll", - test_send_password_to_socket_bad_epoll); - test_add_st("/task/send_password_to_socket/null-password", - test_send_password_to_socket_null_password); - test_add_st("/task/send_password_to_socket/empty-password", - test_send_password_to_socket_empty_password); - test_add_st("/task/send_password_to_socket/empty-str-password", - test_send_password_to_socket_empty_str_pass); - test_add_st("/task/send_password_to_socket/text-password", - test_send_password_to_socket_text_password); - test_add_st("/task/send_password_to_socket/binary-password", - test_send_password_to_socket_binary_password); - test_add_st("/task/send_password_to_socket/nuls-in-password", - test_send_password_to_socket_nuls_in_password); - test_add_st("/task-creators/add_existing_questions/ENOENT", - test_add_existing_questions_ENOENT); - test_add_st("/task-creators/add_existing_questions/no-questions", - test_add_existing_questions_no_questions); - test_add_st("/task-creators/add_existing_questions/one-question", - test_add_existing_questions_one_question); - test_add_st("/task-creators/add_existing_questions/two-questions", - test_add_existing_questions_two_questions); - test_add_st("/task-creators/add_existing_questions/non-questions", - test_add_existing_questions_non_questions); - test_add_st("/task-creators/add_existing_questions/both-types", - test_add_existing_questions_both_types); - - return g_test_run() == 0; -} - -static bool should_only_run_tests(int *argc_p, char **argv_p[]){ - GOptionContext *context = g_option_context_new(""); - - g_option_context_set_help_enabled(context, FALSE); - g_option_context_set_ignore_unknown_options(context, TRUE); - - gboolean run_tests = FALSE; - GOptionEntry entries[] = { - { "test", 0, 0, G_OPTION_ARG_NONE, - &run_tests, "Run tests", NULL }, - { NULL } - }; - g_option_context_add_main_entries(context, entries, NULL); - - GError *error = NULL; - - if(g_option_context_parse(context, argc_p, argv_p, &error) != TRUE){ - g_option_context_free(context); - g_error("Failed to parse options: %s", error->message); - } - - g_option_context_free(context); - return run_tests != FALSE; -} === removed file 'dracut-module/password-agent.xml' --- dracut-module/password-agent.xml 2019-07-27 10:11:45 +0000 +++ dracut-module/password-agent.xml 1970-01-01 00:00:00 +0000 @@ -1,469 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - - Run Mandos client as a systemd password agent. - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - -- - - MANDOS_CLIENT - - OPTIONS - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - &COMMANDNAME; is a program which is meant to - be a systemd - 1 Password - Agent (See Password Agents). The aim of this program is therefore - to acquire and then send a password to some other program which - will use the password to unlock the encrypted root disk. - - - This program is not meant to be invoked directly, but can be in - order to test it. - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - - - - - - Specify a different agent directory. The default is - /run/systemd/ask-password as per the - Password Agents specification. - - - - - - - - - Specify a different helper directory. The default is - /lib/mandos/plugin-helpers, which - will exist in the initial RAM disk - environment. (This will simply be passed to the - MANDOS_CLIENT program via the - MANDOSPLUGINHELPERDIR environment variable. - See - mandos-client8mandos.) - - - - - - - - - Change real user ID to USERID - when running MANDOS_CLIENT. - The default is 65534. Note: This - must be a number, not a name. - - - - - - - - - Change real group ID to GROUPID - when running MANDOS_CLIENT. - The default is 65534. Note: This - must be a number, not a name. - - - - - - MANDOS_CLIENT - - - This specifies the file name for - mandos-client8mandos. If the - option is given, any - following options are passed to the MANDOS_CLIENT program. The default is - /lib/mandos/plugins.d/mandos-client - (which is the correct location for the initial - RAM disk environment) without any - options. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Ignore normal operation; instead only run self-tests. - Adding the option may show more - options possible in combination with - . - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - - - - - OVERVIEW - - - This program, &COMMANDNAME;, will run on the client side in the - initial RAM disk environment, and is - responsible for getting a password from the Mandos client - program itself, and to send that password to whatever is - currently asking for a password using the systemd Password Agents mechanism. - - To accomplish this, &COMMANDNAME; runs the - mandos-client program (which is the actual - client program communicating with the Mandos server) or, - alternatively, any executable file specified as - MANDOS_CLIENT, and, as soon as a - password is acquired from the - MANDOS_CLIENT program, sends that - password (as per the Password Agents specification) to all currently - unanswered password questions. - - - This program should be started (normally as a systemd service, - which in turn is normally started by a systemd.path - 5 file) as a reaction to - files named ask.xxxx appearing in the agent directory - /run/systemd/ask-password - (or the directory specified by - ). - - - - - EXIT STATUS - - Exit status of this program is zero if no errors were - encountered, and otherwise not. - - - - - ENVIRONMENT - - This program does not use any environment variables itself, it - only passes on its environment to - MANDOS_CLIENT. Also, the - option will affect the - environment variable MANDOSPLUGINHELPERDIR for - MANDOS_CLIENT. - - - - - FILES - - - - /run/systemd/ask-password - - - The default directory to watch for password questions as - per the Password Agents specification; can be changed - by the option. - - - - - /lib/mandos/plugin-helpers - - - The helper directory as supplied to - MANDOS_CLIENT via the - MANDOSPLUGINHELPERDIR environment - variable; can be changed by the - option. - - - - - - - - - BUGS - - - - - EXAMPLE - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Run an alternative MANDOS_CLIENT - program:: - - - &COMMANDNAME; /usr/local/sbin/alternate - - - - - Use alternative locations for the helper directory and the - Mandos client, and add extra options suitable for running in - the normal file system: - - - - - &COMMANDNAME; --helper-directory=/usr/lib/x86_64-linux-gnu/mandos/plugin-helpers -- /usr/lib/x86_64-linux-gnu/mandos/plugins.d/mandos-client --pubkey=/etc/keys/mandos/pubkey.txt --seckey=/etc/keys/mandos/seckey.txt --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem --tls-privkey=/etc/keys/mandos/tls-privkey.pem - - - - - - Use the default location for - mandos-client - 8mandos, but add many - options to it: - - - - -&COMMANDNAME; -- /lib/mandos/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem - - - - - - Only run the self-tests: - - - &COMMANDNAME; --test - - - - - SECURITY - - This program will need to run as the root user in order to read - the agent directory and the ask.xxxx files - there, and will, when starting the Mandos client program, - require the ability to set the real user and - group ids to another user, by default user and group 65534, - which are assumed to be non-privileged. This is done in order - to match the expectations of mandos-client8mandos, which assumes that its executable file is - owned by the root user and also has the set-user-ID bit set (see - execve2). - - - - - SEE ALSO - - intro - 8mandos, - mandos-client - 8mandos, - systemd - 1, - - - - - Password Agents - - - - The specification for systemd Password - Agent programs, which - &COMMANDNAME; follows. - - - - - - -
- - - - - === removed file 'init.d-mandos' --- init.d-mandos 2018-02-10 13:23:58 +0000 +++ init.d-mandos 1970-01-01 00:00:00 +0000 @@ -1,164 +0,0 @@ -#! /bin/sh -### BEGIN INIT INFO -# Provides: mandos -# Required-Start: $remote_fs $syslog avahi-daemon -# Required-Stop: $remote_fs $syslog avahi-daemon -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Mandos server -# Description: Server of encrypted passwords to Mandos clients -### END INIT INFO - -# Author: Teddy Hogeborn -# Author: Björn Påhlsson - -# Do NOT "set -e" - -# PATH should only include /usr/* if it runs after the mountnfs.sh script -PATH=/sbin:/usr/sbin:/bin:/usr/bin -DESC="Mandos root file system password server" -NAME=mandos -DAEMON=/usr/sbin/$NAME -DAEMON_ARGS="" -if [ -d /run/. ]; then - PIDFILE=/run/$NAME.pid -else - PIDFILE=/var/run/$NAME.pid -fi -SCRIPTNAME=/etc/init.d/$NAME - -# Exit if the package is not installed -[ -x "$DAEMON" ] || exit 0 - -# Read configuration variable file if it is present -[ -r /etc/default/$NAME ] && . /etc/default/$NAME - -if [ -n "$CONFIGDIR" ]; then - DAEMON_ARGS="$DAEMON_ARGS --configdir $CONFIGDIR" -fi - -# Load the VERBOSE setting and other rcS variables -. /lib/init/vars.sh - -# Define LSB log_* functions. -# Depend on lsb-base (>= 3.2-14) to ensure that this file is present -# and status_of_proc is working. -. /lib/lsb/init-functions - -# -# Function that starts the daemon/service -# -do_start() -{ - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ - || return 1 - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ - $DAEMON_ARGS \ - || return 2 - # Add code here, if necessary, that waits for the process to be ready - # to handle requests from services started subsequently which depend - # on this one. As a last resort, sleep for some time. -} - -# -# Function that stops the daemon/service -# -do_stop() -{ - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME - RETVAL="$?" - [ "$RETVAL" = 2 ] && return 2 - # Wait for children to finish too if this is a daemon that forks - # and if the daemon is only ever run from this initscript. - # If the above conditions are not satisfied then add some other code - # that waits for the process to drop all resources that could be - # needed by services started subsequently. A last resort is to - # sleep for some time. - start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON - [ "$?" = 2 ] && return 2 - # Many daemons don't delete their pidfiles when they exit. - rm -f $PIDFILE - return "$RETVAL" -} - -# -# Function that sends a SIGHUP to the daemon/service -# -do_reload() { - # - # If the daemon can reload its configuration without - # restarting (for example, when it is sent a SIGHUP), - # then implement that here. - # - start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME - return 0 -} - -case "$1" in - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - stop) - [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - status) - status_of_proc "$DAEMON" "$NAME" -p "$PIDFILE" && exit 0 || exit $? - ;; - #reload|force-reload) - # - # If do_reload() is not implemented then leave this commented out - # and leave 'force-reload' as an alias for 'restart'. - # - #log_daemon_msg "Reloading $DESC" "$NAME" - #do_reload - #log_end_msg $? - #;; - restart|force-reload) - # - # If the "reload" option is implemented then remove the - # 'force-reload' alias - # - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # Failed to stop - log_end_msg 1 - ;; - esac - ;; - *) - #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 - echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 - exit 3 - ;; -esac - -: === removed file 'initramfs-tools-conf' --- initramfs-tools-conf 2018-08-19 14:06:55 +0000 +++ initramfs-tools-conf 1970-01-01 00:00:00 +0000 @@ -1,17 +0,0 @@ -# -*- shell-script -*- - -# Since the initramfs image will contain key files, we need to -# restrict permissions on it by setting UMASK here. -# -# The proper place to set UMASK is (according to -# /etc/cryptsetup-initramfs/conf-hook), in -# /etc/initramfs-tools/initramfs.conf, which we shouldn't edit. The -# corresponding directory for drop-in files from packages is -# /usr/share/initramfs-tools/conf.d, and this file will be installed -# there as "mandos-conf". -# -# This setting of UMASK will have unfortunate unintended side effects -# on the files *inside* the initramfs, but these are later fixed by -# "initramfs-tools-hook", installed as -# "/usr/share/initramfs-tools/hooks/mandos". -UMASK=0027 === removed file 'initramfs-tools-conf-hook' --- initramfs-tools-conf-hook 2019-04-09 19:33:36 +0000 +++ initramfs-tools-conf-hook 1970-01-01 00:00:00 +0000 @@ -1,14 +0,0 @@ -# -*- shell-script -*- - -# The UMASK is set by the file "initramfs-tools-conf" (which is copied -# to /usr/share/initramfs-tools/conf.d/mandos-conf on installation) -# since there, as described therein, is the proper place to do that. -# However, it is possible for other packages to override the UMASK in -# any file in /usr/share/initramfs-tools/conf-hooks.d. Therefore, -# this file ("initramfs-tools-conf-hook") will be installed as -# "zz-mandos" in that directory to make sure UMASK is set correctly. - -# For more information on the effects of setting UMASK, see the -# aforementioned /usr/share/initramfs-tools/conf.d/mandos-conf file. - -UMASK=0027 === removed file 'initramfs-tools-hook' --- initramfs-tools-hook 2018-08-19 14:06:55 +0000 +++ initramfs-tools-hook 1970-01-01 00:00:00 +0000 @@ -1,277 +0,0 @@ -#!/bin/sh - -# This script will be run by 'mkinitramfs' when it creates the image. -# Its job is to decide which files to install, then install them into -# the staging area, where the initramfs is being created. This -# happens when a new 'linux-image' package is installed, or when an -# administrator runs 'update-initramfs' by hand to update an initramfs -# image. - -# The environment contains at least: -# -# DESTDIR -- The staging directory where the image is being built. - -# No initramfs pre-requirements -PREREQ="cryptroot" - -prereqs() -{ - echo "$PREREQ" -} - -case $1 in -# get pre-requisites -prereqs) - prereqs - exit 0 - ;; -esac - -. /usr/share/initramfs-tools/hook-functions - -for d in /usr/lib \ - "/usr/lib/`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null`" \ - "`rpm --eval='%{_libdir}' 2>/dev/null`" /usr/local/lib; do - if [ -d "$d"/mandos ]; then - libdir="$d" - break - fi -done -if [ -z "$libdir" ]; then - # Mandos not found - exit 1 -fi - -for d in /etc/keys/mandos /etc/mandos/keys; do - if [ -d "$d" ]; then - keydir="$d" - break - fi -done -if [ -z "$keydir" ]; then - # Mandos key directory not found - exit 1 -fi - -set `{ getent passwd _mandos \ - || getent passwd nobody \ - || echo ::65534:65534:::; } \ - | cut --delimiter=: --fields=3,4 --only-delimited \ - --output-delimiter=" "` -mandos_user="$1" -mandos_group="$2" - -# The Mandos network client uses the network -auto_add_modules net -# The Mandos network client uses IPv6 -force_load ipv6 - -# These are directories inside the initrd -CONFDIR="/conf/conf.d/mandos" -MANDOSDIR="/lib/mandos" -PLUGINDIR="${MANDOSDIR}/plugins.d" -PLUGINHELPERDIR="${MANDOSDIR}/plugin-helpers" -HOOKDIR="${MANDOSDIR}/network-hooks.d" - -# Make directories -install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \ - "${DESTDIR}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}" -install --owner=${mandos_user} --group=${mandos_group} --directory \ - --mode=u=rwx "${DESTDIR}${PLUGINDIR}" \ - "${DESTDIR}${PLUGINHELPERDIR}" - -copy_exec "$libdir"/mandos/mandos-to-cryptroot-unlock "${MANDOSDIR}" - -# Copy the Mandos plugin runner -copy_exec "$libdir"/mandos/plugin-runner "${MANDOSDIR}" - -# Copy the plugins - -# Copy the packaged plugins -for file in "$libdir"/mandos/plugins.d/*; do - base="`basename \"$file\"`" - # Is this plugin overridden? - if [ -e "/etc/mandos/plugins.d/$base" ]; then - continue - fi - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") echo "W: Mandos client plugin directory is empty." >&2 ;; - *) copy_exec "$file" "${PLUGINDIR}" ;; - esac -done - -# Copy the packaged plugin helpers -for file in "$libdir"/mandos/plugin-helpers/*; do - base="`basename \"$file\"`" - # Is this plugin overridden? - if [ -e "/etc/mandos/plugin-helpers/$base" ]; then - continue - fi - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) copy_exec "$file" "${PLUGINHELPERDIR}" ;; - esac -done - -# Copy any user-supplied plugins -for file in /etc/mandos/plugins.d/*; do - base="`basename \"$file\"`" - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) copy_exec "$file" "${PLUGINDIR}" ;; - esac -done - -# Copy any user-supplied plugin helpers -for file in /etc/mandos/plugin-helpers/*; do - base="`basename \"$file\"`" - case "$base" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) copy_exec "$file" "${PLUGINHELPERDIR}" ;; - esac -done - -# Get DEVICE from initramfs.conf and other files -. /etc/initramfs-tools/initramfs.conf -for conf in /etc/initramfs-tools/conf.d/*; do - if [ -n `basename \"$conf\" | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ - | grep -v '\.dpkg-.*$'` ]; then - [ -f "${conf}" ] && . "${conf}" - fi -done -export DEVICE - -# Copy network hooks -for hook in /etc/mandos/network-hooks.d/*; do - case "`basename \"$hook\"`" in - "*") continue ;; - *[!A-Za-z0-9_.-]*) continue ;; - *) test -d "$hook" || copy_exec "$hook" "${HOOKDIR}" ;; - esac - if [ -x "$hook" ]; then - # Copy any files needed by the network hook - MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=files \ - VERBOSITY=0 "$hook" files | while read -r file target; do - if [ ! -e "${file}" ]; then - echo "WARNING: file ${file} not found, requested by Mandos network hook '${hook##*/}'" >&2 - fi - if [ -z "${target}" ]; then - copy_exec "$file" - else - copy_exec "$file" "$target" - fi - done - # Copy and load any modules needed by the network hook - MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=modules \ - VERBOSITY=0 "$hook" modules | while read -r module; do - force_load "$module" - done - fi -done - -# GPGME needs GnuPG -gpg=/usr/bin/gpg -libgpgme11_version="`dpkg-query --showformat='${Version}' --show libgpgme11`" -if dpkg --compare-versions "$libgpgme11_version" ge 1.5.0-0.1; then - if [ -e /usr/bin/gpgconf ]; then - if [ ! -e "${DESTDIR}/usr/bin/gpgconf" ]; then - copy_exec /usr/bin/gpgconf - fi - gpg="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg:[^:]*://p'`" - gpgagent="`/usr/bin/gpgconf|sed --quiet --expression='s/^gpg-agent:[^:]*://p'`" - # Newer versions of GnuPG 2 requires the gpg-agent binary - if [ -e "$gpgagent" ] && [ ! -e "${DESTDIR}$gpgagent" ]; then - copy_exec "$gpgagent" - fi - fi -elif dpkg --compare-versions "$libgpgme11_version" ge 1.4.1-0.1; then - gpg=/usr/bin/gpg2 -fi -if [ ! -e "${DESTDIR}$gpg" ]; then - copy_exec "$gpg" -fi -unset gpg -unset libgpgme11_version - -# Config files -for file in /etc/mandos/plugin-runner.conf; do - if [ -d "$file" ]; then - continue - fi - cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" -done - -if [ ${mandos_user} != 65534 ]; then - sed --in-place --expression="1i--userid=${mandos_user}" \ - "${DESTDIR}${CONFDIR}/plugin-runner.conf" -fi - -if [ ${mandos_group} != 65534 ]; then - sed --in-place --expression="1i--groupid=${mandos_group}" \ - "${DESTDIR}${CONFDIR}/plugin-runner.conf" -fi - -# Key files -for file in "$keydir"/*; do - if [ -d "$file" ]; then - continue - fi - case "$file" in - *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) - : ;; - "*") : ;; - *) - cp --archive --sparse=always "$file" \ - "${DESTDIR}${CONFDIR}" - chown ${mandos_user}:${mandos_group} \ - "${DESTDIR}${CONFDIR}/`basename \"$file\"`" - ;; - esac -done -# Use Diffie-Hellman parameters file if available -if [ -e "${DESTDIR}${CONFDIR}"/dhparams.pem ]; then - sed --in-place \ - --expression="1i--options-for=mandos-client:--dh-params=${CONFDIR}/dhparams.pem" \ - "${DESTDIR}/${CONFDIR}/plugin-runner.conf" -fi - -# /lib/mandos/plugin-runner will drop priviliges, but needs access to -# its plugin directory and its config file. However, since almost all -# files in initrd have been created with umask 027, this opening of -# permissions is needed. -# -# (The umask is not really intended to affect the files inside the -# initrd; it is intended to affect the initrd.img file itself, since -# it now contains secret key files. There is, however, no other way -# to set the permission of the initrd.img file without a race -# condition. This umask is set by "initramfs-tools-conf", installed -# as "/usr/share/initramfs-tools/conf.d/mandos-conf".) -# -for full in "${MANDOSDIR}" "${CONFDIR}"; do - while [ "$full" != "/" ]; do - chmod a+rX "${DESTDIR}$full" - full="`dirname \"$full\"`" - done -done - -# Reset some other things to sane permissions which we have -# inadvertently affected with our umask setting. -for dir in / /bin /etc /keyscripts /sbin /scripts /usr /usr/bin; do - if [ -d "${DESTDIR}$dir" ]; then - chmod a+rX "${DESTDIR}$dir" - fi -done -for dir in "${DESTDIR}"/lib* "${DESTDIR}"/usr/lib*; do - if [ -d "$dir" ]; then - find "$dir" \! -perm -u+rw,g+r -prune -or \! -type l -print0 \ - | xargs --null --no-run-if-empty chmod a+rX -- - fi -done === removed file 'initramfs-tools-script' --- initramfs-tools-script 2018-08-19 01:35:11 +0000 +++ initramfs-tools-script 1970-01-01 00:00:00 +0000 @@ -1,181 +0,0 @@ -#!/bin/sh -e -# -# This script will run in the initrd environment at boot and edit -# /conf/conf.d/cryptroot to set /lib/mandos/plugin-runner as keyscript -# when no other keyscript is set, before cryptsetup. -# - -# This script should be installed as -# "/usr/share/initramfs-tools/scripts/init-premount/mandos" which will -# eventually be "/scripts/init-premount/mandos" in the initrd.img -# file. - -PREREQ="udev" -prereqs() -{ - echo "$PREREQ" -} - -case $1 in -prereqs) - prereqs - exit 0 - ;; -esac - -. /scripts/functions - -for param in `cat /proc/cmdline`; do - case "$param" in - ip=*) IPOPTS="${param#ip=}" ;; - mandos=*) - # Split option line on commas - old_ifs="$IFS" - IFS="$IFS," - for mpar in ${param#mandos=}; do - IFS="$old_ifs" - case "$mpar" in - off) exit 0 ;; - connect) connect="" ;; - connect:*) connect="${mpar#connect:}" ;; - *) log_warning_msg "$0: Bad option ${mpar}" ;; - esac - done - unset mpar - IFS="$old_ifs" - unset old_ifs - ;; - esac -done -unset param - -chmod a=rwxt /tmp - -# Get DEVICE from /conf/initramfs.conf and other files -. /conf/initramfs.conf -for conf in /conf/conf.d/*; do - [ -f "${conf}" ] && . "${conf}" -done -if [ -e /conf/param.conf ]; then - . /conf/param.conf -fi - -# Override DEVICE from sixth field of ip= kernel option, if passed -case "$IPOPTS" in - *:*:*:*:*:*) # At least six fields - # Remove the first five fields - device="${IPOPTS#*:*:*:*:*:}" - # Remove all fields except the first one - DEVICE="${device%%:*}" - ;; -esac - -# Add device setting (if any) to plugin-runner.conf -if [ "${DEVICE+set}" = set ]; then - # Did we get the device from an ip= option? - if [ "${device+set}" = set ]; then - # Let ip= option override local config; append: - cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --options-for=mandos-client:--interface=${DEVICE} -EOF - else - # Prepend device setting so any later options would override: - sed -i -e \ - '1i--options-for=mandos-client:--interface='"${DEVICE}" \ - /conf/conf.d/mandos/plugin-runner.conf - fi -fi -unset device - -# If we are connecting directly, run "configure_networking" (from -# /scripts/functions); it needs IPOPTS and DEVICE -if [ "${connect+set}" = set ]; then - set +e # Required by library functions - configure_networking - set -e - if [ -n "$connect" ]; then - cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --options-for=mandos-client:--connect=${connect} -EOF - fi -fi - -if [ -r /conf/conf.d/cryptroot ]; then - test -w /conf/conf.d - - # Do not replace cryptroot file unless we need to. - replace_cryptroot=no - - # Our keyscript - mandos=/lib/mandos/plugin-runner - test -x "$mandos" - - # parse /conf/conf.d/cryptroot. Format: - # target=sda2_crypt,source=/dev/sda2,rootdev,key=none,keyscript=/foo/bar/baz - # Is the root device specially marked? - changeall=yes - while read -r options; do - case "$options" in - rootdev,*|*,rootdev,*|*,rootdev) - # If the root device is specially marked, don't change all - # lines in crypttab by default. - changeall=no - ;; - esac - done < /conf/conf.d/cryptroot - - exec 3>/conf/conf.d/cryptroot.mandos - while read -r options; do - newopts="" - keyscript="" - changethis="$changeall" - # Split option line on commas - old_ifs="$IFS" - IFS="$IFS," - for opt in $options; do - # Find the keyscript option, if any - case "$opt" in - keyscript=*) - keyscript="${opt#keyscript=}" - newopts="$newopts,$opt" - ;; - "") : ;; - # Always use Mandos on the root device, if marked - rootdev) - changethis=yes - newopts="$newopts,$opt" - ;; - # Don't use Mandos on resume device, if marked - resumedev) - changethis=no - newopts="$newopts,$opt" - ;; - *) - newopts="$newopts,$opt" - ;; - esac - done - IFS="$old_ifs" - unset old_ifs - # If there was no keyscript option, add one. - if [ "$changethis" = yes ] && [ -z "$keyscript" ]; then - replace_cryptroot=yes - newopts="$newopts,keyscript=$mandos" - fi - newopts="${newopts#,}" - echo "$newopts" >&3 - done < /conf/conf.d/cryptroot - exec 3>&- - - # If we need to, replace the old cryptroot file with the new file. - if [ "$replace_cryptroot" = yes ]; then - mv /conf/conf.d/cryptroot /conf/conf.d/cryptroot.mandos-old - mv /conf/conf.d/cryptroot.mandos /conf/conf.d/cryptroot - else - rm -f /conf/conf.d/cryptroot.mandos - fi -elif [ -x /usr/bin/cryptroot-unlock ]; then - setsid /lib/mandos/mandos-to-cryptroot-unlock & -fi === removed file 'initramfs-tools-script-stop' --- initramfs-tools-script-stop 2018-08-19 14:58:40 +0000 +++ initramfs-tools-script-stop 1970-01-01 00:00:00 +0000 @@ -1,65 +0,0 @@ -#!/bin/sh -e -# -# Script to wait for plugin-runner to exit before continuing boot -# -# Copyright © 2018 Teddy Hogeborn -# Copyright © 2018 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# -# This script will run in the initrd environment at boot and remove -# the file keeping the dummy plugin running, forcing plugin-runner to -# exit if it is still running. - -# This script should be installed as -# "/usr/share/initramfs-tools/scripts/local-premount/mandos" which will -# eventually be "/scripts/local-premount/mandos" in the initrd.img -# file. - -PREREQ="" -prereqs() -{ - echo "$PREREQ" -} - -case $1 in -prereqs) - prereqs - exit 0 - ;; -esac - -. /scripts/functions - -pid=$(cat /run/mandos-plugin-runner.pid 2>/dev/null) - -# If the dummy plugin is running, removing this file should force the -# dummy plugin to exit successfully, thereby making plugin-runner shut -# down all its other plugins and then exit itself. -rm -f /run/mandos-keep-running >/dev/null 2>&1 - -# Wait for exit of plugin-runner, if still running -if [ -n "$pid" ]; then - while :; do - case "$(readlink /proc/"$pid"/exe 2>/dev/null)" in - */plugin-runner) sleep 1;; - *) break;; - esac - done - rm -f /run/mandos-plugin-runner.pid >/dev/null 2>&1 -fi === removed file 'initramfs-unpack' --- initramfs-unpack 2019-07-27 10:11:45 +0000 +++ initramfs-unpack 1970-01-01 00:00:00 +0000 @@ -1,84 +0,0 @@ -#!/bin/bash -# -# Initramfs unpacker - unpacks initramfs images into /tmp -# -# Copyright © 2013-2019 Teddy Hogeborn -# Copyright © 2013-2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see -# . -# -# Contact the authors at . - -cpio="cpio --extract --make-directories --unconditional --preserve-modification-time" - -if [ -z "$*" ]; then - set -- /boot/initrd.img-* -fi - -for imgfile in "$@"; do - if ! [ -f "$imgfile" ]; then - echo "Error: Not an existing file: $imgfile" >&2 - continue - fi - imgdir="${TMPDIR:-/tmp}/${imgfile##*/}" - if [ -d "$imgdir" ]; then - rm --recursive -- "$imgdir" - fi - mkdir --parents "$imgdir" - # Does this image contain microcode? - if $cpio --quiet --list --file="$imgfile" >/dev/null 2>&1; then - # Number of bytes to skip to get to the compressed archive - skip=$(($(LANG=C $cpio --io-size=1 --list --file="$imgfile" 2>&1 \ - | sed --quiet \ - --expression='s/^\([0-9]\+\) blocks$/\1/p')+8)) - if [ -x /usr/lib/dracut/skipcpio ]; then - catimg="/usr/lib/dracut/skipcpio $imgfile" - else - catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" - fi - else - echo "No microcode detected" - catimg="cat -- $imgfile" - fi - while :; do - # Determine the compression method - if { $catimg 2>/dev/null | zcat --test >/dev/null 2>&1; - [ ${PIPESTATUS[-1]} -eq 0 ]; }; then - decomp="zcat" - elif { $catimg 2>/dev/null | bzip2 --test >/dev/null 2>&1; - [ ${PIPESTATUS[-1]} -eq 0 ]; }; then - decomp="bzip2 --stdout --decompress" - elif { $catimg 2>/dev/null | lzop --test >/dev/null 2>&1; - [ ${PIPESTATUS[-1]} -eq 0 ]; }; then - decomp="lzop --stdout --decompress" - else - skip=$((${skip}+1)) - echo "Could not determine compression of ${imgfile}; trying to skip ${skip} bytes" >&2 - catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" - continue - fi - break - done - case "$catimg" in - *skipcpio*) echo "Microcode detected, skipping";; - *) echo "Microcode detected, skipping ${skip} bytes";; - esac - $catimg 2>/dev/null | $decomp | ( cd -- "$imgdir" && $cpio --quiet ) - if [ ${PIPESTATUS[-1]} -eq 0 ]; then - echo "$imgfile unpacked into $imgdir" - fi -done === removed file 'intro.xml' --- intro.xml 2019-08-04 12:42:49 +0000 +++ intro.xml 1970-01-01 00:00:00 +0000 @@ -1,472 +0,0 @@ - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - intro - 8mandos - - - - intro - - Introduction to the Mandos system - - - - - DESCRIPTION - - This is the the Mandos system, which allows computers to have - encrypted root file systems and at the same time be capable of - remote and/or unattended reboots. - - - The computers run a small client program in the initial RAM disk - environment which will communicate with a server over a network. - All network communication is encrypted using TLS. The clients - are identified by the server using a TLS public key; each client - has one unique to it. The server sends the clients an encrypted - password. The encrypted password is decrypted by the clients - using a separate OpenPGP key, and the password is then used to - unlock the root file system, whereupon the computers can - continue booting normally. - - - - - INTRODUCTION - - - You know how it is. You’ve heard of it happening. The Man - comes and takes away your servers, your friends’ servers, the - servers of everybody in the same hosting facility. The servers - of their neighbors, and their neighbors’ friends. The servers - of people who owe them money. And like - that, they’re gone. And you doubt you’ll - ever see them again. - - - That is why your servers have encrypted root file systems. - However, there’s a downside. There’s no going around it: - rebooting is a pain. Dragging out that rarely-used keyboard and - screen and unraveling cables behind your servers to plug them in - to type in that password is messy, especially if you have many - servers. There are some people who do clever things like using - serial line consoles and daisy-chain it to the next server, and - keep all the servers connected in a ring with serial cables, - which will work, if your servers are physically close enough. - There are also other out-of-band management solutions, but with - all these, you still have to be on hand and - manually type in the password at boot time. Otherwise the - server just sits there, waiting for a password. - - - Wouldn’t it be great if you could have the security of encrypted - root file systems and still have servers that could boot up - automatically if there was a short power outage while you were - asleep? That you could reboot at will, without having someone - run over to the server to type in the password? - - - Well, with Mandos, you (almost) can! The gain in convenience - will only be offset by a small loss in security. The setup is - as follows: - - - The server will still have its encrypted root file system. The - password to this file system will be stored on another computer - (henceforth known as the Mandos server) on the same local - network. The password will not be stored - in plaintext, but encrypted with OpenPGP. To decrypt this - password, a key is needed. This key (the Mandos client key) - will not be stored there, but back on the original server - (henceforth known as the Mandos client) in the initial RAM disk - image. Oh, and all network Mandos client/server communications - will be encrypted, using TLS (SSL). - - - So, at boot time, the Mandos client will ask for its encrypted - data over the network, decrypt the data to get the password, use - the password to decrypt the root file system, and the client can - then continue booting. - - - Now, of course the initial RAM disk image is not on the - encrypted root file system, so anyone who had physical access - could take the Mandos client computer offline and read the disk - with their own tools to get the authentication keys used by a - client. But, by then the Mandos server - should notice that the original server has been offline for too - long, and will no longer give out the encrypted key. The timing - here is the only real weak point, and the method, frequency and - timeout of the server’s checking can be adjusted to any desired - level of paranoia. - - - (The encrypted keys on the Mandos server is on its normal file - system, so those are safe, provided the root file system of - that server is encrypted.) - - - - - FREQUENTLY ASKED QUESTIONS - - Couldn’t the security be defeated by… - - - Grabbing the Mandos client key from the - initrd <emphasis>really quickly</emphasis>? - - This, as mentioned above, is the only real weak point. But if - you set the timing values tight enough, this will be really - difficult to do. An attacker would have to physically - disassemble the client computer, extract the key from the - initial RAM disk image, and then connect to a still - online Mandos server to get the encrypted key, and do - all this before the Mandos server timeout - kicks in and the Mandos server refuses to give out the key to - anyone. - - - Now, as the typical procedure seems to be to barge in and turn - off and grab all computers, to maybe look - at them months later, this is not likely. If someone does that, - the whole system will lock itself up - completely, since Mandos servers are no longer running. - - - For sophisticated attackers who could do - the clever thing, and had physical access - to the server for enough time, it would be simpler to get a key - for an encrypted file system by using hardware memory scanners - and reading it right off the memory bus. - - - - - Replay attacks? - - Nope, the network stuff is all done over TLS, which provides - protection against that. - - - - - Man-in-the-middle? - - No. The server only gives out the passwords to clients which - have in the TLS handshake proven that - they do indeed hold the private key corresponding to that - client. - - - - - How about sniffing the network traffic and decrypting it - later by physically grabbing the Mandos client and using its - key? - - We only use PFS (Perfect Forward Security) - key exchange algorithms in TLS, which protects against this. - - - - - Physically grabbing the Mandos server computer? - - You could protect that computer the - old-fashioned way, with a must-type-in-the-password-at-boot - method. Or you could have two computers be the Mandos server - for each other. - - - Multiple Mandos servers can coexist on a network without any - trouble. They do not clash, and clients will try all - available servers. This means that if just one reboots then - the other can bring it back up, but if both reboot at the same - time they will stay down until someone types in the password - on one of them. - - - - - Faking checker results? - - If the Mandos client does not have an SSH server, the default - is for the Mandos server to use - fping, the replies to which - could be faked to eliminate the timeout. But this could - easily be changed to any shell command, with any security - measures you like. If the Mandos client - has an SSH server, the default - configuration (as generated by - mandos-keygen with the - option) is for the Mandos server - to use an ssh-keyscan command with strict - keychecking, which can not be faked. Alternatively, IPsec - could be used for the ping packets, making them secure. - - - - - - SECURITY - - So, in summary: The only weakness in the Mandos system is from - people who have: - - - - - The power to come in and physically take your servers, - and - - - - - The cunning and patience to do it carefully, one at a time, - and quickly, faking Mandos - client/server responses for each one before the timeout. - - - - - While there are some who may be threatened by people who have - both these attributes, they do not, - probably, constitute the majority. - - - If you do face such opponents, you must - figure that they could just as well open your servers and read - the file system keys right off the memory by running wires to - the memory bus. - - - What Mandos is designed to protect against is - not such determined, focused, and competent - attacks, but against the early morning knock on your door and - the sudden absence of all the servers in your server room. - Which it does nicely. - - - - - PLUGINS - - In the early designs, the - mandos-client8mandos program (which - retrieves a password from the Mandos server) also prompted for a - password on the terminal, in case a Mandos server could not be - found. Other ways of retrieving a password could easily be - envisoned, but this multiplicity of purpose was seen to be too - complex to be a viable way to continue. Instead, the original - program was separated into mandos-client8mandos and password-prompt8mandos, and a plugin-runner8mandos exist to run them both in parallel, allowing - the first successful plugin to provide the password. This - opened up for any number of additional plugins to run, all - competing to be the first to find a password and provide it to - the plugin runner. - - - Four additional plugins are provided: - - - - - plymouth - 8mandos - - - - This prompts for a password when using - plymouth8. - - - - - - usplash - 8mandos - - - - This prompts for a password when using - usplash8. - - - - - - splashy - 8mandos - - - - This prompts for a password when using - splashy8. - - - - - - askpass-fifo - 8mandos - - - - To provide compatibility with the "askpass" program from - cryptsetup, this plugin listens to the same FIFO as - askpass would do. - - - - - - More plugins can easily be written and added by the system - administrator; see the section called "WRITING PLUGINS" in - plugin-runner - 8mandos to learn the - plugin requirements. - - - - - SYSTEMD - - More advanced startup systems like systemd1, - already have their own plugin-like mechanisms for allowing - multiple agents to independently retrieve a password and deliver - it to the subsystem requesting a password to unlock the root - file system. On these systems, it would make no sense to run - plugin-runner8mandos, the plugins of - which would largely duplicate the work of (and conflict with) - the existing systems prompting for passwords. - - - As for systemd1 in particular, it has - its own Password Agents system. Mandos uses this via its - password-agent8mandos program, which - is run instead of plugin-runner8mandos when systemd1 - is used during system startup. - - - - BUGS - - - - - SEE ALSO - - mandos - 8, - mandos.conf - 5, - mandos-clients.conf - 5, - mandos-ctl - 8, - mandos-monitor - 8, - plugin-runner - 8mandos, - password-agent - 8mandos, - mandos-client - 8mandos, - password-prompt - 8mandos, - plymouth - 8mandos, - usplash - 8mandos, - splashy - 8mandos, - askpass-fifo - 8mandos, - mandos-keygen - 8 - - - - - Mandos - - - - The Mandos home page. - - - - - -
- - - - - === added file 'key.pem' --- key.pem 1970-01-01 00:00:00 +0000 +++ key.pem 2007-10-20 21:38:25 +0000 @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAriPn2IkZ+KvmPGXiAJ4vKFcQPhgZ3jaj+WVd1a1g70ge2sJm +SedEiOO+JDQ8mTolCkgaQ2t1bm5FJBxMqyFwQ4vxymtQd74ymLzfiLts5o+UN0li +gxq2QcS+r3bB5cV8przIblS2YBuK6jfzwludWBTscCqrWKmL51IyglTQVHerEoLd +6KhBB2r5AHbnsF5k0OrdKtZOplfDum7gOaI5kmGkwBXkKaNAq3hkLQR0HIZy3S2n +dfezCkFj7Rr7CUUwpV7VYbL11+GQ1FWFQJc0o4K5pogyyfyK4Jw2WQjDgZWG8mP0 +At9FlLlXNFooiE6cW5vj4/FgKW3WCF9MGGTVpCOJKXUlP6q2Cm9Kp3fg6di0tGn4 +vkRwtBI+jKQbZOOxTGdhe0GI+hm4iTu/lqVCVtPbQOa5aGkIybh/DVbRDp5cvYA+ +2uMC3sdLv2oOinJIviy3ydORjd/OaNbxWmDVjgfI3ppjy1H04iNLBmEK/42iLFtl +OhScteahG1Cc7qtKnuou4kI8LWBsNzVNHiZyWvlW6ZvxqDWJBOKLRK+Gpjy7WwMw +aUv1plbRZrKR+gLrhxR0Eqb1Hp23TxjTEB69JrF1yyYr2QJ8Ik6jqcnalD35Z1KN +gSGrkMsEaGl+HOz5TVkvQdiZN/JWOZta4NlpDViG3i8d/VNjACkWMYka3/sCAwEA +AQKCAgEApV0fWvbGnOfQGOa++Ms+CNa0a+LDHctRZxElTDX6aP9ZnW8hZ2igIkXy +V7rrGK6oYd0aY4910koQijv9ajy0uM/56biCj1MkBPrGYrdosIEDxISBcfI5xLaq +RUFG24Tv2/5FbtAu55EAF27OoXASOISWCeXbFLTcT+w0XqNfufZxk4CGbdro9bxV +fGVtmoPoxKNjJryfr0KEcVO8xb4RYborkuS25/tI/Au1RTKHeFcMWJB0B4gSktiJ +pa0LWkBD86Xch4xD/J8MwzX69d0gdW08ErIfWMPitWxiB6ZU0YdGwIK+QsP29UYT +U3mSJ+5OeIfOnrSmFnFg4E5umnOWk1fCkX9FpFwjFy2H9FrF/BHyZrIbv8IeKgzw +mySRXFl2KZUk8r8YyCTm4dtf2ZCU5KcUo9TOae1VzywjXFyV+AyOtFYcJ+FjHQag +CjuT4NzmFqlwa4JWXsAtnom7eR+f4jpn3nPW2CYCE/tYxtF9fpGXdXCNaKo8fbsA +FzOkvoPSAeOjDjcKba6Z/mMBb8or1ide06NEu77e8de8rWCEfdGbMGDmdLQaYI6u +/WIY7ALRppqT9WKnAjKGgYkxFeZGGF8D4ARaLJYYJ71DGAKeSkK6RqHqhmWDcSI8 +cLh5WHqzCflQhxjVf5rCF/ASwTnbw0dsClqF35gw+TKAlXc/uhECggEBAOMVwVIR +ZkCvcfH35RGxqMZ/Ca8Viqap5zLY/gL0YTM/HYhRPE8iUutCWGN1SBDQm2yKuk84 +H1Jjj7zTOxIVW0yov1m36KCsF31s8ks1b3uo+yEOyLhGpJPUq8mYAVbl/13Tcwp3 +0zs7oflZxK65vICUreJM9P/BqXJ/Edfddj88uIqjW98XGgTCgCHpFBxKfRzNkmi+ +3TBswZm2yqQ3qcrFN6H7ieUNCgN2Bj6tWlNmB/RCV+stbbEWwXbDlv1M+JNoM/Jw +c1A++csAhx04BphNeKi01W0zEyRt6tlZWqolvhSUTom6xxWtjikqwfad6HMwn09V +8luh5Ote2WNE8JcCggEBAMRQUlYKCTSvHuIscDukLExW+9JVKaufeXZvCuTK94Bs +P0MtAgrHkgXdiRIBBFAViPjadMQitGkwKYd+zRPUmdzTuH9Lamqjl6e8dUpcO+Bf +0scM94Te0gD8Gtv2Uu4Kw75P2RTXHRk9e8VbFpgYvfl9H0Uc8BK8jzlgWPqHgFnB +WUXuMTAcKax4UwXdxWUjyv3xPmDygt7koPqpuX6FKvfxRq50a3TVIFku+8hRQtQR +N+U0ScYrTOClTuc/QE0Q6rmw06lWHmytK3QiVhgpKxdw1xvVyjw5rMbB2eQdz7IQ +2zn/o5+0gYdg/jvamo1HIxKjPCP1Lxm++manmy6kVD0CggEAeDwwm89yoJVEc6WZ +uAClKFRjQDzbqNsU+ytBczcJsCSe8mpw0EWQOdhrDF4wxhZt9M6PTxqcGvd0R7pf +8Hc2XCSNDGf/1/LGjTZ+I6wrVwJl1V8Kj+d3hH56ZscBDo3A5GDs7IH9acNtQ6Vw +KkNVt48Bcmzk2/YiTelR/UXZMipoW5+bKUgGErcZONs8Nq6KCBIgjy1f2B/9cfIC +4WhHkoFRr4aLwKdiwepf7BfFV5sSYxYtjuwCxF0UGln9PCjhBMuLlbZMmBSAFig1 +YhckBsgeNtVom+ULIaLBUkupYaWSOzs7SlmGx8eZGdr10CpTxYndEBiltjbGComx ++ImsCQKCAQBaaX+yHock225Gzh6WaUL5man6sbwyTY0cLYH/4zZfz/rGzmi9XDJ1 +PxVM1GkPFQvzSHE0j6M1Org1rgF3G5gNKvkyryIAoP1MhDAkohv1d0xU3jT03cYs +K++W1HhXJ2AFOzMINRYytK1XNF9QhzyfNa/8HZq3ll4EF8qC/3ruW2zpFw1SUfYj +d3sNHZk2vmhT0hJfhfEeBH/bUeWbTmt+q4FZAUcoFKwERu4w0LQNhSyQBCfh+7k5 +UQjo2amclKj2AmlI+N+kP5DeuJ2cHQG6lv6K1EiCujFHjKn0NIKeSMMekAzklbZ+ +Cf6sxD4fyN5vS/x7twUNP3aFZrXCom4lAoIBABSGp/CGw6xeQDPXvZZ7gpLr5m5P +mp+gS3S2MJZUv+6TEwXgHxnbxt5DMe25RAlqVHTuw9/TVccD6qcERvnhxUFMoscD +5Vg/Hj+D1nPEVxvFgq0xEPI2FcwS6xT8gicV5dsCWIlOY3ZxBzOzByW1iA2YNymj +pNTypqkARnheZZCK+4ortIeczll5XpbyGIXyxfIrKLYyv38gqLwCdb+MJsih91qc +SrijfhjqqK9sr5KyHlPJDI4Mlw//qkPwIiKv70TGsSgu775OyyZFEl3uS+Y4BUGa +dk5d+8OviBYIReMvvyl6U0jFCUK579lEDpEOPDF2C6VvDQ3FyHP7UXaOWrI= +-----END RSA PRIVATE KEY----- === removed file 'legalnotice.xml' --- legalnotice.xml 2017-08-20 16:20:54 +0000 +++ legalnotice.xml 1970-01-01 00:00:00 +0000 @@ -1,28 +0,0 @@ - - - - - This manual page is part of Mandos. - - - - Mandos 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. - - - - Mandos 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 Mandos. If not, see http://www.gnu.org/licenses/. - - === removed file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 2019-02-10 04:20:26 +0000 +++ mandos-clients.conf.xml 1970-01-01 00:00:00 +0000 @@ -1,567 +0,0 @@ - - -/etc/mandos/clients.conf"> - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &CONFNAME; - 5 - - - - &CONFNAME; - - Configuration file for the Mandos server - - - - - &CONFPATH; - - - - DESCRIPTION - - The file &CONFPATH; is a configuration file for mandos - 8, read by it at startup. - The file needs to list all clients that should be able to use - the service. The settings in this file can be overridden by - runtime changes to the server, which it saves across restarts. - (See the section called PERSISTENT STATE in - mandos8.) However, any changes to this file (including adding and removing - clients) will, at startup, override changes done during runtime. - - - The format starts with a [section - header] which is either - [DEFAULT] or [client - name]. The client - name can be anything, and is not tied to a host - name. Following the section header is any number of - option=value entries, - with continuations in the style of RFC 822. option: value is also accepted. Note that - leading whitespace is removed from values. Values can contain - format strings which refer to other values in the same section, - or values in the DEFAULT section (see ). Lines beginning with # - or ; are ignored and may be used to provide - comments. - - - - - OPTIONS - - Note: all option values are subject to - start time expansion, see . - - - Unknown options are ignored. The used options are as follows: - - - - - - - - - This option is optional. - - - How long to wait for external approval before resorting to - use the value. The - default is PT0S, i.e. not to wait. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - This option is optional. - - - How long an external approval lasts. The default is 1 - second. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - Whether to approve a client by default after - the . The default - is True. - - - - - - - - - This option is optional. - - - This option overrides the default shell command that the - server will use to check if the client is still up. Any - output of the command will be ignored, only the exit code - is checked: If the exit code of the command is zero, the - client is considered up. The command will be run using - /bin/sh - , so - PATH will be searched. The default - value for the checker command is fping %%(host)s. Note that - mandos-keygen, when generating output - to be inserted into this file, normally looks for an SSH - server on the Mandos client, and, if it finds one, outputs - a option to check for the - client’s SSH key fingerprint – this is more secure against - spoofing. - - - In addition to normal start time expansion, this option - will also be subject to runtime expansion; see . - - - - - - - - - This option is optional. - - - Extended timeout is an added timeout that is given once - after a password has been sent successfully to a client. - The timeout is by default longer than the normal timeout, - and is used for handling the extra long downtime while a - machine is booting up. Time to take into consideration - when changing this value is file system checks and quota - checks. The default value is 15 minutes. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - This option is required. - - - This option sets the OpenPGP fingerprint that identifies - the public key that clients authenticate themselves with - through TLS. The string needs to be in hexadecimal form, - but spaces or upper/lower case are not significant. - - - - - - - - - This option is optional. - - - This option sets the certificate key ID that identifies - the public key that clients authenticate themselves with - through TLS. The string needs to be in hexadecimal form, - but spaces or upper/lower case are not significant. - - - - - - - - - This option is optional, but highly - recommended unless the - option is modified to a - non-standard value without %%(host)s in it. - - - Host name for this client. This is not used by the server - directly, but can be, and is by default, used by the - checker. See the option. - - - - - - - - - This option is optional. - - - How often to run the checker to confirm that a client is - still up. Note: a new checker will - not be started if an old one is still running. The server - will wait for a checker to complete until the below - timeout occurs, at which - time the client will be disabled, and any running checker - killed. The default interval is 2 minutes. - - - The format of TIME is the same - as for timeout below. - - - - - - - - - This option is only used if is not - specified, in which case this option is - required. - - - Similar to the , except the secret - data is in an external file. The contents of the file - should not be base64-encoded, but - will be sent to clients verbatim. - - - File names of the form ~user/foo/bar - and $ENVVAR/foo/bar - are supported. - - - - - - - - - If this option is not specified, the option is required - to be present. - - - If present, this option must be set to a string of - base64-encoded binary data. It will be decoded and sent - to the client matching the above - or . This should, of course, - be OpenPGP encrypted data, decryptable only by the client. - The program mandos-keygen8 can, using its - option, be used to generate - this, if desired. - - - Note: this value of this option will probably be very - long. A useful feature to avoid having unreadably-long - lines is that a line beginning with white space adds to - the value of the previous line, RFC 822-style. - - - - - - - - - This option is optional. - - - The timeout is how long the server will wait, after a - successful checker run, until a client is disabled and not - allowed to get the data this server holds. By default - Mandos will use 5 minutes. See also the - option. - - - The TIME is specified as an RFC - 3339 duration; for example - P1Y2M3DT4H5M6S meaning - one year, two months, three days, four hours, five - minutes, and six seconds. Some values can be omitted, see - RFC 3339 Appendix A for details. - - - - - - - - - Whether this client should be enabled by default. The - default is true. - - - - - - - - - EXPANSION - - There are two forms of expansion: Start time expansion and - runtime expansion. - - - START TIME EXPANSION - - Any string in an option value of the form - %(foo)s will be replaced by the value of the option - foo either in the same section, or, if it - does not exist there, the [DEFAULT] - section. This is done at start time, when the configuration - file is read. - - - Note that this means that, in order to include an actual - percent character (%) in an option value, two - percent characters in a row (%%) must be - entered. - - - - RUNTIME EXPANSION - - This is currently only done for the checker - option. - - - Any string in an option value of the form - %%(foo)s will be replaced by the value of the attribute - foo of the internal - Client object in the - Mandos server. The currently allowed values for - foo are: - approval_delay, - approval_duration, - created, - enabled, - expires, - key_id, - fingerprint, - host, - interval, - last_approval_request, - last_checked_ok, - last_enabled, - name, - timeout, and, if using - D-Bus, dbus_object_path. - See the source code for details. Currently, none of these attributes - except host are guaranteed - to be valid in future versions. Therefore, please - let the authors know of any attributes that are useful so they - may be preserved to any new versions of this software. - - - Note that this means that, in order to include an actual - percent character (%) in a - checker option, four - percent characters in a row (%%%%) must be - entered. Also, a bad format here will lead to an immediate - but silent run-time fatal exit; debug - mode is needed to expose an error of this kind. - - - - - - - FILES - - The file described here is &CONFPATH; - - - - - BUGS - - The format for specifying times for timeout - and interval is not very good. - - - The difference between - %%(foo)s and - %(foo)s is - obscure. - - - - - - EXAMPLE - - -[DEFAULT] -timeout = PT5M -interval = PT2M -checker = fping -q -- %%(host)s - -# Client "foo" -[foo] -key_id = 788cd77115cd0bb7b2d5e0ae8496f6b48149d5e712c652076b1fd2d957ef7c1f -fingerprint = 7788 2722 5BA7 DE53 9C5A 7CFA 59CF F7CD BD9A 5920 -secret = - hQIOA6QdEjBs2L/HEAf/TCyrDe5Xnm9esa+Pb/vWF9CUqfn4srzVgSu234 - REJMVv7lBSrPE2132Lmd2gqF1HeLKDJRSVxJpt6xoWOChGHg+TMyXDxK+N - Xl89vGvdU1XfhKkVm9MDLOgT5ECDPysDGHFPDhqHOSu3Kaw2DWMV/iH9vz - 3Z20erVNbdcvyBnuojcoWO/6yfB5EQO0BXp7kcyy00USA3CjD5FGZdoQGI - Tb8A/ar0tVA5crSQmaSotm6KmNLhrFnZ5BxX+TiE+eTUTqSloWRY6VAvqW - QHC7OASxK5E6RXPBuFH5IohUA2Qbk5AHt99pYvsIPX88j2rWauOokoiKZo - t/9leJ8VxO5l3wf/U64IH8bkPIoWmWZfd/nqh4uwGNbCgKMyT+AnvH7kMJ - 3i7DivfWl2mKLV0PyPHUNva0VQxX6yYjcOhj1R6fCr/at8/NSLe2OhLchz - dC+Ls9h+kvJXgF8Sisv+Wk/1RadPLFmraRlqvJwt6Ww21LpiXqXHV2mIgq - WnR98YgSvUi3TJHrUQiNc9YyBzuRo0AjgG2C9qiE3FM+Y28+iQ/sR3+bFs - zYuZKVTObqiIslwXu7imO0cvvFRgJF/6u3HNFQ4LUTGhiM3FQmC6NNlF3/ - vJM2hwRDMcJqDd54Twx90Wh+tYz0z7QMsK4ANXWHHWHR0JchnLWmenzbtW - 5MHdW9AYsNJZAQSOpirE4Xi31CSlWAi9KV+cUCmWF5zOFy1x23P6PjdaRm - 4T2zw4dxS5NswXWU0sVEXxjs6PYxuIiCTL7vdpx8QjBkrPWDrAbcMyBr2O - QlnHIvPzEArRQLo= -host = foo.example.org -interval = PT1M - -# Client "bar" -[bar] -key_id = F90C7A81D72D1EA69A51031A91FF8885F36C8B46D155C8C58709A4C99AE9E361 -fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 -secfile = /etc/mandos/bar-secret -timeout = PT15M -approved_by_default = False -approval_delay = PT30S - - - - - - SEE ALSO - - intro - 8mandos, - mandos-keygen - 8, - mandos.conf - 5, - mandos - 8, - fping - 8 - - - - - RFC 3339: Date and Time on the Internet: - Timestamps - - - - The time intervals are in the "duration" format, as - specified in ABNF in Appendix A of RFC 3339. - - - - - -
- - - - - === removed file 'mandos-ctl' --- mandos-ctl 2019-11-03 19:09:41 +0000 +++ mandos-ctl 1970-01-01 00:00:00 +0000 @@ -1,2535 +0,0 @@ -#!/usr/bin/python3 -bbI -# -*- after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) - -try: - from future_builtins import * -except ImportError: - pass - -import sys -import argparse -import locale -import datetime -import re -import os -import collections -import json -import unittest -import logging -import io -import tempfile -import contextlib - -if sys.version_info.major == 2: - __metaclass__ = type - -try: - import pydbus - import gi - dbus_python = None -except ImportError: - import dbus as dbus_python - pydbus = None - class gi: - """Dummy gi module, for the tests""" - class repository: - class GLib: - class Error(Exception): - pass - -# Show warnings by default -if not sys.warnoptions: - import warnings - warnings.simplefilter("default") - -log = logging.getLogger(sys.argv[0]) -logging.basicConfig(level="INFO", # Show info level messages - format="%(message)s") # Show basic log messages - -logging.captureWarnings(True) # Show warnings via the logging system - -if sys.version_info.major == 2: - str = unicode - import StringIO - io.StringIO = StringIO.StringIO - -locale.setlocale(locale.LC_ALL, "") - -version = "1.8.9" - - -def main(): - parser = argparse.ArgumentParser() - add_command_line_options(parser) - - options = parser.parse_args() - check_option_syntax(parser, options) - - clientnames = options.client - - if options.debug: - log.setLevel(logging.DEBUG) - - if pydbus is not None: - bus = pydbus_adapter.CachingBus(pydbus) - else: - bus = dbus_python_adapter.CachingBus(dbus_python) - - try: - all_clients = bus.get_clients_and_properties() - except dbus.ConnectFailed as e: - log.critical("Could not connect to Mandos server: %s", e) - sys.exit(1) - except dbus.Error as e: - log.critical( - "Failed to access Mandos server through D-Bus:\n%s", e) - sys.exit(1) - - # Compile dict of (clientpath: properties) to process - if not clientnames: - clients = all_clients - else: - clients = {} - for name in clientnames: - for objpath, properties in all_clients.items(): - if properties["Name"] == name: - clients[objpath] = properties - break - else: - log.critical("Client not found on server: %r", name) - sys.exit(1) - - commands = commands_from_options(options) - - for command in commands: - command.run(clients, bus) - - -def add_command_line_options(parser): - parser.add_argument("--version", action="version", - version="%(prog)s {}".format(version), - help="show version number and exit") - parser.add_argument("-a", "--all", action="store_true", - help="Select all clients") - parser.add_argument("-v", "--verbose", action="store_true", - help="Print all fields") - parser.add_argument("-j", "--dump-json", dest="commands", - action="append_const", default=[], - const=command.DumpJSON(), - help="Dump client data in JSON format") - enable_disable = parser.add_mutually_exclusive_group() - enable_disable.add_argument("-e", "--enable", dest="commands", - action="append_const", default=[], - const=command.Enable(), - help="Enable client") - enable_disable.add_argument("-d", "--disable", dest="commands", - action="append_const", default=[], - const=command.Disable(), - help="disable client") - parser.add_argument("-b", "--bump-timeout", dest="commands", - action="append_const", default=[], - const=command.BumpTimeout(), - help="Bump timeout for client") - start_stop_checker = parser.add_mutually_exclusive_group() - start_stop_checker.add_argument("--start-checker", - dest="commands", - action="append_const", default=[], - const=command.StartChecker(), - help="Start checker for client") - start_stop_checker.add_argument("--stop-checker", dest="commands", - action="append_const", default=[], - const=command.StopChecker(), - help="Stop checker for client") - parser.add_argument("-V", "--is-enabled", dest="commands", - action="append_const", default=[], - const=command.IsEnabled(), - help="Check if client is enabled") - parser.add_argument("-r", "--remove", dest="commands", - action="append_const", default=[], - const=command.Remove(), - help="Remove client") - parser.add_argument("-c", "--checker", dest="commands", - action="append", default=[], - metavar="COMMAND", type=command.SetChecker, - help="Set checker command for client") - parser.add_argument( - "-t", "--timeout", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetTimeout.argparse(string_to_delta), - help="Set timeout for client") - parser.add_argument( - "--extended-timeout", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetExtendedTimeout.argparse(string_to_delta), - help="Set extended timeout for client") - parser.add_argument( - "-i", "--interval", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetInterval.argparse(string_to_delta), - help="Set checker interval for client") - approve_deny_default = parser.add_mutually_exclusive_group() - approve_deny_default.add_argument( - "--approve-by-default", dest="commands", - action="append_const", default=[], - const=command.ApproveByDefault(), - help="Set client to be approved by default") - approve_deny_default.add_argument( - "--deny-by-default", dest="commands", - action="append_const", default=[], - const=command.DenyByDefault(), - help="Set client to be denied by default") - parser.add_argument( - "--approval-delay", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetApprovalDelay.argparse(string_to_delta), - help="Set delay before client approve/deny") - parser.add_argument( - "--approval-duration", dest="commands", action="append", - default=[], metavar="TIME", - type=command.SetApprovalDuration.argparse(string_to_delta), - help="Set duration of one client approval") - parser.add_argument("-H", "--host", dest="commands", - action="append", default=[], metavar="STRING", - type=command.SetHost, - help="Set host for client") - parser.add_argument( - "-s", "--secret", dest="commands", action="append", - default=[], metavar="FILENAME", - type=command.SetSecret.argparse(argparse.FileType(mode="rb")), - help="Set password blob (file) for client") - approve_deny = parser.add_mutually_exclusive_group() - approve_deny.add_argument( - "-A", "--approve", dest="commands", action="append_const", - default=[], const=command.Approve(), - help="Approve any current client request") - approve_deny.add_argument("-D", "--deny", dest="commands", - action="append_const", default=[], - const=command.Deny(), - help="Deny any current client request") - parser.add_argument("--debug", action="store_true", - help="Debug mode (show D-Bus commands)") - parser.add_argument("--check", action="store_true", - help="Run self-test") - parser.add_argument("client", nargs="*", help="Client name") - - -def string_to_delta(interval): - """Parse a string and return a datetime.timedelta""" - - try: - return rfc3339_duration_to_delta(interval) - except ValueError as e: - log.warning("%s - Parsing as pre-1.6.1 interval instead", - ' '.join(e.args)) - return parse_pre_1_6_1_interval(interval) - - -def rfc3339_duration_to_delta(duration): - """Parse an RFC 3339 "duration" and return a datetime.timedelta - - >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7) - True - >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60) - True - >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(hours=1) - True - >>> # 60 months - >>> rfc3339_duration_to_delta("P60M") == datetime.timedelta(1680) - True - >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1) - True - >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7) - True - >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330) - True - >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200) - True - >>> # Can not be empty: - >>> rfc3339_duration_to_delta("") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "" - >>> # Must start with "P": - >>> rfc3339_duration_to_delta("1D") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "1D" - >>> # Must use correct order - >>> rfc3339_duration_to_delta("PT1S2M") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "PT1S2M" - >>> # Time needs time marker - >>> rfc3339_duration_to_delta("P1H2S") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P1H2S" - >>> # Weeks can not be combined with anything else - >>> rfc3339_duration_to_delta("P1D2W") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P1D2W" - >>> rfc3339_duration_to_delta("P2W2H") - Traceback (most recent call last): - ... - ValueError: Invalid RFC 3339 duration: "P2W2H" - """ - - # Parsing an RFC 3339 duration with regular expressions is not - # possible - there would have to be multiple places for the same - # values, like seconds. The current code, while more esoteric, is - # cleaner without depending on a parsing library. If Python had a - # built-in library for parsing we would use it, but we'd like to - # avoid excessive use of external libraries. - - # New type for defining tokens, syntax, and semantics all-in-one - Token = collections.namedtuple("Token", ( - "regexp", # To match token; if "value" is not None, must have - # a "group" containing digits - "value", # datetime.timedelta or None - "followers")) # Tokens valid after this token - # RFC 3339 "duration" tokens, syntax, and semantics; taken from - # the "duration" ABNF definition in RFC 3339, Appendix A. - token_end = Token(re.compile(r"$"), None, frozenset()) - token_second = Token(re.compile(r"(\d+)S"), - datetime.timedelta(seconds=1), - frozenset((token_end, ))) - token_minute = Token(re.compile(r"(\d+)M"), - datetime.timedelta(minutes=1), - frozenset((token_second, token_end))) - token_hour = Token(re.compile(r"(\d+)H"), - datetime.timedelta(hours=1), - frozenset((token_minute, token_end))) - token_time = Token(re.compile(r"T"), - None, - frozenset((token_hour, token_minute, - token_second))) - token_day = Token(re.compile(r"(\d+)D"), - datetime.timedelta(days=1), - frozenset((token_time, token_end))) - token_month = Token(re.compile(r"(\d+)M"), - datetime.timedelta(weeks=4), - frozenset((token_day, token_end))) - token_year = Token(re.compile(r"(\d+)Y"), - datetime.timedelta(weeks=52), - frozenset((token_month, token_end))) - token_week = Token(re.compile(r"(\d+)W"), - datetime.timedelta(weeks=1), - frozenset((token_end, ))) - token_duration = Token(re.compile(r"P"), None, - frozenset((token_year, token_month, - token_day, token_time, - token_week))) - # Define starting values: - # Value so far - value = datetime.timedelta() - found_token = None - # Following valid tokens - followers = frozenset((token_duration, )) - # String left to parse - s = duration - # Loop until end token is found - while found_token is not token_end: - # Search for any currently valid tokens - for token in followers: - match = token.regexp.match(s) - if match is not None: - # Token found - if token.value is not None: - # Value found, parse digits - factor = int(match.group(1), 10) - # Add to value so far - value += factor * token.value - # Strip token from string - s = token.regexp.sub("", s, 1) - # Go to found token - found_token = token - # Set valid next tokens - followers = found_token.followers - break - else: - # No currently valid tokens were found - raise ValueError("Invalid RFC 3339 duration: \"{}\"" - .format(duration)) - # End token found - return value - - -def parse_pre_1_6_1_interval(interval): - """Parse an interval string as documented by Mandos before 1.6.1, - and return a datetime.timedelta - - >>> parse_pre_1_6_1_interval('7d') == datetime.timedelta(days=7) - True - >>> parse_pre_1_6_1_interval('60s') == datetime.timedelta(0, 60) - True - >>> parse_pre_1_6_1_interval('60m') == datetime.timedelta(hours=1) - True - >>> parse_pre_1_6_1_interval('24h') == datetime.timedelta(days=1) - True - >>> parse_pre_1_6_1_interval('1w') == datetime.timedelta(days=7) - True - >>> parse_pre_1_6_1_interval('5m 30s') == datetime.timedelta(0, 330) - True - >>> parse_pre_1_6_1_interval('') == datetime.timedelta(0) - True - >>> # Ignore unknown characters, allow any order and repetitions - >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m') == datetime.timedelta(2, 480, 18000) - True - - """ - - value = datetime.timedelta(0) - regexp = re.compile(r"(\d+)([dsmhw]?)") - - for num, suffix in regexp.findall(interval): - if suffix == "d": - value += datetime.timedelta(int(num)) - elif suffix == "s": - value += datetime.timedelta(0, int(num)) - elif suffix == "m": - value += datetime.timedelta(0, 0, 0, 0, int(num)) - elif suffix == "h": - value += datetime.timedelta(0, 0, 0, 0, 0, int(num)) - elif suffix == "w": - value += datetime.timedelta(0, 0, 0, 0, 0, 0, int(num)) - elif suffix == "": - value += datetime.timedelta(0, 0, 0, int(num)) - return value - - -def check_option_syntax(parser, options): - """Apply additional restrictions on options, not expressible in -argparse""" - - def has_commands(options, commands=None): - if commands is None: - commands = (command.Enable, - command.Disable, - command.BumpTimeout, - command.StartChecker, - command.StopChecker, - command.IsEnabled, - command.Remove, - command.SetChecker, - command.SetTimeout, - command.SetExtendedTimeout, - command.SetInterval, - command.ApproveByDefault, - command.DenyByDefault, - command.SetApprovalDelay, - command.SetApprovalDuration, - command.SetHost, - command.SetSecret, - command.Approve, - command.Deny) - return any(isinstance(cmd, commands) - for cmd in options.commands) - - if has_commands(options) and not (options.client or options.all): - parser.error("Options require clients names or --all.") - if options.verbose and has_commands(options): - parser.error("--verbose can only be used alone.") - if (has_commands(options, (command.DumpJSON,)) - and (options.verbose or len(options.commands) > 1)): - parser.error("--dump-json can only be used alone.") - if options.all and not has_commands(options): - parser.error("--all requires an action.") - if (has_commands(options, (command.IsEnabled,)) - and len(options.client) > 1): - parser.error("--is-enabled requires exactly one client") - if (len(options.commands) > 1 - and has_commands(options, (command.Remove,)) - and not has_commands(options, (command.Deny,))): - parser.error("--remove can only be combined with --deny") - - -class dbus: - - class SystemBus: - - object_manager_iface = "org.freedesktop.DBus.ObjectManager" - def get_managed_objects(self, busname, objectpath): - return self.call_method("GetManagedObjects", busname, - objectpath, - self.object_manager_iface) - - properties_iface = "org.freedesktop.DBus.Properties" - def set_property(self, busname, objectpath, interface, key, - value): - self.call_method("Set", busname, objectpath, - self.properties_iface, interface, key, - value) - - - class MandosBus(SystemBus): - busname_domain = "se.recompile" - busname = busname_domain + ".Mandos" - server_path = "/" - server_interface = busname_domain + ".Mandos" - client_interface = busname_domain + ".Mandos.Client" - del busname_domain - - def get_clients_and_properties(self): - managed_objects = self.get_managed_objects( - self.busname, self.server_path) - return {objpath: properties[self.client_interface] - for objpath, properties in managed_objects.items() - if self.client_interface in properties} - - def set_client_property(self, objectpath, key, value): - return self.set_property(self.busname, objectpath, - self.client_interface, key, - value) - - def call_client_method(self, objectpath, method, *args): - return self.call_method(method, self.busname, objectpath, - self.client_interface, *args) - - def call_server_method(self, method, *args): - return self.call_method(method, self.busname, - self.server_path, - self.server_interface, *args) - - class Error(Exception): - pass - - class ConnectFailed(Error): - pass - - -class dbus_python_adapter: - - class SystemBus(dbus.MandosBus): - """Use dbus-python""" - - def __init__(self, module=dbus_python): - self.dbus_python = module - self.bus = self.dbus_python.SystemBus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except self.dbus_python.exceptions.DBusException as e: - # This does what "raise from" would do - exc = exception_class(*e.args) - exc.__cause__ = e - raise exc - - def call_method(self, methodname, busname, objectpath, - interface, *args): - proxy_object = self.get_object(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, - interface, methodname, - ", ".join(repr(a) for a in args)) - method = getattr(proxy_object, methodname) - with self.convert_exception(): - with dbus_python_adapter.SilenceLogger( - "dbus.proxies"): - value = method(*args, dbus_interface=interface) - return self.type_filter(value) - - def get_object(self, busname, objectpath): - log.debug("D-Bus: Connect to: (busname=%r, path=%r)", - busname, objectpath) - with self.convert_exception(dbus.ConnectFailed): - return self.bus.get_object(busname, objectpath) - - def type_filter(self, value): - """Convert the most bothersome types to Python types""" - if isinstance(value, self.dbus_python.Boolean): - return bool(value) - if isinstance(value, self.dbus_python.ObjectPath): - return str(value) - # Also recurse into dictionaries - if isinstance(value, self.dbus_python.Dictionary): - return {self.type_filter(key): - self.type_filter(subval) - for key, subval in value.items()} - return value - - def set_client_property(self, objectpath, key, value): - if key == "Secret": - if not isinstance(value, bytes): - value = value.encode("utf-8") - value = self.dbus_python.ByteArray(value) - return self.set_property(self.busname, objectpath, - self.client_interface, key, - value) - - class SilenceLogger: - "Simple context manager to silence a particular logger" - def __init__(self, loggername): - self.logger = logging.getLogger(loggername) - - def __enter__(self): - self.logger.addFilter(self.nullfilter) - - class NullFilter(logging.Filter): - def filter(self, record): - return False - - nullfilter = NullFilter() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.logger.removeFilter(self.nullfilter) - - - class CachingBus(SystemBus): - """A caching layer for dbus_python_adapter.SystemBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(dbus_python_adapter.CachingBus, - self).__init__(*args, **kwargs) - def get_object(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = super( - dbus_python_adapter.CachingBus, - self).get_object(busname, objectpath) - self.object_cache[(busname, objectpath)] = new_object - return new_object - - -class pydbus_adapter: - class SystemBus(dbus.MandosBus): - def __init__(self, module=pydbus): - self.pydbus = module - self.bus = self.pydbus.SystemBus() - - @contextlib.contextmanager - def convert_exception(self, exception_class=dbus.Error): - try: - yield - except gi.repository.GLib.Error as e: - # This does what "raise from" would do - exc = exception_class(*e.args) - exc.__cause__ = e - raise exc - - def call_method(self, methodname, busname, objectpath, - interface, *args): - proxy_object = self.get(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, - interface, methodname, - ", ".join(repr(a) for a in args)) - method = getattr(proxy_object[interface], methodname) - with self.convert_exception(): - return method(*args) - - def get(self, busname, objectpath): - log.debug("D-Bus: Connect to: (busname=%r, path=%r)", - busname, objectpath) - with self.convert_exception(dbus.ConnectFailed): - if sys.version_info.major <= 2: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", "", DeprecationWarning, - r"^xml\.etree\.ElementTree$") - return self.bus.get(busname, objectpath) - else: - return self.bus.get(busname, objectpath) - - def set_property(self, busname, objectpath, interface, key, - value): - proxy_object = self.get(busname, objectpath) - log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname, - objectpath, self.properties_iface, interface, - key, value) - setattr(proxy_object[interface], key, value) - - class CachingBus(SystemBus): - """A caching layer for pydbus_adapter.SystemBus""" - def __init__(self, *args, **kwargs): - self.object_cache = {} - super(pydbus_adapter.CachingBus, - self).__init__(*args, **kwargs) - def get(self, busname, objectpath): - try: - return self.object_cache[(busname, objectpath)] - except KeyError: - new_object = (super(pydbus_adapter.CachingBus, self) - .get(busname, objectpath)) - self.object_cache[(busname, objectpath)] = new_object - return new_object - - -def commands_from_options(options): - - commands = list(options.commands) - - def find_cmd(cmd, commands): - i = 0 - for i, c in enumerate(commands): - if isinstance(c, cmd): - return i - return i+1 - - # If command.Remove is present, move any instances of command.Deny - # to occur ahead of command.Remove. - index_of_remove = find_cmd(command.Remove, commands) - before_remove = commands[:index_of_remove] - after_remove = commands[index_of_remove:] - cleaned_after = [] - for cmd in after_remove: - if isinstance(cmd, command.Deny): - before_remove.append(cmd) - else: - cleaned_after.append(cmd) - if cleaned_after != after_remove: - commands = before_remove + cleaned_after - - # If no command option has been given, show table of clients, - # optionally verbosely - if not commands: - commands.append(command.PrintTable(verbose=options.verbose)) - - return commands - - -class command: - """A namespace for command classes""" - - class Base: - """Abstract base class for commands""" - def run(self, clients, bus=None): - """Normal commands should implement run_on_one_client(), -but commands which want to operate on all clients at the same time can -override this run() method instead. -""" - self.bus = bus - for client, properties in clients.items(): - self.run_on_one_client(client, properties) - - - class IsEnabled(Base): - def run(self, clients, bus=None): - properties = next(iter(clients.values())) - if properties["Enabled"]: - sys.exit(0) - sys.exit(1) - - - class Approve(Base): - def run_on_one_client(self, client, properties): - self.bus.call_client_method(client, "Approve", True) - - - class Deny(Base): - def run_on_one_client(self, client, properties): - self.bus.call_client_method(client, "Approve", False) - - - class Remove(Base): - def run(self, clients, bus): - for clientpath in frozenset(clients.keys()): - bus.call_server_method("RemoveClient", clientpath) - - - class Output(Base): - """Abstract class for commands outputting client details""" - all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK", - "Created", "Interval", "Host", "KeyID", - "Fingerprint", "CheckerRunning", - "LastEnabled", "ApprovalPending", - "ApprovedByDefault", "LastApprovalRequest", - "ApprovalDelay", "ApprovalDuration", - "Checker", "ExtendedTimeout", "Expires", - "LastCheckerStatus") - - - class DumpJSON(Output): - def run(self, clients, bus=None): - data = {properties["Name"]: - {key: properties[key] - for key in self.all_keywords} - for properties in clients.values()} - print(json.dumps(data, indent=4, separators=(',', ': '))) - - - class PrintTable(Output): - def __init__(self, verbose=False): - self.verbose = verbose - - def run(self, clients, bus=None): - default_keywords = ("Name", "Enabled", "Timeout", - "LastCheckedOK") - keywords = default_keywords - if self.verbose: - keywords = self.all_keywords - print(self.TableOfClients(clients.values(), keywords)) - - class TableOfClients: - tableheaders = { - "Name": "Name", - "Enabled": "Enabled", - "Timeout": "Timeout", - "LastCheckedOK": "Last Successful Check", - "LastApprovalRequest": "Last Approval Request", - "Created": "Created", - "Interval": "Interval", - "Host": "Host", - "Fingerprint": "Fingerprint", - "KeyID": "Key ID", - "CheckerRunning": "Check Is Running", - "LastEnabled": "Last Enabled", - "ApprovalPending": "Approval Is Pending", - "ApprovedByDefault": "Approved By Default", - "ApprovalDelay": "Approval Delay", - "ApprovalDuration": "Approval Duration", - "Checker": "Checker", - "ExtendedTimeout": "Extended Timeout", - "Expires": "Expires", - "LastCheckerStatus": "Last Checker Status", - } - - def __init__(self, clients, keywords): - self.clients = clients - self.keywords = keywords - - def __str__(self): - return "\n".join(self.rows()) - - if sys.version_info.major == 2: - __unicode__ = __str__ - def __str__(self): - return str(self).encode( - locale.getpreferredencoding()) - - def rows(self): - format_string = self.row_formatting_string() - rows = [self.header_line(format_string)] - rows.extend(self.client_line(client, format_string) - for client in self.clients) - return rows - - def row_formatting_string(self): - "Format string used to format table rows" - return " ".join("{{{key}:{width}}}".format( - width=max(len(self.tableheaders[key]), - *(len(self.string_from_client(client, - key)) - for client in self.clients)), - key=key) - for key in self.keywords) - - def string_from_client(self, client, key): - return self.valuetostring(client[key], key) - - @classmethod - def valuetostring(cls, value, keyword): - if isinstance(value, bool): - return "Yes" if value else "No" - if keyword in ("Timeout", "Interval", "ApprovalDelay", - "ApprovalDuration", "ExtendedTimeout"): - return cls.milliseconds_to_string(value) - return str(value) - - def header_line(self, format_string): - return format_string.format(**self.tableheaders) - - def client_line(self, client, format_string): - return format_string.format( - **{key: self.string_from_client(client, key) - for key in self.keywords}) - - @staticmethod - def milliseconds_to_string(ms): - td = datetime.timedelta(0, 0, 0, ms) - return ("{days}{hours:02}:{minutes:02}:{seconds:02}" - .format(days="{}T".format(td.days) - if td.days else "", - hours=td.seconds // 3600, - minutes=(td.seconds % 3600) // 60, - seconds=td.seconds % 60)) - - - class PropertySetter(Base): - "Abstract class for Actions for setting one client property" - - def run_on_one_client(self, client, properties=None): - """Set the Client's D-Bus property""" - self.bus.set_client_property(client, self.propname, - self.value_to_set) - - @property - def propname(self): - raise NotImplementedError() - - - class Enable(PropertySetter): - propname = "Enabled" - value_to_set = True - - - class Disable(PropertySetter): - propname = "Enabled" - value_to_set = False - - - class BumpTimeout(PropertySetter): - propname = "LastCheckedOK" - value_to_set = "" - - - class StartChecker(PropertySetter): - propname = "CheckerRunning" - value_to_set = True - - - class StopChecker(PropertySetter): - propname = "CheckerRunning" - value_to_set = False - - - class ApproveByDefault(PropertySetter): - propname = "ApprovedByDefault" - value_to_set = True - - - class DenyByDefault(PropertySetter): - propname = "ApprovedByDefault" - value_to_set = False - - - class PropertySetterValue(PropertySetter): - """Abstract class for PropertySetter recieving a value as -constructor argument instead of a class attribute.""" - def __init__(self, value): - self.value_to_set = value - - @classmethod - def argparse(cls, argtype): - def cmdtype(arg): - return cls(argtype(arg)) - return cmdtype - - class SetChecker(PropertySetterValue): - propname = "Checker" - - - class SetHost(PropertySetterValue): - propname = "Host" - - - class SetSecret(PropertySetterValue): - propname = "Secret" - - @property - def value_to_set(self): - return self._vts - - @value_to_set.setter - def value_to_set(self, value): - """When setting, read data from supplied file object""" - self._vts = value.read() - value.close() - - - class PropertySetterValueMilliseconds(PropertySetterValue): - """Abstract class for PropertySetterValue taking a value -argument as a datetime.timedelta() but should store it as -milliseconds.""" - - @property - def value_to_set(self): - return self._vts - - @value_to_set.setter - def value_to_set(self, value): - "When setting, convert value from a datetime.timedelta" - self._vts = int(round(value.total_seconds() * 1000)) - - - class SetTimeout(PropertySetterValueMilliseconds): - propname = "Timeout" - - - class SetExtendedTimeout(PropertySetterValueMilliseconds): - propname = "ExtendedTimeout" - - - class SetInterval(PropertySetterValueMilliseconds): - propname = "Interval" - - - class SetApprovalDelay(PropertySetterValueMilliseconds): - propname = "ApprovalDelay" - - - class SetApprovalDuration(PropertySetterValueMilliseconds): - propname = "ApprovalDuration" - - - -class TestCaseWithAssertLogs(unittest.TestCase): - """unittest.TestCase.assertLogs only exists in Python 3.4""" - - if not hasattr(unittest.TestCase, "assertLogs"): - @contextlib.contextmanager - def assertLogs(self, logger, level=logging.INFO): - capturing_handler = self.CapturingLevelHandler(level) - old_level = logger.level - old_propagate = logger.propagate - logger.addHandler(capturing_handler) - logger.setLevel(level) - logger.propagate = False - try: - yield capturing_handler.watcher - finally: - logger.propagate = old_propagate - logger.removeHandler(capturing_handler) - logger.setLevel(old_level) - self.assertGreater(len(capturing_handler.watcher.records), - 0) - - class CapturingLevelHandler(logging.Handler): - def __init__(self, level, *args, **kwargs): - logging.Handler.__init__(self, *args, **kwargs) - self.watcher = self.LoggingWatcher([], []) - def emit(self, record): - self.watcher.records.append(record) - self.watcher.output.append(self.format(record)) - - LoggingWatcher = collections.namedtuple("LoggingWatcher", - ("records", - "output")) - - -class Unique: - """Class for objects which exist only to be unique objects, since -unittest.mock.sentinel only exists in Python 3.3""" - - -class Test_string_to_delta(TestCaseWithAssertLogs): - # Just test basic RFC 3339 functionality here, the doc string for - # rfc3339_duration_to_delta() already has more comprehensive - # tests, which are run by doctest. - - def test_rfc3339_zero_seconds(self): - self.assertEqual(datetime.timedelta(), - string_to_delta("PT0S")) - - def test_rfc3339_zero_days(self): - self.assertEqual(datetime.timedelta(), string_to_delta("P0D")) - - def test_rfc3339_one_second(self): - self.assertEqual(datetime.timedelta(0, 1), - string_to_delta("PT1S")) - - def test_rfc3339_two_hours(self): - self.assertEqual(datetime.timedelta(0, 7200), - string_to_delta("PT2H")) - - def test_falls_back_to_pre_1_6_1_with_warning(self): - with self.assertLogs(log, logging.WARNING): - value = string_to_delta("2h") - self.assertEqual(datetime.timedelta(0, 7200), value) - - -class Test_check_option_syntax(unittest.TestCase): - def setUp(self): - self.parser = argparse.ArgumentParser() - add_command_line_options(self.parser) - - def test_actions_requires_client_or_all(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value) - with self.assertParseError(): - self.parse_args(args) - - # This mostly corresponds to the definition from has_commands() in - # check_option_syntax() - actions = { - "--enable": None, - "--disable": None, - "--bump-timeout": None, - "--start-checker": None, - "--stop-checker": None, - "--is-enabled": None, - "--remove": None, - "--checker": "x", - "--timeout": "PT0S", - "--extended-timeout": "PT0S", - "--interval": "PT0S", - "--approve-by-default": None, - "--deny-by-default": None, - "--approval-delay": "PT0S", - "--approval-duration": "PT0S", - "--host": "hostname", - "--secret": "/dev/null", - "--approve": None, - "--deny": None, - } - - @staticmethod - def actionargs(action, value, *args): - if value is not None: - return [action, value] + list(args) - else: - return [action] + list(args) - - @contextlib.contextmanager - def assertParseError(self): - with self.assertRaises(SystemExit) as e: - with self.redirect_stderr_to_devnull(): - yield - # Exit code from argparse is guaranteed to be "2". Reference: - # https://docs.python.org/3/library - # /argparse.html#exiting-methods - self.assertEqual(2, e.exception.code) - - def parse_args(self, args): - options = self.parser.parse_args(args) - check_option_syntax(self.parser, options) - - @staticmethod - @contextlib.contextmanager - def redirect_stderr_to_devnull(): - old_stderr = sys.stderr - with contextlib.closing(open(os.devnull, "w")) as null: - sys.stderr = null - try: - yield - finally: - sys.stderr = old_stderr - - def check_option_syntax(self, options): - check_option_syntax(self.parser, options) - - def test_actions_all_conflicts_with_verbose(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--all", - "--verbose") - with self.assertParseError(): - self.parse_args(args) - - def test_actions_with_client_conflicts_with_verbose(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--verbose", - "client") - with self.assertParseError(): - self.parse_args(args) - - def test_dump_json_conflicts_with_verbose(self): - args = ["--dump-json", "--verbose"] - with self.assertParseError(): - self.parse_args(args) - - def test_dump_json_conflicts_with_action(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--dump-json") - with self.assertParseError(): - self.parse_args(args) - - def test_all_can_not_be_alone(self): - args = ["--all"] - with self.assertParseError(): - self.parse_args(args) - - def test_all_is_ok_with_any_action(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "--all") - self.parse_args(args) - - def test_any_action_is_ok_with_one_client(self): - for action, value in self.actions.items(): - args = self.actionargs(action, value, "client") - self.parse_args(args) - - def test_one_client_with_all_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client") - self.parse_args(args) - - def test_two_clients_with_all_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client1", - "client2") - self.parse_args(args) - - def test_two_clients_are_ok_with_actions_except_is_enabled(self): - for action, value in self.actions.items(): - if action == "--is-enabled": - continue - args = self.actionargs(action, value, "client1", - "client2") - self.parse_args(args) - - def test_is_enabled_fails_without_client(self): - args = ["--is-enabled"] - with self.assertParseError(): - self.parse_args(args) - - def test_is_enabled_fails_with_two_clients(self): - args = ["--is-enabled", "client1", "client2"] - with self.assertParseError(): - self.parse_args(args) - - def test_remove_can_only_be_combined_with_action_deny(self): - for action, value in self.actions.items(): - if action in {"--remove", "--deny"}: - continue - args = self.actionargs(action, value, "--all", - "--remove") - with self.assertParseError(): - self.parse_args(args) - - -class Test_dbus_exceptions(unittest.TestCase): - - def test_dbus_ConnectFailed_is_Error(self): - with self.assertRaises(dbus.Error): - raise dbus.ConnectFailed() - - -class Test_dbus_MandosBus(unittest.TestCase): - - class MockMandosBus(dbus.MandosBus): - def __init__(self): - self._name = "se.recompile.Mandos" - self._server_path = "/" - self._server_interface = "se.recompile.Mandos" - self._client_interface = "se.recompile.Mandos.Client" - self.calls = [] - self.call_method_return = Unique() - - def call_method(self, methodname, busname, objectpath, - interface, *args): - self.calls.append((methodname, busname, objectpath, - interface, args)) - return self.call_method_return - - def setUp(self): - self.bus = self.MockMandosBus() - - def test_set_client_property(self): - self.bus.set_client_property("objectpath", "key", "value") - expected_call = ("Set", self.bus._name, "objectpath", - "org.freedesktop.DBus.Properties", - (self.bus._client_interface, "key", "value")) - self.assertIn(expected_call, self.bus.calls) - - def test_call_client_method(self): - ret = self.bus.call_client_method("objectpath", "methodname") - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, "objectpath", - self.bus._client_interface, ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_client_method_with_args(self): - args = (Unique(), Unique()) - ret = self.bus.call_client_method("objectpath", "methodname", - *args) - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, "objectpath", - self.bus._client_interface, - (args[0], args[1])) - self.assertIn(expected_call, self.bus.calls) - - def test_get_clients_and_properties(self): - managed_objects = { - "objectpath": { - self.bus._client_interface: { - "key": "value", - "bool": True, - }, - "irrelevant_interface": { - "key": "othervalue", - "bool": False, - }, - }, - "other_objectpath": { - "other_irrelevant_interface": { - "key": "value 3", - "bool": None, - }, - }, - } - expected_clients_and_properties = { - "objectpath": { - "key": "value", - "bool": True, - } - } - self.bus.call_method_return = managed_objects - ret = self.bus.get_clients_and_properties() - self.assertDictEqual(expected_clients_and_properties, ret) - expected_call = ("GetManagedObjects", self.bus._name, - self.bus._server_path, - "org.freedesktop.DBus.ObjectManager", ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_server_method(self): - ret = self.bus.call_server_method("methodname") - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, - self.bus._server_path, - self.bus._server_interface, ()) - self.assertIn(expected_call, self.bus.calls) - - def test_call_server_method_with_args(self): - args = (Unique(), Unique()) - ret = self.bus.call_server_method("methodname", *args) - self.assertIs(self.bus.call_method_return, ret) - expected_call = ("methodname", self.bus._name, - self.bus._server_path, - self.bus._server_interface, - (args[0], args[1])) - self.assertIn(expected_call, self.bus.calls) - - -class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs): - - def MockDBusPython_func(self, func): - class mock_dbus_python: - """mock dbus-python module""" - class exceptions: - """Pseudo-namespace""" - class DBusException(Exception): - pass - class SystemBus: - @staticmethod - def get_object(busname, objectpath): - DBusObject = collections.namedtuple( - "DBusObject", ("methodname", "Set")) - def method(*args, **kwargs): - self.assertEqual({"dbus_interface": - "interface"}, - kwargs) - return func(*args) - def set_property(interface, key, value, - dbus_interface=None): - self.assertEqual( - "org.freedesktop.DBus.Properties", - dbus_interface) - self.assertEqual("Secret", key) - return func(interface, key, value, - dbus_interface=dbus_interface) - return DBusObject(methodname=method, - Set=set_property) - class Boolean: - def __init__(self, value): - self.value = bool(value) - def __bool__(self): - return self.value - if sys.version_info.major == 2: - __nonzero__ = __bool__ - class ObjectPath(str): - pass - class Dictionary(dict): - pass - class ByteArray(bytes): - pass - return mock_dbus_python - - def call_method(self, bus, methodname, busname, objectpath, - interface, *args): - with self.assertLogs(log, logging.DEBUG): - return bus.call_method(methodname, busname, objectpath, - interface, *args) - - def test_call_method_returns(self): - expected_method_return = Unique() - method_args = (Unique(), Unique()) - def func(*args): - self.assertEqual(len(method_args), len(args)) - for marg, arg in zip(method_args, args): - self.assertIs(marg, arg) - return expected_method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_filters_bool_true(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Boolean(True) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertTrue(ret) - self.assertNotIsInstance(ret, mock_dbus_python.Boolean) - - def test_call_method_filters_bool_false(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Boolean(False) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertFalse(ret) - self.assertNotIsInstance(ret, mock_dbus_python.Boolean) - - def test_call_method_filters_objectpath(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.ObjectPath("objectpath") - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - self.assertEqual("objectpath", ret) - self.assertIsNot("objectpath", ret) - self.assertNotIsInstance(ret, mock_dbus_python.ObjectPath) - - def test_call_method_filters_booleans_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {mock_dbus_python.Boolean(True): - mock_dbus_python.Boolean(False), - mock_dbus_python.Boolean(False): - mock_dbus_python.Boolean(True)}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {True: False, - False: True} - self.assertEqual(expected_method_return, ret) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - - def test_call_method_filters_objectpaths_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {mock_dbus_python.ObjectPath("objectpath_key_1"): - mock_dbus_python.ObjectPath("objectpath_value_1"), - mock_dbus_python.ObjectPath("objectpath_key_2"): - mock_dbus_python.ObjectPath("objectpath_value_2")}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {str(key): str(value) - for key, value in - method_return.items()} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - - def test_call_method_filters_dict_in_dict(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {"key1": mock_dbus_python.Dictionary({"key11": "value11", - "key12": "value12"}), - "key2": mock_dbus_python.Dictionary({"key21": "value21", - "key22": "value22"})}) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = { - "key1": {"key11": "value11", - "key12": "value12"}, - "key2": {"key21": "value21", - "key22": "value22"}, - } - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - for key, value in ret.items(): - self.assertIsInstance(value, dict) - self.assertEqual(expected_method_return[key], value) - self.assertNotIsInstance(value, - mock_dbus_python.Dictionary) - - def test_call_method_filters_dict_three_deep(self): - def func(): - return method_return - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - method_return = mock_dbus_python.Dictionary( - {"key1": - mock_dbus_python.Dictionary( - {"key2": - mock_dbus_python.Dictionary( - {"key3": - mock_dbus_python.Boolean(True), - }), - }), - }) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - expected_method_return = {"key1": {"key2": {"key3": True}}} - self.assertEqual(expected_method_return, ret) - self.assertIsInstance(ret, dict) - self.assertNotIsInstance(ret, mock_dbus_python.Dictionary) - self.assertIsInstance(ret["key1"], dict) - self.assertNotIsInstance(ret["key1"], - mock_dbus_python.Dictionary) - self.assertIsInstance(ret["key1"]["key2"], dict) - self.assertNotIsInstance(ret["key1"]["key2"], - mock_dbus_python.Dictionary) - self.assertTrue(ret["key1"]["key2"]["key3"]) - self.assertNotIsInstance(ret["key1"]["key2"]["key3"], - mock_dbus_python.Boolean) - - def test_call_method_handles_exception(self): - dbus_logger = logging.getLogger("dbus.proxies") - - def func(): - dbus_logger.error("Test") - raise mock_dbus_python.exceptions.DBusException() - - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - - class CountingHandler(logging.Handler): - count = 0 - def emit(self, record): - self.count += 1 - - counting_handler = CountingHandler() - - dbus_logger.addHandler(counting_handler) - - try: - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - finally: - dbus_logger.removeFilter(counting_handler) - - self.assertNotIsInstance(e, dbus.ConnectFailed) - - # Make sure the dbus logger was suppressed - self.assertEqual(0, counting_handler.count) - - def test_Set_Secret_sends_bytearray(self): - ret = [None] - def func(*args, **kwargs): - ret[0] = (args, kwargs) - mock_dbus_python = self.MockDBusPython_func(func) - bus = dbus_python_adapter.SystemBus(mock_dbus_python) - bus.set_client_property("objectpath", "Secret", "value") - expected_call = (("se.recompile.Mandos.Client", "Secret", - mock_dbus_python.ByteArray(b"value")), - {"dbus_interface": - "org.freedesktop.DBus.Properties"}) - self.assertEqual(expected_call, ret[0]) - if sys.version_info.major == 2: - self.assertIsInstance(ret[0][0][-1], - mock_dbus_python.ByteArray) - - def test_get_object_converts_to_correct_exception(self): - bus = dbus_python_adapter.SystemBus( - self.fake_dbus_python_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - class fake_dbus_python_raises_exception_on_connect: - """fake dbus-python module""" - class exceptions: - """Pseudo-namespace""" - class DBusException(Exception): - pass - - @classmethod - def SystemBus(cls): - def get_object(busname, objectpath): - raise cls.exceptions.DBusException() - Bus = collections.namedtuple("Bus", ["get_object"]) - return Bus(get_object=get_object) - - -class Test_dbus_python_adapter_CachingBus(unittest.TestCase): - class mock_dbus_python: - """mock dbus-python modules""" - class SystemBus: - @staticmethod - def get_object(busname, objectpath): - return Unique() - - def setUp(self): - self.bus = dbus_python_adapter.CachingBus( - self.mock_dbus_python) - - def test_returns_distinct_objectpaths(self): - obj1 = self.bus.get_object("busname", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname", "objectpath2") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_busnames(self): - obj1 = self.bus.get_object("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_both(self): - obj1 = self.bus.get_object("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_same(self): - obj1 = self.bus.get_object("busname", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIs(obj1, obj2) - - def test_returns_same_old(self): - obj1 = self.bus.get_object("busname1", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get_object("busname2", "objectpath2") - self.assertIsInstance(obj2, Unique) - obj1b = self.bus.get_object("busname1", "objectpath1") - self.assertIsInstance(obj1b, Unique) - self.assertIsNot(obj1, obj2) - self.assertIsNot(obj2, obj1b) - self.assertIs(obj1, obj1b) - - -class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs): - - def Stub_pydbus_func(self, func): - class stub_pydbus: - """stub pydbus module""" - class SystemBus: - @staticmethod - def get(busname, objectpath): - DBusObject = collections.namedtuple( - "DBusObject", ("methodname",)) - return {"interface": - DBusObject(methodname=func)} - return stub_pydbus - - def call_method(self, bus, methodname, busname, objectpath, - interface, *args): - with self.assertLogs(log, logging.DEBUG): - return bus.call_method(methodname, busname, objectpath, - interface, *args) - - def test_call_method_returns(self): - expected_method_return = Unique() - method_args = (Unique(), Unique()) - def func(*args): - self.assertEqual(len(method_args), len(args)) - for marg, arg in zip(method_args, args): - self.assertIs(marg, arg) - return expected_method_return - stub_pydbus = self.Stub_pydbus_func(func) - bus = pydbus_adapter.SystemBus(stub_pydbus) - ret = self.call_method(bus, "methodname", "busname", - "objectpath", "interface", - *method_args) - self.assertIs(ret, expected_method_return) - - def test_call_method_handles_exception(self): - dbus_logger = logging.getLogger("dbus.proxies") - - def func(): - raise gi.repository.GLib.Error() - - stub_pydbus = self.Stub_pydbus_func(func) - bus = pydbus_adapter.SystemBus(stub_pydbus) - - with self.assertRaises(dbus.Error) as e: - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - self.assertNotIsInstance(e, dbus.ConnectFailed) - - def test_get_converts_to_correct_exception(self): - bus = pydbus_adapter.SystemBus( - self.fake_pydbus_raises_exception_on_connect) - with self.assertRaises(dbus.ConnectFailed): - self.call_method(bus, "methodname", "busname", - "objectpath", "interface") - - class fake_pydbus_raises_exception_on_connect: - """fake dbus-python module""" - @classmethod - def SystemBus(cls): - def get(busname, objectpath): - raise gi.repository.GLib.Error() - Bus = collections.namedtuple("Bus", ["get"]) - return Bus(get=get) - - def test_set_property_uses_setattr(self): - class Object: - pass - obj = Object() - class pydbus_spy: - class SystemBus: - @staticmethod - def get(busname, objectpath): - return {"interface": obj} - bus = pydbus_adapter.SystemBus(pydbus_spy) - value = Unique() - bus.set_property("busname", "objectpath", "interface", "key", - value) - self.assertIs(value, obj.key) - - def test_get_suppresses_xml_deprecation_warning(self): - if sys.version_info.major >= 3: - return - class stub_pydbus_get: - class SystemBus: - @staticmethod - def get(busname, objectpath): - warnings.warn_explicit( - "deprecated", DeprecationWarning, - "xml.etree.ElementTree", 0) - bus = pydbus_adapter.SystemBus(stub_pydbus_get) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - bus.get("busname", "objectpath") - self.assertEqual(0, len(w)) - - -class Test_pydbus_adapter_CachingBus(unittest.TestCase): - class stub_pydbus: - """stub pydbus module""" - class SystemBus: - @staticmethod - def get(busname, objectpath): - return Unique() - - def setUp(self): - self.bus = pydbus_adapter.CachingBus(self.stub_pydbus) - - def test_returns_distinct_objectpaths(self): - obj1 = self.bus.get("busname", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname", "objectpath2") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_busnames(self): - obj1 = self.bus.get("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_distinct_both(self): - obj1 = self.bus.get("busname1", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIsNot(obj1, obj2) - - def test_returns_same(self): - obj1 = self.bus.get("busname", "objectpath") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname", "objectpath") - self.assertIsInstance(obj2, Unique) - self.assertIs(obj1, obj2) - - def test_returns_same_old(self): - obj1 = self.bus.get("busname1", "objectpath1") - self.assertIsInstance(obj1, Unique) - obj2 = self.bus.get("busname2", "objectpath2") - self.assertIsInstance(obj2, Unique) - obj1b = self.bus.get("busname1", "objectpath1") - self.assertIsInstance(obj1b, Unique) - self.assertIsNot(obj1, obj2) - self.assertIsNot(obj2, obj1b) - self.assertIs(obj1, obj1b) - - -class Test_commands_from_options(unittest.TestCase): - - def setUp(self): - self.parser = argparse.ArgumentParser() - add_command_line_options(self.parser) - - def test_is_enabled(self): - self.assert_command_from_args(["--is-enabled", "client"], - command.IsEnabled) - - def assert_command_from_args(self, args, command_cls, length=1, - clients=None, **cmd_attrs): - """Assert that parsing ARGS should result in an instance of -COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS).""" - options = self.parser.parse_args(args) - check_option_syntax(self.parser, options) - commands = commands_from_options(options) - self.assertEqual(length, len(commands)) - for command in commands: - if isinstance(command, command_cls): - break - else: - self.assertIsInstance(command, command_cls) - if clients is not None: - self.assertEqual(clients, options.client) - for key, value in cmd_attrs.items(): - self.assertEqual(value, getattr(command, key)) - - def assert_commands_from_args(self, args, commands, clients=None): - for cmd in commands: - self.assert_command_from_args(args, cmd, - length=len(commands), - clients=clients) - - def test_is_enabled_short(self): - self.assert_command_from_args(["-V", "client"], - command.IsEnabled) - - def test_approve(self): - self.assert_command_from_args(["--approve", "client"], - command.Approve) - - def test_approve_short(self): - self.assert_command_from_args(["-A", "client"], - command.Approve) - - def test_deny(self): - self.assert_command_from_args(["--deny", "client"], - command.Deny) - - def test_deny_short(self): - self.assert_command_from_args(["-D", "client"], command.Deny) - - def test_remove(self): - self.assert_command_from_args(["--remove", "client"], - command.Remove) - - def test_deny_before_remove(self): - options = self.parser.parse_args(["--deny", "--remove", - "client"]) - check_option_syntax(self.parser, options) - commands = commands_from_options(options) - self.assertEqual(2, len(commands)) - self.assertIsInstance(commands[0], command.Deny) - self.assertIsInstance(commands[1], command.Remove) - - def test_deny_before_remove_reversed(self): - options = self.parser.parse_args(["--remove", "--deny", - "--all"]) - check_option_syntax(self.parser, options) - commands = commands_from_options(options) - self.assertEqual(2, len(commands)) - self.assertIsInstance(commands[0], command.Deny) - self.assertIsInstance(commands[1], command.Remove) - - def test_remove_short(self): - self.assert_command_from_args(["-r", "client"], - command.Remove) - - def test_dump_json(self): - self.assert_command_from_args(["--dump-json"], - command.DumpJSON) - - def test_enable(self): - self.assert_command_from_args(["--enable", "client"], - command.Enable) - - def test_enable_short(self): - self.assert_command_from_args(["-e", "client"], - command.Enable) - - def test_disable(self): - self.assert_command_from_args(["--disable", "client"], - command.Disable) - - def test_disable_short(self): - self.assert_command_from_args(["-d", "client"], - command.Disable) - - def test_bump_timeout(self): - self.assert_command_from_args(["--bump-timeout", "client"], - command.BumpTimeout) - - def test_bump_timeout_short(self): - self.assert_command_from_args(["-b", "client"], - command.BumpTimeout) - - def test_start_checker(self): - self.assert_command_from_args(["--start-checker", "client"], - command.StartChecker) - - def test_stop_checker(self): - self.assert_command_from_args(["--stop-checker", "client"], - command.StopChecker) - - def test_approve_by_default(self): - self.assert_command_from_args(["--approve-by-default", - "client"], - command.ApproveByDefault) - - def test_deny_by_default(self): - self.assert_command_from_args(["--deny-by-default", "client"], - command.DenyByDefault) - - def test_checker(self): - self.assert_command_from_args(["--checker", ":", "client"], - command.SetChecker, - value_to_set=":") - - def test_checker_empty(self): - self.assert_command_from_args(["--checker", "", "client"], - command.SetChecker, - value_to_set="") - - def test_checker_short(self): - self.assert_command_from_args(["-c", ":", "client"], - command.SetChecker, - value_to_set=":") - - def test_host(self): - self.assert_command_from_args( - ["--host", "client.example.org", "client"], - command.SetHost, value_to_set="client.example.org") - - def test_host_short(self): - self.assert_command_from_args( - ["-H", "client.example.org", "client"], command.SetHost, - value_to_set="client.example.org") - - def test_secret_devnull(self): - self.assert_command_from_args(["--secret", os.path.devnull, - "client"], command.SetSecret, - value_to_set=b"") - - def test_secret_tempfile(self): - with tempfile.NamedTemporaryFile(mode="r+b") as f: - value = b"secret\0xyzzy\nbar" - f.write(value) - f.seek(0) - self.assert_command_from_args(["--secret", f.name, - "client"], - command.SetSecret, - value_to_set=value) - - def test_secret_devnull_short(self): - self.assert_command_from_args(["-s", os.path.devnull, - "client"], command.SetSecret, - value_to_set=b"") - - def test_secret_tempfile_short(self): - with tempfile.NamedTemporaryFile(mode="r+b") as f: - value = b"secret\0xyzzy\nbar" - f.write(value) - f.seek(0) - self.assert_command_from_args(["-s", f.name, "client"], - command.SetSecret, - value_to_set=value) - - def test_timeout(self): - self.assert_command_from_args(["--timeout", "PT5M", "client"], - command.SetTimeout, - value_to_set=300000) - - def test_timeout_short(self): - self.assert_command_from_args(["-t", "PT5M", "client"], - command.SetTimeout, - value_to_set=300000) - - def test_extended_timeout(self): - self.assert_command_from_args(["--extended-timeout", "PT15M", - "client"], - command.SetExtendedTimeout, - value_to_set=900000) - - def test_interval(self): - self.assert_command_from_args(["--interval", "PT2M", - "client"], command.SetInterval, - value_to_set=120000) - - def test_interval_short(self): - self.assert_command_from_args(["-i", "PT2M", "client"], - command.SetInterval, - value_to_set=120000) - - def test_approval_delay(self): - self.assert_command_from_args(["--approval-delay", "PT30S", - "client"], - command.SetApprovalDelay, - value_to_set=30000) - - def test_approval_duration(self): - self.assert_command_from_args(["--approval-duration", "PT1S", - "client"], - command.SetApprovalDuration, - value_to_set=1000) - - def test_print_table(self): - self.assert_command_from_args([], command.PrintTable, - verbose=False) - - def test_print_table_verbose(self): - self.assert_command_from_args(["--verbose"], - command.PrintTable, - verbose=True) - - def test_print_table_verbose_short(self): - self.assert_command_from_args(["-v"], command.PrintTable, - verbose=True) - - - def test_manual_page_example_1(self): - self.assert_command_from_args("", - command.PrintTable, - clients=[], - verbose=False) - - def test_manual_page_example_2(self): - self.assert_command_from_args( - "--verbose foo1.example.org foo2.example.org".split(), - command.PrintTable, clients=["foo1.example.org", - "foo2.example.org"], - verbose=True) - - def test_manual_page_example_3(self): - self.assert_command_from_args("--enable --all".split(), - command.Enable, - clients=[]) - - def test_manual_page_example_4(self): - self.assert_commands_from_args( - ("--timeout=PT5M --interval=PT1M foo1.example.org" - " foo2.example.org").split(), - [command.SetTimeout, command.SetInterval], - clients=["foo1.example.org", "foo2.example.org"]) - - def test_manual_page_example_5(self): - self.assert_command_from_args("--approve --all".split(), - command.Approve, - clients=[]) - - -class TestCommand(unittest.TestCase): - """Abstract class for tests of command classes""" - - class FakeMandosBus(dbus.MandosBus): - def __init__(self, testcase): - self.client_properties = { - "Name": "foo", - "KeyID": ("92ed150794387c03ce684574b1139a65" - "94a34f895daaaf09fd8ea90a27cddb12"), - "Secret": b"secret", - "Host": "foo.example.org", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-03T00:00:00", - "Created": "2019-01-02T00:00:00", - "Interval": 120000, - "Fingerprint": ("778827225BA7DE539C5A" - "7CFA59CFF7CDBD9A5920"), - "CheckerRunning": False, - "LastEnabled": "2019-01-03T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": True, - "LastApprovalRequest": "", - "ApprovalDelay": 0, - "ApprovalDuration": 1000, - "Checker": "fping -q -- %(host)s", - "ExtendedTimeout": 900000, - "Expires": "2019-02-04T00:00:00", - "LastCheckerStatus": 0, - } - self.other_client_properties = { - "Name": "barbar", - "KeyID": ("0558568eedd67d622f5c83b35a115f79" - "6ab612cff5ad227247e46c2b020f441c"), - "Secret": b"secretbar", - "Host": "192.0.2.3", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-04T00:00:00", - "Created": "2019-01-03T00:00:00", - "Interval": 120000, - "Fingerprint": ("3E393AEAEFB84C7E89E2" - "F547B3A107558FCA3A27"), - "CheckerRunning": True, - "LastEnabled": "2019-01-04T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": False, - "LastApprovalRequest": "2019-01-03T00:00:00", - "ApprovalDelay": 30000, - "ApprovalDuration": 93785000, - "Checker": ":", - "ExtendedTimeout": 900000, - "Expires": "2019-02-05T00:00:00", - "LastCheckerStatus": -2, - } - self.clients = collections.OrderedDict( - [ - ("client_objectpath", self.client_properties), - ("other_client_objectpath", - self.other_client_properties), - ]) - self.one_client = {"client_objectpath": - self.client_properties} - self.testcase = testcase - self.calls = [] - - def call_method(self, methodname, busname, objectpath, - interface, *args): - self.testcase.assertEqual("se.recompile.Mandos", busname) - self.calls.append((methodname, busname, objectpath, - interface, args)) - if interface == "org.freedesktop.DBus.Properties": - if methodname == "Set": - self.testcase.assertEqual(3, len(args)) - interface, key, value = args - self.testcase.assertEqual( - "se.recompile.Mandos.Client", interface) - self.clients[objectpath][key] = value - return - elif interface == "se.recompile.Mandos": - self.testcase.assertEqual("RemoveClient", methodname) - self.testcase.assertEqual(1, len(args)) - clientpath = args[0] - del self.clients[clientpath] - return - elif interface == "se.recompile.Mandos.Client": - if methodname == "Approve": - self.testcase.assertEqual(1, len(args)) - return - raise ValueError() - - def setUp(self): - self.bus = self.FakeMandosBus(self) - - -class TestBaseCommands(TestCommand): - - def test_IsEnabled_exits_successfully(self): - with self.assertRaises(SystemExit) as e: - command.IsEnabled().run(self.bus.one_client) - if e.exception.code is not None: - self.assertEqual(0, e.exception.code) - else: - self.assertIsNone(e.exception.code) - - def test_IsEnabled_exits_with_failure(self): - self.bus.client_properties["Enabled"] = False - with self.assertRaises(SystemExit) as e: - command.IsEnabled().run(self.bus.one_client) - if isinstance(e.exception.code, int): - self.assertNotEqual(0, e.exception.code) - else: - self.assertIsNotNone(e.exception.code) - - def test_Approve(self): - busname = "se.recompile.Mandos" - client_interface = "se.recompile.Mandos.Client" - command.Approve().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (True,)), self.bus.calls) - - def test_Deny(self): - busname = "se.recompile.Mandos" - client_interface = "se.recompile.Mandos.Client" - command.Deny().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("Approve", busname, clientpath, - client_interface, (False,)), - self.bus.calls) - - def test_Remove(self): - command.Remove().run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - self.assertIn(("RemoveClient", dbus_busname, - dbus_server_path, dbus_server_interface, - (clientpath,)), self.bus.calls) - - expected_json = { - "foo": { - "Name": "foo", - "KeyID": ("92ed150794387c03ce684574b1139a65" - "94a34f895daaaf09fd8ea90a27cddb12"), - "Host": "foo.example.org", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-03T00:00:00", - "Created": "2019-01-02T00:00:00", - "Interval": 120000, - "Fingerprint": ("778827225BA7DE539C5A" - "7CFA59CFF7CDBD9A5920"), - "CheckerRunning": False, - "LastEnabled": "2019-01-03T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": True, - "LastApprovalRequest": "", - "ApprovalDelay": 0, - "ApprovalDuration": 1000, - "Checker": "fping -q -- %(host)s", - "ExtendedTimeout": 900000, - "Expires": "2019-02-04T00:00:00", - "LastCheckerStatus": 0, - }, - "barbar": { - "Name": "barbar", - "KeyID": ("0558568eedd67d622f5c83b35a115f79" - "6ab612cff5ad227247e46c2b020f441c"), - "Host": "192.0.2.3", - "Enabled": True, - "Timeout": 300000, - "LastCheckedOK": "2019-02-04T00:00:00", - "Created": "2019-01-03T00:00:00", - "Interval": 120000, - "Fingerprint": ("3E393AEAEFB84C7E89E2" - "F547B3A107558FCA3A27"), - "CheckerRunning": True, - "LastEnabled": "2019-01-04T00:00:00", - "ApprovalPending": False, - "ApprovedByDefault": False, - "LastApprovalRequest": "2019-01-03T00:00:00", - "ApprovalDelay": 30000, - "ApprovalDuration": 93785000, - "Checker": ":", - "ExtendedTimeout": 900000, - "Expires": "2019-02-05T00:00:00", - "LastCheckerStatus": -2, - }, - } - - def test_DumpJSON_normal(self): - with self.capture_stdout_to_buffer() as buffer: - command.DumpJSON().run(self.bus.clients) - json_data = json.loads(buffer.getvalue()) - self.assertDictEqual(self.expected_json, json_data) - - @staticmethod - @contextlib.contextmanager - def capture_stdout_to_buffer(): - capture_buffer = io.StringIO() - old_stdout = sys.stdout - sys.stdout = capture_buffer - try: - yield capture_buffer - finally: - sys.stdout = old_stdout - - def test_DumpJSON_one_client(self): - with self.capture_stdout_to_buffer() as buffer: - command.DumpJSON().run(self.bus.one_client) - json_data = json.loads(buffer.getvalue()) - expected_json = {"foo": self.expected_json["foo"]} - self.assertDictEqual(expected_json, json_data) - - def test_PrintTable_normal(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable().run(self.bus.clients) - expected_output = "\n".join(( - "Name Enabled Timeout Last Successful Check", - "foo Yes 00:05:00 2019-02-03T00:00:00 ", - "barbar Yes 00:05:00 2019-02-04T00:00:00 ", - )) + "\n" - self.assertEqual(expected_output, buffer.getvalue()) - - def test_PrintTable_verbose(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable(verbose=True).run(self.bus.clients) - columns = ( - ( - "Name ", - "foo ", - "barbar ", - ),( - "Enabled ", - "Yes ", - "Yes ", - ),( - "Timeout ", - "00:05:00 ", - "00:05:00 ", - ),( - "Last Successful Check ", - "2019-02-03T00:00:00 ", - "2019-02-04T00:00:00 ", - ),( - "Created ", - "2019-01-02T00:00:00 ", - "2019-01-03T00:00:00 ", - ),( - "Interval ", - "00:02:00 ", - "00:02:00 ", - ),( - "Host ", - "foo.example.org ", - "192.0.2.3 ", - ),( - ("Key ID " - " "), - ("92ed150794387c03ce684574b1139a6594a34f895daaaf09fd8" - "ea90a27cddb12 "), - ("0558568eedd67d622f5c83b35a115f796ab612cff5ad227247e" - "46c2b020f441c "), - ),( - "Fingerprint ", - "778827225BA7DE539C5A7CFA59CFF7CDBD9A5920 ", - "3E393AEAEFB84C7E89E2F547B3A107558FCA3A27 ", - ),( - "Check Is Running ", - "No ", - "Yes ", - ),( - "Last Enabled ", - "2019-01-03T00:00:00 ", - "2019-01-04T00:00:00 ", - ),( - "Approval Is Pending ", - "No ", - "No ", - ),( - "Approved By Default ", - "Yes ", - "No ", - ),( - "Last Approval Request ", - " ", - "2019-01-03T00:00:00 ", - ),( - "Approval Delay ", - "00:00:00 ", - "00:00:30 ", - ),( - "Approval Duration ", - "00:00:01 ", - "1T02:03:05 ", - ),( - "Checker ", - "fping -q -- %(host)s ", - ": ", - ),( - "Extended Timeout ", - "00:15:00 ", - "00:15:00 ", - ),( - "Expires ", - "2019-02-04T00:00:00 ", - "2019-02-05T00:00:00 ", - ),( - "Last Checker Status", - "0 ", - "-2 ", - ) - ) - num_lines = max(len(rows) for rows in columns) - expected_output = ("\n".join("".join(rows[line] - for rows in columns) - for line in range(num_lines)) - + "\n") - self.assertEqual(expected_output, buffer.getvalue()) - - def test_PrintTable_one_client(self): - with self.capture_stdout_to_buffer() as buffer: - command.PrintTable().run(self.bus.one_client) - expected_output = "\n".join(( - "Name Enabled Timeout Last Successful Check", - "foo Yes 00:05:00 2019-02-03T00:00:00 ", - )) + "\n" - self.assertEqual(expected_output, buffer.getvalue()) - - -class TestPropertySetterCmd(TestCommand): - """Abstract class for tests of command.PropertySetter classes""" - - def runTest(self): - if not hasattr(self, "command"): - return # Abstract TestCase class - - if hasattr(self, "values_to_set"): - cmd_args = [(value,) for value in self.values_to_set] - values_to_get = getattr(self, "values_to_get", - self.values_to_set) - else: - cmd_args = [() for x in range(len(self.values_to_get))] - values_to_get = self.values_to_get - for value_to_get, cmd_arg in zip(values_to_get, cmd_args): - for clientpath in self.bus.clients: - self.bus.clients[clientpath][self.propname] = ( - Unique()) - self.command(*cmd_arg).run(self.bus.clients, self.bus) - for clientpath in self.bus.clients: - value = (self.bus.clients[clientpath] - [self.propname]) - self.assertNotIsInstance(value, Unique) - self.assertEqual(value_to_get, value) - - -class TestEnableCmd(TestPropertySetterCmd): - command = command.Enable - propname = "Enabled" - values_to_get = [True] - - -class TestDisableCmd(TestPropertySetterCmd): - command = command.Disable - propname = "Enabled" - values_to_get = [False] - - -class TestBumpTimeoutCmd(TestPropertySetterCmd): - command = command.BumpTimeout - propname = "LastCheckedOK" - values_to_get = [""] - - -class TestStartCheckerCmd(TestPropertySetterCmd): - command = command.StartChecker - propname = "CheckerRunning" - values_to_get = [True] - - -class TestStopCheckerCmd(TestPropertySetterCmd): - command = command.StopChecker - propname = "CheckerRunning" - values_to_get = [False] - - -class TestApproveByDefaultCmd(TestPropertySetterCmd): - command = command.ApproveByDefault - propname = "ApprovedByDefault" - values_to_get = [True] - - -class TestDenyByDefaultCmd(TestPropertySetterCmd): - command = command.DenyByDefault - propname = "ApprovedByDefault" - values_to_get = [False] - - -class TestSetCheckerCmd(TestPropertySetterCmd): - command = command.SetChecker - propname = "Checker" - values_to_set = ["", ":", "fping -q -- %s"] - - -class TestSetHostCmd(TestPropertySetterCmd): - command = command.SetHost - propname = "Host" - values_to_set = ["192.0.2.3", "client.example.org"] - - -class TestSetSecretCmd(TestPropertySetterCmd): - command = command.SetSecret - propname = "Secret" - values_to_set = [io.BytesIO(b""), - io.BytesIO(b"secret\0xyzzy\nbar")] - values_to_get = [f.getvalue() for f in values_to_set] - - -class TestSetTimeoutCmd(TestPropertySetterCmd): - command = command.SetTimeout - propname = "Timeout" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetExtendedTimeoutCmd(TestPropertySetterCmd): - command = command.SetExtendedTimeout - propname = "ExtendedTimeout" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetIntervalCmd(TestPropertySetterCmd): - command = command.SetInterval - propname = "Interval" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetApprovalDelayCmd(TestPropertySetterCmd): - command = command.SetApprovalDelay - propname = "ApprovalDelay" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - -class TestSetApprovalDurationCmd(TestPropertySetterCmd): - command = command.SetApprovalDuration - propname = "ApprovalDuration" - values_to_set = [datetime.timedelta(), - datetime.timedelta(minutes=5), - datetime.timedelta(seconds=1), - datetime.timedelta(weeks=1), - datetime.timedelta(weeks=52)] - values_to_get = [dt.total_seconds()*1000 for dt in values_to_set] - - - -def should_only_run_tests(): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--check", action='store_true') - args, unknown_args = parser.parse_known_args() - run_tests = args.check - if run_tests: - # Remove --check argument from sys.argv - sys.argv[1:] = unknown_args - return run_tests - -# Add all tests from doctest strings -def load_tests(loader, tests, none): - import doctest - tests.addTests(doctest.DocTestSuite()) - return tests - -if __name__ == "__main__": - try: - if should_only_run_tests(): - # Call using ./tdd-python-script --check [--verbose] - unittest.main() - else: - main() - finally: - logging.shutdown() === removed file 'mandos-ctl.xml' --- mandos-ctl.xml 2019-07-29 16:35:53 +0000 +++ mandos-ctl.xml 1970-01-01 00:00:00 +0000 @@ -1,652 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Control or query the operation of the Mandos server - - - - - - &COMMANDNAME; - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - CLIENT - - - - - &COMMANDNAME; - - - - - - CLIENT - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - DESCRIPTION - - &COMMANDNAME; is a program to control or - query the operation of the Mandos server - mandos8. - - - This program can be used to change client settings, approve or - deny client requests, and to remove clients from the server. - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - - - - - - - Show a help message and exit - - - - - - - - - - Enable client(s). An enabled client will be eligble to - receive its secret. - - - - - - - - - - Disable client(s). A disabled client will not be eligble - to receive its secret, and no checkers will be started for - it. - - - - - - - - - Bump the timeout of the specified client(s), just as if a - checker had completed successfully for it/them. - - - - - - - - - Start a new checker now for the specified client(s). - - - - - - - - - Stop any running checker for the specified client(s). - - - - - - - - - - Remove the specified client(s) from the server. - - - - - - - - - - Set the checker option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - - Set the timeout option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - Set the extended_timeout option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - - Set the interval option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - - Set the approved_by_default option of - the specified client(s) to True or - False, respectively; see - mandos-clients.conf5. - - - - - - - - - Set the approval_delay option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - Set the approval_duration option of the - specified client(s); see mandos-clients.conf5. - - - - - - - - - - Set the host option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - - Set the secfile option of the specified - client(s); see mandos-clients.conf5. - - - - - - - - - - Approve client(s) if currently waiting for approval. - - - - - - - - - - Deny client(s) if currently waiting for approval. - - - - - - - - - - Make the client-modifying options modify all clients. - - - - - - - - - - Show all client settings, not just a subset. - - - - - - - - - - Dump client settings as JSON to standard output. - - - - - - - - - - Check if a single client is enabled or not, and exit with - a successful exit status only if the client is enabled. - - - - - - - - - Show debug output; currently, this means show D-Bus calls. - - - - - - - - - Run self-tests. This includes any unit tests, etc. - - - - - - - - - OVERVIEW - - - This program is a small utility to generate new OpenPGP keys for - new Mandos clients, and to generate sections for inclusion in - clients.conf on the server. - - - - - EXIT STATUS - - If the option is used, the exit - status will be 0 only if the specified client is enabled. - - - - - BUGS - - - - - EXAMPLE - - - - - To list all clients: - - - &COMMANDNAME; - - - - - - - To list all settings for the clients - named foo1.example.org and foo2.example.org: - - - - -&COMMANDNAME; --verbose foo1.example.org foo2.example.org - - - - - - - - To enable all clients: - - - &COMMANDNAME; --enable --all - - - - - - - To change timeout and interval value for the clients - named foo1.example.org and foo2.example.org: - - - - -&COMMANDNAME; --timeout=PT5M --interval=PT1M foo1.example.org foo2.example.org - - - - - - - - To approve all clients currently waiting for approval: - - - &COMMANDNAME; --approve --all - - - - - - SECURITY - - This program must be permitted to access the Mandos server via - the D-Bus interface. This normally requires the root user, but - could be configured otherwise by reconfiguring the D-Bus server. - - - - - SEE ALSO - - intro - 8mandos, - mandos - 8, - mandos-clients.conf - 5, - mandos-monitor - 8 - - - -
- - - - - === removed file 'mandos-keygen' --- mandos-keygen 2019-09-03 19:06:41 +0000 +++ mandos-keygen 1970-01-01 00:00:00 +0000 @@ -1,451 +0,0 @@ -#!/bin/sh -e -# -# Mandos key generator - create new keys for a Mandos client -# -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# - -VERSION="1.8.9" - -KEYDIR="/etc/keys/mandos" -KEYTYPE=RSA -KEYLENGTH=4096 -SUBKEYTYPE=RSA -SUBKEYLENGTH=4096 -KEYNAME="`hostname --fqdn 2>/dev/null || hostname`" -KEYEMAIL="" -KEYCOMMENT="" -KEYEXPIRE=0 -TLS_KEYTYPE=ed25519 -FORCE=no -SSH=yes -KEYCOMMENT_ORIG="$KEYCOMMENT" -mode=keygen - -if [ ! -d "$KEYDIR" ]; then - KEYDIR="/etc/mandos/keys" -fi - -# Parse options -TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:T:fS \ - --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,tls-keytype:,force,no-ssh \ - --name "$0" -- "$@"` - -help(){ -basename="`basename "$0"`" -cat <&2; exit 1;; - esac -done -if [ "$#" -gt 0 ]; then - echo "Unknown arguments: '$*'" >&2 - exit 1 -fi - -SECKEYFILE="$KEYDIR/seckey.txt" -PUBKEYFILE="$KEYDIR/pubkey.txt" -TLS_PRIVKEYFILE="$KEYDIR/tls-privkey.pem" -TLS_PUBKEYFILE="$KEYDIR/tls-pubkey.pem" - -# Check for some invalid values -if [ ! -d "$KEYDIR" ]; then - echo "$KEYDIR not a directory" >&2 - exit 1 -fi -if [ ! -r "$KEYDIR" ]; then - echo "Directory $KEYDIR not readable" >&2 - exit 1 -fi - -if [ "$mode" = keygen ]; then - if [ ! -w "$KEYDIR" ]; then - echo "Directory $KEYDIR not writeable" >&2 - exit 1 - fi - if [ -z "$KEYTYPE" ]; then - echo "Empty key type" >&2 - exit 1 - fi - - if [ -z "$KEYNAME" ]; then - echo "Empty key name" >&2 - exit 1 - fi - - if [ -z "$KEYLENGTH" ] || [ "$KEYLENGTH" -lt 512 ]; then - echo "Invalid key length" >&2 - exit 1 - fi - - if [ -z "$KEYEXPIRE" ]; then - echo "Empty key expiration" >&2 - exit 1 - fi - - # Make FORCE be 0 or 1 - case "$FORCE" in - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) FORCE=1;; - [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;; - esac - - if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ] \ - || [ -e "$TLS_PRIVKEYFILE" ] \ - || [ -e "$TLS_PUBKEYFILE" ]; } \ - && [ "$FORCE" -eq 0 ]; then - echo "Refusing to overwrite old key files; use --force" >&2 - exit 1 - fi - - # Set lines for GnuPG batch file - if [ -n "$KEYCOMMENT" ]; then - KEYCOMMENTLINE="Name-Comment: $KEYCOMMENT" - fi - if [ -n "$KEYEMAIL" ]; then - KEYEMAILLINE="Name-Email: $KEYEMAIL" - fi - - # Create temporary gpg batch file - BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`" - TLS_PRIVKEYTMP="`mktemp -t mandos-keygen-privkey.XXXXXXXXXX`" -fi - -if [ "$mode" = password ]; then - # Create temporary encrypted password file - SECFILE="`mktemp -t mandos-keygen-secfile.XXXXXXXXXX`" -fi - -# Create temporary key ring directory -RINGDIR="`mktemp -d -t mandos-keygen-keyrings.XXXXXXXXXX`" - -# Remove temporary files on exit -trap " -set +e; \ -test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \ -test -n \"$TLS_PRIVKEYTMP\" && shred --remove \"$TLS_PRIVKEYTMP\"; \ -shred --remove \"$RINGDIR\"/sec* 2>/dev/null; -test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \ -rm --recursive --force \"$RINGDIR\"; -tty --quiet && stty echo; \ -" EXIT - -set -e - -umask 077 - -if [ "$mode" = keygen ]; then - # Create batch file for GnuPG - cat >"$BATCHFILE" <<-EOF - Key-Type: $KEYTYPE - Key-Length: $KEYLENGTH - Key-Usage: sign,auth - Subkey-Type: $SUBKEYTYPE - Subkey-Length: $SUBKEYLENGTH - Subkey-Usage: encrypt - Name-Real: $KEYNAME - $KEYCOMMENTLINE - $KEYEMAILLINE - Expire-Date: $KEYEXPIRE - #Preferences: - #Handle: - #%pubring pubring.gpg - #%secring secring.gpg - %no-protection - %commit - EOF - - if tty --quiet; then - cat <<-EOF - Note: Due to entropy requirements, key generation could take - anything from a few minutes to SEVERAL HOURS. Please be - patient and/or supply the system with more entropy if needed. - EOF - echo -n "Started: " - date - fi - - # Generate TLS private key - if certtool --generate-privkey --password='' \ - --outfile "$TLS_PRIVKEYTMP" --sec-param ultra \ - --key-type="$TLS_KEYTYPE" --pkcs8 --no-text 2>/dev/null; then - - # Backup any old key files - if cp --backup=numbered --force "$TLS_PRIVKEYFILE" "$TLS_PRIVKEYFILE" \ - 2>/dev/null; then - shred --remove "$TLS_PRIVKEYFILE" 2>/dev/null || : - fi - if cp --backup=numbered --force "$TLS_PUBKEYFILE" "$TLS_PUBKEYFILE" \ - 2>/dev/null; then - rm --force "$TLS_PUBKEYFILE" - fi - cp --archive "$TLS_PRIVKEYTMP" "$TLS_PRIVKEYFILE" - shred --remove "$TLS_PRIVKEYTMP" 2>/dev/null || : - - ## TLS public key - - # First try certtool from GnuTLS - if ! certtool --password='' --load-privkey="$TLS_PRIVKEYFILE" \ - --outfile="$TLS_PUBKEYFILE" --pubkey-info --no-text \ - 2>/dev/null; then - # Otherwise try OpenSSL - if ! openssl pkey -in "$TLS_PRIVKEYFILE" \ - -out "$TLS_PUBKEYFILE" -pubout; then - rm --force "$TLS_PUBKEYFILE" - # None of the commands succeded; give up - return 1 - fi - fi - fi - - # Make sure trustdb.gpg exists; - # this is a workaround for Debian bug #737128 - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" \ - --import-ownertrust < /dev/null - # Generate a new key in the key rings - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always \ - --gen-key "$BATCHFILE" - rm --force "$BATCHFILE" - - if tty --quiet; then - echo -n "Finished: " - date - fi - - # Backup any old key files - if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \ - 2>/dev/null; then - shred --remove "$SECKEYFILE" 2>/dev/null || : - fi - if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \ - 2>/dev/null; then - rm --force "$PUBKEYFILE" - fi - - FILECOMMENT="Mandos client key for $KEYNAME" - if [ "$KEYCOMMENT" != "$KEYCOMMENT_ORIG" ]; then - FILECOMMENT="$FILECOMMENT ($KEYCOMMENT)" - fi - - if [ -n "$KEYEMAIL" ]; then - FILECOMMENT="$FILECOMMENT <$KEYEMAIL>" - fi - - # Export key from key rings to key files - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --armor --export-options export-minimal \ - --comment "$FILECOMMENT" --output "$SECKEYFILE" \ - --export-secret-keys - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --armor --export-options export-minimal \ - --comment "$FILECOMMENT" --output "$PUBKEYFILE" --export -fi - -if [ "$mode" = password ]; then - - # Make SSH be 0 or 1 - case "$SSH" in - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) SSH=1;; - [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) SSH=0;; - esac - - if [ $SSH -eq 1 ]; then - for ssh_keytype in ecdsa-sha2-nistp256 ed25519 rsa; do - set +e - ssh_fingerprint="`ssh-keyscan -t $ssh_keytype localhost 2>/dev/null`" - err=$? - set -e - if [ $err -ne 0 ]; then - ssh_fingerprint="" - continue - fi - if [ -n "$ssh_fingerprint" ]; then - ssh_fingerprint="${ssh_fingerprint#localhost }" - break - fi - done - fi - - # Import key into temporary key rings - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always --armor \ - --import "$SECKEYFILE" - gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always --armor \ - --import "$PUBKEYFILE" - - # Get fingerprint of key - FINGERPRINT="`gpg --quiet --batch --no-tty --no-options \ - --enable-dsa2 --homedir "$RINGDIR" --trust-model always \ - --fingerprint --with-colons \ - | sed --quiet \ - --expression='/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`" - - test -n "$FINGERPRINT" - - if [ -r "$TLS_PUBKEYFILE" ]; then - KEY_ID="$(certtool --key-id --hash=sha256 \ - --infile="$TLS_PUBKEYFILE" 2>/dev/null || :)" - - if [ -z "$KEY_ID" ]; then - KEY_ID=$(openssl pkey -pubin -in "$TLS_PUBKEYFILE" \ - -outform der \ - | openssl sha256 \ - | sed --expression='s/^.*[^[:xdigit:]]//') - fi - test -n "$KEY_ID" - fi - - FILECOMMENT="Encrypted password for a Mandos client" - - while [ ! -s "$SECFILE" ]; do - if [ -n "$PASSFILE" ]; then - cat -- "$PASSFILE" - else - tty --quiet && stty -echo - echo -n "Enter passphrase: " >/dev/tty - read -r first - tty --quiet && echo >&2 - echo -n "Repeat passphrase: " >/dev/tty - read -r second - if tty --quiet; then - echo >&2 - stty echo - fi - if [ "$first" != "$second" ]; then - echo "Passphrase mismatch" >&2 - touch "$RINGDIR"/mismatch - else - echo -n "$first" - fi - fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ - --homedir "$RINGDIR" --trust-model always --armor \ - --encrypt --sign --recipient "$FINGERPRINT" --comment \ - "$FILECOMMENT" > "$SECFILE" - if [ -e "$RINGDIR"/mismatch ]; then - rm --force "$RINGDIR"/mismatch - if tty --quiet; then - > "$SECFILE" - else - exit 1 - fi - fi - done - - cat <<-EOF - [$KEYNAME] - host = $KEYNAME - EOF - if [ -n "$KEY_ID" ]; then - echo "key_id = $KEY_ID" - fi - cat <<-EOF - fingerprint = $FINGERPRINT - secret = - EOF - sed --quiet --expression=' - /^-----BEGIN PGP MESSAGE-----$/,/^-----END PGP MESSAGE-----$/{ - /^$/,${ - # Remove 24-bit Radix-64 checksum - s/=....$// - # Indent four spaces - /^[^-]/s/^/ /p - } - }' < "$SECFILE" - if [ -n "$ssh_fingerprint" ]; then - echo 'checker = ssh-keyscan -t '"$ssh_keytype"' %%(host)s 2>/dev/null | grep --fixed-strings --line-regexp --quiet --regexp=%%(host)s" %(ssh_fingerprint)s"' - echo "ssh_fingerprint = ${ssh_fingerprint}" - fi -fi - -trap - EXIT - -set +e -# Remove the password file, if any -if [ -n "$SECFILE" ]; then - shred --remove "$SECFILE" 2>/dev/null -fi -# Remove the key rings -shred --remove "$RINGDIR"/sec* 2>/dev/null -rm --recursive --force "$RINGDIR" === removed file 'mandos-keygen.xml' --- mandos-keygen.xml 2019-07-18 00:02:43 +0000 +++ mandos-keygen.xml 1970-01-01 00:00:00 +0000 @@ -1,590 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Generate key and password for Mandos client and server. - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - FILE - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - &COMMANDNAME; is a program to generate the - TLS and OpenPGP keys used by - mandos-client - 8mandos. The keys are - normally written to /etc/keys/mandos for later installation into - the initrd image, but this, and most other things, can be - changed with command line options. - - - This program can also be used with the - or - options to generate a ready-made section for - clients.conf (see - mandos-clients.conf - 5). - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - - - - - - - Show a help message and exit - - - - - - - - - - Target directory for key files. Default is /etc/keys/mandos. - - - - - - - - - - OpenPGP key type. Default is RSA. - - - - - - - - - - OpenPGP key length in bits. Default is 4096. - - - - - - - - - - OpenPGP subkey type. Default is RSA - - - - - - - - - - OpenPGP subkey length in bits. Default is 4096. - - - - - - - - - - Email address of key. Default is empty. - - - - - - - - - - Comment field for key. Default is empty. - - - - - - - - - - Key expire time. Default is no expiration. See - gpg - 1 for syntax. - - - - - - - - - - TLS key type. Default is ed25519 - - - - - - - - - - Force overwriting old key. - - - - - - - - - Prompt for a password and encrypt it with the key already - present in either /etc/keys/mandos or - the directory specified with the - option. Outputs, on standard output, a section suitable - for inclusion in mandos-clients.conf8. The host name or the name - specified with the option is used - for the section header. All other options are ignored, - and no key is created. Note: white space is stripped from - the beginning and from the end of the password; See . - - - - - - - - - The same as , but read from - FILE, not the terminal, and - white space is not stripped from the password in any way. - - - - - - - - - When or - is given, this option will - prevent &COMMANDNAME; from calling - ssh-keyscan to get an SSH fingerprint - for this host and, if successful, output suitable config - options to use this fingerprint as a - option in the output. This is - otherwise the default behavior. - - - - - - - - OVERVIEW - - - This program is a small utility to generate new TLS and OpenPGP - keys for new Mandos clients, and to generate sections for - inclusion in clients.conf on the server. - - - - - EXIT STATUS - - The exit status will be 0 if a new key (or password, if the - option was used) was successfully - created, otherwise not. - - - - - ENVIRONMENT - - - TMPDIR - - - If set, temporary files will be created here. See - mktemp - 1. - - - - - - - - FILES - - Use the option to change where - &COMMANDNAME; will write the key files. The - default file names are shown here. - - - - /etc/keys/mandos/seckey.txt - - - OpenPGP secret key file which will be created or - overwritten. - - - - - /etc/keys/mandos/pubkey.txt - - - OpenPGP public key file which will be created or - overwritten. - - - - - /etc/keys/mandos/tls-privkey.pem - - - Private key file which will be created or overwritten. - - - - - /etc/keys/mandos/tls-pubkey.pem - - - Public key file which will be created or overwritten. - - - - - /tmp - - - Temporary files will be written here if - TMPDIR is not set. - - - - - - - - BUGS - - The / option - strips white space from the start and from the end of the - password before using it. If this is a problem, use the - option instead, which does not do - this. - - - - - - EXAMPLE - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Create key in another directory and of another type. Force - overwriting old key files: - - - - -&COMMANDNAME; --dir ~/keydir --type RSA --force - - - - - - Prompt for a password, encrypt it with the keys in /etc/keys/mandos and output a - section suitable for clients.conf. - - - &COMMANDNAME; --password - - - - - Prompt for a password, encrypt it with the keys in the - client-key directory and output a section - suitable for clients.conf. - - - - -&COMMANDNAME; --password --dir client-key - - - - - - - SECURITY - - The , , - , and - options can be used to create keys of low security. If in - doubt, leave them to the default values. - - - The key expire time is not guaranteed to be - honored by mandos - 8. - - - - - SEE ALSO - - intro - 8mandos, - gpg - 1, - mandos-clients.conf - 5, - mandos - 8, - mandos-client - 8mandos, - ssh-keyscan - 1 - - - -
- - - - - === removed file 'mandos-monitor' --- mandos-monitor 2019-11-03 19:09:41 +0000 +++ mandos-monitor 1970-01-01 00:00:00 +0000 @@ -1,757 +0,0 @@ -#!/usr/bin/python3 -bbI -# -*- mode: python; coding: utf-8 -*- -# -# Mandos Monitor - Control and monitor the Mandos server -# -# Copyright © 2009-2019 Teddy Hogeborn -# Copyright © 2009-2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) -try: - from future_builtins import * -except ImportError: - pass - -import sys -import os -import warnings -import datetime -import locale -import logging - -import urwid.curses_display -import urwid - -from dbus.mainloop.glib import DBusGMainLoop -from gi.repository import GLib - -import dbus - -if sys.version_info.major == 2: - str = unicode - -log = logging.getLogger(os.path.basename(sys.argv[0])) -logging.basicConfig(level="NOTSET", # Show all messages - format="%(message)s") # Show basic log messages - -logging.captureWarnings(True) # Show warnings via the logging system - -locale.setlocale(locale.LC_ALL, "") - -logging.getLogger("dbus.proxies").setLevel(logging.CRITICAL) - -# Some useful constants -domain = "se.recompile" -server_interface = domain + ".Mandos" -client_interface = domain + ".Mandos.Client" -version = "1.8.9" - -try: - dbus.OBJECT_MANAGER_IFACE -except AttributeError: - dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - - -def isoformat_to_datetime(iso): - "Parse an ISO 8601 date string to a datetime.datetime()" - if not iso: - return None - d, t = iso.split("T", 1) - year, month, day = d.split("-", 2) - hour, minute, second = t.split(":", 2) - second, fraction = divmod(float(second), 1) - return datetime.datetime(int(year), - int(month), - int(day), - int(hour), - int(minute), - int(second), # Whole seconds - int(fraction*1000000)) # Microseconds - - -class MandosClientPropertyCache(object): - """This wraps a Mandos Client D-Bus proxy object, caches the - properties and calls a hook function when any of them are - changed. - """ - def __init__(self, proxy_object=None, properties=None, **kwargs): - self.proxy = proxy_object # Mandos Client proxy object - self.properties = dict() if properties is None else properties - self.property_changed_match = ( - self.proxy.connect_to_signal("PropertiesChanged", - self.properties_changed, - dbus.PROPERTIES_IFACE, - byte_arrays=True)) - - if properties is None: - self.properties.update(self.proxy.GetAll( - client_interface, - dbus_interface=dbus.PROPERTIES_IFACE)) - - super(MandosClientPropertyCache, self).__init__(**kwargs) - - def properties_changed(self, interface, properties, invalidated): - """This is called whenever we get a PropertiesChanged signal - It updates the changed properties in the "properties" dict. - """ - # Update properties dict with new value - if interface == client_interface: - self.properties.update(properties) - - def delete(self): - self.property_changed_match.remove() - - -class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache): - """A Mandos Client which is visible on the screen. - """ - - def __init__(self, server_proxy_object=None, update_hook=None, - delete_hook=None, **kwargs): - # Called on update - self.update_hook = update_hook - # Called on delete - self.delete_hook = delete_hook - # Mandos Server proxy object - self.server_proxy_object = server_proxy_object - - self._update_timer_callback_tag = None - - # The widget shown normally - self._text_widget = urwid.Text("") - # The widget shown when we have focus - self._focus_text_widget = urwid.Text("") - super(MandosClientWidget, self).__init__(**kwargs) - self.update() - self.opened = False - - self.match_objects = ( - self.proxy.connect_to_signal("CheckerCompleted", - self.checker_completed, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("CheckerStarted", - self.checker_started, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("GotSecret", - self.got_secret, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("NeedApproval", - self.need_approval, - client_interface, - byte_arrays=True), - self.proxy.connect_to_signal("Rejected", - self.rejected, - client_interface, - byte_arrays=True)) - log.debug("Created client %s", self.properties["Name"]) - - def using_timer(self, flag): - """Call this method with True or False when timer should be - activated or deactivated. - """ - if flag and self._update_timer_callback_tag is None: - # Will update the shown timer value every second - self._update_timer_callback_tag = ( - GLib.timeout_add(1000, - glib_safely(self.update_timer))) - elif not (flag or self._update_timer_callback_tag is None): - GLib.source_remove(self._update_timer_callback_tag) - self._update_timer_callback_tag = None - - def checker_completed(self, exitstatus, condition, command): - if exitstatus == 0: - log.debug('Checker for client %s (command "%s")' - " succeeded", self.properties["Name"], command) - self.update() - return - # Checker failed - if os.WIFEXITED(condition): - log.info('Checker for client %s (command "%s") failed' - " with exit code %d", self.properties["Name"], - command, os.WEXITSTATUS(condition)) - elif os.WIFSIGNALED(condition): - log.info('Checker for client %s (command "%s") was' - " killed by signal %d", self.properties["Name"], - command, os.WTERMSIG(condition)) - self.update() - - def checker_started(self, command): - """Server signals that a checker started.""" - log.debug('Client %s started checker "%s"', - self.properties["Name"], command) - - def got_secret(self): - log.info("Client %s received its secret", - self.properties["Name"]) - - def need_approval(self, timeout, default): - if not default: - message = "Client %s needs approval within %f seconds" - else: - message = "Client %s will get its secret in %f seconds" - log.info(message, self.properties["Name"], timeout/1000) - - def rejected(self, reason): - log.info("Client %s was rejected; reason: %s", - self.properties["Name"], reason) - - def selectable(self): - """Make this a "selectable" widget. - This overrides the method from urwid.FlowWidget.""" - return True - - def rows(self, maxcolrow, focus=False): - """How many rows this widget will occupy might depend on - whether we have focus or not. - This overrides the method from urwid.FlowWidget""" - return self.current_widget(focus).rows(maxcolrow, focus=focus) - - def current_widget(self, focus=False): - if focus or self.opened: - return self._focus_widget - return self._widget - - def update(self): - "Called when what is visible on the screen should be updated." - # How to add standout mode to a style - with_standout = {"normal": "standout", - "bold": "bold-standout", - "underline-blink": - "underline-blink-standout", - "bold-underline-blink": - "bold-underline-blink-standout", - } - - # Rebuild focus and non-focus widgets using current properties - - # Base part of a client. Name! - base = "{name}: ".format(name=self.properties["Name"]) - if not self.properties["Enabled"]: - message = "DISABLED" - self.using_timer(False) - elif self.properties["ApprovalPending"]: - timeout = datetime.timedelta( - milliseconds=self.properties["ApprovalDelay"]) - last_approval_request = isoformat_to_datetime( - self.properties["LastApprovalRequest"]) - if last_approval_request is not None: - timer = max(timeout - (datetime.datetime.utcnow() - - last_approval_request), - datetime.timedelta()) - else: - timer = datetime.timedelta() - if self.properties["ApprovedByDefault"]: - message = "Approval in {}. (d)eny?" - else: - message = "Denial in {}. (a)pprove?" - message = message.format(str(timer).rsplit(".", 1)[0]) - self.using_timer(True) - elif self.properties["LastCheckerStatus"] != 0: - # When checker has failed, show timer until client expires - expires = self.properties["Expires"] - if expires == "": - timer = datetime.timedelta(0) - else: - expires = (datetime.datetime.strptime - (expires, "%Y-%m-%dT%H:%M:%S.%f")) - timer = max(expires - datetime.datetime.utcnow(), - datetime.timedelta()) - message = ("A checker has failed! Time until client" - " gets disabled: {}" - .format(str(timer).rsplit(".", 1)[0])) - self.using_timer(True) - else: - message = "enabled" - self.using_timer(False) - self._text = "{}{}".format(base, message) - - if not urwid.supports_unicode(): - self._text = self._text.encode("ascii", "replace") - textlist = [("normal", self._text)] - self._text_widget.set_text(textlist) - self._focus_text_widget.set_text([(with_standout[text[0]], - text[1]) - if isinstance(text, tuple) - else text - for text in textlist]) - self._widget = self._text_widget - self._focus_widget = urwid.AttrWrap(self._focus_text_widget, - "standout") - # Run update hook, if any - if self.update_hook is not None: - self.update_hook() - - def update_timer(self): - """called by GLib. Will indefinitely loop until - GLib.source_remove() on tag is called - """ - self.update() - return True # Keep calling this - - def delete(self, **kwargs): - if self._update_timer_callback_tag is not None: - GLib.source_remove(self._update_timer_callback_tag) - self._update_timer_callback_tag = None - for match in self.match_objects: - match.remove() - self.match_objects = () - if self.delete_hook is not None: - self.delete_hook(self) - return super(MandosClientWidget, self).delete(**kwargs) - - def render(self, maxcolrow, focus=False): - """Render differently if we have focus. - This overrides the method from urwid.FlowWidget""" - return self.current_widget(focus).render(maxcolrow, - focus=focus) - - def keypress(self, maxcolrow, key): - """Handle keys. - This overrides the method from urwid.FlowWidget""" - if key == "+": - self.proxy.Set(client_interface, "Enabled", - dbus.Boolean(True), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) - elif key == "-": - self.proxy.Set(client_interface, "Enabled", False, - ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) - elif key == "a": - self.proxy.Approve(dbus.Boolean(True, variant_level=1), - dbus_interface=client_interface, - ignore_reply=True) - elif key == "d": - self.proxy.Approve(dbus.Boolean(False, variant_level=1), - dbus_interface=client_interface, - ignore_reply=True) - elif key == "R" or key == "_" or key == "ctrl k": - self.server_proxy_object.RemoveClient(self.proxy - .object_path, - ignore_reply=True) - elif key == "s": - self.proxy.Set(client_interface, "CheckerRunning", - dbus.Boolean(True), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) - elif key == "S": - self.proxy.Set(client_interface, "CheckerRunning", - dbus.Boolean(False), ignore_reply=True, - dbus_interface=dbus.PROPERTIES_IFACE) - elif key == "C": - self.proxy.CheckedOK(dbus_interface=client_interface, - ignore_reply=True) - # xxx -# elif key == "p" or key == "=": -# self.proxy.pause() -# elif key == "u" or key == ":": -# self.proxy.unpause() -# elif key == "RET": -# self.open() - else: - return key - - def properties_changed(self, interface, properties, invalidated): - """Call self.update() if any properties changed. - This overrides the method from MandosClientPropertyCache""" - old_values = {key: self.properties.get(key) - for key in properties.keys()} - super(MandosClientWidget, self).properties_changed( - interface, properties, invalidated) - if any(old_values[key] != self.properties.get(key) - for key in old_values): - self.update() - - -def glib_safely(func, retval=True): - def safe_func(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception: - log.exception("") - return retval - return safe_func - - -class ConstrainedListBox(urwid.ListBox): - """Like a normal urwid.ListBox, but will consume all "up" or - "down" key presses, thus not allowing any containing widgets to - use them as an excuse to shift focus away from this widget. - """ - def keypress(self, *args, **kwargs): - ret = (super(ConstrainedListBox, self) - .keypress(*args, **kwargs)) - if ret in ("up", "down"): - return - return ret - - -class UserInterface(object): - """This is the entire user interface - the whole screen - with boxes, lists of client widgets, etc. - """ - def __init__(self, max_log_length=1000): - DBusGMainLoop(set_as_default=True) - - self.screen = urwid.curses_display.Screen() - - self.screen.register_palette(( - ("normal", - "default", "default", None), - ("bold", - "bold", "default", "bold"), - ("underline-blink", - "underline,blink", "default", "underline,blink"), - ("standout", - "standout", "default", "standout"), - ("bold-underline-blink", - "bold,underline,blink", "default", - "bold,underline,blink"), - ("bold-standout", - "bold,standout", "default", "bold,standout"), - ("underline-blink-standout", - "underline,blink,standout", "default", - "underline,blink,standout"), - ("bold-underline-blink-standout", - "bold,underline,blink,standout", "default", - "bold,underline,blink,standout"), - )) - - if urwid.supports_unicode(): - self.divider = "─" # \u2500 - else: - self.divider = "_" # \u005f - - self.screen.start() - - self.size = self.screen.get_cols_rows() - - self.clients = urwid.SimpleListWalker([]) - self.clients_dict = {} - - # We will add Text widgets to this list - self.log = urwid.SimpleListWalker([]) - self.max_log_length = max_log_length - - # We keep a reference to the log widget so we can remove it - # from the ListWalker without it getting destroyed - self.logbox = ConstrainedListBox(self.log) - - # This keeps track of whether self.uilist currently has - # self.logbox in it or not - self.log_visible = True - self.log_wrap = "any" - - self.loghandler = UILogHandler(self) - - self.rebuild() - self.add_log_line(("bold", - "Mandos Monitor version " + version)) - self.add_log_line(("bold", "q: Quit ?: Help")) - - self.busname = domain + ".Mandos" - self.main_loop = GLib.MainLoop() - - def client_not_found(self, key_id, address): - log.info("Client with address %s and key ID %s could" - " not be found", address, key_id) - - def rebuild(self): - """This rebuilds the User Interface. - Call this when the widget layout needs to change""" - self.uilist = [] - # self.uilist.append(urwid.ListBox(self.clients)) - self.uilist.append(urwid.Frame(ConstrainedListBox(self. - clients), - # header=urwid.Divider(), - header=None, - footer=urwid.Divider( - div_char=self.divider))) - if self.log_visible: - self.uilist.append(self.logbox) - self.topwidget = urwid.Pile(self.uilist) - - def add_log_line(self, markup): - self.log.append(urwid.Text(markup, wrap=self.log_wrap)) - if self.max_log_length: - if len(self.log) > self.max_log_length: - del self.log[0:(len(self.log) - self.max_log_length)] - self.logbox.set_focus(len(self.logbox.body.contents)-1, - coming_from="above") - self.refresh() - - def toggle_log_display(self): - """Toggle visibility of the log buffer.""" - self.log_visible = not self.log_visible - self.rebuild() - log.debug("Log visibility changed to: %s", self.log_visible) - - def change_log_display(self): - """Change type of log display. - Currently, this toggles wrapping of text lines.""" - if self.log_wrap == "clip": - self.log_wrap = "any" - else: - self.log_wrap = "clip" - for textwidget in self.log: - textwidget.set_wrap_mode(self.log_wrap) - log.debug("Wrap mode: %s", self.log_wrap) - - def find_and_remove_client(self, path, interfaces): - """Find a client by its object path and remove it. - - This is connected to the InterfacesRemoved signal from the - Mandos server object.""" - if client_interface not in interfaces: - # Not a Mandos client object; ignore - return - try: - client = self.clients_dict[path] - except KeyError: - # not found? - log.warning("Unknown client %s removed", path) - return - client.delete() - - def add_new_client(self, path, ifs_and_props): - """Find a client by its object path and remove it. - - This is connected to the InterfacesAdded signal from the - Mandos server object. - """ - if client_interface not in ifs_and_props: - # Not a Mandos client object; ignore - return - client_proxy_object = self.bus.get_object(self.busname, path) - self.add_client(MandosClientWidget( - server_proxy_object=self.mandos_serv, - proxy_object=client_proxy_object, - update_hook=self.refresh, - delete_hook=self.remove_client, - properties=dict(ifs_and_props[client_interface])), - path=path) - - def add_client(self, client, path=None): - self.clients.append(client) - if path is None: - path = client.proxy.object_path - self.clients_dict[path] = client - self.clients.sort(key=lambda c: c.properties["Name"]) - self.refresh() - - def remove_client(self, client, path=None): - self.clients.remove(client) - if path is None: - path = client.proxy.object_path - del self.clients_dict[path] - self.refresh() - - def refresh(self): - """Redraw the screen""" - canvas = self.topwidget.render(self.size, focus=True) - self.screen.draw_screen(self.size, canvas) - - def run(self): - """Start the main loop and exit when it's done.""" - log.addHandler(self.loghandler) - self.orig_log_propagate = log.propagate - log.propagate = False - self.orig_log_level = log.level - log.setLevel("INFO") - self.bus = dbus.SystemBus() - mandos_dbus_objc = self.bus.get_object( - self.busname, "/", follow_name_owner_changes=True) - self.mandos_serv = dbus.Interface( - mandos_dbus_objc, dbus_interface=server_interface) - try: - mandos_clients = (self.mandos_serv - .GetAllClientsWithProperties()) - if not mandos_clients: - log.warning("Note: Server has no clients.") - except dbus.exceptions.DBusException: - log.warning("Note: No Mandos server running.") - mandos_clients = dbus.Dictionary() - - (self.mandos_serv - .connect_to_signal("InterfacesRemoved", - self.find_and_remove_client, - dbus_interface=dbus.OBJECT_MANAGER_IFACE, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("InterfacesAdded", - self.add_new_client, - dbus_interface=dbus.OBJECT_MANAGER_IFACE, - byte_arrays=True)) - (self.mandos_serv - .connect_to_signal("ClientNotFound", - self.client_not_found, - dbus_interface=server_interface, - byte_arrays=True)) - for path, client in mandos_clients.items(): - client_proxy_object = self.bus.get_object(self.busname, - path) - self.add_client(MandosClientWidget( - server_proxy_object=self.mandos_serv, - proxy_object=client_proxy_object, - properties=client, - update_hook=self.refresh, - delete_hook=self.remove_client), - path=path) - - self.refresh() - self._input_callback_tag = ( - GLib.io_add_watch( - GLib.IOChannel.unix_new(sys.stdin.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN, - glib_safely(self.process_input))) - self.main_loop.run() - # Main loop has finished, we should close everything now - GLib.source_remove(self._input_callback_tag) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", BytesWarning) - self.screen.stop() - - def stop(self): - self.main_loop.quit() - log.removeHandler(self.loghandler) - log.propagate = self.orig_log_propagate - - def process_input(self, source, condition): - keys = self.screen.get_input() - translations = {"ctrl n": "down", # Emacs - "ctrl p": "up", # Emacs - "ctrl v": "page down", # Emacs - "meta v": "page up", # Emacs - " ": "page down", # less - "f": "page down", # less - "b": "page up", # less - "j": "down", # vi - "k": "up", # vi - } - for key in keys: - try: - key = translations[key] - except KeyError: # :-) - pass - - if key == "q" or key == "Q": - self.stop() - break - elif key == "window resize": - self.size = self.screen.get_cols_rows() - self.refresh() - elif key == "ctrl l": - self.screen.clear() - self.refresh() - elif key == "l" or key == "D": - self.toggle_log_display() - self.refresh() - elif key == "w" or key == "i": - self.change_log_display() - self.refresh() - elif key == "?" or key == "f1" or key == "esc": - if not self.log_visible: - self.log_visible = True - self.rebuild() - self.add_log_line(("bold", - " ".join(("q: Quit", - "?: Help", - "l: Log window toggle", - "TAB: Switch window", - "w: Wrap (log lines)", - "v: Toggle verbose log", - )))) - self.add_log_line(("bold", - " ".join(("Clients:", - "+: Enable", - "-: Disable", - "R: Remove", - "s: Start new checker", - "S: Stop checker", - "C: Checker OK", - "a: Approve", - "d: Deny", - )))) - self.refresh() - elif key == "tab": - if self.topwidget.get_focus() is self.logbox: - self.topwidget.set_focus(0) - else: - self.topwidget.set_focus(self.logbox) - self.refresh() - elif key == "v": - if log.level < logging.INFO: - log.setLevel(logging.INFO) - log.info("Verbose mode: Off") - else: - log.setLevel(logging.NOTSET) - log.info("Verbose mode: On") - # elif (key == "end" or key == "meta >" or key == "G" - # or key == ">"): - # pass # xxx end-of-buffer - # elif (key == "home" or key == "meta <" or key == "g" - # or key == "<"): - # pass # xxx beginning-of-buffer - # elif key == "ctrl e" or key == "$": - # pass # xxx move-end-of-line - # elif key == "ctrl a" or key == "^": - # pass # xxx move-beginning-of-line - # elif key == "ctrl b" or key == "meta (" or key == "h": - # pass # xxx left - # elif key == "ctrl f" or key == "meta )" or key == "l": - # pass # xxx right - # elif key == "a": - # pass # scroll up log - # elif key == "z": - # pass # scroll down log - elif self.topwidget.selectable(): - self.topwidget.keypress(self.size, key) - self.refresh() - return True - - -class UILogHandler(logging.Handler): - def __init__(self, ui, *args, **kwargs): - self.ui = ui - super(UILogHandler, self).__init__(*args, **kwargs) - self.setFormatter( - logging.Formatter("%(asctime)s: %(message)s")) - def emit(self, record): - msg = self.format(record) - if record.levelno > logging.INFO: - msg = ("bold", msg) - self.ui.add_log_line(msg) - - -ui = UserInterface() -try: - ui.run() -except KeyboardInterrupt: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "", BytesWarning) - ui.screen.stop() -except Exception: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "", BytesWarning) - ui.screen.stop() - raise === removed file 'mandos-monitor.xml' --- mandos-monitor.xml 2019-02-10 04:20:26 +0000 +++ mandos-monitor.xml 1970-01-01 00:00:00 +0000 @@ -1,249 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Text-based GUI to control the Mandos server. - - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - &COMMANDNAME; is an interactive program to - monitor and control the operations of the Mandos server (see - mandos8). - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OVERVIEW - - - This program is used to monitor and control the Mandos server. - In particular, it can be used to approve Mandos clients which - have been configured to require approval. It also shows all - significant events reported by the Mandos server. - - - - - KEYS - - This program is used to monitor and control the Mandos server. - In particular, it can be used to approve Mandos clients which - have been configured to require approval. It also shows all - significant events reported by the Mandos server. - - - Global Keys - - Keys - Function - - - - q, Q - Quit - - - Ctrl-L - Redraw screen - - - ?, F1 - Show help - - - l, D - Toggle log window - - - TAB - Switch window - - - w, i - Toggle log window line wrap - - - v - Toggle verbose logging - - - Up, Ctrl-P, k - Move up a line - - - Down, Ctrl-N, j - Move down a line - - - PageUp, Meta-V, b - Move up a page - - - PageDown, Ctrl-V, SPACE, f - Move down a page - -
- - Client List Keys - - Keys - Function - - - - + - Enable client - - - - - Disable client - - - a - Approve client - - - d - Deny client - - - R, _, Ctrl-K - Remove client - - - s - Start checker for client - - - S - Stop checker for client - - - C - Force a successful check for this client. - -
-
- - - BUGS - - This program can currently only be used to monitor and control a - Mandos server with the default D-Bus bus name of - se.recompile.Mandos. - - - - - - EXAMPLE - - - This program takes no options: - - - &COMMANDNAME; - - - - - - SECURITY - - This program must be permitted to access the Mandos server via - the D-Bus interface. This normally requires the root user, but - could be configured otherwise by reconfiguring the D-Bus server. - - - - - SEE ALSO - - intro - 8mandos, - mandos - 8, - mandos-ctl - 8 - - - -
- - - - - === removed file 'mandos-options.xml' --- mandos-options.xml 2019-07-25 21:42:40 +0000 +++ mandos-options.xml 1970-01-01 00:00:00 +0000 @@ -1,125 +0,0 @@ - - - - - -
- - - <para id="interface"> - If this is specified, the server will only announce the service - and listen to requests on the specified network interface. - Default is to use all available interfaces. <emphasis - >Note:</emphasis> a failure to bind to the specified - interface is not considered critical, and the server will not - exit, but instead continue normally. - </para> - - <para id="address"> - If this option is used, the server will only listen to the - specified IPv6 address. If a link-local address is specified, an - interface should be set, since a link-local address is only valid - on a single interface. By default, the server will listen to all - available addresses. If set, this must normally be an IPv6 - address; an IPv4 address can only be specified using IPv4-mapped - IPv6 address syntax: <quote><systemitem class="ipaddress" - >::FFFF:192.0.2.3</systemitem ></quote>. (Only if IPv6 usage is - <emphasis>disabled</emphasis> (see below) must this be an IPv4 - address.) - </para> - - <para id="port"> - If this option is used, the server will bind to that port. By - default, the server will listen to an arbitrary port given by the - operating system. - </para> - - <para id="debug"> - If the server is run in debug mode, it will run in the foreground - and print a lot of debugging information. The default is to - <emphasis>not</emphasis> run in debug mode. - </para> - - <para id="priority"> - GnuTLS priority string for the <acronym>TLS</acronym> handshake. - The default is - <!-- ​ is Unicode ZERO WIDTH SPACE; allows line breaks --> - <quote><literal>SECURE128​:!CTYPE-X.509​:+CTYPE-RAWPK​:!RSA​:!VERS-ALL​:+VERS-TLS1.3​:%PROFILE_ULTRA</literal></quote> - when using raw public keys in TLS, and - <quote><literal>SECURE256​:!CTYPE-X.509​:+CTYPE-OPENPGP​:!RSA​:+SIGN-DSA-SHA256</literal></quote> - when using OpenPGP keys in TLS,. See <citerefentry><refentrytitle - >gnutls_priority_init</refentrytitle> - <manvolnum>3</manvolnum></citerefentry> for the syntax. - <emphasis>Warning</emphasis>: changing this may make the - <acronym>TLS</acronym> handshake fail, making server-client - communication impossible. Changing this option may also make the - network traffic decryptable by an attacker. - </para> - - <para id="servicename"> - Zeroconf service name. The default is - <quote><literal>Mandos</literal></quote>. This only needs to be - changed if for some reason is would be necessary to run more than - one server on the same <emphasis>host</emphasis>. This would not - normally be useful. If there are name collisions on the same - <emphasis>network</emphasis>, the newer server will automatically - rename itself to <quote><literal>Mandos #2</literal></quote>, and - so on; therefore, this option is not needed in that case. - </para> - - <para id="dbus"> - This option controls whether the server will provide a D-Bus - system bus interface. The default is to provide such an - interface. - </para> - - <para id="ipv6"> - This option controls whether the server will use IPv6 sockets and - addresses. The default is to use IPv6. This option should - <emphasis>never</emphasis> normally be turned off, <emphasis>even in - IPv4-only environments</emphasis>. This is because <citerefentry> - <refentrytitle>mandos-client</refentrytitle> - <manvolnum>8mandos</manvolnum></citerefentry> will normally use - IPv6 link-local addresses, and will not be able to find or connect - to the server if this option is turned off. <emphasis>Only - advanced users should consider changing this option</emphasis>. - </para> - - <para id="restore"> - This option controls whether the server will restore its state - from the last time it ran. Default is to restore last state. - </para> - - <para id="statedir"> - Directory to save (and restore) state in. Default is - <quote><filename - class="directory">/var/lib/mandos</filename></quote>. - </para> - - <para id="socket"> - If this option is used, the server will not create a new network - socket, but will instead use the supplied file descriptor. By - default, the server will create a new network socket. - </para> - - <para id="foreground"> - This option will make the server run in the foreground and not - write a PID file. The default is to <emphasis>not</emphasis> run - in the foreground, except in <option>debug</option> mode, which - implies this option. - </para> - - <para id="zeroconf"> - This option controls whether the server will announce its - existence using Zeroconf. Default is to use Zeroconf. If - Zeroconf is not used, a <option>port</option> number or a - <option>socket</option> is required. - </para> - -</section> === removed file 'mandos-to-cryptroot-unlock' --- mandos-to-cryptroot-unlock 2019-07-24 11:02:24 +0000 +++ mandos-to-cryptroot-unlock 1970-01-01 00:00:00 +0000 @@ -1,82 +0,0 @@ -#!/bin/sh -# -# Script to get password from plugin-runner to cryptroot-unlock -# -# Copyright © 2018 Teddy Hogeborn -# Copyright © 2018 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos 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. -# -# Mandos 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 Mandos. If not, see <http://www.gnu.org/licenses/>. -# -# Contact the authors at <mandos@recompile.se>. - -# This script is made to run in the initramfs, and must not be run in -# the normal system environment. - -# Temporary file for the password -passfile=$(mktemp -p /run -t mandos.XXXXXX) -trap "rm -f -- $passfile 2>/dev/null" EXIT - -# Disable the plugins which conflict with "askpass" as distributed by -# cryptsetup. -cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf - - --disable=askpass-fifo - --disable=password-prompt - --disable=plymouth -EOF - -# In case a password is retrieved by other means than by plugin-runner -# (such as typing it on the console into the prompt given by the -# "askpass" program), this dummy plugin will be made to exit -# successfully, thereby forcing plugin-runner to stop all its plugins -# and also exit itself. -cat <<-EOF > /lib/mandos/plugins.d/dummy - #!/bin/sh - - while [ -e /run/mandos-keep-running ]; do - sleep 1 - done - - # exit successfully to force plugin-runner to finish - exit 0 -EOF -chmod u=rwx,go=rx /lib/mandos/plugins.d/dummy - -# This file is the flag which keeps the dummy plugin running -touch /run/mandos-keep-running - -# Keep running plugin-runner and trying any password, until either a -# password is accepted by cryptroot-unlock, or plugin-runner fails, or -# the file /run/mandos-keep-running has been removed. -while command -v cryptroot-unlock >/dev/null 2>&1; do - /lib/mandos/plugin-runner > "$passfile" & - echo $! > /run/mandos-plugin-runner.pid - wait %% || break - - # Try this password ten times (or ten seconds) - for loop in 1 2 3 4 5 6 7 8 9 10; do - if [ -e /run/mandos-keep-running ]; then - cryptroot-unlock < "$passfile" >/dev/null 2>&1 && break 2 - sleep 1 - else - break 2 - fi - done -done - -exec >/dev/null 2>&1 - -rm -f /run/mandos-plugin-runner.pid /run/mandos-keep-running === removed file 'mandos.conf.xml' --- mandos.conf.xml 2019-06-20 18:54:10 +0000 +++ mandos.conf.xml 1970-01-01 00:00:00 +0000 @@ -1,308 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" - "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ -<!ENTITY CONFNAME "mandos.conf"> -<!ENTITY CONFPATH "<filename>/etc/mandos/mandos.conf</filename>"> -<!ENTITY TIMESTAMP "2019-06-20"> -<!ENTITY % common SYSTEM "common.ent"> -%common; -]> - -<refentry xmlns:xi="http://www.w3.org/2001/XInclude"> - <refentryinfo> - <title>Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - - - - - &CONFNAME; - 5 - - - - &CONFNAME; - - Configuration file for the Mandos server - - - - - &CONFPATH; - - - - DESCRIPTION - - The file &CONFPATH; is a simple configuration file for - mandos - 8, and is read by it at - startup. The configuration file starts with [DEFAULT] on a line by itself, followed by - any number of option=value entries, - with continuations in the style of RFC 822. option: value is also accepted. Note that - leading whitespace is removed from values. Lines beginning with - # or ; are ignored and may be used - to provide comments. - - - - - OPTIONS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FILES - - The file described here is &CONFPATH; - - - - - BUGS - - The [DEFAULT] is necessary because the Python - built-in module ConfigParser - requires it. - - - - - - EXAMPLE - - - No options are actually required: - - -[DEFAULT] - - - - - An example using all the options: - - -[DEFAULT] -# A configuration example -interface = enp1s0 -address = fe80::aede:48ff:fe71:f6f2 -port = 1025 -debug = True -priority = SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA:!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA -servicename = Daena -use_dbus = False -use_ipv6 = True -restore = True -statedir = /var/lib/mandos - - - - - - SEE ALSO - - intro - 8mandos, - gnutls_priority_init3, - mandos - 8, - mandos-clients.conf - 5 - - - - - - RFC 4291: IP Version 6 Addressing - Architecture - - - - - Section 2.2: Text Representation of - Addresses - - - - Section 2.5.5.2: IPv4-Mapped IPv6 - Address - - - - Section 2.5.6, Link-Local IPv6 Unicast - Addresses - - - The clients use IPv6 link-local addresses, which are - immediately usable since a link-local addresses is - automatically assigned to a network interface when it - is brought up. - - - - - - - - - Zeroconf - - - - Zeroconf is the network protocol standard used by clients - for finding the Mandos server on the local network. - - - - - - - - - - - === removed file 'mandos.lsm' --- mandos.lsm 2019-09-03 19:06:41 +0000 +++ mandos.lsm 1970-01-01 00:00:00 +0000 @@ -1,23 +0,0 @@ -Begin4 -Title: Mandos -Version: 1.8.9 -Entered-date: 2019-09-03 -Description: The Mandos system allows computers to have encrypted - root file systems and at the same time be capable of - remote and/or unattended reboots. -Keywords: boot, encryption, luks, cryptsetup, network, openpgp, - tls, dm-crypt -Author: teddy@recompile.se (Teddy Hogeborn), - belorn@recompile.se (Björn Påhlsson) -Maintained-by: teddy@recompile.se (Teddy Hogeborn), - belorn@recompile.se (Björn Påhlsson) -Primary-site: https://www.recompile.se/mandos - 234K mandos_1.8.9.orig.tar.gz -Alternate-site: ftp://ftp.recompile.se/pub/mandos - 234K mandos_1.8.9.orig.tar.gz -Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.7, and - various other libraries. While made for Debian - GNU/Linux, it is probably portable to other - distributions, but not other Unixes. -Copying-policy: GNU General Public License version 3.0 or later -End === removed file 'mandos.service' --- mandos.service 2017-08-20 14:14:14 +0000 +++ mandos.service 1970-01-01 00:00:00 +0000 @@ -1,35 +0,0 @@ -[Unit] -Description=Server of encrypted passwords to Mandos clients -Documentation=man:intro(8mandos) man:mandos(8) -## If the server is configured to listen to a specific IP or network -## interface, it may be necessary to change "network.target" to -## "network-online.target". -After=network.target -## If the server is configured to not use ZeroConf, these two lines -## become unnecessary and should be removed or commented out. -After=avahi-daemon.service -Requisite=avahi-daemon.service - -[Service] -## If the server's D-Bus interface is disabled, the "BusName" setting -## should be removed or commented out. -BusName=se.recompile.Mandos -ExecStart=/usr/sbin/mandos --foreground -Restart=always -KillMode=mixed -## Using socket activation won't work, because systemd always does -## bind() on the socket, and also won't announce the ZeroConf service. -#ExecStart=/usr/sbin/mandos --foreground --socket=0 -#StandardInput=socket -# Restrict what the Mandos daemon can do. Note that this also affects -# "checker" programs! -PrivateTmp=yes -PrivateDevices=yes -ProtectSystem=full -ProtectHome=yes -CapabilityBoundingSet=CAP_KILL CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_NET_RAW -ProtectKernelTunables=yes -ProtectControlGroups=yes - -[Install] -WantedBy=multi-user.target === removed file 'mandos.xml' --- mandos.xml 2019-07-24 06:16:09 +0000 +++ mandos.xml 1970-01-01 00:00:00 +0000 @@ -1,806 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8 - - - - &COMMANDNAME; - - Gives encrypted passwords to authenticated Mandos clients - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - DESCRIPTION - - &COMMANDNAME; is a server daemon which - handles incoming request for passwords for a pre-defined list of - client host computers. For an introduction, see - intro - 8mandos. The Mandos server - uses Zeroconf to announce itself on the local network, and uses - TLS to communicate securely with and to authenticate the - clients. The Mandos server uses IPv6 to allow Mandos clients to - use IPv6 link-local addresses, since the clients will probably - not have any other addresses configured (see ). Any authenticated client is then given - the stored pre-encrypted password for that specific client. - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - - - - - - Show a help message and exit - - - - - - - NAME - - NAME - - - - - - - - - - - - - - - - - - - - - - - - - - Run the server’s self-tests. This includes any unit - tests, etc. - - - - - - - - - - - - - - - - Set the debugging log level. - LEVEL is a string, one of - CRITICAL, - ERROR, - WARNING, - INFO, or - DEBUG, in order of - increasing verbosity. The default level is - WARNING. - - - - - - - - - - - - - - - - - - - - - - - Directory to search for configuration files. Default is - /etc/mandos. See - mandos.conf - 5 and - mandos-clients.conf - 5. - - - - - - - - - Prints the program version and exit. - - - - - - - - - - See also . - - - - - - - - - - - - - - - - - See also . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OVERVIEW - - - This program is the server part. It is a normal server program - and will run in a normal system environment, not in an initial - RAM disk environment. - - - - - NETWORK PROTOCOL - - The Mandos server announces itself as a Zeroconf service of type - _mandos._tcp. The Mandos - client connects to the announced address and port, and sends a - line of text where the first whitespace-separated field is the - protocol version, which currently is - 1. The client and server then - start a TLS protocol handshake with a slight quirk: the Mandos - server program acts as a TLS client while the - connecting Mandos client acts as a TLS server. - The Mandos client must supply a TLS public key, and the key ID - of this public key is used by the Mandos server to look up (in a - list read from clients.conf at start time) - which binary blob to give the client. No other authentication - or authorization is done by the server. - - - Mandos Protocol (Version 1) - - Mandos Client - Direction - Mandos Server - - - - Connect - -> - - - 1\r\n - -> - - - TLS handshake as TLS server - - <-> - TLS handshake as TLS client - - - - Public key (part of TLS handshake) - -> - - - - <- - Binary blob (client will assume OpenPGP data) - - - - <- - Close - -
-
- - - CHECKING - - The server will, by default, continually check that the clients - are still up. If a client has not been confirmed as being up - for some time, the client is assumed to be compromised and is no - longer eligible to receive the encrypted password. (Manual - intervention is required to re-enable a client.) The timeout, - extended timeout, checker program, and interval between checks - can be configured both globally and per client; see - mandos-clients.conf - 5. - - - - - APPROVAL - - The server can be configured to require manual approval for a - client before it is sent its secret. The delay to wait for such - approval and the default action (approve or deny) can be - configured both globally and per client; see - mandos-clients.conf - 5. By default all clients - will be approved immediately without delay. - - - This can be used to deny a client its secret if not manually - approved within a specified time. It can also be used to make - the server delay before giving a client its secret, allowing - optional manual denying of this specific client. - - - - - - LOGGING - - The server will send log message with various severity levels to - /dev/log. With the - option, it will log even more messages, - and also show them on the console. - - - - - PERSISTENT STATE - - Client settings, initially read from - clients.conf, are persistent across - restarts, and run-time changes will override settings in - clients.conf. However, if a setting is - changed (or a client added, or removed) in - clients.conf, this will take precedence. - - - - - D-BUS INTERFACE - - The server will by default provide a D-Bus system bus interface. - This interface will only be accessible by the root user or a - Mandos-specific user, if such a user exists. For documentation - of the D-Bus API, see the file DBUS-API. - - - - - EXIT STATUS - - The server will exit with a non-zero exit status only when a - critical error is encountered. - - - - - ENVIRONMENT - - - PATH - - - To start the configured checker (see ), the server uses - /bin/sh, which in turn uses - PATH to search for matching commands if - an absolute path is not given. See - sh1 - . - - - - - - - - FILES - - Use the option to change where - &COMMANDNAME; looks for its configurations - files. The default file names are listed here. - - - - /etc/mandos/mandos.conf - - - Server-global settings. See - mandos.conf - 5 for details. - - - - - /etc/mandos/clients.conf - - - List of clients and client-specific settings. See - mandos-clients.conf - 5 for details. - - - - - /run/mandos.pid - - - The file containing the process id of the - &COMMANDNAME; process started last. - Note: If the /run directory does not - exist, /var/run/mandos.pid will be - used instead. - - - - - /var/lib/mandos - - - Directory where persistent state will be saved. Change - this with the option. See - also the option. - - - - - /dev/log - - - The Unix domain socket to where local syslog messages are - sent. - - - - - /bin/sh - - - This is used to start the configured checker command for - each client. See - mandos-clients.conf - 5 for details. - - - - - - - - BUGS - - This server might, on especially fatal errors, emit a Python - backtrace. This could be considered a feature. - - - There is no fine-grained control over logging and debug output. - - - - - - EXAMPLE - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Run the server in debug mode, read configuration files from - the ~/mandos directory, - and use the Zeroconf service name Test to not - collide with any other official Mandos server on this host: - - - - -&COMMANDNAME; --debug --configdir ~/mandos --servicename Test - - - - - - Run the server normally, but only listen to one interface and - only on the link-local address on that interface: - - - - -&COMMANDNAME; --interface eth7 --address fe80::aede:48ff:fe71:f6f2 - - - - - - - SECURITY - - SERVER - - Running this &COMMANDNAME; server program - should not in itself present any security risk to the host - computer running it. The program switches to a non-root user - soon after startup. - - - - CLIENTS - - The server only gives out its stored data to clients which - does have the correct key ID of the stored key ID. This is - guaranteed by the fact that the client sends its public key in - the TLS handshake; this ensures it to be genuine. The server - computes the key ID of the key itself and looks up the key ID - in its list of clients. The clients.conf - file (see - mandos-clients.conf - 5) - must be made non-readable by anyone - except the user starting the server (usually root). - - - As detailed in , the status of all - client computers will continually be checked and be assumed - compromised if they are gone for too long. - - - For more details on client-side security, see - mandos-client - 8mandos. - - - - - - SEE ALSO - - intro - 8mandos, - mandos-clients.conf - 5, - mandos.conf - 5, - mandos-client - 8mandos, - sh - 1 - - - - - Zeroconf - - - - Zeroconf is the network protocol standard used by clients - for finding this Mandos server on the local network. - - - - - - Avahi - - - - Avahi is the library this server calls to implement - Zeroconf service announcements. - - - - - - GnuTLS - - - - GnuTLS is the library this server uses to implement TLS for - communicating securely with the client, and at the same time - confidently get the client’s public key. - - - - - - RFC 4291: IP Version 6 Addressing - Architecture - - - - - Section 2.2: Text Representation of - Addresses - - - - Section 2.5.5.2: IPv4-Mapped IPv6 - Address - - - - Section 2.5.6, Link-Local IPv6 Unicast - Addresses - - - The clients use IPv6 link-local addresses, which are - immediately usable since a link-local addresses is - automatically assigned to a network interfaces when it - is brought up. - - - - - - - - - RFC 5246: The Transport Layer Security (TLS) - Protocol Version 1.2 - - - - TLS 1.2 is the protocol implemented by GnuTLS. - - - - - - RFC 4880: OpenPGP Message Format - - - - The data sent to clients is binary encrypted OpenPGP data. - - - - - - RFC 7250: Using Raw Public Keys in Transport - Layer Security (TLS) and Datagram Transport Layer Security - (DTLS) - - - - This is implemented by GnuTLS version 3.6.6 and is, if - present, used by this server so that raw public keys can be - used. - - - - - - RFC 6091: Using OpenPGP Keys for Transport Layer - Security (TLS) Authentication - - - - This is implemented by GnuTLS before version 3.6.0 and is, - if present, used by this server so that OpenPGP keys can be - used. - - - - - -
- - - - - === removed directory 'network-hooks.d' === removed file 'network-hooks.d/bridge' --- network-hooks.d/bridge 2018-02-08 10:23:55 +0000 +++ network-hooks.d/bridge 1970-01-01 00:00:00 +0000 @@ -1,93 +0,0 @@ -#!/bin/sh -# -# This is an example of a Mandos client network hook. This hook -# brings up a bridge interface as specified in a separate -# configuration file. To be used, this file and any needed -# configuration file(s) should be copied into the -# /etc/mandos/network-hooks.d directory. -# -# Copyright © 2012-2018 Teddy Hogeborn -# Copyright © 2012-2018 Björn Påhlsson -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. - -set -e - -CONFIG="$MANDOSNETHOOKDIR/bridge.conf" - -addrtoif(){ - grep -liFe "$1" /sys/class/net/*/address \ - | sed -e 's,.*/\([^/]*\)/[^/]*,\1,' -e "/^${BRIDGE}\$/d" -} - -# Read config file, which must set "BRIDGE", "PORT_ADDRESSES", and -# optionally "IPADDRS" and "ROUTES". -if [ -e "$CONFIG" ]; then - . "$CONFIG" -fi - -if [ -z "$BRIDGE" ] || [ -z "$PORT_ADDRESSES" ]; then - exit -fi - -if [ -n "$DEVICE" ]; then - case "$DEVICE" in - *,"$BRIDGE"|*,"$BRIDGE",*|"$BRIDGE",*|"$BRIDGE") :;; - *) exit;; - esac -fi - -brctl="/sbin/brctl" -for b in "$brctl" /usr/sbin/brctl; do - if [ -e "$b" ]; then - brctl="$b" - break - fi -done - -do_start(){ - "$brctl" addbr "$BRIDGE" - for address in $PORT_ADDRESSES; do - interface=`addrtoif "$address"` - "$brctl" addif "$BRIDGE" "$interface" - ip link set dev "$interface" up - done - ip link set dev "$BRIDGE" up - sleep "${DELAY%%.*}" - if [ -n "$IPADDRS" ]; then - for ipaddr in $IPADDRS; do - ip addr add "$ipaddr" dev "$BRIDGE" - done - fi - if [ -n "$ROUTES" ]; then - for route in $ROUTES; do - ip route add "$route" dev "$BRIDGE" - done - fi -} - -do_stop(){ - ip link set dev "$BRIDGE" down - for address in $PORT_ADDRESSES; do - interface=`addrtoif "$address"` - ip link set dev "$interface" down - "$brctl" delif "$BRIDGE" "$interface" - done - "$brctl" delbr "$BRIDGE" -} - -case "${MODE:-$1}" in - start|stop) - do_"${MODE:-$1}" - ;; - files) - echo /bin/ip - echo "$brctl" - ;; - modules) - echo bridge - ;; -esac === removed file 'network-hooks.d/bridge.conf' --- network-hooks.d/bridge.conf 2011-12-31 13:25:58 +0000 +++ network-hooks.d/bridge.conf 1970-01-01 00:00:00 +0000 @@ -1,11 +0,0 @@ -## Required - -#BRIDGE=br0 - -#PORT_ADDRESSES="00:11:22:33:44:55 11:22:33:44:55:66" - -## Optional - -#IPADDRS="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" - -#ROUTES="192.0.2.0/24 2001:DB8::/32" === removed file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 2018-02-08 10:23:55 +0000 +++ network-hooks.d/openvpn 1970-01-01 00:00:00 +0000 @@ -1,66 +0,0 @@ -#!/bin/sh -# -# This is an example of a Mandos client network hook. This hook -# brings up an OpenVPN interface as specified in a separate -# configuration file. To be used, this file and any needed -# configuration file(s) should be copied into the -# /etc/mandos/network-hooks.d directory. -# -# Copyright © 2012-2018 Teddy Hogeborn -# Copyright © 2012-2018 Björn Påhlsson -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. - -set -e - -CONFIG="openvpn.conf" - -# Extract the "dev" setting from the config file -VPNDEVICE=`sed -n -e 's/[[:space:]]#.*//' \ - -e 's/^[[:space:]]*dev[[:space:]]\+//p' \ - "$MANDOSNETHOOKDIR/$CONFIG"` - -PIDFILE=/run/openvpn-mandos.pid - -# Exit if no device set in config -if [ -z "$VPNDEVICE" ]; then - exit -fi - -# Exit if DEVICE is set and it doesn't match the VPN interface -if [ -n "$DEVICE" ]; then - case "$DEVICE" in - *,"$VPNDEVICE"*|"$VPNDEVICE"*) :;; - *) exit;; - esac -fi - -openvpn=/usr/sbin/openvpn - -do_start(){ - "$openvpn" --cd "$MANDOSNETHOOKDIR" --daemon 'openvpn(Mandos)' \ - --writepid "$PIDFILE" --config "$CONFIG" - sleep "$DELAY" -} - -do_stop(){ - PID="`cat \"$PIDFILE\"`" - if [ "$PID" -gt 0 ]; then - kill "$PID" - fi -} - -case "${MODE:-$1}" in - start|stop) - do_"${MODE:-$1}" - ;; - files) - echo "$openvpn" - ;; - modules) - echo tun - ;; -esac === removed file 'network-hooks.d/openvpn.conf' --- network-hooks.d/openvpn.conf 2011-12-02 16:52:50 +0000 +++ network-hooks.d/openvpn.conf 1970-01-01 00:00:00 +0000 @@ -1,19 +0,0 @@ -# Sample OpenVPN configuration file -# Uncomment and change - see openvpn(8) - -# Network device. -#dev tun - -# Our remote peer -#remote 192.0.2.3 -#float 192.0.2.3 -#port 1194 - -# VPN endpoints -#ifconfig 10.1.0.1 10.1.0.2 - -# A pre-shared static key -#secret openvpn.key - -# Cipher -#cipher AES-128-CBC === removed file 'network-hooks.d/wireless' --- network-hooks.d/wireless 2018-02-08 10:23:55 +0000 +++ network-hooks.d/wireless 1970-01-01 00:00:00 +0000 @@ -1,165 +0,0 @@ -#!/bin/sh -# -# This is an example of a Mandos client network hook. This hook -# brings up a wireless interface as specified in a separate -# configuration file. To be used, this file and any needed -# configuration file(s) should be copied into the -# /etc/mandos/network-hooks.d directory. -# -# Copyright © 2012-2018 Teddy Hogeborn -# Copyright © 2012-2018 Björn Påhlsson -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without any warranty. - -set -e - -RUNDIR="/run" -CTRL="$RUNDIR/wpa_supplicant-global" -CTRLDIR="$RUNDIR/wpa_supplicant" -PIDFILE="$RUNDIR/wpa_supplicant-mandos.pid" - -CONFIG="$MANDOSNETHOOKDIR/wireless.conf" - -addrtoif(){ - grep -liFe "$1" /sys/class/net/*/address \ - | sed -e 's,.*/\([^/]*\)/[^/]*,\1,' -} - -# Read config file -if [ -e "$CONFIG" ]; then - . "$CONFIG" -else - exit -fi - -ifkeys=`sed -n -e 's/^ADDRESS_\([^=]*\)=.*/\1/p' "$CONFIG" | sort -u` - -# Exit if DEVICE is set and is not any of the wireless interfaces -if [ -n "$DEVICE" ]; then - while :; do - for KEY in $ifkeys; do - ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` - INTERFACE=`addrtoif "$ADDRESS"` - - case "$DEVICE" in - *,"$INTERFACE"|*,"$INTERFACE",*|"$INTERFACE",*|"$INTERFACE") - break 2;; - esac - done - exit - done -fi - -wpa_supplicant=/sbin/wpa_supplicant -wpa_cli=/sbin/wpa_cli -ip=/bin/ip - -# Used by the wpa_interface_* functions in the wireless.conf file -wpa_cli_set(){ - case "$1" in - ssid|psk) arg="\"$2\"" ;; - *) arg="$2" ;; - esac - "$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" set_network "$NETWORK" \ - "$1" "$arg" 2>&1 | sed -e '/^OK$/d' -} - -if [ $VERBOSITY -gt 0 ]; then - WPAS_OPTIONS="-d $WPAS_OPTIONS" -fi -if [ -n "$PIDFILE" ]; then - WPAS_OPTIONS="-P$PIDFILE $WPAS_OPTIONS" -fi - -do_start(){ - mkdir -m u=rwx,go= -p "$CTRLDIR" - "$wpa_supplicant" -B -g "$CTRL" -p "$CTRLDIR" $WPAS_OPTIONS - for KEY in $ifkeys; do - ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` - INTERFACE=`addrtoif "$ADDRESS"` - DRIVER=`eval 'echo "$WPA_DRIVER_'"$KEY"\"` - IFDELAY=`eval 'echo "$DELAY_'"$KEY"\"` - "$wpa_cli" -g "$CTRL" interface_add "$INTERFACE" "" \ - "${DRIVER:-wext}" "$CTRLDIR" > /dev/null \ - | sed -e '/^OK$/d' - NETWORK=`"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" add_network` - eval wpa_interface_"$KEY" - "$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" enable_network \ - "$NETWORK" | sed -e '/^OK$/d' - sleep "${IFDELAY:-$DELAY}" & - sleep=$! - while :; do - kill -0 $sleep 2>/dev/null || break - STATE=`"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" status \ - | sed -n -e 's/^wpa_state=//p'` - if [ "$STATE" = COMPLETED ]; then - while :; do - kill -0 $sleep 2>/dev/null || break 2 - UP=`cat /sys/class/net/"$INTERFACE"/operstate` - if [ "$UP" = up ]; then - kill $sleep 2>/dev/null - break 2 - fi - sleep 1 - done - fi - sleep 1 - done & - wait $sleep || : - IPADDRS=`eval 'echo "$IPADDRS_'"$KEY"\"` - if [ -n "$IPADDRS" ]; then - if [ "$IPADDRS" = dhcp ]; then - ipconfig -c dhcp -d "$INTERFACE" || : - #dhclient "$INTERFACE" - else - for ipaddr in $IPADDRS; do - "$ip" addr add "$ipaddr" dev "$INTERFACE" - done - fi - fi - ROUTES=`eval 'echo "$ROUTES_'"$KEY"\"` - if [ -n "$ROUTES" ]; then - for route in $ROUTES; do - "$ip" route add "$route" dev "$INTERFACE" - done - fi - done -} - -do_stop(){ - "$wpa_cli" -g "$CTRL" terminate 2>&1 | sed -e '/^OK$/d' - for KEY in $ifkeys; do - ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` - INTERFACE=`addrtoif "$ADDRESS"` - "$ip" addr show scope global permanent dev "$INTERFACE" \ - | while read type addr rest; do - case "$type" in - inet|inet6) - "$ip" addr del "$addr" dev "$INTERFACE" - ;; - esac - done - "$ip" link set dev "$INTERFACE" down - done -} - -case "${MODE:-$1}" in - start|stop) - do_"${MODE:-$1}" - ;; - files) - echo "$wpa_supplicant" - echo "$wpa_cli" - echo "$ip" - ;; - modules) - if [ "$IPADDRS" = dhcp ]; then - echo af_packet - fi - sed -n -e 's/#.*$//' -e 's/[ ]*$//' \ - -e 's/^MODULE_[^=]\+=//p' "$CONFIG" - ;; -esac === removed file 'network-hooks.d/wireless.conf' --- network-hooks.d/wireless.conf 2011-12-31 13:25:58 +0000 +++ network-hooks.d/wireless.conf 1970-01-01 00:00:00 +0000 @@ -1,23 +0,0 @@ -# Extra options for wpa_supplicant, if any -#WPAS_OPTIONS="" - -# wlan0 -ADDRESS_0=00:11:22:33:44:55 -MODULE_0=ath9k -#WPA_DRIVER_0=wext -wpa_interface_0(){ - # Use this format to set simple things: - wpa_cli_set ssid home - wpa_cli_set psk "secret passphrase" - # Use this format to do more complex things with wpa_cli: - #"$wpa_cli" -p "$CTRLDIR" -i "$INTERFACE" bssid "$NETWORK" 00:11:22:33:44:55 - #"$wpa_cli" -g "$CTRL" ping -} -#DELAY_0=10 -IPADDRS_0=dhcp -#IPADDRS_0="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" -#ROUTES_0="192.0.2.0/24 2001:DB8::/32" - -#ADDRESS_1=11:22:33:44:55:66 -#MODULE_1=... -#... === removed file 'overview.xml' --- overview.xml 2019-02-09 23:23:26 +0000 +++ overview.xml 1970-01-01 00:00:00 +0000 @@ -1,17 +0,0 @@ - - - - This is part of the Mandos system for allowing computers to have - encrypted root file systems and at the same time be capable of - remote and/or unattended reboots. The computers run a small client - program in the initial RAM disk environment which - will communicate with a server over a network. All network - communication is encrypted using TLS. The - clients are identified by the server using a TLS key; each client - has one unique to it. The server sends the clients an encrypted - password. The encrypted password is decrypted by the clients using - a separate OpenPGP key, and the password is then used to unlock the - root file system, whereupon the computers can continue booting - normally. - === renamed file 'plugin-runner.c' => 'plugbasedclient.c' --- plugin-runner.c 2019-07-07 20:50:21 +0000 +++ plugbasedclient.c 2008-07-29 03:35:39 +0000 @@ -1,1331 +1,348 @@ -/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* -*- coding: utf-8 -*- */ /* * Mandos plugin runner - Run Mandos plugins * - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-2018 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos is distributed in the hope that it will be useful, but + * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson + * + * 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 Mandos. If not, see . + * along with this program. If not, see + * . * - * Contact the authors at . + * Contact the authors at . */ -#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), - O_CLOEXEC, pipe2() */ -#include /* size_t, NULL */ -#include /* malloc(), exit(), EXIT_SUCCESS, - realloc() */ -#include /* bool, true, false */ -#include /* fileno(), fprintf(), - stderr, STDOUT_FILENO, fclose() */ -#include /* fstat(), struct stat, waitpid(), - WIFEXITED(), WEXITSTATUS(), wait(), - pid_t, uid_t, gid_t, getuid(), - getgid() */ -#include /* fd_set, select(), FD_ZERO(), - FD_SET(), FD_ISSET(), FD_CLR */ -#include /* wait(), waitpid(), WIFEXITED(), - WEXITSTATUS(), WTERMSIG() */ -#include /* struct stat, fstat(), S_ISREG() */ -#include /* and, or, not */ -#include /* struct dirent, scandirat() */ -#include /* fcntl(), F_GETFD, F_SETFD, - FD_CLOEXEC, write(), STDOUT_FILENO, - struct stat, fstat(), close(), - setgid(), setuid(), S_ISREG(), - faccessat() pipe2(), fork(), - _exit(), dup2(), fexecve(), read() - */ -#include /* fcntl(), F_GETFD, F_SETFD, - FD_CLOEXEC, openat(), scandirat(), - pipe2() */ -#include /* strsep, strlen(), strsignal(), - strcmp(), strncmp() */ -#include /* errno */ -#include /* struct argp_option, struct - argp_state, struct argp, - argp_parse(), ARGP_ERR_UNKNOWN, - ARGP_KEY_END, ARGP_KEY_ARG, - error_t */ -#include /* struct sigaction, sigemptyset(), - sigaddset(), sigaction(), - sigprocmask(), SIG_BLOCK, SIGCHLD, - SIG_UNBLOCK, kill(), sig_atomic_t - */ -#include /* errno, EBADF */ -#include /* intmax_t, PRIdMAX, strtoimax() */ -#include /* EX_OSERR, EX_USAGE, EX_IOERR, - EX_CONFIG, EX_UNAVAILABLE, EX_OK */ -#include /* errno */ -#include /* error() */ -#include /* fnmatch() */ - -#define BUFFER_SIZE 256 - -#define PDIR "/lib/mandos/plugins.d" -#define PHDIR "/lib/mandos/plugin-helpers" -#define AFILE "/conf/conf.d/mandos/plugin-runner.conf" - -const char *argp_program_version = "plugin-runner " VERSION; -const char *argp_program_bug_address = ""; - -typedef struct plugin{ - char *name; /* can be NULL or any plugin name */ - char **argv; - int argc; - char **environ; - int envc; - bool disabled; - - /* Variables used for running processes*/ +#include /* popen, fileno */ +#include /* and, or, not */ +#include /* DIR, opendir, stat, struct stat, waitpid, + WIFEXITED, WEXITSTATUS, wait */ +#include /* wait */ +#include /* DIR, opendir */ +#include /* stat, struct stat */ +#include /* stat, struct stat, chdir */ +#include /* EXIT_FAILURE */ +#include /* fd_set, select, FD_ZERO, FD_SET, + FD_ISSET */ +#include /* strlen, strcpy, strcat */ +#include /* true */ +#include /* waitpid, WIFEXITED, WEXITSTATUS */ +#include /* errno */ +#include /* argp */ + +struct process; + +typedef struct process{ pid_t pid; int fd; char *buffer; size_t buffer_size; size_t buffer_length; - bool eof; - volatile sig_atomic_t completed; - int status; + struct process *next; +} process; + +typedef struct plugin{ + char *name; /* can be "global" and any plugin name */ + char **argv; + int argc; struct plugin *next; } plugin; -static plugin *plugin_list = NULL; - -/* Gets an existing plugin based on name, - or if none is found, creates a new one */ -__attribute__((warn_unused_result)) -static plugin *getplugin(char *name){ - /* Check for existing plugin with that name */ - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if((p->name == name) - or (p->name and name and (strcmp(p->name, name) == 0))){ +plugin *getplugin(char *name, plugin **plugin_list){ + for (plugin *p = *plugin_list; p != NULL; p = p->next){ + if ((p->name == name) + or (p->name and name and (strcmp(p->name, name) == 0))){ return p; } } /* Create a new plugin */ - plugin *new_plugin = NULL; - do { - new_plugin = malloc(sizeof(plugin)); - } while(new_plugin == NULL and errno == EINTR); - if(new_plugin == NULL){ - return NULL; - } - char *copy_name = NULL; - if(name != NULL){ - do { - copy_name = strdup(name); - } while(copy_name == NULL and errno == EINTR); - if(copy_name == NULL){ - int e = errno; - free(new_plugin); - errno = e; - return NULL; - } - } - - *new_plugin = (plugin){ .name = copy_name, - .argc = 1, - .disabled = false, - .next = plugin_list }; - - do { - new_plugin->argv = malloc(sizeof(char *) * 2); - } while(new_plugin->argv == NULL and errno == EINTR); - if(new_plugin->argv == NULL){ - int e = errno; - free(copy_name); - free(new_plugin); - errno = e; - return NULL; - } - new_plugin->argv[0] = copy_name; + plugin *new_plugin = malloc(sizeof(plugin)); + if (new_plugin == NULL){ + perror("malloc"); + exit(EXIT_FAILURE); + } + new_plugin->name = name; + new_plugin->argv = malloc(sizeof(char *) * 2); + if (new_plugin->argv == NULL){ + perror("malloc"); + exit(EXIT_FAILURE); + } + new_plugin->argv[0] = name; new_plugin->argv[1] = NULL; - - do { - new_plugin->environ = malloc(sizeof(char *)); - } while(new_plugin->environ == NULL and errno == EINTR); - if(new_plugin->environ == NULL){ - int e = errno; - free(copy_name); - free(new_plugin->argv); - free(new_plugin); - errno = e; - return NULL; - } - new_plugin->environ[0] = NULL; - + new_plugin->argc = 1; /* Append the new plugin to the list */ - plugin_list = new_plugin; + new_plugin->next = *plugin_list; + *plugin_list = new_plugin; return new_plugin; } -/* Helper function for add_argument and add_environment */ -__attribute__((nonnull, warn_unused_result)) -static bool add_to_char_array(const char *new, char ***array, - int *len){ - /* Resize the pointed-to array to hold one more pointer */ - char **new_array = NULL; - do { - new_array = realloc(*array, sizeof(char *) - * (size_t) ((*len) + 2)); - } while(new_array == NULL and errno == EINTR); - /* Malloc check */ - if(new_array == NULL){ - return false; - } - *array = new_array; - /* Make a copy of the new string */ - char *copy; - do { - copy = strdup(new); - } while(copy == NULL and errno == EINTR); - if(copy == NULL){ - return false; - } - /* Insert the copy */ - (*array)[*len] = copy; - (*len)++; - /* Add a new terminating NULL pointer to the last element */ - (*array)[*len] = NULL; - return true; -} - -/* Add to a plugin's argument vector */ -__attribute__((nonnull(2), warn_unused_result)) -static bool add_argument(plugin *p, const char *arg){ - if(p == NULL){ - return false; - } - return add_to_char_array(arg, &(p->argv), &(p->argc)); -} - -/* Add to a plugin's environment */ -__attribute__((nonnull(2), warn_unused_result)) -static bool add_environment(plugin *p, const char *def, bool replace){ - if(p == NULL){ - return false; - } - /* namelen = length of name of environment variable */ - size_t namelen = (size_t)(strchrnul(def, '=') - def); - /* Search for this environment variable */ - for(char **envdef = p->environ; *envdef != NULL; envdef++){ - if(strncmp(*envdef, def, namelen + 1) == 0){ - /* It already exists */ - if(replace){ - char *new_envdef; - do { - new_envdef = realloc(*envdef, strlen(def) + 1); - } while(new_envdef == NULL and errno == EINTR); - if(new_envdef == NULL){ - return false; - } - *envdef = new_envdef; - strcpy(*envdef, def); - } - return true; - } - } - return add_to_char_array(def, &(p->environ), &(p->envc)); -} - -#ifndef O_CLOEXEC -/* - * Based on the example in the GNU LibC manual chapter 13.13 "File - * Descriptor Flags". - | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] | - */ -__attribute__((warn_unused_result)) -static int set_cloexec_flag(int fd){ - int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0)); - /* If reading the flags failed, return error indication now. */ - if(ret < 0){ - return ret; - } - /* Store modified flag word in the descriptor. */ - return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD, - ret | FD_CLOEXEC)); -} -#endif /* not O_CLOEXEC */ - - -/* Mark processes as completed when they exit, and save their exit - status. */ -static void handle_sigchld(__attribute__((unused)) int sig){ - int old_errno = errno; - while(true){ - plugin *proc = plugin_list; - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - if(pid == 0){ - /* Only still running child processes */ - break; - } - if(pid == -1){ - if(errno == ECHILD){ - /* No child processes */ - break; - } - error(0, errno, "waitpid"); - } - - /* A child exited, find it in process_list */ - while(proc != NULL and proc->pid != pid){ - proc = proc->next; - } - if(proc == NULL){ - /* Process not found in process list */ - continue; - } - proc->status = status; - proc->completed = 1; - } - errno = old_errno; -} - -/* Prints out a password to stdout */ -__attribute__((nonnull, warn_unused_result)) -static bool print_out_password(const char *buffer, size_t length){ - ssize_t ret; - for(size_t written = 0; written < length; written += (size_t)ret){ - ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written, - length - written)); - if(ret < 0){ - return false; - } - } - return true; -} - -/* Removes and free a plugin from the plugin list */ -__attribute__((nonnull)) -static void free_plugin(plugin *plugin_node){ - - for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){ - free(*arg); - } - free(plugin_node->name); - free(plugin_node->argv); - for(char **env = plugin_node->environ; *env != NULL; env++){ - free(*env); - } - free(plugin_node->environ); - free(plugin_node->buffer); - - /* Removes the plugin from the singly-linked list */ - if(plugin_node == plugin_list){ - /* First one - simple */ - plugin_list = plugin_list->next; - } else { - /* Second one or later */ - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if(p->next == plugin_node){ - p->next = plugin_node->next; - break; - } - } - } - - free(plugin_node); -} - -static void free_plugin_list(void){ - while(plugin_list != NULL){ - free_plugin(plugin_list); - } -} +void addarguments(plugin *p, char *arg){ + p->argv[p->argc] = arg; + p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2)); + if (p->argv == NULL){ + perror("malloc"); + exit(EXIT_FAILURE); + } + p->argc++; + p->argv[p->argc] = NULL; +} + +#define BUFFER_SIZE 256 + +const char *argp_program_version = + "plugbasedclient 0.9"; +const char *argp_program_bug_address = + ""; +static char doc[] = + "Mandos plugin runner -- Run Mandos plugins"; +/* A description of the arguments we accept. */ +static char args_doc[] = ""; int main(int argc, char *argv[]){ - char *plugindir = NULL; - char *pluginhelperdir = NULL; - char *argfile = NULL; - FILE *conffp; - struct dirent **direntries = NULL; + char plugindir[] = "plugins.d"; + size_t d_name_len, plugindir_len = sizeof(plugindir)-1; + DIR *dir; + struct dirent *dirst; struct stat st; - fd_set rfds_all; + fd_set rfds_orig; int ret, maxfd = 0; - ssize_t sret; - uid_t uid = 65534; - gid_t gid = 65534; - bool debug = false; - int exitstatus = EXIT_SUCCESS; - struct sigaction old_sigchld_action; - struct sigaction sigchld_action = { .sa_handler = handle_sigchld, - .sa_flags = SA_NOCLDSTOP }; - char **custom_argv = NULL; - int custom_argc = 0; - int dir_fd = -1; - - /* Establish a signal handler */ - sigemptyset(&sigchld_action.sa_mask); - ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD); - if(ret == -1){ - error(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto fallback; - } - ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action); - if(ret == -1){ - error(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto fallback; - } + process *process_list = NULL; /* The options we understand. */ struct argp_option options[] = { { .name = "global-options", .key = 'g', - .arg = "OPTION[,OPTION[,...]]", - .doc = "Options passed to all plugins" }, - { .name = "global-env", .key = 'G', - .arg = "VAR=value", - .doc = "Environment variable passed to all plugins" }, + .arg = "option[,option[,...]]", .flags = 0, + .doc = "Options effecting all plugins" }, { .name = "options-for", .key = 'o', - .arg = "PLUGIN:OPTION[,OPTION[,...]]", - .doc = "Options passed only to specified plugin" }, - { .name = "env-for", .key = 'E', - .arg = "PLUGIN:ENV=value", - .doc = "Environment variable passed to specified plugin" }, - { .name = "disable", .key = 'd', - .arg = "PLUGIN", - .doc = "Disable a specific plugin", .group = 1 }, - { .name = "enable", .key = 'e', - .arg = "PLUGIN", - .doc = "Enable a specific plugin", .group = 1 }, - { .name = "plugin-dir", .key = 128, - .arg = "DIRECTORY", - .doc = "Specify a different plugin directory", .group = 2 }, - { .name = "config-file", .key = 129, - .arg = "FILE", - .doc = "Specify a different configuration file", .group = 2 }, - { .name = "userid", .key = 130, - .arg = "ID", .flags = 0, - .doc = "User ID the plugins will run as", .group = 3 }, - { .name = "groupid", .key = 131, - .arg = "ID", .flags = 0, - .doc = "Group ID the plugins will run as", .group = 3 }, - { .name = "debug", .key = 132, - .doc = "Debug mode", .group = 4 }, - { .name = "plugin-helper-dir", .key = 133, - .arg = "DIRECTORY", - .doc = "Specify a different plugin helper directory", - .group = 2 }, - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - { .name = "help", .key = '?', - .doc = "Give this help list", .group = -1 }, - { .name = "usage", .key = -3, - .doc = "Give a short usage message", .group = -1 }, - { .name = "version", .key = 'V', - .doc = "Print program version", .group = -1 }, + .arg = "plugin:option[,option[,...]]", .flags = 0, + .doc = "Options effecting only specified plugins" }, { .name = NULL } }; - __attribute__((nonnull(3))) - error_t parse_opt(int key, char *arg, struct argp_state *state){ - errno = 0; - switch(key){ - char *tmp; - intmax_t tmp_id; - case 'g': /* --global-options */ - { - char *plugin_option; - while((plugin_option = strsep(&arg, ",")) != NULL){ - if(not add_argument(getplugin(NULL), plugin_option)){ - break; - } - } - errno = 0; - } - break; - case 'G': /* --global-env */ - if(add_environment(getplugin(NULL), arg, true)){ - errno = 0; - } - break; - case 'o': /* --options-for */ - { - char *option_list = strchr(arg, ':'); - if(option_list == NULL){ - argp_error(state, "No colon in \"%s\"", arg); - errno = EINVAL; - break; - } - *option_list = '\0'; - option_list++; - if(arg[0] == '\0'){ - argp_error(state, "Empty plugin name"); - errno = EINVAL; - break; - } - char *option; - while((option = strsep(&option_list, ",")) != NULL){ - if(not add_argument(getplugin(arg), option)){ - break; - } - } - errno = 0; - } - break; - case 'E': /* --env-for */ - { - char *envdef = strchr(arg, ':'); - if(envdef == NULL){ - argp_error(state, "No colon in \"%s\"", arg); - errno = EINVAL; - break; - } - *envdef = '\0'; - envdef++; - if(arg[0] == '\0'){ - argp_error(state, "Empty plugin name"); - errno = EINVAL; - break; - } - if(add_environment(getplugin(arg), envdef, true)){ - errno = 0; - } - } - break; - case 'd': /* --disable */ - { - plugin *p = getplugin(arg); - if(p != NULL){ - p->disabled = true; - errno = 0; - } - } - break; - case 'e': /* --enable */ - { - plugin *p = getplugin(arg); - if(p != NULL){ - p->disabled = false; - errno = 0; - } - } - break; - case 128: /* --plugin-dir */ - free(plugindir); - plugindir = strdup(arg); - if(plugindir != NULL){ - errno = 0; - } - break; - case 129: /* --config-file */ - /* This is already done by parse_opt_config_file() */ - break; - case 130: /* --userid */ - tmp_id = strtoimax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (uid_t)tmp_id){ - argp_error(state, "Bad user ID number: \"%s\", using %" - PRIdMAX, arg, (intmax_t)uid); - break; - } - uid = (uid_t)tmp_id; - errno = 0; - break; - case 131: /* --groupid */ - tmp_id = strtoimax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmp_id != (gid_t)tmp_id){ - argp_error(state, "Bad group ID number: \"%s\", using %" - PRIdMAX, arg, (intmax_t)gid); - break; - } - gid = (gid_t)tmp_id; - errno = 0; - break; - case 132: /* --debug */ - debug = true; - break; - case 133: /* --plugin-helper-dir */ - free(pluginhelperdir); - pluginhelperdir = strdup(arg); - if(pluginhelperdir != NULL){ - errno = 0; - } - break; - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - case '?': /* --help */ - state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ - argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); - __builtin_unreachable(); - case -3: /* --usage */ - state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ - argp_state_help(state, state->out_stream, - ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK); - __builtin_unreachable(); - case 'V': /* --version */ - fprintf(state->out_stream, "%s\n", argp_program_version); - exit(EXIT_SUCCESS); - break; -/* - * When adding more options before this line, remember to also add a - * "case" to the "parse_opt_config_file" function below. - */ - case ARGP_KEY_ARG: - /* Cryptsetup always passes an argument, which is an empty - string if "none" was specified in /etc/crypttab. So if - argument was empty, we ignore it silently. */ - if(arg[0] == '\0'){ - break; - } -#if __GNUC__ >= 7 - __attribute__((fallthrough)); -#else - /* FALLTHROUGH */ -#endif - default: - return ARGP_ERR_UNKNOWN; - } - return errno; /* Set to 0 at start */ - } - - /* This option parser is the same as parse_opt() above, except it - ignores everything but the --config-file option. */ - error_t parse_opt_config_file(int key, char *arg, - __attribute__((unused)) - struct argp_state *state){ - errno = 0; - switch(key){ - case 'g': /* --global-options */ - case 'G': /* --global-env */ - case 'o': /* --options-for */ - case 'E': /* --env-for */ - case 'd': /* --disable */ - case 'e': /* --enable */ - case 128: /* --plugin-dir */ - break; - case 129: /* --config-file */ - free(argfile); - argfile = strdup(arg); - if(argfile != NULL){ - errno = 0; - } - break; - case 130: /* --userid */ - case 131: /* --groupid */ - case 132: /* --debug */ - case 133: /* --plugin-helper-dir */ - case '?': /* --help */ - case -3: /* --usage */ - case 'V': /* --version */ - case ARGP_KEY_ARG: - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, - .parser = parse_opt_config_file, - .args_doc = "", - .doc = "Mandos plugin runner -- Run plugins" }; - - /* Parse using parse_opt_config_file() in order to get the custom - config file location, if any. */ - ret = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, - NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error(0, errno, "argp_parse"); - exitstatus = EX_OSERR; - goto fallback; - case EINVAL: - exitstatus = EX_USAGE; - goto fallback; - } - - /* Reset to the normal argument parser */ - argp.parser = parse_opt; - - /* Open the configfile if available */ - if(argfile == NULL){ - conffp = fopen(AFILE, "r"); - } else { - conffp = fopen(argfile, "r"); - } - if(conffp != NULL){ - char *org_line = NULL; - char *p, *arg, *new_arg, *line; - size_t size = 0; - const char whitespace_delims[] = " \r\t\f\v\n"; - const char comment_delim[] = "#"; - - custom_argc = 1; - custom_argv = malloc(sizeof(char*) * 2); - if(custom_argv == NULL){ - error(0, errno, "malloc"); - exitstatus = EX_OSERR; - goto fallback; - } - custom_argv[0] = argv[0]; - custom_argv[1] = NULL; - - /* for each line in the config file, strip whitespace and ignore - commented text */ + error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the INPUT argument from `argp_parse', which we + know is a pointer to our arguments structure. */ + plugin **plugins = state->input; + switch (key) { + case 'g': + if (arg != NULL){ + char *p = strtok(arg, ","); + do{ + addarguments(getplugin(NULL, plugins), p); + p = strtok(NULL, ","); + } while (p); + } + break; + case 'o': + if (arg != NULL){ + char *name = strtok(arg, ":"); + char *p = strtok(NULL, ":"); + p = strtok(p, ","); + do{ + addarguments(getplugin(name, plugins), p); + p = strtok(NULL, ","); + } while (p); + } + break; + case ARGP_KEY_ARG: + argp_usage (state); + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + + plugin *plugin_list = NULL; + + struct argp argp = { .options = options, .parser = parse_opt, + .args_doc = args_doc, .doc = doc }; + + argp_parse (&argp, argc, argv, 0, 0, &plugin_list); + +/* for(plugin *p = plugin_list; p != NULL; p=p->next){ */ +/* fprintf(stderr, "Plugin: %s has %d arguments\n", p->name ? p->name : "Global", p->argc); */ +/* for(char **a = p->argv + 1; *a != NULL; a++){ */ +/* fprintf(stderr, "\tArg: %s\n", *a); */ +/* } */ +/* } */ + +/* return 0; */ + + dir = opendir(plugindir); + + if(dir == NULL){ + fprintf(stderr, "Can not open directory\n"); + return EXIT_FAILURE; + } + + FD_ZERO(&rfds_orig); + + while(true){ + dirst = readdir(dir); + + // All directory entries have been processed + if(dirst == NULL){ + break; + } + + d_name_len = strlen(dirst->d_name); + + // Ignore dotfiles and backup files + if (dirst->d_name[0] == '.' + or dirst->d_name[d_name_len - 1] == '~'){ + continue; + } + + char *filename = malloc(d_name_len + plugindir_len + 2); + strcpy(filename, plugindir); + strcat(filename, "/"); + strcat(filename, dirst->d_name); + + stat(filename, &st); + + if (S_ISREG(st.st_mode) and (access(filename, X_OK) == 0)){ + // Starting a new process to be watched + process *new_process = malloc(sizeof(process)); + int pipefd[2]; + ret = pipe(pipefd); + if (ret == -1){ + perror(argv[0]); + goto end; + } + new_process->pid = fork(); + if(new_process->pid == 0){ + /* this is the child process */ + closedir(dir); + close(pipefd[0]); /* close unused read end of pipe */ + dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ + char *basename; + basename = strrchr(filename, '/'); + if (basename == NULL){ + basename = filename; + } else { + basename++; + } + plugin *p = getplugin(basename, &plugin_list); + + plugin *g = getplugin(NULL, &plugin_list); + for(char **a = g->argv + 1; *a != NULL; a++){ + addarguments(p, *a); + } + if(execv(filename, p->argv) < 0){ + perror(argv[0]); + close(pipefd[1]); + exit(EXIT_FAILURE); + } + /* no return */ + } + close(pipefd[1]); /* close unused write end of pipe */ + new_process->fd = pipefd[0]; + new_process->buffer = malloc(BUFFER_SIZE); + if (new_process->buffer == NULL){ + perror(argv[0]); + goto end; + } + new_process->buffer_size = BUFFER_SIZE; + new_process->buffer_length = 0; + FD_SET(new_process->fd, &rfds_orig); + + if (maxfd < new_process->fd){ + maxfd = new_process->fd; + } + + //List handling + new_process->next = process_list; + process_list = new_process; + } + } + + closedir(dir); + + if (process_list != NULL){ while(true){ - sret = getline(&org_line, &size, conffp); - if(sret == -1){ - break; - } - - line = org_line; - arg = strsep(&line, comment_delim); - while((p = strsep(&arg, whitespace_delims)) != NULL){ - if(p[0] == '\0'){ - continue; - } - new_arg = strdup(p); - if(new_arg == NULL){ - error(0, errno, "strdup"); - exitstatus = EX_OSERR; - free(org_line); - goto fallback; - } - - custom_argc += 1; - { - char **new_argv = realloc(custom_argv, sizeof(char *) - * ((size_t)custom_argc + 1)); - if(new_argv == NULL){ - error(0, errno, "realloc"); - exitstatus = EX_OSERR; - free(new_arg); - free(org_line); - goto fallback; - } else { - custom_argv = new_argv; - } - } - custom_argv[custom_argc-1] = new_arg; - custom_argv[custom_argc] = NULL; - } - } - do { - ret = fclose(conffp); - } while(ret == EOF and errno == EINTR); - if(ret == EOF){ - error(0, errno, "fclose"); - exitstatus = EX_IOERR; - goto fallback; - } - free(org_line); - } else { - /* Check for harmful errors and go to fallback. Other errors might - not affect opening plugins */ - if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){ - error(0, errno, "fopen"); - exitstatus = EX_OSERR; - goto fallback; - } - } - /* If there were any arguments from the configuration file, pass - them to parser as command line arguments */ - if(custom_argv != NULL){ - ret = argp_parse(&argp, custom_argc, custom_argv, - ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, - NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error(0, errno, "argp_parse"); - exitstatus = EX_OSERR; - goto fallback; - case EINVAL: - exitstatus = EX_CONFIG; - goto fallback; - } - } - - /* Parse actual command line arguments, to let them override the - config file */ - ret = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, - NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error(0, errno, "argp_parse"); - exitstatus = EX_OSERR; - goto fallback; - case EINVAL: - exitstatus = EX_USAGE; - goto fallback; - } - - { - char *pluginhelperenv; - bool bret = true; - ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s", - pluginhelperdir != NULL ? pluginhelperdir : PHDIR); - if(ret != -1){ - bret = add_environment(getplugin(NULL), pluginhelperenv, true); - } - if(ret == -1 or not bret){ - error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR" - " environment variable to \"%s\" for all plugins\n", - pluginhelperdir != NULL ? pluginhelperdir : PHDIR); - } - if(ret != -1){ - free(pluginhelperenv); - } - } - - if(debug){ - for(plugin *p = plugin_list; p != NULL; p = p->next){ - fprintf(stderr, "Plugin: %s has %d arguments\n", - p->name ? p->name : "Global", p->argc - 1); - for(char **a = p->argv; *a != NULL; a++){ - fprintf(stderr, "\tArg: %s\n", *a); - } - fprintf(stderr, "...and %d environment variables\n", p->envc); - for(char **a = p->environ; *a != NULL; a++){ - fprintf(stderr, "\t%s\n", *a); - } - } - } - - if(getuid() == 0){ - /* Work around Debian bug #633582: - */ - int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY); - if(plugindir_fd == -1){ - if(errno != ENOENT){ - error(0, errno, "open(\"" PDIR "\")"); - } - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st)); - if(ret == -1){ - error(0, errno, "fstat"); - } else { - if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(plugindir_fd, uid, gid); - if(ret == -1){ - error(0, errno, "fchown"); - } - } - } - close(plugindir_fd); - } - } - - /* Lower permissions */ - ret = setgid(gid); - if(ret == -1){ - error(0, errno, "setgid"); - } - ret = setuid(uid); - if(ret == -1){ - error(0, errno, "setuid"); - } - - /* Open plugin directory with close_on_exec flag */ - { - dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY | -#ifdef O_CLOEXEC - O_CLOEXEC -#else /* not O_CLOEXEC */ - 0 -#endif /* not O_CLOEXEC */ - ); - if(dir_fd == -1){ - error(0, errno, "Could not open plugin dir"); - exitstatus = EX_UNAVAILABLE; - goto fallback; - } - -#ifndef O_CLOEXEC - /* Set the FD_CLOEXEC flag on the directory */ - ret = set_cloexec_flag(dir_fd); - if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - exitstatus = EX_OSERR; - goto fallback; - } -#endif /* O_CLOEXEC */ - } - - int good_name(const struct dirent * const dirent){ - const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new", - "*.dpkg-old", "*.dpkg-bak", - "*.dpkg-divert", NULL }; -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - for(const char **pat = (const char **)patterns; - *pat != NULL; pat++){ -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD) - != FNM_NOMATCH){ - if(debug){ - fprintf(stderr, "Ignoring plugin dir entry \"%s\"" - " matching pattern %s\n", dirent->d_name, *pat); - } - return 0; - } - } - return 1; - } - - int numplugins = scandirat(dir_fd, ".", &direntries, good_name, - alphasort); - if(numplugins == -1){ - error(0, errno, "Could not scan plugin dir"); - direntries = NULL; - exitstatus = EX_OSERR; - goto fallback; - } - - FD_ZERO(&rfds_all); - - /* Read and execute any executable in the plugin directory*/ - for(int i = 0; i < numplugins; i++){ - - int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY); - if(plugin_fd == -1){ - error(0, errno, "Could not open plugin"); - free(direntries[i]); - continue; - } - ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st)); - if(ret == -1){ - error(0, errno, "stat"); - close(plugin_fd); - free(direntries[i]); - continue; - } - - /* Ignore non-executable files */ - if(not S_ISREG(st.st_mode) - or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name, - X_OK, 0)) != 0)){ - if(debug){ - fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\"" - " with bad type or mode\n", - plugindir != NULL ? plugindir : PDIR, - direntries[i]->d_name); - } - close(plugin_fd); - free(direntries[i]); - continue; - } - - plugin *p = getplugin(direntries[i]->d_name); - if(p == NULL){ - error(0, errno, "getplugin"); - close(plugin_fd); - free(direntries[i]); - continue; - } - if(p->disabled){ - if(debug){ - fprintf(stderr, "Ignoring disabled plugin \"%s\"\n", - direntries[i]->d_name); - } - close(plugin_fd); - free(direntries[i]); - continue; - } - { - /* Add global arguments to argument list for this plugin */ - plugin *g = getplugin(NULL); - if(g != NULL){ - for(char **a = g->argv + 1; *a != NULL; a++){ - if(not add_argument(p, *a)){ - error(0, errno, "add_argument"); - } - } - /* Add global environment variables */ - for(char **e = g->environ; *e != NULL; e++){ - if(not add_environment(p, *e, false)){ - error(0, errno, "add_environment"); - } - } - } - } - /* If this plugin has any environment variables, we need to - duplicate the environment from this process, too. */ - if(p->environ[0] != NULL){ - for(char **e = environ; *e != NULL; e++){ - if(not add_environment(p, *e, false)){ - error(0, errno, "add_environment"); - } - } - } - - int pipefd[2]; -#ifndef O_CLOEXEC - ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd)); -#else /* O_CLOEXEC */ - ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC)); -#endif /* O_CLOEXEC */ - if(ret == -1){ - error(0, errno, "pipe"); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } - if(pipefd[0] >= FD_SETSIZE){ - fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0], - FD_SETSIZE); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } -#ifndef O_CLOEXEC - /* Ask OS to automatic close the pipe on exec */ - ret = set_cloexec_flag(pipefd[0]); - if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } - ret = set_cloexec_flag(pipefd[1]); - if(ret < 0){ - error(0, errno, "set_cloexec_flag"); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } -#endif /* not O_CLOEXEC */ - /* Block SIGCHLD until process is safely in process list */ - ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK, - &sigchld_action.sa_mask, - NULL)); - if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } - /* Starting a new process to be watched */ - pid_t pid; - do { - pid = fork(); - } while(pid == -1 and errno == EINTR); - if(pid == -1){ - error(0, errno, "fork"); - TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK, - &sigchld_action.sa_mask, NULL)); - close(pipefd[0]); - close(pipefd[1]); - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } - if(pid == 0){ - /* this is the child process */ - ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); - if(ret < 0){ - error(0, errno, "sigaction"); - _exit(EX_OSERR); - } - ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); - if(ret < 0){ - error(0, errno, "sigprocmask"); - _exit(EX_OSERR); - } - - ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ - if(ret == -1){ - error(0, errno, "dup2"); - _exit(EX_OSERR); - } - - if(fexecve(plugin_fd, p->argv, - (p->environ[0] != NULL) ? p->environ : environ) < 0){ - error(0, errno, "fexecve for %s/%s", - plugindir != NULL ? plugindir : PDIR, - direntries[i]->d_name); - _exit(EX_OSERR); - } - /* no return */ - } - /* Parent process */ - close(pipefd[1]); /* Close unused write end of pipe */ - close(plugin_fd); - plugin *new_plugin = getplugin(direntries[i]->d_name); - if(new_plugin == NULL){ - error(0, errno, "getplugin"); - ret = (int)(TEMP_FAILURE_RETRY - (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, - NULL))); - if(ret < 0){ - error(0, errno, "sigprocmask"); - } - exitstatus = EX_OSERR; - free(direntries[i]); - goto fallback; - } - free(direntries[i]); - - new_plugin->pid = pid; - new_plugin->fd = pipefd[0]; - - if(debug){ - fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n", - new_plugin->name, (intmax_t) (new_plugin->pid)); - } - - /* Unblock SIGCHLD so signal handler can be run if this process - has already completed */ - ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK, - &sigchld_action.sa_mask, - NULL)); - if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; - goto fallback; - } - - FD_SET(new_plugin->fd, &rfds_all); - - if(maxfd < new_plugin->fd){ - maxfd = new_plugin->fd; - } - } - - free(direntries); - direntries = NULL; - close(dir_fd); - dir_fd = -1; - free_plugin(getplugin(NULL)); - - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if(p->pid != 0){ - break; - } - if(p->next == NULL){ - fprintf(stderr, "No plugin processes started. Incorrect plugin" - " directory?\n"); - free_plugin_list(); - } - } - - /* Main loop while running plugins exist */ - while(plugin_list){ - fd_set rfds = rfds_all; - int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); - if(select_ret == -1 and errno != EINTR){ - error(0, errno, "select"); - exitstatus = EX_OSERR; - goto fallback; - } - /* OK, now either a process completed, or something can be read - from one of them */ - for(plugin *proc = plugin_list; proc != NULL;){ - /* Is this process completely done? */ - if(proc->completed and proc->eof){ - /* Only accept the plugin output if it exited cleanly */ - if(not WIFEXITED(proc->status) - or WEXITSTATUS(proc->status) != 0){ - /* Bad exit by plugin */ - - if(debug){ - if(WIFEXITED(proc->status)){ - fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with" - " status %d\n", proc->name, - (intmax_t) (proc->pid), - WEXITSTATUS(proc->status)); - } else if(WIFSIGNALED(proc->status)){ - fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by" - " signal %d: %s\n", proc->name, - (intmax_t) (proc->pid), - WTERMSIG(proc->status), - strsignal(WTERMSIG(proc->status))); - } - } - - /* Remove the plugin */ - FD_CLR(proc->fd, &rfds_all); - - /* Block signal while modifying process_list */ - ret = (int)TEMP_FAILURE_RETRY(sigprocmask - (SIG_BLOCK, - &sigchld_action.sa_mask, - NULL)); - if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; - goto fallback; - } - - plugin *next_plugin = proc->next; - free_plugin(proc); - proc = next_plugin; - - /* We are done modifying process list, so unblock signal */ - ret = (int)(TEMP_FAILURE_RETRY - (sigprocmask(SIG_UNBLOCK, - &sigchld_action.sa_mask, NULL))); - if(ret < 0){ - error(0, errno, "sigprocmask"); - exitstatus = EX_OSERR; - goto fallback; - } - - if(plugin_list == NULL){ - break; - } - - continue; - } - - /* This process exited nicely, so print its buffer */ - - bool bret = print_out_password(proc->buffer, - proc->buffer_length); - if(not bret){ - error(0, errno, "print_out_password"); - exitstatus = EX_IOERR; - } - goto fallback; - } - - /* This process has not completed. Does it have any output? */ - if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ - /* This process had nothing to say at this time */ - proc = proc->next; - continue; - } - /* Before reading, make the process' data buffer large enough */ - if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){ - char *new_buffer = realloc(proc->buffer, proc->buffer_size - + (size_t) BUFFER_SIZE); - if(new_buffer == NULL){ - error(0, errno, "malloc"); - exitstatus = EX_OSERR; - goto fallback; - } - proc->buffer = new_buffer; - proc->buffer_size += BUFFER_SIZE; - } - /* Read from the process */ - sret = TEMP_FAILURE_RETRY(read(proc->fd, - proc->buffer - + proc->buffer_length, - BUFFER_SIZE)); - if(sret < 0){ - /* Read error from this process; ignore the error */ - proc = proc->next; - continue; - } - if(sret == 0){ - /* got EOF */ - proc->eof = true; - } else { - proc->buffer_length += (size_t) sret; - } - } - } - - - fallback: - - if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS - and exitstatus != EX_OK)){ - /* Fallback if all plugins failed, none are found or an error - occured */ - bool bret; - fprintf(stderr, "Going to fallback mode using getpass(3)\n"); - char *passwordbuffer = getpass("Password: "); - size_t len = strlen(passwordbuffer); - /* Strip trailing newline */ - if(len > 0 and passwordbuffer[len-1] == '\n'){ - passwordbuffer[len-1] = '\0'; /* not strictly necessary */ - len--; - } - bret = print_out_password(passwordbuffer, len); - if(not bret){ - error(0, errno, "print_out_password"); - exitstatus = EX_IOERR; - } - } - - /* Restore old signal handler */ - ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); - if(ret == -1){ - error(0, errno, "sigaction"); - exitstatus = EX_OSERR; - } - - if(custom_argv != NULL){ - for(char **arg = custom_argv+1; *arg != NULL; arg++){ - free(*arg); - } - free(custom_argv); - } - - free(direntries); - - if(dir_fd != -1){ - close(dir_fd); - } - - /* Kill the processes */ - for(plugin *p = plugin_list; p != NULL; p = p->next){ - if(p->pid != 0){ - close(p->fd); - ret = kill(p->pid, SIGTERM); - if(ret == -1 and errno != ESRCH){ - /* Set-uid proccesses might not get closed */ - error(0, errno, "kill"); - } - } - } - - /* Wait for any remaining child processes to terminate */ - do { - ret = wait(NULL); - } while(ret >= 0); - if(errno != ECHILD){ - error(0, errno, "wait"); - } - - free_plugin_list(); - - free(plugindir); - free(pluginhelperdir); - free(argfile); - - return exitstatus; + fd_set rfds = rfds_orig; + int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); + if (select_ret == -1){ + perror(argv[0]); + goto end; + }else{ + for(process *process_itr = process_list; process_itr != NULL; + process_itr = process_itr->next){ + if(FD_ISSET(process_itr->fd, &rfds)){ + if(process_itr->buffer_length + BUFFER_SIZE + > process_itr->buffer_size){ + process_itr->buffer = realloc(process_itr->buffer, + process_itr->buffer_size + + (size_t) BUFFER_SIZE); + if (process_itr->buffer == NULL){ + perror(argv[0]); + goto end; + } + process_itr->buffer_size += BUFFER_SIZE; + } + ret = read(process_itr->fd, process_itr->buffer + + process_itr->buffer_length, BUFFER_SIZE); + if(ret < 0){ + /* Read error from this process; ignore it */ + continue; + } + process_itr->buffer_length += (size_t) ret; + if(ret == 0){ + /* got EOF */ + /* wait for process exit */ + int status; + waitpid(process_itr->pid, &status, 0); + if(WIFEXITED(status) and WEXITSTATUS(status) == 0){ + for(size_t written = 0; + written < process_itr->buffer_length;){ + ret = write(STDOUT_FILENO, + process_itr->buffer + written, + process_itr->buffer_length - written); + if(ret < 0){ + perror(argv[0]); + goto end; + } + written += (size_t)ret; + } + goto end; + } else { + FD_CLR(process_itr->fd, &rfds_orig); + } + } + } + } + } + } + } + + end: + for(process *process_itr = process_list; process_itr != NULL; + process_itr = process_itr->next){ + close(process_itr->fd); + kill(process_itr->pid, SIGTERM); + free(process_itr->buffer); + } + + while(true){ + int status; + ret = wait(&status); + if (ret == -1){ + if(errno != ECHILD){ + perror("wait"); + } + break; + } + } + return EXIT_SUCCESS; } === removed directory 'plugin-helpers' === removed file 'plugin-helpers/mandos-client-iprouteadddel.c' --- plugin-helpers/mandos-client-iprouteadddel.c 2018-02-18 01:29:21 +0000 +++ plugin-helpers/mandos-client-iprouteadddel.c 1970-01-01 00:00:00 +0000 @@ -1,282 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * iprouteadddel - Add or delete direct route to a local IP address - * - * Copyright © 2015-2018 Teddy Hogeborn - * Copyright © 2015-2018 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos 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 Mandos. If not, see . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE /* program_invocation_short_name */ -#include /* bool, false, true */ -#include /* fprintf(), stderr, FILE, vfprintf */ -#include /* program_invocation_short_name, - errno, perror(), EINVAL, ENOMEM */ -#include /* va_list, va_start */ -#include /* EXIT_SUCCESS */ -#include /* struct argp_option, error_t, struct - argp_state, ARGP_KEY_ARG, - argp_usage(), ARGP_KEY_END, - ARGP_ERR_UNKNOWN, struct argp, - argp_parse() */ -#include /* EX_USAGE, EX_OSERR */ -#include /* sa_family_t, AF_INET6, AF_INET */ -#include /* PRIdMAX, intmax_t */ - -#include /* struct nl_addr, nl_addr_parse(), - nl_geterror(), - nl_addr_get_family(), - nl_addr_put() */ -#include /* struct rtnl_route, - struct rtnl_nexthop, - rtnl_route_alloc(), - rtnl_route_set_family(), - rtnl_route_set_protocol(), - RTPROT_BOOT, - rtnl_route_set_scope(), - RT_SCOPE_LINK, - rtnl_route_set_type(), - RTN_UNICAST, - rtnl_route_set_dst(), - rtnl_route_set_table(), - RT_TABLE_MAIN, - rtnl_route_nh_alloc(), - rtnl_route_nh_set_ifindex(), - rtnl_route_add_nexthop(), - rtnl_route_add(), - rtnl_route_delete(), - rtnl_route_put(), - rtnl_route_nh_free() */ -#include /* struct nl_sock, nl_socket_alloc(), - nl_connect(), nl_socket_free() */ -#include /* rtnl_link_get_kernel(), - rtnl_link_get_ifindex(), - rtnl_link_put() */ - -bool debug = false; -const char *argp_program_version = "mandos-client-iprouteadddel " VERSION; -const char *argp_program_bug_address = ""; - -/* Function to use when printing errors */ -void perror_plus(const char *print_text){ - int e = errno; - fprintf(stderr, "Mandos plugin helper %s: ", - program_invocation_short_name); - errno = e; - perror(print_text); -} - -__attribute__((format (gnu_printf, 2, 3), nonnull)) -int fprintf_plus(FILE *stream, const char *format, ...){ - va_list ap; - va_start (ap, format); - - fprintf(stream, "Mandos plugin helper %s: ", - program_invocation_short_name); - return vfprintf(stream, format, ap); -} - -int main(int argc, char *argv[]){ - int ret; - int exitcode = EXIT_SUCCESS; - struct arguments { - bool add; /* true: add, false: delete */ - char *address; /* IP address as string */ - struct nl_addr *nl_addr; /* Netlink IP address */ - char *interface; /* interface name */ - } arguments = { .add = true, .address = NULL, .interface = NULL }; - struct argp_option options[] = { - { .name = "debug", .key = 128, - .doc = "Debug mode" }, - { .name = NULL } - }; - struct rtnl_route *route = NULL; - struct rtnl_nexthop *nexthop = NULL; - struct nl_sock *sk = NULL; - - error_t parse_opt(int key, char *arg, struct argp_state *state){ - int lret; - errno = 0; - switch(key){ - case 128: /* --debug */ - debug = true; - break; - case ARGP_KEY_ARG: - switch(state->arg_num){ - case 0: - if(strcasecmp(arg, "add") == 0){ - ((struct arguments *)(state->input))->add = true; - } else if(strcasecmp(arg, "delete") == 0){ - ((struct arguments *)(state->input))->add = false; - } else { - fprintf_plus(stderr, "Unrecognized command: %s\n", arg); - argp_usage(state); - } - break; - case 1: - ((struct arguments *)(state->input))->address = arg; - lret = nl_addr_parse(arg, AF_UNSPEC, &(((struct arguments *) - (state->input)) - ->nl_addr)); - if(lret != 0){ - fprintf_plus(stderr, "Failed to parse address %s: %s\n", - arg, nl_geterror(lret)); - argp_usage(state); - } - break; - case 2: - ((struct arguments *)(state->input))->interface = arg; - break; - default: - argp_usage(state); - } - break; - case ARGP_KEY_END: - if(state->arg_num < 3){ - argp_usage(state); - } - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "[ add | delete ] ADDRESS INTERFACE", - .doc = "Mandos client helper -- Add or delete" - " local route to IP address on interface" }; - - ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, &arguments); - switch(ret){ - case 0: - break; - case EINVAL: - exit(EX_USAGE); - case ENOMEM: - default: - errno = ret; - perror_plus("argp_parse"); - exitcode = EX_OSERR; - goto end; - } - /* Get netlink socket */ - sk = nl_socket_alloc(); - if(sk == NULL){ - fprintf_plus(stderr, "Failed to allocate netlink socket: %s\n", - nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - /* Connect socket to netlink */ - ret = nl_connect(sk, NETLINK_ROUTE); - if(ret < 0){ - fprintf_plus(stderr, "Failed to connect socket to netlink: %s\n", - nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - /* Get link object of specified interface */ - struct rtnl_link *link = NULL; - ret = rtnl_link_get_kernel(sk, 0, arguments.interface, &link); - if(ret < 0){ - fprintf_plus(stderr, "Failed to use interface %s: %s\n", - arguments.interface, nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - /* Get netlink route object */ - route = rtnl_route_alloc(); - if(route == NULL){ - fprintf_plus(stderr, "Failed to get netlink route:\n"); - exitcode = EX_OSERR; - goto end; - } - /* Get address family of specified address */ - sa_family_t af = (sa_family_t)nl_addr_get_family(arguments.nl_addr); - if(debug){ - fprintf_plus(stderr, "Address family of %s is %s (%" PRIdMAX - ")\n", arguments.address, - af == AF_INET6 ? "AF_INET6" : - ( af == AF_INET ? "AF_INET" : "UNKNOWN"), - (intmax_t)af); - } - /* Set route parameters: */ - rtnl_route_set_family(route, (uint8_t)af); /* Address family */ - rtnl_route_set_protocol(route, RTPROT_BOOT); /* protocol - see - ip-route(8) */ - rtnl_route_set_scope(route, RT_SCOPE_LINK); /* link scope */ - rtnl_route_set_type(route, RTN_UNICAST); /* normal unicast - address route */ - rtnl_route_set_dst(route, arguments.nl_addr); /* Destination - address */ - rtnl_route_set_table(route, RT_TABLE_MAIN); /* "main" routing - table */ - /* Create nexthop */ - nexthop = rtnl_route_nh_alloc(); - if(nexthop == NULL){ - fprintf_plus(stderr, "Failed to get netlink route nexthop\n"); - exitcode = EX_OSERR; - goto end; - } - /* Get index number of specified interface */ - int ifindex = rtnl_link_get_ifindex(link); - if(debug){ - fprintf_plus(stderr, "ifindex of %s is %d\n", arguments.interface, - ifindex); - } - /* Set interface index number on nexthop object */ - rtnl_route_nh_set_ifindex(nexthop, ifindex); - /* Set route to use nexthop object */ - rtnl_route_add_nexthop(route, nexthop); - /* Add or delete route? */ - if(arguments.add){ - ret = rtnl_route_add(sk, route, NLM_F_EXCL); - } else { - ret = rtnl_route_delete(sk, route, 0); - } - if(ret < 0){ - fprintf_plus(stderr, "Failed to %s route: %s\n", - arguments.add ? "add" : "delete", - nl_geterror(ret)); - exitcode = EX_OSERR; - goto end; - } - end: - /* Deallocate route */ - if(route){ - rtnl_route_put(route); - } else if(nexthop) { - /* Deallocate route nexthop */ - rtnl_route_nh_free(nexthop); - } - /* Deallocate parsed address */ - if(arguments.nl_addr){ - nl_addr_put(arguments.nl_addr); - } - /* Deallocate link struct */ - if(link){ - rtnl_link_put(link); - } - /* Deallocate netlink socket struct */ - if(sk){ - nl_socket_free(sk); - } - return exitcode; -} === removed file 'plugin-runner.conf' --- plugin-runner.conf 2009-04-17 08:26:17 +0000 +++ plugin-runner.conf 1970-01-01 00:00:00 +0000 @@ -1,10 +0,0 @@ -## This is the configuration file for plugin-runner(8mandos). This -## file should be installed as "/etc/mandos/plugin-runner.conf", and -## will be copied to "/conf/conf.d/mandos/plugin-runner.conf" in the -## initrd.img file. -## -## After editing this file, the initrd image file must be updated for -## the changes to take effect! - -## Example: -#--options-for=mandos-client:--debug === removed file 'plugin-runner.xml' --- plugin-runner.xml 2019-07-25 22:44:36 +0000 +++ plugin-runner.xml 1970-01-01 00:00:00 +0000 @@ -1,698 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - - Run Mandos plugins, pass data from first to succeed. - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - &COMMANDNAME; is a program which is meant to - be specified as a keyscript for the root disk in - crypttab - 5. The aim of this - program is therefore to output a password, which then - cryptsetup - 8 will use to unlock the - root disk. - - - This program is not meant to be invoked directly, but can be in - order to test it. Note that any password obtained will simply - be output on standard output. - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - - - - - - This option will add an environment variable setting to - all plugins. This will override any inherited environment - variable. - - - - - - - - - - This option will add an environment variable setting to - the PLUGIN plugin. This will - override any inherited environment variables or - environment variables specified using - . - - - - - - - - - - Pass some options to all plugins. - OPTIONS is a comma separated - list of options. This is not a very useful option, except - for specifying the - option to all plugins. - - - - - - - - - - Pass some options to a specific plugin. PLUGIN is the name (file basename) of a - plugin, and OPTIONS is a comma - separated list of options. - - - Note that since options are not split on whitespace, the - way to pass, to the plugin - foo, the option - with the option argument - baz is either - --options-for=foo:--bar=baz or - --options-for=foo:--bar,baz. Using - --options-for="foo:--bar baz". will - not work. - - - - - - - - - - Disable the plugin named - PLUGIN. The plugin will not be - started. - - - - - - - - - - Re-enable the plugin named - PLUGIN. This is only useful to - undo a previous option, maybe - from the configuration file. - - - - - - - - - Change to group ID ID on - startup. The default is 65534. All plugins will be - started using this group ID. Note: - This must be a number, not a name. - - - - - - - - - Change to user ID ID on - startup. The default is 65534. All plugins will be - started using this user ID. Note: - This must be a number, not a name. - - - - - - - - - Specify a different plugin directory. The default is - /lib/mandos/plugins.d, which will - exist in the initial RAM disk - environment. - - - - - - - - - Specify a different plugin helper directory. The default - is /lib/mandos/plugin-helpers, which - will exist in the initial RAM disk - environment. (This will simply be passed to all plugins - via the MANDOSPLUGINHELPERDIR environment - variable. See ) - - - - - - - - - Specify a different file to read additional options from. - See . Other command line options - will override options specified in the file. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - The default is to not run in debug - mode. - - - The plugins will not be affected by - this option. Use - - if complete debugging eruption is desired. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - - - - - OVERVIEW - - - This program will run on the client side in the initial - RAM disk environment, and is responsible for - getting a password. It does this by running plugins, one of - which will normally be the actual client program communicating - with the server. - - - - PLUGINS - - This program will get a password by running a number of - plugins, which are simply executable - programs in a directory in the initial RAM - disk environment. The default directory is - /lib/mandos/plugins.d, but this can be - changed with the option. The - plugins are started in parallel, and the first plugin to output - a password and exit with a successful exit - code will make this plugin-runner output the password from that - plugin, stop any other plugins, and exit. - - - - WRITING PLUGINS - - A plugin is simply a program which prints a password to its - standard output and then exits with a successful (zero) exit - status. If the exit status is not zero, any output on - standard output will be ignored by the plugin runner. Any - output on its standard error channel will simply be passed to - the standard error of the plugin runner, usually the system - console. - - - If the password is a single-line, manually entered passprase, - a final trailing newline character should - not be printed. - - - The plugin will run in the initial RAM disk environment, so - care must be taken not to depend on any files or running - services not available there. Any helper executables required - by the plugin (which are not in the PATH) can - be placed in the plugin helper directory, the name of which - will be made available to the plugin via the - MANDOSPLUGINHELPERDIR environment variable. - - - The plugin must exit cleanly and free all allocated resources - upon getting the TERM signal, since this is what the plugin - runner uses to stop all other plugins when one plugin has - output a password and exited cleanly. - - - The plugin must not use resources, like for instance reading - from the standard input, without knowing that no other plugin - is also using it. - - - It is useful, but not required, for the plugin to take the - option. - - - - - - FALLBACK - - If no plugins succeed, this program will, as a fallback, ask for - a password on the console using getpass3, - and output it. This is not meant to be the normal mode of - operation, as there is a separate plugin for getting a password - from the console. - - - - - EXIT STATUS - - Exit status of this program is zero if no errors were - encountered, and otherwise not. The fallback (see ) may or may not have succeeded in either - case. - - - - - ENVIRONMENT - - This program does not use any environment variables itself, it - only passes on its environment to all the plugins. The - environment passed to plugins can be modified using the - and - options. Also, the option - will affect the environment variable - MANDOSPLUGINHELPERDIR for the plugins. - - - - - FILES - - - - /conf/conf.d/mandos/plugin-runner.conf - - - Since this program will be run as a keyscript, there is - little to no opportunity to pass command line arguments - to it. Therefore, it will also - read this file and use its contents as - whitespace-separated command line options. Also, - everything from a # character to the end - of a line is ignored. - - - This program is meant to run in the initial RAM disk - environment, so that is where this file is assumed to - exist. The file does not need to exist in the normal - file system. - - - This file will be processed before - the normal command line options, so the latter can - override the former, if need be. - - - This file name is the default; the file to read for - arguments can be changed using the - option. - - - - - /lib/mandos/plugins.d - - - The default plugin directory; can be changed by the - option. - - - - - /lib/mandos/plugin-helpers - - - The default plugin helper directory; can be changed by - the option. - - - - - - - - - BUGS - - The option is ignored when - specified from within a configuration file. - - - - - - EXAMPLE - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Run the program, but not the plugins, in debug mode: - - - - - &COMMANDNAME; --debug - - - - - - Run all plugins, but run the foo plugin in - debug mode: - - - - - &COMMANDNAME; --options-for=foo:--debug - - - - - - Run all plugins, but not the program, in debug mode: - - - - - &COMMANDNAME; --global-options=--debug - - - - - - Read a different configuration file, run plugins from a - different directory, specify an alternate plugin helper - directory and add four options to the - mandos-client - 8mandos plugin: - - - - -cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/x86_64-linux-gnu/mandos/plugins.d --plugin-helper-dir /usr/lib/x86_64-linux-gnu/mandos/plugin-helpers --options-for=mandos-client:--pubkey=pubkey.txt,​--seckey=seckey.txt,​--tls-pubkey=tls-pubkey.pem,​--tls-privkey=tls-privkey.pem - - - - - - SECURITY - - This program will, when starting, try to switch to another user. - If it is started as root, it will succeed, and will by default - switch to user and group 65534, which are assumed to be - non-privileged. This user and group is then what all plugins - will be started as. Therefore, the only way to run a plugin as - a privileged user is to have the set-user-ID or set-group-ID bit - set on the plugin executable file (see - execve2 - ). - - - If this program is used as a keyscript in crypttab5 - , there is a slight risk that if this program - fails to work, there might be no way to boot the system except - for booting from another media and editing the initial RAM disk - image to not run this program. This is, however, unlikely, - since the password-prompt8mandos - plugin will read a password from the console in - case of failure of the other plugins, and this plugin runner - will also, in case of catastrophic failure, itself fall back to - asking and outputting a password on the console (see ). - - - - - SEE ALSO - - intro - 8mandos, - cryptsetup - 8, - crypttab - 5, - execve - 2, - mandos - 8, - password-prompt - 8mandos, - mandos-client - 8mandos - - - -
- - - - - === removed file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2019-02-11 07:06:55 +0000 +++ plugins.d/askpass-fifo.c 1970-01-01 00:00:00 +0000 @@ -1,227 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Askpass-FIFO - Read a password from a FIFO and output it - * - * Copyright © 2008-2019 Teddy Hogeborn - * Copyright © 2008-2019 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos 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 Mandos. If not, see . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ -#include /* uid_t, gid_t, ssize_t */ -#include /* mkfifo(), S_IRUSR, S_IWUSR */ -#include /* and */ -#include /* errno, EACCES, ENOTDIR, ELOOP, - ENAMETOOLONG, ENOSPC, EROFS, - ENOENT, EEXIST, EFAULT, EMFILE, - ENFILE, ENOMEM, EBADF, EINVAL, EIO, - EISDIR, EFBIG */ -#include /* error() */ -#include /* fprintf(), vfprintf(), - vasprintf() */ -#include /* EXIT_FAILURE, NULL, size_t, free(), - realloc(), EXIT_SUCCESS */ -#include /* open(), O_RDONLY */ -#include /* read(), close(), write(), - STDOUT_FILENO */ -#include /* EX_OSERR, EX_OSFILE, - EX_UNAVAILABLE, EX_IOERR */ -#include /* strerror() */ -#include /* va_list, va_start(), ... */ - -uid_t uid = 65534; -gid_t gid = 65534; - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - if(status){ - __builtin_unreachable(); - } - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - if(status){ - __builtin_unreachable(); - } - free(text); -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - int ret = 0; - ssize_t sret; - - uid = getuid(); - gid = getgid(); - - /* Create FIFO */ - const char passfifo[] = "/lib/cryptsetup/passfifo"; - ret = mkfifo(passfifo, S_IRUSR | S_IWUSR); - if(ret == -1){ - int e = errno; - switch(e){ - case EACCES: - case ENOTDIR: - case ELOOP: - error_plus(EX_OSFILE, errno, "mkfifo"); - __builtin_unreachable(); - case ENAMETOOLONG: - case ENOSPC: - case EROFS: - default: - error_plus(EX_OSERR, errno, "mkfifo"); - __builtin_unreachable(); - case ENOENT: - /* no "/lib/cryptsetup"? */ - error_plus(EX_UNAVAILABLE, errno, "mkfifo"); - __builtin_unreachable(); - case EEXIST: - break; /* not an error */ - } - } - - /* Open FIFO */ - int fifo_fd = open(passfifo, O_RDONLY); - if(fifo_fd == -1){ - int e = errno; - error_plus(0, errno, "open"); - switch(e){ - case EACCES: - case ENOENT: - case EFAULT: - return EX_UNAVAILABLE; - case ENAMETOOLONG: - case EMFILE: - case ENFILE: - case ENOMEM: - default: - return EX_OSERR; - case ENOTDIR: - case ELOOP: - return EX_OSFILE; - } - } - - /* Lower group privileges */ - if(setgid(gid) == -1){ - error_plus(0, errno, "setgid"); - } - - /* Lower user privileges */ - if(setuid(uid) == -1){ - error_plus(0, errno, "setuid"); - } - - /* Read from FIFO */ - char *buf = NULL; - size_t buf_len = 0; - { - size_t buf_allocated = 0; - const size_t blocksize = 1024; - do { - if(buf_len + blocksize > buf_allocated){ - char *tmp = realloc(buf, buf_allocated + blocksize); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(buf); - return EX_OSERR; - } - buf = tmp; - buf_allocated += blocksize; - } - sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len); - if(sret == -1){ - int e = errno; - free(buf); - errno = e; - error_plus(0, errno, "read"); - switch(e){ - case EBADF: - case EFAULT: - case EINVAL: - default: - return EX_OSERR; - case EIO: - return EX_IOERR; - case EISDIR: - return EX_UNAVAILABLE; - } - } - buf_len += (size_t)sret; - } while(sret != 0); - } - - /* Close FIFO */ - close(fifo_fd); - - /* Print password to stdout */ - size_t written = 0; - while(written < buf_len){ - sret = write(STDOUT_FILENO, buf + written, buf_len - written); - if(sret == -1){ - int e = errno; - free(buf); - errno = e; - error_plus(0, errno, "write"); - switch(e){ - case EBADF: - case EFAULT: - case EINVAL: - return EX_OSFILE; - case EFBIG: - case EIO: - case ENOSPC: - default: - return EX_IOERR; - } - } - written += (size_t)sret; - } - free(buf); - - ret = close(STDOUT_FILENO); - if(ret == -1){ - int e = errno; - error_plus(0, errno, "close"); - switch(e){ - case EBADF: - return EX_OSFILE; - case EIO: - default: - return EX_IOERR; - } - } - return EXIT_SUCCESS; -} === removed file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 2019-02-10 04:20:26 +0000 +++ plugins.d/askpass-fifo.xml 1970-01-01 00:00:00 +0000 @@ -1,178 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to get a password from a - FIFO. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program reads a password from a FIFO and - outputs it to standard output. - - - This program is not very useful on its own. This program is - really meant to run as a plugin in the Mandos client-side system, where it is used as a - fallback and alternative to retrieving passwords from a - Mandos server. - - - This program is meant to be imitate a feature of the - askpass program, so that programs written to - interface with it can keep working under the - Mandos system. - - - - - OPTIONS - - This program takes no options. - - - - - EXIT STATUS - - If exit status is 0, the output from the program is the password - as it was read. Otherwise, if exit status is other than 0, the - program was interrupted or encountered an error, and any output - so far could be corrupt and/or truncated, and should therefore - be ignored. - - - - - FILES - - - /lib/cryptsetup/passfifo - - - This is the FIFO where this program - will read the password. If it does not exist, it will be - created. - - - - - - - - BUGS - - - - - EXAMPLE - - Note that normally, this program will not be invoked directly, - but instead started by the Mandos plugin-runner8mandos - . - - - - This program takes no options. - - - &COMMANDNAME; - - - - - - SECURITY - - The only thing that could be considered worthy of note is - this: This program is meant to be run by - plugin-runner8mandos, and will, when run - standalone, outside, in a normal environment, immediately output - on its standard output any presumably secret password it just - received. Therefore, when running this program standalone - (which should never normally be done), take care not to type in - any real secret password by force of habit, since it would then - immediately be shown as output. - - - - - SEE ALSO - - intro - 8mandos, - fifo - 7, - plugin-runner - 8mandos - - -
- - - - - === removed file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/mandos-client.xml 1970-01-01 00:00:00 +0000 @@ -1,1016 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - - Client for Mandos - - - - - - &COMMANDNAME; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - &COMMANDNAME; is a client program that - communicates with mandos8 - to get a password. In slightly more detail, this client program - brings up network interfaces, uses the interfaces’ IPv6 - link-local addresses to get network connectivity, uses Zeroconf - to find servers on the local network, and communicates with - servers using TLS with a raw public key to ensure authenticity - and confidentiality. This client program keeps running, trying - all servers on the network, until it receives a satisfactory - reply or a TERM signal. After all servers have been tried, all - servers are periodically retried. If no servers are found it - will wait indefinitely for new servers to appear. - - - The network interfaces are selected like this: If any interfaces - are specified using the option, - those interface are used. Otherwise, - &COMMANDNAME; will use all interfaces that - are not loopback interfaces, are not point-to-point interfaces, - are capable of broadcasting and do not have the NOARP flag (see - netdevice - 7). (If the - option is used, point-to-point - interfaces and non-broadcast interfaces are accepted.) If any - used interfaces are not up and running, they are first taken up - (and later taken down again on program exit). - - - Before network interfaces are selected, all network - hooks are run; see . - - - This program is not meant to be run directly; it is really meant - to run as a plugin of the Mandos - plugin-runner - 8mandos, which runs in the - initial RAM disk environment because it is - specified as a keyscript in the - crypttab5 - file. - - - - - PURPOSE - - The purpose of this is to enable remote and unattended - rebooting of client host computer with an - encrypted root file system. See for details. - - - - - OPTIONS - - This program is commonly not invoked from the command line; it - is normally started by the Mandos - plugin runner, see plugin-runner8mandos - . Any command line options this program accepts - are therefore normally provided by the plugin runner, and not - directly. - - - - - - - - - Do not use Zeroconf to locate servers. Connect directly - to only one specified Mandos - server. Note that an IPv6 address has colon characters in - it, so the last colon character is - assumed to separate the address from the port number. - - - Normally, Zeroconf would be used to locate Mandos servers, - in which case this option would only be used when testing - and debugging. - - - - - - - - - - Comma separated list of network interfaces that will be - brought up and scanned for Mandos servers to connect to. - The default is the empty string, which will automatically - use all appropriate interfaces. - - - If the option is used, and - exactly one interface name is specified (except - none), this specifies - the interface to use to connect to the address given. - - - Note that since this program will normally run in the - initial RAM disk environment, the interface must be an - interface which exists at that stage. Thus, the interface - can normally not be a pseudo-interface such as - br0 or tun0; such interfaces - will not exist until much later in the boot process, and - can not be used by this program, unless created by a - network hook — see . - - - NAME can be the string - none; this will make - &COMMANDNAME; only bring up interfaces - specified before this string. This - is not recommended, and only meant for advanced users. - - - - - - - - - - OpenPGP public key file name. The default name is - /conf/conf.d/mandos/pubkey.txt. - - - - - - - - - - OpenPGP secret key file name. The default name is - /conf/conf.d/mandos/seckey.txt. - - - - - - - - - - TLS raw public key file name. The default name is - /conf/conf.d/mandos/tls-pubkey.pem. - - - - - - - - - - TLS secret key file name. The default name is - /conf/conf.d/mandos/tls-privkey.pem. - - - - - - - - - - - - - - - - Sets the number of bits to use for the prime number in the - TLS Diffie-Hellman key exchange. The default value is - selected automatically based on the GnuTLS security - profile set in its priority string. Note that if the - option is used, the values - from that file will be used instead. - - - - - - - - - Specifies a PEM-encoded PKCS#3 file to read the parameters - needed by the TLS Diffie-Hellman key exchange from. If - this option is not given, or if the file for some reason - could not be used, the parameters will be generated on - startup, which will take some time and processing power. - Those using servers running under time, power or processor - constraints may want to generate such a file in advance - and use this option. - - - - - - - - - After bringing a network interface up, the program waits - for the interface to arrive in a running - state before proceeding. During this time, the kernel log - level will be lowered to reduce clutter on the system - console, alleviating any other plugins which might be - using the system console. This option sets the upper - limit of seconds to wait. The default is 2.5 seconds. - - - - - - - - - All Mandos servers are tried repeatedly until a password - is received. This value specifies, in seconds, how long - between each successive try for the same - server. The default is 10 seconds. - - - - - - - - - Network hook directory. The default directory is - /lib/mandos/network-hooks.d. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - - - It will also enable debug mode in the Avahi and GnuTLS - libraries, making them print large amounts of debugging - output. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - - - - - OVERVIEW - - - This program is the client part. It is a plugin started by - plugin-runner - 8mandos which will run in - an initial RAM disk environment. - - - This program could, theoretically, be used as a keyscript in - /etc/crypttab, but it would then be - impossible to enter a password for the encrypted root disk at - the console, since this program does not read from the console - at all. This is why a separate plugin runner ( - plugin-runner - 8mandos) is used to run - both this program and others in in parallel, - one of which ( - password-prompt - 8mandos) will prompt for - passwords on the system console. - - - - - EXIT STATUS - - This program will exit with a successful (zero) exit status if a - server could be found and the password received from it could be - successfully decrypted and output on standard output. The - program will exit with a non-zero exit status only if a critical - error occurs. Otherwise, it will forever connect to any - discovered Mandos servers, trying to - get a decryptable password and print it. - - - - - ENVIRONMENT - - - MANDOSPLUGINHELPERDIR - - - This environment variable will be assumed to contain the - directory containing any helper executables. The use and - nature of these helper executables, if any, is purposely - not documented. - - - - - - This program does not use any other environment variables, not - even the ones provided by cryptsetup8 - . - - - - - NETWORK HOOKS - - If a network interface like a bridge or tunnel is required to - find a Mandos server, this requires the interface to be up and - running before &COMMANDNAME; starts looking - for Mandos servers. This can be accomplished by creating a - network hook program, and placing it in a special - directory. - - - Before the network is used (and again before program exit), any - runnable programs found in the network hook directory are run - with the argument start or - stop. This should bring up or - down, respectively, any network interface which - &COMMANDNAME; should use. - - - REQUIREMENTS - - A network hook must be an executable file, and its name must - consist entirely of upper and lower case letters, digits, - underscores, periods, and hyphens. - - - A network hook will receive one argument, which can be one of - the following: - - - - start - - - This should make the network hook create (if necessary) - and bring up a network interface. - - - - - stop - - - This should make the network hook take down a network - interface, and delete it if it did not exist previously. - - - - - files - - - This should make the network hook print, one - file per line, all the files needed for it to - run. (These files will be copied into the initial RAM - filesystem.) Typical use is for a network hook which is - a shell script to print its needed binaries. - - - It is not necessary to print any non-executable files - already in the network hook directory, these will be - copied implicitly if they otherwise satisfy the name - requirements. - - - - - modules - - - This should make the network hook print, on - separate lines, all the kernel modules needed - for it to run. (These modules will be copied into the - initial RAM filesystem.) For instance, a tunnel - interface needs the - tun module. - - - - - - The network hook will be provided with a number of environment - variables: - - - - MANDOSNETHOOKDIR - - - The network hook directory, specified to - &COMMANDNAME; by the - option. Note: this - should always be used by the - network hook to refer to itself or any files in the hook - directory it may require. - - - - - DEVICE - - - The network interfaces, as specified to - &COMMANDNAME; by the - option, combined to one - string and separated by commas. If this is set, and - does not contain the interface a hook will bring up, - there is no reason for a hook to continue. - - - - - MODE - - - This will be the same as the first argument; - i.e. start, - stop, - files, or - modules. - - - - - VERBOSITY - - - This will be the 1 if - the option is passed to - &COMMANDNAME;, otherwise - 0. - - - - - DELAY - - - This will be the same as the - option passed to &COMMANDNAME;. Is - only set if MODE is - start or - stop. - - - - - CONNECT - - - This will be the same as the - option passed to &COMMANDNAME;. Is - only set if is passed and - MODE is - start or - stop. - - - - - - A hook may not read from standard input, and should be - restrictive in printing to standard output or standard error - unless VERBOSITY is - 1. - - - - - - FILES - - - /conf/conf.d/mandos/pubkey.txt - /conf/conf.d/mandos/seckey.txt - - - OpenPGP public and private key files, in ASCII - Armor format. These are the default file names, - they can be changed with the and - options. - - - - - /conf/conf.d/mandos/tls-pubkey.pem - /conf/conf.d/mandos/tls-privkey.pem - - - Public and private raw key files, in PEM - format. These are the default file names, they can be - changed with the and - options. - - - - - /lib/mandos/network-hooks.d - - - Directory where network hooks are located. Change this - with the option. See - . - - - - - - - - BUGS - - - - - EXAMPLE - - Note that normally, command line options will not be given - directly, but via options for the Mandos plugin-runner - 8mandos. - - - - Normal invocation needs no options, if the network interfaces - can be automatically determined: - - - &COMMANDNAME; - - - - - Search for Mandos servers (and connect to them) using one - specific interface: - - - - &COMMANDNAME; --interface eth1 - - - - - Run in debug mode, and use custom keys: - - - - -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem - - - - - - Run in debug mode, with custom keys, and do not use Zeroconf - to locate a server; connect directly to the IPv6 link-local - address fe80::aede:48ff:fe71:f6f2, port 4711, - using interface eth2: - - - - -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --tls-pubkey keydir/tls-pubkey.pem --tls-privkey keydir/tls-privkey.pem --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 - - - - - - - SECURITY - - This program assumes that it is set-uid to root, and will switch - back to the original (and presumably non-privileged) user and - group after bringing up the network interface. - - - To use this program for its intended purpose (see ), the password for the root file system will - have to be given out to be stored in a server computer, after - having been encrypted using an OpenPGP key. This encrypted data - which will be stored in a server can only be decrypted by the - OpenPGP key, and the data will only be given out to those - clients who can prove they actually have that key. This key, - however, is stored unencrypted on the client side in its initial - RAM disk image file system. This is normally - readable by all, but this is normally fixed during installation - of this program; file permissions are set so that no-one is able - to read that file. - - - The only remaining weak point is that someone with physical - access to the client hard drive might turn off the client - computer, read the OpenPGP and TLS keys directly from the hard - drive, and communicate with the server. To safeguard against - this, the server is supposed to notice the client disappearing - and stop giving out the encrypted data. Therefore, it is - important to set the timeout and checker interval values tightly - on the server. See mandos8. - - - It will also help if the checker program on the server is - configured to request something from the client which can not be - spoofed by someone else on the network, like SSH server key - fingerprints, and unlike unencrypted ICMP - echo (ping) replies. - - - Note: This makes it completely insecure to - have Mandos clients which dual-boot - to another operating system which is not - trusted to keep the initial RAM disk image - confidential. - - - - - SEE ALSO - - intro - 8mandos, - cryptsetup - 8, - crypttab - 5, - mandos - 8, - password-prompt - 8mandos, - plugin-runner - 8mandos - - - - - Zeroconf - - - - Zeroconf is the network protocol standard used for finding - Mandos servers on the local network. - - - - - - Avahi - - - - Avahi is the library this program calls to find Zeroconf - services. - - - - - - GnuTLS - - - - GnuTLS is the library this client uses to implement TLS for - communicating securely with the server, and at the same time - send the public key to the server. - - - - - - GPGME - - - - GPGME is the library used to decrypt the OpenPGP data sent - by the server. - - - - - - RFC 4291: IP Version 6 Addressing - Architecture - - - - - Section 2.2: Text Representation of - Addresses - - - - Section 2.5.5.2: IPv4-Mapped IPv6 - Address - - - - Section 2.5.6, Link-Local IPv6 Unicast - Addresses - - - This client uses IPv6 link-local addresses, which are - immediately usable since a link-local addresses is - automatically assigned to a network interface when it - is brought up. - - - - - - - - - RFC 5246: The Transport Layer Security (TLS) - Protocol Version 1.2 - - - - TLS 1.2 is the protocol implemented by GnuTLS. - - - - - - RFC 4880: OpenPGP Message Format - - - - The data received from the server is binary encrypted - OpenPGP data. - - - - - - RFC 7250: Using Raw Public Keys in Transport - Layer Security (TLS) and Datagram Transport Layer Security - (DTLS) - - - - This is implemented by GnuTLS in version 3.6.6 and is, if - present, used by this program so that raw public keys can be - used. - - - - - - RFC 6091: Using OpenPGP Keys for Transport Layer - Security - - - - This is implemented by GnuTLS before version 3.6.0 and is, - if present, used by this program so that OpenPGP keys can be - used. - - - - - -
- - - - - - === renamed file 'plugins.d/mandos-client.c' => 'plugins.d/mandosclient.c' --- plugins.d/mandos-client.c 2019-02-11 05:14:10 +0000 +++ plugins.d/mandosclient.c 2008-07-31 19:48:05 +0000 @@ -1,6 +1,6 @@ /* -*- coding: utf-8 -*- */ /* - * Mandos-client - get and decrypt data from a Mandos server + * Mandos client - get and decrypt data from a Mandos server * * This program is partly derived from an example program for an Avahi * service browser, downloaded from @@ -9,107 +9,36 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2019 Teddy Hogeborn - * Copyright © 2008-2019 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos is distributed in the hope that it will be useful, but + * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson + * + * 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 Mandos. If not, see . + * along with this program. If not, see + * . * - * Contact the authors at . + * Contact the authors at and + * . */ /* Needed by GPGME, specifically gpgme_data_seek() */ -#ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE -#endif /* not _LARGEFILE_SOURCE */ -#ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 -#endif /* not _FILE_OFFSET_BITS */ - -#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ - -#include /* fprintf(), stderr, fwrite(), - stdout, ferror() */ -#include /* uint16_t, uint32_t, intptr_t */ -#include /* NULL, size_t, ssize_t */ -#include /* free(), EXIT_SUCCESS, srand(), - strtof(), abort() */ -#include /* bool, false, true */ -#include /* strcmp(), strlen(), strerror(), - asprintf(), strncpy(), strsignal() - */ -#include /* ioctl */ -#include /* socket(), inet_pton(), sockaddr, - sockaddr_in6, PF_INET6, - SOCK_STREAM, uid_t, gid_t, open(), - opendir(), DIR */ -#include /* open(), S_ISREG */ -#include /* socket(), struct sockaddr_in6, - inet_pton(), connect(), - getnameinfo() */ -#include /* open(), unlinkat(), AT_REMOVEDIR */ -#include /* opendir(), struct dirent, readdir() - */ -#include /* PRIu16, PRIdMAX, intmax_t, - strtoimax() */ -#include /* perror(), errno, EINTR, EINVAL, - EAI_SYSTEM, ENETUNREACH, - EHOSTUNREACH, ECONNREFUSED, EPROTO, - EIO, ENOENT, ENXIO, ENOMEM, EISDIR, - ENOTEMPTY, - program_invocation_short_name */ -#include /* nanosleep(), time(), sleep() */ -#include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, - SIOCSIFFLAGS, if_indextoname(), - if_nametoindex(), IF_NAMESIZE */ -#include /* IN6_IS_ADDR_LINKLOCAL, - INET_ADDRSTRLEN, INET6_ADDRSTRLEN - */ -#include /* close(), SEEK_SET, off_t, write(), - getuid(), getgid(), seteuid(), - setgid(), pause(), _exit(), - unlinkat() */ -#include /* inet_pton(), htons() */ -#include /* not, or, and */ -#include /* struct argp_option, error_t, struct - argp_state, struct argp, - argp_parse(), ARGP_KEY_ARG, - ARGP_KEY_END, ARGP_ERR_UNKNOWN */ -#include /* sigemptyset(), sigaddset(), - sigaction(), SIGTERM, sig_atomic_t, - raise() */ -#include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, - EX_NOHOST, EX_IOERR, EX_PROTOCOL */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS(), WTERMSIG() */ -#include /* setgroups() */ -#include /* argz_add_sep(), argz_next(), - argz_delete(), argz_append(), - argz_stringify(), argz_add(), - argz_count() */ -#include /* getnameinfo(), NI_NUMERICHOST, - EAI_SYSTEM, gai_strerror() */ - -#ifdef __linux__ -#include /* klogctl() */ -#endif /* __linux__ */ - -/* Avahi */ -/* All Avahi types, constants and functions - Avahi*, avahi_*, - AVAHI_* */ + +#include +#include +#include +#include +#include /* if_nametoindex */ + #include #include #include @@ -117,1578 +46,514 @@ #include #include -/* GnuTLS */ -#include /* All GnuTLS types, constants and - functions: - gnutls_* - init_gnutls_session(), - GNUTLS_* */ -#if GNUTLS_VERSION_NUMBER < 0x030600 -#include - /* gnutls_certificate_set_openpgp_key_file(), - GNUTLS_OPENPGP_FMT_BASE64 */ -#elif GNUTLS_VERSION_NUMBER >= 0x030606 -#include /* gnutls_pkcs_encrypt_flags_t, - GNUTLS_PKCS_PLAIN, - GNUTLS_PKCS_NULL_PASSWORD */ +//mandos client part +#include /* socket(), inet_pton() */ +#include /* socket(), struct sockaddr_in6, + struct in6_addr, inet_pton() */ +#include /* All GnuTLS stuff */ +#include /* GnuTLS with openpgp stuff */ + +#include /* close() */ +#include +#include /* true */ +#include /* memset */ +#include /* inet_pton() */ +#include /* not */ + +// gpgme +#include /* perror() */ +#include + +// getopt long +#include + +#ifndef CERT_ROOT +#define CERT_ROOT "/conf/conf.d/cryptkeyreq/" #endif - -/* GPGME */ -#include /* All GPGME types, constants and - functions: - gpgme_* - GPGME_PROTOCOL_OpenPGP, - GPG_ERR_NO_* */ - +#define CERTFILE CERT_ROOT "openpgp-client.txt" +#define KEYFILE CERT_ROOT "openpgp-client-key.txt" #define BUFFER_SIZE 256 - -#define PATHDIR "/conf/conf.d/mandos" -#define SECKEY "seckey.txt" -#define PUBKEY "pubkey.txt" -#define TLS_PRIVKEY "tls-privkey.pem" -#define TLS_PUBKEY "tls-pubkey.pem" -#define HOOKDIR "/lib/mandos/network-hooks.d" +#define DH_BITS 1024 bool debug = false; -static const char mandos_protocol_version[] = "1"; -const char *argp_program_version = "mandos-client " VERSION; -const char *argp_program_bug_address = ""; -static const char sys_class_net[] = "/sys/class/net"; -char *connect_to = NULL; -const char *hookdir = HOOKDIR; -int hookdir_fd = -1; -uid_t uid = 65534; -gid_t gid = 65534; - -/* Doubly linked list that need to be circularly linked when used */ -typedef struct server{ - const char *ip; - in_port_t port; - AvahiIfIndex if_index; - int af; - struct timespec last_seen; - struct server *next; - struct server *prev; -} server; - -/* Used for passing in values through the Avahi callback functions */ + typedef struct { - AvahiServer *server; + gnutls_session_t session; gnutls_certificate_credentials_t cred; - unsigned int dh_bits; gnutls_dh_params_t dh_params; - const char *priority; +} encrypted_session; + + +ssize_t pgp_packet_decrypt (char *packet, size_t packet_size, + char **new_packet, const char *homedir){ + gpgme_data_t dh_crypto, dh_plain; gpgme_ctx_t ctx; - server *current_server; - char *interfaces; - size_t interfaces_size; -} mandos_context; - -/* global so signal handler can reach it*/ -AvahiSimplePoll *simple_poll; - -sig_atomic_t quit_now = 0; -int signal_received = 0; - -/* Function to use when printing errors */ -void perror_plus(const char *print_text){ - int e = errno; - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - errno = e; - perror(print_text); -} - -__attribute__((format (gnu_printf, 2, 3), nonnull)) -int fprintf_plus(FILE *stream, const char *format, ...){ - va_list ap; - va_start (ap, format); - - TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ", - program_invocation_short_name)); - return (int)TEMP_FAILURE_RETRY(vfprintf(stream, format, ap)); -} - -/* - * Make additional room in "buffer" for at least BUFFER_SIZE more - * bytes. "buffer_capacity" is how much is currently allocated, - * "buffer_length" is how much is already used. - */ -__attribute__((nonnull, warn_unused_result)) -size_t incbuffer(char **buffer, size_t buffer_length, - size_t buffer_capacity){ - if(buffer_length + BUFFER_SIZE > buffer_capacity){ - char *new_buf = realloc(*buffer, buffer_capacity + BUFFER_SIZE); - if(new_buf == NULL){ - int old_errno = errno; - free(*buffer); - errno = old_errno; - *buffer = NULL; - return 0; - } - *buffer = new_buf; - buffer_capacity += BUFFER_SIZE; - } - return buffer_capacity; -} - -/* Add server to set of servers to retry periodically */ -__attribute__((nonnull, warn_unused_result)) -bool add_server(const char *ip, in_port_t port, AvahiIfIndex if_index, - int af, server **current_server){ - int ret; - server *new_server = malloc(sizeof(server)); - if(new_server == NULL){ - perror_plus("malloc"); - return false; - } - *new_server = (server){ .ip = strdup(ip), - .port = port, - .if_index = if_index, - .af = af }; - if(new_server->ip == NULL){ - perror_plus("strdup"); - free(new_server); - return false; - } - ret = clock_gettime(CLOCK_MONOTONIC, &(new_server->last_seen)); - if(ret == -1){ - perror_plus("clock_gettime"); -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - free((char *)(new_server->ip)); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - free(new_server); - return false; - } - /* Special case of first server */ - if(*current_server == NULL){ - new_server->next = new_server; - new_server->prev = new_server; - *current_server = new_server; - } else { - /* Place the new server last in the list */ - new_server->next = *current_server; - new_server->prev = (*current_server)->prev; - new_server->prev->next = new_server; - (*current_server)->prev = new_server; - } - return true; -} - -/* Set effective uid to 0, return errno */ -__attribute__((warn_unused_result)) -int raise_privileges(void){ - int old_errno = errno; - int ret = 0; - if(seteuid(0) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* Set effective and real user ID to 0. Return errno. */ -__attribute__((warn_unused_result)) -int raise_privileges_permanently(void){ - int old_errno = errno; - int ret = raise_privileges(); - if(ret != 0){ - errno = old_errno; - return ret; - } - if(setuid(0) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* Set effective user ID to unprivileged saved user ID */ -__attribute__((warn_unused_result)) -int lower_privileges(void){ - int old_errno = errno; - int ret = 0; - if(seteuid(uid) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* Lower privileges permanently */ -__attribute__((warn_unused_result)) -int lower_privileges_permanently(void){ - int old_errno = errno; - int ret = 0; - if(setuid(uid) == -1){ - ret = errno; - } - errno = old_errno; - return ret; -} - -/* - * Initialize GPGME. - */ -__attribute__((nonnull, warn_unused_result)) -static bool init_gpgme(const char * const seckey, - const char * const pubkey, - const char * const tempdir, - mandos_context *mc){ gpgme_error_t rc; + ssize_t ret; + ssize_t new_packet_capacity = 0; + ssize_t new_packet_length = 0; gpgme_engine_info_t engine_info; - - /* - * Helper function to insert pub and seckey to the engine keyring. - */ - bool import_key(const char * const filename){ - int ret; - int fd; - gpgme_data_t pgp_data; - - fd = (int)TEMP_FAILURE_RETRY(open(filename, O_RDONLY)); - if(fd == -1){ - perror_plus("open"); - return false; - } - - /* Workaround for systems without a real-time clock; see also - Debian bug #894495: */ - do { - { - time_t currtime = time(NULL); - if(currtime != (time_t)-1){ - struct tm tm; - if(gmtime_r(&currtime, &tm) == NULL) { - perror_plus("gmtime_r"); - break; - } - if(tm.tm_year != 70 or tm.tm_mon != 0){ - break; - } - if(debug){ - fprintf_plus(stderr, "System clock is January 1970"); - } - } else { - if(debug){ - fprintf_plus(stderr, "System clock is invalid"); - } - } - } - struct stat keystat; - ret = fstat(fd, &keystat); - if(ret != 0){ - perror_plus("fstat"); - break; - } - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - break; - } - if(debug){ - fprintf_plus(stderr, - "Setting system clock to key file mtime"); - } - time_t keytime = keystat.st_mtim.tv_sec; - if(stime(&keytime) != 0){ - perror_plus("stime"); - } - ret = lower_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges"); - } - } while(false); - rc = gpgme_data_new_from_fd(&pgp_data, fd); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - return false; - } - - rc = gpgme_op_import(mc->ctx, pgp_data); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_op_import: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - return false; - } - { - gpgme_import_result_t import_result - = gpgme_op_import_result(mc->ctx); - if((import_result->imported < 1 - or import_result->not_imported > 0) - and import_result->unchanged == 0){ - fprintf_plus(stderr, "bad gpgme_op_import_results:\n"); - fprintf_plus(stderr, - "The total number of considered keys: %d\n", - import_result->considered); - fprintf_plus(stderr, - "The number of keys without user ID: %d\n", - import_result->no_user_id); - fprintf_plus(stderr, - "The total number of imported keys: %d\n", - import_result->imported); - fprintf_plus(stderr, "The number of imported RSA keys: %d\n", - import_result->imported_rsa); - fprintf_plus(stderr, "The number of unchanged keys: %d\n", - import_result->unchanged); - fprintf_plus(stderr, "The number of new user IDs: %d\n", - import_result->new_user_ids); - fprintf_plus(stderr, "The number of new sub keys: %d\n", - import_result->new_sub_keys); - fprintf_plus(stderr, "The number of new signatures: %d\n", - import_result->new_signatures); - fprintf_plus(stderr, "The number of new revocations: %d\n", - import_result->new_revocations); - fprintf_plus(stderr, - "The total number of secret keys read: %d\n", - import_result->secret_read); - fprintf_plus(stderr, - "The number of imported secret keys: %d\n", - import_result->secret_imported); - fprintf_plus(stderr, - "The number of unchanged secret keys: %d\n", - import_result->secret_unchanged); - fprintf_plus(stderr, "The number of keys not imported: %d\n", - import_result->not_imported); - for(gpgme_import_status_t import_status - = import_result->imports; - import_status != NULL; - import_status = import_status->next){ - fprintf_plus(stderr, "Import status for key: %s\n", - import_status->fpr); - if(import_status->result != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "Import result: %s: %s\n", - gpgme_strsource(import_status->result), - gpgme_strerror(import_status->result)); - } - fprintf_plus(stderr, "Key status:\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_NEW - ? "The key was new.\n" - : "The key was not new.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_UID - ? "The key contained new user IDs.\n" - : "The key did not contain new user IDs.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SIG - ? "The key contained new signatures.\n" - : "The key did not contain new signatures.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SUBKEY - ? "The key contained new sub keys.\n" - : "The key did not contain new sub keys.\n"); - fprintf_plus(stderr, - import_status->status & GPGME_IMPORT_SECRET - ? "The key contained a secret key.\n" - : "The key did not contain a secret key.\n"); - } - return false; - } - } - - ret = close(fd); - if(ret == -1){ - perror_plus("close"); - } - gpgme_data_release(pgp_data); - return true; - } - - if(debug){ - fprintf_plus(stderr, "Initializing GPGME\n"); + if (debug){ + fprintf(stderr, "Trying to decrypt OpenPGP packet\n"); } /* Init GPGME */ gpgme_check_version(NULL); - rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_engine_check_version: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - return false; - } + gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); - /* Set GPGME home directory for the OpenPGP engine only */ - rc = gpgme_get_engine_info(&engine_info); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_get_engine_info: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - return false; + /* Set GPGME home directory */ + rc = gpgme_get_engine_info (&engine_info); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + return -1; } while(engine_info != NULL){ if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){ gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, - engine_info->file_name, tempdir); + engine_info->file_name, homedir); break; } engine_info = engine_info->next; } if(engine_info == NULL){ - fprintf_plus(stderr, "Could not set GPGME home dir to %s\n", - tempdir); - return false; - } - - /* Create new GPGME "context" */ - rc = gpgme_new(&(mc->ctx)); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - return false; - } - - if(not import_key(pubkey) or not import_key(seckey)){ - return false; - } - - return true; -} - -/* - * Decrypt OpenPGP data. - * Returns -1 on error - */ -__attribute__((nonnull, warn_unused_result)) -static ssize_t pgp_packet_decrypt(const char *cryptotext, - size_t crypto_size, - char **plaintext, - mandos_context *mc){ - gpgme_data_t dh_crypto, dh_plain; - gpgme_error_t rc; - ssize_t ret; - size_t plaintext_capacity = 0; - ssize_t plaintext_length = 0; - - if(debug){ - fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n"); - } - - /* Create new GPGME data buffer from memory cryptotext */ - rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size, - 0); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf(stderr, "Could not set home dir to %s\n", homedir); + return -1; + } + + /* Create new GPGME data buffer from packet buffer */ + rc = gpgme_data_new_from_mem(&dh_crypto, packet, packet_size, 0); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return -1; } /* Create new empty GPGME data buffer for the plaintext */ rc = gpgme_data_new(&dh_plain); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_data_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - gpgme_data_release(dh_crypto); - return -1; - } - - /* Decrypt data from the cryptotext data buffer to the plaintext - data buffer */ - rc = gpgme_op_decrypt(mc->ctx, dh_crypto, dh_plain); - if(rc != GPG_ERR_NO_ERROR){ - fprintf_plus(stderr, "bad gpgme_op_decrypt: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); - plaintext_length = -1; - if(debug){ - gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc->ctx); - if(result == NULL){ - fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); - } else { - if(result->unsupported_algorithm != NULL) { - fprintf_plus(stderr, "Unsupported algorithm: %s\n", - result->unsupported_algorithm); - } - fprintf_plus(stderr, "Wrong key usage: %s\n", - result->wrong_key_usage ? "Yes" : "No"); - if(result->file_name != NULL){ - fprintf_plus(stderr, "File name: %s\n", result->file_name); - } - - for(gpgme_recipient_t r = result->recipients; r != NULL; - r = r->next){ - fprintf_plus(stderr, "Public key algorithm: %s\n", - gpgme_pubkey_algo_name(r->pubkey_algo)); - fprintf_plus(stderr, "Key ID: %s\n", r->keyid); - fprintf_plus(stderr, "Secret key available: %s\n", - r->status == GPG_ERR_NO_SECKEY ? "No" : "Yes"); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_data_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + return -1; + } + + /* Create new GPGME "context" */ + rc = gpgme_new(&ctx); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + return -1; + } + + /* Decrypt data from the FILE pointer to the plaintext data + buffer */ + rc = gpgme_op_decrypt(ctx, dh_crypto, dh_plain); + if (rc != GPG_ERR_NO_ERROR){ + fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); + return -1; + } + + if(debug){ + fprintf(stderr, "Decryption of OpenPGP packet succeeded\n"); + } + + if (debug){ + gpgme_decrypt_result_t result; + result = gpgme_op_decrypt_result(ctx); + if (result == NULL){ + fprintf(stderr, "gpgme_op_decrypt_result failed\n"); + } else { + fprintf(stderr, "Unsupported algorithm: %s\n", + result->unsupported_algorithm); + fprintf(stderr, "Wrong key usage: %d\n", + result->wrong_key_usage); + if(result->file_name != NULL){ + fprintf(stderr, "File name: %s\n", result->file_name); + } + gpgme_recipient_t recipient; + recipient = result->recipients; + if(recipient){ + while(recipient != NULL){ + fprintf(stderr, "Public key algorithm: %s\n", + gpgme_pubkey_algo_name(recipient->pubkey_algo)); + fprintf(stderr, "Key ID: %s\n", recipient->keyid); + fprintf(stderr, "Secret key available: %s\n", + recipient->status == GPG_ERR_NO_SECKEY + ? "No" : "Yes"); + recipient = recipient->next; } } } - goto decrypt_end; } - if(debug){ - fprintf_plus(stderr, "Decryption of OpenPGP data succeeded\n"); - } + /* Delete the GPGME FILE pointer cryptotext data buffer */ + gpgme_data_release(dh_crypto); /* Seek back to the beginning of the GPGME plaintext data buffer */ - if(gpgme_data_seek(dh_plain, (off_t)0, SEEK_SET) == -1){ - perror_plus("gpgme_data_seek"); - plaintext_length = -1; - goto decrypt_end; - } - - *plaintext = NULL; + gpgme_data_seek(dh_plain, (off_t) 0, SEEK_SET); + + *new_packet = 0; while(true){ - plaintext_capacity = incbuffer(plaintext, - (size_t)plaintext_length, - plaintext_capacity); - if(plaintext_capacity == 0){ - perror_plus("incbuffer"); - plaintext_length = -1; - goto decrypt_end; + if (new_packet_length + BUFFER_SIZE > new_packet_capacity){ + *new_packet = realloc(*new_packet, + (unsigned int)new_packet_capacity + + BUFFER_SIZE); + if (*new_packet == NULL){ + perror("realloc"); + return -1; + } + new_packet_capacity += BUFFER_SIZE; } - ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, + ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length, BUFFER_SIZE); /* Print the data, if any */ - if(ret == 0){ - /* EOF */ + if (ret == 0){ break; } if(ret < 0){ - perror_plus("gpgme_data_read"); - plaintext_length = -1; - goto decrypt_end; - } - plaintext_length += ret; - } - - if(debug){ - fprintf_plus(stderr, "Decrypted password is: "); - for(ssize_t i = 0; i < plaintext_length; i++){ - fprintf(stderr, "%02hhX ", (*plaintext)[i]); - } - fprintf(stderr, "\n"); - } - - decrypt_end: - - /* Delete the GPGME cryptotext data buffer */ - gpgme_data_release(dh_crypto); + perror("gpgme_data_read"); + return -1; + } + new_packet_length += ret; + } + + /* FIXME: check characters before printing to screen so to not print + terminal control characters */ + /* if(debug){ */ + /* fprintf(stderr, "decrypted password is: "); */ + /* fwrite(*new_packet, 1, new_packet_length, stderr); */ + /* fprintf(stderr, "\n"); */ + /* } */ /* Delete the GPGME plaintext data buffer */ gpgme_data_release(dh_plain); - return plaintext_length; -} - -__attribute__((warn_unused_result, const)) -static const char *safe_string(const char *str){ - if(str == NULL) - return "(unknown)"; - return str; -} - -__attribute__((warn_unused_result)) -static const char *safer_gnutls_strerror(int value){ - const char *ret = gnutls_strerror(value); - return safe_string(ret); -} - -/* GnuTLS log function callback */ -__attribute__((nonnull)) -static void debuggnutls(__attribute__((unused)) int level, - const char* string){ - fprintf_plus(stderr, "GnuTLS: %s", string); -} - -__attribute__((nonnull(1, 2, 4), warn_unused_result)) -static int init_gnutls_global(const char *pubkeyfilename, - const char *seckeyfilename, - const char *dhparamsfilename, - mandos_context *mc){ + return new_packet_length; +} + +static const char * safer_gnutls_strerror (int value) { + const char *ret = gnutls_strerror (value); + if (ret == NULL) + ret = "(unknown)"; + return ret; +} + +void debuggnutls(__attribute__((unused)) int level, + const char* string){ + fprintf(stderr, "%s", string); +} + +int initgnutls(encrypted_session *es){ + const char *err; int ret; if(debug){ - fprintf_plus(stderr, "Initializing GnuTLS\n"); + fprintf(stderr, "Initializing GnuTLS\n"); } - if(debug){ - /* "Use a log level over 10 to enable all debugging options." - * - GnuTLS manual - */ + if ((ret = gnutls_global_init ()) + != GNUTLS_E_SUCCESS) { + fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret)); + return -1; + } + + if (debug){ gnutls_global_set_log_level(11); gnutls_global_set_log_function(debuggnutls); } - /* OpenPGP credentials */ - ret = gnutls_certificate_allocate_credentials(&mc->cred); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "GnuTLS memory error: %s\n", - safer_gnutls_strerror(ret)); + /* openpgp credentials */ + if ((ret = gnutls_certificate_allocate_credentials (&es->cred)) + != GNUTLS_E_SUCCESS) { + fprintf (stderr, "memory error: %s\n", + safer_gnutls_strerror(ret)); return -1; } if(debug){ - fprintf_plus(stderr, "Attempting to use public key %s and" - " private key %s as GnuTLS credentials\n", - pubkeyfilename, - seckeyfilename); + fprintf(stderr, "Attempting to use OpenPGP certificate %s" + " and keyfile %s as GnuTLS credentials\n", CERTFILE, + KEYFILE); } -#if GNUTLS_VERSION_NUMBER >= 0x030606 - ret = gnutls_certificate_set_rawpk_key_file - (mc->cred, pubkeyfilename, seckeyfilename, - GNUTLS_X509_FMT_PEM, /* format */ - NULL, /* pass */ - /* key_usage */ - GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT, - NULL, /* names */ - 0, /* names_length */ - /* privkey_flags */ - GNUTLS_PKCS_PLAIN | GNUTLS_PKCS_NULL_PASSWORD, - 0); /* pkcs11_flags */ -#elif GNUTLS_VERSION_NUMBER < 0x030600 ret = gnutls_certificate_set_openpgp_key_file - (mc->cred, pubkeyfilename, seckeyfilename, - GNUTLS_OPENPGP_FMT_BASE64); -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, - "Error[%d] while reading the key pair ('%s'," - " '%s')\n", ret, pubkeyfilename, seckeyfilename); - fprintf_plus(stderr, "The GnuTLS error is: %s\n", - safer_gnutls_strerror(ret)); - goto globalfail; - } - - /* GnuTLS server initialization */ - ret = gnutls_dh_params_init(&mc->dh_params); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error in GnuTLS DH parameter" - " initialization: %s\n", - safer_gnutls_strerror(ret)); - goto globalfail; - } - /* If a Diffie-Hellman parameters file was given, try to use it */ - if(dhparamsfilename != NULL){ - gnutls_datum_t params = { .data = NULL, .size = 0 }; - do { - int dhpfile = open(dhparamsfilename, O_RDONLY); - if(dhpfile == -1){ - perror_plus("open"); - dhparamsfilename = NULL; - break; - } - size_t params_capacity = 0; - while(true){ - params_capacity = incbuffer((char **)¶ms.data, - (size_t)params.size, - (size_t)params_capacity); - if(params_capacity == 0){ - perror_plus("incbuffer"); - free(params.data); - params.data = NULL; - dhparamsfilename = NULL; - break; - } - ssize_t bytes_read = read(dhpfile, - params.data + params.size, - BUFFER_SIZE); - /* EOF */ - if(bytes_read == 0){ - break; - } - /* check bytes_read for failure */ - if(bytes_read < 0){ - perror_plus("read"); - free(params.data); - params.data = NULL; - dhparamsfilename = NULL; - break; - } - params.size += (unsigned int)bytes_read; - } - ret = close(dhpfile); - if(ret == -1){ - perror_plus("close"); - } - if(params.data == NULL){ - dhparamsfilename = NULL; - } - if(dhparamsfilename == NULL){ - break; - } - ret = gnutls_dh_params_import_pkcs3(mc->dh_params, ¶ms, - GNUTLS_X509_FMT_PEM); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Failed to parse DH parameters in file" - " \"%s\": %s\n", dhparamsfilename, - safer_gnutls_strerror(ret)); - dhparamsfilename = NULL; - } - free(params.data); - } while(false); - } - if(dhparamsfilename == NULL){ - if(mc->dh_bits == 0){ -#if GNUTLS_VERSION_NUMBER < 0x030600 - /* Find out the optimal number of DH bits */ - /* Try to read the private key file */ - gnutls_datum_t buffer = { .data = NULL, .size = 0 }; - do { - int secfile = open(seckeyfilename, O_RDONLY); - if(secfile == -1){ - perror_plus("open"); - break; - } - size_t buffer_capacity = 0; - while(true){ - buffer_capacity = incbuffer((char **)&buffer.data, - (size_t)buffer.size, - (size_t)buffer_capacity); - if(buffer_capacity == 0){ - perror_plus("incbuffer"); - free(buffer.data); - buffer.data = NULL; - break; - } - ssize_t bytes_read = read(secfile, - buffer.data + buffer.size, - BUFFER_SIZE); - /* EOF */ - if(bytes_read == 0){ - break; - } - /* check bytes_read for failure */ - if(bytes_read < 0){ - perror_plus("read"); - free(buffer.data); - buffer.data = NULL; - break; - } - buffer.size += (unsigned int)bytes_read; - } - close(secfile); - } while(false); - /* If successful, use buffer to parse private key */ - gnutls_sec_param_t sec_param = GNUTLS_SEC_PARAM_ULTRA; - if(buffer.data != NULL){ - { - gnutls_openpgp_privkey_t privkey = NULL; - ret = gnutls_openpgp_privkey_init(&privkey); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error initializing OpenPGP key" - " structure: %s", - safer_gnutls_strerror(ret)); - free(buffer.data); - buffer.data = NULL; - } else { - ret = gnutls_openpgp_privkey_import - (privkey, &buffer, GNUTLS_OPENPGP_FMT_BASE64, "", 0); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error importing OpenPGP key : %s", - safer_gnutls_strerror(ret)); - privkey = NULL; - } - free(buffer.data); - buffer.data = NULL; - if(privkey != NULL){ - /* Use private key to suggest an appropriate - sec_param */ - sec_param = gnutls_openpgp_privkey_sec_param(privkey); - gnutls_openpgp_privkey_deinit(privkey); - if(debug){ - fprintf_plus(stderr, "This OpenPGP key implies using" - " a GnuTLS security parameter \"%s\".\n", - safe_string(gnutls_sec_param_get_name - (sec_param))); - } - } - } - } - if(sec_param == GNUTLS_SEC_PARAM_UNKNOWN){ - /* Err on the side of caution */ - sec_param = GNUTLS_SEC_PARAM_ULTRA; - if(debug){ - fprintf_plus(stderr, "Falling back to security parameter" - " \"%s\"\n", - safe_string(gnutls_sec_param_get_name - (sec_param))); - } - } - } - unsigned int uret = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, sec_param); - if(uret != 0){ - mc->dh_bits = uret; - if(debug){ - fprintf_plus(stderr, "A \"%s\" GnuTLS security parameter" - " implies %u DH bits; using that.\n", - safe_string(gnutls_sec_param_get_name - (sec_param)), - mc->dh_bits); - } - } else { - fprintf_plus(stderr, "Failed to get implied number of DH" - " bits for security parameter \"%s\"): %s\n", - safe_string(gnutls_sec_param_get_name - (sec_param)), - safer_gnutls_strerror(ret)); - goto globalfail; - } -#endif - } else { /* dh_bits != 0 */ - if(debug){ - fprintf_plus(stderr, "DH bits explicitly set to %u\n", - mc->dh_bits); - } - ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error in GnuTLS prime generation (%u" - " bits): %s\n", mc->dh_bits, - safer_gnutls_strerror(ret)); - goto globalfail; - } - gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); - } - } - - return 0; - - globalfail: - - gnutls_certificate_free_credentials(mc->cred); - gnutls_dh_params_deinit(mc->dh_params); - return -1; -} - -__attribute__((nonnull, warn_unused_result)) -static int init_gnutls_session(gnutls_session_t *session, - mandos_context *mc){ - int ret; - /* GnuTLS session creation */ - do { - ret = gnutls_init(session, (GNUTLS_SERVER -#if GNUTLS_VERSION_NUMBER >= 0x030506 - | GNUTLS_NO_TICKETS -#endif -#if GNUTLS_VERSION_NUMBER >= 0x030606 - | GNUTLS_ENABLE_RAWPK -#endif - )); - if(quit_now){ - return -1; - } - } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, - "Error in GnuTLS session initialization: %s\n", - safer_gnutls_strerror(ret)); - } - - { - const char *err; - do { - ret = gnutls_priority_set_direct(*session, mc->priority, &err); - if(quit_now){ - gnutls_deinit(*session); - return -1; - } - } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Syntax error at: %s\n", err); - fprintf_plus(stderr, "GnuTLS error: %s\n", - safer_gnutls_strerror(ret)); - gnutls_deinit(*session); - return -1; - } - } - - do { - ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - mc->cred); - if(quit_now){ - gnutls_deinit(*session); - return -1; - } - } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); - if(ret != GNUTLS_E_SUCCESS){ - fprintf_plus(stderr, "Error setting GnuTLS credentials: %s\n", - safer_gnutls_strerror(ret)); - gnutls_deinit(*session); + (es->cred, CERTFILE, KEYFILE, GNUTLS_OPENPGP_FMT_BASE64); + if (ret != GNUTLS_E_SUCCESS) { + fprintf + (stderr, "Error[%d] while reading the OpenPGP key pair ('%s'," + " '%s')\n", + ret, CERTFILE, KEYFILE); + fprintf(stdout, "The Error is: %s\n", + safer_gnutls_strerror(ret)); + return -1; + } + + //GnuTLS server initialization + if ((ret = gnutls_dh_params_init (&es->dh_params)) + != GNUTLS_E_SUCCESS) { + fprintf (stderr, "Error in dh parameter initialization: %s\n", + safer_gnutls_strerror(ret)); + return -1; + } + + if ((ret = gnutls_dh_params_generate2 (es->dh_params, DH_BITS)) + != GNUTLS_E_SUCCESS) { + fprintf (stderr, "Error in prime generation: %s\n", + safer_gnutls_strerror(ret)); + return -1; + } + + gnutls_certificate_set_dh_params (es->cred, es->dh_params); + + // GnuTLS session creation + if ((ret = gnutls_init (&es->session, GNUTLS_SERVER)) + != GNUTLS_E_SUCCESS){ + fprintf(stderr, "Error in GnuTLS session initialization: %s\n", + safer_gnutls_strerror(ret)); + } + + if ((ret = gnutls_priority_set_direct (es->session, "NORMAL", &err)) + != GNUTLS_E_SUCCESS) { + fprintf(stderr, "Syntax error at: %s\n", err); + fprintf(stderr, "GnuTLS error: %s\n", + safer_gnutls_strerror(ret)); + return -1; + } + + if ((ret = gnutls_credentials_set + (es->session, GNUTLS_CRD_CERTIFICATE, es->cred)) + != GNUTLS_E_SUCCESS) { + fprintf(stderr, "Error setting a credentials set: %s\n", + safer_gnutls_strerror(ret)); return -1; } /* ignore client certificate if any. */ - gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE); + gnutls_certificate_server_set_request (es->session, + GNUTLS_CERT_IGNORE); + + gnutls_dh_set_prime_bits (es->session, DH_BITS); return 0; } -/* Avahi log function callback */ -static void empty_log(__attribute__((unused)) AvahiLogLevel level, - __attribute__((unused)) const char *txt){} - -/* Helper function to add_local_route() and delete_local_route() */ -__attribute__((nonnull, warn_unused_result)) -static bool add_delete_local_route(const bool add, - const char *address, - AvahiIfIndex if_index){ - int ret; - char helper[] = "mandos-client-iprouteadddel"; - char add_arg[] = "add"; - char delete_arg[] = "delete"; - char debug_flag[] = "--debug"; - char *pluginhelperdir = getenv("MANDOSPLUGINHELPERDIR"); - if(pluginhelperdir == NULL){ - if(debug){ - fprintf_plus(stderr, "MANDOSPLUGINHELPERDIR environment" - " variable not set; cannot run helper\n"); - } - return false; - } - - char interface[IF_NAMESIZE]; - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror_plus("if_indextoname"); - return false; - } - - int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); - if(devnull == -1){ - perror_plus("open(\"/dev/null\", O_RDONLY)"); - return false; - } - pid_t pid = fork(); - if(pid == 0){ - /* Child */ - /* Raise privileges */ - errno = raise_privileges_permanently(); - if(errno != 0){ - perror_plus("Failed to raise privileges"); - /* _exit(EX_NOPERM); */ - } else { - /* Set group */ - errno = 0; - ret = setgid(0); - if(ret == -1){ - perror_plus("setgid"); - _exit(EX_NOPERM); - } - /* Reset supplementary groups */ - errno = 0; - ret = setgroups(0, NULL); - if(ret == -1){ - perror_plus("setgroups"); - _exit(EX_NOPERM); - } - } - ret = dup2(devnull, STDIN_FILENO); - if(ret == -1){ - perror_plus("dup2(devnull, STDIN_FILENO)"); - _exit(EX_OSERR); - } - ret = close(devnull); - if(ret == -1){ - perror_plus("close"); - _exit(EX_OSERR); - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); - if(ret == -1){ - perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); - _exit(EX_OSERR); - } - int helperdir_fd = (int)TEMP_FAILURE_RETRY(open(pluginhelperdir, - O_RDONLY - | O_DIRECTORY - | O_PATH - | O_CLOEXEC)); - if(helperdir_fd == -1){ - perror_plus("open"); - _exit(EX_UNAVAILABLE); - } - int helper_fd = (int)TEMP_FAILURE_RETRY(openat(helperdir_fd, - helper, O_RDONLY)); - if(helper_fd == -1){ - perror_plus("openat"); - close(helperdir_fd); - _exit(EX_UNAVAILABLE); - } - close(helperdir_fd); -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - if(fexecve(helper_fd, (char *const []) - { helper, add ? add_arg : delete_arg, (char *)address, - interface, debug ? debug_flag : NULL, NULL }, - environ) == -1){ -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - perror_plus("fexecve"); - _exit(EXIT_FAILURE); - } - } - if(pid == -1){ - perror_plus("fork"); - return false; - } - int status; - pid_t pret = -1; - errno = 0; - do { - pret = waitpid(pid, &status, 0); - if(pret == -1 and errno == EINTR and quit_now){ - int errno_raising = 0; - if((errno = raise_privileges()) != 0){ - errno_raising = errno; - perror_plus("Failed to raise privileges in order to" - " kill helper program"); - } - if(kill(pid, SIGTERM) == -1){ - perror_plus("kill"); - } - if((errno_raising == 0) and (errno = lower_privileges()) != 0){ - perror_plus("Failed to lower privileges after killing" - " helper program"); - } - return false; - } - } while(pret == -1 and errno == EINTR); - if(pret == -1){ - perror_plus("waitpid"); - return false; - } - if(WIFEXITED(status)){ - if(WEXITSTATUS(status) != 0){ - fprintf_plus(stderr, "Error: iprouteadddel exited" - " with status %d\n", WEXITSTATUS(status)); - return false; - } - return true; - } - if(WIFSIGNALED(status)){ - fprintf_plus(stderr, "Error: iprouteadddel died by" - " signal %d\n", WTERMSIG(status)); - return false; - } - fprintf_plus(stderr, "Error: iprouteadddel crashed\n"); - return false; -} - -__attribute__((nonnull, warn_unused_result)) -static bool add_local_route(const char *address, - AvahiIfIndex if_index){ - if(debug){ - fprintf_plus(stderr, "Adding route to %s\n", address); - } - return add_delete_local_route(true, address, if_index); -} - -__attribute__((nonnull, warn_unused_result)) -static bool delete_local_route(const char *address, +void empty_log(__attribute__((unused)) AvahiLogLevel level, + __attribute__((unused)) const char *txt){} + +int start_mandos_communication(const char *ip, uint16_t port, AvahiIfIndex if_index){ - if(debug){ - fprintf_plus(stderr, "Removing route to %s\n", address); - } - return add_delete_local_route(false, address, if_index); -} - -/* Called when a Mandos server is found */ -__attribute__((nonnull, warn_unused_result)) -static int start_mandos_communication(const char *ip, in_port_t port, - AvahiIfIndex if_index, - int af, mandos_context *mc){ - int ret, tcp_sd = -1; - ssize_t sret; - struct sockaddr_storage to; + int ret, tcp_sd; + struct sockaddr_in6 to; + encrypted_session es; char *buffer = NULL; - char *decrypted_buffer = NULL; + char *decrypted_buffer; size_t buffer_length = 0; size_t buffer_capacity = 0; - size_t written; - int retval = -1; - gnutls_session_t session; - int pf; /* Protocol family */ - bool route_added = false; - - errno = 0; - - if(quit_now){ - errno = EINTR; - return -1; - } - - switch(af){ - case AF_INET6: - pf = PF_INET6; - break; - case AF_INET: - pf = PF_INET; - break; - default: - fprintf_plus(stderr, "Bad address family: %d\n", af); - errno = EINVAL; - return -1; - } - - /* If the interface is specified and we have a list of interfaces */ - if(if_index != AVAHI_IF_UNSPEC and mc->interfaces != NULL){ - /* Check if the interface is one of the interfaces we are using */ - bool match = false; - { - char *interface = NULL; - while((interface = argz_next(mc->interfaces, - mc->interfaces_size, - interface))){ - if(if_nametoindex(interface) == (unsigned int)if_index){ - match = true; - break; - } - } - } - if(not match){ - /* This interface does not match any in the list, so we don't - connect to the server */ - if(debug){ - char interface[IF_NAMESIZE]; - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror_plus("if_indextoname"); - } else { - fprintf_plus(stderr, "Skipping server on non-used interface" - " \"%s\"\n", - if_indextoname((unsigned int)if_index, - interface)); - } - } - return -1; - } - } - - ret = init_gnutls_session(&session, mc); - if(ret != 0){ - return -1; - } - - if(debug){ - fprintf_plus(stderr, "Setting up a TCP connection to %s, port %" - PRIuMAX "\n", ip, (uintmax_t)port); - } - - tcp_sd = socket(pf, SOCK_STREAM | SOCK_CLOEXEC, 0); - if(tcp_sd < 0){ - int e = errno; - perror_plus("socket"); - errno = e; - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - if(af == AF_INET6){ - struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&to; - *to6 = (struct sockaddr_in6){ .sin6_family = (sa_family_t)af }; - ret = inet_pton(af, ip, &to6->sin6_addr); - } else { /* IPv4 */ - struct sockaddr_in *to4 = (struct sockaddr_in *)&to; - *to4 = (struct sockaddr_in){ .sin_family = (sa_family_t)af }; - ret = inet_pton(af, ip, &to4->sin_addr); - } - if(ret < 0 ){ - int e = errno; - perror_plus("inet_pton"); - errno = e; - goto mandos_end; - } + ssize_t decrypted_buffer_size; + size_t written = 0; + int retval = 0; + char interface[IF_NAMESIZE]; + + if(debug){ + fprintf(stderr, "Setting up a tcp connection to %s, port %d\n", + ip, port); + } + + tcp_sd = socket(PF_INET6, SOCK_STREAM, 0); + if(tcp_sd < 0) { + perror("socket"); + return -1; + } + + if(if_indextoname((unsigned int)if_index, interface) == NULL){ + if(debug){ + perror("if_indextoname"); + } + return -1; + } + + if(debug){ + fprintf(stderr, "Binding to interface %s\n", interface); + } + + memset(&to,0,sizeof(to)); /* Spurious warning */ + to.sin6_family = AF_INET6; + ret = inet_pton(AF_INET6, ip, &to.sin6_addr); + if (ret < 0 ){ + perror("inet_pton"); + return -1; + } if(ret == 0){ - int e = errno; - fprintf_plus(stderr, "Bad address: %s\n", ip); - errno = e; - goto mandos_end; - } - if(af == AF_INET6){ - ((struct sockaddr_in6 *)&to)->sin6_port = htons(port); - if(IN6_IS_ADDR_LINKLOCAL - (&((struct sockaddr_in6 *)&to)->sin6_addr)){ - if(if_index == AVAHI_IF_UNSPEC){ - fprintf_plus(stderr, "An IPv6 link-local address is" - " incomplete without a network interface\n"); - errno = EINVAL; - goto mandos_end; - } - /* Set the network interface number as scope */ - ((struct sockaddr_in6 *)&to)->sin6_scope_id = (uint32_t)if_index; - } - } else { - ((struct sockaddr_in *)&to)->sin_port = htons(port); - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - if(debug){ - if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){ - char interface[IF_NAMESIZE]; - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror_plus("if_indextoname"); - } else { - fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIuMAX - "\n", ip, interface, (uintmax_t)port); - } - } else { - fprintf_plus(stderr, "Connection to: %s, port %" PRIuMAX "\n", - ip, (uintmax_t)port); - } - char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? - INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; - if(af == AF_INET6){ - ret = getnameinfo((struct sockaddr *)&to, - sizeof(struct sockaddr_in6), - addrstr, sizeof(addrstr), NULL, 0, - NI_NUMERICHOST); - } else { - ret = getnameinfo((struct sockaddr *)&to, - sizeof(struct sockaddr_in), - addrstr, sizeof(addrstr), NULL, 0, - NI_NUMERICHOST); - } - if(ret == EAI_SYSTEM){ - perror_plus("getnameinfo"); - } else if(ret != 0) { - fprintf_plus(stderr, "getnameinfo: %s", gai_strerror(ret)); - } else if(strcmp(addrstr, ip) != 0){ - fprintf_plus(stderr, "Canonical address form: %s\n", addrstr); - } - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - while(true){ - if(af == AF_INET6){ - ret = connect(tcp_sd, (struct sockaddr *)&to, - sizeof(struct sockaddr_in6)); - } else { - ret = connect(tcp_sd, (struct sockaddr *)&to, /* IPv4 */ - sizeof(struct sockaddr_in)); - } - if(ret < 0){ - if(((errno == ENETUNREACH) or (errno == EHOSTUNREACH)) - and if_index != AVAHI_IF_UNSPEC - and connect_to == NULL - and not route_added and - ((af == AF_INET6 and not - IN6_IS_ADDR_LINKLOCAL(&(((struct sockaddr_in6 *) - &to)->sin6_addr))) - or (af == AF_INET and - /* Not a a IPv4LL address */ - (ntohl(((struct sockaddr_in *)&to)->sin_addr.s_addr) - & 0xFFFF0000L) != 0xA9FE0000L))){ - /* Work around Avahi bug - Avahi does not announce link-local - addresses if it has a global address, so local hosts with - *only* a link-local address (e.g. Mandos clients) cannot - connect to a Mandos server announced by Avahi on a server - host with a global address. Work around this by retrying - with an explicit route added with the server's address. - - Avahi bug reference: - https://lists.freedesktop.org/archives/avahi/2010-February/001833.html - https://bugs.debian.org/587961 - */ - if(debug){ - fprintf_plus(stderr, "Mandos server unreachable, trying" - " direct route\n"); - } - int e = errno; - route_added = add_local_route(ip, if_index); - if(route_added){ - continue; - } - errno = e; - } - if(errno != ECONNREFUSED or debug){ - int e = errno; - perror_plus("connect"); - errno = e; - } - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - break; - } - - const char *out = mandos_protocol_version; - written = 0; - while(true){ - size_t out_size = strlen(out); - ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, - out_size - written)); - if(ret == -1){ - int e = errno; - perror_plus("write"); - errno = e; - goto mandos_end; - } - written += (size_t)ret; - if(written < out_size){ - continue; - } else { - if(out == mandos_protocol_version){ - written = 0; - out = "\r\n"; - } else { - break; - } - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - } - - if(debug){ - fprintf_plus(stderr, "Establishing TLS session with %s\n", ip); - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - /* This casting via intptr_t is to eliminate warning about casting - an int to a pointer type. This is exactly how the GnuTLS Guile - function "set-session-transport-fd!" does it. */ - gnutls_transport_set_ptr(session, - (gnutls_transport_ptr_t)(intptr_t)tcp_sd); - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - do { - ret = gnutls_handshake(session); - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - - if(ret != GNUTLS_E_SUCCESS){ + fprintf(stderr, "Bad address: %s\n", ip); + return -1; + } + to.sin6_port = htons(port); /* Spurious warning */ + + to.sin6_scope_id = (uint32_t)if_index; + + if(debug){ + fprintf(stderr, "Connection to: %s, port %d\n", ip, port); +/* char addrstr[INET6_ADDRSTRLEN]; */ +/* if(inet_ntop(to.sin6_family, &(to.sin6_addr), addrstr, */ +/* sizeof(addrstr)) == NULL){ */ +/* perror("inet_ntop"); */ +/* } else { */ +/* fprintf(stderr, "Really connecting to: %s, port %d\n", */ +/* addrstr, ntohs(to.sin6_port)); */ +/* } */ + } + + ret = connect(tcp_sd, (struct sockaddr *) &to, sizeof(to)); + if (ret < 0){ + perror("connect"); + return -1; + } + + ret = initgnutls (&es); + if (ret != 0){ + retval = -1; + return -1; + } + + gnutls_transport_set_ptr (es.session, + (gnutls_transport_ptr_t) tcp_sd); + + if(debug){ + fprintf(stderr, "Establishing TLS session with %s\n", ip); + } + + ret = gnutls_handshake (es.session); + + if (ret != GNUTLS_E_SUCCESS){ if(debug){ - fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n"); - gnutls_perror(ret); + fprintf(stderr, "\n*** Handshake failed ***\n"); + gnutls_perror (ret); } - errno = EPROTO; - goto mandos_end; + retval = -1; + goto exit; } - /* Read OpenPGP packet that contains the wanted password */ + //Retrieve OpenPGP packet that contains the wanted password if(debug){ - fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from" - " %s\n", ip); + fprintf(stderr, "Retrieving pgp encrypted password from %s\n", + ip); } - + while(true){ - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - buffer_capacity = incbuffer(&buffer, buffer_length, - buffer_capacity); - if(buffer_capacity == 0){ - int e = errno; - perror_plus("incbuffer"); - errno = e; - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - sret = gnutls_record_recv(session, buffer+buffer_length, - BUFFER_SIZE); - if(sret == 0){ + if (buffer_length + BUFFER_SIZE > buffer_capacity){ + buffer = realloc(buffer, buffer_capacity + BUFFER_SIZE); + if (buffer == NULL){ + perror("realloc"); + goto exit; + } + buffer_capacity += BUFFER_SIZE; + } + + ret = gnutls_record_recv + (es.session, buffer+buffer_length, BUFFER_SIZE); + if (ret == 0){ break; } - if(sret < 0){ - switch(sret){ + if (ret < 0){ + switch(ret){ case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: break; case GNUTLS_E_REHANDSHAKE: - do { - ret = gnutls_handshake(session); - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - if(ret < 0){ - fprintf_plus(stderr, "*** GnuTLS Re-handshake failed " - "***\n"); - gnutls_perror(ret); - errno = EPROTO; - goto mandos_end; + ret = gnutls_handshake (es.session); + if (ret < 0){ + fprintf(stderr, "\n*** Handshake failed ***\n"); + gnutls_perror (ret); + retval = -1; + goto exit; } break; default: - fprintf_plus(stderr, "Unknown error while reading data from" - " encrypted session with Mandos server\n"); - gnutls_bye(session, GNUTLS_SHUT_RDWR); - errno = EIO; - goto mandos_end; + fprintf(stderr, "Unknown error while reading data from" + " encrypted session with mandos server\n"); + retval = -1; + gnutls_bye (es.session, GNUTLS_SHUT_RDWR); + goto exit; } } else { - buffer_length += (size_t) sret; - } - } - - if(debug){ - fprintf_plus(stderr, "Closing TLS session\n"); - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - do { - ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); - - if(buffer_length > 0){ - ssize_t decrypted_buffer_size; - decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, - &decrypted_buffer, mc); - if(decrypted_buffer_size >= 0){ - - clearerr(stdout); - written = 0; + buffer_length += (size_t) ret; + } + } + + if (buffer_length > 0){ + decrypted_buffer_size = pgp_packet_decrypt(buffer, + buffer_length, + &decrypted_buffer, + CERT_ROOT); + if (decrypted_buffer_size >= 0){ while(written < (size_t) decrypted_buffer_size){ - if(quit_now){ - errno = EINTR; - goto mandos_end; - } - - ret = (int)fwrite(decrypted_buffer + written, 1, - (size_t)decrypted_buffer_size - written, - stdout); + ret = (int)fwrite (decrypted_buffer + written, 1, + (size_t)decrypted_buffer_size - written, + stdout); if(ret == 0 and ferror(stdout)){ - int e = errno; if(debug){ - fprintf_plus(stderr, "Error writing encrypted data: %s\n", - strerror(errno)); + fprintf(stderr, "Error writing encrypted data: %s\n", + strerror(errno)); } - errno = e; - goto mandos_end; + retval = -1; + break; } written += (size_t)ret; } - ret = fflush(stdout); - if(ret != 0){ - int e = errno; - if(debug){ - fprintf_plus(stderr, "Error writing encrypted data: %s\n", - strerror(errno)); - } - errno = e; - goto mandos_end; - } - retval = 0; - } - } - - /* Shutdown procedure */ - - mandos_end: - { - if(route_added){ - if(not delete_local_route(ip, if_index)){ - fprintf_plus(stderr, "Failed to delete local route to %s on" - " interface %d", ip, if_index); - } - } - int e = errno; - free(decrypted_buffer); - free(buffer); - if(tcp_sd >= 0){ - ret = close(tcp_sd); - } - if(ret == -1){ - if(e == 0){ - e = errno; - } - perror_plus("close"); - } - gnutls_deinit(session); - errno = e; - if(quit_now){ - errno = EINTR; + free(decrypted_buffer); + } else { retval = -1; } } + + //shutdown procedure + + if(debug){ + fprintf(stderr, "Closing TLS session\n"); + } + + free(buffer); + gnutls_bye (es.session, GNUTLS_SHUT_RDWR); + exit: + close(tcp_sd); + gnutls_deinit (es.session); + gnutls_certificate_free_credentials (es.cred); + gnutls_global_deinit (); return retval; } -static void resolve_callback(AvahiSServiceResolver *r, - AvahiIfIndex interface, - AvahiProtocol proto, - AvahiResolverEvent event, - const char *name, - const char *type, - const char *domain, - const char *host_name, - const AvahiAddress *address, - uint16_t port, - AVAHI_GCC_UNUSED AvahiStringList *txt, - AVAHI_GCC_UNUSED AvahiLookupResultFlags - flags, - void *mc){ - if(r == NULL){ - return; - } +static AvahiSimplePoll *simple_poll = NULL; +static AvahiServer *server = NULL; + +static void resolve_callback( + AvahiSServiceResolver *r, + AvahiIfIndex interface, + AVAHI_GCC_UNUSED AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *address, + uint16_t port, + AVAHI_GCC_UNUSED AvahiStringList *txt, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + + assert(r); /* Spurious warning */ /* Called whenever a service has been resolved successfully or timed out */ - if(quit_now){ - avahi_s_service_resolver_free(r); - return; - } - - switch(event){ + switch (event) { default: case AVAHI_RESOLVER_FAILURE: - fprintf_plus(stderr, "(Avahi Resolver) Failed to resolve service " - "'%s' of type '%s' in domain '%s': %s\n", name, type, - domain, - avahi_strerror(avahi_server_errno - (((mandos_context*)mc)->server))); + fprintf(stderr, "(Resolver) Failed to resolve service '%s' of" + " type '%s' in domain '%s': %s\n", name, type, domain, + avahi_strerror(avahi_server_errno(server))); break; case AVAHI_RESOLVER_FOUND: @@ -1696,1654 +561,209 @@ char ip[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(ip, sizeof(ip), address); if(debug){ - fprintf_plus(stderr, "Mandos server \"%s\" found on %s (%s, %" - PRIdMAX ") on port %" PRIu16 "\n", name, - host_name, ip, (intmax_t)interface, port); + fprintf(stderr, "Mandos server \"%s\" found on %s (%s) on" + " port %d\n", name, host_name, ip, port); } - int ret = start_mandos_communication(ip, (in_port_t)port, - interface, - avahi_proto_to_af(proto), - mc); - if(ret == 0){ - avahi_simple_poll_quit(simple_poll); - } else { - if(not add_server(ip, (in_port_t)port, interface, - avahi_proto_to_af(proto), - &((mandos_context*)mc)->current_server)){ - fprintf_plus(stderr, "Failed to add server \"%s\" to server" - " list\n", name); - } + int ret = start_mandos_communication(ip, port, interface); + if (ret == 0){ + exit(EXIT_SUCCESS); } } } avahi_s_service_resolver_free(r); } -static void browse_callback(AvahiSServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AVAHI_GCC_UNUSED AvahiLookupResultFlags - flags, - void *mc){ - if(b == NULL){ - return; - } - - /* Called whenever a new services becomes available on the LAN or - is removed from the LAN */ - - if(quit_now){ - return; - } - - switch(event){ - default: - case AVAHI_BROWSER_FAILURE: - - fprintf_plus(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno - (((mandos_context*)mc)->server))); - avahi_simple_poll_quit(simple_poll); - return; - - case AVAHI_BROWSER_NEW: - /* We ignore the returned Avahi resolver object. In the callback - function we free it. If the Avahi server is terminated before - the callback function is called the Avahi server will free the - resolver for us. */ - - if(avahi_s_service_resolver_new(((mandos_context*)mc)->server, - interface, protocol, name, type, - domain, protocol, 0, - resolve_callback, mc) == NULL) - fprintf_plus(stderr, "Avahi: Failed to resolve service '%s':" - " %s\n", name, - avahi_strerror(avahi_server_errno - (((mandos_context*)mc)->server))); - break; - - case AVAHI_BROWSER_REMOVE: - break; - - case AVAHI_BROWSER_ALL_FOR_NOW: - case AVAHI_BROWSER_CACHE_EXHAUSTED: - if(debug){ - fprintf_plus(stderr, "No Mandos server found, still" - " searching...\n"); - } - break; - } -} - -/* Signal handler that stops main loop after SIGTERM */ -static void handle_sigterm(int sig){ - if(quit_now){ - return; - } - quit_now = 1; - signal_received = sig; - int old_errno = errno; - /* set main loop to exit */ - if(simple_poll != NULL){ - avahi_simple_poll_quit(simple_poll); - } - errno = old_errno; -} - -__attribute__((nonnull, warn_unused_result)) -bool get_flags(const char *ifname, struct ifreq *ifr){ - int ret; - int old_errno; - - int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(s < 0){ - old_errno = errno; - perror_plus("socket"); - errno = old_errno; - return false; - } - strncpy(ifr->ifr_name, ifname, IF_NAMESIZE); - ifr->ifr_name[IF_NAMESIZE-1] = '\0'; /* NUL terminate */ - ret = ioctl(s, SIOCGIFFLAGS, ifr); - if(ret == -1){ - if(debug){ - old_errno = errno; - perror_plus("ioctl SIOCGIFFLAGS"); - errno = old_errno; - } - if((close(s) == -1) and debug){ - old_errno = errno; - perror_plus("close"); - errno = old_errno; - } - return false; - } - if((close(s) == -1) and debug){ - old_errno = errno; - perror_plus("close"); - errno = old_errno; - } - return true; -} - -__attribute__((nonnull, warn_unused_result)) -bool good_flags(const char *ifname, const struct ifreq *ifr){ - - /* Reject the loopback device */ - if(ifr->ifr_flags & IFF_LOOPBACK){ - if(debug){ - fprintf_plus(stderr, "Rejecting loopback interface \"%s\"\n", - ifname); - } - return false; - } - /* Accept point-to-point devices only if connect_to is specified */ - if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){ - if(debug){ - fprintf_plus(stderr, "Accepting point-to-point interface" - " \"%s\"\n", ifname); - } - return true; - } - /* Otherwise, reject non-broadcast-capable devices */ - if(not (ifr->ifr_flags & IFF_BROADCAST)){ - if(debug){ - fprintf_plus(stderr, "Rejecting non-broadcast interface" - " \"%s\"\n", ifname); - } - return false; - } - /* Reject non-ARP interfaces (including dummy interfaces) */ - if(ifr->ifr_flags & IFF_NOARP){ - if(debug){ - fprintf_plus(stderr, "Rejecting non-ARP interface \"%s\"\n", - ifname); - } - return false; - } - - /* Accept this device */ - if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is good\n", ifname); - } - return true; -} - -/* - * This function determines if a directory entry in /sys/class/net - * corresponds to an acceptable network device. - * (This function is passed to scandir(3) as a filter function.) - */ -__attribute__((nonnull, warn_unused_result)) -int good_interface(const struct dirent *if_entry){ - if(if_entry->d_name[0] == '.'){ - return 0; - } - - struct ifreq ifr; - if(not get_flags(if_entry->d_name, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", if_entry->d_name); - } - return 0; - } - - if(not good_flags(if_entry->d_name, &ifr)){ - return 0; - } - return 1; -} - -/* - * This function determines if a network interface is up. - */ -__attribute__((nonnull, warn_unused_result)) -bool interface_is_up(const char *interface){ - struct ifreq ifr; - if(not get_flags(interface, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - } - return false; - } - - return (bool)(ifr.ifr_flags & IFF_UP); -} - -/* - * This function determines if a network interface is running - */ -__attribute__((nonnull, warn_unused_result)) -bool interface_is_running(const char *interface){ - struct ifreq ifr; - if(not get_flags(interface, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - } - return false; - } - - return (bool)(ifr.ifr_flags & IFF_RUNNING); -} - -__attribute__((nonnull, pure, warn_unused_result)) -int notdotentries(const struct dirent *direntry){ - /* Skip "." and ".." */ - if(direntry->d_name[0] == '.' - and (direntry->d_name[1] == '\0' - or (direntry->d_name[1] == '.' - and direntry->d_name[2] == '\0'))){ - return 0; - } - return 1; -} - -/* Is this directory entry a runnable program? */ -__attribute__((nonnull, warn_unused_result)) -int runnable_hook(const struct dirent *direntry){ - int ret; - size_t sret; - struct stat st; - - if((direntry->d_name)[0] == '\0'){ - /* Empty name? */ - return 0; - } - - sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "_.-"); - if((direntry->d_name)[sret] != '\0'){ - /* Contains non-allowed characters */ - if(debug){ - fprintf_plus(stderr, "Ignoring hook \"%s\" with bad name\n", - direntry->d_name); - } - return 0; - } - - ret = fstatat(hookdir_fd, direntry->d_name, &st, 0); - if(ret == -1){ - if(debug){ - perror_plus("Could not stat hook"); - } - return 0; - } - if(not (S_ISREG(st.st_mode))){ - /* Not a regular file */ - if(debug){ - fprintf_plus(stderr, "Ignoring hook \"%s\" - not a file\n", - direntry->d_name); - } - return 0; - } - if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){ - /* Not executable */ - if(debug){ - fprintf_plus(stderr, "Ignoring hook \"%s\" - not executable\n", - direntry->d_name); - } - return 0; - } - if(debug){ - fprintf_plus(stderr, "Hook \"%s\" is acceptable\n", - direntry->d_name); - } - return 1; -} - -__attribute__((nonnull, warn_unused_result)) -int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval, - mandos_context *mc){ - int ret; - struct timespec now; - struct timespec waited_time; - intmax_t block_time; - - while(true){ - if(mc->current_server == NULL){ - if(debug){ - fprintf_plus(stderr, "Wait until first server is found." - " No timeout!\n"); - } - ret = avahi_simple_poll_iterate(s, -1); - } else { - if(debug){ - fprintf_plus(stderr, "Check current_server if we should run" - " it, or wait\n"); - } - /* the current time */ - ret = clock_gettime(CLOCK_MONOTONIC, &now); - if(ret == -1){ - perror_plus("clock_gettime"); - return -1; - } - /* Calculating in ms how long time between now and server - who we visted longest time ago. Now - last seen. */ - waited_time.tv_sec = (now.tv_sec - - mc->current_server->last_seen.tv_sec); - waited_time.tv_nsec = (now.tv_nsec - - mc->current_server->last_seen.tv_nsec); - /* total time is 10s/10,000ms. - Converting to s from ms by dividing by 1,000, - and ns to ms by dividing by 1,000,000. */ - block_time = ((retry_interval - - ((intmax_t)waited_time.tv_sec * 1000)) - - ((intmax_t)waited_time.tv_nsec / 1000000)); - - if(debug){ - fprintf_plus(stderr, "Blocking for %" PRIdMAX " ms\n", - block_time); - } - - if(block_time <= 0){ - ret = start_mandos_communication(mc->current_server->ip, - mc->current_server->port, - mc->current_server->if_index, - mc->current_server->af, mc); - if(ret == 0){ - avahi_simple_poll_quit(s); - return 0; - } - ret = clock_gettime(CLOCK_MONOTONIC, - &mc->current_server->last_seen); - if(ret == -1){ - perror_plus("clock_gettime"); - return -1; - } - mc->current_server = mc->current_server->next; - block_time = 0; /* Call avahi to find new Mandos - servers, but don't block */ - } - - ret = avahi_simple_poll_iterate(s, (int)block_time); - } - if(ret != 0){ - if(ret > 0 or errno != EINTR){ - return (ret != 1) ? ret : 0; - } - } - } -} - -__attribute__((nonnull)) -void run_network_hooks(const char *mode, const char *interface, - const float delay){ - struct dirent **direntries = NULL; - if(hookdir_fd == -1){ - hookdir_fd = open(hookdir, O_RDONLY | O_DIRECTORY | O_PATH - | O_CLOEXEC); - if(hookdir_fd == -1){ - if(errno == ENOENT){ - if(debug){ - fprintf_plus(stderr, "Network hook directory \"%s\" not" - " found\n", hookdir); - } - } else { - perror_plus("open"); - } - return; - } - } - int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); - if(devnull == -1){ - perror_plus("open(\"/dev/null\", O_RDONLY)"); - return; - } - int numhooks = scandirat(hookdir_fd, ".", &direntries, - runnable_hook, alphasort); - if(numhooks == -1){ - perror_plus("scandir"); - close(devnull); - return; - } - struct dirent *direntry; - int ret; - for(int i = 0; i < numhooks; i++){ - direntry = direntries[i]; - if(debug){ - fprintf_plus(stderr, "Running network hook \"%s\"\n", - direntry->d_name); - } - pid_t hook_pid = fork(); - if(hook_pid == 0){ - /* Child */ - /* Raise privileges */ - errno = raise_privileges_permanently(); - if(errno != 0){ - perror_plus("Failed to raise privileges"); - _exit(EX_NOPERM); - } - /* Set group */ - errno = 0; - ret = setgid(0); - if(ret == -1){ - perror_plus("setgid"); - _exit(EX_NOPERM); - } - /* Reset supplementary groups */ - errno = 0; - ret = setgroups(0, NULL); - if(ret == -1){ - perror_plus("setgroups"); - _exit(EX_NOPERM); - } - ret = setenv("MANDOSNETHOOKDIR", hookdir, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - ret = setenv("DEVICE", interface, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - ret = setenv("VERBOSITY", debug ? "1" : "0", 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - ret = setenv("MODE", mode, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - char *delaystring; - ret = asprintf(&delaystring, "%f", (double)delay); - if(ret == -1){ - perror_plus("asprintf"); - _exit(EX_OSERR); - } - ret = setenv("DELAY", delaystring, 1); - if(ret == -1){ - free(delaystring); - perror_plus("setenv"); - _exit(EX_OSERR); - } - free(delaystring); - if(connect_to != NULL){ - ret = setenv("CONNECT", connect_to, 1); - if(ret == -1){ - perror_plus("setenv"); - _exit(EX_OSERR); - } - } - int hook_fd = (int)TEMP_FAILURE_RETRY(openat(hookdir_fd, - direntry->d_name, - O_RDONLY)); - if(hook_fd == -1){ - perror_plus("openat"); - _exit(EXIT_FAILURE); - } - if(close(hookdir_fd) == -1){ - perror_plus("close"); - _exit(EXIT_FAILURE); - } - ret = dup2(devnull, STDIN_FILENO); - if(ret == -1){ - perror_plus("dup2(devnull, STDIN_FILENO)"); - _exit(EX_OSERR); - } - ret = close(devnull); - if(ret == -1){ - perror_plus("close"); - _exit(EX_OSERR); - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); - if(ret == -1){ - perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); - _exit(EX_OSERR); - } - if(fexecve(hook_fd, (char *const []){ direntry->d_name, NULL }, - environ) == -1){ - perror_plus("fexecve"); - _exit(EXIT_FAILURE); - } - } else { - if(hook_pid == -1){ - perror_plus("fork"); - free(direntry); - continue; - } - int status; - if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){ - perror_plus("waitpid"); - free(direntry); - continue; - } - if(WIFEXITED(status)){ - if(WEXITSTATUS(status) != 0){ - fprintf_plus(stderr, "Warning: network hook \"%s\" exited" - " with status %d\n", direntry->d_name, - WEXITSTATUS(status)); - free(direntry); - continue; - } - } else if(WIFSIGNALED(status)){ - fprintf_plus(stderr, "Warning: network hook \"%s\" died by" - " signal %d\n", direntry->d_name, - WTERMSIG(status)); - free(direntry); - continue; - } else { - fprintf_plus(stderr, "Warning: network hook \"%s\"" - " crashed\n", direntry->d_name); - free(direntry); - continue; - } - } - if(debug){ - fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n", - direntry->d_name); - } - free(direntry); - } - free(direntries); - if(close(hookdir_fd) == -1){ - perror_plus("close"); - } else { - hookdir_fd = -1; - } - close(devnull); -} - -__attribute__((nonnull, warn_unused_result)) -int bring_up_interface(const char *const interface, - const float delay){ - int old_errno = errno; - int ret; - struct ifreq network; - unsigned int if_index = if_nametoindex(interface); - if(if_index == 0){ - fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); - errno = old_errno; - return ENXIO; - } - - if(quit_now){ - errno = old_errno; - return EINTR; - } - - if(not interface_is_up(interface)){ - int ret_errno = 0; - int ioctl_errno = 0; - if(not get_flags(interface, &network)){ - ret_errno = errno; - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - errno = old_errno; - return ret_errno; - } - network.ifr_flags |= IFF_UP; /* set flag */ - - int sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd == -1){ - ret_errno = errno; - perror_plus("socket"); - errno = old_errno; - return ret_errno; - } - - if(quit_now){ - ret = close(sd); - if(ret == -1){ - perror_plus("close"); - } - errno = old_errno; - return EINTR; - } - - if(debug){ - fprintf_plus(stderr, "Bringing up interface \"%s\"\n", - interface); - } - - /* Raise privileges */ - ret_errno = raise_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to raise privileges"); - } - -#ifdef __linux__ - int ret_linux; - bool restore_loglevel = false; - if(ret_errno == 0){ - /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO - messages about the network interface to mess up the prompt */ - ret_linux = klogctl(8, NULL, 5); - if(ret_linux == -1){ - perror_plus("klogctl"); - } else { - restore_loglevel = true; - } - } -#endif /* __linux__ */ - int ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); - ioctl_errno = errno; -#ifdef __linux__ - if(restore_loglevel){ - ret_linux = klogctl(7, NULL, 0); - if(ret_linux == -1){ - perror_plus("klogctl"); - } - } -#endif /* __linux__ */ - - /* If raise_privileges() succeeded above */ - if(ret_errno == 0){ - /* Lower privileges */ - ret_errno = lower_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to lower privileges"); - } - } - - /* Close the socket */ - ret = close(sd); - if(ret == -1){ - perror_plus("close"); - } - - if(ret_setflags == -1){ - errno = ioctl_errno; - perror_plus("ioctl SIOCSIFFLAGS +IFF_UP"); - errno = old_errno; - return ioctl_errno; - } - } else if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is already up; good\n", - interface); - } - - /* Sleep checking until interface is running. - Check every 0.25s, up to total time of delay */ - for(int i = 0; i < delay * 4; i++){ - if(interface_is_running(interface)){ - break; - } - struct timespec sleeptime = { .tv_nsec = 250000000 }; - ret = nanosleep(&sleeptime, NULL); - if(ret == -1 and errno != EINTR){ - perror_plus("nanosleep"); - } - } - - errno = old_errno; - return 0; -} - -__attribute__((nonnull, warn_unused_result)) -int take_down_interface(const char *const interface){ - int old_errno = errno; - struct ifreq network; - unsigned int if_index = if_nametoindex(interface); - if(if_index == 0){ - fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); - errno = old_errno; - return ENXIO; - } - if(interface_is_up(interface)){ - int ret_errno = 0; - int ioctl_errno = 0; - if(not get_flags(interface, &network) and debug){ - ret_errno = errno; - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", interface); - errno = old_errno; - return ret_errno; - } - network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ - - int sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd == -1){ - ret_errno = errno; - perror_plus("socket"); - errno = old_errno; - return ret_errno; - } - - if(debug){ - fprintf_plus(stderr, "Taking down interface \"%s\"\n", - interface); - } - - /* Raise privileges */ - ret_errno = raise_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to raise privileges"); - } - - int ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); - ioctl_errno = errno; - - /* If raise_privileges() succeeded above */ - if(ret_errno == 0){ - /* Lower privileges */ - ret_errno = lower_privileges(); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("Failed to lower privileges"); - } - } - - /* Close the socket */ - int ret = close(sd); - if(ret == -1){ - perror_plus("close"); - } - - if(ret_setflags == -1){ - errno = ioctl_errno; - perror_plus("ioctl SIOCSIFFLAGS -IFF_UP"); - errno = old_errno; - return ioctl_errno; - } - } else if(debug){ - fprintf_plus(stderr, "Interface \"%s\" is already down; odd\n", - interface); - } - - errno = old_errno; - return 0; -} - -int main(int argc, char *argv[]){ - mandos_context mc = { .server = NULL, .dh_bits = 0, -#if GNUTLS_VERSION_NUMBER >= 0x030606 - .priority = "SECURE128:!CTYPE-X.509" - ":+CTYPE-RAWPK:!RSA:!VERS-ALL:+VERS-TLS1.3" - ":%PROFILE_ULTRA", -#elif GNUTLS_VERSION_NUMBER < 0x030600 - .priority = "SECURE256:!CTYPE-X.509" - ":+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256", -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif - .current_server = NULL, .interfaces = NULL, - .interfaces_size = 0 }; - AvahiSServiceBrowser *sb = NULL; - error_t ret_errno; - int ret; - intmax_t tmpmax; - char *tmp; - int exitcode = EXIT_SUCCESS; - char *interfaces_to_take_down = NULL; - size_t interfaces_to_take_down_size = 0; - char run_tempdir[] = "/run/tmp/mandosXXXXXX"; - char old_tempdir[] = "/tmp/mandosXXXXXX"; - char *tempdir = NULL; - AvahiIfIndex if_index = AVAHI_IF_UNSPEC; - const char *seckey = PATHDIR "/" SECKEY; - const char *pubkey = PATHDIR "/" PUBKEY; -#if GNUTLS_VERSION_NUMBER >= 0x030606 - const char *tls_privkey = PATHDIR "/" TLS_PRIVKEY; - const char *tls_pubkey = PATHDIR "/" TLS_PUBKEY; -#endif - const char *dh_params_file = NULL; - char *interfaces_hooks = NULL; - - bool gnutls_initialized = false; - bool gpgme_initialized = false; - float delay = 2.5f; - double retry_interval = 10; /* 10s between trying a server and - retrying the same server again */ - - struct sigaction old_sigterm_action = { .sa_handler = SIG_DFL }; - struct sigaction sigterm_action = { .sa_handler = handle_sigterm }; - - uid = getuid(); - gid = getgid(); - - /* Lower any group privileges we might have, just to be safe */ - errno = 0; - ret = setgid(gid); - if(ret == -1){ - perror_plus("setgid"); - } - - /* Lower user privileges (temporarily) */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - - if(quit_now){ - goto end; - } - - { - struct argp_option options[] = { - { .name = "debug", .key = 128, - .doc = "Debug mode", .group = 3 }, - { .name = "connect", .key = 'c', - .arg = "ADDRESS:PORT", - .doc = "Connect directly to a specific Mandos server", - .group = 1 }, - { .name = "interface", .key = 'i', - .arg = "NAME", - .doc = "Network interface that will be used to search for" - " Mandos servers", - .group = 1 }, - { .name = "seckey", .key = 's', - .arg = "FILE", - .doc = "OpenPGP secret key file base name", - .group = 1 }, - { .name = "pubkey", .key = 'p', - .arg = "FILE", - .doc = "OpenPGP public key file base name", - .group = 1 }, - { .name = "tls-privkey", .key = 't', - .arg = "FILE", -#if GNUTLS_VERSION_NUMBER >= 0x030606 - .doc = "TLS private key file base name", -#else - .doc = "Dummy; ignored (requires GnuTLS 3.6.6)", -#endif - .group = 1 }, - { .name = "tls-pubkey", .key = 'T', - .arg = "FILE", -#if GNUTLS_VERSION_NUMBER >= 0x030606 - .doc = "TLS public key file base name", -#else - .doc = "Dummy; ignored (requires GnuTLS 3.6.6)", -#endif - .group = 1 }, - { .name = "dh-bits", .key = 129, - .arg = "BITS", - .doc = "Bit length of the prime number used in the" - " Diffie-Hellman key exchange", - .group = 2 }, - { .name = "dh-params", .key = 134, - .arg = "FILE", - .doc = "PEM-encoded PKCS#3 file with pre-generated parameters" - " for the Diffie-Hellman key exchange", - .group = 2 }, - { .name = "priority", .key = 130, - .arg = "STRING", - .doc = "GnuTLS priority string for the TLS handshake", - .group = 1 }, - { .name = "delay", .key = 131, - .arg = "SECONDS", - .doc = "Maximum delay to wait for interface startup", - .group = 2 }, - { .name = "retry", .key = 132, - .arg = "SECONDS", - .doc = "Retry interval used when denied by the Mandos server", - .group = 2 }, - { .name = "network-hook-dir", .key = 133, - .arg = "DIR", - .doc = "Directory where network hooks are located", - .group = 2 }, - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - { .name = "help", .key = '?', - .doc = "Give this help list", .group = -1 }, - { .name = "usage", .key = -3, - .doc = "Give a short usage message", .group = -1 }, - { .name = "version", .key = 'V', - .doc = "Print program version", .group = -1 }, - { .name = NULL } - }; - - error_t parse_opt(int key, char *arg, - struct argp_state *state){ - errno = 0; - switch(key){ - case 128: /* --debug */ - debug = true; - break; - case 'c': /* --connect */ - connect_to = arg; - break; - case 'i': /* --interface */ - ret_errno = argz_add_sep(&mc.interfaces, &mc.interfaces_size, - arg, (int)','); - if(ret_errno != 0){ - argp_error(state, "%s", strerror(ret_errno)); - } - break; - case 's': /* --seckey */ - seckey = arg; - break; - case 'p': /* --pubkey */ - pubkey = arg; - break; - case 't': /* --tls-privkey */ -#if GNUTLS_VERSION_NUMBER >= 0x030606 - tls_privkey = arg; -#endif - break; - case 'T': /* --tls-pubkey */ -#if GNUTLS_VERSION_NUMBER >= 0x030606 - tls_pubkey = arg; -#endif - break; - case 129: /* --dh-bits */ - errno = 0; - tmpmax = strtoimax(arg, &tmp, 10); - if(errno != 0 or tmp == arg or *tmp != '\0' - or tmpmax != (typeof(mc.dh_bits))tmpmax){ - argp_error(state, "Bad number of DH bits"); - } - mc.dh_bits = (typeof(mc.dh_bits))tmpmax; - break; - case 134: /* --dh-params */ - dh_params_file = arg; - break; - case 130: /* --priority */ - mc.priority = arg; - break; - case 131: /* --delay */ - errno = 0; - delay = strtof(arg, &tmp); - if(errno != 0 or tmp == arg or *tmp != '\0'){ - argp_error(state, "Bad delay"); - } - case 132: /* --retry */ - errno = 0; - retry_interval = strtod(arg, &tmp); - if(errno != 0 or tmp == arg or *tmp != '\0' - or (retry_interval * 1000) > INT_MAX - or retry_interval < 0){ - argp_error(state, "Bad retry interval"); - } - break; - case 133: /* --network-hook-dir */ - hookdir = arg; - break; - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - case '?': /* --help */ - argp_state_help(state, state->out_stream, - (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) - & ~(unsigned int)ARGP_HELP_EXIT_OK); - __builtin_unreachable(); - case -3: /* --usage */ - argp_state_help(state, state->out_stream, - ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); - __builtin_unreachable(); - case 'V': /* --version */ - fprintf_plus(state->out_stream, "%s\n", argp_program_version); - exit(argp_err_exit_status); - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "", - .doc = "Mandos client -- Get and decrypt" - " passwords from a Mandos server" }; - ret_errno = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL); - switch(ret_errno){ - case 0: - break; - case ENOMEM: +static void browse_callback( + AvahiSServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + void* userdata) { + + AvahiServer *s = userdata; + assert(b); /* Spurious warning */ + + /* Called whenever a new services becomes available on the LAN or + is removed from the LAN */ + + switch (event) { default: - errno = ret_errno; - perror_plus("argp_parse"); - exitcode = EX_OSERR; - goto end; - case EINVAL: - exitcode = EX_USAGE; - goto end; - } - } - - { - /* Work around Debian bug #633582: - */ - - /* Re-raise privileges */ - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - } else { - struct stat st; - - if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ - int seckey_fd = open(seckey, O_RDONLY); - if(seckey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) - and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(seckey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - close(seckey_fd); - } - } - - if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ - int pubkey_fd = open(pubkey, O_RDONLY); - if(pubkey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) - and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(pubkey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - close(pubkey_fd); - } - } - - if(dh_params_file != NULL - and strcmp(dh_params_file, PATHDIR "/dhparams.pem" ) == 0){ - int dhparams_fd = open(dh_params_file, O_RDONLY); - if(dhparams_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(dhparams_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) - and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(dhparams_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - close(dhparams_fd); - } - } - - /* Lower privileges */ - ret = lower_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges"); - } - } - } - - /* Remove invalid interface names (except "none") */ - { - char *interface = NULL; - while((interface = argz_next(mc.interfaces, mc.interfaces_size, - interface))){ - if(strcmp(interface, "none") != 0 - and if_nametoindex(interface) == 0){ - if(interface[0] != '\0'){ - fprintf_plus(stderr, "Not using nonexisting interface" - " \"%s\"\n", interface); - } - argz_delete(&mc.interfaces, &mc.interfaces_size, interface); - interface = NULL; - } - } - } - - /* Run network hooks */ - { - if(mc.interfaces != NULL){ - interfaces_hooks = malloc(mc.interfaces_size); - if(interfaces_hooks == NULL){ - perror_plus("malloc"); - goto end; - } - memcpy(interfaces_hooks, mc.interfaces, mc.interfaces_size); - argz_stringify(interfaces_hooks, mc.interfaces_size, (int)','); - } - run_network_hooks("start", interfaces_hooks != NULL ? - interfaces_hooks : "", delay); - } - - if(not debug){ - avahi_set_log_function(empty_log); - } - - /* Initialize Avahi early so avahi_simple_poll_quit() can be called - from the signal handler */ - /* Initialize the pseudo-RNG for Avahi */ - srand((unsigned int) time(NULL)); - simple_poll = avahi_simple_poll_new(); - if(simple_poll == NULL){ - fprintf_plus(stderr, - "Avahi: Failed to create simple poll object.\n"); - exitcode = EX_UNAVAILABLE; - goto end; - } - - sigemptyset(&sigterm_action.sa_mask); - ret = sigaddset(&sigterm_action.sa_mask, SIGINT); - if(ret == -1){ - perror_plus("sigaddset"); - exitcode = EX_OSERR; - goto end; - } - ret = sigaddset(&sigterm_action.sa_mask, SIGHUP); - if(ret == -1){ - perror_plus("sigaddset"); - exitcode = EX_OSERR; - goto end; - } - ret = sigaddset(&sigterm_action.sa_mask, SIGTERM); - if(ret == -1){ - perror_plus("sigaddset"); - exitcode = EX_OSERR; - goto end; - } - /* Need to check if the handler is SIG_IGN before handling: - | [[info:libc:Initial Signal Actions]] | - | [[info:libc:Basic Signal Handling]] | - */ - ret = sigaction(SIGINT, NULL, &old_sigterm_action); - if(ret == -1){ - perror_plus("sigaction"); - return EX_OSERR; - } - if(old_sigterm_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGINT, &sigterm_action, NULL); - if(ret == -1){ - perror_plus("sigaction"); - exitcode = EX_OSERR; - goto end; - } - } - ret = sigaction(SIGHUP, NULL, &old_sigterm_action); - if(ret == -1){ - perror_plus("sigaction"); - return EX_OSERR; - } - if(old_sigterm_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGHUP, &sigterm_action, NULL); - if(ret == -1){ - perror_plus("sigaction"); - exitcode = EX_OSERR; - goto end; - } - } - ret = sigaction(SIGTERM, NULL, &old_sigterm_action); - if(ret == -1){ - perror_plus("sigaction"); - return EX_OSERR; - } - if(old_sigterm_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGTERM, &sigterm_action, NULL); - if(ret == -1){ - perror_plus("sigaction"); - exitcode = EX_OSERR; - goto end; - } - } - - /* If no interfaces were specified, make a list */ - if(mc.interfaces == NULL){ - struct dirent **direntries = NULL; - /* Look for any good interfaces */ - ret = scandir(sys_class_net, &direntries, good_interface, - alphasort); - if(ret >= 1){ - /* Add all found interfaces to interfaces list */ - for(int i = 0; i < ret; ++i){ - ret_errno = argz_add(&mc.interfaces, &mc.interfaces_size, - direntries[i]->d_name); - if(ret_errno != 0){ - errno = ret_errno; - perror_plus("argz_add"); - free(direntries[i]); - continue; - } - if(debug){ - fprintf_plus(stderr, "Will use interface \"%s\"\n", - direntries[i]->d_name); - } - free(direntries[i]); - } - free(direntries); - } else { - if(ret == 0){ - free(direntries); - } - fprintf_plus(stderr, "Could not find a network interface\n"); - exitcode = EXIT_FAILURE; - goto end; - } - } - - /* Bring up interfaces which are down, and remove any "none"s */ - { - char *interface = NULL; - while((interface = argz_next(mc.interfaces, mc.interfaces_size, - interface))){ - /* If interface name is "none", stop bringing up interfaces. - Also remove all instances of "none" from the list */ - if(strcmp(interface, "none") == 0){ - argz_delete(&mc.interfaces, &mc.interfaces_size, - interface); - interface = NULL; - while((interface = argz_next(mc.interfaces, - mc.interfaces_size, interface))){ - if(strcmp(interface, "none") == 0){ - argz_delete(&mc.interfaces, &mc.interfaces_size, - interface); - interface = NULL; - } - } - break; - } - bool interface_was_up = interface_is_up(interface); - errno = bring_up_interface(interface, delay); - if(not interface_was_up){ - if(errno != 0){ - fprintf_plus(stderr, "Failed to bring up interface \"%s\":" - " %s\n", interface, strerror(errno)); - } else { - errno = argz_add(&interfaces_to_take_down, - &interfaces_to_take_down_size, - interface); - if(errno != 0){ - perror_plus("argz_add"); - } - } - } - } - if(debug and (interfaces_to_take_down == NULL)){ - fprintf_plus(stderr, "No interfaces were brought up\n"); - } - } - - /* If we only got one interface, explicitly use only that one */ - if(argz_count(mc.interfaces, mc.interfaces_size) == 1){ - if(debug){ - fprintf_plus(stderr, "Using only interface \"%s\"\n", - mc.interfaces); - } - if_index = (AvahiIfIndex)if_nametoindex(mc.interfaces); - } - - if(quit_now){ - goto end; - } - -#if GNUTLS_VERSION_NUMBER >= 0x030606 - ret = init_gnutls_global(tls_pubkey, tls_privkey, dh_params_file, &mc); -#elif GNUTLS_VERSION_NUMBER < 0x030600 - ret = init_gnutls_global(pubkey, seckey, dh_params_file, &mc); -#else -#error "Needs GnuTLS 3.6.6 or later, or before 3.6.0" -#endif - if(ret == -1){ - fprintf_plus(stderr, "init_gnutls_global failed\n"); - exitcode = EX_UNAVAILABLE; - goto end; - } else { - gnutls_initialized = true; - } - - if(quit_now){ - goto end; - } - - /* Try /run/tmp before /tmp */ - tempdir = mkdtemp(run_tempdir); - if(tempdir == NULL and errno == ENOENT){ - if(debug){ - fprintf_plus(stderr, "Tempdir %s did not work, trying %s\n", - run_tempdir, old_tempdir); - } - tempdir = mkdtemp(old_tempdir); - } - if(tempdir == NULL){ - perror_plus("mkdtemp"); - goto end; - } - - if(quit_now){ - goto end; - } - - if(not init_gpgme(pubkey, seckey, tempdir, &mc)){ - fprintf_plus(stderr, "init_gpgme failed\n"); - exitcode = EX_UNAVAILABLE; - goto end; - } else { - gpgme_initialized = true; - } - - if(quit_now){ - goto end; - } - - if(connect_to != NULL){ - /* Connect directly, do not use Zeroconf */ - /* (Mainly meant for debugging) */ - char *address = strrchr(connect_to, ':'); - - if(address == NULL){ - fprintf_plus(stderr, "No colon in address\n"); - exitcode = EX_USAGE; - goto end; - } - - if(quit_now){ - goto end; - } - - in_port_t port; - errno = 0; - tmpmax = strtoimax(address+1, &tmp, 10); - if(errno != 0 or tmp == address+1 or *tmp != '\0' - or tmpmax != (in_port_t)tmpmax){ - fprintf_plus(stderr, "Bad port number\n"); - exitcode = EX_USAGE; - goto end; - } - - if(quit_now){ - goto end; - } - - port = (in_port_t)tmpmax; - *address = '\0'; - /* Colon in address indicates IPv6 */ - int af; - if(strchr(connect_to, ':') != NULL){ - af = AF_INET6; - /* Accept [] around IPv6 address - see RFC 5952 */ - if(connect_to[0] == '[' and address[-1] == ']') - { - connect_to++; - address[-1] = '\0'; - } - } else { - af = AF_INET; - } - address = connect_to; - - if(quit_now){ - goto end; - } - - while(not quit_now){ - ret = start_mandos_communication(address, port, if_index, af, - &mc); - if(quit_now or ret == 0){ - break; - } - if(debug){ - fprintf_plus(stderr, "Retrying in %d seconds\n", - (int)retry_interval); - } - sleep((unsigned int)retry_interval); - } - - if(not quit_now){ - exitcode = EXIT_SUCCESS; - } - - goto end; - } - - if(quit_now){ - goto end; - } - - { + case AVAHI_BROWSER_FAILURE: + + fprintf(stderr, "(Browser) %s\n", + avahi_strerror(avahi_server_errno(server))); + avahi_simple_poll_quit(simple_poll); + return; + + case AVAHI_BROWSER_NEW: + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ + + if (!(avahi_s_service_resolver_new(s, interface, protocol, name, + type, domain, + AVAHI_PROTO_INET6, 0, + resolve_callback, s))) + fprintf(stderr, "Failed to resolve service '%s': %s\n", name, + avahi_strerror(avahi_server_errno(s))); + break; + + case AVAHI_BROWSER_REMOVE: + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + } +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) { AvahiServerConfig config; - /* Do not publish any local Zeroconf records */ + AvahiSServiceBrowser *sb = NULL; + int error; + int ret; + int returncode = EXIT_SUCCESS; + const char *interface = NULL; + AvahiIfIndex if_index = AVAHI_IF_UNSPEC; + char *connect_to = NULL; + + while (true){ + static struct option long_options[] = { + {"debug", no_argument, (int *)&debug, 1}, + {"connect", required_argument, 0, 'c'}, + {"interface", required_argument, 0, 'i'}, + {0, 0, 0, 0} }; + + int option_index = 0; + ret = getopt_long (argc, argv, "i:", long_options, + &option_index); + + if (ret == -1){ + break; + } + + switch(ret){ + case 0: + break; + case 'i': + interface = optarg; + break; + case 'c': + connect_to = optarg; + break; + default: + exit(EXIT_FAILURE); + } + } + + if(interface != NULL){ + if_index = (AvahiIfIndex) if_nametoindex(interface); + if(if_index == 0){ + fprintf(stderr, "No such interface: \"%s\"\n", interface); + exit(EXIT_FAILURE); + } + } + + if(connect_to != NULL){ + /* Connect directly, do not use Zeroconf */ + /* (Mainly meant for debugging) */ + char *address = strrchr(connect_to, ':'); + if(address == NULL){ + fprintf(stderr, "No colon in address\n"); + exit(EXIT_FAILURE); + } + errno = 0; + uint16_t port = (uint16_t) strtol(address+1, NULL, 10); + if(errno){ + perror("Bad port number"); + exit(EXIT_FAILURE); + } + *address = '\0'; + address = connect_to; + ret = start_mandos_communication(address, port, if_index); + if(ret < 0){ + exit(EXIT_FAILURE); + } else { + exit(EXIT_SUCCESS); + } + } + + if (not debug){ + avahi_set_log_function(empty_log); + } + + /* Initialize the psuedo-RNG */ + srand((unsigned int) time(NULL)); + + /* Allocate main loop object */ + if (!(simple_poll = avahi_simple_poll_new())) { + fprintf(stderr, "Failed to create simple poll object.\n"); + + goto exit; + } + + /* Do not publish any local records */ avahi_server_config_init(&config); config.publish_hinfo = 0; config.publish_addresses = 0; config.publish_workstation = 0; config.publish_domain = 0; - + /* Allocate a new server */ - mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll), - &config, NULL, NULL, &ret); - - /* Free the Avahi configuration data */ + server = avahi_server_new(avahi_simple_poll_get(simple_poll), + &config, NULL, NULL, &error); + + /* Free the configuration data */ avahi_server_config_free(&config); - } - - /* Check if creating the Avahi server object succeeded */ - if(mc.server == NULL){ - fprintf_plus(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(ret)); - exitcode = EX_UNAVAILABLE; - goto end; - } - - if(quit_now){ - goto end; - } - - /* Create the Avahi service browser */ - sb = avahi_s_service_browser_new(mc.server, if_index, - AVAHI_PROTO_UNSPEC, "_mandos._tcp", - NULL, 0, browse_callback, - (void *)&mc); - if(sb == NULL){ - fprintf_plus(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); - exitcode = EX_UNAVAILABLE; - goto end; - } - - if(quit_now){ - goto end; - } - - /* Run the main loop */ - - if(debug){ - fprintf_plus(stderr, "Starting Avahi loop search\n"); - } - - ret = avahi_loop_with_timeout(simple_poll, - (int)(retry_interval * 1000), &mc); - if(debug){ - fprintf_plus(stderr, "avahi_loop_with_timeout exited %s\n", - (ret == 0) ? "successfully" : "with error"); - } - - end: - - if(debug){ - if(signal_received){ - fprintf_plus(stderr, "%s exiting due to signal %d: %s\n", - argv[0], signal_received, - strsignal(signal_received)); - } else { - fprintf_plus(stderr, "%s exiting\n", argv[0]); - } - } - - /* Cleanup things */ - free(mc.interfaces); - - if(sb != NULL) - avahi_s_service_browser_free(sb); - - if(mc.server != NULL) - avahi_server_free(mc.server); - - if(simple_poll != NULL) - avahi_simple_poll_free(simple_poll); - - if(gnutls_initialized){ - gnutls_certificate_free_credentials(mc.cred); - gnutls_dh_params_deinit(mc.dh_params); - } - - if(gpgme_initialized){ - gpgme_release(mc.ctx); - } - - /* Cleans up the circular linked list of Mandos servers the client - has seen */ - if(mc.current_server != NULL){ - mc.current_server->prev->next = NULL; - while(mc.current_server != NULL){ - server *next = mc.current_server->next; -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - free((char *)(mc.current_server->ip)); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - free(mc.current_server); - mc.current_server = next; - } - } - - /* Re-raise privileges */ - { - ret = raise_privileges(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to raise privileges"); - } else { - - /* Run network hooks */ - run_network_hooks("stop", interfaces_hooks != NULL ? - interfaces_hooks : "", delay); - - /* Take down the network interfaces which were brought up */ - { - char *interface = NULL; - while((interface = argz_next(interfaces_to_take_down, - interfaces_to_take_down_size, - interface))){ - ret = take_down_interface(interface); - if(ret != 0){ - errno = ret; - perror_plus("Failed to take down interface"); - } - } - if(debug and (interfaces_to_take_down == NULL)){ - fprintf_plus(stderr, "No interfaces needed to be taken" - " down\n"); - } - } - } - - ret = lower_privileges_permanently(); - if(ret != 0){ - errno = ret; - perror_plus("Failed to lower privileges permanently"); - } - } - - free(interfaces_to_take_down); - free(interfaces_hooks); - - void clean_dir_at(int base, const char * const dirname, - uintmax_t level){ - struct dirent **direntries = NULL; - int dret; - int dir_fd = (int)TEMP_FAILURE_RETRY(openat(base, dirname, - O_RDONLY - | O_NOFOLLOW - | O_DIRECTORY - | O_PATH)); - if(dir_fd == -1){ - perror_plus("open"); - return; - } - int numentries = scandirat(dir_fd, ".", &direntries, - notdotentries, alphasort); - if(numentries >= 0){ - for(int i = 0; i < numentries; i++){ - if(debug){ - fprintf_plus(stderr, "Unlinking \"%s/%s\"\n", - dirname, direntries[i]->d_name); - } - dret = unlinkat(dir_fd, direntries[i]->d_name, 0); - if(dret == -1){ - if(errno == EISDIR){ - dret = unlinkat(dir_fd, direntries[i]->d_name, - AT_REMOVEDIR); - } - if((dret == -1) and (errno == ENOTEMPTY) - and (strcmp(direntries[i]->d_name, "private-keys-v1.d") - == 0) and (level == 0)){ - /* Recurse only in this special case */ - clean_dir_at(dir_fd, direntries[i]->d_name, level+1); - dret = 0; - } - if((dret == -1) and (errno != ENOENT)){ - fprintf_plus(stderr, "unlink(\"%s/%s\"): %s\n", dirname, - direntries[i]->d_name, strerror(errno)); - } - } - free(direntries[i]); - } - - /* need to clean even if 0 because man page doesn't specify */ - free(direntries); - dret = unlinkat(base, dirname, AT_REMOVEDIR); - if(dret == -1 and errno != ENOENT){ - perror_plus("rmdir"); - } - } else { - perror_plus("scandirat"); - } - close(dir_fd); - } - - /* Removes the GPGME temp directory and all files inside */ - if(tempdir != NULL){ - clean_dir_at(-1, tempdir, 0); - } - - if(quit_now){ - sigemptyset(&old_sigterm_action.sa_mask); - old_sigterm_action.sa_handler = SIG_DFL; - ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, - &old_sigterm_action, - NULL)); - if(ret == -1){ - perror_plus("sigaction"); - } - do { - ret = raise(signal_received); - } while(ret != 0 and errno == EINTR); - if(ret != 0){ - perror_plus("raise"); - abort(); - } - TEMP_FAILURE_RETRY(pause()); - } - - return exitcode; + + /* Check if creating the server object succeeded */ + if (!server) { + fprintf(stderr, "Failed to create server: %s\n", + avahi_strerror(error)); + returncode = EXIT_FAILURE; + goto exit; + } + + /* Create the service browser */ + sb = avahi_s_service_browser_new(server, if_index, + AVAHI_PROTO_INET6, + "_mandos._tcp", NULL, 0, + browse_callback, server); + if (!sb) { + fprintf(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(server))); + returncode = EXIT_FAILURE; + goto exit; + } + + /* Run the main loop */ + + if (debug){ + fprintf(stderr, "Starting avahi loop search\n"); + } + + avahi_simple_poll_loop(simple_poll); + + exit: + + if (debug){ + fprintf(stderr, "%s exiting\n", argv[0]); + } + + /* Cleanup things */ + if (sb) + avahi_s_service_browser_free(sb); + + if (server) + avahi_server_free(server); + + if (simple_poll) + avahi_simple_poll_free(simple_poll); + + return returncode; } === renamed file 'plugins.d/password-prompt.c' => 'plugins.d/passprompt.c' --- plugins.d/password-prompt.c 2019-07-27 10:11:45 +0000 +++ plugins.d/passprompt.c 2008-07-29 03:35:39 +0000 @@ -1,581 +1,175 @@ -/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* -*- coding: utf-8 -*- */ /* - * Password-prompt - Read a password from the terminal and print it - * - * Copyright © 2008-2019 Teddy Hogeborn - * Copyright © 2008-2019 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos is distributed in the hope that it will be useful, but + * Passprompt - Read a password from the terminal and print it + * + * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson + * + * 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 Mandos. If not, see . + * along with this program. If not, see + * . * - * Contact the authors at . + * Contact the authors at and + * . */ -#define _GNU_SOURCE /* getline(), asprintf() */ +#define _GNU_SOURCE /* getline() */ -#include /* struct termios, tcsetattr(), +#include /* struct termios, tcsetattr(), TCSAFLUSH, tcgetattr(), ECHO */ -#include /* access(), struct termios, - tcsetattr(), STDIN_FILENO, - TCSAFLUSH, tcgetattr(), ECHO, - readlink() */ +#include /* struct termios, tcsetattr(), + STDIN_FILENO, TCSAFLUSH, + tcgetattr(), ECHO */ #include /* sig_atomic_t, raise(), struct sigaction, sigemptyset(), sigaction(), sigaddset(), SIGINT, - SIGQUIT, SIGHUP, SIGTERM, - raise() */ -#include /* NULL, size_t, ssize_t */ -#include /* ssize_t, struct dirent, pid_t, - ssize_t, open() */ + SIGQUIT, SIGHUP, SIGTERM */ +#include /* NULL, size_t */ +#include /* ssize_t */ #include /* EXIT_SUCCESS, EXIT_FAILURE, - getenv(), free() */ -#include /* scandir(), alphasort() */ + getopt_long */ #include /* fprintf(), stderr, getline(), - stdin, feof(), fputc(), vfprintf(), - vasprintf() */ -#include /* errno, EBADF, ENOTTY, EINVAL, - EFAULT, EFBIG, EIO, ENOSPC, EINTR - */ -#include /* error() */ + stdin, feof(), perror(), fputc(), + stdout, getopt_long */ +#include /* errno, EINVAL */ #include /* or, not */ #include /* bool, false, true */ -#include /* strtoumax() */ -#include /* struct stat, lstat(), open() */ -#include /* strlen, rindex, memcmp, strerror() - */ -#include /* struct argp_option, struct - argp_state, struct argp, - argp_parse(), error_t, - ARGP_KEY_ARG, ARGP_KEY_END, - ARGP_ERR_UNKNOWN */ -#include /* EX_SOFTWARE, EX_OSERR, - EX_UNAVAILABLE, EX_IOERR, EX_OK */ -#include /* open() */ -#include /* va_list, va_start(), ... */ +#include /* strlen, rindex, strncmp, strcmp */ +#include /* getopt_long */ -volatile sig_atomic_t quit_now = 0; -int signal_received; +volatile bool quit_now = false; bool debug = false; -const char *argp_program_version = "password-prompt " VERSION; -const char *argp_program_bug_address = ""; - -/* Needed for conflict resolution */ -const char plymouth_name[] = "plymouthd"; - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": %s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - -static void termination_handler(int signum){ - if(quit_now){ - return; - } - quit_now = 1; - signal_received = signum; -} - -bool conflict_detection(void){ - - /* plymouth conflicts with password-prompt since both want to read - from the terminal. Password-prompt will exit if it detects - plymouth since plymouth performs the same functionality. - */ - if(access("/run/plymouth/pid", R_OK) == 0){ - return true; - } - - __attribute__((nonnull)) - int is_plymouth(const struct dirent *proc_entry){ - int ret; - int cl_fd; - { - uintmax_t proc_id; - char *tmp; - errno = 0; - proc_id = strtoumax(proc_entry->d_name, &tmp, 10); - - if(errno != 0 or *tmp != '\0' - or proc_id != (uintmax_t)((pid_t)proc_id)){ - return 0; - } - } - - char *cmdline_filename; - ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", - proc_entry->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - return 0; - } - - /* Open /proc//cmdline */ - cl_fd = open(cmdline_filename, O_RDONLY); - free(cmdline_filename); - if(cl_fd == -1){ - if(errno != ENOENT){ - error_plus(0, errno, "open"); - } - return 0; - } - - char *cmdline = NULL; - { - size_t cmdline_len = 0; - size_t cmdline_allocated = 0; - char *tmp; - const size_t blocksize = 1024; - ssize_t sret; - do { - /* Allocate more space? */ - if(cmdline_len + blocksize + 1 > cmdline_allocated){ - tmp = realloc(cmdline, cmdline_allocated + blocksize + 1); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(cmdline); - close(cl_fd); - return 0; - } - cmdline = tmp; - cmdline_allocated += blocksize; - } - - /* Read data */ - sret = read(cl_fd, cmdline + cmdline_len, - cmdline_allocated - cmdline_len); - if(sret == -1){ - error_plus(0, errno, "read"); - free(cmdline); - close(cl_fd); - return 0; - } - cmdline_len += (size_t)sret; - } while(sret != 0); - ret = close(cl_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - free(cmdline); - return 0; - } - cmdline[cmdline_len] = '\0'; /* Make sure it is terminated */ - } - /* we now have cmdline */ - - /* get basename */ - char *cmdline_base = strrchr(cmdline, '/'); - if(cmdline_base != NULL){ - cmdline_base += 1; /* skip the slash */ - } else { - cmdline_base = cmdline; - } - - if(strcmp(cmdline_base, plymouth_name) != 0){ - if(debug){ - fprintf(stderr, "\"%s\" is not \"%s\"\n", cmdline_base, - plymouth_name); - } - free(cmdline); - return 0; - } - if(debug){ - fprintf(stderr, "\"%s\" equals \"%s\"\n", cmdline_base, - plymouth_name); - } - free(cmdline); - return 1; - } - - struct dirent **direntries = NULL; - int ret; - ret = scandir("/proc", &direntries, is_plymouth, alphasort); - if(ret == -1){ - error_plus(1, errno, "scandir"); - } - { - int i = ret; - while(i--){ - free(direntries[i]); - } - } - free(direntries); - return ret > 0; -} - + +void termination_handler(__attribute__((unused))int signum){ + quit_now = true; +} int main(int argc, char **argv){ - ssize_t sret; - int ret; + ssize_t ret; size_t n; struct termios t_new, t_old; char *buffer = NULL; char *prefix = NULL; - char *prompt = NULL; int status = EXIT_SUCCESS; struct sigaction old_action, new_action = { .sa_handler = termination_handler, .sa_flags = 0 }; - { - struct argp_option options[] = { - { .name = "prefix", .key = 'p', - .arg = "PREFIX", .flags = 0, - .doc = "Prefix shown before the prompt", .group = 2 }, - { .name = "prompt", .key = 129, - .arg = "PROMPT", .flags = 0, - .doc = "The prompt to show", .group = 2 }, - { .name = "debug", .key = 128, - .doc = "Debug mode", .group = 3 }, - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - { .name = "help", .key = '?', - .doc = "Give this help list", .group = -1 }, - { .name = "usage", .key = -3, - .doc = "Give a short usage message", .group = -1 }, - { .name = "version", .key = 'V', - .doc = "Print program version", .group = -1 }, - { .name = NULL } - }; - - __attribute__((nonnull(3))) - error_t parse_opt (int key, char *arg, struct argp_state *state){ - errno = 0; - switch (key){ - case 'p': /* --prefix */ - prefix = arg; - break; - case 128: /* --debug */ - debug = true; - break; - case 129: /* --prompt */ - prompt = arg; - break; - /* - * These reproduce what we would get without ARGP_NO_HELP - */ - case '?': /* --help */ - argp_state_help(state, state->out_stream, - (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) - & ~(unsigned int)ARGP_HELP_EXIT_OK); - __builtin_unreachable(); - case -3: /* --usage */ - argp_state_help(state, state->out_stream, - ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); - __builtin_unreachable(); - case 'V': /* --version */ - fprintf(state->out_stream, "%s\n", argp_program_version); - exit(argp_err_exit_status); - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; + + while (true){ + static struct option long_options[] = { + {"debug", no_argument, (int *)&debug, 1}, + {"prefix", required_argument, 0, 'p'}, + {0, 0, 0, 0} }; + + int option_index = 0; + ret = getopt_long (argc, argv, "p:", long_options, &option_index); + + if (ret == -1){ + break; } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "", - .doc = "Mandos password-prompt -- Read and" - " output a password" }; - ret = argp_parse(&argp, argc, argv, - ARGP_IN_ORDER | ARGP_NO_HELP, NULL, NULL); + switch(ret){ case 0: break; - case ENOMEM: + case 'p': + prefix = optarg; + break; default: - errno = ret; - error_plus(0, errno, "argp_parse"); - return EX_OSERR; - case EINVAL: - return EX_USAGE; + fprintf(stderr, "bad arguments\n"); + exit(EXIT_FAILURE); } } - - if(debug){ + + if (debug){ fprintf(stderr, "Starting %s\n", argv[0]); } - - if(conflict_detection()){ - if(debug){ - fprintf(stderr, "Stopping %s because of conflict\n", argv[0]); - } - return EXIT_FAILURE; - } - - if(debug){ + if (debug){ fprintf(stderr, "Storing current terminal attributes\n"); } - if(tcgetattr(STDIN_FILENO, &t_old) != 0){ - int e = errno; - error_plus(0, errno, "tcgetattr"); - switch(e){ - case EBADF: - case ENOTTY: - return EX_UNAVAILABLE; - default: - return EX_OSERR; - } + if (tcgetattr(STDIN_FILENO, &t_old) != 0){ + return EXIT_FAILURE; } sigemptyset(&new_action.sa_mask); - ret = sigaddset(&new_action.sa_mask, SIGINT); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - return EX_OSERR; - } - ret = sigaddset(&new_action.sa_mask, SIGHUP); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - return EX_OSERR; - } - ret = sigaddset(&new_action.sa_mask, SIGTERM); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - return EX_OSERR; - } - /* Need to check if the handler is SIG_IGN before handling: - | [[info:libc:Initial Signal Actions]] | - | [[info:libc:Basic Signal Handling]] | - */ - ret = sigaction(SIGINT, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGINT, &new_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; - } - } - ret = sigaction(SIGHUP, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGHUP, &new_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; - } - } - ret = sigaction(SIGTERM, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGTERM, &new_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - return EX_OSERR; - } - } - - - if(debug){ + sigaddset(&new_action.sa_mask, SIGINT); + sigaddset(&new_action.sa_mask, SIGQUIT); + sigaddset(&new_action.sa_mask, SIGHUP); + sigaddset(&new_action.sa_mask, SIGTERM); + sigaction(SIGINT, NULL, &old_action); + if (old_action.sa_handler != SIG_IGN) + sigaction(SIGINT, &new_action, NULL); + sigaction(SIGQUIT, NULL, &old_action); + if (old_action.sa_handler != SIG_IGN) + sigaction(SIGQUIT, &new_action, NULL); + sigaction(SIGHUP, NULL, &old_action); + if (old_action.sa_handler != SIG_IGN) + sigaction(SIGHUP, &new_action, NULL); + sigaction(SIGTERM, NULL, &old_action); + if (old_action.sa_handler != SIG_IGN) + sigaction(SIGTERM, &new_action, NULL); + + + if (debug){ fprintf(stderr, "Removing echo flag from terminal attributes\n"); } t_new = t_old; - t_new.c_lflag &= ~(tcflag_t)ECHO; - if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ - int e = errno; - error_plus(0, errno, "tcsetattr-echo"); - switch(e){ - case EBADF: - case ENOTTY: - return EX_UNAVAILABLE; - case EINVAL: - default: - return EX_OSERR; - } + t_new.c_lflag &= ~ECHO; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ + perror("tcsetattr-echo"); + return EXIT_FAILURE; } - - if(debug){ + + if (debug){ fprintf(stderr, "Waiting for input from stdin \n"); } while(true){ - if(quit_now){ - if(debug){ - fprintf(stderr, "Interrupted by signal, exiting.\n"); - } + if (quit_now){ status = EXIT_FAILURE; break; } if(prefix){ - fprintf(stderr, "%s ", prefix); - } - if(prompt != NULL){ - fprintf(stderr, "%s: ", prompt); + fprintf(stderr, "%s Password: ", prefix); } else { - const char *cryptsource = getenv("CRYPTTAB_SOURCE"); - const char *crypttarget = getenv("CRYPTTAB_NAME"); - /* Before cryptsetup 1.1.0~rc2 */ - if(cryptsource == NULL){ - cryptsource = getenv("cryptsource"); - } - if(crypttarget == NULL){ - crypttarget = getenv("crypttarget"); - } - const char *const prompt1 = "Unlocking the disk"; - const char *const prompt2 = "Enter passphrase"; - if(cryptsource == NULL){ - if(crypttarget == NULL){ - fprintf(stderr, "%s to unlock the disk: ", prompt2); - } else { - fprintf(stderr, "%s (%s)\n%s: ", prompt1, crypttarget, - prompt2); - } - } else { - if(crypttarget == NULL){ - fprintf(stderr, "%s %s\n%s: ", prompt1, cryptsource, - prompt2); - } else { - fprintf(stderr, "%s %s (%s)\n%s: ", prompt1, cryptsource, - crypttarget, prompt2); - } - } - } - sret = getline(&buffer, &n, stdin); - if(sret > 0){ + fprintf(stderr, "Password: "); + } + ret = getline(&buffer, &n, stdin); + if (ret > 0){ + fprintf(stdout, "%s", buffer); status = EXIT_SUCCESS; - /* Make n = data size instead of allocated buffer size */ - n = (size_t)sret; - /* Strip final newline */ - if(n > 0 and buffer[n-1] == '\n'){ - buffer[n-1] = '\0'; /* not strictly necessary */ - n--; - } - size_t written = 0; - while(written < n){ - sret = write(STDOUT_FILENO, buffer + written, n - written); - if(sret < 0){ - int e = errno; - error_plus(0, errno, "write"); - switch(e){ - case EBADF: - case EFAULT: - case EINVAL: - case EFBIG: - case EIO: - case ENOSPC: - default: - status = EX_IOERR; - break; - case EINTR: - status = EXIT_FAILURE; - break; - } - break; - } - written += (size_t)sret; - } - sret = close(STDOUT_FILENO); - if(sret == -1){ - int e = errno; - error_plus(0, errno, "close"); - switch(e){ - case EBADF: - status = EX_OSFILE; - break; - case EIO: - default: - status = EX_IOERR; - break; - } - } break; } - if(sret < 0){ - int e = errno; - if(errno != EINTR){ - if(not feof(stdin)){ - error_plus(0, errno, "getline"); - switch(e){ - case EBADF: - status = EX_UNAVAILABLE; - break; - case EIO: - case EINVAL: - default: - status = EX_IOERR; - break; - } - break; - } else { - clearerr(stdin); - } + // ret == 0 makes no other sence than to retry to read from stdin + if (ret < 0){ + if (errno != EINTR and not feof(stdin)){ + perror("getline"); + status = EXIT_FAILURE; + break; } } - /* if(sret == 0), then the only sensible thing to do is to retry - to read from stdin */ fputc('\n', stderr); - if(debug and not quit_now){ - /* If quit_now is nonzero, we were interrupted by a signal, and - will print that later, so no need to show this too. */ - fprintf(stderr, "getline() returned 0, retrying.\n"); - } } - - free(buffer); - - if(debug){ + + if (debug){ fprintf(stderr, "Restoring terminal attributes\n"); } - if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ - error_plus(0, errno, "tcsetattr+echo"); - } - - if(quit_now){ - sigemptyset(&old_action.sa_mask); - old_action.sa_handler = SIG_DFL; - ret = sigaction(signal_received, &old_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - } - raise(signal_received); - } - - if(debug){ - fprintf(stderr, "%s is exiting with status %d\n", argv[0], - status); - } - if(status == EXIT_SUCCESS or status == EX_OK){ - fputc('\n', stderr); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ + perror("tcsetattr+echo"); + } + + if (debug){ + fprintf(stderr, "%s is exiting\n", argv[0]); } return status; === removed file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/password-prompt.xml 1970-01-01 00:00:00 +0000 @@ -1,334 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Prompt for a password and output it. - - - - - &COMMANDNAME; - - - PREFIX - - - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - All &COMMANDNAME; does is prompt for a - password and output any given password to standard output. - - - This program is not very useful on its own. This program is - really meant to run as a plugin in the Mandos client-side system, where it is used as a - fallback and alternative to retrieving passwords from a - Mandos server. - - - This program is little more than a getpass3 - wrapper, although actual use of that function is not guaranteed - or implied. - - - This program tries to detect if a Plymouth daemon - (plymouthd8) - is running, by looking for a - /run/plymouth/pid file or a process named - plymouthd. If it is detected, - this process will immediately exit without doing anything. - - - - - OPTIONS - - This program is commonly not invoked from the command line; it - is normally started by the Mandos - plugin runner, see plugin-runner8mandos - . Any command line options this program accepts - are therefore normally provided by the plugin runner, and not - directly. - - - - - - - - - Prefix string shown before the password prompt. - - - - - - - - - The password prompt. Using this option will make this - program ignore the CRYPTTAB_SOURCE and - CRYPTTAB_NAME environment variables. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - - - - - EXIT STATUS - - If exit status is 0, the output from the program is the password - as it was read. Otherwise, if exit status is other than 0, the - program has encountered an error, and any output so far could be - corrupt and/or truncated, and should therefore be ignored. - - - - - ENVIRONMENT - - - CRYPTTAB_SOURCE - CRYPTTAB_NAME - - - If set, and if the option is not - used, these environment variables will be assumed to - contain the source device name and the target device - mapper name, respectively, and will be shown as part of - the prompt. - - - These variables will normally be inherited from - plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. - - - This behavior is meant to exactly mirror the behavior of - askpass, the default password prompter - from initramfs-tools. - - - - - - - - BUGS - - - - - EXAMPLE - - Note that normally, command line options will not be given - directly, but via options for the Mandos plugin-runner - 8mandos. - - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Show a prefix before the prompt; in this case, a host name. - It might be useful to be reminded of which host needs a - password, in case of KVM switches, etc. - - - - -&COMMANDNAME; --prefix=host.example.org: - - - - - - Run in debug mode. - - - - &COMMANDNAME; --debug - - - - - - SECURITY - - On its own, this program is very simple, and does not exactly - present any security risks. The one thing that could be - considered worthy of note is this: This program is meant to be - run by plugin-runner8mandos - , and will, when run standalone, outside, in a - normal environment, immediately output on its standard output - any presumably secret password it just received. Therefore, - when running this program standalone (which should never - normally be done), take care not to type in any real secret - password by force of habit, since it would then immediately be - shown as output. - - - To further alleviate any risk of being locked out of a system, - the plugin-runner - 8mandos has a fallback - mode which does the same thing as this program, only with less - features. - - - - - SEE ALSO - - intro - 8mandos, - mandos-client - 8mandos, - plugin-runner - 8mandos, - plymouthd - 8 - - -
- - - - - === removed file 'plugins.d/plymouth.c' --- plugins.d/plymouth.c 2019-07-27 10:11:45 +0000 +++ plugins.d/plymouth.c 1970-01-01 00:00:00 +0000 @@ -1,613 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Plymouth - Read a password from Plymouth and output it - * - * Copyright © 2010-2019 Teddy Hogeborn - * Copyright © 2010-2019 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos 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 Mandos. If not, see . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */ -#include /* sig_atomic_t, struct sigaction, - sigemptyset(), sigaddset(), SIGINT, - SIGHUP, SIGTERM, sigaction(), - kill(), SIG_IGN */ -#include /* bool, false, true */ -#include /* open(), O_RDONLY */ -#include /* and, or, not*/ -#include /* size_t, ssize_t, pid_t, struct - dirent, waitpid() */ -#include /* waitpid() */ -#include /* NULL */ -#include /* strchr(), memcmp() */ -#include /* asprintf(), perror(), fopen(), - fscanf(), vasprintf(), fprintf(), - vfprintf() */ -#include /* close(), readlink(), read(), - fork(), setsid(), chdir(), dup2(), - STDERR_FILENO, execv(), access() */ -#include /* free(), EXIT_FAILURE, realloc(), - EXIT_SUCCESS, malloc(), _exit(), - getenv() */ -#include /* scandir(), alphasort() */ -#include /* intmax_t, strtoumax(), SCNuMAX */ -#include /* struct stat, lstat() */ -#include /* EX_OSERR, EX_UNAVAILABLE */ -#include /* error() */ -#include /* TEMP_FAILURE_RETRY */ -#include /* argz_count(), argz_extract() */ -#include /* va_list, va_start(), ... */ -#include - -sig_atomic_t interrupted_by_signal = 0; -const char *argp_program_version = "plymouth " VERSION; -const char *argp_program_bug_address = ""; - -/* Used by Ubuntu 11.04 (Natty Narwahl) */ -const char plymouth_old_old_pid[] = "/dev/.initramfs/plymouth.pid"; -/* Used by Ubuntu 11.10 (Oneiric Ocelot) */ -const char plymouth_old_pid[] = "/run/initramfs/plymouth.pid"; -/* Used by Debian 9 (stretch) */ -const char plymouth_pid[] = "/run/plymouth/pid"; - -const char plymouth_path[] = "/bin/plymouth"; -const char plymouthd_path[] = "/sbin/plymouthd"; -const char *plymouthd_default_argv[] = {"/sbin/plymouthd", - "--mode=boot", - "--attach-to-session", - NULL }; -bool debug = false; - -static void termination_handler(__attribute__((unused))int signum){ - if(interrupted_by_signal){ - return; - } - interrupted_by_signal = 1; -} - -__attribute__((format (gnu_printf, 2, 3), nonnull)) -int fprintf_plus(FILE *stream, const char *format, ...){ - va_list ap; - va_start (ap, format); - fprintf(stream, "Mandos plugin %s: ", program_invocation_short_name); - return vfprintf(stream, format, ap); -} - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - -/* Create prompt string */ -char *makeprompt(void){ - int ret = 0; - char *prompt; - const char *const cryptsource = getenv("cryptsource"); - const char *const crypttarget = getenv("crypttarget"); - const char prompt_start[] = "Unlocking the disk"; - const char prompt_end[] = "Enter passphrase"; - - if(cryptsource == NULL){ - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s\n%s", prompt_start, prompt_end); - } else { - ret = asprintf(&prompt, "%s (%s)\n%s", prompt_start, - crypttarget, prompt_end); - } - } else { - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s %s\n%s", prompt_start, cryptsource, - prompt_end); - } else { - ret = asprintf(&prompt, "%s %s (%s)\n%s", prompt_start, - cryptsource, crypttarget, prompt_end); - } - } - if(ret == -1){ - return NULL; - } - return prompt; -} - -void kill_and_wait(pid_t pid){ - TEMP_FAILURE_RETRY(kill(pid, SIGTERM)); - TEMP_FAILURE_RETRY(waitpid(pid, NULL, 0)); -} - -bool become_a_daemon(void){ - int ret = setuid(geteuid()); - if(ret == -1){ - error_plus(0, errno, "setuid"); - } - - setsid(); - ret = chdir("/"); - if(ret == -1){ - error_plus(0, errno, "chdir"); - return false; - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ - if(ret == -1){ - error_plus(0, errno, "dup2"); - return false; - } - return true; -} - -__attribute__((nonnull (2, 3))) -bool exec_and_wait(pid_t *pid_return, const char *path, - const char * const * const argv, bool interruptable, - bool daemonize){ - int status; - int ret; - pid_t pid; - if(debug){ - for(const char * const *arg = argv; *arg != NULL; arg++){ - fprintf_plus(stderr, "exec_and_wait arg: %s\n", *arg); - } - fprintf_plus(stderr, "exec_and_wait end of args\n"); - } - - pid = fork(); - if(pid == -1){ - error_plus(0, errno, "fork"); - return false; - } - if(pid == 0){ - /* Child */ - if(daemonize){ - if(not become_a_daemon()){ - _exit(EX_OSERR); - } - } - - char **new_argv = malloc(sizeof(const char *)); - if(new_argv == NULL){ - error_plus(0, errno, "malloc"); - _exit(EX_OSERR); - } - char **tmp; - int i = 0; - for (; argv[i] != NULL; i++){ - tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2)); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(new_argv); - _exit(EX_OSERR); - } - new_argv = tmp; - new_argv[i] = strdup(argv[i]); - } - new_argv[i] = NULL; - - execv(path, (char *const *)new_argv); - error_plus(0, errno, "execv"); - _exit(EXIT_FAILURE); - } - if(pid_return != NULL){ - *pid_return = pid; - } - do { - ret = waitpid(pid, &status, 0); - } while(ret == -1 and errno == EINTR - and ((not interrupted_by_signal) - or (not interruptable))); - if(interrupted_by_signal and interruptable){ - if(debug){ - fprintf_plus(stderr, "Interrupted by signal\n"); - } - return false; - } - if(ret == -1){ - error_plus(0, errno, "waitpid"); - return false; - } - if(debug){ - if(WIFEXITED(status)){ - fprintf_plus(stderr, "exec_and_wait exited: %d\n", - WEXITSTATUS(status)); - } else if(WIFSIGNALED(status)) { - fprintf_plus(stderr, "exec_and_wait signaled: %d\n", - WTERMSIG(status)); - } - } - if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){ - return true; - } - return false; -} - -__attribute__((nonnull)) -int is_plymouth(const struct dirent *proc_entry){ - int ret; - { - uintmax_t proc_id; - char *tmp; - errno = 0; - proc_id = strtoumax(proc_entry->d_name, &tmp, 10); - - if(errno != 0 or *tmp != '\0' - or proc_id != (uintmax_t)((pid_t)proc_id)){ - return 0; - } - } - char exe_target[sizeof(plymouthd_path)]; - char *exe_link; - ret = asprintf(&exe_link, "/proc/%s/exe", proc_entry->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - return 0; - } - - struct stat exe_stat; - ret = lstat(exe_link, &exe_stat); - if(ret == -1){ - free(exe_link); - if(errno != ENOENT){ - error_plus(0, errno, "lstat"); - } - return 0; - } - - if(not S_ISLNK(exe_stat.st_mode) - or exe_stat.st_uid != 0 - or exe_stat.st_gid != 0){ - free(exe_link); - return 0; - } - - ssize_t sret = readlink(exe_link, exe_target, sizeof(exe_target)); - free(exe_link); - if((sret != (ssize_t)sizeof(plymouthd_path)-1) or - (memcmp(plymouthd_path, exe_target, - sizeof(plymouthd_path)-1) != 0)){ - return 0; - } - return 1; -} - -pid_t get_pid(void){ - int ret; - uintmax_t proc_id = 0; - FILE *pidfile = fopen(plymouth_pid, "r"); - /* Try the new pid file location */ - if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); - if(ret != 1){ - proc_id = 0; - } - fclose(pidfile); - } - /* Try the old pid file location */ - if(proc_id == 0){ - pidfile = fopen(plymouth_old_pid, "r"); - if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); - if(ret != 1){ - proc_id = 0; - } - fclose(pidfile); - } - } - /* Try the old old pid file location */ - if(proc_id == 0){ - pidfile = fopen(plymouth_old_old_pid, "r"); - if(pidfile != NULL){ - ret = fscanf(pidfile, "%" SCNuMAX, &proc_id); - if(ret != 1){ - proc_id = 0; - } - fclose(pidfile); - } - } - /* Look for a plymouth process */ - if(proc_id == 0){ - struct dirent **direntries = NULL; - ret = scandir("/proc", &direntries, is_plymouth, alphasort); - if(ret == -1){ - error_plus(0, errno, "scandir"); - } - if(ret > 0){ - for(int i = ret-1; i >= 0; i--){ - if(proc_id == 0){ - ret = sscanf(direntries[i]->d_name, "%" SCNuMAX, &proc_id); - if(ret < 0){ - error_plus(0, errno, "sscanf"); - } - } - free(direntries[i]); - } - } - /* scandir might preallocate for this variable (man page unclear). - even if ret == 0, therefore we need to free it. */ - free(direntries); - } - pid_t pid; - pid = (pid_t)proc_id; - if((uintmax_t)pid == proc_id){ - return pid; - } - - return 0; -} - -char **getargv(pid_t pid){ - int cl_fd; - char *cmdline_filename; - ssize_t sret; - int ret; - - ret = asprintf(&cmdline_filename, "/proc/%" PRIuMAX "/cmdline", - (uintmax_t)pid); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - return NULL; - } - - /* Open /proc//cmdline */ - cl_fd = open(cmdline_filename, O_RDONLY); - free(cmdline_filename); - if(cl_fd == -1){ - error_plus(0, errno, "open"); - return NULL; - } - - size_t cmdline_allocated = 0; - size_t cmdline_len = 0; - char *cmdline = NULL; - char *tmp; - const size_t blocksize = 1024; - do { - /* Allocate more space? */ - if(cmdline_len + blocksize > cmdline_allocated){ - tmp = realloc(cmdline, cmdline_allocated + blocksize); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - free(cmdline); - close(cl_fd); - return NULL; - } - cmdline = tmp; - cmdline_allocated += blocksize; - } - - /* Read data */ - sret = read(cl_fd, cmdline + cmdline_len, - cmdline_allocated - cmdline_len); - if(sret == -1){ - error_plus(0, errno, "read"); - free(cmdline); - close(cl_fd); - return NULL; - } - cmdline_len += (size_t)sret; - } while(sret != 0); - ret = close(cl_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - free(cmdline); - return NULL; - } - - /* we got cmdline and cmdline_len, ignore rest... */ - char **argv = malloc((argz_count(cmdline, cmdline_len) + 1) - * sizeof(char *)); /* Get number of args */ - if(argv == NULL){ - error_plus(0, errno, "argv = malloc()"); - free(cmdline); - return NULL; - } - argz_extract(cmdline, cmdline_len, argv); /* Create argv */ - return argv; -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - char *prompt = NULL; - char *prompt_arg; - pid_t plymouth_command_pid; - int ret; - bool bret; - - { - struct argp_option options[] = { - { .name = "prompt", .key = 128, .arg = "PROMPT", - .doc = "The prompt to show" }, - { .name = "debug", .key = 129, - .doc = "Debug mode" }, - { .name = NULL } - }; - - __attribute__((nonnull(3))) - error_t parse_opt (int key, char *arg, __attribute__((unused)) - struct argp_state *state){ - errno = 0; - switch (key){ - case 128: /* --prompt */ - prompt = arg; - if(debug){ - fprintf_plus(stderr, "Custom prompt \"%s\"\n", prompt); - } - break; - case 129: /* --debug */ - debug = true; - break; - default: - return ARGP_ERR_UNKNOWN; - } - return errno; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "", - .doc = "Mandos plymouth -- Read and" - " output a password" }; - ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, NULL); - switch(ret){ - case 0: - break; - case ENOMEM: - default: - errno = ret; - error_plus(0, errno, "argp_parse"); - return EX_OSERR; - case EINVAL: - error_plus(0, errno, "argp_parse"); - return EX_USAGE; - } - } - - /* test -x /bin/plymouth */ - ret = access(plymouth_path, X_OK); - if(ret == -1){ - /* Plymouth is probably not installed. Don't print an error - message, just exit. */ - if(debug){ - fprintf_plus(stderr, "Plymouth (%s) not found\n", - plymouth_path); - } - exit(EX_UNAVAILABLE); - } - - { /* Add signal handlers */ - struct sigaction old_action, - new_action = { .sa_handler = termination_handler, - .sa_flags = 0 }; - sigemptyset(&new_action.sa_mask); - for(int *sig = (int[]){ SIGINT, SIGHUP, SIGTERM, 0 }; - *sig != 0; sig++){ - ret = sigaddset(&new_action.sa_mask, *sig); - if(ret == -1){ - error_plus(EX_OSERR, errno, "sigaddset"); - } - ret = sigaction(*sig, NULL, &old_action); - if(ret == -1){ - error_plus(EX_OSERR, errno, "sigaction"); - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(*sig, &new_action, NULL); - if(ret == -1){ - error_plus(EX_OSERR, errno, "sigaction"); - } - } - } - } - - /* plymouth --ping */ - bret = exec_and_wait(&plymouth_command_pid, plymouth_path, - (const char *[]) - { plymouth_path, "--ping", NULL }, - true, false); - if(not bret){ - if(interrupted_by_signal){ - kill_and_wait(plymouth_command_pid); - exit(EXIT_FAILURE); - } - /* Plymouth is probably not running. Don't print an error - message, just exit. */ - if(debug){ - fprintf_plus(stderr, "Plymouth not running\n"); - } - exit(EX_UNAVAILABLE); - } - - if(prompt != NULL){ - ret = asprintf(&prompt_arg, "--prompt=%s", prompt); - } else { - char *made_prompt = makeprompt(); - ret = asprintf(&prompt_arg, "--prompt=%s", made_prompt); - free(made_prompt); - } - if(ret == -1){ - error_plus(EX_OSERR, errno, "asprintf"); - } - - /* plymouth ask-for-password --prompt="$prompt" */ - if(debug){ - fprintf_plus(stderr, "Prompting for password via Plymouth\n"); - } - bret = exec_and_wait(&plymouth_command_pid, - plymouth_path, (const char *[]) - { plymouth_path, "ask-for-password", - prompt_arg, NULL }, - true, false); - free(prompt_arg); - if(bret){ - exit(EXIT_SUCCESS); - } - if(not interrupted_by_signal){ - /* exec_and_wait failed for some other reason */ - exit(EXIT_FAILURE); - } - kill_and_wait(plymouth_command_pid); - - char **plymouthd_argv = NULL; - pid_t pid = get_pid(); - if(pid == 0){ - error_plus(0, 0, "plymouthd pid not found"); - } else { - plymouthd_argv = getargv(pid); - } - - bret = exec_and_wait(NULL, plymouth_path, (const char *[]) - { plymouth_path, "quit", NULL }, - false, false); - if(not bret){ - if(plymouthd_argv != NULL){ - free(*plymouthd_argv); - free(plymouthd_argv); - } - exit(EXIT_FAILURE); - } - bret = exec_and_wait(NULL, plymouthd_path, - (plymouthd_argv != NULL) - ? (const char * const *)plymouthd_argv - : plymouthd_default_argv, - false, true); - if(plymouthd_argv != NULL){ - free(*plymouthd_argv); - free(plymouthd_argv); - } - if(not bret){ - exit(EXIT_FAILURE); - } - exec_and_wait(NULL, plymouth_path, (const char *[]) - { plymouth_path, "show-splash", NULL }, - false, false); - exit(EXIT_FAILURE); -} === removed file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/plymouth.xml 1970-01-01 00:00:00 +0000 @@ -1,370 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to use plymouth to get a - password. - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - - - - &COMMANDNAME; - - - - &COMMANDNAME; - - - - - - - - - DESCRIPTION - - This program prompts for a password using - plymouth8 - and outputs any given password to standard - output. If no plymouth8 - process can be found, this program will immediately exit with an - exit code indicating failure. - - - This program is not very useful on its own. This program is - really meant to run as a plugin in the Mandos client-side system, where it is used as a - fallback and alternative to retrieving passwords from a - Mandos server. - - - If this program is killed (presumably by - plugin-runner - 8mandos because some other - plugin provided the password), it cannot tell - plymouth8 - to abort requesting a password, because - plymouth - 8 does not support this. - Therefore, this program will then kill the - running plymouth - 8 process and start a - new one using the same command line - arguments as the old one was using. - - - - - OPTIONS - - This program is commonly not invoked from the command line; it - is normally started by the Mandos - plugin runner, see plugin-runner8mandos - . Any command line options this program accepts - are therefore normally provided by the plugin runner, and not - directly. - - - - - - - - The password prompt. Note that using this option will - make this program ignore the cryptsource - and crypttarget environment variables. - - - - - - - - - Enable debug mode. This will enable a lot of output to - standard error about what the program is doing. The - program will still perform all other functions normally. - - - - - - - - - - Gives a help message about options and their meanings. - - - - - - - - - Gives a short usage message. - - - - - - - - - - Prints the program version. - - - - - - - - EXIT STATUS - - If exit status is 0, the output from the program is the password - as it was read. Otherwise, if exit status is other than 0, the - program was interrupted or encountered an error, and any output - so far could be corrupt and/or truncated, and should therefore - be ignored. - - - - - ENVIRONMENT - - - cryptsource - crypttarget - - - If set, and if the option is not - used, these environment variables will be assumed to - contain the source device name and the target device - mapper name, respectively, and will be shown as part of - the prompt. - - - These variables will normally be inherited from - plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. - - - This behavior is meant to exactly mirror the behavior of - askpass, the default password prompter - from initramfs-tools. - - - - - - - - FILES - - - /bin/plymouth - - - This is the command run to retrieve a password from - plymouth - 8. - - - - - /proc - - - To find the running plymouth8 - , this directory will be searched for - numeric entries which will be assumed to be directories. - In all those directories, the exe and - cmdline entries will be used to - determine the name of the running binary, effective user - and group ID, and the command line - arguments. See proc5 - . - - - - - /sbin/plymouthd - - - This is the name of the binary which will be searched for - in the process list. See plymouth8 - . - - - - - - - - BUGS - - Killing the plymouth8 - daemon and starting a new one is ugly, but necessary as long as - it does not support aborting a password request. - - - - - - EXAMPLE - - Note that normally, this program will not be invoked directly, - but instead started by the Mandos plugin-runner8mandos - . - - - - Normal invocation needs no options: - - - &COMMANDNAME; - - - - - Show a different prompt. - - - &COMMANDNAME; --prompt=Password - - - - - - SECURITY - - If this program is killed by a signal, it will kill the process - ID which at the start of this program was - determined to run plymouth8 - as root (see also ). There is a very - slight risk that, in the time between those events, that process - ID was freed and then taken up by another - process; the wrong process would then be killed. Now, this - program can only be killed by the user who started it; see - plugin-runner - 8mandos. This program - should therefore be started by a completely separate - non-privileged user, and no other programs should be allowed to - run as that special user. This means that it is not recommended - to use the user "nobody" to start this program, as other - possibly less trusted programs could be running as "nobody", and - they would then be able to kill this program, triggering the - killing of the process ID which may or may not - be plymouth - 8. - - - The only other thing that could be considered worthy of note is - this: This program is meant to be run by - plugin-runner8mandos, and will, when run - standalone, outside, in a normal environment, immediately output - on its standard output any presumably secret password it just - received. Therefore, when running this program standalone - (which should never normally be done), take care not to type in - any real secret password by force of habit, since it would then - immediately be shown as output. - - - - - SEE ALSO - - intro - 8mandos, - plugin-runner - 8mandos, - proc - 5, - plymouth - 8 - - -
- - - - - === removed file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2018-02-08 10:23:55 +0000 +++ plugins.d/splashy.c 1970-01-01 00:00:00 +0000 @@ -1,472 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Splashy - Read a password from splashy and output it - * - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-2018 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos 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 Mandos. If not, see . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ -#include /* sig_atomic_t, struct sigaction, - sigemptyset(), sigaddset(), SIGINT, - SIGHUP, SIGTERM, sigaction, - SIG_IGN, kill(), SIGKILL */ -#include /* NULL */ -#include /* getenv() */ -#include /* asprintf(), vasprintf(), vprintf(), - fprintf() */ -#include /* EXIT_FAILURE, free(), - EXIT_SUCCESS */ -#include /* pid_t, DIR, struct dirent, - ssize_t */ -#include /* opendir(), readdir(), closedir() */ -#include /* intmax_t, strtoimax() */ -#include /* struct stat, lstat(), S_ISLNK */ -#include /* not, or, and */ -#include /* readlink(), fork(), execl(), - sleep(), dup2() STDERR_FILENO, - STDOUT_FILENO, _exit(), - pause() */ -#include /* memcmp(), strerror() */ -#include /* errno, EACCES, ENOTDIR, ELOOP, - ENOENT, ENAMETOOLONG, EMFILE, - ENFILE, ENOMEM, ENOEXEC, EINVAL, - E2BIG, EFAULT, EIO, ETXTBSY, - EISDIR, ELIBBAD, EPERM, EINTR, - ECHILD */ -#include /* error() */ -#include /* waitpid(), WIFEXITED(), - WEXITSTATUS() */ -#include /* EX_OSERR, EX_OSFILE, - EX_UNAVAILABLE */ -#include /* va_list, va_start(), ... */ - -sig_atomic_t interrupted_by_signal = 0; -int signal_received; - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - - -static void termination_handler(int signum){ - if(interrupted_by_signal){ - return; - } - interrupted_by_signal = 1; - signal_received = signum; -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - int ret = 0; - char *prompt = NULL; - DIR *proc_dir = NULL; - pid_t splashy_pid = 0; - pid_t splashy_command_pid = 0; - int exitstatus = EXIT_FAILURE; - - /* Create prompt string */ - { - const char *const cryptsource = getenv("cryptsource"); - const char *const crypttarget = getenv("crypttarget"); - const char *const prompt_start = "getpass " - "Enter passphrase to unlock the disk"; - - if(cryptsource == NULL){ - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s: ", prompt_start); - } else { - ret = asprintf(&prompt, "%s (%s): ", prompt_start, - crypttarget); - } - } else { - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); - } else { - ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, - cryptsource, crypttarget); - } - } - if(ret == -1){ - prompt = NULL; - exitstatus = EX_OSERR; - goto failure; - } - } - - /* Find splashy process */ - { - const char splashy_name[] = "/sbin/splashy"; - proc_dir = opendir("/proc"); - if(proc_dir == NULL){ - int e = errno; - error_plus(0, errno, "opendir"); - switch(e){ - case EACCES: - case ENOTDIR: - case ELOOP: - case ENOENT: - default: - exitstatus = EX_OSFILE; - break; - case ENAMETOOLONG: - case EMFILE: - case ENFILE: - case ENOMEM: - exitstatus = EX_OSERR; - break; - } - goto failure; - } - for(struct dirent *proc_ent = readdir(proc_dir); - proc_ent != NULL; - proc_ent = readdir(proc_dir)){ - pid_t pid; - { - intmax_t tmpmax; - char *tmp; - errno = 0; - tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); - if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' - or tmpmax != (pid_t)tmpmax){ - /* Not a process */ - continue; - } - pid = (pid_t)tmpmax; - } - /* Find the executable name by doing readlink() on the - /proc//exe link */ - char exe_target[sizeof(splashy_name)]; - ssize_t sret; - { - char *exe_link; - ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - exitstatus = EX_OSERR; - goto failure; - } - - /* Check that it refers to a symlink owned by root:root */ - struct stat exe_stat; - ret = lstat(exe_link, &exe_stat); - if(ret == -1){ - if(errno == ENOENT){ - free(exe_link); - continue; - } - int e = errno; - error_plus(0, errno, "lstat"); - free(exe_link); - switch(e){ - case EACCES: - case ENOTDIR: - case ELOOP: - default: - exitstatus = EX_OSFILE; - break; - case ENAMETOOLONG: - exitstatus = EX_OSERR; - break; - } - goto failure; - } - if(not S_ISLNK(exe_stat.st_mode) - or exe_stat.st_uid != 0 - or exe_stat.st_gid != 0){ - free(exe_link); - continue; - } - - sret = readlink(exe_link, exe_target, sizeof(exe_target)); - free(exe_link); - } - if((sret == ((ssize_t)sizeof(exe_target)-1)) - and (memcmp(splashy_name, exe_target, - sizeof(exe_target)-1) == 0)){ - splashy_pid = pid; - break; - } - } - closedir(proc_dir); - proc_dir = NULL; - } - if(splashy_pid == 0){ - exitstatus = EX_UNAVAILABLE; - goto failure; - } - - /* Set up the signal handler */ - { - struct sigaction old_action, - new_action = { .sa_handler = termination_handler, - .sa_flags = 0 }; - sigemptyset(&new_action.sa_mask); - ret = sigaddset(&new_action.sa_mask, SIGINT); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGHUP); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGTERM); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - exitstatus = EX_OSERR; - goto failure; - } - ret = sigaction(SIGINT, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGINT, &new_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto failure; - } - } - ret = sigaction(SIGHUP, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGHUP, &new_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto failure; - } - } - ret = sigaction(SIGTERM, NULL, &old_action); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGTERM, &new_action, NULL); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - exitstatus = EX_OSERR; - goto failure; - } - } - } - - if(interrupted_by_signal){ - goto failure; - } - - /* Fork off the splashy command to prompt for password */ - splashy_command_pid = fork(); - if(splashy_command_pid != 0 and interrupted_by_signal){ - goto failure; - } - if(splashy_command_pid == -1){ - error_plus(0, errno, "fork"); - exitstatus = EX_OSERR; - goto failure; - } - /* Child */ - if(splashy_command_pid == 0){ - if(not interrupted_by_signal){ - const char splashy_command[] = "/sbin/splashy_update"; - execl(splashy_command, splashy_command, prompt, (char *)NULL); - int e = errno; - error_plus(0, errno, "execl"); - switch(e){ - case EACCES: - case ENOENT: - case ENOEXEC: - case EINVAL: - _exit(EX_UNAVAILABLE); - case ENAMETOOLONG: - case E2BIG: - case ENOMEM: - case EFAULT: - case EIO: - case EMFILE: - case ENFILE: - case ETXTBSY: - default: - _exit(EX_OSERR); - case ENOTDIR: - case ELOOP: - case EISDIR: -#ifdef ELIBBAD - case ELIBBAD: /* Linux only */ -#endif - case EPERM: - _exit(EX_OSFILE); - } - } - free(prompt); - _exit(EXIT_FAILURE); - } - - /* Parent */ - free(prompt); - prompt = NULL; - - if(interrupted_by_signal){ - goto failure; - } - - /* Wait for command to complete */ - { - int status; - do { - ret = waitpid(splashy_command_pid, &status, 0); - } while(ret == -1 and errno == EINTR - and not interrupted_by_signal); - if(interrupted_by_signal){ - goto failure; - } - if(ret == -1){ - error_plus(0, errno, "waitpid"); - if(errno == ECHILD){ - splashy_command_pid = 0; - } - } else { - /* The child process has exited */ - splashy_command_pid = 0; - if(WIFEXITED(status) and WEXITSTATUS(status) == 0){ - return EXIT_SUCCESS; - } - } - } - - failure: - - free(prompt); - - if(proc_dir != NULL){ - TEMP_FAILURE_RETRY(closedir(proc_dir)); - } - - if(splashy_command_pid != 0){ - TEMP_FAILURE_RETRY(kill(splashy_command_pid, SIGTERM)); - - TEMP_FAILURE_RETRY(kill(splashy_pid, SIGTERM)); - sleep(2); - while(TEMP_FAILURE_RETRY(kill(splashy_pid, 0)) == 0){ - TEMP_FAILURE_RETRY(kill(splashy_pid, SIGKILL)); - sleep(1); - } - pid_t new_splashy_pid = (pid_t)TEMP_FAILURE_RETRY(fork()); - if(new_splashy_pid == 0){ - /* Child; will become new splashy process */ - - /* Make the effective user ID (root) the only user ID instead of - the real user ID (_mandos) */ - ret = setuid(geteuid()); - if(ret == -1){ - error_plus(0, errno, "setuid"); - } - - setsid(); - ret = chdir("/"); - if(ret == -1){ - error_plus(0, errno, "chdir"); - } -/* if(fork() != 0){ */ -/* _exit(EXIT_SUCCESS); */ -/* } */ - ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */ - if(ret == -1){ - error_plus(0, errno, "dup2"); - _exit(EX_OSERR); - } - - execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); - { - int e = errno; - error_plus(0, errno, "execl"); - switch(e){ - case EACCES: - case ENOENT: - case ENOEXEC: - default: - _exit(EX_UNAVAILABLE); - case ENAMETOOLONG: - case E2BIG: - case ENOMEM: - _exit(EX_OSERR); - case ENOTDIR: - case ELOOP: - _exit(EX_OSFILE); - } - } - } - } - - if(interrupted_by_signal){ - struct sigaction signal_action; - sigemptyset(&signal_action.sa_mask); - signal_action.sa_handler = SIG_DFL; - ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, - &signal_action, NULL)); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - } - do { - ret = raise(signal_received); - } while(ret != 0 and errno == EINTR); - if(ret != 0){ - error_plus(0, errno, "raise"); - abort(); - } - TEMP_FAILURE_RETRY(pause()); - } - - return exitstatus; -} === removed file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/splashy.xml 1970-01-01 00:00:00 +0000 @@ -1,284 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to use splashy to get a - password. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program prompts for a password using - splashy_update - 8 and outputs any given - password to standard output. If no splashy8 - process can be found, this program will immediately exit with an - exit code indicating failure. - - - This program is not very useful on its own. This program is - really meant to run as a plugin in the Mandos client-side system, where it is used as a - fallback and alternative to retrieving passwords from a - Mandos server. - - - If this program is killed (presumably by - plugin-runner - 8mandos because some other - plugin provided the password), it cannot tell - splashy8 - to abort requesting a password, because - splashy - 8 does not support this. - Therefore, this program will then kill the - running splashy - 8 process and start a - new one, using boot as the only argument. - - - - - OPTIONS - - This program takes no options. - - - - - EXIT STATUS - - If exit status is 0, the output from the program is the password - as it was read. Otherwise, if exit status is other than 0, the - program was interrupted or encountered an error, and any output - so far could be corrupt and/or truncated, and should therefore - be ignored. - - - - - ENVIRONMENT - - - cryptsource - crypttarget - - - If set, these environment variables will be assumed to - contain the source device name and the target device - mapper name, respectively, and will be shown as part of - the prompt. - - - These variables will normally be inherited from - plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. - - - This behavior is meant to exactly mirror the behavior of - askpass, the default password prompter. - - - - - - - - FILES - - - /sbin/splashy_update - - - This is the command run to retrieve a password from - splashy - 8. See - splashy_update8 - . - - - - - /proc - - - To find the running splashy8 - , this directory will be searched for - numeric entries which will be assumed to be directories. - In all those directories, the exe - entry will be used to determine the name of the running - binary and the effective user and group - ID of the process. See - proc5. - - - - - /sbin/splashy - - - This is the name of the binary which will be searched for - in the process list. See splashy8 - . - - - - - - - - BUGS - - Killing splashy - 8 and starting a new one - is ugly, but necessary as long as it does not support aborting a - password request. - - - - - - EXAMPLE - - Note that normally, this program will not be invoked directly, - but instead started by the Mandos plugin-runner8mandos - . - - - - This program takes no options. - - - &COMMANDNAME; - - - - - - SECURITY - - If this program is killed by a signal, it will kill the process - ID which at the start of this program was - determined to run splashy8 - as root (see also ). There is a very - slight risk that, in the time between those events, that process - ID was freed and then taken up by another - process; the wrong process would then be killed. Now, this - program can only be killed by the user who started it; see - plugin-runner - 8mandos. This program - should therefore be started by a completely separate - non-privileged user, and no other programs should be allowed to - run as that special user. This means that it is not recommended - to use the user "nobody" to start this program, as other - possibly less trusted programs could be running as "nobody", and - they would then be able to kill this program, triggering the - killing of the process ID which may or may not - be splashy - 8. - - - The only other thing that could be considered worthy of note is - this: This program is meant to be run by - plugin-runner8mandos, and will, when run - standalone, outside, in a normal environment, immediately output - on its standard output any presumably secret password it just - received. Therefore, when running this program standalone - (which should never normally be done), take care not to type in - any real secret password by force of habit, since it would then - immediately be shown as output. - - - - - SEE ALSO - - intro - 8mandos, - plugin-runner - 8mandos, - proc - 5, - splashy - 8, - splashy_update - 8 - - -
- - - - - === removed file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2018-02-08 10:23:55 +0000 +++ plugins.d/usplash.c 1970-01-01 00:00:00 +0000 @@ -1,685 +0,0 @@ -/* -*- coding: utf-8 -*- */ -/* - * Usplash - Read a password from usplash and output it - * - * Copyright © 2008-2018 Teddy Hogeborn - * Copyright © 2008-2018 Björn Påhlsson - * - * This file is part of Mandos. - * - * Mandos 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. - * - * Mandos 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 Mandos. If not, see . - * - * Contact the authors at . - */ - -#define _GNU_SOURCE /* asprintf(), TEMP_FAILURE_RETRY() */ -#include /* sig_atomic_t, struct sigaction, - sigemptyset(), sigaddset(), SIGINT, - SIGHUP, SIGTERM, sigaction(), - SIG_IGN, kill(), SIGKILL */ -#include /* bool, false, true */ -#include /* open(), O_WRONLY, O_RDONLY */ -#include /* and, or, not*/ -#include /* errno, EINTR */ -#include -#include /* size_t, ssize_t, pid_t, DIR, struct - dirent */ -#include /* NULL */ -#include /* strlen(), memcmp(), strerror() */ -#include /* asprintf(), vasprintf(), vprintf(), - fprintf() */ -#include /* close(), write(), readlink(), - read(), STDOUT_FILENO, sleep(), - fork(), setuid(), geteuid(), - setsid(), chdir(), dup2(), - STDERR_FILENO, execv() */ -#include /* free(), EXIT_FAILURE, realloc(), - EXIT_SUCCESS, malloc(), _exit(), - getenv() */ -#include /* opendir(), readdir(), closedir() */ -#include /* intmax_t, strtoimax() */ -#include /* struct stat, lstat(), S_ISLNK */ -#include /* EX_OSERR, EX_UNAVAILABLE */ -#include /* argz_count(), argz_extract() */ -#include /* va_list, va_start(), ... */ - -sig_atomic_t interrupted_by_signal = 0; -int signal_received; -const char usplash_name[] = "/sbin/usplash"; - -/* Function to use when printing errors */ -__attribute__((format (gnu_printf, 3, 4))) -void error_plus(int status, int errnum, const char *formatstring, - ...){ - va_list ap; - char *text; - int ret; - - va_start(ap, formatstring); - ret = vasprintf(&text, formatstring, ap); - if(ret == -1){ - fprintf(stderr, "Mandos plugin %s: ", - program_invocation_short_name); - vfprintf(stderr, formatstring, ap); - fprintf(stderr, ": "); - fprintf(stderr, "%s\n", strerror(errnum)); - error(status, errno, "vasprintf while printing error"); - return; - } - fprintf(stderr, "Mandos plugin "); - error(status, errnum, "%s", text); - free(text); -} - -static void termination_handler(int signum){ - if(interrupted_by_signal){ - return; - } - interrupted_by_signal = 1; - signal_received = signum; -} - -static bool usplash_write(int *fifo_fd_r, - const char *cmd, const char *arg){ - /* - * usplash_write(&fd, "TIMEOUT", "15") will write "TIMEOUT 15\0" - * usplash_write(&fd, "PULSATE", NULL) will write "PULSATE\0" - * SEE ALSO - * usplash_write(8) - */ - int ret; - if(*fifo_fd_r == -1){ - ret = open("/dev/.initramfs/usplash_fifo", O_WRONLY); - if(ret == -1){ - return false; - } - *fifo_fd_r = ret; - } - - const char *cmd_line; - size_t cmd_line_len; - char *cmd_line_alloc = NULL; - if(arg == NULL){ - cmd_line = cmd; - cmd_line_len = strlen(cmd) + 1; - } else { - do { - ret = asprintf(&cmd_line_alloc, "%s %s", cmd, arg); - if(ret == -1){ - int e = errno; - close(*fifo_fd_r); - errno = e; - return false; - } - } while(ret == -1); - cmd_line = cmd_line_alloc; - cmd_line_len = (size_t)ret + 1; - } - - size_t written = 0; - ssize_t sret = 0; - while(written < cmd_line_len){ - sret = write(*fifo_fd_r, cmd_line + written, - cmd_line_len - written); - if(sret == -1){ - int e = errno; - close(*fifo_fd_r); - free(cmd_line_alloc); - errno = e; - return false; - } - written += (size_t)sret; - } - free(cmd_line_alloc); - - return true; -} - -/* Create prompt string */ -char *makeprompt(void){ - int ret = 0; - char *prompt; - const char *const cryptsource = getenv("cryptsource"); - const char *const crypttarget = getenv("crypttarget"); - const char prompt_start[] = "Enter passphrase to unlock the disk"; - - if(cryptsource == NULL){ - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s: ", prompt_start); - } else { - ret = asprintf(&prompt, "%s (%s): ", prompt_start, - crypttarget); - } - } else { - if(crypttarget == NULL){ - ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource); - } else { - ret = asprintf(&prompt, "%s %s (%s): ", prompt_start, - cryptsource, crypttarget); - } - } - if(ret == -1){ - return NULL; - } - return prompt; -} - -pid_t find_usplash(char **cmdline_r, size_t *cmdline_len_r){ - int ret = 0; - ssize_t sret = 0; - char *cmdline = NULL; - size_t cmdline_len = 0; - DIR *proc_dir = opendir("/proc"); - if(proc_dir == NULL){ - error_plus(0, errno, "opendir"); - return -1; - } - errno = 0; - for(struct dirent *proc_ent = readdir(proc_dir); - proc_ent != NULL; - proc_ent = readdir(proc_dir)){ - pid_t pid; - { - intmax_t tmpmax; - char *tmp; - tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); - if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' - or tmpmax != (pid_t)tmpmax){ - /* Not a process */ - errno = 0; - continue; - } - pid = (pid_t)tmpmax; - } - /* Find the executable name by doing readlink() on the - /proc//exe link */ - char exe_target[sizeof(usplash_name)]; - { - /* create file name string */ - char *exe_link; - ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - goto fail_find_usplash; - } - - /* Check that it refers to a symlink owned by root:root */ - struct stat exe_stat; - ret = lstat(exe_link, &exe_stat); - if(ret == -1){ - if(errno == ENOENT){ - free(exe_link); - continue; - } - error_plus(0, errno, "lstat"); - free(exe_link); - goto fail_find_usplash; - } - if(not S_ISLNK(exe_stat.st_mode) - or exe_stat.st_uid != 0 - or exe_stat.st_gid != 0){ - free(exe_link); - continue; - } - - sret = readlink(exe_link, exe_target, sizeof(exe_target)); - free(exe_link); - } - /* Compare executable name */ - if((sret != ((ssize_t)sizeof(exe_target)-1)) - or (memcmp(usplash_name, exe_target, - sizeof(exe_target)-1) != 0)){ - /* Not it */ - continue; - } - /* Found usplash */ - /* Read and save the command line of usplash in "cmdline" */ - { - /* Open /proc//cmdline */ - int cl_fd; - { - char *cmdline_filename; - ret = asprintf(&cmdline_filename, "/proc/%s/cmdline", - proc_ent->d_name); - if(ret == -1){ - error_plus(0, errno, "asprintf"); - goto fail_find_usplash; - } - cl_fd = open(cmdline_filename, O_RDONLY); - free(cmdline_filename); - if(cl_fd == -1){ - error_plus(0, errno, "open"); - goto fail_find_usplash; - } - } - size_t cmdline_allocated = 0; - char *tmp; - const size_t blocksize = 1024; - do { - /* Allocate more space? */ - if(cmdline_len + blocksize > cmdline_allocated){ - tmp = realloc(cmdline, cmdline_allocated + blocksize); - if(tmp == NULL){ - error_plus(0, errno, "realloc"); - close(cl_fd); - goto fail_find_usplash; - } - cmdline = tmp; - cmdline_allocated += blocksize; - } - /* Read data */ - sret = read(cl_fd, cmdline + cmdline_len, - cmdline_allocated - cmdline_len); - if(sret == -1){ - error_plus(0, errno, "read"); - close(cl_fd); - goto fail_find_usplash; - } - cmdline_len += (size_t)sret; - } while(sret != 0); - ret = close(cl_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - goto fail_find_usplash; - } - } - /* Close directory */ - ret = closedir(proc_dir); - if(ret == -1){ - error_plus(0, errno, "closedir"); - goto fail_find_usplash; - } - /* Success */ - *cmdline_r = cmdline; - *cmdline_len_r = cmdline_len; - return pid; - } - - fail_find_usplash: - - free(cmdline); - if(proc_dir != NULL){ - int e = errno; - closedir(proc_dir); - errno = e; - } - return 0; -} - -int main(__attribute__((unused))int argc, - __attribute__((unused))char **argv){ - int ret = 0; - ssize_t sret; - int fifo_fd = -1; - int outfifo_fd = -1; - char *buf = NULL; - size_t buf_len = 0; - pid_t usplash_pid = -1; - bool usplash_accessed = false; - int status = EXIT_FAILURE; /* Default failure exit status */ - - char *prompt = makeprompt(); - if(prompt == NULL){ - status = EX_OSERR; - goto failure; - } - - /* Find usplash process */ - char *cmdline = NULL; - size_t cmdline_len = 0; - usplash_pid = find_usplash(&cmdline, &cmdline_len); - if(usplash_pid == 0){ - status = EX_UNAVAILABLE; - goto failure; - } - - /* Set up the signal handler */ - { - struct sigaction old_action, - new_action = { .sa_handler = termination_handler, - .sa_flags = 0 }; - sigemptyset(&new_action.sa_mask); - ret = sigaddset(&new_action.sa_mask, SIGINT); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - status = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGHUP); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - status = EX_OSERR; - goto failure; - } - ret = sigaddset(&new_action.sa_mask, SIGTERM); - if(ret == -1){ - error_plus(0, errno, "sigaddset"); - status = EX_OSERR; - goto failure; - } - ret = sigaction(SIGINT, NULL, &old_action); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGINT, &new_action, NULL); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - } - ret = sigaction(SIGHUP, NULL, &old_action); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGHUP, &new_action, NULL); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - } - ret = sigaction(SIGTERM, NULL, &old_action); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - if(old_action.sa_handler != SIG_IGN){ - ret = sigaction(SIGTERM, &new_action, NULL); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "sigaction"); - status = EX_OSERR; - } - goto failure; - } - } - } - - usplash_accessed = true; - /* Write command to FIFO */ - if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - if(not usplash_write(&fifo_fd, "INPUTQUIET", prompt)){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - free(prompt); - prompt = NULL; - - /* Read reply from usplash */ - /* Open FIFO */ - outfifo_fd = open("/dev/.initramfs/usplash_outfifo", O_RDONLY); - if(outfifo_fd == -1){ - if(errno != EINTR){ - error_plus(0, errno, "open"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - /* Read from FIFO */ - size_t buf_allocated = 0; - const size_t blocksize = 1024; - do { - /* Allocate more space */ - if(buf_len + blocksize > buf_allocated){ - char *tmp = realloc(buf, buf_allocated + blocksize); - if(tmp == NULL){ - if(errno != EINTR){ - error_plus(0, errno, "realloc"); - status = EX_OSERR; - } - goto failure; - } - buf = tmp; - buf_allocated += blocksize; - } - sret = read(outfifo_fd, buf + buf_len, - buf_allocated - buf_len); - if(sret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "read"); - status = EX_OSERR; - } - close(outfifo_fd); - goto failure; - } - if(interrupted_by_signal){ - break; - } - - buf_len += (size_t)sret; - } while(sret != 0); - ret = close(outfifo_fd); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "close"); - status = EX_OSERR; - } - goto failure; - } - outfifo_fd = -1; - - if(interrupted_by_signal){ - goto failure; - } - - if(not usplash_write(&fifo_fd, "TIMEOUT", "15")){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - status = EX_OSERR; - } - goto failure; - } - - if(interrupted_by_signal){ - goto failure; - } - - ret = close(fifo_fd); - if(ret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "close"); - status = EX_OSERR; - } - goto failure; - } - fifo_fd = -1; - - /* Print password to stdout */ - size_t written = 0; - while(written < buf_len){ - do { - sret = write(STDOUT_FILENO, buf + written, buf_len - written); - if(sret == -1){ - if(errno != EINTR){ - error_plus(0, errno, "write"); - status = EX_OSERR; - } - goto failure; - } - } while(sret == -1); - - if(interrupted_by_signal){ - goto failure; - } - written += (size_t)sret; - } - free(buf); - buf = NULL; - - if(interrupted_by_signal){ - goto failure; - } - - free(cmdline); - return EXIT_SUCCESS; - - failure: - - free(buf); - - free(prompt); - - /* If usplash was never accessed, we can stop now */ - if(not usplash_accessed){ - return status; - } - - /* Close FIFO */ - if(fifo_fd != -1){ - ret = close(fifo_fd); - if(ret == -1 and errno != EINTR){ - error_plus(0, errno, "close"); - } - fifo_fd = -1; - } - - /* Close output FIFO */ - if(outfifo_fd != -1){ - ret = close(outfifo_fd); - if(ret == -1){ - error_plus(0, errno, "close"); - } - } - - /* Create argv for new usplash*/ - char **cmdline_argv = malloc((argz_count(cmdline, cmdline_len) + 1) - * sizeof(char *)); /* Count args */ - if(cmdline_argv == NULL){ - error_plus(0, errno, "malloc"); - return status; - } - argz_extract(cmdline, cmdline_len, cmdline_argv); /* Create argv */ - - /* Kill old usplash */ - kill(usplash_pid, SIGTERM); - sleep(2); - while(kill(usplash_pid, 0) == 0){ - kill(usplash_pid, SIGKILL); - sleep(1); - } - - pid_t new_usplash_pid = fork(); - if(new_usplash_pid == 0){ - /* Child; will become new usplash process */ - - /* Make the effective user ID (root) the only user ID instead of - the real user ID (_mandos) */ - ret = setuid(geteuid()); - if(ret == -1){ - error_plus(0, errno, "setuid"); - } - - setsid(); - ret = chdir("/"); - if(ret == -1){ - error_plus(0, errno, "chdir"); - _exit(EX_OSERR); - } -/* if(fork() != 0){ */ -/* _exit(EXIT_SUCCESS); */ -/* } */ - ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ - if(ret == -1){ - error_plus(0, errno, "dup2"); - _exit(EX_OSERR); - } - - execv(usplash_name, cmdline_argv); - if(not interrupted_by_signal){ - error_plus(0, errno, "execv"); - } - free(cmdline); - free(cmdline_argv); - _exit(EX_OSERR); - } - free(cmdline); - free(cmdline_argv); - sleep(2); - if(not usplash_write(&fifo_fd, "PULSATE", NULL)){ - if(errno != EINTR){ - error_plus(0, errno, "usplash_write"); - } - } - - /* Close FIFO (again) */ - if(fifo_fd != -1){ - ret = close(fifo_fd); - if(ret == -1 and errno != EINTR){ - error_plus(0, errno, "close"); - } - fifo_fd = -1; - } - - if(interrupted_by_signal){ - struct sigaction signal_action = { .sa_handler = SIG_DFL }; - sigemptyset(&signal_action.sa_mask); - ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received, - &signal_action, NULL)); - if(ret == -1){ - error_plus(0, errno, "sigaction"); - } - do { - ret = raise(signal_received); - } while(ret != 0 and errno == EINTR); - if(ret != 0){ - error_plus(0, errno, "raise"); - abort(); - } - TEMP_FAILURE_RETRY(pause()); - } - - return status; -} === removed file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 2019-07-27 10:11:45 +0000 +++ plugins.d/usplash.xml 1970-01-01 00:00:00 +0000 @@ -1,298 +0,0 @@ - - - - -%common; -]> - - - - Mandos Manual - - Mandos - &version; - &TIMESTAMP; - - - Björn - Påhlsson -
- belorn@recompile.se -
-
- - Teddy - Hogeborn -
- teddy@recompile.se -
-
-
- - 2008 - 2009 - 2010 - 2011 - 2012 - 2013 - 2014 - 2015 - 2016 - 2017 - 2018 - 2019 - Teddy Hogeborn - Björn Påhlsson - - -
- - - &COMMANDNAME; - 8mandos - - - - &COMMANDNAME; - Mandos plugin to use usplash to get a - password. - - - - - &COMMANDNAME; - - - - - DESCRIPTION - - This program prompts for a password using - usplash8 - and outputs any given password to standard - output. If no usplash8 - process can be found, this program will immediately exit with an - exit code indicating failure. - - - This program is not very useful on its own. This program is - really meant to run as a plugin in the Mandos client-side system, where it is used as a - fallback and alternative to retrieving passwords from a - Mandos server. - - - If this program is killed (presumably by - plugin-runner - 8mandos because some other - plugin provided the password), it cannot tell - usplash8 - to abort requesting a password, because - usplash - 8 does not support this. - Therefore, this program will then kill the - running usplash - 8 process and start a - new one using the same command line - arguments as the old one was using. - - - - - OPTIONS - - This program takes no options. - - - - - EXIT STATUS - - If exit status is 0, the output from the program is the password - as it was read. Otherwise, if exit status is other than 0, the - program was interrupted or encountered an error, and any output - so far could be corrupt and/or truncated, and should therefore - be ignored. - - - - - ENVIRONMENT - - - cryptsource - crypttarget - - - If set, these environment variables will be assumed to - contain the source device name and the target device - mapper name, respectively, and will be shown as part of - the prompt. - - - These variables will normally be inherited from - plugin-runner - 8mandos, which might - have in turn inherited them from its calling process. - - - This behavior is meant to exactly mirror the behavior of - askpass, the default password prompter. - - - - - - - - FILES - - - /dev/.initramfs/usplash_fifo - - - This is the FIFO to where this program - will write the commands for usplash8 - . See fifo7 - . - - - - - /dev/.initramfs/usplash_outfifo - - - This is the FIFO where this program - will read the password from usplash8 - . See fifo7 - . - - - - - /proc - - - To find the running usplash8 - , this directory will be searched for - numeric entries which will be assumed to be directories. - In all those directories, the exe and - cmdline entries will be used to - determine the name of the running binary, effective user - and group ID, and the command line - arguments. See proc5 - . - - - - - /sbin/usplash - - - This is the name of the binary which will be searched for - in the process list. See usplash8 - . - - - - - - - - BUGS - - Killing usplash - 8 and starting a new one - is ugly, but necessary as long as it does not support aborting a - password request. - - - - - - EXAMPLE - - Note that normally, this program will not be invoked directly, - but instead started by the Mandos plugin-runner8mandos - . - - - - This program takes no options. - - - &COMMANDNAME; - - - - - - SECURITY - - If this program is killed by a signal, it will kill the process - ID which at the start of this program was - determined to run usplash8 - as root (see also ). There is a very - slight risk that, in the time between those events, that process - ID was freed and then taken up by another - process; the wrong process would then be killed. Now, this - program can only be killed by the user who started it; see - plugin-runner - 8mandos. This program - should therefore be started by a completely separate - non-privileged user, and no other programs should be allowed to - run as that special user. This means that it is not recommended - to use the user "nobody" to start this program, as other - possibly less trusted programs could be running as "nobody", and - they would then be able to kill this program, triggering the - killing of the process ID which may or may not - be usplash - 8. - - - The only other thing that could be considered worthy of note is - this: This program is meant to be run by - plugin-runner8mandos, and will, when run - standalone, outside, in a normal environment, immediately output - on its standard output any presumably secret password it just - received. Therefore, when running this program standalone - (which should never normally be done), take care not to type in - any real secret password by force of habit, since it would then - immediately be shown as output. - - - - - SEE ALSO - - intro - 8mandos, - fifo - 7, - plugin-runner - 8mandos, - proc - 5, - usplash - 8 - - -
- - - - - === renamed file 'mandos.conf' => 'server.conf' --- mandos.conf 2015-07-20 03:03:33 +0000 +++ server.conf 2008-07-29 03:35:39 +0000 @@ -1,53 +1,7 @@ -# This file must have exactly one section named "DEFAULT". -[DEFAULT] - -# These are the default values for the server, uncomment and change -# them if needed. - -# If "interface" is set, the server will only listen to a specific -# network interface. -;interface = - -# If "address" is set, the server will only listen to a specific -# address. This must currently be an IPv6 address; an IPv4 address -# can be specified using the "::FFFF:192.0.2.3" syntax. Also, if this -# is a link-local address, an interface should be set above. -;address = - -# If "port" is set, the server to bind to that port. By default, the -# server will listen to an arbitrary port. -;port = - -# If "debug" is true, the server will run in the foreground and print -# a lot of debugging information. -;debug = False - -# GnuTLS priority for the TLS handshake. See gnutls_priority_init(3). -;priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256 - -# Zeroconf service name. You need to change this if you for some -# reason want to run more than one server on the same *host*. -# If there are name collisions on the same *network*, the server will -# rename itself to "Mandos #2", etc. -;servicename = Mandos - -# Whether to provide a D-Bus system bus interface or not -;use_dbus = True - -# Whether to use IPv6. (Changing this is NOT recommended.) -;use_ipv6 = True - -# Whether to restore saved state on startup -;restore = True - -# The directory where state is saved -;statedir = /var/lib/mandos - -# Whether to run in the foreground -;foreground = False - -# File descriptor number to use for network socket -;socket = - -# Whether to use ZeroConf; if false, requires port or socket -;zeroconf = True +[server] +#interface = +#address = +#port = +#debug = +#priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP +#servicename = Mandos === renamed file 'mandos' => 'server.py' --- mandos 2019-11-03 19:09:41 +0000 +++ server.py 2008-07-31 19:48:05 +0000 @@ -1,2930 +1,645 @@ -#!/usr/bin/python3 -bI -# -*- mode: python; after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*- -# +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- +# # Mandos server - give out binary blobs to connecting clients. -# +# # This program is partly derived from an example program for an Avahi # service publisher, downloaded from # . This includes the -# methods "add", "remove", "server_state_changed", -# "entry_group_state_changed", "cleanup", and "activate" in the -# "AvahiService" class, and some lines in "main". -# +# following functions: "AvahiService.add", "AvahiService.remove", +# "server_state_changed", "entry_group_state_changed", and some lines +# in "main". +# # Everything else is -# Copyright © 2008-2019 Teddy Hogeborn -# Copyright © 2008-2019 Björn Påhlsson -# -# This file is part of Mandos. -# -# Mandos is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by +# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson +# +# 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. # -# Mandos is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of +# 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 Mandos. If not, see . -# -# Contact the authors at . -# - -from __future__ import (division, absolute_import, print_function, - unicode_literals) - -try: - from future_builtins import * -except ImportError: - pass - -try: - import SocketServer as socketserver -except ImportError: - import socketserver +# along with this program. If not, see . +# +# Contact the authors at . +# + +from __future__ import division + +import SocketServer import socket -import argparse +import select +from optparse import OptionParser import datetime import errno -try: - import ConfigParser as configparser -except ImportError: - import configparser +import gnutls.crypto +import gnutls.connection +import gnutls.errors +import gnutls.library.functions +import gnutls.library.constants +import gnutls.library.types +import ConfigParser import sys import re import os import signal +from sets import Set import subprocess import atexit import stat import logging import logging.handlers -import pwd -import contextlib -import struct -import fcntl -import functools -try: - import cPickle as pickle -except ImportError: - import pickle -import multiprocessing -import types -import binascii -import tempfile -import itertools -import collections -import codecs -import unittest import dbus -import dbus.service -import gi -from gi.repository import GLib +import gobject +import avahi from dbus.mainloop.glib import DBusGMainLoop import ctypes -import ctypes.util -import xml.dom.minidom -import inspect - -if sys.version_info.major == 2: - __metaclass__ = type - -# Show warnings by default -if not sys.warnoptions: - import warnings - warnings.simplefilter("default") - -# Try to find the value of SO_BINDTODEVICE: -try: - # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and - # newer, and it is also the most natural place for it: - SO_BINDTODEVICE = socket.SO_BINDTODEVICE -except AttributeError: - try: - # This is where SO_BINDTODEVICE was up to and including Python - # 2.6, and also 3.2: - from IN import SO_BINDTODEVICE - except ImportError: - # In Python 2.7 it seems to have been removed entirely. - # Try running the C preprocessor: - try: - cc = subprocess.Popen(["cc", "--language=c", "-E", - "/dev/stdin"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - stdout = cc.communicate( - "#include \nSO_BINDTODEVICE\n")[0] - SO_BINDTODEVICE = int(stdout.splitlines()[-1]) - except (OSError, ValueError, IndexError): - # No value found - SO_BINDTODEVICE = None - -if sys.version_info.major == 2: - str = unicode - -if sys.version_info < (3, 2): - configparser.Configparser = configparser.SafeConfigParser - -version = "1.8.9" -stored_state_file = "clients.pickle" - -logger = logging.getLogger() -logging.captureWarnings(True) # Show warnings via the logging system -syslogger = None - -try: - if_nametoindex = ctypes.cdll.LoadLibrary( - ctypes.util.find_library("c")).if_nametoindex -except (OSError, AttributeError): - - def if_nametoindex(interface): - "Get an interface index the hard way, i.e. using fcntl()" - SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h - with contextlib.closing(socket.socket()) as s: - ifreq = fcntl.ioctl(s, SIOCGIFINDEX, - struct.pack(b"16s16x", interface)) - interface_index = struct.unpack("I", ifreq[16:20])[0] - return interface_index - - -def copy_function(func): - """Make a copy of a function""" - if sys.version_info.major == 2: - return types.FunctionType(func.func_code, - func.func_globals, - func.func_name, - func.func_defaults, - func.func_closure) - else: - return types.FunctionType(func.__code__, - func.__globals__, - func.__name__, - func.__defaults__, - func.__closure__) - - -def initlogger(debug, level=logging.WARNING): - """init logger and add loglevel""" - - global syslogger - syslogger = (logging.handlers.SysLogHandler( - facility=logging.handlers.SysLogHandler.LOG_DAEMON, - address="/dev/log")) - syslogger.setFormatter(logging.Formatter - ('Mandos [%(process)d]: %(levelname)s:' - ' %(message)s')) - logger.addHandler(syslogger) - - if debug: - console = logging.StreamHandler() - console.setFormatter(logging.Formatter('%(asctime)s %(name)s' - ' [%(process)d]:' - ' %(levelname)s:' - ' %(message)s')) - logger.addHandler(console) - logger.setLevel(level) - - -class PGPError(Exception): - """Exception if encryption/decryption fails""" - pass - - -class PGPEngine: - """A simple class for OpenPGP symmetric encryption & decryption""" - - def __init__(self): - self.tempdir = tempfile.mkdtemp(prefix="mandos-") - self.gpg = "gpg" - try: - output = subprocess.check_output(["gpgconf"]) - for line in output.splitlines(): - name, text, path = line.split(b":") - if name == b"gpg": - self.gpg = path - break - except OSError as e: - if e.errno != errno.ENOENT: - raise - self.gnupgargs = ['--batch', - '--homedir', self.tempdir, - '--force-mdc', - '--quiet'] - # Only GPG version 1 has the --no-use-agent option. - if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"): - self.gnupgargs.append("--no-use-agent") - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self._cleanup() - return False - - def __del__(self): - self._cleanup() - - def _cleanup(self): - if self.tempdir is not None: - # Delete contents of tempdir - for root, dirs, files in os.walk(self.tempdir, - topdown=False): - for filename in files: - os.remove(os.path.join(root, filename)) - for dirname in dirs: - os.rmdir(os.path.join(root, dirname)) - # Remove tempdir - os.rmdir(self.tempdir) - self.tempdir = None - - def password_encode(self, password): - # Passphrase can not be empty and can not contain newlines or - # NUL bytes. So we prefix it and hex encode it. - encoded = b"mandos" + binascii.hexlify(password) - if len(encoded) > 2048: - # GnuPG can't handle long passwords, so encode differently - encoded = (b"mandos" + password.replace(b"\\", b"\\\\") - .replace(b"\n", b"\\n") - .replace(b"\0", b"\\x00")) - return encoded - - def encrypt(self, data, password): - passphrase = self.password_encode(password) - with tempfile.NamedTemporaryFile( - dir=self.tempdir) as passfile: - passfile.write(passphrase) - passfile.flush() - proc = subprocess.Popen([self.gpg, '--symmetric', - '--passphrase-file', - passfile.name] - + self.gnupgargs, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - ciphertext, err = proc.communicate(input=data) - if proc.returncode != 0: - raise PGPError(err) - return ciphertext - - def decrypt(self, data, password): - passphrase = self.password_encode(password) - with tempfile.NamedTemporaryFile( - dir=self.tempdir) as passfile: - passfile.write(passphrase) - passfile.flush() - proc = subprocess.Popen([self.gpg, '--decrypt', - '--passphrase-file', - passfile.name] - + self.gnupgargs, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - decrypted_plaintext, err = proc.communicate(input=data) - if proc.returncode != 0: - raise PGPError(err) - return decrypted_plaintext - - -# Pretend that we have an Avahi module -class avahi: - """This isn't so much a class as it is a module-like namespace.""" - IF_UNSPEC = -1 # avahi-common/address.h - PROTO_UNSPEC = -1 # avahi-common/address.h - PROTO_INET = 0 # avahi-common/address.h - PROTO_INET6 = 1 # avahi-common/address.h - DBUS_NAME = "org.freedesktop.Avahi" - DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup" - DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server" - DBUS_PATH_SERVER = "/" - - @staticmethod - def string_array_to_txt_array(t): - return dbus.Array((dbus.ByteArray(s.encode("utf-8")) - for s in t), signature="ay") - ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h - ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h - ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h - SERVER_INVALID = 0 # avahi-common/defs.h - SERVER_REGISTERING = 1 # avahi-common/defs.h - SERVER_RUNNING = 2 # avahi-common/defs.h - SERVER_COLLISION = 3 # avahi-common/defs.h - SERVER_FAILURE = 4 # avahi-common/defs.h + +# Brief description of the operation of this program: +# +# This server announces itself as a Zeroconf service. Connecting +# clients use the TLS protocol, with the unusual quirk that this +# server program acts as a TLS "client" while a connecting client acts +# as a TLS "server". The client (acting as a TLS "server") must +# supply an OpenPGP certificate, and the fingerprint of this +# certificate is used by this server to look up (in a list read from a +# file at start time) which binary blob to give the client. No other +# authentication or authorization is done by this server. + + +logger = logging.Logger('mandos') +syslogger = logging.handlers.SysLogHandler\ + (facility = logging.handlers.SysLogHandler.LOG_DAEMON) +syslogger.setFormatter(logging.Formatter\ + ('%(levelname)s: %(message)s')) +logger.addHandler(syslogger) +del syslogger class AvahiError(Exception): - def __init__(self, value, *args, **kwargs): + def __init__(self, value): self.value = value - return super(AvahiError, self).__init__(value, *args, - **kwargs) - + def __str__(self): + return repr(self.value) class AvahiServiceError(AvahiError): pass - class AvahiGroupError(AvahiError): pass -class AvahiService: - """An Avahi (Zeroconf) service. - - Attributes: +class AvahiService(object): + """ interface: integer; avahi.IF_UNSPEC or an interface index. Used to optionally bind to the specified interface. - name: string; Example: 'Mandos' - type: string; Example: '_mandos._tcp'. - See - port: integer; what port to announce - TXT: list of strings; TXT record for the service - domain: string; Domain to publish on, default to .local if empty. - host: string; Host to publish records for, default is localhost - max_renames: integer; maximum number of renames - rename_count: integer; counter so we only rename after collisions - a sensible number of times - group: D-Bus Entry Group - server: D-Bus Server - bus: dbus.SystemBus() + name = string; Example: "Mandos" + type = string; Example: "_mandos._tcp". + See + port = integer; what port to announce + TXT = list of strings; TXT record for the service + domain = string; Domain to publish on, default to .local if empty. + host = string; Host to publish records for, default to localhost + if empty. + max_renames = integer; maximum number of renames + rename_count = integer; counter so we only rename after collisions + a sensible number of times """ - - def __init__(self, - interface=avahi.IF_UNSPEC, - name=None, - servicetype=None, - port=None, - TXT=None, - domain="", - host="", - max_renames=32768, - protocol=avahi.PROTO_UNSPEC, - bus=None): + def __init__(self, interface = avahi.IF_UNSPEC, name = None, + type = None, port = None, TXT = None, domain = "", + host = "", max_renames = 12): + """An Avahi (Zeroconf) service. """ self.interface = interface self.name = name - self.type = servicetype + self.type = type self.port = port - self.TXT = TXT if TXT is not None else [] + if TXT is None: + self.TXT = [] + else: + self.TXT = TXT self.domain = domain self.host = host self.rename_count = 0 - self.max_renames = max_renames - self.protocol = protocol - self.group = None # our entry group - self.server = None - self.bus = bus - self.entry_group_state_changed_match = None - - def rename(self, remove=True): + def rename(self): """Derived from the Avahi example code""" if self.rename_count >= self.max_renames: - logger.critical("No suitable Zeroconf service name found" - " after %i retries, exiting.", - self.rename_count) + logger.critical(u"No suitable service name found after %i" + u" retries, exiting.", rename_count) raise AvahiServiceError("Too many renames") - self.name = str( - self.server.GetAlternativeServiceName(self.name)) + name = server.GetAlternativeServiceName(name) + logger.notice(u"Changing name to %r ...", name) + self.remove() + self.add() self.rename_count += 1 - logger.info("Changing Zeroconf service name to %r ...", - self.name) - if remove: - self.remove() - try: - self.add() - except dbus.exceptions.DBusException as error: - if (error.get_dbus_name() - == "org.freedesktop.Avahi.CollisionError"): - logger.info("Local Zeroconf service name collision.") - return self.rename(remove=False) - else: - logger.critical("D-Bus Exception", exc_info=error) - self.cleanup() - os._exit(1) - def remove(self): """Derived from the Avahi example code""" - if self.entry_group_state_changed_match is not None: - self.entry_group_state_changed_match.remove() - self.entry_group_state_changed_match = None - if self.group is not None: - self.group.Reset() - + if group is not None: + group.Reset() def add(self): """Derived from the Avahi example code""" - self.remove() - if self.group is None: - self.group = dbus.Interface( - self.bus.get_object(avahi.DBUS_NAME, - self.server.EntryGroupNew()), - avahi.DBUS_INTERFACE_ENTRY_GROUP) - self.entry_group_state_changed_match = ( - self.group.connect_to_signal( - 'StateChanged', self.entry_group_state_changed)) - logger.debug("Adding Zeroconf service '%s' of type '%s' ...", - self.name, self.type) - self.group.AddService( - self.interface, - self.protocol, - dbus.UInt32(0), # flags - self.name, self.type, - self.domain, self.host, - dbus.UInt16(self.port), - avahi.string_array_to_txt_array(self.TXT)) - self.group.Commit() - - def entry_group_state_changed(self, state, error): - """Derived from the Avahi example code""" - logger.debug("Avahi entry group state change: %i", state) - - if state == avahi.ENTRY_GROUP_ESTABLISHED: - logger.debug("Zeroconf service established.") - elif state == avahi.ENTRY_GROUP_COLLISION: - logger.info("Zeroconf service name collision.") - self.rename() - elif state == avahi.ENTRY_GROUP_FAILURE: - logger.critical("Avahi: Error in group state changed %s", - str(error)) - raise AvahiGroupError("State changed: {!s}".format(error)) - - def cleanup(self): - """Derived from the Avahi example code""" - if self.group is not None: - try: - self.group.Free() - except (dbus.exceptions.UnknownMethodException, - dbus.exceptions.DBusException): - pass - self.group = None - self.remove() - - def server_state_changed(self, state, error=None): - """Derived from the Avahi example code""" - logger.debug("Avahi server state change: %i", state) - bad_states = { - avahi.SERVER_INVALID: "Zeroconf server invalid", - avahi.SERVER_REGISTERING: None, - avahi.SERVER_COLLISION: "Zeroconf server name collision", - avahi.SERVER_FAILURE: "Zeroconf server failure", - } - if state in bad_states: - if bad_states[state] is not None: - if error is None: - logger.error(bad_states[state]) - else: - logger.error(bad_states[state] + ": %r", error) - self.cleanup() - elif state == avahi.SERVER_RUNNING: - try: - self.add() - except dbus.exceptions.DBusException as error: - if (error.get_dbus_name() - == "org.freedesktop.Avahi.CollisionError"): - logger.info("Local Zeroconf service name" - " collision.") - return self.rename(remove=False) - else: - logger.critical("D-Bus Exception", exc_info=error) - self.cleanup() - os._exit(1) - else: - if error is None: - logger.debug("Unknown state: %r", state) - else: - logger.debug("Unknown state: %r: %r", state, error) - - def activate(self): - """Derived from the Avahi example code""" - if self.server is None: - self.server = dbus.Interface( - self.bus.get_object(avahi.DBUS_NAME, - avahi.DBUS_PATH_SERVER, - follow_name_owner_changes=True), - avahi.DBUS_INTERFACE_SERVER) - self.server.connect_to_signal("StateChanged", - self.server_state_changed) - self.server_state_changed(self.server.GetState()) - - -class AvahiServiceToSyslog(AvahiService): - def rename(self, *args, **kwargs): - """Add the new name to the syslog messages""" - ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs) - syslogger.setFormatter(logging.Formatter( - 'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s' - .format(self.name))) - return ret - - -# Pretend that we have a GnuTLS module -class gnutls: - """This isn't so much a class as it is a module-like namespace.""" - - library = ctypes.util.find_library("gnutls") - if library is None: - library = ctypes.util.find_library("gnutls-deb0") - _library = ctypes.cdll.LoadLibrary(library) - del library - - # Unless otherwise indicated, the constants and types below are - # all from the gnutls/gnutls.h C header file. - - # Constants - E_SUCCESS = 0 - E_INTERRUPTED = -52 - E_AGAIN = -28 - CRT_OPENPGP = 2 - CRT_RAWPK = 3 - CLIENT = 2 - SHUT_RDWR = 0 - CRD_CERTIFICATE = 1 - E_NO_CERTIFICATE_FOUND = -49 - X509_FMT_DER = 0 - NO_TICKETS = 1<<10 - ENABLE_RAWPK = 1<<18 - CTYPE_PEERS = 3 - KEYID_USE_SHA256 = 1 # gnutls/x509.h - OPENPGP_FMT_RAW = 0 # gnutls/openpgp.h - - # Types - class session_int(ctypes.Structure): - _fields_ = [] - session_t = ctypes.POINTER(session_int) - - class certificate_credentials_st(ctypes.Structure): - _fields_ = [] - certificate_credentials_t = ctypes.POINTER( - certificate_credentials_st) - certificate_type_t = ctypes.c_int - - class datum_t(ctypes.Structure): - _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)), - ('size', ctypes.c_uint)] - - class openpgp_crt_int(ctypes.Structure): - _fields_ = [] - openpgp_crt_t = ctypes.POINTER(openpgp_crt_int) - openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h - log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) - credentials_type_t = ctypes.c_int - transport_ptr_t = ctypes.c_void_p - close_request_t = ctypes.c_int - - # Exceptions - class Error(Exception): - def __init__(self, message=None, code=None, args=()): - # Default usage is by a message string, but if a return - # code is passed, convert it to a string with - # gnutls.strerror() - self.code = code - if message is None and code is not None: - message = gnutls.strerror(code) - return super(gnutls.Error, self).__init__( - message, *args) - - class CertificateSecurityError(Error): - pass - - # Classes - class Credentials: - def __init__(self): - self._c_object = gnutls.certificate_credentials_t() - gnutls.certificate_allocate_credentials( - ctypes.byref(self._c_object)) - self.type = gnutls.CRD_CERTIFICATE - - def __del__(self): - gnutls.certificate_free_credentials(self._c_object) - - class ClientSession: - def __init__(self, socket, credentials=None): - self._c_object = gnutls.session_t() - gnutls_flags = gnutls.CLIENT - if gnutls.check_version(b"3.5.6"): - gnutls_flags |= gnutls.NO_TICKETS - if gnutls.has_rawpk: - gnutls_flags |= gnutls.ENABLE_RAWPK - gnutls.init(ctypes.byref(self._c_object), gnutls_flags) - del gnutls_flags - gnutls.set_default_priority(self._c_object) - gnutls.transport_set_ptr(self._c_object, socket.fileno()) - gnutls.handshake_set_private_extensions(self._c_object, - True) - self.socket = socket - if credentials is None: - credentials = gnutls.Credentials() - gnutls.credentials_set(self._c_object, credentials.type, - ctypes.cast(credentials._c_object, - ctypes.c_void_p)) - self.credentials = credentials - - def __del__(self): - gnutls.deinit(self._c_object) - - def handshake(self): - return gnutls.handshake(self._c_object) - - def send(self, data): - data = bytes(data) - data_len = len(data) - while data_len > 0: - data_len -= gnutls.record_send(self._c_object, - data[-data_len:], - data_len) - - def bye(self): - return gnutls.bye(self._c_object, gnutls.SHUT_RDWR) - - # Error handling functions - def _error_code(result): - """A function to raise exceptions on errors, suitable - for the 'restype' attribute on ctypes functions""" - if result >= 0: - return result - if result == gnutls.E_NO_CERTIFICATE_FOUND: - raise gnutls.CertificateSecurityError(code=result) - raise gnutls.Error(code=result) - - def _retry_on_error(result, func, arguments): - """A function to retry on some errors, suitable - for the 'errcheck' attribute on ctypes functions""" - while result < 0: - if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN): - return _error_code(result) - result = func(*arguments) - return result - - # Unless otherwise indicated, the function declarations below are - # all from the gnutls/gnutls.h C header file. - - # Functions - priority_set_direct = _library.gnutls_priority_set_direct - priority_set_direct.argtypes = [session_t, ctypes.c_char_p, - ctypes.POINTER(ctypes.c_char_p)] - priority_set_direct.restype = _error_code - - init = _library.gnutls_init - init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int] - init.restype = _error_code - - set_default_priority = _library.gnutls_set_default_priority - set_default_priority.argtypes = [session_t] - set_default_priority.restype = _error_code - - record_send = _library.gnutls_record_send - record_send.argtypes = [session_t, ctypes.c_void_p, - ctypes.c_size_t] - record_send.restype = ctypes.c_ssize_t - record_send.errcheck = _retry_on_error - - certificate_allocate_credentials = ( - _library.gnutls_certificate_allocate_credentials) - certificate_allocate_credentials.argtypes = [ - ctypes.POINTER(certificate_credentials_t)] - certificate_allocate_credentials.restype = _error_code - - certificate_free_credentials = ( - _library.gnutls_certificate_free_credentials) - certificate_free_credentials.argtypes = [ - certificate_credentials_t] - certificate_free_credentials.restype = None - - handshake_set_private_extensions = ( - _library.gnutls_handshake_set_private_extensions) - handshake_set_private_extensions.argtypes = [session_t, - ctypes.c_int] - handshake_set_private_extensions.restype = None - - credentials_set = _library.gnutls_credentials_set - credentials_set.argtypes = [session_t, credentials_type_t, - ctypes.c_void_p] - credentials_set.restype = _error_code - - strerror = _library.gnutls_strerror - strerror.argtypes = [ctypes.c_int] - strerror.restype = ctypes.c_char_p - - certificate_type_get = _library.gnutls_certificate_type_get - certificate_type_get.argtypes = [session_t] - certificate_type_get.restype = _error_code - - certificate_get_peers = _library.gnutls_certificate_get_peers - certificate_get_peers.argtypes = [session_t, - ctypes.POINTER(ctypes.c_uint)] - certificate_get_peers.restype = ctypes.POINTER(datum_t) - - global_set_log_level = _library.gnutls_global_set_log_level - global_set_log_level.argtypes = [ctypes.c_int] - global_set_log_level.restype = None - - global_set_log_function = _library.gnutls_global_set_log_function - global_set_log_function.argtypes = [log_func] - global_set_log_function.restype = None - - deinit = _library.gnutls_deinit - deinit.argtypes = [session_t] - deinit.restype = None - - handshake = _library.gnutls_handshake - handshake.argtypes = [session_t] - handshake.restype = _error_code - handshake.errcheck = _retry_on_error - - transport_set_ptr = _library.gnutls_transport_set_ptr - transport_set_ptr.argtypes = [session_t, transport_ptr_t] - transport_set_ptr.restype = None - - bye = _library.gnutls_bye - bye.argtypes = [session_t, close_request_t] - bye.restype = _error_code - bye.errcheck = _retry_on_error - - check_version = _library.gnutls_check_version - check_version.argtypes = [ctypes.c_char_p] - check_version.restype = ctypes.c_char_p - - _need_version = b"3.3.0" - if check_version(_need_version) is None: - raise self.Error("Needs GnuTLS {} or later" - .format(_need_version)) - - _tls_rawpk_version = b"3.6.6" - has_rawpk = bool(check_version(_tls_rawpk_version)) - - if has_rawpk: - # Types - class pubkey_st(ctypes.Structure): - _fields = [] - pubkey_t = ctypes.POINTER(pubkey_st) - - x509_crt_fmt_t = ctypes.c_int - - # All the function declarations below are from gnutls/abstract.h - pubkey_init = _library.gnutls_pubkey_init - pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)] - pubkey_init.restype = _error_code - - pubkey_import = _library.gnutls_pubkey_import - pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t), - x509_crt_fmt_t] - pubkey_import.restype = _error_code - - pubkey_get_key_id = _library.gnutls_pubkey_get_key_id - pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int, - ctypes.POINTER(ctypes.c_ubyte), - ctypes.POINTER(ctypes.c_size_t)] - pubkey_get_key_id.restype = _error_code - - pubkey_deinit = _library.gnutls_pubkey_deinit - pubkey_deinit.argtypes = [pubkey_t] - pubkey_deinit.restype = None - else: - # All the function declarations below are from gnutls/openpgp.h - - openpgp_crt_init = _library.gnutls_openpgp_crt_init - openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)] - openpgp_crt_init.restype = _error_code - - openpgp_crt_import = _library.gnutls_openpgp_crt_import - openpgp_crt_import.argtypes = [openpgp_crt_t, - ctypes.POINTER(datum_t), - openpgp_crt_fmt_t] - openpgp_crt_import.restype = _error_code - - openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self - openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint, - ctypes.POINTER(ctypes.c_uint)] - openpgp_crt_verify_self.restype = _error_code - - openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit - openpgp_crt_deinit.argtypes = [openpgp_crt_t] - openpgp_crt_deinit.restype = None - - openpgp_crt_get_fingerprint = ( - _library.gnutls_openpgp_crt_get_fingerprint) - openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t, - ctypes.c_void_p, - ctypes.POINTER( - ctypes.c_size_t)] - openpgp_crt_get_fingerprint.restype = _error_code - - if check_version(b"3.6.4"): - certificate_type_get2 = _library.gnutls_certificate_type_get2 - certificate_type_get2.argtypes = [session_t, ctypes.c_int] - certificate_type_get2.restype = _error_code - - # Remove non-public functions - del _error_code, _retry_on_error - - -def call_pipe(connection, # : multiprocessing.Connection - func, *args, **kwargs): - """This function is meant to be called by multiprocessing.Process - - This function runs func(*args, **kwargs), and writes the resulting - return value on the provided multiprocessing.Connection. - """ - connection.send(func(*args, **kwargs)) - connection.close() - - -class Client: + global group + if group is None: + group = dbus.Interface\ + (bus.get_object(avahi.DBUS_NAME, + server.EntryGroupNew()), + avahi.DBUS_INTERFACE_ENTRY_GROUP) + group.connect_to_signal('StateChanged', + entry_group_state_changed) + logger.debug(u"Adding service '%s' of type '%s' ...", + service.name, service.type) + group.AddService( + self.interface, # interface + avahi.PROTO_INET6, # protocol + dbus.UInt32(0), # flags + self.name, self.type, + self.domain, self.host, + dbus.UInt16(self.port), + avahi.string_array_to_txt_array(self.TXT)) + group.Commit() + +# From the Avahi example code: +group = None # our entry group +# End of Avahi example code + + +class Client(object): """A representation of a client host served by this server. - Attributes: - approved: bool(); 'None' if not yet approved/disapproved - approval_delay: datetime.timedelta(); Time to wait for approval - approval_duration: datetime.timedelta(); Duration of one approval - checker: multiprocessing.Process(); a running checker process used - to see if the client lives. 'None' if no process is - running. - checker_callback_tag: a GLib event source tag, or None - checker_command: string; External command which is run to check - if client lives. %() expansions are done at + name: string; from the config file, used in log messages + fingerprint: string (40 or 32 hexadecimal digits); used to + uniquely identify the client + secret: bytestring; sent verbatim (over TLS) to client + fqdn: string (FQDN); available for use by the checker command + created: datetime.datetime(); object creation, not client host + last_checked_ok: datetime.datetime() or None if not yet checked OK + timeout: datetime.timedelta(); How long from last_checked_ok + until this client is invalid + interval: datetime.timedelta(); How often to start a new checker + stop_hook: If set, called by stop() as stop_hook(self) + checker: subprocess.Popen(); a running checker process used + to see if the client lives. + 'None' if no process is running. + checker_initiator_tag: a gobject event source tag, or None + stop_initiator_tag: - '' - + checker_callback_tag: - '' - + checker_command: string; External command which is run to check if + client lives. %() expansions are done at runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. - checker_initiator_tag: a GLib event source tag, or None - created: datetime.datetime(); (UTC) object creation - client_structure: Object describing what attributes a client has - and is used for storing the client at exit - current_checker_command: string; current running checker_command - disable_initiator_tag: a GLib event source tag, or None - enabled: bool() - fingerprint: string (40 or 32 hexadecimal digits); used to - uniquely identify an OpenPGP client - key_id: string (64 hexadecimal digits); used to uniquely identify - a client using raw public keys - host: string; available for use by the checker command - interval: datetime.timedelta(); How often to start a new checker - last_approval_request: datetime.datetime(); (UTC) or None - last_checked_ok: datetime.datetime(); (UTC) or None - last_checker_status: integer between 0 and 255 reflecting exit - status of last checker. -1 reflects crashed - checker, -2 means no checker completed yet. - last_checker_signal: The signal which killed the last checker, if - last_checker_status is -1 - last_enabled: datetime.datetime(); (UTC) or None - name: string; from the config file, used in log messages and - D-Bus identifiers - secret: bytestring; sent verbatim (over TLS) to client - timeout: datetime.timedelta(); How long from last_checked_ok - until this client is disabled - extended_timeout: extra long timeout when secret has been sent - runtime_expansions: Allowed attributes for runtime expansion. - expires: datetime.datetime(); time (UTC) when a client will be - disabled, or None - server_settings: The server_settings dict from main() + Private attibutes: + _timeout: Real variable for 'timeout' + _interval: Real variable for 'interval' + _timeout_milliseconds: Used when calling gobject.timeout_add() + _interval_milliseconds: - '' - """ - - runtime_expansions = ("approval_delay", "approval_duration", - "created", "enabled", "expires", "key_id", - "fingerprint", "host", "interval", - "last_approval_request", "last_checked_ok", - "last_enabled", "name", "timeout") - client_defaults = { - "timeout": "PT5M", - "extended_timeout": "PT15M", - "interval": "PT2M", - "checker": "fping -q -- %%(host)s", - "host": "", - "approval_delay": "PT0S", - "approval_duration": "PT1S", - "approved_by_default": "True", - "enabled": "True", - } - - @staticmethod - def config_parser(config): - """Construct a new dict of client settings of this form: - { client_name: {setting_name: value, ...}, ...} - with exceptions for any special settings as defined above. - NOTE: Must be a pure function. Must return the same result - value given the same arguments. - """ - settings = {} - for client_name in config.sections(): - section = dict(config.items(client_name)) - client = settings[client_name] = {} - - client["host"] = section["host"] - # Reformat values from string types to Python types - client["approved_by_default"] = config.getboolean( - client_name, "approved_by_default") - client["enabled"] = config.getboolean(client_name, - "enabled") - - # Uppercase and remove spaces from key_id and fingerprint - # for later comparison purposes with return value from the - # key_id() and fingerprint() functions - client["key_id"] = (section.get("key_id", "").upper() - .replace(" ", "")) - client["fingerprint"] = (section["fingerprint"].upper() - .replace(" ", "")) - if "secret" in section: - client["secret"] = codecs.decode(section["secret"] - .encode("utf-8"), - "base64") - elif "secfile" in section: - with open(os.path.expanduser(os.path.expandvars - (section["secfile"])), - "rb") as secfile: - client["secret"] = secfile.read() - else: - raise TypeError("No secret or secfile for section {}" - .format(section)) - client["timeout"] = string_to_delta(section["timeout"]) - client["extended_timeout"] = string_to_delta( - section["extended_timeout"]) - client["interval"] = string_to_delta(section["interval"]) - client["approval_delay"] = string_to_delta( - section["approval_delay"]) - client["approval_duration"] = string_to_delta( - section["approval_duration"]) - client["checker_command"] = section["checker"] - client["last_approval_request"] = None - client["last_checked_ok"] = None - client["last_checker_status"] = -2 - - return settings - - def __init__(self, settings, name=None, server_settings=None): + def _set_timeout(self, timeout): + "Setter function for 'timeout' attribute" + self._timeout = timeout + self._timeout_milliseconds = ((self.timeout.days + * 24 * 60 * 60 * 1000) + + (self.timeout.seconds * 1000) + + (self.timeout.microseconds + // 1000)) + timeout = property(lambda self: self._timeout, + _set_timeout) + del _set_timeout + def _set_interval(self, interval): + "Setter function for 'interval' attribute" + self._interval = interval + self._interval_milliseconds = ((self.interval.days + * 24 * 60 * 60 * 1000) + + (self.interval.seconds + * 1000) + + (self.interval.microseconds + // 1000)) + interval = property(lambda self: self._interval, + _set_interval) + del _set_interval + def __init__(self, name=None, stop_hook=None, fingerprint=None, + secret=None, secfile=None, fqdn=None, timeout=None, + interval=-1, checker=None): + """Note: the 'checker' argument sets the 'checker_command' + attribute and not the 'checker' attribute..""" self.name = name - if server_settings is None: - server_settings = {} - self.server_settings = server_settings - # adding all client settings - for setting, value in settings.items(): - setattr(self, setting, value) - - if self.enabled: - if not hasattr(self, "last_enabled"): - self.last_enabled = datetime.datetime.utcnow() - if not hasattr(self, "expires"): - self.expires = (datetime.datetime.utcnow() - + self.timeout) + logger.debug(u"Creating client %r", self.name) + # Uppercase and remove spaces from fingerprint + # for later comparison purposes with return value of + # the fingerprint() function + self.fingerprint = fingerprint.upper().replace(u" ", u"") + logger.debug(u" Fingerprint: %s", self.fingerprint) + if secret: + self.secret = secret.decode(u"base64") + elif secfile: + sf = open(secfile) + self.secret = sf.read() + sf.close() else: - self.last_enabled = None - self.expires = None - - logger.debug("Creating client %r", self.name) - logger.debug(" Key ID: %s", self.key_id) - logger.debug(" Fingerprint: %s", self.fingerprint) - self.created = settings.get("created", - datetime.datetime.utcnow()) - - # attributes specific for this server instance + raise TypeError(u"No secret or secfile for client %s" + % self.name) + self.fqdn = fqdn + self.created = datetime.datetime.now() + self.last_checked_ok = None + self.timeout = string_to_delta(timeout) + self.interval = string_to_delta(interval) + self.stop_hook = stop_hook self.checker = None self.checker_initiator_tag = None - self.disable_initiator_tag = None + self.stop_initiator_tag = None self.checker_callback_tag = None - self.current_checker_command = None - self.approved = None - self.approvals_pending = 0 - self.changedstate = multiprocessing_manager.Condition( - multiprocessing_manager.Lock()) - self.client_structure = [attr - for attr in self.__dict__.keys() - if not attr.startswith("_")] - self.client_structure.append("client_structure") - - for name, t in inspect.getmembers( - type(self), lambda obj: isinstance(obj, property)): - if not name.startswith("_"): - self.client_structure.append(name) - - # Send notice to process children that client state has changed - def send_changedstate(self): - with self.changedstate: - self.changedstate.notify_all() - - def enable(self): + self.check_command = checker + def start(self): """Start this client's checker and timeout hooks""" - if getattr(self, "enabled", False): - # Already enabled - return - self.expires = datetime.datetime.utcnow() + self.timeout - self.enabled = True - self.last_enabled = datetime.datetime.utcnow() - self.init_checker() - self.send_changedstate() - - def disable(self, quiet=True): - """Disable this client.""" - if not getattr(self, "enabled", False): - return False - if not quiet: - logger.info("Disabling client %s", self.name) - if getattr(self, "disable_initiator_tag", None) is not None: - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = None - self.expires = None - if getattr(self, "checker_initiator_tag", None) is not None: - GLib.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = None - self.stop_checker() - self.enabled = False - if not quiet: - self.send_changedstate() - # Do not run this again if called by a GLib.timeout_add - return False - - def __del__(self): - self.disable() - - def init_checker(self): # Schedule a new checker to be started an 'interval' from now, # and every interval from then on. - if self.checker_initiator_tag is not None: - GLib.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = GLib.timeout_add( - int(self.interval.total_seconds() * 1000), - self.start_checker) - # Schedule a disable() when 'timeout' has passed - if self.disable_initiator_tag is not None: - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = GLib.timeout_add( - int(self.timeout.total_seconds() * 1000), self.disable) + self.checker_initiator_tag = gobject.timeout_add\ + (self._interval_milliseconds, + self.start_checker) # Also start a new checker *right now*. self.start_checker() - - def checker_callback(self, source, condition, connection, - command): + # Schedule a stop() when 'timeout' has passed + self.stop_initiator_tag = gobject.timeout_add\ + (self._timeout_milliseconds, + self.stop) + def stop(self): + """Stop this client. + The possibility that a client might be restarted is left open, + but not currently used.""" + # If this client doesn't have a secret, it is already stopped. + if self.secret: + logger.debug(u"Stopping client %s", self.name) + self.secret = None + else: + return False + if getattr(self, "stop_initiator_tag", False): + gobject.source_remove(self.stop_initiator_tag) + self.stop_initiator_tag = None + if getattr(self, "checker_initiator_tag", False): + gobject.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = None + self.stop_checker() + if self.stop_hook: + self.stop_hook(self) + # Do not run this again if called by a gobject.timeout_add + return False + def __del__(self): + self.stop_hook = None + self.stop() + def checker_callback(self, pid, condition): """The checker has completed, so take appropriate actions.""" - # Read return code from connection (see call_pipe) - returncode = connection.recv() - connection.close() - if self.checker is not None: - self.checker.join() + now = datetime.datetime.now() self.checker_callback_tag = None self.checker = None - - if returncode >= 0: - self.last_checker_status = returncode - self.last_checker_signal = None - if self.last_checker_status == 0: - logger.info("Checker for %(name)s succeeded", - vars(self)) - self.checked_ok() - else: - logger.info("Checker for %(name)s failed", vars(self)) - else: - self.last_checker_status = -1 - self.last_checker_signal = -returncode - logger.warning("Checker for %(name)s crashed?", + if os.WIFEXITED(condition) \ + and (os.WEXITSTATUS(condition) == 0): + logger.debug(u"Checker for %(name)s succeeded", + vars(self)) + self.last_checked_ok = now + gobject.source_remove(self.stop_initiator_tag) + self.stop_initiator_tag = gobject.timeout_add\ + (self._timeout_milliseconds, + self.stop) + elif not os.WIFEXITED(condition): + logger.warning(u"Checker for %(name)s crashed?", vars(self)) - return False - - def checked_ok(self): - """Assert that the client has been seen, alive and well.""" - self.last_checked_ok = datetime.datetime.utcnow() - self.last_checker_status = 0 - self.last_checker_signal = None - self.bump_timeout() - - def bump_timeout(self, timeout=None): - """Bump up the timeout for this client.""" - if timeout is None: - timeout = self.timeout - if self.disable_initiator_tag is not None: - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = None - if getattr(self, "enabled", False): - self.disable_initiator_tag = GLib.timeout_add( - int(timeout.total_seconds() * 1000), self.disable) - self.expires = datetime.datetime.utcnow() + timeout - - def need_approval(self): - self.last_approval_request = datetime.datetime.utcnow() - + else: + logger.debug(u"Checker for %(name)s failed", + vars(self)) def start_checker(self): """Start a new checker subprocess if one is not running. - If a checker already exists, leave it running and do nothing.""" # The reason for not killing a running checker is that if we - # did that, and if a checker (for some reason) started running - # slowly and taking more than 'interval' time, then the client - # would inevitably timeout, since no checker would get a - # chance to run to completion. If we instead leave running + # did that, then if a checker (for some reason) started + # running slowly and taking more than 'interval' time, the + # client would inevitably timeout, since no checker would get + # a chance to run to completion. If we instead leave running # checkers alone, the checker would have to take more time - # than 'timeout' for the client to be disabled, which is as it - # should be. - - if self.checker is not None and not self.checker.is_alive(): - logger.warning("Checker was not alive; joining") - self.checker.join() - self.checker = None - # Start a new checker if needed + # than 'timeout' for the client to be declared invalid, which + # is as it should be. if self.checker is None: - # Escape attributes for the shell - escaped_attrs = { - attr: re.escape(str(getattr(self, attr))) - for attr in self.runtime_expansions} - try: - command = self.checker_command % escaped_attrs - except TypeError as error: - logger.error('Could not format string "%s"', - self.checker_command, - exc_info=error) - return True # Try again later - self.current_checker_command = command - logger.info("Starting checker %r for %s", command, - self.name) - # We don't need to redirect stdout and stderr, since - # in normal mode, that is already done by daemon(), - # and in debug mode we don't want to. (Stdin is - # always replaced by /dev/null.) - # The exception is when not debugging but nevertheless - # running in the foreground; use the previously - # created wnull. - popen_args = {"close_fds": True, - "shell": True, - "cwd": "/"} - if (not self.server_settings["debug"] - and self.server_settings["foreground"]): - popen_args.update({"stdout": wnull, - "stderr": wnull}) - pipe = multiprocessing.Pipe(duplex=False) - self.checker = multiprocessing.Process( - target=call_pipe, - args=(pipe[1], subprocess.call, command), - kwargs=popen_args) - self.checker.start() - self.checker_callback_tag = GLib.io_add_watch( - GLib.IOChannel.unix_new(pipe[0].fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN, - self.checker_callback, pipe[0], command) - # Re-run this periodically if run by GLib.timeout_add + try: + # In case check_command has exactly one % operator + command = self.check_command % self.fqdn + except TypeError: + # Escape attributes for the shell + escaped_attrs = dict((key, re.escape(str(val))) + for key, val in + vars(self).iteritems()) + try: + command = self.check_command % escaped_attrs + except TypeError, error: + logger.error(u'Could not format string "%s":' + u' %s', self.check_command, error) + return True # Try again later + try: + logger.debug(u"Starting checker %r for %s", + command, self.name) + self.checker = subprocess.Popen(command, + close_fds=True, + shell=True, cwd="/") + self.checker_callback_tag = gobject.child_watch_add\ + (self.checker.pid, + self.checker_callback) + except subprocess.OSError, error: + logger.error(u"Failed to start subprocess: %s", + error) + # Re-run this periodically if run by gobject.timeout_add return True - def stop_checker(self): """Force the checker process, if any, to stop.""" if self.checker_callback_tag: - GLib.source_remove(self.checker_callback_tag) + gobject.source_remove(self.checker_callback_tag) self.checker_callback_tag = None if getattr(self, "checker", None) is None: return logger.debug("Stopping checker for %(name)s", vars(self)) - self.checker.terminate() + try: + os.kill(self.checker.pid, signal.SIGTERM) + #os.sleep(0.5) + #if self.checker.poll() is None: + # os.kill(self.checker.pid, signal.SIGKILL) + except OSError, error: + if error.errno != errno.ESRCH: # No such process + raise self.checker = None - - -def dbus_service_property(dbus_interface, - signature="v", - access="readwrite", - byte_arrays=False): - """Decorators for marking methods of a DBusObjectWithProperties to - become properties on the D-Bus. - - The decorated method will be called with no arguments by "Get" - and with one argument by "Set". - - The parameters, where they are supported, are the same as - dbus.service.method, except there is only "signature", since the - type from Get() and the type sent to Set() is the same. - """ - # Encoding deeply encoded byte arrays is not supported yet by the - # "Set" method, so we fail early here: - if byte_arrays and signature != "ay": - raise ValueError("Byte arrays not supported for non-'ay'" - " signature {!r}".format(signature)) - - def decorator(func): - func._dbus_is_property = True - func._dbus_interface = dbus_interface - func._dbus_signature = signature - func._dbus_access = access - func._dbus_name = func.__name__ - if func._dbus_name.endswith("_dbus_property"): - func._dbus_name = func._dbus_name[:-14] - func._dbus_get_args_options = {'byte_arrays': byte_arrays} - return func - - return decorator - - -def dbus_interface_annotations(dbus_interface): - """Decorator for marking functions returning interface annotations - - Usage: - - @dbus_interface_annotations("org.example.Interface") - def _foo(self): # Function name does not matter - return {"org.freedesktop.DBus.Deprecated": "true", - "org.freedesktop.DBus.Property.EmitsChangedSignal": - "false"} - """ - - def decorator(func): - func._dbus_is_interface = True - func._dbus_interface = dbus_interface - func._dbus_name = dbus_interface - return func - - return decorator - - -def dbus_annotations(annotations): - """Decorator to annotate D-Bus methods, signals or properties - Usage: - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true", - "org.freedesktop.DBus.Property." - "EmitsChangedSignal": "false"}) - @dbus_service_property("org.example.Interface", signature="b", - access="r") - def Property_dbus_property(self): - return dbus.Boolean(False) - - See also the DBusObjectWithAnnotations class. - """ - - def decorator(func): - func._dbus_annotations = annotations - return func - - return decorator - - -class DBusPropertyException(dbus.exceptions.DBusException): - """A base class for D-Bus property-related exceptions - """ - pass - - -class DBusPropertyAccessException(DBusPropertyException): - """A property's access permissions disallows an operation. - """ - pass - - -class DBusPropertyNotFound(DBusPropertyException): - """An attempt was made to access a non-existing property. - """ - pass - - -class DBusObjectWithAnnotations(dbus.service.Object): - """A D-Bus object with annotations. - - Classes inheriting from this can use the dbus_annotations - decorator to add annotations to methods or signals. - """ - - @staticmethod - def _is_dbus_thing(thing): - """Returns a function testing if an attribute is a D-Bus thing - - If called like _is_dbus_thing("method") it returns a function - suitable for use as predicate to inspect.getmembers(). - """ - return lambda obj: getattr(obj, "_dbus_is_{}".format(thing), - False) - - def _get_all_dbus_things(self, thing): - """Returns a generator of (name, attribute) pairs - """ - return ((getattr(athing.__get__(self), "_dbus_name", name), - athing.__get__(self)) - for cls in self.__class__.__mro__ - for name, athing in - inspect.getmembers(cls, self._is_dbus_thing(thing))) - - @dbus.service.method(dbus.INTROSPECTABLE_IFACE, - out_signature="s", - path_keyword='object_path', - connection_keyword='connection') - def Introspect(self, object_path, connection): - """Overloading of standard D-Bus method. - - Inserts annotation tags on methods and signals. - """ - xmlstring = dbus.service.Object.Introspect(self, object_path, - connection) - try: - document = xml.dom.minidom.parseString(xmlstring) - - for if_tag in document.getElementsByTagName("interface"): - # Add annotation tags - for typ in ("method", "signal"): - for tag in if_tag.getElementsByTagName(typ): - annots = dict() - for name, prop in (self. - _get_all_dbus_things(typ)): - if (name == tag.getAttribute("name") - and prop._dbus_interface - == if_tag.getAttribute("name")): - annots.update(getattr( - prop, "_dbus_annotations", {})) - for name, value in annots.items(): - ann_tag = document.createElement( - "annotation") - ann_tag.setAttribute("name", name) - ann_tag.setAttribute("value", value) - tag.appendChild(ann_tag) - # Add interface annotation tags - for annotation, value in dict( - itertools.chain.from_iterable( - annotations().items() - for name, annotations - in self._get_all_dbus_things("interface") - if name == if_tag.getAttribute("name") - )).items(): - ann_tag = document.createElement("annotation") - ann_tag.setAttribute("name", annotation) - ann_tag.setAttribute("value", value) - if_tag.appendChild(ann_tag) - # Fix argument name for the Introspect method itself - if (if_tag.getAttribute("name") - == dbus.INTROSPECTABLE_IFACE): - for cn in if_tag.getElementsByTagName("method"): - if cn.getAttribute("name") == "Introspect": - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute("name", - "xml_data") - xmlstring = document.toxml("utf-8") - document.unlink() - except (AttributeError, xml.dom.DOMException, - xml.parsers.expat.ExpatError) as error: - logger.error("Failed to override Introspection method", - exc_info=error) - return xmlstring - - -class DBusObjectWithProperties(DBusObjectWithAnnotations): - """A D-Bus object with properties. - - Classes inheriting from this can use the dbus_service_property - decorator to expose methods as D-Bus properties. It exposes the - standard Get(), Set(), and GetAll() methods on the D-Bus. - """ - - def _get_dbus_property(self, interface_name, property_name): - """Returns a bound method if one exists which is a D-Bus - property with the specified name and interface. - """ - for cls in self.__class__.__mro__: - for name, value in inspect.getmembers( - cls, self._is_dbus_thing("property")): - if (value._dbus_name == property_name - and value._dbus_interface == interface_name): - return value.__get__(self) - - # No such property - raise DBusPropertyNotFound("{}:{}.{}".format( - self.dbus_object_path, interface_name, property_name)) - - @classmethod - def _get_all_interface_names(cls): - """Get a sequence of all interfaces supported by an object""" - return (name for name in set(getattr(getattr(x, attr), - "_dbus_interface", None) - for x in (inspect.getmro(cls)) - for attr in dir(x)) - if name is not None) - - @dbus.service.method(dbus.PROPERTIES_IFACE, - in_signature="ss", - out_signature="v") - def Get(self, interface_name, property_name): - """Standard D-Bus property Get() method, see D-Bus standard. - """ - prop = self._get_dbus_property(interface_name, property_name) - if prop._dbus_access == "write": - raise DBusPropertyAccessException(property_name) - value = prop() - if not hasattr(value, "variant_level"): - return value - return type(value)(value, variant_level=value.variant_level+1) - - @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv") - def Set(self, interface_name, property_name, value): - """Standard D-Bus property Set() method, see D-Bus standard. - """ - prop = self._get_dbus_property(interface_name, property_name) - if prop._dbus_access == "read": - raise DBusPropertyAccessException(property_name) - if prop._dbus_get_args_options["byte_arrays"]: - # The byte_arrays option is not supported yet on - # signatures other than "ay". - if prop._dbus_signature != "ay": - raise ValueError("Byte arrays not supported for non-" - "'ay' signature {!r}" - .format(prop._dbus_signature)) - value = dbus.ByteArray(b''.join(chr(byte) - for byte in value)) - prop(value) - - @dbus.service.method(dbus.PROPERTIES_IFACE, - in_signature="s", - out_signature="a{sv}") - def GetAll(self, interface_name): - """Standard D-Bus property GetAll() method, see D-Bus - standard. - - Note: Will not include properties with access="write". - """ - properties = {} - for name, prop in self._get_all_dbus_things("property"): - if (interface_name - and interface_name != prop._dbus_interface): - # Interface non-empty but did not match - continue - # Ignore write-only properties - if prop._dbus_access == "write": - continue - value = prop() - if not hasattr(value, "variant_level"): - properties[name] = value - continue - properties[name] = type(value)( - value, variant_level=value.variant_level + 1) - return dbus.Dictionary(properties, signature="sv") - - @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as") - def PropertiesChanged(self, interface_name, changed_properties, - invalidated_properties): - """Standard D-Bus PropertiesChanged() signal, see D-Bus - standard. - """ - pass - - @dbus.service.method(dbus.INTROSPECTABLE_IFACE, - out_signature="s", - path_keyword='object_path', - connection_keyword='connection') - def Introspect(self, object_path, connection): - """Overloading of standard D-Bus method. - - Inserts property tags and interface annotation tags. - """ - xmlstring = DBusObjectWithAnnotations.Introspect(self, - object_path, - connection) - try: - document = xml.dom.minidom.parseString(xmlstring) - - def make_tag(document, name, prop): - e = document.createElement("property") - e.setAttribute("name", name) - e.setAttribute("type", prop._dbus_signature) - e.setAttribute("access", prop._dbus_access) - return e - - for if_tag in document.getElementsByTagName("interface"): - # Add property tags - for tag in (make_tag(document, name, prop) - for name, prop - in self._get_all_dbus_things("property") - if prop._dbus_interface - == if_tag.getAttribute("name")): - if_tag.appendChild(tag) - # Add annotation tags for properties - for tag in if_tag.getElementsByTagName("property"): - annots = dict() - for name, prop in self._get_all_dbus_things( - "property"): - if (name == tag.getAttribute("name") - and prop._dbus_interface - == if_tag.getAttribute("name")): - annots.update(getattr( - prop, "_dbus_annotations", {})) - for name, value in annots.items(): - ann_tag = document.createElement( - "annotation") - ann_tag.setAttribute("name", name) - ann_tag.setAttribute("value", value) - tag.appendChild(ann_tag) - # Add the names to the return values for the - # "org.freedesktop.DBus.Properties" methods - if (if_tag.getAttribute("name") - == "org.freedesktop.DBus.Properties"): - for cn in if_tag.getElementsByTagName("method"): - if cn.getAttribute("name") == "Get": - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute("name", "value") - elif cn.getAttribute("name") == "GetAll": - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute("name", "props") - xmlstring = document.toxml("utf-8") - document.unlink() - except (AttributeError, xml.dom.DOMException, - xml.parsers.expat.ExpatError) as error: - logger.error("Failed to override Introspection method", - exc_info=error) - return xmlstring - - -try: - dbus.OBJECT_MANAGER_IFACE -except AttributeError: - dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" - - -class DBusObjectWithObjectManager(DBusObjectWithAnnotations): - """A D-Bus object with an ObjectManager. - - Classes inheriting from this exposes the standard - GetManagedObjects call and the InterfacesAdded and - InterfacesRemoved signals on the standard - "org.freedesktop.DBus.ObjectManager" interface. - - Note: No signals are sent automatically; they must be sent - manually. - """ - @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, - out_signature="a{oa{sa{sv}}}") - def GetManagedObjects(self): - """This function must be overridden""" - raise NotImplementedError() - - @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, - signature="oa{sa{sv}}") - def InterfacesAdded(self, object_path, interfaces_and_properties): - pass - - @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas") - def InterfacesRemoved(self, object_path, interfaces): - pass - - @dbus.service.method(dbus.INTROSPECTABLE_IFACE, - out_signature="s", - path_keyword='object_path', - connection_keyword='connection') - def Introspect(self, object_path, connection): - """Overloading of standard D-Bus method. - - Override return argument name of GetManagedObjects to be - "objpath_interfaces_and_properties" - """ - xmlstring = DBusObjectWithAnnotations.Introspect(self, - object_path, - connection) - try: - document = xml.dom.minidom.parseString(xmlstring) - - for if_tag in document.getElementsByTagName("interface"): - # Fix argument name for the GetManagedObjects method - if (if_tag.getAttribute("name") - == dbus.OBJECT_MANAGER_IFACE): - for cn in if_tag.getElementsByTagName("method"): - if (cn.getAttribute("name") - == "GetManagedObjects"): - for arg in cn.getElementsByTagName("arg"): - if (arg.getAttribute("direction") - == "out"): - arg.setAttribute( - "name", - "objpath_interfaces" - "_and_properties") - xmlstring = document.toxml("utf-8") - document.unlink() - except (AttributeError, xml.dom.DOMException, - xml.parsers.expat.ExpatError) as error: - logger.error("Failed to override Introspection method", - exc_info=error) - return xmlstring - - -def datetime_to_dbus(dt, variant_level=0): - """Convert a UTC datetime.datetime() to a D-Bus type.""" - if dt is None: - return dbus.String("", variant_level=variant_level) - return dbus.String(dt.isoformat(), variant_level=variant_level) - - -def alternate_dbus_interfaces(alt_interface_names, deprecate=True): - """A class decorator; applied to a subclass of - dbus.service.Object, it will add alternate D-Bus attributes with - interface names according to the "alt_interface_names" mapping. - Usage: - - @alternate_dbus_interfaces({"org.example.Interface": - "net.example.AlternateInterface"}) - class SampleDBusObject(dbus.service.Object): - @dbus.service.method("org.example.Interface") - def SampleDBusMethod(): - pass - - The above "SampleDBusMethod" on "SampleDBusObject" will be - reachable via two interfaces: "org.example.Interface" and - "net.example.AlternateInterface", the latter of which will have - its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to - "true", unless "deprecate" is passed with a False value. - - This works for methods and signals, and also for D-Bus properties - (from DBusObjectWithProperties) and interfaces (from the - dbus_interface_annotations decorator). - """ - - def wrapper(cls): - for orig_interface_name, alt_interface_name in ( - alt_interface_names.items()): - attr = {} - interface_names = set() - # Go though all attributes of the class - for attrname, attribute in inspect.getmembers(cls): - # Ignore non-D-Bus attributes, and D-Bus attributes - # with the wrong interface name - if (not hasattr(attribute, "_dbus_interface") - or not attribute._dbus_interface.startswith( - orig_interface_name)): - continue - # Create an alternate D-Bus interface name based on - # the current name - alt_interface = attribute._dbus_interface.replace( - orig_interface_name, alt_interface_name) - interface_names.add(alt_interface) - # Is this a D-Bus signal? - if getattr(attribute, "_dbus_is_signal", False): - # Extract the original non-method undecorated - # function by black magic - if sys.version_info.major == 2: - nonmethod_func = (dict( - zip(attribute.func_code.co_freevars, - attribute.__closure__)) - ["func"].cell_contents) - else: - nonmethod_func = (dict( - zip(attribute.__code__.co_freevars, - attribute.__closure__)) - ["func"].cell_contents) - # Create a new, but exactly alike, function - # object, and decorate it to be a new D-Bus signal - # with the alternate D-Bus interface name - new_function = copy_function(nonmethod_func) - new_function = (dbus.service.signal( - alt_interface, - attribute._dbus_signature)(new_function)) - # Copy annotations, if any - try: - new_function._dbus_annotations = dict( - attribute._dbus_annotations) - except AttributeError: - pass - - # Define a creator of a function to call both the - # original and alternate functions, so both the - # original and alternate signals gets sent when - # the function is called - def fixscope(func1, func2): - """This function is a scope container to pass - func1 and func2 to the "call_both" function - outside of its arguments""" - - @functools.wraps(func2) - def call_both(*args, **kwargs): - """This function will emit two D-Bus - signals by calling func1 and func2""" - func1(*args, **kwargs) - func2(*args, **kwargs) - # Make wrapper function look like a D-Bus - # signal - for name, attr in inspect.getmembers(func2): - if name.startswith("_dbus_"): - setattr(call_both, name, attr) - - return call_both - # Create the "call_both" function and add it to - # the class - attr[attrname] = fixscope(attribute, new_function) - # Is this a D-Bus method? - elif getattr(attribute, "_dbus_is_method", False): - # Create a new, but exactly alike, function - # object. Decorate it to be a new D-Bus method - # with the alternate D-Bus interface name. Add it - # to the class. - attr[attrname] = ( - dbus.service.method( - alt_interface, - attribute._dbus_in_signature, - attribute._dbus_out_signature) - (copy_function(attribute))) - # Copy annotations, if any - try: - attr[attrname]._dbus_annotations = dict( - attribute._dbus_annotations) - except AttributeError: - pass - # Is this a D-Bus property? - elif getattr(attribute, "_dbus_is_property", False): - # Create a new, but exactly alike, function - # object, and decorate it to be a new D-Bus - # property with the alternate D-Bus interface - # name. Add it to the class. - attr[attrname] = (dbus_service_property( - alt_interface, attribute._dbus_signature, - attribute._dbus_access, - attribute._dbus_get_args_options - ["byte_arrays"]) - (copy_function(attribute))) - # Copy annotations, if any - try: - attr[attrname]._dbus_annotations = dict( - attribute._dbus_annotations) - except AttributeError: - pass - # Is this a D-Bus interface? - elif getattr(attribute, "_dbus_is_interface", False): - # Create a new, but exactly alike, function - # object. Decorate it to be a new D-Bus interface - # with the alternate D-Bus interface name. Add it - # to the class. - attr[attrname] = ( - dbus_interface_annotations(alt_interface) - (copy_function(attribute))) - if deprecate: - # Deprecate all alternate interfaces - iname = "_AlternateDBusNames_interface_annotation{}" - for interface_name in interface_names: - - @dbus_interface_annotations(interface_name) - def func(self): - return {"org.freedesktop.DBus.Deprecated": - "true"} - # Find an unused name - for aname in (iname.format(i) - for i in itertools.count()): - if aname not in attr: - attr[aname] = func - break - if interface_names: - # Replace the class with a new subclass of it with - # methods, signals, etc. as created above. - if sys.version_info.major == 2: - cls = type(b"{}Alternate".format(cls.__name__), - (cls, ), attr) - else: - cls = type("{}Alternate".format(cls.__name__), - (cls, ), attr) - return cls - - return wrapper - - -@alternate_dbus_interfaces({"se.recompile.Mandos": - "se.bsnet.fukt.Mandos"}) -class ClientDBus(Client, DBusObjectWithProperties): - """A Client class using D-Bus - - Attributes: - dbus_object_path: dbus.ObjectPath - bus: dbus.SystemBus() - """ - - runtime_expansions = (Client.runtime_expansions - + ("dbus_object_path", )) - - _interface = "se.recompile.Mandos.Client" - - # dbus.service.Object doesn't use super(), so we can't either. - - def __init__(self, bus=None, *args, **kwargs): - self.bus = bus - Client.__init__(self, *args, **kwargs) - # Only now, when this client is initialized, can it show up on - # the D-Bus - client_object_name = str(self.name).translate( - {ord("."): ord("_"), - ord("-"): ord("_")}) - self.dbus_object_path = dbus.ObjectPath( - "/clients/" + client_object_name) - DBusObjectWithProperties.__init__(self, self.bus, - self.dbus_object_path) - - def notifychangeproperty(transform_func, dbus_name, - type_func=lambda x: x, - variant_level=1, - invalidate_only=False, - _interface=_interface): - """ Modify a variable so that it's a property which announces - its changes to DBus. - - transform_fun: Function that takes a value and a variant_level - and transforms it to a D-Bus type. - dbus_name: D-Bus name of the variable - type_func: Function that transform the value before sending it - to the D-Bus. Default: no transform - variant_level: D-Bus variant level. Default: 1 - """ - attrname = "_{}".format(dbus_name) - - def setter(self, value): - if hasattr(self, "dbus_object_path"): - if (not hasattr(self, attrname) or - type_func(getattr(self, attrname, None)) - != type_func(value)): - if invalidate_only: - self.PropertiesChanged( - _interface, dbus.Dictionary(), - dbus.Array((dbus_name, ))) - else: - dbus_value = transform_func( - type_func(value), - variant_level=variant_level) - self.PropertyChanged(dbus.String(dbus_name), - dbus_value) - self.PropertiesChanged( - _interface, - dbus.Dictionary({dbus.String(dbus_name): - dbus_value}), - dbus.Array()) - setattr(self, attrname, value) - - return property(lambda self: getattr(self, attrname), setter) - - expires = notifychangeproperty(datetime_to_dbus, "Expires") - approvals_pending = notifychangeproperty(dbus.Boolean, - "ApprovalPending", - type_func=bool) - enabled = notifychangeproperty(dbus.Boolean, "Enabled") - last_enabled = notifychangeproperty(datetime_to_dbus, - "LastEnabled") - checker = notifychangeproperty( - dbus.Boolean, "CheckerRunning", - type_func=lambda checker: checker is not None) - last_checked_ok = notifychangeproperty(datetime_to_dbus, - "LastCheckedOK") - last_checker_status = notifychangeproperty(dbus.Int16, - "LastCheckerStatus") - last_approval_request = notifychangeproperty( - datetime_to_dbus, "LastApprovalRequest") - approved_by_default = notifychangeproperty(dbus.Boolean, - "ApprovedByDefault") - approval_delay = notifychangeproperty( - dbus.UInt64, "ApprovalDelay", - type_func=lambda td: td.total_seconds() * 1000) - approval_duration = notifychangeproperty( - dbus.UInt64, "ApprovalDuration", - type_func=lambda td: td.total_seconds() * 1000) - host = notifychangeproperty(dbus.String, "Host") - timeout = notifychangeproperty( - dbus.UInt64, "Timeout", - type_func=lambda td: td.total_seconds() * 1000) - extended_timeout = notifychangeproperty( - dbus.UInt64, "ExtendedTimeout", - type_func=lambda td: td.total_seconds() * 1000) - interval = notifychangeproperty( - dbus.UInt64, "Interval", - type_func=lambda td: td.total_seconds() * 1000) - checker_command = notifychangeproperty(dbus.String, "Checker") - secret = notifychangeproperty(dbus.ByteArray, "Secret", - invalidate_only=True) - - del notifychangeproperty - - def __del__(self, *args, **kwargs): - try: - self.remove_from_connection() - except LookupError: - pass - if hasattr(DBusObjectWithProperties, "__del__"): - DBusObjectWithProperties.__del__(self, *args, **kwargs) - Client.__del__(self, *args, **kwargs) - - def checker_callback(self, source, condition, - connection, command, *args, **kwargs): - ret = Client.checker_callback(self, source, condition, - connection, command, *args, - **kwargs) - exitstatus = self.last_checker_status - if exitstatus >= 0: - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(exitstatus), - # This is specific to GNU libC - dbus.Int64(exitstatus << 8), - dbus.String(command)) - else: - # Emit D-Bus signal - self.CheckerCompleted(dbus.Int16(-1), - dbus.Int64( - # This is specific to GNU libC - (exitstatus << 8) - | self.last_checker_signal), - dbus.String(command)) - return ret - - def start_checker(self, *args, **kwargs): - old_checker_pid = getattr(self.checker, "pid", None) - r = Client.start_checker(self, *args, **kwargs) - # Only if new checker process was started - if (self.checker is not None - and old_checker_pid != self.checker.pid): - # Emit D-Bus signal - self.CheckerStarted(self.current_checker_command) - return r - - def _reset_approved(self): - self.approved = None - return False - - def approve(self, value=True): - self.approved = value - GLib.timeout_add(int(self.approval_duration.total_seconds() - * 1000), self._reset_approved) - self.send_changedstate() - - # D-Bus methods, signals & properties - - # Interfaces - - # Signals - - # CheckerCompleted - signal - @dbus.service.signal(_interface, signature="nxs") - def CheckerCompleted(self, exitcode, waitstatus, command): - "D-Bus signal" - pass - - # CheckerStarted - signal - @dbus.service.signal(_interface, signature="s") - def CheckerStarted(self, command): - "D-Bus signal" - pass - - # PropertyChanged - signal - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.signal(_interface, signature="sv") - def PropertyChanged(self, property, value): - "D-Bus signal" - pass - - # GotSecret - signal - @dbus.service.signal(_interface) - def GotSecret(self): - """D-Bus signal - Is sent after a successful transfer of secret from the Mandos - server to mandos-client - """ - pass - - # Rejected - signal - @dbus.service.signal(_interface, signature="s") - def Rejected(self, reason): - "D-Bus signal" - pass - - # NeedApproval - signal - @dbus.service.signal(_interface, signature="tb") - def NeedApproval(self, timeout, default): - "D-Bus signal" - return self.need_approval() - - # Methods - - # Approve - method - @dbus.service.method(_interface, in_signature="b") - def Approve(self, value): - self.approve(value) - - # CheckedOK - method - @dbus.service.method(_interface) - def CheckedOK(self): - self.checked_ok() - - # Enable - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def Enable(self): - "D-Bus method" - self.enable() - - # StartChecker - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def StartChecker(self): - "D-Bus method" - self.start_checker() - - # Disable - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def Disable(self): - "D-Bus method" - self.disable() - - # StopChecker - method - @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) - @dbus.service.method(_interface) - def StopChecker(self): - self.stop_checker() - - # Properties - - # ApprovalPending - property - @dbus_service_property(_interface, signature="b", access="read") - def ApprovalPending_dbus_property(self): - return dbus.Boolean(bool(self.approvals_pending)) - - # ApprovedByDefault - property - @dbus_service_property(_interface, - signature="b", - access="readwrite") - def ApprovedByDefault_dbus_property(self, value=None): - if value is None: # get - return dbus.Boolean(self.approved_by_default) - self.approved_by_default = bool(value) - - # ApprovalDelay - property - @dbus_service_property(_interface, - signature="t", - access="readwrite") - def ApprovalDelay_dbus_property(self, value=None): - if value is None: # get - return dbus.UInt64(self.approval_delay.total_seconds() - * 1000) - self.approval_delay = datetime.timedelta(0, 0, 0, value) - - # ApprovalDuration - property - @dbus_service_property(_interface, - signature="t", - access="readwrite") - def ApprovalDuration_dbus_property(self, value=None): - if value is None: # get - return dbus.UInt64(self.approval_duration.total_seconds() - * 1000) - self.approval_duration = datetime.timedelta(0, 0, 0, value) - - # Name - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def Name_dbus_property(self): - return dbus.String(self.name) - - # KeyID - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def KeyID_dbus_property(self): - return dbus.String(self.key_id) - - # Fingerprint - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def Fingerprint_dbus_property(self): - return dbus.String(self.fingerprint) - - # Host - property - @dbus_service_property(_interface, - signature="s", - access="readwrite") - def Host_dbus_property(self, value=None): - if value is None: # get - return dbus.String(self.host) - self.host = str(value) - - # Created - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"}) - @dbus_service_property(_interface, signature="s", access="read") - def Created_dbus_property(self): - return datetime_to_dbus(self.created) - - # LastEnabled - property - @dbus_service_property(_interface, signature="s", access="read") - def LastEnabled_dbus_property(self): - return datetime_to_dbus(self.last_enabled) - - # Enabled - property - @dbus_service_property(_interface, - signature="b", - access="readwrite") - def Enabled_dbus_property(self, value=None): - if value is None: # get - return dbus.Boolean(self.enabled) - if value: - self.enable() - else: - self.disable() - - # LastCheckedOK - property - @dbus_service_property(_interface, - signature="s", - access="readwrite") - def LastCheckedOK_dbus_property(self, value=None): - if value is not None: - self.checked_ok() - return - return datetime_to_dbus(self.last_checked_ok) - - # LastCheckerStatus - property - @dbus_service_property(_interface, signature="n", access="read") - def LastCheckerStatus_dbus_property(self): - return dbus.Int16(self.last_checker_status) - - # Expires - property - @dbus_service_property(_interface, signature="s", access="read") - def Expires_dbus_property(self): - return datetime_to_dbus(self.expires) - - # LastApprovalRequest - property - @dbus_service_property(_interface, signature="s", access="read") - def LastApprovalRequest_dbus_property(self): - return datetime_to_dbus(self.last_approval_request) - - # Timeout - property - @dbus_service_property(_interface, - signature="t", - access="readwrite") - def Timeout_dbus_property(self, value=None): - if value is None: # get - return dbus.UInt64(self.timeout.total_seconds() * 1000) - old_timeout = self.timeout - self.timeout = datetime.timedelta(0, 0, 0, value) - # Reschedule disabling - if self.enabled: - now = datetime.datetime.utcnow() - self.expires += self.timeout - old_timeout - if self.expires <= now: - # The timeout has passed - self.disable() - else: - if (getattr(self, "disable_initiator_tag", None) - is None): - return - GLib.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = GLib.timeout_add( - int((self.expires - now).total_seconds() * 1000), - self.disable) - - # ExtendedTimeout - property - @dbus_service_property(_interface, - signature="t", - access="readwrite") - def ExtendedTimeout_dbus_property(self, value=None): - if value is None: # get - return dbus.UInt64(self.extended_timeout.total_seconds() - * 1000) - self.extended_timeout = datetime.timedelta(0, 0, 0, value) - - # Interval - property - @dbus_service_property(_interface, - signature="t", - access="readwrite") - def Interval_dbus_property(self, value=None): - if value is None: # get - return dbus.UInt64(self.interval.total_seconds() * 1000) - self.interval = datetime.timedelta(0, 0, 0, value) - if getattr(self, "checker_initiator_tag", None) is None: - return - if self.enabled: - # Reschedule checker run - GLib.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = GLib.timeout_add( - value, self.start_checker) - self.start_checker() # Start one now, too - - # Checker - property - @dbus_service_property(_interface, - signature="s", - access="readwrite") - def Checker_dbus_property(self, value=None): - if value is None: # get - return dbus.String(self.checker_command) - self.checker_command = str(value) - - # CheckerRunning - property - @dbus_service_property(_interface, - signature="b", - access="readwrite") - def CheckerRunning_dbus_property(self, value=None): - if value is None: # get - return dbus.Boolean(self.checker is not None) - if value: - self.start_checker() - else: - self.stop_checker() - - # ObjectPath - property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const", - "org.freedesktop.DBus.Deprecated": "true"}) - @dbus_service_property(_interface, signature="o", access="read") - def ObjectPath_dbus_property(self): - return self.dbus_object_path # is already a dbus.ObjectPath - - # Secret = property - @dbus_annotations( - {"org.freedesktop.DBus.Property.EmitsChangedSignal": - "invalidates"}) - @dbus_service_property(_interface, - signature="ay", - access="write", - byte_arrays=True) - def Secret_dbus_property(self, value): - self.secret = bytes(value) - - del _interface - - -class ProxyClient: - def __init__(self, child_pipe, key_id, fpr, address): - self._pipe = child_pipe - self._pipe.send(('init', key_id, fpr, address)) - if not self._pipe.recv(): - raise KeyError(key_id or fpr) - - def __getattribute__(self, name): - if name == '_pipe': - return super(ProxyClient, self).__getattribute__(name) - self._pipe.send(('getattr', name)) - data = self._pipe.recv() - if data[0] == 'data': - return data[1] - if data[0] == 'function': - - def func(*args, **kwargs): - self._pipe.send(('funcall', name, args, kwargs)) - return self._pipe.recv()[1] - - return func - - def __setattr__(self, name, value): - if name == '_pipe': - return super(ProxyClient, self).__setattr__(name, value) - self._pipe.send(('setattr', name, value)) - - -class ClientHandler(socketserver.BaseRequestHandler, object): - """A class to handle client connections. - - Instantiated once for each connection to handle it. + def still_valid(self): + """Has the timeout not yet passed for this client?""" + now = datetime.datetime.now() + if self.last_checked_ok is None: + return now < (self.created + self.timeout) + else: + return now < (self.last_checked_ok + self.timeout) + + +def peer_certificate(session): + "Return the peer's OpenPGP certificate as a bytestring" + # If not an OpenPGP certificate... + if gnutls.library.functions.gnutls_certificate_type_get\ + (session._c_object) \ + != gnutls.library.constants.GNUTLS_CRT_OPENPGP: + # ...do the normal thing + return session.peer_certificate + list_size = ctypes.c_uint() + cert_list = gnutls.library.functions.gnutls_certificate_get_peers\ + (session._c_object, ctypes.byref(list_size)) + if list_size.value == 0: + return None + cert = cert_list[0] + return ctypes.string_at(cert.data, cert.size) + + +def fingerprint(openpgp): + "Convert an OpenPGP bytestring to a hexdigit fingerprint string" + # New empty GnuTLS certificate + crt = gnutls.library.types.gnutls_openpgp_crt_t() + gnutls.library.functions.gnutls_openpgp_crt_init\ + (ctypes.byref(crt)) + # New GnuTLS "datum" with the OpenPGP public key + datum = gnutls.library.types.gnutls_datum_t\ + (ctypes.cast(ctypes.c_char_p(openpgp), + ctypes.POINTER(ctypes.c_ubyte)), + ctypes.c_uint(len(openpgp))) + # Import the OpenPGP public key into the certificate + ret = gnutls.library.functions.gnutls_openpgp_crt_import\ + (crt, + ctypes.byref(datum), + gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW) + # New buffer for the fingerprint + buffer = ctypes.create_string_buffer(20) + buffer_length = ctypes.c_size_t() + # Get the fingerprint from the certificate into the buffer + gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\ + (crt, ctypes.byref(buffer), ctypes.byref(buffer_length)) + # Deinit the certificate + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + # Convert the buffer to a Python bytestring + fpr = ctypes.string_at(buffer, buffer_length.value) + # Convert the bytestring to hexadecimal notation + hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr) + return hex_fpr + + +class tcp_handler(SocketServer.BaseRequestHandler, object): + """A TCP request handler class. + Instantiated by IPv6_TCPServer for each request to handle it. Note: This will run in its own forked process.""" - + def handle(self): - with contextlib.closing(self.server.child_pipe) as child_pipe: - logger.info("TCP connection from: %s", - str(self.client_address)) - logger.debug("Pipe FD: %d", - self.server.child_pipe.fileno()) - - session = gnutls.ClientSession(self.request) - - # priority = ':'.join(("NONE", "+VERS-TLS1.1", - # "+AES-256-CBC", "+SHA1", - # "+COMP-NULL", "+CTYPE-OPENPGP", - # "+DHE-DSS")) - # Use a fallback default, since this MUST be set. - priority = self.server.gnutls_priority - if priority is None: - priority = "NORMAL" - gnutls.priority_set_direct(session._c_object, - priority.encode("utf-8"), - None) - - # Start communication using the Mandos protocol - # Get protocol number - line = self.request.makefile().readline() - logger.debug("Protocol version: %r", line) - try: - if int(line.strip().split()[0]) > 1: - raise RuntimeError(line) - except (ValueError, IndexError, RuntimeError) as error: - logger.error("Unknown protocol version: %s", error) - return - - # Start GnuTLS connection - try: - session.handshake() - except gnutls.Error as error: - logger.warning("Handshake failed: %s", error) - # Do not run session.bye() here: the session is not - # established. Just abandon the request. - return - logger.debug("Handshake succeeded") - - approval_required = False - try: - if gnutls.has_rawpk: - fpr = b"" - try: - key_id = self.key_id( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Key ID: %s", key_id) - - else: - key_id = b"" - try: - fpr = self.fingerprint( - self.peer_certificate(session)) - except (TypeError, gnutls.Error) as error: - logger.warning("Bad certificate: %s", error) - return - logger.debug("Fingerprint: %s", fpr) - - try: - client = ProxyClient(child_pipe, key_id, fpr, - self.client_address) - except KeyError: - return - - if client.approval_delay: - delay = client.approval_delay - client.approvals_pending += 1 - approval_required = True - - while True: - if not client.enabled: - logger.info("Client %s is disabled", - client.name) - if self.server.use_dbus: - # Emit D-Bus signal - client.Rejected("Disabled") - return - - if client.approved or not client.approval_delay: - # We are approved or approval is disabled - break - elif client.approved is None: - logger.info("Client %s needs approval", - client.name) - if self.server.use_dbus: - # Emit D-Bus signal - client.NeedApproval( - client.approval_delay.total_seconds() - * 1000, client.approved_by_default) - else: - logger.warning("Client %s was not approved", - client.name) - if self.server.use_dbus: - # Emit D-Bus signal - client.Rejected("Denied") - return - - # wait until timeout or approved - time = datetime.datetime.now() - client.changedstate.acquire() - client.changedstate.wait(delay.total_seconds()) - client.changedstate.release() - time2 = datetime.datetime.now() - if (time2 - time) >= delay: - if not client.approved_by_default: - logger.warning("Client %s timed out while" - " waiting for approval", - client.name) - if self.server.use_dbus: - # Emit D-Bus signal - client.Rejected("Approval timed out") - return - else: - break - else: - delay -= time2 - time - - try: - session.send(client.secret) - except gnutls.Error as error: - logger.warning("gnutls send failed", - exc_info=error) - return - - logger.info("Sending secret to %s", client.name) - # bump the timeout using extended_timeout - client.bump_timeout(client.extended_timeout) - if self.server.use_dbus: - # Emit D-Bus signal - client.GotSecret() - - finally: - if approval_required: - client.approvals_pending -= 1 - try: - session.bye() - except gnutls.Error as error: - logger.warning("GnuTLS bye failed", - exc_info=error) - - @staticmethod - def peer_certificate(session): - "Return the peer's certificate as a bytestring" - try: - cert_type = gnutls.certificate_type_get2(session._c_object, - gnutls.CTYPE_PEERS) - except AttributeError: - cert_type = gnutls.certificate_type_get(session._c_object) - if gnutls.has_rawpk: - valid_cert_types = frozenset((gnutls.CRT_RAWPK,)) - else: - valid_cert_types = frozenset((gnutls.CRT_OPENPGP,)) - # If not a valid certificate type... - if cert_type not in valid_cert_types: - logger.info("Cert type %r not in %r", cert_type, - valid_cert_types) - # ...return invalid data - return b"" - list_size = ctypes.c_uint(1) - cert_list = (gnutls.certificate_get_peers - (session._c_object, ctypes.byref(list_size))) - if not bool(cert_list) and list_size.value != 0: - raise gnutls.Error("error getting peer certificate") - if list_size.value == 0: - return None - cert = cert_list[0] - return ctypes.string_at(cert.data, cert.size) - - @staticmethod - def key_id(certificate): - "Convert a certificate bytestring to a hexdigit key ID" - # New GnuTLS "datum" with the public key - datum = gnutls.datum_t( - ctypes.cast(ctypes.c_char_p(certificate), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.c_uint(len(certificate))) - # XXX all these need to be created in the gnutls "module" - # New empty GnuTLS certificate - pubkey = gnutls.pubkey_t() - gnutls.pubkey_init(ctypes.byref(pubkey)) - # Import the raw public key into the certificate - gnutls.pubkey_import(pubkey, - ctypes.byref(datum), - gnutls.X509_FMT_DER) - # New buffer for the key ID - buf = ctypes.create_string_buffer(32) - buf_len = ctypes.c_size_t(len(buf)) - # Get the key ID from the raw public key into the buffer - gnutls.pubkey_get_key_id(pubkey, - gnutls.KEYID_USE_SHA256, - ctypes.cast(ctypes.byref(buf), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.byref(buf_len)) - # Deinit the certificate - gnutls.pubkey_deinit(pubkey) - - # Convert the buffer to a Python bytestring - key_id = ctypes.string_at(buf, buf_len.value) - # Convert the bytestring to hexadecimal notation - hex_key_id = binascii.hexlify(key_id).upper() - return hex_key_id - - @staticmethod - def fingerprint(openpgp): - "Convert an OpenPGP bytestring to a hexdigit fingerprint" - # New GnuTLS "datum" with the OpenPGP public key - datum = gnutls.datum_t( - ctypes.cast(ctypes.c_char_p(openpgp), - ctypes.POINTER(ctypes.c_ubyte)), - ctypes.c_uint(len(openpgp))) - # New empty GnuTLS certificate - crt = gnutls.openpgp_crt_t() - gnutls.openpgp_crt_init(ctypes.byref(crt)) - # Import the OpenPGP public key into the certificate - gnutls.openpgp_crt_import(crt, ctypes.byref(datum), - gnutls.OPENPGP_FMT_RAW) - # Verify the self signature in the key - crtverify = ctypes.c_uint() - gnutls.openpgp_crt_verify_self(crt, 0, - ctypes.byref(crtverify)) - if crtverify.value != 0: - gnutls.openpgp_crt_deinit(crt) - raise gnutls.CertificateSecurityError(code - =crtverify.value) - # New buffer for the fingerprint - buf = ctypes.create_string_buffer(20) - buf_len = ctypes.c_size_t() - # Get the fingerprint from the certificate into the buffer - gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf), - ctypes.byref(buf_len)) - # Deinit the certificate - gnutls.openpgp_crt_deinit(crt) - # Convert the buffer to a Python bytestring - fpr = ctypes.string_at(buf, buf_len.value) - # Convert the bytestring to hexadecimal notation - hex_fpr = binascii.hexlify(fpr).upper() - return hex_fpr - - -class MultiprocessingMixIn: - """Like socketserver.ThreadingMixIn, but with multiprocessing""" - - def sub_process_main(self, request, address): - try: - self.finish_request(request, address) - except Exception: - self.handle_error(request, address) - self.close_request(request) - - def process_request(self, request, address): - """Start a new process to process the request.""" - proc = multiprocessing.Process(target=self.sub_process_main, - args=(request, address)) - proc.start() - return proc - - -class MultiprocessingMixInWithPipe(MultiprocessingMixIn): - """ adds a pipe to the MixIn """ - - def process_request(self, request, client_address): - """Overrides and wraps the original process_request(). - - This function creates a new pipe in self.pipe - """ - parent_pipe, self.child_pipe = multiprocessing.Pipe() - - proc = MultiprocessingMixIn.process_request(self, request, - client_address) - self.child_pipe.close() - self.add_pipe(parent_pipe, proc) - - def add_pipe(self, parent_pipe, proc): - """Dummy function; override as necessary""" - raise NotImplementedError() - - -class IPv6_TCPServer(MultiprocessingMixInWithPipe, - socketserver.TCPServer): - """IPv6-capable TCP server. Accepts 'None' as address and/or port - + logger.debug(u"TCP connection from: %s", + unicode(self.client_address)) + session = gnutls.connection.ClientSession\ + (self.request, gnutls.connection.X509Credentials()) + # Note: gnutls.connection.X509Credentials is really a generic + # GnuTLS certificate credentials object so long as no X.509 + # keys are added to it. Therefore, we can use it here despite + # using OpenPGP certificates. + + #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC", + # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", + # "+DHE-DSS")) + priority = "NORMAL" # Fallback default, since this + # MUST be set. + if self.server.settings["priority"]: + priority = self.server.settings["priority"] + gnutls.library.functions.gnutls_priority_set_direct\ + (session._c_object, priority, None); + + try: + session.handshake() + except gnutls.errors.GNUTLSError, error: + logger.debug(u"Handshake failed: %s", error) + # Do not run session.bye() here: the session is not + # established. Just abandon the request. + return + try: + fpr = fingerprint(peer_certificate(session)) + except (TypeError, gnutls.errors.GNUTLSError), error: + logger.debug(u"Bad certificate: %s", error) + session.bye() + return + logger.debug(u"Fingerprint: %s", fpr) + client = None + for c in self.server.clients: + if c.fingerprint == fpr: + client = c + break + if not client: + logger.debug(u"Client not found for fingerprint: %s", fpr) + session.bye() + return + # Have to check if client.still_valid(), since it is possible + # that the client timed out while establishing the GnuTLS + # session. + if not client.still_valid(): + logger.debug(u"Client %(name)s is invalid", vars(client)) + session.bye() + return + sent_size = 0 + while sent_size < len(client.secret): + sent = session.send(client.secret[sent_size:]) + logger.debug(u"Sent: %d, remaining: %d", + sent, len(client.secret) + - (sent_size + sent)) + sent_size += sent + session.bye() + + +class IPv6_TCPServer(SocketServer.ForkingTCPServer, object): + """IPv6 TCP server. Accepts 'None' as address and/or port. Attributes: - enabled: Boolean; whether this server is activated yet - interface: None or a network interface name (string) - use_ipv6: Boolean; to use IPv6 or not + settings: Server settings + clients: Set() of Client objects """ - - def __init__(self, server_address, RequestHandlerClass, - interface=None, - use_ipv6=True, - socketfd=None): - """If socketfd is set, use that file descriptor instead of - creating a new one with socket.socket(). - """ - self.interface = interface - if use_ipv6: - self.address_family = socket.AF_INET6 - if socketfd is not None: - # Save the file descriptor - self.socketfd = socketfd - # Save the original socket.socket() function - self.socket_socket = socket.socket - - # To implement --socket, we monkey patch socket.socket. - # - # (When socketserver.TCPServer is a new-style class, we - # could make self.socket into a property instead of monkey - # patching socket.socket.) - # - # Create a one-time-only replacement for socket.socket() - @functools.wraps(socket.socket) - def socket_wrapper(*args, **kwargs): - # Restore original function so subsequent calls are - # not affected. - socket.socket = self.socket_socket - del self.socket_socket - # This time only, return a new socket object from the - # saved file descriptor. - return socket.fromfd(self.socketfd, *args, **kwargs) - # Replace socket.socket() function with wrapper - socket.socket = socket_wrapper - # The socketserver.TCPServer.__init__ will call - # socket.socket(), which might be our replacement, - # socket_wrapper(), if socketfd was set. - socketserver.TCPServer.__init__(self, server_address, - RequestHandlerClass) - + address_family = socket.AF_INET6 + def __init__(self, *args, **kwargs): + if "settings" in kwargs: + self.settings = kwargs["settings"] + del kwargs["settings"] + if "clients" in kwargs: + self.clients = kwargs["clients"] + del kwargs["clients"] + return super(type(self), self).__init__(*args, **kwargs) def server_bind(self): """This overrides the normal server_bind() function to bind to an interface if one was specified, and also NOT to bind to an address or port if they were not specified.""" - global SO_BINDTODEVICE - if self.interface is not None: - if SO_BINDTODEVICE is None: - # Fall back to a hard-coded value which seems to be - # common enough. - logger.warning("SO_BINDTODEVICE not found, trying 25") - SO_BINDTODEVICE = 25 + if self.settings["interface"]: + # 25 is from /usr/include/asm-i486/socket.h + SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25) try: - self.socket.setsockopt( - socket.SOL_SOCKET, SO_BINDTODEVICE, - (self.interface + "\0").encode("utf-8")) - except socket.error as error: - if error.errno == errno.EPERM: - logger.error("No permission to bind to" - " interface %s", self.interface) - elif error.errno == errno.ENOPROTOOPT: - logger.error("SO_BINDTODEVICE not available;" - " cannot bind to interface %s", - self.interface) - elif error.errno == errno.ENODEV: - logger.error("Interface %s does not exist," - " cannot bind", self.interface) + self.socket.setsockopt(socket.SOL_SOCKET, + SO_BINDTODEVICE, + self.settings["interface"]) + except socket.error, error: + if error[0] == errno.EPERM: + logger.warning(u"No permission to" + u" bind to interface %s", + self.settings["interface"]) else: - raise + raise error # Only bind(2) the socket if we really need to. if self.server_address[0] or self.server_address[1]: - if self.server_address[1]: - self.allow_reuse_address = True if not self.server_address[0]: - if self.address_family == socket.AF_INET6: - any_address = "::" # in6addr_any - else: - any_address = "0.0.0.0" # INADDR_ANY - self.server_address = (any_address, + in6addr_any = "::" + self.server_address = (in6addr_any, self.server_address[1]) - elif not self.server_address[1]: - self.server_address = (self.server_address[0], 0) -# if self.interface: -# self.server_address = (self.server_address[0], -# 0, # port -# 0, # flowinfo -# if_nametoindex -# (self.interface)) - return socketserver.TCPServer.server_bind(self) - - -class MandosServer(IPv6_TCPServer): - """Mandos server. - - Attributes: - clients: set of Client objects - gnutls_priority GnuTLS priority string - use_dbus: Boolean; to emit D-Bus signals or not - - Assumes a GLib.MainLoop event loop. - """ - - def __init__(self, server_address, RequestHandlerClass, - interface=None, - use_ipv6=True, - clients=None, - gnutls_priority=None, - use_dbus=True, - socketfd=None): - self.enabled = False - self.clients = clients - if self.clients is None: - self.clients = {} - self.use_dbus = use_dbus - self.gnutls_priority = gnutls_priority - IPv6_TCPServer.__init__(self, server_address, - RequestHandlerClass, - interface=interface, - use_ipv6=use_ipv6, - socketfd=socketfd) - - def server_activate(self): - if self.enabled: - return socketserver.TCPServer.server_activate(self) - - def enable(self): - self.enabled = True - - def add_pipe(self, parent_pipe, proc): - # Call "handle_ipc" for both data and EOF events - GLib.io_add_watch( - GLib.IOChannel.unix_new(parent_pipe.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP, - functools.partial(self.handle_ipc, - parent_pipe=parent_pipe, - proc=proc)) - - def handle_ipc(self, source, condition, - parent_pipe=None, - proc=None, - client_object=None): - # error, or the other end of multiprocessing.Pipe has closed - if condition & (GLib.IO_ERR | GLib.IO_HUP): - # Wait for other process to exit - proc.join() - return False - - # Read a request from the child - request = parent_pipe.recv() - command = request[0] - - if command == 'init': - key_id = request[1].decode("ascii") - fpr = request[2].decode("ascii") - address = request[3] - - for c in self.clients.values(): - if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855": - continue - if key_id and c.key_id == key_id: - client = c - break - if fpr and c.fingerprint == fpr: - client = c - break - else: - logger.info("Client not found for key ID: %s, address" - ": %s", key_id or fpr, address) - if self.use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientNotFound(key_id or fpr, - address[0]) - parent_pipe.send(False) - return False - - GLib.io_add_watch( - GLib.IOChannel.unix_new(parent_pipe.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP, - functools.partial(self.handle_ipc, - parent_pipe=parent_pipe, - proc=proc, - client_object=client)) - parent_pipe.send(True) - # remove the old hook in favor of the new above hook on - # same fileno - return False - if command == 'funcall': - funcname = request[1] - args = request[2] - kwargs = request[3] - - parent_pipe.send(('data', getattr(client_object, - funcname)(*args, - **kwargs))) - - if command == 'getattr': - attrname = request[1] - if isinstance(client_object.__getattribute__(attrname), - collections.Callable): - parent_pipe.send(('function', )) - else: - parent_pipe.send(( - 'data', client_object.__getattribute__(attrname))) - - if command == 'setattr': - attrname = request[1] - value = request[2] - setattr(client_object, attrname, value) - - return True - - -def rfc3339_duration_to_delta(duration): - """Parse an RFC 3339 "duration" and return a datetime.timedelta - - >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7) - True - >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60) - True - >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(0, 3600) - True - >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1) - True - >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7) - True - >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330) - True - >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200) - True - """ - - # Parsing an RFC 3339 duration with regular expressions is not - # possible - there would have to be multiple places for the same - # values, like seconds. The current code, while more esoteric, is - # cleaner without depending on a parsing library. If Python had a - # built-in library for parsing we would use it, but we'd like to - # avoid excessive use of external libraries. - - # New type for defining tokens, syntax, and semantics all-in-one - Token = collections.namedtuple("Token", ( - "regexp", # To match token; if "value" is not None, must have - # a "group" containing digits - "value", # datetime.timedelta or None - "followers")) # Tokens valid after this token - # RFC 3339 "duration" tokens, syntax, and semantics; taken from - # the "duration" ABNF definition in RFC 3339, Appendix A. - token_end = Token(re.compile(r"$"), None, frozenset()) - token_second = Token(re.compile(r"(\d+)S"), - datetime.timedelta(seconds=1), - frozenset((token_end, ))) - token_minute = Token(re.compile(r"(\d+)M"), - datetime.timedelta(minutes=1), - frozenset((token_second, token_end))) - token_hour = Token(re.compile(r"(\d+)H"), - datetime.timedelta(hours=1), - frozenset((token_minute, token_end))) - token_time = Token(re.compile(r"T"), - None, - frozenset((token_hour, token_minute, - token_second))) - token_day = Token(re.compile(r"(\d+)D"), - datetime.timedelta(days=1), - frozenset((token_time, token_end))) - token_month = Token(re.compile(r"(\d+)M"), - datetime.timedelta(weeks=4), - frozenset((token_day, token_end))) - token_year = Token(re.compile(r"(\d+)Y"), - datetime.timedelta(weeks=52), - frozenset((token_month, token_end))) - token_week = Token(re.compile(r"(\d+)W"), - datetime.timedelta(weeks=1), - frozenset((token_end, ))) - token_duration = Token(re.compile(r"P"), None, - frozenset((token_year, token_month, - token_day, token_time, - token_week))) - # Define starting values: - # Value so far - value = datetime.timedelta() - found_token = None - # Following valid tokens - followers = frozenset((token_duration, )) - # String left to parse - s = duration - # Loop until end token is found - while found_token is not token_end: - # Search for any currently valid tokens - for token in followers: - match = token.regexp.match(s) - if match is not None: - # Token found - if token.value is not None: - # Value found, parse digits - factor = int(match.group(1), 10) - # Add to value so far - value += factor * token.value - # Strip token from string - s = token.regexp.sub("", s, 1) - # Go to found token - found_token = token - # Set valid next tokens - followers = found_token.followers - break - else: - # No currently valid tokens were found - raise ValueError("Invalid RFC 3339 duration: {!r}" - .format(duration)) - # End token found - return value + elif self.server_address[1] is None: + self.server_address = (self.server_address[0], + 0) + return super(type(self), self).server_bind() def string_to_delta(interval): """Parse a string and return a datetime.timedelta - >>> string_to_delta('7d') == datetime.timedelta(7) - True - >>> string_to_delta('60s') == datetime.timedelta(0, 60) - True - >>> string_to_delta('60m') == datetime.timedelta(0, 3600) - True - >>> string_to_delta('24h') == datetime.timedelta(1) - True - >>> string_to_delta('1w') == datetime.timedelta(7) - True - >>> string_to_delta('5m 30s') == datetime.timedelta(0, 330) - True + >>> string_to_delta('7d') + datetime.timedelta(7) + >>> string_to_delta('60s') + datetime.timedelta(0, 60) + >>> string_to_delta('60m') + datetime.timedelta(0, 3600) + >>> string_to_delta('24h') + datetime.timedelta(1) + >>> string_to_delta(u'1w') + datetime.timedelta(7) """ - - try: - return rfc3339_duration_to_delta(interval) - except ValueError: - pass - - timevalue = datetime.timedelta(0) - for s in interval.split(): - try: - suffix = s[-1] - value = int(s[:-1]) - if suffix == "d": - delta = datetime.timedelta(value) - elif suffix == "s": - delta = datetime.timedelta(0, value) - elif suffix == "m": - delta = datetime.timedelta(0, 0, 0, 0, value) - elif suffix == "h": - delta = datetime.timedelta(0, 0, 0, 0, 0, value) - elif suffix == "w": - delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) - else: - raise ValueError("Unknown suffix {!r}".format(suffix)) - except IndexError as e: - raise ValueError(*(e.args)) - timevalue += delta - return timevalue - - -def daemon(nochdir=False, noclose=False): + try: + suffix=unicode(interval[-1]) + value=int(interval[:-1]) + if suffix == u"d": + delta = datetime.timedelta(value) + elif suffix == u"s": + delta = datetime.timedelta(0, value) + elif suffix == u"m": + delta = datetime.timedelta(0, 0, 0, 0, value) + elif suffix == u"h": + delta = datetime.timedelta(0, 0, 0, 0, 0, value) + elif suffix == u"w": + delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) + else: + raise ValueError + except (ValueError, IndexError): + raise ValueError + return delta + + +def server_state_changed(state): + """Derived from the Avahi example code""" + if state == avahi.SERVER_COLLISION: + logger.warning(u"Server name collision") + service.remove() + elif state == avahi.SERVER_RUNNING: + service.add() + + +def entry_group_state_changed(state, error): + """Derived from the Avahi example code""" + logger.debug(u"state change: %i", state) + + if state == avahi.ENTRY_GROUP_ESTABLISHED: + logger.debug(u"Service established.") + elif state == avahi.ENTRY_GROUP_COLLISION: + logger.warning(u"Service name collision.") + service.rename() + elif state == avahi.ENTRY_GROUP_FAILURE: + logger.critical(u"Error in group state changed %s", + unicode(error)) + raise AvahiGroupError("State changed: %s", str(error)) + +def if_nametoindex(interface, _func=[None]): + """Call the C function if_nametoindex(), or equivalent""" + if _func[0] is not None: + return _func[0](interface) + try: + if "ctypes.util" not in sys.modules: + import ctypes.util + while True: + try: + libc = ctypes.cdll.LoadLibrary\ + (ctypes.util.find_library("c")) + _func[0] = libc.if_nametoindex + return _func[0](interface) + except IOError, e: + if e != errno.EINTR: + raise + except (OSError, AttributeError): + if "struct" not in sys.modules: + import struct + if "fcntl" not in sys.modules: + import fcntl + def the_hard_way(interface): + "Get an interface index the hard way, i.e. using fcntl()" + SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h + s = socket.socket() + ifreq = fcntl.ioctl(s, SIOCGIFINDEX, + struct.pack("16s16x", interface)) + s.close() + interface_index = struct.unpack("I", ifreq[16:20])[0] + return interface_index + _func[0] = the_hard_way + return _func[0](interface) + + +def daemon(nochdir, noclose): """See daemon(3). Standard BSD Unix function. - This should really exist as os.daemon, but it doesn't (yet).""" if os.fork(): sys.exit() os.setsid() if not nochdir: os.chdir("/") - if os.fork(): - sys.exit() if not noclose: # Close all standard open file descriptors - null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR) + null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) if not stat.S_ISCHR(os.fstat(null).st_mode): raise OSError(errno.ENODEV, - "{} not a character device" - .format(os.devnull)) + "/dev/null not a character device") os.dup2(null, sys.stdin.fileno()) os.dup2(null, sys.stdout.fileno()) os.dup2(null, sys.stderr.fileno()) @@ -2933,721 +648,181 @@ def main(): - - ################################################################## - # Parsing of options, both command line and config file - - parser = argparse.ArgumentParser() - parser.add_argument("-v", "--version", action="version", - version="%(prog)s {}".format(version), - help="show version number and exit") - parser.add_argument("-i", "--interface", metavar="IF", - help="Bind to interface IF") - parser.add_argument("-a", "--address", - help="Address to listen for requests on") - parser.add_argument("-p", "--port", type=int, - help="Port number to receive requests on") - parser.add_argument("--check", action="store_true", - help="Run self-test") - parser.add_argument("--debug", action="store_true", - help="Debug mode; run in foreground and log" - " to terminal", default=None) - parser.add_argument("--debuglevel", metavar="LEVEL", - help="Debug level for stdout output") - parser.add_argument("--priority", help="GnuTLS" - " priority string (see GnuTLS documentation)") - parser.add_argument("--servicename", - metavar="NAME", help="Zeroconf service name") - parser.add_argument("--configdir", - default="/etc/mandos", metavar="DIR", - help="Directory to search for configuration" - " files") - parser.add_argument("--no-dbus", action="store_false", - dest="use_dbus", help="Do not provide D-Bus" - " system bus interface", default=None) - parser.add_argument("--no-ipv6", action="store_false", - dest="use_ipv6", help="Do not use IPv6", - default=None) - parser.add_argument("--no-restore", action="store_false", - dest="restore", help="Do not restore stored" - " state", default=None) - parser.add_argument("--socket", type=int, - help="Specify a file descriptor to a network" - " socket to use instead of creating one") - parser.add_argument("--statedir", metavar="DIR", - help="Directory to save/restore state in") - parser.add_argument("--foreground", action="store_true", - help="Run in foreground", default=None) - parser.add_argument("--no-zeroconf", action="store_false", - dest="zeroconf", help="Do not use Zeroconf", - default=None) - - options = parser.parse_args() - + global main_loop_started + main_loop_started = False + + parser = OptionParser() + parser.add_option("-i", "--interface", type="string", + metavar="IF", help="Bind to interface IF") + parser.add_option("-a", "--address", type="string", + help="Address to listen for requests on") + parser.add_option("-p", "--port", type="int", + help="Port number to receive requests on") + parser.add_option("--check", action="store_true", default=False, + help="Run self-test") + parser.add_option("--debug", action="store_true", default=False, + help="Debug mode; run in foreground and log to" + " terminal") + parser.add_option("--priority", type="string", help="GnuTLS" + " priority string (see GnuTLS documentation)") + parser.add_option("--servicename", type="string", metavar="NAME", + help="Zeroconf service name") + parser.add_option("--configdir", type="string", + default="/etc/mandos", metavar="DIR", + help="Directory to search for configuration" + " files") + (options, args) = parser.parse_args() + + if options.check: + import doctest + doctest.testmod() + sys.exit() + # Default values for config file for server-global settings - if gnutls.has_rawpk: - priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA" - ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA") - else: - priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA" - ":+SIGN-DSA-SHA256") - server_defaults = {"interface": "", - "address": "", - "port": "", - "debug": "False", - "priority": priority, - "servicename": "Mandos", - "use_dbus": "True", - "use_ipv6": "True", - "debuglevel": "", - "restore": "True", - "socket": "", - "statedir": "/var/lib/mandos", - "foreground": "False", - "zeroconf": "True", - } - del priority - + server_defaults = { "interface": "", + "address": "", + "port": "", + "debug": "False", + "priority": + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", + "servicename": "Mandos", + } + # Parse config file for server-global settings - server_config = configparser.ConfigParser(server_defaults) + server_config = ConfigParser.SafeConfigParser(server_defaults) del server_defaults - server_config.read(os.path.join(options.configdir, "mandos.conf")) - # Convert the ConfigParser object to a dict - server_settings = server_config.defaults() - # Use the appropriate methods on the non-string config options - for option in ("debug", "use_dbus", "use_ipv6", "restore", - "foreground", "zeroconf"): - server_settings[option] = server_config.getboolean("DEFAULT", - option) - if server_settings["port"]: - server_settings["port"] = server_config.getint("DEFAULT", - "port") - if server_settings["socket"]: - server_settings["socket"] = server_config.getint("DEFAULT", - "socket") - # Later, stdin will, and stdout and stderr might, be dup'ed - # over with an opened os.devnull. But we don't want this to - # happen with a supplied network socket. - if 0 <= server_settings["socket"] <= 2: - server_settings["socket"] = os.dup(server_settings - ["socket"]) + server_config.read(os.path.join(options.configdir, "server.conf")) + server_section = "server" + # Convert the SafeConfigParser object to a dict + server_settings = dict(server_config.items(server_section)) + # Use getboolean on the boolean config option + server_settings["debug"] = server_config.getboolean\ + (server_section, "debug") del server_config - + # Override the settings from the config file with command line # options, if set. for option in ("interface", "address", "port", "debug", - "priority", "servicename", "configdir", "use_dbus", - "use_ipv6", "debuglevel", "restore", "statedir", - "socket", "foreground", "zeroconf"): + "priority", "servicename", "configdir"): value = getattr(options, option) if value is not None: server_settings[option] = value del options - # Force all strings to be unicode - for option in server_settings.keys(): - if isinstance(server_settings[option], bytes): - server_settings[option] = (server_settings[option] - .decode("utf-8")) - # Force all boolean options to be boolean - for option in ("debug", "use_dbus", "use_ipv6", "restore", - "foreground", "zeroconf"): - server_settings[option] = bool(server_settings[option]) - # Debug implies foreground - if server_settings["debug"]: - server_settings["foreground"] = True # Now we have our good server settings in "server_settings" - - ################################################################## - - if (not server_settings["zeroconf"] - and not (server_settings["port"] - or server_settings["socket"] != "")): - parser.error("Needs port or socket to work without Zeroconf") - - # For convenience - debug = server_settings["debug"] - debuglevel = server_settings["debuglevel"] - use_dbus = server_settings["use_dbus"] - use_ipv6 = server_settings["use_ipv6"] - stored_state_path = os.path.join(server_settings["statedir"], - stored_state_file) - foreground = server_settings["foreground"] - zeroconf = server_settings["zeroconf"] - - if debug: - initlogger(debug, logging.DEBUG) - else: - if not debuglevel: - initlogger(debug) - else: - level = getattr(logging, debuglevel.upper()) - initlogger(debug, level) - - if server_settings["servicename"] != "Mandos": - syslogger.setFormatter( - logging.Formatter('Mandos ({}) [%(process)d]:' - ' %(levelname)s: %(message)s'.format( - server_settings["servicename"]))) - + # Parse config file with clients - client_config = configparser.ConfigParser(Client.client_defaults) + client_defaults = { "timeout": "1h", + "interval": "5m", + "checker": "fping -q -- %%(fqdn)s", + } + client_config = ConfigParser.SafeConfigParser(client_defaults) client_config.read(os.path.join(server_settings["configdir"], "clients.conf")) - - global mandos_dbus_service - mandos_dbus_service = None - - socketfd = None - if server_settings["socket"] != "": - socketfd = server_settings["socket"] - tcp_server = MandosServer( - (server_settings["address"], server_settings["port"]), - ClientHandler, - interface=(server_settings["interface"] or None), - use_ipv6=use_ipv6, - gnutls_priority=server_settings["priority"], - use_dbus=use_dbus, - socketfd=socketfd) - if not foreground: - pidfilename = "/run/mandos.pid" - if not os.path.isdir("/run/."): - pidfilename = "/var/run/mandos.pid" - pidfile = None - try: - pidfile = codecs.open(pidfilename, "w", encoding="utf-8") - except IOError as e: - logger.error("Could not open file %r", pidfilename, - exc_info=e) - - for name, group in (("_mandos", "_mandos"), - ("mandos", "mandos"), - ("nobody", "nogroup")): - try: - uid = pwd.getpwnam(name).pw_uid - gid = pwd.getpwnam(group).pw_gid - break - except KeyError: - continue - else: - uid = 65534 - gid = 65534 - try: - os.setgid(gid) - os.setuid(uid) - if debug: - logger.debug("Did setuid/setgid to {}:{}".format(uid, - gid)) - except OSError as error: - logger.warning("Failed to setuid/setgid to {}:{}: {}" - .format(uid, gid, os.strerror(error.errno))) - if error.errno != errno.EPERM: - raise - - if debug: - # Enable all possible GnuTLS debugging - - # "Use a log level over 10 to enable all debugging options." - # - GnuTLS manual - gnutls.global_set_log_level(11) - - @gnutls.log_func - def debug_gnutls(level, string): - logger.debug("GnuTLS: %s", string[:-1]) - - gnutls.global_set_log_function(debug_gnutls) - - # Redirect stdin so all checkers get /dev/null - null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR) - os.dup2(null, sys.stdin.fileno()) - if null > 2: - os.close(null) - - # Need to fork before connecting to D-Bus - if not foreground: - # Close all input and output, do double fork, etc. - daemon() - - if gi.version_info < (3, 10, 2): - # multiprocessing will use threads, so before we use GLib we - # need to inform GLib that threads will be used. - GLib.threads_init() - + + global service + service = AvahiService(name = server_settings["servicename"], + type = "_mandos._tcp", ); + if server_settings["interface"]: + service.interface = if_nametoindex(server_settings["interface"]) + global main_loop + global bus + global server # From the Avahi example code - DBusGMainLoop(set_as_default=True) - main_loop = GLib.MainLoop() + DBusGMainLoop(set_as_default=True ) + main_loop = gobject.MainLoop() bus = dbus.SystemBus() + server = dbus.Interface( + bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), + avahi.DBUS_INTERFACE_SERVER ) # End of Avahi example code - if use_dbus: - try: - bus_name = dbus.service.BusName("se.recompile.Mandos", - bus, - do_not_queue=True) - old_bus_name = dbus.service.BusName( - "se.bsnet.fukt.Mandos", bus, - do_not_queue=True) - except dbus.exceptions.DBusException as e: - logger.error("Disabling D-Bus:", exc_info=e) - use_dbus = False - server_settings["use_dbus"] = False - tcp_server.use_dbus = False - if zeroconf: - protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET - service = AvahiServiceToSyslog( - name=server_settings["servicename"], - servicetype="_mandos._tcp", - protocol=protocol, - bus=bus) - if server_settings["interface"]: - service.interface = if_nametoindex( - server_settings["interface"].encode("utf-8")) - - global multiprocessing_manager - multiprocessing_manager = multiprocessing.Manager() - - client_class = Client - if use_dbus: - client_class = functools.partial(ClientDBus, bus=bus) - - client_settings = Client.config_parser(client_config) - old_client_settings = {} - clients_data = {} - - # This is used to redirect stdout and stderr for checker processes - global wnull - wnull = open(os.devnull, "w") # A writable /dev/null - # Only used if server is running in foreground but not in debug - # mode - if debug or not foreground: - wnull.close() - - # Get client data and settings from last running state. - if server_settings["restore"]: - try: - with open(stored_state_path, "rb") as stored_state: - if sys.version_info.major == 2: - clients_data, old_client_settings = pickle.load( - stored_state) - else: - bytes_clients_data, bytes_old_client_settings = ( - pickle.load(stored_state, encoding="bytes")) - # Fix bytes to strings - # clients_data - # .keys() - clients_data = {(key.decode("utf-8") - if isinstance(key, bytes) - else key): value - for key, value in - bytes_clients_data.items()} - del bytes_clients_data - for key in clients_data: - value = {(k.decode("utf-8") - if isinstance(k, bytes) else k): v - for k, v in - clients_data[key].items()} - clients_data[key] = value - # .client_structure - value["client_structure"] = [ - (s.decode("utf-8") - if isinstance(s, bytes) - else s) for s in - value["client_structure"]] - # .name, .host, and .checker_command - for k in ("name", "host", "checker_command"): - if isinstance(value[k], bytes): - value[k] = value[k].decode("utf-8") - if "key_id" not in value: - value["key_id"] = "" - elif "fingerprint" not in value: - value["fingerprint"] = "" - # old_client_settings - # .keys() - old_client_settings = { - (key.decode("utf-8") - if isinstance(key, bytes) - else key): value - for key, value in - bytes_old_client_settings.items()} - del bytes_old_client_settings - # .host and .checker_command - for value in old_client_settings.values(): - for attribute in ("host", "checker_command"): - if isinstance(value[attribute], bytes): - value[attribute] = (value[attribute] - .decode("utf-8")) - os.remove(stored_state_path) - except IOError as e: - if e.errno == errno.ENOENT: - logger.warning("Could not load persistent state:" - " {}".format(os.strerror(e.errno))) - else: - logger.critical("Could not load persistent state:", - exc_info=e) - raise - except EOFError as e: - logger.warning("Could not load persistent state: " - "EOFError:", - exc_info=e) - - with PGPEngine() as pgp: - for client_name, client in clients_data.items(): - # Skip removed clients - if client_name not in client_settings: - continue - - # Decide which value to use after restoring saved state. - # We have three different values: Old config file, - # new config file, and saved state. - # New config value takes precedence if it differs from old - # config value, otherwise use saved state. - for name, value in client_settings[client_name].items(): - try: - # For each value in new config, check if it - # differs from the old config value (Except for - # the "secret" attribute) - if (name != "secret" - and (value != - old_client_settings[client_name][name])): - client[name] = value - except KeyError: - pass - - # Clients who has passed its expire date can still be - # enabled if its last checker was successful. A Client - # whose checker succeeded before we stored its state is - # assumed to have successfully run all checkers during - # downtime. - if client["enabled"]: - if datetime.datetime.utcnow() >= client["expires"]: - if not client["last_checked_ok"]: - logger.warning( - "disabling client {} - Client never " - "performed a successful checker".format( - client_name)) - client["enabled"] = False - elif client["last_checker_status"] != 0: - logger.warning( - "disabling client {} - Client last" - " checker failed with error code" - " {}".format( - client_name, - client["last_checker_status"])) - client["enabled"] = False - else: - client["expires"] = ( - datetime.datetime.utcnow() - + client["timeout"]) - logger.debug("Last checker succeeded," - " keeping {} enabled".format( - client_name)) - try: - client["secret"] = pgp.decrypt( - client["encrypted_secret"], - client_settings[client_name]["secret"]) - except PGPError: - # If decryption fails, we use secret from new settings - logger.debug("Failed to decrypt {} old secret".format( - client_name)) - client["secret"] = (client_settings[client_name] - ["secret"]) - - # Add/remove clients based on new changes made to config - for client_name in (set(old_client_settings) - - set(client_settings)): - del clients_data[client_name] - for client_name in (set(client_settings) - - set(old_client_settings)): - clients_data[client_name] = client_settings[client_name] - - # Create all client objects - for client_name, client in clients_data.items(): - tcp_server.clients[client_name] = client_class( - name=client_name, - settings=client, - server_settings=server_settings) - - if not tcp_server.clients: - logger.warning("No clients defined") - - if not foreground: - if pidfile is not None: - pid = os.getpid() - try: - with pidfile: - print(pid, file=pidfile) - except IOError: - logger.error("Could not write to file %r with PID %d", - pidfilename, pid) - del pidfile - del pidfilename - - for termsig in (signal.SIGHUP, signal.SIGTERM): - GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig, - lambda: main_loop.quit() and False) - - if use_dbus: - - @alternate_dbus_interfaces( - {"se.recompile.Mandos": "se.bsnet.fukt.Mandos"}) - class MandosDBusService(DBusObjectWithObjectManager): - """A D-Bus proxy object""" - - def __init__(self): - dbus.service.Object.__init__(self, bus, "/") - - _interface = "se.recompile.Mandos" - - @dbus.service.signal(_interface, signature="o") - def ClientAdded(self, objpath): - "D-Bus signal" - pass - - @dbus.service.signal(_interface, signature="ss") - def ClientNotFound(self, key_id, address): - "D-Bus signal" - pass - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.signal(_interface, signature="os") - def ClientRemoved(self, objpath, name): - "D-Bus signal" - pass - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.method(_interface, out_signature="ao") - def GetAllClients(self): - "D-Bus method" - return dbus.Array(c.dbus_object_path for c in - tcp_server.clients.values()) - - @dbus_annotations({"org.freedesktop.DBus.Deprecated": - "true"}) - @dbus.service.method(_interface, - out_signature="a{oa{sv}}") - def GetAllClientsWithProperties(self): - "D-Bus method" - return dbus.Dictionary( - {c.dbus_object_path: c.GetAll( - "se.recompile.Mandos.Client") - for c in tcp_server.clients.values()}, - signature="oa{sv}") - - @dbus.service.method(_interface, in_signature="o") - def RemoveClient(self, object_path): - "D-Bus method" - for c in tcp_server.clients.values(): - if c.dbus_object_path == object_path: - del tcp_server.clients[c.name] - c.remove_from_connection() - # Don't signal the disabling - c.disable(quiet=True) - # Emit D-Bus signal for removal - self.client_removed_signal(c) - return - raise KeyError(object_path) - - del _interface - - @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, - out_signature="a{oa{sa{sv}}}") - def GetManagedObjects(self): - """D-Bus method""" - return dbus.Dictionary( - {client.dbus_object_path: - dbus.Dictionary( - {interface: client.GetAll(interface) - for interface in - client._get_all_interface_names()}) - for client in tcp_server.clients.values()}) - - def client_added_signal(self, client): - """Send the new standard signal and the old signal""" - if use_dbus: - # New standard signal - self.InterfacesAdded( - client.dbus_object_path, - dbus.Dictionary( - {interface: client.GetAll(interface) - for interface in - client._get_all_interface_names()})) - # Old signal - self.ClientAdded(client.dbus_object_path) - - def client_removed_signal(self, client): - """Send the new standard signal and the old signal""" - if use_dbus: - # New standard signal - self.InterfacesRemoved( - client.dbus_object_path, - client._get_all_interface_names()) - # Old signal - self.ClientRemoved(client.dbus_object_path, - client.name) - - mandos_dbus_service = MandosDBusService() - - # Save modules to variables to exempt the modules from being - # unloaded before the function registered with atexit() is run. - mp = multiprocessing - wn = wnull - + + debug = server_settings["debug"] + + if debug: + console = logging.StreamHandler() + # console.setLevel(logging.DEBUG) + console.setFormatter(logging.Formatter\ + ('%(levelname)s: %(message)s')) + logger.addHandler(console) + del console + + clients = Set() + def remove_from_clients(client): + clients.remove(client) + if not clients: + logger.debug(u"No clients left, exiting") + sys.exit() + + clients.update(Set(Client(name=section, + stop_hook = remove_from_clients, + **(dict(client_config\ + .items(section)))) + for section in client_config.sections())) + + if not debug: + daemon(False, False) + def cleanup(): "Cleanup function; run on exit" - if zeroconf: - service.cleanup() - - mp.active_children() - wn.close() - if not (tcp_server.clients or client_settings): - return - - # Store client before exiting. Secrets are encrypted with key - # based on what config file has. If config file is - # removed/edited, old secret will thus be unrecovable. - clients = {} - with PGPEngine() as pgp: - for client in tcp_server.clients.values(): - key = client_settings[client.name]["secret"] - client.encrypted_secret = pgp.encrypt(client.secret, - key) - client_dict = {} - - # A list of attributes that can not be pickled - # + secret. - exclude = {"bus", "changedstate", "secret", - "checker", "server_settings"} - for name, typ in inspect.getmembers(dbus.service - .Object): - exclude.add(name) - - client_dict["encrypted_secret"] = (client - .encrypted_secret) - for attr in client.client_structure: - if attr not in exclude: - client_dict[attr] = getattr(client, attr) - - clients[client.name] = client_dict - del client_settings[client.name]["secret"] - - try: - with tempfile.NamedTemporaryFile( - mode='wb', - suffix=".pickle", - prefix='clients-', - dir=os.path.dirname(stored_state_path), - delete=False) as stored_state: - pickle.dump((clients, client_settings), stored_state, - protocol=2) - tempname = stored_state.name - os.rename(tempname, stored_state_path) - except (IOError, OSError) as e: - if not debug: - try: - os.remove(tempname) - except NameError: - pass - if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST): - logger.warning("Could not save persistent state: {}" - .format(os.strerror(e.errno))) - else: - logger.warning("Could not save persistent state:", - exc_info=e) - raise - - # Delete all clients, and settings from config - while tcp_server.clients: - name, client = tcp_server.clients.popitem() - if use_dbus: - client.remove_from_connection() - # Don't signal the disabling - client.disable(quiet=True) - # Emit D-Bus signal for removal - if use_dbus: - mandos_dbus_service.client_removed_signal(client) - client_settings.clear() - + global group + # From the Avahi example code + if not group is None: + group.Free() + group = None + # End of Avahi example code + + while clients: + client = clients.pop() + client.stop_hook = None + client.stop() + atexit.register(cleanup) - - for client in tcp_server.clients.values(): - if use_dbus: - # Emit D-Bus signal for adding - mandos_dbus_service.client_added_signal(client) - # Need to initiate checking of clients - if client.enabled: - client.init_checker() - - tcp_server.enable() - tcp_server.server_activate() - + + if not debug: + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) + signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) + + for client in clients: + client.start() + + tcp_server = IPv6_TCPServer((server_settings["address"], + server_settings["port"]), + tcp_handler, + settings=server_settings, + clients=clients) # Find out what port we got - if zeroconf: - service.port = tcp_server.socket.getsockname()[1] - if use_ipv6: - logger.info("Now listening on address %r, port %d," - " flowinfo %d, scope_id %d", - *tcp_server.socket.getsockname()) - else: # IPv4 - logger.info("Now listening on address %r, port %d", - *tcp_server.socket.getsockname()) - - # service.interface = tcp_server.socket.getsockname()[3] - + service.port = tcp_server.socket.getsockname()[1] + logger.debug(u"Now listening on address %r, port %d, flowinfo %d," + u" scope_id %d" % tcp_server.socket.getsockname()) + + #service.interface = tcp_server.socket.getsockname()[3] + try: - if zeroconf: - # From the Avahi example code - try: - service.activate() - except dbus.exceptions.DBusException as error: - logger.critical("D-Bus Exception", exc_info=error) - cleanup() - sys.exit(1) - # End of Avahi example code - - GLib.io_add_watch( - GLib.IOChannel.unix_new(tcp_server.fileno()), - GLib.PRIORITY_DEFAULT, GLib.IO_IN, - lambda *args, **kwargs: (tcp_server.handle_request - (*args[2:], **kwargs) or True)) - + # From the Avahi example code + server.connect_to_signal("StateChanged", server_state_changed) + try: + server_state_changed(server.GetState()) + except dbus.exceptions.DBusException, error: + logger.critical(u"DBusException: %s", error) + sys.exit(1) + # End of Avahi example code + + gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN, + lambda *args, **kwargs: + tcp_server.handle_request\ + (*args[2:], **kwargs) or True) + logger.debug("Starting main loop") + main_loop_started = True main_loop.run() - except AvahiError as error: - logger.critical("Avahi Error", exc_info=error) - cleanup() + except AvahiError, error: + logger.critical(u"AvahiError: %s" + unicode(error)) sys.exit(1) except KeyboardInterrupt: if debug: - print("", file=sys.stderr) - logger.debug("Server received KeyboardInterrupt") - logger.debug("Server exiting") - # Must run before the D-Bus bus name gets deregistered - cleanup() - - -def should_only_run_tests(): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--check", action='store_true') - args, unknown_args = parser.parse_known_args() - run_tests = args.check - if run_tests: - # Remove --check argument from sys.argv - sys.argv[1:] = unknown_args - return run_tests - -# Add all tests from doctest strings -def load_tests(loader, tests, none): - import doctest - tests.addTests(doctest.DocTestSuite()) - return tests + print if __name__ == '__main__': - try: - if should_only_run_tests(): - # Call using ./mandos --check [--verbose] - unittest.main() - else: - main() - finally: - logging.shutdown() + main() === removed file 'sysusers.d-mandos.conf' --- sysusers.d-mandos.conf 2019-08-18 00:42:22 +0000 +++ sysusers.d-mandos.conf 1970-01-01 00:00:00 +0000 @@ -1,3 +0,0 @@ -# This file will be installed as mandos.conf and/or mandos-client.conf -# in the /usr/lib/sysusers.d directory. See sysusers.d(5) -u _mandos - "Mandos password system" === removed file 'tmpfiles.d-mandos.conf' --- tmpfiles.d-mandos.conf 2016-03-19 03:51:23 +0000 +++ tmpfiles.d-mandos.conf 1970-01-01 00:00:00 +0000 @@ -1,1 +0,0 @@ -d /var/lib/mandos 700 _mandos _mandos