=== added directory '.bzr-builddeb' === added file '.bzr-builddeb/default.conf' --- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000 +++ .bzr-builddeb/default.conf 2008-09-17 00:34:09 +0000 @@ -0,0 +1,2 @@ +[BUILDDEB] +split = True === added file '.bzrignore' --- .bzrignore 1970-01-01 00:00:00 +0000 +++ .bzrignore 2012-05-17 01:55:58 +0000 @@ -0,0 +1,14 @@ +*.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 === added file 'COPYING' --- COPYING 1970-01-01 00:00:00 +0000 +++ COPYING 2008-08-15 20:17:32 +0000 @@ -0,0 +1,676 @@ + + 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 +. + === added file 'DBUS-API' --- DBUS-API 1970-01-01 00:00:00 +0000 +++ DBUS-API 2012-01-15 20:27:28 +0000 @@ -0,0 +1,181 @@ + -*- 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 | + | "/clients/CLIENTNAME" | Mandos Client | + + +* Mandos Server Interface: + Interface name: "se.recompile.Mandos" + +** Methods: +*** GetAllClients() → (ao: Clients) + Returns an array of all client D-Bus object paths + +*** GetAllClientsWithProperties() → (a{oa{sv}}: ClientProperties) + Returns an array of all clients and all their properties + +*** RemoveClient(o: ObjectPath) → nothing + Removes a client + +** Signals: +*** ClientAdded(o: ObjectPath) + A new client was added. + +*** ClientNotFound(s: Fingerprint, s: Address) + A client connected from Address using Fingerprint, but was + rejected because it was not found in the server. The fingerprint + is represented as a string of hexadecimal digits. The address is + an IPv4 or IPv6 address in its normal string format. + +*** ClientRemoved(o: ObjectPath, s: Name) + A client named Name on ObjectPath was removed. + + +* 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. + +*** Disable() → nothing + Disable this client. See also the "Enabled" property. + +*** Enable() → nothing + Enable this client. See also the "Enabled" property. + +*** StartChecker() → nothing + Start a new checker for this client, if none is currently + running. See also the "CheckerRunning" property. + +*** StopChecker() → nothing + Abort a running checker process for this client, if any. See also + the "CheckerRunning" 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 | + | 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) | + | ObjectPath | o | Read | N/A | + | Secret (k) | ay | Write | secret (or secfile) | + | Timeout (a) | t | Read/Write | timeout | + + a) Represented as milliseconds. + + b) An approval is currently pending. + + c) Setting this property is equivalent to calling StartChecker() or + StopChecker(). + + d) The creation time of this client object, as an RFC 3339 string. + + e) Setting this property is equivalent to calling Enable() or + Disable(). + + 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. + +*** PropertyChanged(s: Property, v: Value) + The Property on this client has changed to Value. + +*** Rejected(s: Reason) + This client was not given its secret for a specified Reason. + +* Copyright + + Copyright © 2010-2012 Teddy Hogeborn + Copyright © 2010-2012 Björn Påhlsson + +** License: + + 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 + . + + +#+STARTUP: showall === added file 'INSTALL' --- INSTALL 1970-01-01 00:00:00 +0000 +++ INSTALL 2013-06-23 15:13:06 +0000 @@ -0,0 +1,137 @@ +-*- org -*- + +* Prerequisites + +** Operating System + + Debian 6.0 "squeeze" or Ubuntu 10.10 "Maverick Meerkat". + + 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 initrd.img 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/topic/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 2.4 http://www.gnu.org/software/gnutls/ + + Avahi 0.6.16 http://www.avahi.org/ + + Python 2.6 http://www.python.org/ + + Python-GnuTLS 1.1.5 http://pypi.python.org/pypi/python-gnutls/ + + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ + + PyGObject 2.14.2 http://library.gnome.org/devel/pygobject/ + + Urwid 0.9.8.3 http://excess.org/urwid/ + + Strongly recommended: + + fping 2.4b2-to-ipv6 http://www.fping.com/ + + Package names: + python-gnutls avahi-daemon python python-avahi python-dbus + python-gobject python-urwid + +*** Mandos Client + + initramfs-tools 0.85i + http://packages.qa.debian.org/i/initramfs-tools.html + + GnuTLS 2.4 http://www.gnu.org/software/gnutls/ + + Avahi 0.6.16 http://www.avahi.org/ + + GnuPG 1.4.9 http://www.gnupg.org/ + + GPGME 1.1.6 http://www.gnupg.org/related_software/gpgme/ + + Package names: + initramfs-tools libgnutls-dev libavahi-core-dev gnupg + libgpgme11-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 the correct network interface. The + interface to use is automatically chosen at boot, and if this + needs to be adjusted, it will be necessary to edit + /etc/initramfs-tools/initramfs.conf to change the DEVICE setting + there. Alternatively, the file /etc/mandos/plugin-runner.conf + can be edited to add a "--device" parameter for the + mandos-client(8) plugin. Please note: If any of those files are + changed, 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; 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 an hour 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, it is suggested that a more cryptographically + secure checker program is used and configured, since without IPsec + ping packets can be faked. === modified file 'Makefile' --- Makefile 2008-07-20 02:52:20 +0000 +++ Makefile 2012-06-22 23:33:56 +0000 @@ -1,13 +1,446 @@ -CFLAGS="-Wall -std=gnu99" -LDFLAGS=-lgnutls - -all: plugbasedclient +WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs \ + -Wswitch-default -Wswitch-enum -Wunused-parameter \ + -Wstrict-aliasing=1 -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 -Winline -Wvolatile-register-var +# -Wunreachable-code +#DEBUG=-ggdb3 +# For info about _FORTIFY_SOURCE, see +# +# 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 +#COVERAGE=--coverage +OPTIMIZE=-Os +LANGUAGE=-std=gnu99 +htmldir=man +version=1.6.0 +SED=sed + +USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) +GROUP=$(firstword $(subst :, ,$(shell getent group _mandos || getent group nobody || echo 65534))) + +## 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 +# STATEDIR=$(DESTDIR)/var/lib/mandos +## + +## 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 +STATEDIR=$(DESTDIR)/var/lib/mandos +## + +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) + +# Do not change these two +CFLAGS=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ + $(LANGUAGE) $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) \ + -DVERSION='"$(version)"' +LDFLAGS=-Xlinker --as-needed $(COVERAGE) $(LINK_FORTIFY) $(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 $<); \ + $(MANPOST) $(notdir $@);\ + if locale --all 2>/dev/null | grep --regexp='^en_US\.utf8$$' \ + && type man 2>/dev/null; then LANG=en_US.UTF-8 MANWIDTH=80 \ + man --warnings --encoding=UTF-8 --local-file $(notdir $@); \ + fi >/dev/null) +# DocBook-to-man post-processing to fix a '\n' escape bug +MANPOST=$(SED) --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g' + +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 +CPROGS=plugin-runner $(PLUGINS) +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 \ + 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) + +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/' \ + $@) + +plugins.d/mandos-client: plugins.d/mandos-client.c + $(LINK.c) $^ -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ + ) $(GPGME_LIBS) $(LOADLIBES) $(LDLIBS) -o $@ + +.PHONY : all doc html clean distclean run-client run-server install \ + install-server install-client uninstall uninstall-server \ + uninstall-client purge purge-server purge-client clean: - rm -f plugbasedclient - -client_debug: client - mv -f client client.tmp - $(MAKE) client CXXFLAGS="$(CXXFLAGS) -DDEBUG -DCERT_ROOT=\\\"./\\\"" - mv client client_debug - mv client.tmp client + -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 + +# Run the client with a local config and key +run-client: all keydir/seckey.txt keydir/pubkey.txt + @echo "###################################################################" + @echo "# The following error messages are harmless and can be safely #" + @echo "# ignored. The messages are caused by not running as root, but #" + @echo "# you should NOT run \"make run-client\" as root unless you also #" + @echo "# unpacked and compiled Mandos as root, which is NOT recommended. #" + @echo "# From plugin-runner: setuid: Operation not permitted #" + @echo "# From askpass-fifo: mkfifo: Permission denied #" + @echo "# From mandos-client: setuid: Operation not permitted #" + @echo "# seteuid: Operation not permitted #" + @echo "# klogctl: Operation not permitted #" + @echo "###################################################################" + ./plugin-runner --plugin-dir=plugins.d \ + --config-file=plugin-runner.conf \ + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ + $(CLIENTARGS) + +# Used by run-client +keydir/seckey.txt keydir/pubkey.txt: 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 + install --directory confdir + install --mode=u=rw $< $@ +# Add a client password + ./mandos-keygen --dir keydir --password >> $@ +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) + install --directory --mode=u=rwx --owner=$(USER) \ + --group=$(GROUP) $(STATEDIR) + 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 + 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 $(PREFIX)/lib/mandos $(CONFDIR) + install --directory --mode=u=rwx $(KEYDIR) \ + $(PREFIX)/lib/mandos/plugins.d + if [ "$(CONFDIR)" != "$(PREFIX)/lib/mandos" ]; then \ + install --mode=u=rwx \ + --directory "$(CONFDIR)/plugins.d"; \ + fi + install --mode=u=rwx,go=rx --directory \ + "$(CONFDIR)/network-hooks.d" + install --mode=u=rwx,go=rx \ + --target-directory=$(PREFIX)/lib/mandos plugin-runner + install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ + mandos-keygen + install --mode=u=rwx,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/password-prompt + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/mandos-client + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/usplash + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/splashy + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/askpass-fifo + install --mode=u=rwxs,go=rx \ + --target-directory=$(PREFIX)/lib/mandos/plugins.d \ + plugins.d/plymouth + install initramfs-tools-hook \ + $(INITRAMFSTOOLS)/hooks/mandos + install --mode=u=rw,go=r initramfs-tools-hook-conf \ + $(INITRAMFSTOOLS)/conf-hooks.d/mandos + install initramfs-tools-script \ + $(INITRAMFSTOOLS)/scripts/init-premount/mandos + 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 + +install-client: install-client-nokey +# Post-installation stuff + -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)" + update-initramfs -k all -u + 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 \ + $(PREFIX)/lib/mandos/plugin-runner \ + $(PREFIX)/lib/mandos/plugins.d/password-prompt \ + $(PREFIX)/lib/mandos/plugins.d/mandos-client \ + $(PREFIX)/lib/mandos/plugins.d/usplash \ + $(PREFIX)/lib/mandos/plugins.d/splashy \ + $(PREFIX)/lib/mandos/plugins.d/askpass-fifo \ + $(PREFIX)/lib/mandos/plugins.d/plymouth \ + $(INITRAMFSTOOLS)/hooks/mandos \ + $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ + $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ + $(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 \ + -rmdir $(PREFIX)/lib/mandos/plugins.d $(CONFDIR)/plugins.d \ + $(PREFIX)/lib/mandos $(CONFDIR) $(KEYDIR) + update-initramfs -k all -u + +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 \ + $(DESTDIR)/var/run/mandos.pid + -rmdir $(CONFDIR) + +purge-client: uninstall-client + -shred --remove $(KEYDIR)/seckey.txt + -rm --force $(CONFDIR)/plugin-runner.conf \ + $(KEYDIR)/pubkey.txt $(KEYDIR)/seckey.txt + -rmdir $(KEYDIR) $(CONFDIR)/plugins.d $(CONFDIR) === added file 'NEWS' --- NEWS 1970-01-01 00:00:00 +0000 +++ NEWS 2012-06-17 22:26:40 +0000 @@ -0,0 +1,262 @@ +This NEWS file records noteworthy changes, very tersely. +See the manual for detailed information. + +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. === added file 'README' --- README 1970-01-01 00:00:00 +0000 +++ README 2012-01-01 20:45:53 +0000 @@ -0,0 +1,11 @@ +Please see: http://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 2008-07-19 18:43:24 +0000 +++ TODO 2013-06-23 15:30:34 +0000 @@ -1,16 +1,125 @@ -[Client] -configuration for cert, key, CA and interface -IPv4 support -OpenPGP keys support - -[Server] -config file -client-list -run-time communication with server - -handle SIGTERM, SIGQUIT, etc. - -[Mandos-tools/utilities] - List clients - Enable client - Disable client +-*- org -*- + +* [[http://www.undeadly.org/cgi?action=article&sid=20110530221728][OpenBSD]] + +* Testing +** python-nemu + +* mandos-applet + +* mandos-client +** TODO [#B] Use capabilities instead of seteuid(). +** TODO [#B] Use struct sockaddr_storage instead of a union +** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() +** TODO [#B] Use getnameinfo(serv=NULL, NI_NUMERICHOST) instead of inet_ntop() +** TODO [#B] Prefer /run/tmp over /tmp, if it exists +** TODO [#C] Make start_mandos_communication() take "struct server". + +* splashy +** TODO [#B] use scandir(3) instead of readdir(3) + +* usplash (Deprecated) +** TODO [#A] Make it work again +** TODO [#B] use scandir(3) instead of readdir(3) + +* askpass-fifo +** TODO [#B] Drop privileges after opening FIFO. + +* password-prompt +** TODO [#B] lock stdin (with flock()?) + +* plymouth + +* 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 [#B] use scandir(3) instead of readdir(3) +** TODO [#C] use same file name rules as run-parts(8) +** kernel command line option for debug info +** TODO [#B] Use openat() + +* mandos (server) +** 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 [#C] systemd/launchd + http://0pointer.de/blog/projects/systemd.html + http://wiki.debian.org/systemd +** TODO Separate logging logic to own object +** TODO [#A] 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 Use python-tlslite? +** TODO D-Bus AddClient() method on server object +** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. :2: +** TODO Emit [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties][org.freedesktop.DBus.Properties.PropertiesChanged]] signal :2: + TODO Deprecate se.recompile.Mandos.Client.PropertyChanged - annotate! + TODO Can use "invalidates" annotation to also emit on changed secret. +** TODO Support [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager][org.freedesktop.DBus.ObjectManager]] interface on server object :2: + Deprecate methods GetAllClients(), GetAllClientsWithProperties() + and signals ClientAdded and ClientRemoved. +** TODO Save state periodically to recover better from hard shutdowns +** TODO CheckerCompleted method, deprecate CheckedOK +** TODO Secret Service API? + http://standards.freedesktop.org/secret-service/ +** TODO Remove D-Bus interfaces with old domain name :2: +** TODO Remove old string_to_delta format :2: +** TODO --no-zeroconf (only valid if port or socket is set) + +* mandos.xml +** Add mandos contact info in manual pages + +* mandos-ctl +*** Handle "no D-Bus server" and/or "no Mandos server found" better +*** [#B] --dump option +** 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 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. +** Rename module "gobject" to "GObject". + +* mandos-keygen +** TODO "--secfile" option + Using the "secfile" option instead of "secret" +** TODO [#B] "--test" option + For testing decryption before rebooting. + +* Makefile +** TODO [#C] Implement DEB_BUILD_OPTIONS + http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options + +* 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] /etc/bash_completion.d/mandos + 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 + + +#+STARTUP: showall === removed file 'ca.pem' --- ca.pem 2007-10-20 21:38:25 +0000 +++ ca.pem 1970-01-01 00:00:00 +0000 @@ -1,40 +0,0 @@ ------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----- === removed file 'cert.pem' --- cert.pem 2007-10-20 21:38:25 +0000 +++ cert.pem 1970-01-01 00:00:00 +0000 @@ -1,40 +0,0 @@ ------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----- === removed file 'client-cert.pem' --- client-cert.pem 2007-10-20 21:38:25 +0000 +++ client-cert.pem 1970-01-01 00:00:00 +0000 @@ -1,40 +0,0 @@ ------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----- === removed file 'client-key.pem' --- client-key.pem 2007-10-20 21:38:25 +0000 +++ client-key.pem 1970-01-01 00:00:00 +0000 @@ -1,51 +0,0 @@ ------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----- === renamed file 'mandos-clients.conf' => 'clients.conf' --- mandos-clients.conf 2008-07-20 02:52:20 +0000 +++ clients.conf 2012-06-23 00:58:49 +0000 @@ -1,26 +1,87 @@ -# Example -#[foo] -#fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 -#secfile = openpgp/secret.txt.asc -#fqdn = foo.example.org -#checker = echo %%(fqdn)s >&2; false -# -[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 +# 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] +; +;# 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 fingerprint is not space or case sensitive +;fingerprint = 3e393aeaefb84c7e89e2f547b3a107558fca3a27 +; +;# If "secret" is not specified, a file can be read for the data. +;secfile = /etc/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 +;#### === added file 'common.ent' --- common.ent 1970-01-01 00:00:00 +0000 +++ common.ent 2012-06-17 22:26:40 +0000 @@ -0,0 +1,3 @@ + + + === removed file 'crl.pem' --- crl.pem 2007-10-20 21:38:25 +0000 +++ crl.pem 1970-01-01 00:00:00 +0000 @@ -1,18 +0,0 @@ ------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----- === added file 'dbus-mandos.conf' --- dbus-mandos.conf 1970-01-01 00:00:00 +0000 +++ dbus-mandos.conf 2011-10-02 19:18:24 +0000 @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + === added directory 'debian' === added file 'debian/changelog' --- debian/changelog 1970-01-01 00:00:00 +0000 +++ debian/changelog 2012-06-17 22:26:40 +0000 @@ -0,0 +1,346 @@ +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 === added file 'debian/compat' --- debian/compat 1970-01-01 00:00:00 +0000 +++ debian/compat 2008-09-17 00:34:09 +0000 @@ -0,0 +1,1 @@ +7 === added file 'debian/control' --- debian/control 1970-01-01 00:00:00 +0000 +++ debian/control 2013-10-05 19:34:40 +0000 @@ -0,0 +1,56 @@ +Source: mandos +Section: admin +Priority: extra +Maintainer: Mandos Maintainers +Uploaders: Teddy Hogeborn , + Björn Påhlsson +Build-Depends: debhelper (>= 8.9.7), docbook-xml, docbook-xsl, + libavahi-core-dev, libgpgme11-dev, libgnutls-dev, xsltproc, + pkg-config +Standards-Version: 3.9.3 +Vcs-Bzr: http://ftp.recompile.se/pub/mandos/trunk +Vcs-Browser: http://bzr.recompile.se/loggerhead/mandos/trunk/files +Homepage: http://www.recompile.se/mandos +DM-Upload-Allowed: yes + +Package: mandos +Architecture: all +Depends: ${misc:Depends}, python (>=2.6), python-gnutls, python-dbus, + python-avahi, python-gobject, avahi-daemon, adduser, + python-urwid, python (>=2.7) | python-argparse, gnupg (<< 2) +Recommends: fping +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 an OpenPGP + 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 the same 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, + gnupg (<< 2), initramfs-tools +Breaks: dropbear (<= 0.53.1-1) +Enhances: cryptsetup +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 an OpenPGP + 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 the same OpenPGP key, and the + password is then used to unlock the root file system, + whereupon the computers can continue booting normally. === added file 'debian/copyright' --- debian/copyright 1970-01-01 00:00:00 +0000 +++ debian/copyright 2012-06-01 21:48:12 +0000 @@ -0,0 +1,25 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Mandos +Upstream-Contact: Mandos +Source: + +Files: * +Copyright: Copyright © 2008-2012 Teddy Hogeborn + Copyright © 2008-2012 Björn Påhlsson +License: GPL-3+ + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see + . + . + On Debian systems, the complete text of the GNU General Public + License can be found in "/usr/share/common-licenses/GPL". === added file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.README.Debian 2013-09-29 15:52:19 +0000 @@ -0,0 +1,93 @@ +* 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: + + /usr/lib/mandos/plugins.d/mandos-client \ + --pubkey=/etc/keys/mandos/pubkey.txt \ + --seckey=/etc/keys/mandos/seckey.txt; 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 interface.) *If* the DEVICE + setting is changed, it will be necessary to update the initrd image + by running the command + + update-initramfs -k all -u + + 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 "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: + + update-initramfs -k all -u + +* 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 it 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". + + -- Teddy Hogeborn , Sun, 23 Jun 2013 17:31:53 +0200 === added file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 @@ -0,0 +1,5 @@ +usr/share/man/man8 +usr/sbin +usr/share/initramfs-tools/hooks +usr/share/initramfs-tools/conf-hooks.d +usr/share/initramfs-tools/scripts/init-premount === added file 'debian/mandos-client.docs' --- debian/mandos-client.docs 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.docs 2012-06-01 21:48:12 +0000 @@ -0,0 +1,3 @@ +NEWS +README +TODO === added file 'debian/mandos-client.examples' --- debian/mandos-client.examples 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.examples 2012-06-01 21:48:12 +0000 @@ -0,0 +1,1 @@ +network-hooks.d === added file 'debian/mandos-client.links' --- debian/mandos-client.links 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.links 2008-09-19 13:50:22 +0000 @@ -0,0 +1,1 @@ +usr/share/man/man8/plugin-runner.8mandos.gz usr/share/man/man5/plugin-runner.conf.5mandos.gz === added file 'debian/mandos-client.lintian-overrides' --- debian/mandos-client.lintian-overrides 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.lintian-overrides 2010-10-13 06:12:52 +0000 @@ -0,0 +1,28 @@ +# 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 + +# 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 === added file 'debian/mandos-client.postinst' --- debian/mandos-client.postinst 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.postinst 2011-10-10 20:29:58 +0000 @@ -0,0 +1,83 @@ +#!/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 + +set -e + +# Update the initial RAM file system image +update_initramfs() +{ + if [ -x /usr/sbin/update-initramfs ]; then + update-initramfs -u -k all + 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 pair +create_key(){ + if [ -r /etc/keys/mandos/pubkey.txt \ + -a -r /etc/keys/mandos/seckey.txt ]; then + return 0 + fi + if [ -x /usr/sbin/mandos-keygen ]; then + mandos-keygen + fi +} + +case "$1" in + configure) + add_mandos_user "$@" + create_key "$@" + update_initramfs "$@" + ;; + abort-upgrade|abort-deconfigure|abort-remove) + ;; + + *) + echo "$0 called with unknown argument '$1'" 1>&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 === added file 'debian/mandos-client.postrm' --- debian/mandos-client.postrm 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.postrm 2011-10-10 20:29:58 +0000 @@ -0,0 +1,62 @@ +#!/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 [ -x /usr/sbin/update-initramfs ]; then + update-initramfs -u -k all + 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 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 === added file 'debian/mandos.README.Debian' --- debian/mandos.README.Debian 1970-01-01 00:00:00 +0000 +++ debian/mandos.README.Debian 2011-10-05 16:00:56 +0000 @@ -0,0 +1,10 @@ +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 === added file 'debian/mandos.dirs' --- debian/mandos.dirs 1970-01-01 00:00:00 +0000 +++ debian/mandos.dirs 2011-11-26 22:22:20 +0000 @@ -0,0 +1,7 @@ +usr/share/man/man5 +usr/share/man/man8 +etc/init.d +etc/default +etc/dbus-1/system.d +usr/sbin +var/lib/mandos === added file 'debian/mandos.docs' --- debian/mandos.docs 1970-01-01 00:00:00 +0000 +++ debian/mandos.docs 2010-09-12 03:00:40 +0000 @@ -0,0 +1,4 @@ +NEWS +README +TODO +DBUS-API === added file 'debian/mandos.lintian-overrides' --- debian/mandos.lintian-overrides 1970-01-01 00:00:00 +0000 +++ debian/mandos.lintian-overrides 2008-10-01 15:29:01 +0000 @@ -0,0 +1,4 @@ +# 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 === added file 'debian/mandos.postinst' --- debian/mandos.postinst 1970-01-01 00:00:00 +0000 +++ debian/mandos.postinst 2011-11-26 22:22:20 +0000 @@ -0,0 +1,52 @@ +#!/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 + +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 + ;; + 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 + chown _mandos:_mandos /var/lib/mandos + ;; + + abort-upgrade|abort-deconfigure|abort-remove) + ;; + + *) + echo "$0 called with unknown argument '$1'" 1>&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 === added file 'debian/mandos.prerm' --- debian/mandos.prerm 1970-01-01 00:00:00 +0000 +++ debian/mandos.prerm 2011-10-10 20:29:58 +0000 @@ -0,0 +1,38 @@ +#!/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) + if [ -x /etc/init.d/mandos ]; then + if [ -x /usr/sbin/invoke-rc.d ]; then + invoke-rc.d mandos stop + else + /etc/init.d/mandos stop + fi + fi + ;; + upgrade|failed-upgrade) + ;; + *) + echo "prerm called with unknown argument '$1'" >&2 + exit 0 + ;; +esac + +#DEBHELPER# + +exit 0 === added directory 'debian/po' === added file 'debian/rules' --- debian/rules 1970-01-01 00:00:00 +0000 +++ debian/rules 2013-10-05 19:34:40 +0000 @@ -0,0 +1,29 @@ +#!/usr/bin/make -f +%: + 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: + dh_fixperms --exclude etc/keys/mandos \ + --exclude etc/mandos/clients.conf \ + --exclude etc/mandos/plugins.d \ + --exclude usr/lib/mandos/plugins.d \ + --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" === added directory 'debian/source' === added file 'debian/source/format' --- debian/source/format 1970-01-01 00:00:00 +0000 +++ debian/source/format 2010-10-02 17:41:05 +0000 @@ -0,0 +1,1 @@ +3.0 (quilt) === added file 'debian/source/local-options' --- debian/source/local-options 1970-01-01 00:00:00 +0000 +++ debian/source/local-options 2011-10-08 21:13:46 +0000 @@ -0,0 +1,1 @@ +--single-debian-patch === added file 'debian/watch' --- debian/watch 1970-01-01 00:00:00 +0000 +++ debian/watch 2011-10-05 16:00:56 +0000 @@ -0,0 +1,2 @@ +version=3 +ftp://ftp.recompile.se/pub/mandos/mandos[-_]([^\s]+?)(?:\.orig)?\.tar\.(?:gz|bz2|7z|xz) === added file 'default-mandos' --- default-mandos 1970-01-01 00:00:00 +0000 +++ default-mandos 2008-09-17 00:34:09 +0000 @@ -0,0 +1,7 @@ +# Directory where configuration files are located. Default is +# "/etc/mandos". +# +#CONFIGDIR=/etc/mandos + +# Additional options that are passed to the Daemon. +DAEMON_ARGS="" === added file 'init.d-mandos' --- init.d-mandos 1970-01-01 00:00:00 +0000 +++ init.d-mandos 2012-06-01 21:48:12 +0000 @@ -0,0 +1,162 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: mandos +# Required-Start: $remote_fs $syslog avahi +# Required-Stop: $remote_fs $syslog avahi +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Mandos server +# Description: Gives encrypted passwords to Mandos clients +### END INIT INFO + +# Author: Teddy Hogeborn +# Author: Björn Påhlsson +# +# Please remove the "Author" lines above and replace them +# with your own name if you copy and modify this script. + +# 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="" +PIDFILE=/var/run/$NAME.pid +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.0-6) to ensure that this file is present. +. /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 + ;; + #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 + ;; + status) + status_of_proc "$DAEMON" "$NAME" -p "$PIDFILE" + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: === added file 'initramfs-tools-hook' --- initramfs-tools-hook 1970-01-01 00:00:00 +0000 +++ initramfs-tools-hook 2013-10-13 01:49:18 +0000 @@ -0,0 +1,222 @@ +#!/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 /usr/local; do + if [ -d "$d"/lib/mandos ]; then + prefix="$d" + break + fi +done +if [ -z "$prefix" ]; 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" +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}" + +# Copy the Mandos plugin runner +copy_exec "$prefix"/lib/mandos/plugin-runner "${MANDOSDIR}" + +# Copy the plugins + +# Copy the packaged plugins +for file in "$prefix"/lib/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 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 + +# 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 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 module; do + if [ -z "${target}" ]; then + force_load "$module" + fi + done + fi +done + +# GPGME needs GnuPG +libgpgme11_version="`dpkg-query --showformat='${Version}' --show libgpgme11`" +if dpkg --compare-versions "$libgpgme11_version" ge 1.4.1-0.1; then + gpg=/usr/bin/gpg2 +else + gpg=/usr/bin/gpg +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 + cp --archive --sparse=always "$file" "${DESTDIR}${CONFDIR}" + chown ${mandos_user}:${mandos_group} \ + "${DESTDIR}${CONFDIR}/`basename \"$file\"`" +done + +# /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-hook-conf", +# installed as "/usr/share/initramfs-tools/conf-hooks.d/mandos".) +# +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 -print0 \ + | xargs --null --no-run-if-empty chmod a+rX + fi +done === added file 'initramfs-tools-hook-conf' --- initramfs-tools-hook-conf 1970-01-01 00:00:00 +0000 +++ initramfs-tools-hook-conf 2009-05-17 00:50:09 +0000 @@ -0,0 +1,13 @@ +# -*- shell-script -*- + +# if mkinitramfs is started by mkinitramfs-kpkg, mkinitramfs-kpkg has +# already touched the initrd file with umask 022 before we had a +# chance to affect it. We cannot allow a readable initrd file, +# therefore we must fix this now. +if [ -e "${outfile}" ] \ + && [ `stat --format=%s "${outfile}"` -eq 0 ]; then + rm "${outfile}" + (umask 027; touch "${outfile}") +fi + +UMASK=027 === added file 'initramfs-tools-script' --- initramfs-tools-script 1970-01-01 00:00:00 +0000 +++ initramfs-tools-script 2011-07-16 00:29:19 +0000 @@ -0,0 +1,152 @@ +#!/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 + +test -r /conf/conf.d/cryptroot +test -w /conf/conf.d + +# 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 + configure_networking + if [ -n "$connect" ]; then + cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf + + --options-for=mandos-client:--connect=${connect} +EOF + fi +fi + +# 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,key=none,keyscript=/foo/bar/baz +exec 3>/conf/conf.d/cryptroot.mandos +while read options; do + newopts="" + # 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" + ;; + "") : ;; + *) + newopts="$newopts,$opt" + ;; + esac + done + IFS="$old_ifs" + unset old_ifs + # If there was no keyscript option, add one. + if [ -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 /conf/conf.d/cryptroot.mandos +fi === added file 'initramfs-unpack' --- initramfs-unpack 1970-01-01 00:00:00 +0000 +++ initramfs-unpack 2013-10-13 15:43:42 +0000 @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Initramfs unpacker - unpacks initramfs images into /tmp +# +# Copyright © 2013 Teddy Hogeborn +# Copyright © 2013 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 this program. 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)) + catimg="dd if=$imgfile bs=$skip skip=1 status=noxfer" + else + catimg="cat -- $imgfile" + fi + # 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 + echo "Error: Could not determine type of $imgfile" >&2 + continue + fi + $catimg 2>/dev/null | $decomp | ( cd -- "$imgdir" && $cpio --quiet ) + if [ ${PIPESTATUS[-1]} -eq 0 ]; then + echo "$imgfile unpacked into $imgdir" + fi +done === added file 'intro.xml' --- intro.xml 1970-01-01 00:00:00 +0000 +++ intro.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,411 @@ + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2011 + 2012 + 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 an OpenPGP 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 the same 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 it to get the password, use it to + decrypt the root file, and 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 OpenPGP private key corresponding to + that client. + + + + + 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 ping replies? + + The default for the server is 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. It could, for instance, be changed to an + SSH command with strict keychecking, which could not be faked. + Or 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. + + + + + SEE ALSO + + mandos + 8, + mandos.conf + 5, + mandos-clients.conf + 5, + mandos-ctl + 8, + mandos-monitor + 8, + plugin-runner + 8mandos, + mandos-client + 8mandos, + password-prompt + 8mandos, + plymouth + 8mandos, + usplash + 8mandos, + splashy + 8mandos, + askpass-fifo + 8mandos, + mandos-keygen + 8 + + + + + Mandos + + + + The Mandos home page. + + + + + +
+ + + + + === removed file 'key.pem' --- key.pem 2007-10-20 21:38:25 +0000 +++ key.pem 1970-01-01 00:00:00 +0000 @@ -1,51 +0,0 @@ ------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----- === added file 'legalnotice.xml' --- legalnotice.xml 1970-01-01 00:00:00 +0000 +++ legalnotice.xml 2008-09-06 17:24:58 +0000 @@ -0,0 +1,27 @@ + + + + + This manual page 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 manual page 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 + http://www.gnu.org/licenses/. + + === renamed file 'server.py' => 'mandos' --- server.py 2008-07-20 02:52:20 +0000 +++ mandos 2013-06-23 15:13:06 +0000 @@ -1,11 +1,44 @@ #!/usr/bin/python - -from __future__ import division - -import SocketServer +# -*- 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". +# +# Everything else is +# Copyright © 2008-2012 Teddy Hogeborn +# Copyright © 2008-2012 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 this program. If not, see +# . +# +# Contact the authors at . +# + +from __future__ import (division, absolute_import, print_function, + unicode_literals) + +from future_builtins import * + +import SocketServer as socketserver import socket -import select -from optparse import OptionParser +import argparse import datetime import errno import gnutls.crypto @@ -14,403 +47,2174 @@ import gnutls.library.functions import gnutls.library.constants import gnutls.library.types -import ConfigParser +import ConfigParser as 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 +import cPickle as pickle +import multiprocessing +import types +import binascii +import tempfile +import itertools +import collections import dbus +import dbus.service import gobject import avahi from dbus.mainloop.glib import DBusGMainLoop import ctypes - -import logging -import logging.handlers - -# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s') - -logger = logging.Logger('mandos') -logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON)) - -# This variable is used to optionally bind to a specified interface. -# It is a global variable to fit in with the other variables from the -# Avahi server example code. -serviceInterface = avahi.IF_UNSPEC -# From the Avahi server example code: -serviceName = "Mandos" -serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html -servicePort = None # Not known at startup -serviceTXT = [] # TXT record for the service -domain = "" # Domain to publish on, default to .local -host = "" # Host to publish records for, default to localhost -group = None #our entry group -rename_count = 12 # Counter so we only rename after collisions a - # sensible number of times -# End of Avahi example code +import ctypes.util +import xml.dom.minidom +import inspect + +try: + SO_BINDTODEVICE = socket.SO_BINDTODEVICE +except AttributeError: + try: + from IN import SO_BINDTODEVICE + except ImportError: + SO_BINDTODEVICE = None + +version = "1.6.0" +stored_state_file = "clients.pickle" + +logger = logging.getLogger() +syslogger = (logging.handlers.SysLogHandler + (facility = logging.handlers.SysLogHandler.LOG_DAEMON, + address = str("/dev/log"))) + +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(str("16s16x"), + interface)) + interface_index = struct.unpack(str("I"), + ifreq[16:20])[0] + return interface_index + + +def initlogger(debug, level=logging.WARNING): + """init logger and add loglevel""" + + 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(object): + """A simple class for OpenPGP symmetric encryption & decryption""" + def __init__(self): + self.tempdir = tempfile.mkdtemp(prefix="mandos-") + self.gnupgargs = ['--batch', + '--home', self.tempdir, + '--force-mdc', + '--quiet', + '--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. + return b"mandos" + binascii.hexlify(password) + + 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(['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(['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 + + +class AvahiError(Exception): + def __init__(self, value, *args, **kwargs): + self.value = value + super(AvahiError, self).__init__(value, *args, **kwargs) + def __unicode__(self): + return unicode(repr(self.value)) + +class AvahiServiceError(AvahiError): + pass + +class AvahiGroupError(AvahiError): + pass + + +class AvahiService(object): + """An Avahi (Zeroconf) service. + + Attributes: + 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() + """ + + 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): + self.interface = interface + self.name = name + self.type = servicetype + self.port = port + self.TXT = TXT if TXT is not None else [] + 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): + """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) + raise AvahiServiceError("Too many renames") + self.name = unicode(self.server + .GetAlternativeServiceName(self.name)) + logger.info("Changing Zeroconf service name to %r ...", + self.name) + self.remove() + try: + self.add() + except dbus.exceptions.DBusException as error: + logger.critical("D-Bus Exception", exc_info=error) + self.cleanup() + os._exit(1) + self.rename_count += 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() + + 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", + unicode(error)) + raise AvahiGroupError("State changed: {0!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: + self.add() + 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): + """Add the new name to the syslog messages""" + ret = AvahiService.rename(self) + syslogger.setFormatter(logging.Formatter + ('Mandos ({0}) [%(process)d]:' + ' %(levelname)s: %(message)s' + .format(self.name))) + return ret + + +def timedelta_to_milliseconds(td): + "Convert a datetime.timedelta() to milliseconds" + return ((td.days * 24 * 60 * 60 * 1000) + + (td.seconds * 1000) + + (td.microseconds // 1000)) class Client(object): """A representation of a client host served by this server. + Attributes: - name: string; from the config file, used in log messages + 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: subprocess.Popen(); a running checker process used + to see if the client lives. + 'None' if no process is running. + checker_callback_tag: a gobject event source tag, or None + 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 gobject 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 gobject event source tag, or None + enabled: bool() 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() - last_seen: datetime.datetime() or None if not yet seen - timeout: datetime.timedelta(); How long from last_seen 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. - Is 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. %()s expansions are done at - runtime with vars(self) as dict, so that for - instance %(name)s can be used in the command. - Private attibutes: - _timeout: Real variable for 'timeout' - _interval: Real variable for 'interval' - _timeout_milliseconds: Used by gobject.timeout_add() - _interval_milliseconds: - '' - + 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_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() """ - 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, options=None, stop_hook=None, - fingerprint=None, secret=None, secfile=None, fqdn=None, - timeout=None, interval=-1, checker=None): + + runtime_expansions = ("approval_delay", "approval_duration", + "created", "enabled", "expires", + "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", + } + + def timeout_milliseconds(self): + "Return the 'timeout' attribute in milliseconds" + return timedelta_to_milliseconds(self.timeout) + + def extended_timeout_milliseconds(self): + "Return the 'extended_timeout' attribute in milliseconds" + return timedelta_to_milliseconds(self.extended_timeout) + + def interval_milliseconds(self): + "Return the 'interval' attribute in milliseconds" + return timedelta_to_milliseconds(self.interval) + + def approval_delay_milliseconds(self): + return timedelta_to_milliseconds(self.approval_delay) + + @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") + + client["fingerprint"] = (section["fingerprint"].upper() + .replace(" ", "")) + if "secret" in section: + client["secret"] = section["secret"].decode("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 {0}" + .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): self.name = 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"") - if secret: - self.secret = secret.decode(u"base64") - elif secfile: - sf = open(secfile) - self.secret = sf.read() - sf.close() - else: - raise RuntimeError(u"No secret or secfile for client %s" - % self.name) - self.fqdn = fqdn # string - self.created = datetime.datetime.now() - self.last_seen = None - if timeout is None: - timeout = options.timeout - self.timeout = timeout - if interval == -1: - interval = options.interval - else: - interval = string_to_delta(interval) - self.interval = interval - self.stop_hook = stop_hook + if server_settings is None: + server_settings = {} + self.server_settings = server_settings + # adding all client settings + for setting, value in settings.iteritems(): + 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) + else: + self.last_enabled = None + self.expires = None + + logger.debug("Creating client %r", self.name) + # Uppercase and remove spaces from fingerprint for later + # comparison purposes with return value from the fingerprint() + # function + logger.debug(" Fingerprint: %s", self.fingerprint) + self.created = settings.get("created", + datetime.datetime.utcnow()) + + # attributes specific for this server instance self.checker = None self.checker_initiator_tag = None - self.stop_initiator_tag = None + self.disable_initiator_tag = None self.checker_callback_tag = None - self.check_command = checker - def start(self): - """Start this clients checker and timeout hooks""" + 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__.iterkeys() + 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): + """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: + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = None + self.expires = None + if getattr(self, "checker_initiator_tag", None) is not None: + gobject.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 gobject.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. - self.checker_initiator_tag = gobject.timeout_add\ - (self._interval_milliseconds, - self.start_checker) + if self.checker_initiator_tag is not None: + gobject.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = (gobject.timeout_add + (self.interval_milliseconds(), + self.start_checker)) + # Schedule a disable() when 'timeout' has passed + if self.disable_initiator_tag is not None: + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = (gobject.timeout_add + (self.timeout_milliseconds(), + self.disable)) # Also start a new checker *right now*. self.start_checker() - # 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 this client might be restarted is left - open, but not currently used.""" - logger.debug(u"Stopping client %s", self.name) - self.secret = None - if self.stop_initiator_tag: - gobject.source_remove(self.stop_initiator_tag) - self.stop_initiator_tag = None - if self.checker_initiator_tag: - 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): - # Some code duplication here and in stop() - if hasattr(self, "stop_initiator_tag") \ - and self.stop_initiator_tag: - gobject.source_remove(self.stop_initiator_tag) - self.stop_initiator_tag = None - if hasattr(self, "checker_initiator_tag") \ - and self.checker_initiator_tag: - gobject.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = None - self.stop_checker() - def checker_callback(self, pid, condition): + + def checker_callback(self, pid, condition, command): """The checker has completed, so take appropriate actions.""" - now = datetime.datetime.now() - if os.WIFEXITED(condition) \ - and (os.WEXITSTATUS(condition) == 0): - logger.debug(u"Checker for %(name)s succeeded", - vars(self)) - self.last_seen = now - gobject.source_remove(self.stop_initiator_tag) - self.stop_initiator_tag = gobject.timeout_add\ - (self._timeout_milliseconds, - self.stop) - if not os.WIFEXITED(condition): - logger.warning(u"Checker for %(name)s crashed?", + self.checker_callback_tag = None + self.checker = None + if os.WIFEXITED(condition): + self.last_checker_status = os.WEXITSTATUS(condition) + 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 + logger.warning("Checker for %(name)s crashed?", vars(self)) - else: - logger.debug(u"Checker for %(name)s failed", - vars(self)) - self.checker = None - self.checker_callback_tag = None + + 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.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: + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = None + if getattr(self, "enabled", False): + self.disable_initiator_tag = (gobject.timeout_add + (timedelta_to_milliseconds + (timeout), self.disable)) + self.expires = datetime.datetime.utcnow() + timeout + + def need_approval(self): + self.last_approval_request = datetime.datetime.utcnow() + 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 + # 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 a checker exists, make sure it is not a zombie + try: + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + except (AttributeError, OSError) as error: + if (isinstance(error, OSError) + and error.errno != errno.ECHILD): + raise error + else: + if pid: + logger.warning("Checker was a zombie") + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, + self.current_checker_command) + # Start a new checker if needed if self.checker is None: - logger.debug(u"Starting checker for %s", - self.name) - try: - command = self.check_command % self.fqdn - except TypeError: - 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.critical(u'Could not format string "%s": %s', - self.check_command, error) - return True # Try again later - try: - self.checker = subprocess.\ - Popen(command, - stdout=subprocess.PIPE, - 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) + # Escape attributes for the shell + escaped_attrs = dict( + (attr, re.escape(unicode(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 + try: + 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 = {} + if (not self.server_settings["debug"] + and self.server_settings["foreground"]): + popen_args.update({"stdout": wnull, + "stderr": wnull }) + self.checker = subprocess.Popen(command, + close_fds=True, + shell=True, cwd="/", + **popen_args) + except OSError as error: + logger.error("Failed to start subprocess", + exc_info=error) + return True + self.checker_callback_tag = (gobject.child_watch_add + (self.checker.pid, + self.checker_callback, + data=command)) + # The checker may have completed before the gobject + # watch was added. Check for this. + try: + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + except OSError as error: + if error.errno == errno.ECHILD: + # This should never happen + logger.error("Child process vanished", + exc_info=error) + return True + raise + if pid: + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, command) # 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 not hasattr(self, "checker") or self.checker is None: + if self.checker_callback_tag: + gobject.source_remove(self.checker_callback_tag) + self.checker_callback_tag = None + if getattr(self, "checker", None) is None: return - gobject.source_remove(self.checker_callback_tag) + logger.debug("Stopping checker for %(name)s", vars(self)) + try: + self.checker.terminate() + #time.sleep(0.5) + #if self.checker.poll() is None: + # self.checker.kill() + except OSError as 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 {0!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_service_property("org.example.Interface", signature="b", + access="r") + @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true", + "org.freedesktop.DBus.Property." + "EmitsChangedSignal": "false"}) + def Property_dbus_property(self): + return dbus.Boolean(False) + """ + 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 + """ + def __unicode__(self): + return unicode(str(self)) + + +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 DBusObjectWithProperties(dbus.service.Object): + """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. + """ + + @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_{0}".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))) + + 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(self.dbus_object_path + ":" + + interface_name + "." + + property_name) + + @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 + 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.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 = dbus.service.Object.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 typ in ("method", "signal", "property"): + 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.iteritems(): + 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().iteritems() + for name, annotations in + self._get_all_dbus_things("interface") + if name == if_tag.getAttribute("name") + )).iteritems(): + ann_tag = document.createElement("annotation") + ann_tag.setAttribute("name", annotation) + ann_tag.setAttribute("value", value) + if_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 + + +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.iteritems()): + 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 + nonmethod_func = (dict( + zip(attribute.func_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 = (dbus.service.signal + (alt_interface, + attribute._dbus_signature) + (types.FunctionType( + nonmethod_func.func_code, + nonmethod_func.func_globals, + nonmethod_func.func_name, + nonmethod_func.func_defaults, + nonmethod_func.func_closure))) + # 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""" + def call_both(*args, **kwargs): + """This function will emit two D-Bus + signals by calling func1 and func2""" + func1(*args, **kwargs) + func2(*args, **kwargs) + 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) + (types.FunctionType + (attribute.func_code, + attribute.func_globals, + attribute.func_name, + attribute.func_defaults, + attribute.func_closure))) + # 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"]) + (types.FunctionType + (attribute.func_code, + attribute.func_globals, + attribute.func_name, + attribute.func_defaults, + attribute.func_closure))) + # 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) + (types.FunctionType + (attribute.func_code, + attribute.func_globals, + attribute.func_name, + attribute.func_defaults, + attribute.func_closure))) + if deprecate: + # Deprecate all alternate interfaces + iname="_AlternateDBusNames_interface_annotation{0}" + 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. + cls = type(b"{0}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",)) + + # 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 = unicode(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): + """ 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 = "_{0}".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)): + dbus_value = transform_func(type_func(value), + variant_level + =variant_level) + self.PropertyChanged(dbus.String(dbus_name), + dbus_value) + 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 = + timedelta_to_milliseconds) + approval_duration = notifychangeproperty( + dbus.UInt64, "ApprovalDuration", + type_func = timedelta_to_milliseconds) + host = notifychangeproperty(dbus.String, "Host") + timeout = notifychangeproperty(dbus.UInt64, "Timeout", + type_func = + timedelta_to_milliseconds) + extended_timeout = notifychangeproperty( + dbus.UInt64, "ExtendedTimeout", + type_func = timedelta_to_milliseconds) + interval = notifychangeproperty(dbus.UInt64, + "Interval", + type_func = + timedelta_to_milliseconds) + checker_command = notifychangeproperty(dbus.String, "Checker") + + 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, pid, condition, command, + *args, **kwargs): self.checker_callback_tag = None - os.kill(self.checker.pid, signal.SIGTERM) - if self.checker.poll() is None: - os.kill(self.checker.pid, signal.SIGKILL) self.checker = None - def still_valid(self, now=None): - """Has the timeout not yet passed for this client?""" - if now is None: - now = datetime.datetime.now() - if self.last_seen is None: - return now < (self.created + self.timeout) - else: - return now < (self.last_seen + self.timeout) - - -def peer_certificate(session): - # 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): - # 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. + if os.WIFEXITED(condition): + exitstatus = os.WEXITSTATUS(condition) + # Emit D-Bus signal + self.CheckerCompleted(dbus.Int16(exitstatus), + dbus.Int64(condition), + dbus.String(command)) + else: + # Emit D-Bus signal + self.CheckerCompleted(dbus.Int16(-1), + dbus.Int64(condition), + dbus.String(command)) + + return Client.checker_callback(self, pid, condition, command, + *args, **kwargs) + + def start_checker(self, *args, **kwargs): + old_checker = self.checker + if self.checker is not None: + old_checker_pid = self.checker.pid + else: + old_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 + gobject.timeout_add(timedelta_to_milliseconds + (self.approval_duration), + self._reset_approved) + self.send_changedstate() + + ## D-Bus methods, signals & properties + _interface = "se.recompile.Mandos.Client" + + ## Interfaces + + @dbus_interface_annotations(_interface) + def _foo(self): + return { "org.freedesktop.DBus.Property.EmitsChangedSignal": + "false"} + + ## 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.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.service.method(_interface) + def Enable(self): + "D-Bus method" + self.enable() + + # StartChecker - method + @dbus.service.method(_interface) + def StartChecker(self): + "D-Bus method" + self.start_checker() + + # Disable - method + @dbus.service.method(_interface) + def Disable(self): + "D-Bus method" + self.disable() + + # StopChecker - method + @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_milliseconds()) + 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(timedelta_to_milliseconds( + self.approval_duration)) + self.approval_duration = datetime.timedelta(0, 0, 0, value) + + # Name - property + @dbus_service_property(_interface, signature="s", access="read") + def Name_dbus_property(self): + return dbus.String(self.name) + + # Fingerprint - property + @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 = unicode(value) + + # Created - property + @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_milliseconds()) + 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 + gobject.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = ( + gobject.timeout_add( + timedelta_to_milliseconds(self.expires - now), + 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_milliseconds()) + 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_milliseconds()) + self.interval = datetime.timedelta(0, 0, 0, value) + if getattr(self, "checker_initiator_tag", None) is None: + return + if self.enabled: + # Reschedule checker run + gobject.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = (gobject.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 = unicode(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_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_service_property(_interface, signature="ay", + access="write", byte_arrays=True) + def Secret_dbus_property(self, value): + self.secret = str(value) + + del _interface + + +class ProxyClient(object): + def __init__(self, child_pipe, fpr, address): + self._pipe = child_pipe + self._pipe.send(('init', fpr, address)) + if not self._pipe.recv(): + raise KeyError() + + 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. Note: This will run in its own forked process.""" def handle(self): - logger.debug(u"TCP connection from: %s", - unicode(self.client_address)) - session = gnutls.connection.ClientSession(self.request, - gnutls.connection.\ - X509Credentials()) - - #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC", - # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", - # "+DHE-DSS")) - priority = "SECURE256" - - 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 clients: - if c.fingerprint == fpr: - client = c - break - # Have to check if client.still_valid(), since it is possible - # that the client timed out while establishing the GnuTLS - # session. - if (not client) or (not client.still_valid()): - if client: - logger.debug(u"Client %(name)s is invalid", - vars(client)) - else: - logger.debug(u"Client not found for fingerprint: %s", - fpr) - 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. + with contextlib.closing(self.server.child_pipe) as child_pipe: + logger.info("TCP connection from: %s", + unicode(self.client_address)) + logger.debug("Pipe FD: %d", + self.server.child_pipe.fileno()) + + 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")) + # Use a fallback default, since this MUST be set. + priority = self.server.gnutls_priority + if priority is None: + priority = "NORMAL" + (gnutls.library.functions + .gnutls_priority_set_direct(session._c_object, + priority, 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 + except (ValueError, IndexError, RuntimeError) as error: + logger.error("Unknown protocol version: %s", error) + return + + # Start GnuTLS connection + try: + session.handshake() + except gnutls.errors.GNUTLSError 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: + try: + fpr = self.fingerprint(self.peer_certificate + (session)) + except (TypeError, + gnutls.errors.GNUTLSError) as error: + logger.warning("Bad certificate: %s", error) + return + logger.debug("Fingerprint: %s", fpr) + + try: + client = ProxyClient(child_pipe, 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_milliseconds(), + 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( + float(timedelta_to_milliseconds(delay) + / 1000)) + 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 + + sent_size = 0 + while sent_size < len(client.secret): + try: + sent = session.send(client.secret[sent_size:]) + except gnutls.errors.GNUTLSError as error: + logger.warning("gnutls send failed", + exc_info=error) + return + logger.debug("Sent: %d, remaining: %d", + sent, len(client.secret) + - (sent_size + sent)) + sent_size += sent + + 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.errors.GNUTLSError as error: + logger.warning("GnuTLS bye failed", + exc_info=error) + + @staticmethod + 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(1) + cert_list = (gnutls.library.functions + .gnutls_certificate_get_peers + (session._c_object, ctypes.byref(list_size))) + if not bool(cert_list) and list_size.value != 0: + raise gnutls.errors.GNUTLSError("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 fingerprint(openpgp): + "Convert an OpenPGP bytestring to a hexdigit fingerprint" + # 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)))) + # New empty GnuTLS certificate + crt = gnutls.library.types.gnutls_openpgp_crt_t() + (gnutls.library.functions + .gnutls_openpgp_crt_init(ctypes.byref(crt))) + # Import the OpenPGP public key into the certificate + (gnutls.library.functions + .gnutls_openpgp_crt_import(crt, ctypes.byref(datum), + gnutls.library.constants + .GNUTLS_OPENPGP_FMT_RAW)) + # Verify the self signature in the key + crtverify = ctypes.c_uint() + (gnutls.library.functions + .gnutls_openpgp_crt_verify_self(crt, 0, + ctypes.byref(crtverify))) + if crtverify.value != 0: + gnutls.library.functions.gnutls_openpgp_crt_deinit(crt) + raise (gnutls.errors.CertificateSecurityError + ("Verify failed")) + # 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.library.functions + .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf), + ctypes.byref(buf_len))) + # Deinit the certificate + gnutls.library.functions.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(object): + """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, object): + """ 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, object): + """IPv6-capable TCP server. Accepts 'None' as address and/or port + Attributes: - options: Command line options - clients: Set() of Client objects + enabled: Boolean; whether this server is activated yet + interface: None or a network interface name (string) + use_ipv6: Boolean; to use IPv6 or not """ - address_family = socket.AF_INET6 - def __init__(self, *args, **kwargs): - if "options" in kwargs: - self.options = kwargs["options"] - del kwargs["options"] - if "clients" in kwargs: - self.clients = kwargs["clients"] - del kwargs["clients"] - return super(type(self), self).__init__(*args, **kwargs) + 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) + 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.""" - if self.options.interface: - if not hasattr(socket, "SO_BINDTODEVICE"): - # From /usr/include/asm-i486/socket.h - socket.SO_BINDTODEVICE = 25 - try: - self.socket.setsockopt(socket.SOL_SOCKET, - socket.SO_BINDTODEVICE, - self.options.interface) - except socket.error, error: - if error[0] == errno.EPERM: - logger.warning(u"No permission to" - u" bind to interface %s", - self.options.interface) - else: - raise error + if self.interface is not None: + if SO_BINDTODEVICE is None: + logger.error("SO_BINDTODEVICE does not exist;" + " cannot bind to interface %s", + self.interface) + else: + try: + self.socket.setsockopt(socket.SOL_SOCKET, + SO_BINDTODEVICE, + str(self.interface + '\0')) + 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) + else: + raise # Only bind(2) the socket if we really need to. if self.server_address[0] or self.server_address[1]: if not self.server_address[0]: - in6addr_any = "::" - self.server_address = (in6addr_any, + 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, self.server_address[1]) - elif self.server_address[1] is None: + elif not self.server_address[1]: self.server_address = (self.server_address[0], 0) - return super(type(self), self).server_bind() +# 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 gobject.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 + gobject.io_add_watch(parent_pipe.fileno(), + gobject.IO_IN | gobject.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 & (gobject.IO_ERR | gobject.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': + fpr = request[1] + address = request[2] + + for c in self.clients.itervalues(): + if c.fingerprint == fpr: + client = c + break + else: + logger.info("Client not found for fingerprint: %s, ad" + "dress: %s", fpr, address) + if self.use_dbus: + # Emit D-Bus signal + mandos_dbus_service.ClientNotFound(fpr, + address[0]) + parent_pipe.send(False) + return False + + gobject.io_add_watch(parent_pipe.fileno(), + gobject.IO_IN | gobject.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 callable(client_object.__getattribute__(attrname)): + 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) + >>> rfc3339_duration_to_delta("PT60S") + datetime.timedelta(0, 60) + >>> rfc3339_duration_to_delta("PT60M") + datetime.timedelta(0, 3600) + >>> rfc3339_duration_to_delta("PT24H") + datetime.timedelta(1) + >>> rfc3339_duration_to_delta("P1W") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT5M30S") + datetime.timedelta(0, 330) + >>> rfc3339_duration_to_delta("P1DT3M20S") + datetime.timedelta(1, 200) + """ + + # 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 = datetime.timedelta() # Value so far + found_token = None + followers = frozenset(token_duration,) # Following valid tokens + s = duration # String left to parse + # 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") + # End token found + return value def string_to_delta(interval): """Parse a string and return a datetime.timedelta - + >>> string_to_delta('7d') datetime.timedelta(7) >>> string_to_delta('60s') @@ -419,230 +2223,636 @@ datetime.timedelta(0, 3600) >>> string_to_delta('24h') datetime.timedelta(1) - >>> string_to_delta(u'1w') + >>> string_to_delta('1w') datetime.timedelta(7) + >>> string_to_delta('5m 30s') + datetime.timedelta(0, 330) """ - 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 add_service(): - """From the Avahi server example code""" - global group, serviceName, serviceType, servicePort, serviceTXT, \ - domain, host - 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' ...", - serviceName, serviceType) - - group.AddService( - serviceInterface, # interface - avahi.PROTO_INET6, # protocol - dbus.UInt32(0), # flags - serviceName, serviceType, - domain, host, - dbus.UInt16(servicePort), - avahi.string_array_to_txt_array(serviceTXT)) - group.Commit() - - -def remove_service(): - """From the Avahi server example code""" - global group - - if not group is None: - group.Reset() - - -def server_state_changed(state): - """From the Avahi server example code""" - if state == avahi.SERVER_COLLISION: - logger.warning(u"Server name collision") - remove_service() - elif state == avahi.SERVER_RUNNING: - add_service() - - -def entry_group_state_changed(state, error): - """From the Avahi server example code""" - global serviceName, server, rename_count - - logger.debug(u"state change: %i", state) - - if state == avahi.ENTRY_GROUP_ESTABLISHED: - logger.debug(u"Service established.") - elif state == avahi.ENTRY_GROUP_COLLISION: - - rename_count = rename_count - 1 - if rename_count > 0: - name = server.GetAlternativeServiceName(name) - logger.warning(u"Service name collision, " - u"changing name to '%s' ...", name) - remove_service() - add_service() - - else: - logger.error(u"No suitable service name found " - u"after %i retries, exiting.", - n_rename) - main_loop.quit() - elif state == avahi.ENTRY_GROUP_FAILURE: - logger.error(u"Error in group state changed %s", - unicode(error)) - main_loop.quit() - return - - -def if_nametoindex(interface): - """Call the C function if_nametoindex()""" - try: - libc = ctypes.cdll.LoadLibrary("libc.so.6") - return libc.if_nametoindex(interface) - except (OSError, AttributeError): - if "struct" not in sys.modules: - import struct - if "fcntl" not in sys.modules: - import 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 - - -if __name__ == '__main__': - parser = OptionParser() - parser.add_option("-i", "--interface", type="string", - default=None, metavar="IF", - help="Bind to interface IF") - parser.add_option("--cert", type="string", default="cert.pem", - metavar="FILE", - help="Public key certificate PEM file to use") - parser.add_option("--key", type="string", default="key.pem", - metavar="FILE", - help="Private key PEM file to use") - parser.add_option("--ca", type="string", default="ca.pem", - metavar="FILE", - help="Certificate Authority certificate PEM file to use") - parser.add_option("--crl", type="string", default="crl.pem", - metavar="FILE", - help="Certificate Revokation List PEM file to use") - parser.add_option("-p", "--port", type="int", default=None, - help="Port number to receive requests on") - parser.add_option("--timeout", type="string", # Parsed later - default="1h", - help="Amount of downtime allowed for clients") - parser.add_option("--interval", type="string", # Parsed later - default="5m", - help="How often to check that a client is up") - 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") - (options, args) = parser.parse_args() + + try: + return rfc3339_duration_to_delta(interval) + except ValueError: + pass + + timevalue = datetime.timedelta(0) + for s in interval.split(): + try: + suffix = unicode(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 {0!r}" + .format(suffix)) + except (ValueError, IndexError) as e: + raise ValueError(*(e.args)) + timevalue += delta + return timevalue + + +def daemon(nochdir = False, noclose = False): + """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) + if not stat.S_ISCHR(os.fstat(null).st_mode): + raise OSError(errno.ENODEV, + "{0} not a character device" + .format(os.devnull)) + os.dup2(null, sys.stdin.fileno()) + os.dup2(null, sys.stdout.fileno()) + os.dup2(null, sys.stderr.fileno()) + if null > 2: + os.close(null) + + +def main(): + + ################################################################## + # Parsing of options, both command line and config file + + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--version", action="version", + version = "%(prog)s {0}".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) + + options = parser.parse_args() if options.check: import doctest doctest.testmod() sys.exit() - # Parse the time arguments - try: - options.timeout = string_to_delta(options.timeout) - except ValueError: - parser.error("option --timeout: Unparseable time") - try: - options.interval = string_to_delta(options.interval) - except ValueError: - parser.error("option --interval: Unparseable time") - - # Parse config file - defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" } - client_config = ConfigParser.SafeConfigParser(defaults) - #client_config.readfp(open("secrets.conf"), "secrets.conf") - client_config.read("mandos-clients.conf") - - # From the Avahi server example code - DBusGMainLoop(set_as_default=True ) + # Default values for config file for server-global settings + server_defaults = { "interface": "", + "address": "", + "port": "", + "debug": "False", + "priority": + "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224", + "servicename": "Mandos", + "use_dbus": "True", + "use_ipv6": "True", + "debuglevel": "", + "restore": "True", + "socket": "", + "statedir": "/var/lib/mandos", + "foreground": "False", + } + + # Parse config file for server-global settings + server_config = configparser.SafeConfigParser(server_defaults) + del server_defaults + server_config.read(os.path.join(options.configdir, + "mandos.conf")) + # Convert the SafeConfigParser 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", "foreground"): + 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"]) + 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"): + 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 type(server_settings[option]) is str: + server_settings[option] = unicode(server_settings[option]) + # Force all boolean options to be boolean + for option in ("debug", "use_dbus", "use_ipv6", "restore", + "foreground"): + 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" + + ################################################################## + + # 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"] + + 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 ({0}) [%(process)d]:' + ' %(levelname)s: %(message)s' + .format(server_settings + ["servicename"]))) + + # Parse config file with clients + client_config = configparser.SafeConfigParser(Client + .client_defaults) + client_config.read(os.path.join(server_settings["configdir"], + "clients.conf")) + + global mandos_dbus_service + mandos_dbus_service = None + + 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=(server_settings["socket"] + or None)) + if not foreground: + pidfilename = "/var/run/mandos.pid" + pidfile = None + try: + pidfile = open(pidfilename, "w") + except IOError as e: + logger.error("Could not open file %r", pidfilename, + exc_info=e) + + for name in ("_mandos", "mandos", "nobody"): + try: + uid = pwd.getpwnam(name).pw_uid + gid = pwd.getpwnam(name).pw_gid + break + except KeyError: + continue + else: + uid = 65534 + gid = 65534 + try: + os.setgid(gid) + os.setuid(uid) + except OSError as error: + if error.errno != errno.EPERM: + raise error + + if debug: + # Enable all possible GnuTLS debugging + + # "Use a log level over 10 to enable all debugging options." + # - GnuTLS manual + gnutls.library.functions.gnutls_global_set_log_level(11) + + @gnutls.library.types.gnutls_log_func + def debug_gnutls(level, string): + logger.debug("GnuTLS: %s", string[:-1]) + + (gnutls.library.functions + .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() + + # multiprocessing will use threads, so before we use gobject we + # need to inform gobject that threads will be used. + gobject.threads_init() + + global main_loop + # From the Avahi example code + 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 - - debug = options.debug - - clients = Set() - def remove_from_clients(client): - clients.remove(client) - if not clients: - logger.debug(u"No clients left, exiting") - main_loop.quit() - - clients.update(Set(Client(name=section, options=options, - stop_hook = remove_from_clients, - **(dict(client_config\ - .items(section)))) - for section in client_config.sections())) - for client in clients: - client.start() - - tcp_server = IPv6_TCPServer((None, options.port), - tcp_handler, - options=options, - clients=clients) - # Find out what random port we got - servicePort = tcp_server.socket.getsockname()[1] - logger.debug(u"Now listening on port %d", servicePort) - - if options.interface is not None: - serviceInterface = if_nametoindex(options.interface) - - # From the Avahi server example code - server.connect_to_signal("StateChanged", server_state_changed) - server_state_changed(server.GetState()) - # 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) + # 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.NameExistsException as e: + logger.error("Disabling D-Bus:", exc_info=e) + use_dbus = False + server_settings["use_dbus"] = False + tcp_server.use_dbus = False + 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 + (str(server_settings["interface"]))) + + 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: + clients_data, old_client_settings = (pickle.load + (stored_state)) + os.remove(stored_state_path) + except IOError as e: + if e.errno == errno.ENOENT: + logger.warning("Could not load persistent state: {0}" + .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.iteritems(): + # 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. Clients + # 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 {0} - Client never " + "performed a successful checker" + .format(client_name)) + client["enabled"] = False + elif client["last_checker_status"] != 0: + logger.warning( + "disabling client {0} - Client " + "last checker failed with error code {1}" + .format(client_name, + client["last_checker_status"])) + client["enabled"] = False + else: + client["expires"] = (datetime.datetime + .utcnow() + + client["timeout"]) + logger.debug("Last checker succeeded," + " keeping {0} 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 {0} 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.iteritems(): + 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: + try: + with pidfile: + pid = os.getpid() + pidfile.write(str(pid) + "\n".encode("utf-8")) + except IOError: + logger.error("Could not write to file %r with PID %d", + pidfilename, pid) + del pidfile + del pidfilename + + signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) + signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) + + if use_dbus: + @alternate_dbus_interfaces({"se.recompile.Mandos": + "se.bsnet.fukt.Mandos"}) + class MandosDBusService(DBusObjectWithProperties): + """A D-Bus proxy object""" + def __init__(self): + dbus.service.Object.__init__(self, bus, "/") + _interface = "se.recompile.Mandos" + + @dbus_interface_annotations(_interface) + def _foo(self): + return { "org.freedesktop.DBus.Property" + ".EmitsChangedSignal": + "false"} + + @dbus.service.signal(_interface, signature="o") + def ClientAdded(self, objpath): + "D-Bus signal" + pass + + @dbus.service.signal(_interface, signature="ss") + def ClientNotFound(self, fingerprint, address): + "D-Bus signal" + pass + + @dbus.service.signal(_interface, signature="os") + def ClientRemoved(self, objpath, name): + "D-Bus signal" + pass + + @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.itervalues()) + + @dbus.service.method(_interface, + out_signature="a{oa{sv}}") + def GetAllClientsWithProperties(self): + "D-Bus method" + return dbus.Dictionary( + ((c.dbus_object_path, c.GetAll("")) + for c in tcp_server.clients.itervalues()), + signature="oa{sv}") + + @dbus.service.method(_interface, in_signature="o") + def RemoveClient(self, object_path): + "D-Bus method" + for c in tcp_server.clients.itervalues(): + if c.dbus_object_path == object_path: + del tcp_server.clients[c.name] + c.remove_from_connection() + # Don't signal anything except ClientRemoved + c.disable(quiet=True) + # Emit D-Bus signal + self.ClientRemoved(object_path, c.name) + return + raise KeyError(object_path) + + del _interface + + mandos_dbus_service = MandosDBusService() + + def cleanup(): + "Cleanup function; run on exit" + service.cleanup() + + multiprocessing.active_children() + wnull.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.itervalues(): + 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 = set(("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) + 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: {0}" + .format(os.strerror(e.errno))) + else: + logger.warning("Could not save persistent state:", + exc_info=e) + raise e + + # 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 anything except ClientRemoved + client.disable(quiet=True) + if use_dbus: + # Emit D-Bus signal + mandos_dbus_service.ClientRemoved(client + .dbus_object_path, + client.name) + client_settings.clear() + + atexit.register(cleanup) + + for client in tcp_server.clients.itervalues(): + if use_dbus: + # Emit D-Bus signal + mandos_dbus_service.ClientAdded(client.dbus_object_path) + # Need to initiate checking of clients + if client.enabled: + client.init_checker() + + tcp_server.enable() + tcp_server.server_activate() + + # Find out what port we got + 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] + try: + # 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 + + 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.run() + except AvahiError as error: + logger.critical("Avahi Error", exc_info=error) + cleanup() + sys.exit(1) except KeyboardInterrupt: - print - - # Cleanup here + 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() - # From the Avahi server example code - if not group is None: - group.Free() - # End of Avahi example code - - for client in clients: - client.stop_hook = None - client.stop() +if __name__ == '__main__': + main() === added file 'mandos-clients.conf.xml' --- mandos-clients.conf.xml 1970-01-01 00:00:00 +0000 +++ mandos-clients.conf.xml 2012-06-23 00:58:49 +0000 @@ -0,0 +1,534 @@ + + +/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 + 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 0s, 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. + + + 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 hexidecimal 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 + . 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, + 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] +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] +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. + + + + + +
+ + + + + === added file 'mandos-ctl' --- mandos-ctl 1970-01-01 00:00:00 +0000 +++ mandos-ctl 2012-11-14 21:03:24 +0000 @@ -0,0 +1,460 @@ +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- +# +# Mandos Monitor - Control and monitor the Mandos server +# +# Copyright © 2008-2012 Teddy Hogeborn +# Copyright © 2008-2012 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 this program. If not, see +# . +# +# Contact the authors at . +# + +from __future__ import (division, absolute_import, print_function, + unicode_literals) + +from future_builtins import * + +import sys +import argparse +import locale +import datetime +import re +import os +import collections +import doctest + +import dbus + +locale.setlocale(locale.LC_ALL, "") + +tablewords = { + "Name": "Name", + "Enabled": "Enabled", + "Timeout": "Timeout", + "LastCheckedOK": "Last Successful Check", + "LastApprovalRequest": "Last Approval Request", + "Created": "Created", + "Interval": "Interval", + "Host": "Host", + "Fingerprint": "Fingerprint", + "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" + } +defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK") +domain = "se.recompile" +busname = domain + ".Mandos" +server_path = "/" +server_interface = domain + ".Mandos" +client_interface = domain + ".Mandos.Client" +version = "1.6.0" + +def timedelta_to_milliseconds(td): + """Convert a datetime.timedelta object to milliseconds""" + return ((td.days * 24 * 60 * 60 * 1000) + + (td.seconds * 1000) + + (td.microseconds // 1000)) + +def milliseconds_to_string(ms): + td = datetime.timedelta(0, 0, 0, ms) + return ("{days}{hours:02}:{minutes:02}:{seconds:02}" + .format(days = "{0}T".format(td.days) if td.days else "", + hours = td.seconds // 3600, + minutes = (td.seconds % 3600) // 60, + seconds = td.seconds % 60, + )) + + +def rfc3339_duration_to_delta(duration): + """Parse an RFC 3339 "duration" and return a datetime.timedelta + + >>> rfc3339_duration_to_delta("P7D") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT60S") + datetime.timedelta(0, 60) + >>> rfc3339_duration_to_delta("PT60M") + datetime.timedelta(0, 3600) + >>> rfc3339_duration_to_delta("PT24H") + datetime.timedelta(1) + >>> rfc3339_duration_to_delta("P1W") + datetime.timedelta(7) + >>> rfc3339_duration_to_delta("PT5M30S") + datetime.timedelta(0, 330) + >>> rfc3339_duration_to_delta("P1DT3M20S") + datetime.timedelta(1, 200) + """ + + # 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 = datetime.timedelta() # Value so far + found_token = None + followers = frozenset(token_duration,) # Following valid tokens + s = duration # String left to parse + # 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") + # End token found + return value + + +def string_to_delta(interval): + """Parse a string and return a datetime.timedelta + + >>> 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("1w") + datetime.timedelta(7) + >>> string_to_delta("5m 30s") + datetime.timedelta(0, 330) + """ + + try: + return rfc3339_duration_to_delta(interval) + except ValueError: + pass + + 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 print_clients(clients, keywords): + def valuetostring(value, keyword): + if type(value) is dbus.Boolean: + return "Yes" if value else "No" + if keyword in ("Timeout", "Interval", "ApprovalDelay", + "ApprovalDuration", "ExtendedTimeout"): + return milliseconds_to_string(value) + return unicode(value) + + # Create format string to print table rows + format_string = " ".join("{{{key}:{width}}}".format( + width = max(len(tablewords[key]), + max(len(valuetostring(client[key], + key)) + for client in + clients)), + key = key) for key in keywords) + # Print header line + print(format_string.format(**tablewords)) + for client in clients: + print(format_string.format(**dict((key, + valuetostring(client[key], + key)) + for key in keywords))) + +def has_actions(options): + return any((options.enable, + options.disable, + options.bump_timeout, + options.start_checker, + options.stop_checker, + options.is_enabled, + options.remove, + options.checker is not None, + options.timeout is not None, + options.extended_timeout is not None, + options.interval is not None, + options.approved_by_default is not None, + options.approval_delay is not None, + options.approval_duration is not None, + options.host is not None, + options.secret is not None, + options.approve, + options.deny)) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--version", action="version", + version = "%(prog)s {0}".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("-e", "--enable", action="store_true", + help="Enable client") + parser.add_argument("-d", "--disable", action="store_true", + help="disable client") + parser.add_argument("-b", "--bump-timeout", action="store_true", + help="Bump timeout for client") + parser.add_argument("--start-checker", action="store_true", + help="Start checker for client") + parser.add_argument("--stop-checker", action="store_true", + help="Stop checker for client") + parser.add_argument("-V", "--is-enabled", action="store_true", + help="Check if client is enabled") + parser.add_argument("-r", "--remove", action="store_true", + help="Remove client") + parser.add_argument("-c", "--checker", + help="Set checker command for client") + parser.add_argument("-t", "--timeout", + help="Set timeout for client") + parser.add_argument("--extended-timeout", + help="Set extended timeout for client") + parser.add_argument("-i", "--interval", + help="Set checker interval for client") + parser.add_argument("--approve-by-default", action="store_true", + default=None, dest="approved_by_default", + help="Set client to be approved by default") + parser.add_argument("--deny-by-default", action="store_false", + dest="approved_by_default", + help="Set client to be denied by default") + parser.add_argument("--approval-delay", + help="Set delay before client approve/deny") + parser.add_argument("--approval-duration", + help="Set duration of one client approval") + parser.add_argument("-H", "--host", help="Set host for client") + parser.add_argument("-s", "--secret", type=file, + help="Set password blob (file) for client") + parser.add_argument("-A", "--approve", action="store_true", + help="Approve any current client request") + parser.add_argument("-D", "--deny", action="store_true", + help="Deny any current client request") + parser.add_argument("--check", action="store_true", + help="Run self-test") + parser.add_argument("client", nargs="*", help="Client name") + options = parser.parse_args() + + if has_actions(options) and not (options.client or options.all): + parser.error("Options require clients names or --all.") + if options.verbose and has_actions(options): + parser.error("--verbose can only be used alone or with" + " --all.") + if options.all and not has_actions(options): + parser.error("--all requires an action.") + + if options.check: + fail_count, test_count = doctest.testmod() + sys.exit(0 if fail_count == 0 else 1) + + try: + bus = dbus.SystemBus() + mandos_dbus_objc = bus.get_object(busname, server_path) + except dbus.exceptions.DBusException: + print("Could not connect to Mandos server", + file=sys.stderr) + sys.exit(1) + + mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface = server_interface) + + #block stderr since dbus library prints to stderr + null = os.open(os.path.devnull, os.O_RDWR) + stderrcopy = os.dup(sys.stderr.fileno()) + os.dup2(null, sys.stderr.fileno()) + os.close(null) + try: + try: + mandos_clients = mandos_serv.GetAllClientsWithProperties() + finally: + #restore stderr + os.dup2(stderrcopy, sys.stderr.fileno()) + os.close(stderrcopy) + except dbus.exceptions.DBusException: + print("Access denied: Accessing mandos server through dbus.", + file=sys.stderr) + sys.exit(1) + + # Compile dict of (clients: properties) to process + clients={} + + if options.all or not options.client: + clients = dict((bus.get_object(busname, path), properties) + for path, properties in + mandos_clients.iteritems()) + else: + for name in options.client: + for path, client in mandos_clients.iteritems(): + if client["Name"] == name: + client_objc = bus.get_object(busname, path) + clients[client_objc] = client + break + else: + print("Client not found on server: {0!r}" + .format(name), file=sys.stderr) + sys.exit(1) + + if not has_actions(options) and clients: + if options.verbose: + keywords = ("Name", "Enabled", "Timeout", + "LastCheckedOK", "Created", "Interval", + "Host", "Fingerprint", "CheckerRunning", + "LastEnabled", "ApprovalPending", + "ApprovedByDefault", + "LastApprovalRequest", "ApprovalDelay", + "ApprovalDuration", "Checker", + "ExtendedTimeout") + else: + keywords = defaultkeywords + + print_clients(clients.values(), keywords) + else: + # Process each client in the list by all selected options + for client in clients: + def set_client_prop(prop, value): + """Set a Client D-Bus property""" + client.Set(client_interface, prop, value, + dbus_interface=dbus.PROPERTIES_IFACE) + def set_client_prop_ms(prop, value): + """Set a Client D-Bus property, converted + from a string to milliseconds.""" + set_client_prop(prop, + timedelta_to_milliseconds + (string_to_delta(value))) + if options.remove: + mandos_serv.RemoveClient(client.__dbus_object_path__) + if options.enable: + set_client_prop("Enabled", dbus.Boolean(True)) + if options.disable: + set_client_prop("Enabled", dbus.Boolean(False)) + if options.bump_timeout: + set_client_prop("LastCheckedOK", "") + if options.start_checker: + set_client_prop("CheckerRunning", dbus.Boolean(True)) + if options.stop_checker: + set_client_prop("CheckerRunning", dbus.Boolean(False)) + if options.is_enabled: + sys.exit(0 if client.Get(client_interface, + "Enabled", + dbus_interface= + dbus.PROPERTIES_IFACE) + else 1) + if options.checker is not None: + set_client_prop("Checker", options.checker) + if options.host is not None: + set_client_prop("Host", options.host) + if options.interval is not None: + set_client_prop_ms("Interval", options.interval) + if options.approval_delay is not None: + set_client_prop_ms("ApprovalDelay", + options.approval_delay) + if options.approval_duration is not None: + set_client_prop_ms("ApprovalDuration", + options.approval_duration) + if options.timeout is not None: + set_client_prop_ms("Timeout", options.timeout) + if options.extended_timeout is not None: + set_client_prop_ms("ExtendedTimeout", + options.extended_timeout) + if options.secret is not None: + set_client_prop("Secret", + dbus.ByteArray(options.secret.read())) + if options.approved_by_default is not None: + set_client_prop("ApprovedByDefault", + dbus.Boolean(options + .approved_by_default)) + if options.approve: + client.Approve(dbus.Boolean(True), + dbus_interface=client_interface) + elif options.deny: + client.Approve(dbus.Boolean(False), + dbus_interface=client_interface) + +if __name__ == "__main__": + main() === added file 'mandos-ctl.xml' --- mandos-ctl.xml 1970-01-01 00:00:00 +0000 +++ mandos-ctl.xml 2012-06-22 23:33:56 +0000 @@ -0,0 +1,605 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2010 + 2011 + 2012 + Teddy Hogeborn + Björn Påhlsson + + +
+ + + &COMMANDNAME; + 8 + + + + &COMMANDNAME; + + Control the operation of the Mandos server + + + + + + &COMMANDNAME; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CLIENT + + + + + &COMMANDNAME; + + + + + + + CLIENT + + + + + &COMMANDNAME; + + + + + CLIENT + + + &COMMANDNAME; + + + + + + + &COMMANDNAME; + + + + + + + &COMMANDNAME; + + + + + + DESCRIPTION + + &COMMANDNAME; is a program to control 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. + + + + + + + + + + Check if a single client is enabled or not, and exit with + a successful exit status only if the client is enabled. + + + + + + + + + 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. + + + + + + + + + + + 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="5m" --interval="1m" foo1.example.org foo2.example.org + + + + + + + To approve all clients currently waiting for it: + + + &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 + + + +
+ + + + + === added file 'mandos-keygen' --- mandos-keygen 1970-01-01 00:00:00 +0000 +++ mandos-keygen 2013-09-29 15:52:19 +0000 @@ -0,0 +1,352 @@ +#!/bin/sh -e +# +# Mandos key generator - create a new OpenPGP key for a Mandos client +# +# Copyright © 2008-2012 Teddy Hogeborn +# Copyright © 2008-2012 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 this program. If not, see . +# +# Contact the authors at . +# + +VERSION="1.6.0" + +KEYDIR="/etc/keys/mandos" +KEYTYPE=RSA +KEYLENGTH=4096 +SUBKEYTYPE=RSA +SUBKEYLENGTH=4096 +KEYNAME="`hostname --fqdn 2>/dev/null || hostname`" +KEYEMAIL="" +KEYCOMMENT="Mandos client key" +KEYEXPIRE=0 +FORCE=no +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:f \ + --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,force \ + --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" + +# 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" -o -e "$PUBKEYFILE" \) \ + -a "$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`" +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\"; \ +shred --remove \"$RINGDIR\"/sec*; +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: encrypt,sign,auth + Subkey-Type: $SUBKEYTYPE + Subkey-Length: $SUBKEYLENGTH + #Subkey-Usage: encrypt,sign,auth + Name-Real: $KEYNAME + $KEYCOMMENTLINE + $KEYEMAILLINE + Expire-Date: $KEYEXPIRE + #Preferences: + #Handle: + #%pubring pubring.gpg + #%secring secring.gpg + %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 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" + 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 + # 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" + + 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: " + read first + tty --quiet && echo >&2 + echo -n "Repeat passphrase: " + read 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 + 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" +fi + +trap - EXIT + +set +e +# Remove the password file, if any +if [ -n "$SECFILE" ]; then + shred --remove "$SECFILE" +fi +# Remove the key rings +shred --remove "$RINGDIR"/sec* +rm --recursive --force "$RINGDIR" === added file 'mandos-keygen.xml' --- mandos-keygen.xml 1970-01-01 00:00:00 +0000 +++ mandos-keygen.xml 2013-09-29 15:52:19 +0000 @@ -0,0 +1,515 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2011 + 2012 + 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 + OpenPGP key used by + mandos-client + 8mandos. The key is + normally written to /etc/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/mandos. + + + + + + + + + + Key type. Default is RSA. + + + + + + + + + + Key length in bits. Default is 4096. + + + + + + + + + + Subkey type. Default is RSA (Elgamal + encryption-only). + + + + + + + + + + Subkey length in bits. Default is 4096. + + + + + + + + + + Email address of key. Default is empty. + + + + + + + + + + Comment field for key. The default value is + Mandos client key. + + + + + + + + + + Key expire time. Default is no expiration. See + gpg + 1 for syntax. + + + + + + + + + + Force overwriting old key. + + + + + + + + + Prompt for a password and encrypt it with the key already + present in either /etc/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. + + + + + + + + + The same as , but read from + FILE, not the terminal. + + + + + + + + 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 + + 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/mandos/seckey.txt + + + OpenPGP secret key file which will be created or + overwritten. + + + + + /etc/mandos/pubkey.txt + + + OpenPGP public key file which will be created or + overwritten. + + + + + /tmp + + + Temporary files will be written here if + TMPDIR is not set. + + + + + + + + + + + + + + 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 key in /etc/mandos and output a section + suitable for clients.conf. + + + &COMMANDNAME; --password + + + + + Prompt for a password, encrypt it with the key 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 + + + +
+ + + + + === added file 'mandos-monitor' --- mandos-monitor 1970-01-01 00:00:00 +0000 +++ mandos-monitor 2013-05-22 20:00:18 +0000 @@ -0,0 +1,724 @@ +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- +# +# Mandos Monitor - Control and monitor the Mandos server +# +# Copyright © 2009-2012 Teddy Hogeborn +# Copyright © 2009-2012 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 this program. 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 signal + +import datetime + +import urwid.curses_display +import urwid + +from dbus.mainloop.glib import DBusGMainLoop +try: + import gobject +except ImportError: + from gi.repository import GObject as gobject + +import dbus + +import locale + +if sys.version_info[0] == 2: + str = unicode + +locale.setlocale(locale.LC_ALL, '') + +import logging +logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL) + +# Some useful constants +domain = 'se.recompile' +server_interface = domain + '.Mandos' +client_interface = domain + '.Mandos.Client' +version = "1.6.0" + +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("PropertyChanged", + self._property_changed, + client_interface, + 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 _property_changed(self, property, value): + """Helper which takes positional arguments""" + return self.property_changed(property=property, value=value) + + def property_changed(self, property=None, value=None): + """This is called whenever we get a PropertyChanged signal + It updates the changed property in the "properties" dict. + """ + # Update properties dict with new value + self.properties[property] = value + + 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, logger=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 + # Logger + self.logger = logger + + 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)) + #self.logger('Created client {0}' + # .format(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 = (gobject.timeout_add + (1000, + self.update_timer)) + elif not (flag or self._update_timer_callback_tag is None): + gobject.source_remove(self._update_timer_callback_tag) + self._update_timer_callback_tag = None + + def checker_completed(self, exitstatus, condition, command): + if exitstatus == 0: + self.update() + return + # Checker failed + if os.WIFEXITED(condition): + self.logger('Checker for client {0} (command "{1}")' + ' failed with exit code {2}' + .format(self.properties["Name"], command, + os.WEXITSTATUS(condition))) + elif os.WIFSIGNALED(condition): + self.logger('Checker for client {0} (command "{1}") was' + ' killed by signal {2}' + .format(self.properties["Name"], command, + os.WTERMSIG(condition))) + elif os.WCOREDUMP(condition): + self.logger('Checker for client {0} (command "{1}")' + ' dumped core' + .format(self.properties["Name"], command)) + else: + self.logger('Checker for client {0} completed' + ' mysteriously' + .format(self.properties["Name"])) + self.update() + + def checker_started(self, command): + """Server signals that a checker started. This could be useful + to log in the future. """ + #self.logger('Client {0} started checker "{1}"' + # .format(self.properties["Name"], + # str(command))) + pass + + def got_secret(self): + self.logger('Client {0} received its secret' + .format(self.properties["Name"])) + + def need_approval(self, timeout, default): + if not default: + message = 'Client {0} needs approval within {1} seconds' + else: + message = 'Client {0} will get its secret in {1} seconds' + self.logger(message.format(self.properties["Name"], + timeout/1000)) + + def rejected(self, reason): + self.logger('Client {0} was rejected; reason: {1}' + .format(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 {0}. (d)eny?" + else: + message = "Denial in {0}. (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: {0}' + .format(str(timer).rsplit(".", 1)[0])) + self.using_timer(True) + else: + message = "enabled" + self.using_timer(False) + self._text = "{0}{1}".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 gobject. Will indefinitely loop until + gobject.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: + gobject.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.Enable(dbus_interface = client_interface, + ignore_reply=True) + elif key == "-": + self.proxy.Disable(dbus_interface = client_interface, + ignore_reply=True) + 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.StartChecker(dbus_interface = client_interface, + ignore_reply=True) + elif key == "S": + self.proxy.StopChecker(dbus_interface = client_interface, + ignore_reply=True) + 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 property_changed(self, property=None, **kwargs): + """Call self.update() if old value is not new value. + This overrides the method from MandosClientPropertyCache""" + property_name = str(property) + old_value = self.properties.get(property_name) + super(MandosClientWidget, self).property_changed( + property=property, **kwargs) + if self.properties.get(property_name) != old_value: + self.update() + + +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 + #self.divider = "━" # \u2501 + else: + #self.divider = "-" # \u002d + 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 = [] + 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.rebuild() + self.log_message_raw(("bold", + "Mandos Monitor version " + version)) + self.log_message_raw(("bold", + "q: Quit ?: Help")) + + self.busname = domain + '.Mandos' + self.main_loop = gobject.MainLoop() + + def client_not_found(self, fingerprint, address): + self.log_message("Client with address {0} and fingerprint" + " {1} could not be found" + .format(address, fingerprint)) + + 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 log_message(self, message): + """Log message formatted with timestamp""" + timestamp = datetime.datetime.now().isoformat() + self.log_message_raw(timestamp + ": " + message) + + def log_message_raw(self, markup): + """Add a log message to the log buffer.""" + self.log.append(urwid.Text(markup, wrap=self.log_wrap)) + if (self.max_log_length + and len(self.log) > self.max_log_length): + del self.log[0:len(self.log)-self.max_log_length-1] + self.logbox.set_focus(len(self.logbox.body.contents), + 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() + #self.log_message("Log visibility changed to: " + # + str(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) + #self.log_message("Wrap mode: " + self.log_wrap) + + def find_and_remove_client(self, path, name): + """Find a client by its object path and remove it. + + This is connected to the ClientRemoved signal from the + Mandos server object.""" + try: + client = self.clients_dict[path] + except KeyError: + # not found? + self.log_message("Unknown client {0!r} ({1!r}) removed" + .format(name, path)) + return + client.delete() + + def add_new_client(self, path): + 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, + logger + =self.log_message), + 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.""" + 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: + self.log_message_raw(("bold", "Note: Server has no clients.")) + except dbus.exceptions.DBusException: + self.log_message_raw(("bold", "Note: No Mandos server running.")) + mandos_clients = dbus.Dictionary() + + (self.mandos_serv + .connect_to_signal("ClientRemoved", + self.find_and_remove_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal("ClientAdded", + self.add_new_client, + dbus_interface=server_interface, + 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, + logger + =self.log_message), + path=path) + + self.refresh() + self._input_callback_tag = (gobject.io_add_watch + (sys.stdin.fileno(), + gobject.IO_IN, + self.process_input)) + self.main_loop.run() + # Main loop has finished, we should close everything now + gobject.source_remove(self._input_callback_tag) + self.screen.stop() + + def stop(self): + self.main_loop.quit() + + 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 == "\f": # Ctrl-L + 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.log_message_raw(("bold", + " ". + join(("q: Quit", + "?: Help", + "l: Log window toggle", + "TAB: Switch window", + "w: Wrap (log)")))) + self.log_message_raw(("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 == "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 + +ui = UserInterface() +try: + ui.run() +except KeyboardInterrupt: + ui.screen.stop() +except Exception as e: + ui.log_message(str(e)) + ui.screen.stop() + raise === added file 'mandos-monitor.xml' --- mandos-monitor.xml 1970-01-01 00:00:00 +0000 +++ mandos-monitor.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,237 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2010 + 2011 + 2012 + 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 + + + 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 service name of + 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 + + + +
+ + + + + === added file 'mandos-options.xml' --- mandos-options.xml 1970-01-01 00:00:00 +0000 +++ mandos-options.xml 2013-06-23 15:13:06 +0000 @@ -0,0 +1,114 @@ + + + + + +
+ + + <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 <quote><literal + >SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224</literal></quote>. + 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. + </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> + +</section> === added file 'mandos.conf' --- mandos.conf 1970-01-01 00:00:00 +0000 +++ mandos.conf 2013-06-23 15:13:06 +0000 @@ -0,0 +1,47 @@ +# 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:+SIGN-RSA-SHA224 + +# 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 === added file 'mandos.conf.xml' --- mandos.conf.xml 1970-01-01 00:00:00 +0000 +++ mandos.conf.xml 2012-05-26 22:21:17 +0000 @@ -0,0 +1,299 @@ +<?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 "2012-05-26"> +<!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 + 2011 + 2012 + 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 = eth0 +address = fe80::aede:48ff:fe71:f6f2 +port = 1025 +debug = true +priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP +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. + + + + + + + + + + + === added file 'mandos.lsm' --- mandos.lsm 1970-01-01 00:00:00 +0000 +++ mandos.lsm 2012-06-17 22:26:40 +0000 @@ -0,0 +1,22 @@ +Begin4 +Title: Mandos +Version: 1.6.0 +Entered-date: 2012-06-18 +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: http://www.recompile.se/mandos + 150K mandos_1.6.0.orig.tar.gz +Alternate-site: ftp://ftp.recompile.se/pub/mandos + 150K mandos_1.6.0.orig.tar.gz +Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.6, 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 === added file 'mandos.xml' --- mandos.xml 1970-01-01 00:00:00 +0000 +++ mandos.xml 2012-06-17 14:55:31 +0000 @@ -0,0 +1,778 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2010 + 2011 + 2012 + 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 an OpenPGP certificate, and the + fingerprint of this certificate 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 + + + + OpenPGP 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. + + + + + /var/run/mandos.pid + + + The file containing the process id of the + &COMMANDNAME; process started last. + + + + + /dev/log + + + /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. + + + This server does not check the expire time of clients’ OpenPGP + keys. + + + + + 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 OpenPGP key of the stored fingerprint. This is + guaranteed by the fact that the client sends its OpenPGP + public key in the TLS handshake; this ensures it to be + genuine. The server computes the fingerprint of the key + itself and looks up the fingerprint 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 OpenPGP 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 4346: The Transport Layer Security (TLS) + Protocol Version 1.1 + + + + TLS 1.1 is the protocol implemented by GnuTLS. + + + + + + RFC 4880: OpenPGP Message Format + + + + The data sent to clients is binary encrypted OpenPGP data. + + + + + + RFC 5081: Using OpenPGP Keys for Transport Layer + Security + + + + This is implemented by GnuTLS and used by this server so + that OpenPGP keys can be used. + + + + + +
+ + + + + === added directory 'network-hooks.d' === added file 'network-hooks.d/bridge' --- network-hooks.d/bridge 1970-01-01 00:00:00 +0000 +++ network-hooks.d/bridge 2012-06-13 22:06:57 +0000 @@ -0,0 +1,93 @@ +#!/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 Teddy Hogeborn +# Copyright © 2012 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" -o -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 === added file 'network-hooks.d/bridge.conf' --- network-hooks.d/bridge.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/bridge.conf 2011-12-31 13:25:58 +0000 @@ -0,0 +1,11 @@ +## 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" === added file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 1970-01-01 00:00:00 +0000 +++ network-hooks.d/openvpn 2012-06-13 22:06:57 +0000 @@ -0,0 +1,66 @@ +#!/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 Teddy Hogeborn +# Copyright © 2012 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 === added file 'network-hooks.d/openvpn.conf' --- network-hooks.d/openvpn.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/openvpn.conf 2011-12-02 16:52:50 +0000 @@ -0,0 +1,19 @@ +# 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 === added file 'network-hooks.d/wireless' --- network-hooks.d/wireless 1970-01-01 00:00:00 +0000 +++ network-hooks.d/wireless 2012-06-13 22:06:57 +0000 @@ -0,0 +1,165 @@ +#!/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 Teddy Hogeborn +# Copyright © 2012 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 === added file 'network-hooks.d/wireless.conf' --- network-hooks.d/wireless.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/wireless.conf 2011-12-31 13:25:58 +0000 @@ -0,0 +1,23 @@ +# 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=... +#... === added file 'overview.xml' --- overview.xml 1970-01-01 00:00:00 +0000 +++ overview.xml 2008-09-13 15:36:18 +0000 @@ -0,0 +1,17 @@ + + + + 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 an OpenPGP 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 the same OpenPGP key, and the password is then used to + unlock the root file system, whereupon the computers can continue + booting normally. + === renamed file 'plugbasedclient.c' => 'plugin-runner.c' --- plugbasedclient.c 2008-07-20 02:52:20 +0000 +++ plugin-runner.c 2013-08-27 21:47:35 +0000 @@ -1,182 +1,1286 @@ -#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 */ - -struct process; - -typedef struct process{ +/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* + * Mandos plugin runner - Run Mandos plugins + * + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 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 this program. If not, see + * . + * + * Contact the authors at . + */ + +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), + asprintf(), O_CLOEXEC */ +#include /* size_t, NULL */ +#include /* malloc(), exit(), EXIT_SUCCESS, + realloc() */ +#include /* bool, true, false */ +#include /* fileno(), fprintf(), + stderr, STDOUT_FILENO */ +#include /* DIR, fdopendir(), stat(), struct + stat, waitpid(), WIFEXITED(), + WEXITSTATUS(), wait(), pid_t, + uid_t, gid_t, getuid(), getgid(), + dirfd() */ +#include /* fd_set, select(), FD_ZERO(), + FD_SET(), FD_ISSET(), FD_CLR */ +#include /* wait(), waitpid(), WIFEXITED(), + WEXITSTATUS(), WTERMSIG(), + WCOREDUMP() */ +#include /* struct stat, stat(), S_ISREG() */ +#include /* and, or, not */ +#include /* DIR, struct dirent, fdopendir(), + readdir(), closedir(), dirfd() */ +#include /* struct stat, stat(), S_ISREG(), + fcntl(), setuid(), setgid(), + F_GETFD, F_SETFD, FD_CLOEXEC, + access(), pipe(), fork(), close() + dup2(), STDOUT_FILENO, _exit(), + execv(), write(), read(), + close() */ +#include /* fcntl(), F_GETFD, F_SETFD, + FD_CLOEXEC */ +#include /* strsep, strlen(), asprintf(), + 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() */ + +#define BUFFER_SIZE 256 + +#define PDIR "/lib/mandos/plugins.d" +#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*/ pid_t pid; int fd; char *buffer; - int buffer_size; - int buffer_length; - struct process *next; -} process; - -#define BUFFER_SIZE 256 + size_t buffer_size; + size_t buffer_length; + bool eof; + volatile sig_atomic_t completed; + int status; + 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 */ +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))){ + 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; + 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; + + /* Append the new plugin to the list */ + plugin_list = new_plugin; + return new_plugin; +} + +/* Helper function for add_argument and add_environment */ +__attribute__((nonnull)) +static bool add_to_char_array(const char *new, char ***array, + int *len){ + /* Resize the pointed-to array to hold one more pointer */ + do { + *array = realloc(*array, sizeof(char *) + * (size_t) ((*len) + 2)); + } while(*array == NULL and errno == EINTR); + /* Malloc check */ + if(*array == NULL){ + return false; + } + /* 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))) +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))) +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 **e = p->environ; *e != NULL; e++){ + if(strncmp(*e, def, namelen + 1) == 0){ + /* It already exists */ + if(replace){ + char *new; + do { + new = realloc(*e, strlen(def) + 1); + } while(new == NULL and errno == EINTR); + if(new == NULL){ + return false; + } + *e = new; + strcpy(*e, def); + } + return true; + } + } + return add_to_char_array(def, &(p->environ), &(p->envc)); +} + +/* + * Based on the example in the GNU LibC manual chapter 13.13 "File + * Descriptor Flags". + | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] | + */ +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)); +} + + +/* 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)) +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; *arg != NULL; arg++){ + free(*arg); + } + 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); + } +} int main(int argc, char *argv[]){ - char plugindir[] = "plugins.d"; - size_t d_name_len, plugindir_len = sizeof(plugindir)-1; - DIR *dir; + char *plugindir = NULL; + char *argfile = NULL; + FILE *conffp; + size_t d_name_len; + DIR *dir = NULL; struct dirent *dirst; struct stat st; - fd_set rfds_orig; + fd_set rfds_all; int ret, maxfd = 0; - process *process_list = NULL; - - dir = opendir(plugindir); - - if(dir == NULL){ - fprintf(stderr, "Can not open directory\n"); - return EXIT_FAILURE; - } - - FD_ZERO(&rfds_orig); - + 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; + + /* 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; + } + + /* 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" }, + { .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 }, + /* + * 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){ + 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; + } + } + } + break; + case 'G': /* --global-env */ + add_environment(getplugin(NULL), arg, true); + 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; + } + } + } + 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; + } + add_environment(getplugin(arg), envdef, true); + } + break; + case 'd': /* --disable */ + { + plugin *p = getplugin(arg); + if(p != NULL){ + p->disabled = true; + } + } + break; + case 'e': /* --enable */ + { + plugin *p = getplugin(arg); + if(p != NULL){ + p->disabled = false; + } + } + break; + case 128: /* --plugin-dir */ + free(plugindir); + plugindir = strdup(arg); + 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; + 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; + break; + case 132: /* --debug */ + debug = true; + 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); + 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); + 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; + } + 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); + break; + case 130: /* --userid */ + case 131: /* --groupid */ + case 132: /* --debug */ + 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 */ + 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; + custom_argv = realloc(custom_argv, sizeof(char *) + * ((unsigned int) custom_argc + 1)); + if(custom_argv == NULL){ + error(0, errno, "realloc"); + exitstatus = EX_OSERR; + free(org_line); + goto fallback; + } + 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; + } + + 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){ + error(0, errno, "open"); + } 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"); + } + } + } + TEMP_FAILURE_RETRY(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 */ + { + int dir_fd = -1; + if(plugindir == NULL){ + dir_fd = open(PDIR, O_RDONLY | +#ifdef O_CLOEXEC + O_CLOEXEC +#else /* not O_CLOEXEC */ + 0 +#endif /* not O_CLOEXEC */ + ); + } else { + dir_fd = open(plugindir, 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"); + TEMP_FAILURE_RETRY(close(dir_fd)); + exitstatus = EX_OSERR; + goto fallback; + } +#endif /* O_CLOEXEC */ + + dir = fdopendir(dir_fd); + if(dir == NULL){ + error(0, errno, "Could not open plugin dir"); + TEMP_FAILURE_RETRY(close(dir_fd)); + exitstatus = EX_OSERR; + goto fallback; + } + } + + FD_ZERO(&rfds_all); + + /* Read and execute any executable in the plugin directory*/ while(true){ - dirst = readdir(dir); + do { + dirst = readdir(dir); + } while(dirst == NULL and errno == EINTR); - // All directory entries have been processed + /* All directory entries have been processed */ if(dirst == NULL){ + if(errno == EBADF){ + error(0, errno, "readdir"); + exitstatus = EX_IOERR; + goto fallback; + } 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 + 1); - 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]; - pipe(pipefd); - new_process->pid = fork(); - if(new_process->pid == 0){ - /* this is the child process */ + /* Ignore dotfiles, backup files and other junk */ + { + bool bad_name = false; + + const char const *bad_prefixes[] = { ".", "#", NULL }; + + const char const *bad_suffixes[] = { "~", "#", ".dpkg-new", + ".dpkg-old", + ".dpkg-bak", + ".dpkg-divert", NULL }; + for(const char **pre = bad_prefixes; *pre != NULL; pre++){ + size_t pre_len = strlen(*pre); + if((d_name_len >= pre_len) + and strncmp((dirst->d_name), *pre, pre_len) == 0){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad prefix %s\n", dirst->d_name, *pre); + } + bad_name = true; + break; + } + } + if(bad_name){ + continue; + } + for(const char **suf = bad_suffixes; *suf != NULL; suf++){ + size_t suf_len = strlen(*suf); + if((d_name_len >= suf_len) + and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf) + == 0)){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad suffix %s\n", dirst->d_name, *suf); + } + bad_name = true; + break; + } + } + + if(bad_name){ + continue; + } + } + + char *filename; + if(plugindir == NULL){ + ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s", + dirst->d_name)); + } else { + ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s", + plugindir, + dirst->d_name)); + } + if(ret < 0){ + error(0, errno, "asprintf"); + continue; + } + + ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st)); + if(ret == -1){ + error(0, errno, "stat"); + free(filename); + continue; + } + + /* Ignore non-executable files */ + if(not S_ISREG(st.st_mode) + or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){ + if(debug){ + fprintf(stderr, "Ignoring plugin dir entry \"%s\"" + " with bad type or mode\n", filename); + } + free(filename); + continue; + } + + plugin *p = getplugin(dirst->d_name); + if(p == NULL){ + error(0, errno, "getplugin"); + free(filename); + continue; + } + if(p->disabled){ + if(debug){ + fprintf(stderr, "Ignoring disabled plugin \"%s\"\n", + dirst->d_name); + } + free(filename); + 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 will call + using execve and 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]; + ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd)); + if(ret == -1){ + error(0, errno, "pipe"); + exitstatus = EX_OSERR; + goto fallback; + } + /* Ask OS to automatic close the pipe on exec */ + ret = set_cloexec_flag(pipefd[0]); + if(ret < 0){ + error(0, errno, "set_cloexec_flag"); + exitstatus = EX_OSERR; + goto fallback; + } + ret = set_cloexec_flag(pipefd[1]); + if(ret < 0){ + error(0, errno, "set_cloexec_flag"); + exitstatus = EX_OSERR; + goto fallback; + } + /* 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; + 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"); + exitstatus = EX_OSERR; + 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(dirfd(dir) < 0){ + /* If dir has no file descriptor, we could not set FD_CLOEXEC + above and must now close it manually here. */ closedir(dir); - close(pipefd[0]); /* close unused read end of pipe */ - dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ - /* create a new modified argument list */ - char **new_argv = malloc(sizeof(char *) * argc + 1); - new_argv[0] = filename; - for(int i = 1; i < argc; i++){ - new_argv[i] = argv[i]; - } - new_argv[argc] = NULL; - if(execv(filename, new_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){ - 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 - + 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); - process_itr->buffer_length+=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){ - write(STDOUT_FILENO, process_itr->buffer, - process_itr->buffer_length); - 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"); - } + } + if(p->environ[0] == NULL){ + if(execv(filename, p->argv) < 0){ + error(0, errno, "execv for %s", filename); + _exit(EX_OSERR); + } + } else { + if(execve(filename, p->argv, p->environ) < 0){ + error(0, errno, "execve for %s", filename); + _exit(EX_OSERR); + } + } + /* no return */ + } + /* Parent process */ + TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of + pipe */ + free(filename); + plugin *new_plugin = getplugin(dirst->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; + goto fallback; + } + + new_plugin->pid = pid; + new_plugin->fd = pipefd[0]; + + /* 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); /* Spurious warning from + -Wconversion */ + + if(maxfd < new_plugin->fd){ + maxfd = new_plugin->fd; + } + } + + TEMP_FAILURE_RETRY(closedir(dir)); + dir = NULL; + free_plugin(getplugin(NULL)); + + for(plugin *p = plugin_list; p != NULL; p = p->next){ + if(p->pid != 0){ break; } - } - return EXIT_SUCCESS; + 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))); + } else if(WCOREDUMP(proc->status)){ + fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped" + " core\n", proc->name, (intmax_t) (proc->pid)); + } + } + + /* Remove the plugin */ + FD_CLR(proc->fd, &rfds_all); /* Spurious warning from + -Wconversion */ + + /* 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)){ /* Spurious + warning from + -Wconversion */ + /* 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){ + proc->buffer = realloc(proc->buffer, proc->buffer_size + + (size_t) BUFFER_SIZE); + if(proc->buffer == NULL){ + error(0, errno, "malloc"); + exitstatus = EX_OSERR; + goto fallback; + } + 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); + } + + if(dir != NULL){ + closedir(dir); + } + + /* 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(argfile); + + return exitstatus; } === added file 'plugin-runner.conf' --- plugin-runner.conf 1970-01-01 00:00:00 +0000 +++ plugin-runner.conf 2009-04-17 08:26:17 +0000 @@ -0,0 +1,10 @@ +## 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 === added file 'plugin-runner.xml' --- plugin-runner.xml 1970-01-01 00:00:00 +0000 +++ plugin-runner.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,643 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2012 + 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 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. + + + 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. + + + + + 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. + + + + + + + + + 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 + + + + + + Run plugins from a different directory, read a different + configuration file, and add two options to the + mandos-client + 8mandos plugin: + + + + +cd /etc/keys/mandos; &COMMANDNAME; --config-file=/etc/mandos/plugin-runner.conf --plugin-dir /usr/lib/mandos/plugins.d --options-for=mandos-client:--pubkey=pubkey.txt,--seckey=seckey.txt + + + + + + 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/Makefile' --- plugins.d/Makefile 2008-07-20 02:52:20 +0000 +++ plugins.d/Makefile 1970-01-01 00:00:00 +0000 @@ -1,18 +0,0 @@ -CFLAGS=-Wall -g --std=gnu99 -LDFLAGS=-lgnutls -lavahi-core -lgpgme - -PROGS=mandosclient passprompt - -objects=mandosclient.o passprompt.o - -all: $(PROGS) - -mandosclient: mandosclient.o - $(LINK.o) -lgnutls -lavahi-core -lgpgme $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ - -passprompt: passprompt.o - $(LINK.o) $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ - -.PHONY : clean -clean : - -rm -f $(PROGS) $(objects) core === added file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 1970-01-01 00:00:00 +0000 +++ plugins.d/askpass-fifo.c 2011-12-31 23:05:34 +0000 @@ -0,0 +1,202 @@ +/* -*- coding: utf-8 -*- */ +/* + * Askpass-FIFO - Read a password from a FIFO and output it + * + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 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 this program. If not, see + * . + * + * Contact the authors at . + */ + +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY() */ +#include /* 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(), ... */ + + +/* 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); +} + +int main(__attribute__((unused))int argc, + __attribute__((unused))char **argv){ + int ret = 0; + ssize_t sret; + + /* 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"); + case ENAMETOOLONG: + case ENOSPC: + case EROFS: + default: + error_plus(EX_OSERR, errno, "mkfifo"); + case ENOENT: + /* no "/lib/cryptsetup"? */ + error_plus(EX_UNAVAILABLE, errno, "mkfifo"); + 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; + } + } + + /* 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; +} === added file 'plugins.d/askpass-fifo.xml' --- plugins.d/askpass-fifo.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/askpass-fifo.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,166 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2011 + 2012 + 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. + + + + + + + + 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 + + +
+ + + + + === renamed file 'plugins.d/mandosclient.c' => 'plugins.d/mandos-client.c' --- plugins.d/mandosclient.c 2008-07-20 02:52:20 +0000 +++ plugins.d/mandos-client.c 2013-10-05 19:34:40 +0000 @@ -1,44 +1,105 @@ -/* $Id$ */ - -/* PLEASE NOTE * - * This file demonstrates how to use Avahi's core API, this is - * the embeddable mDNS stack for embedded applications. +/* -*- coding: utf-8 -*- */ +/* + * Mandos-client - get and decrypt data from a Mandos server * - * End user applications should *not* use this API and should use - * the D-Bus or C APIs, please see - * client-browse-services.c and glib-integration.c - * - * I repeat, you probably do *not* want to use this example. + * This program is partly derived from an example program for an Avahi + * service browser, downloaded from + * . This + * includes the following functions: "resolve_callback", + * "browse_callback", and parts of "main". + * + * Everything else is + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 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 this program. If not, see + * . + * + * Contact the authors at . */ -/*** - This file is part of avahi. - - avahi is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 2.1 of the - License, or (at your option) any later version. - - avahi is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General - Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with avahi; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - +/* Needed by GPGME, specifically gpgme_data_seek() */ +#ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE +#endif +#ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include /* if_nametoindex */ - +#endif + +#define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ + +#include /* fprintf(), stderr, fwrite(), + stdout, ferror(), remove() */ +#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 /* memset(), strcmp(), strlen(), + strerror(), asprintf(), strcpy() */ +#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() */ +#include /* open() */ +#include /* opendir(), struct dirent, readdir() + */ +#include /* PRIu16, PRIdMAX, intmax_t, + strtoimax() */ +#include /* perror(), errno, + 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() */ +#include /* inet_pton(), htons, inet_ntop() */ +#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() */ + +#ifdef __linux__ +#include /* klogctl() */ +#endif /* __linux__ */ + +/* Avahi */ +/* All Avahi types, constants and functions + Avahi*, avahi_*, + AVAHI_* */ #include #include #include @@ -46,526 +107,2427 @@ #include #include -//mandos client part -#include /* socket(), setsockopt(), inet_pton() */ -#include /* socket(), setsockopt(), 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 - - -#ifndef CERT_ROOT -#define CERT_ROOT "/conf/conf.d/cryptkeyreq/" -#endif -#define CERTFILE CERT_ROOT "openpgp-client.txt" -#define KEYFILE CERT_ROOT "openpgp-client-key.txt" +/* GnuTLS */ +#include /* All GnuTLS types, constants and + functions: + gnutls_* + init_gnutls_session(), + GNUTLS_* */ +#include + /* gnutls_certificate_set_openpgp_key_file(), + GNUTLS_OPENPGP_FMT_BASE64 */ + +/* GPGME */ +#include /* All GPGME types, constants and + functions: + gpgme_* + GPGME_PROTOCOL_OpenPGP, + GPG_ERR_NO_* */ + #define BUFFER_SIZE 256 -#define DH_BITS 1024 - + +#define PATHDIR "/conf/conf.d/mandos" +#define SECKEY "seckey.txt" +#define PUBKEY "pubkey.txt" +#define HOOKDIR "/lib/mandos/network-hooks.d" + +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; +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 { - gnutls_session_t session; + AvahiServer *server; gnutls_certificate_credentials_t cred; + unsigned int dh_bits; gnutls_dh_params_t dh_params; -} encrypted_session; - - -ssize_t gpg_packet_decrypt (char *packet, size_t packet_size, char **new_packet, char *homedir){ - gpgme_data_t dh_crypto, dh_plain; + const char *priority; 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))) +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. + */ +size_t incbuffer(char **buffer, size_t buffer_length, + size_t buffer_capacity){ + if(buffer_length + BUFFER_SIZE > buffer_capacity){ + *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE); + if(buffer == NULL){ + return 0; + } + buffer_capacity += BUFFER_SIZE; + } + return buffer_capacity; +} + +/* Add server to set of servers to retry periodically */ +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"); + 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; + /* Place the new server last in the list */ + } else { + new_server->next = *current_server; + new_server->prev = (*current_server)->prev; + new_server->prev->next = new_server; + (*current_server)->prev = new_server; + } + ret = clock_gettime(CLOCK_MONOTONIC, &(*current_server)->last_seen); + if(ret == -1){ + perror_plus("clock_gettime"); + return false; + } + return true; +} + +/* + * Initialize GPGME. + */ +static bool init_gpgme(const char *seckey, const char *pubkey, + const char *tempdir, mandos_context *mc){ gpgme_error_t rc; - ssize_t ret; - size_t new_packet_capacity = 0; - size_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 *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; + } + + 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; + } + + ret = (int)TEMP_FAILURE_RETRY(close(fd)); + if(ret == -1){ + perror_plus("close"); + } + gpgme_data_release(pgp_data); + return true; + } + + if(debug){ + fprintf_plus(stderr, "Initializing GPGME\n"); + } + /* Init GPGME */ gpgme_check_version(NULL); - gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); + 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; + } - /* 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; + /* 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; } while(engine_info != NULL){ if(engine_info->protocol == GPGME_PROTOCOL_OpenPGP){ gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, - engine_info->file_name, homedir); + engine_info->file_name, tempdir); break; } engine_info = engine_info->next; } if(engine_info == NULL){ - 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)); + 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, "Mandos plugin mandos-client: " + "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 + */ +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)); return -1; } /* Create new empty GPGME data buffer for the plaintext */ rc = gpgme_data_new(&dh_plain); - 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; - } - -/* gpgme_decrypt_result_t result; */ -/* result = gpgme_op_decrypt_result(ctx); */ -/* 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; */ -/* } */ -/* } */ - - /* Delete the GPGME FILE pointer cryptotext data buffer */ - gpgme_data_release(dh_crypto); + if(rc != GPG_ERR_NO_ERROR){ + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "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 { + fprintf_plus(stderr, "Unsupported algorithm: %s\n", + result->unsupported_algorithm); + fprintf_plus(stderr, "Wrong key usage: %u\n", + result->wrong_key_usage); + if(result->file_name != NULL){ + fprintf_plus(stderr, "File name: %s\n", result->file_name); + } + gpgme_recipient_t recipient; + recipient = result->recipients; + while(recipient != NULL){ + fprintf_plus(stderr, "Public key algorithm: %s\n", + gpgme_pubkey_algo_name + (recipient->pubkey_algo)); + fprintf_plus(stderr, "Key ID: %s\n", recipient->keyid); + fprintf_plus(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"); + } /* Seek back to the beginning of the GPGME plaintext data buffer */ - gpgme_data_seek(dh_plain, 0, SEEK_SET); - - *new_packet = 0; + 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; while(true){ - if (new_packet_length + BUFFER_SIZE > new_packet_capacity){ - *new_packet = realloc(*new_packet, new_packet_capacity + BUFFER_SIZE); - if (*new_packet == NULL){ - perror("realloc"); - return -1; - } - new_packet_capacity += BUFFER_SIZE; + plaintext_capacity = incbuffer(plaintext, + (size_t)plaintext_length, + plaintext_capacity); + if(plaintext_capacity == 0){ + perror_plus("incbuffer"); + plaintext_length = -1; + goto decrypt_end; } - ret = gpgme_data_read(dh_plain, *new_packet + new_packet_length, BUFFER_SIZE); + ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, + BUFFER_SIZE); /* Print the data, if any */ - if (ret == 0){ - /* If password is empty, then a incorrect error will be printed */ + if(ret == 0){ + /* EOF */ break; } if(ret < 0){ - perror("gpgme_data_read"); - return -1; - } - new_packet_length += ret; - } - - /* Delete the GPGME plaintext data buffer */ + 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); + + /* Delete the GPGME plaintext data buffer */ gpgme_data_release(dh_plain); - return new_packet_length; + return plaintext_length; } -static const char * safer_gnutls_strerror (int value) { - const char *ret = gnutls_strerror (value); - if (ret == NULL) +static const char * safer_gnutls_strerror(int value){ + const char *ret = gnutls_strerror(value); + if(ret == NULL) ret = "(unknown)"; return ret; } -void debuggnutls(int level, const char* string){ - fprintf(stderr, "%s", string); +/* GnuTLS log function callback */ +static void debuggnutls(__attribute__((unused)) int level, + const char* string){ + fprintf_plus(stderr, "GnuTLS: %s", string); } -int initgnutls(encrypted_session *es){ - const char *err; +static int init_gnutls_global(const char *pubkeyfilename, + const char *seckeyfilename, + mandos_context *mc){ int ret; - if ((ret = gnutls_global_init ()) - != GNUTLS_E_SUCCESS) { - fprintf (stderr, "global_init: %s\n", safer_gnutls_strerror(ret)); - return -1; - } - - /* Uncomment to enable full debuggin on the gnutls library */ - /* gnutls_global_set_log_level(11); */ - /* gnutls_global_set_log_function(debuggnutls); */ - - - /* 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, "Initializing GnuTLS\n"); + } + + ret = gnutls_global_init(); + if(ret != GNUTLS_E_SUCCESS){ + fprintf_plus(stderr, "GnuTLS global_init: %s\n", + safer_gnutls_strerror(ret)); + return -1; + } + + if(debug){ + /* "Use a log level over 10 to enable all debugging options." + * - GnuTLS manual + */ + 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)); + gnutls_global_deinit(); + return -1; + } + + if(debug){ + fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and" + " secret key %s as GnuTLS credentials\n", + pubkeyfilename, + seckeyfilename); + } + ret = gnutls_certificate_set_openpgp_key_file - (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; - } - + (mc->cred, pubkeyfilename, seckeyfilename, + GNUTLS_OPENPGP_FMT_BASE64); + if(ret != GNUTLS_E_SUCCESS){ + fprintf_plus(stderr, + "Error[%d] while reading the OpenPGP 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; + } + ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); + if(ret != GNUTLS_E_SUCCESS){ + fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n", + 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_global_deinit(); + gnutls_dh_params_deinit(mc->dh_params); + return -1; +} + +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(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); + return -1; + } + /* ignore client certificate if any. */ - gnutls_certificate_server_set_request (es->session, GNUTLS_CERT_IGNORE); + gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE); - gnutls_dh_set_prime_bits (es->session, DH_BITS); + gnutls_dh_set_prime_bits(*session, mc->dh_bits); return 0; } -void empty_log(AvahiLogLevel level, const char *txt){} +/* Avahi log function callback */ +static void empty_log(__attribute__((unused)) AvahiLogLevel level, + __attribute__((unused)) const char *txt){} -int start_mandos_communcation(char *ip, uint16_t port){ - int ret, tcp_sd; - struct sockaddr_in6 to; - struct in6_addr ip_addr; - encrypted_session es; +/* Called when a Mandos server is found */ +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; + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + } to; char *buffer = NULL; - char *decrypted_buffer; + char *decrypted_buffer = NULL; size_t buffer_length = 0; size_t buffer_capacity = 0; - ssize_t decrypted_buffer_size; - int retval = 0; - - - tcp_sd = socket(PF_INET6, SOCK_STREAM, 0); - if(tcp_sd < 0) { - perror("socket"); - return -1; - } - - ret = setsockopt(tcp_sd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 5); - if(tcp_sd < 0) { - perror("setsockopt bindtodevice"); - return -1; - } - - memset(&to,0,sizeof(to)); - to.sin6_family = AF_INET6; - ret = inet_pton(AF_INET6, ip, &ip_addr); - if (ret < 0 ){ - perror("inet_pton"); - return -1; - } + size_t written; + int retval = -1; + gnutls_session_t session; + int pf; /* Protocol family */ + + 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, 0); + if(tcp_sd < 0){ + int e = errno; + perror_plus("socket"); + errno = e; + goto mandos_end; + } + + if(quit_now){ + errno = EINTR; + goto mandos_end; + } + + memset(&to, 0, sizeof(to)); + if(af == AF_INET6){ + to.in6.sin6_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in6.sin6_addr); + } else { /* IPv4 */ + to.in.sin_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in.sin_addr); + } + if(ret < 0 ){ + int e = errno; + perror_plus("inet_pton"); + errno = e; + goto mandos_end; + } if(ret == 0){ - fprintf(stderr, "Bad address: %s\n", ip); - return -1; - } - to.sin6_port = htons(port); - to.sin6_scope_id = if_nametoindex("eth0"); - - 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); - - ret = gnutls_handshake (es.session); - - if (ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "\n*** Handshake failed ***\n"); - gnutls_perror (ret); - retval = -1; - goto exit; - } - - //retrive password - while(true){ - 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){ + int e = errno; + fprintf_plus(stderr, "Bad address: %s\n", ip); + errno = e; + goto mandos_end; + } + if(af == AF_INET6){ + to.in6.sin6_port = htons(port); + if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ + (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and + -Wunreachable-code*/ + 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 */ + to.in6.sin6_scope_id = (uint32_t)if_index; + } + } else { + to.in.sin_port = htons(port); /* Spurious warnings from + -Wconversion and + -Wunreachable-code */ + } + + 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] = ""; + const char *pcret; + if(af == AF_INET6){ + pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr, + sizeof(addrstr)); + } else { + pcret = inet_ntop(af, &(to.in.sin_addr), addrstr, + sizeof(addrstr)); + } + if(pcret == NULL){ + perror_plus("inet_ntop"); + } else { + if(strcmp(addrstr, ip) != 0){ + fprintf_plus(stderr, "Canonical address form: %s\n", addrstr); + } + } + } + + if(quit_now){ + errno = EINTR; + goto mandos_end; + } + + if(af == AF_INET6){ + ret = connect(tcp_sd, &to.in6, sizeof(to)); + } else { + ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */ + } + if(ret < 0){ + if ((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){ + int e = errno; + perror_plus("connect"); + errno = e; + } + goto mandos_end; + } + + if(quit_now){ + errno = EINTR; + goto mandos_end; + } + + 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){ + if(debug){ + fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n"); + gnutls_perror(ret); + } + errno = EPROTO; + goto mandos_end; + } + + /* Read OpenPGP packet that contains the wanted password */ + + if(debug){ + fprintf_plus(stderr, "Retrieving OpenPGP 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){ break; } - if (ret < 0){ - switch(ret){ + if(sret < 0){ + switch(sret){ case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: break; case GNUTLS_E_REHANDSHAKE: - ret = gnutls_handshake (es.session); - if (ret < 0){ - fprintf(stderr, "\n*** Handshake failed ***\n"); - gnutls_perror (ret); - retval = -1; - goto exit; + 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; } break; default: - 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; + 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; } } else { - buffer_length += ret; - } - } - - if (buffer_length > 0){ - if ((decrypted_buffer_size = gpg_packet_decrypt(buffer, buffer_length, &decrypted_buffer, CERT_ROOT)) == 0){ + 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){ + + written = 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); + if(ret == 0 and ferror(stdout)){ + int e = errno; + if(debug){ + fprintf_plus(stderr, "Error writing encrypted data: %s\n", + strerror(errno)); + } + errno = e; + goto mandos_end; + } + written += (size_t)ret; + } + retval = 0; + } + } + + /* Shutdown procedure */ + + mandos_end: + { + int e = errno; + free(decrypted_buffer); + free(buffer); + if(tcp_sd >= 0){ + ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd)); + } + if(ret == -1){ + if(e == 0){ + e = errno; + } + perror_plus("close"); + } + gnutls_deinit(session); + errno = e; + if(quit_now){ + errno = EINTR; retval = -1; - } else { - fwrite (decrypted_buffer, 1, decrypted_buffer_size, stdout); - free(decrypted_buffer); } } - - free(buffer); - - //shutdown procedure - 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 AvahiSimplePoll *simple_poll = NULL; -static AvahiServer *server = NULL; - -static void resolve_callback( - AvahiSServiceResolver *r, - AVAHI_GCC_UNUSED 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, - AvahiStringList *txt, - AvahiLookupResultFlags flags, - AVAHI_GCC_UNUSED void* userdata) { - - assert(r); - - /* Called whenever a service has been resolved successfully or timed out */ - - switch (event) { - case AVAHI_RESOLVER_FAILURE: - 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: { - char ip[AVAHI_ADDRESS_STR_MAX]; - avahi_address_snprint(ip, sizeof(ip), address); - int ret = start_mandos_communcation(ip, port); - if (ret == 0){ - exit(EXIT_SUCCESS); - } else { - exit(EXIT_FAILURE); - } - } - } - 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* userdata) { - - AvahiServer *s = userdata; - assert(b); - - /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ - - switch (event) { - - 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[]) { +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; + } + + /* Called whenever a service has been resolved successfully or + timed out */ + + if(quit_now){ + return; + } + + 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))); + break; + + case AVAHI_RESOLVER_FOUND: + { + 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); + } + 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); + } + } + } + } + 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; +} + +bool get_flags(const char *ifname, struct ifreq *ifr){ + int ret; + error_t ret_errno; + + int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(s < 0){ + ret_errno = errno; + perror_plus("socket"); + errno = ret_errno; + return false; + } + strcpy(ifr->ifr_name, ifname); + ret = ioctl(s, SIOCGIFFLAGS, ifr); + if(ret == -1){ + if(debug){ + ret_errno = errno; + perror_plus("ioctl SIOCGIFFLAGS"); + errno = ret_errno; + } + return false; + } + return true; +} + +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.) + */ +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. + */ +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 + */ +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); +} + +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? */ +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; + } + + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + return 0; + } + + ret = stat(fullname, &st); + 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; +} + +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; + } + } + } +} + +/* Set effective uid to 0, return errno */ +error_t raise_privileges(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(seteuid(0) == -1){ + ret_errno = errno; + perror_plus("seteuid"); + } + errno = old_errno; + return ret_errno; +} + +/* Set effective and real user ID to 0. Return errno. */ +error_t raise_privileges_permanently(void){ + error_t old_errno = errno; + error_t ret_errno = raise_privileges(); + if(ret_errno != 0){ + errno = old_errno; + return ret_errno; + } + if(setuid(0) == -1){ + ret_errno = errno; + perror_plus("seteuid"); + } + errno = old_errno; + return ret_errno; +} + +/* Set effective user ID to unprivileged saved user ID */ +error_t lower_privileges(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(seteuid(uid) == -1){ + ret_errno = errno; + perror_plus("seteuid"); + } + errno = old_errno; + return ret_errno; +} + +/* Lower privileges permanently */ +error_t lower_privileges_permanently(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(setuid(uid) == -1){ + ret_errno = errno; + perror_plus("setuid"); + } + errno = old_errno; + return ret_errno; +} + +bool run_network_hooks(const char *mode, const char *interface, + const float delay){ + struct dirent **direntries; + struct dirent *direntry; + int ret; + int numhooks = scandir(hookdir, &direntries, runnable_hook, + alphasort); + if(numhooks == -1){ + if(errno == ENOENT){ + if(debug){ + fprintf_plus(stderr, "Network hook directory \"%s\" not" + " found\n", hookdir); + } + } else { + perror_plus("scandir"); + } + } else { + int devnull = open("/dev/null", O_RDONLY); + for(int i = 0; i < numhooks; i++){ + direntry = direntries[i]; + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + continue; + } + 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 */ + raise_privileges_permanently(); + /* Set group */ + errno = 0; + ret = setgid(0); + if(ret == -1){ + perror_plus("setgid"); + } + /* Reset supplementary groups */ + errno = 0; + ret = setgroups(0, NULL); + if(ret == -1){ + perror_plus("setgroups"); + } + dup2(devnull, STDIN_FILENO); + close(devnull); + dup2(STDERR_FILENO, STDOUT_FILENO); + 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", 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); + } + } + if(execl(fullname, direntry->d_name, mode, NULL) == -1){ + perror_plus("execl"); + _exit(EXIT_FAILURE); + } + } else { + int status; + if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){ + perror_plus("waitpid"); + free(fullname); + 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(fullname); + continue; + } + } else if(WIFSIGNALED(status)){ + fprintf_plus(stderr, "Warning: network hook \"%s\" died by" + " signal %d\n", direntry->d_name, + WTERMSIG(status)); + free(fullname); + continue; + } else { + fprintf_plus(stderr, "Warning: network hook \"%s\"" + " crashed\n", direntry->d_name); + free(fullname); + continue; + } + } + free(fullname); + if(debug){ + fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n", + direntry->d_name); + } + } + close(devnull); + } + return true; +} + +error_t bring_up_interface(const char *const interface, + const float delay){ + int sd = -1; + error_t old_errno = errno; + error_t ret_errno = 0; + int ret, ret_setflags; + 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)){ + if(not get_flags(interface, &network) and debug){ + ret_errno = errno; + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", interface); + return ret_errno; + } + network.ifr_flags |= IFF_UP; + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + ret_errno = errno; + perror_plus("socket"); + errno = old_errno; + return ret_errno; + } + + if(quit_now){ + close(sd); + errno = old_errno; + return EINTR; + } + + if(debug){ + fprintf_plus(stderr, "Bringing up interface \"%s\"\n", + interface); + } + + /* Raise priviliges */ + raise_privileges(); + +#ifdef __linux__ + /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO + messages about the network interface to mess up the prompt */ + int ret_linux = klogctl(8, NULL, 5); + bool restore_loglevel = true; + if(ret_linux == -1){ + restore_loglevel = false; + perror_plus("klogctl"); + } +#endif /* __linux__ */ + ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); + ret_errno = errno; +#ifdef __linux__ + if(restore_loglevel){ + ret_linux = klogctl(7, NULL, 0); + if(ret_linux == -1){ + perror_plus("klogctl"); + } + } +#endif /* __linux__ */ + + /* Lower privileges */ + lower_privileges(); + + /* Close the socket */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror_plus("close"); + } + + if(ret_setflags == -1){ + errno = ret_errno; + perror_plus("ioctl SIOCSIFFLAGS +IFF_UP"); + errno = old_errno; + return ret_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; +} + +error_t take_down_interface(const char *const interface){ + int sd = -1; + error_t old_errno = errno; + error_t ret_errno = 0; + int ret, ret_setflags; + 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)){ + if(not get_flags(interface, &network) and debug){ + ret_errno = errno; + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", interface); + return ret_errno; + } + network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + ret_errno = errno; + perror_plus("socket"); + errno = old_errno; + return ret_errno; + } + + if(debug){ + fprintf_plus(stderr, "Taking down interface \"%s\"\n", + interface); + } + + /* Raise priviliges */ + raise_privileges(); + + ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); + ret_errno = errno; + + /* Lower privileges */ + lower_privileges(); + + /* Close the socket */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror_plus("close"); + } + + if(ret_setflags == -1){ + errno = ret_errno; + perror_plus("ioctl SIOCSIFFLAGS -IFF_UP"); + errno = old_errno; + return ret_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 = 1024, + .priority = "SECURE256:!CTYPE-X.509:" + "+CTYPE-OPENPGP", .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 tempdir[] = "/tmp/mandosXXXXXX"; + bool tempdir_created = false; + AvahiIfIndex if_index = AVAHI_IF_UNSPEC; + const char *seckey = PATHDIR "/" SECKEY; + const char *pubkey = PATHDIR "/" PUBKEY; + char *interfaces_hooks = NULL; + size_t interfaces_hooks_size = 0; + + 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 = 2 }, + { .name = "dh-bits", .key = 129, + .arg = "BITS", + .doc = "Bit length of the prime number used in 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 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 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); + case -3: /* --usage */ + argp_state_help(state, state->out_stream, + ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); + 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 = argp_parse(&argp, argc, argv, + ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL); + switch(ret){ + case 0: + break; + case ENOMEM: + default: + errno = ret; + perror_plus("argp_parse"); + exitcode = EX_OSERR; + goto end; + case EINVAL: + exitcode = EX_USAGE; + goto end; + } + } + + { + /* Work around Debian bug #633582: + */ + + /* Re-raise priviliges */ + if(raise_privileges() == 0){ + 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"); + } + } + } + TEMP_FAILURE_RETRY(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"); + } + } + } + TEMP_FAILURE_RETRY(close(pubkey_fd)); + } + } + + /* Lower privileges */ + 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); + interfaces_hooks_size = mc.interfaces_size; + argz_stringify(interfaces_hooks, interfaces_hooks_size, + (int)','); + } + if(not run_network_hooks("start", interfaces_hooks != NULL ? + interfaces_hooks : "", delay)){ + goto end; + } + } + + 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; + /* 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){ + perror_plus("argz_add"); + continue; + } + if(debug){ + fprintf_plus(stderr, "Will use interface \"%s\"\n", + direntries[i]->d_name); + } + } + free(direntries); + } else { + 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); + ret = bring_up_interface(interface, delay); + if(not interface_was_up){ + if(ret != 0){ + errno = ret; + perror_plus("Failed to bring up interface"); + } else { + ret_errno = argz_add(&interfaces_to_take_down, + &interfaces_to_take_down_size, + interface); + } + } + } + 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; + } + + ret = init_gnutls_global(pubkey, seckey, &mc); + 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; + } + + if(mkdtemp(tempdir) == NULL){ + perror_plus("mkdtemp"); + goto end; + } + tempdir_created = true; + + 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; + } + + { AvahiServerConfig config; - AvahiSServiceBrowser *sb = NULL; - int error; - int ret = 1; - - avahi_set_log_function(empty_log); - - /* Initialize the psuedo-RNG */ - srand(time(NULL)); - - /* Allocate main loop object */ - if (!(simple_poll = avahi_simple_poll_new())) { - fprintf(stderr, "Failed to create simple poll object.\n"); - goto fail; - } - - /* Do not publish any local records */ + /* Do not publish any local Zeroconf records */ avahi_server_config_init(&config); config.publish_hinfo = 0; config.publish_addresses = 0; config.publish_workstation = 0; config.publish_domain = 0; - -/* /\* Set a unicast DNS server for wide area DNS-SD *\/ */ -/* avahi_address_parse("193.11.177.11", AVAHI_PROTO_UNSPEC, &config.wide_area_servers[0]); */ -/* config.n_wide_area_servers = 1; */ -/* config.enable_wide_area = 1; */ /* Allocate a new server */ - server = avahi_server_new(avahi_simple_poll_get(simple_poll), &config, NULL, NULL, &error); - - /* Free the configuration data */ + mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll), + &config, NULL, NULL, &ret_errno); + + /* Free the Avahi configuration data */ avahi_server_config_free(&config); - - /* Check wether creating the server object succeeded */ - if (!server) { - fprintf(stderr, "Failed to create server: %s\n", avahi_strerror(error)); - goto fail; - } - - /* Create the service browser */ - if (!(sb = avahi_s_service_browser_new(server, if_nametoindex("eth0"), AVAHI_PROTO_INET6, "_mandos._tcp", NULL, 0, browse_callback, server))) { - fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_server_errno(server))); - goto fail; - } - - /* Run the main loop */ - avahi_simple_poll_loop(simple_poll); - - ret = 0; - -fail: - - /* 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 ret; + } + + /* 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_errno)); + 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){ + 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_global_deinit(); + 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; + free(mc.current_server); + mc.current_server = next; + } + } + + /* Re-raise priviliges */ + { + raise_privileges(); + + /* 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_errno = take_down_interface(interface); + if(ret_errno != 0){ + errno = ret_errno; + 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"); + } + } + + lower_privileges_permanently(); + } + + free(interfaces_to_take_down); + free(interfaces_hooks); + + /* Removes the GPGME temp directory and all files inside */ + if(tempdir_created){ + struct dirent **direntries = NULL; + struct dirent *direntry = NULL; + int numentries = scandir(tempdir, &direntries, notdotentries, + alphasort); + if (numentries > 0){ + for(int i = 0; i < numentries; i++){ + direntry = direntries[i]; + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", tempdir, + direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + continue; + } + ret = remove(fullname); + if(ret == -1){ + fprintf_plus(stderr, "remove(\"%s\"): %s\n", fullname, + strerror(errno)); + } + free(fullname); + } + } + + /* need to clean even if 0 because man page doesn't specify */ + free(direntries); + if (numentries == -1){ + perror_plus("scandir"); + } + ret = rmdir(tempdir); + if(ret == -1 and errno != ENOENT){ + perror_plus("rmdir"); + } + } + + 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; } === added file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/mandos-client.xml 2013-06-23 15:13:06 +0000 @@ -0,0 +1,899 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2012 + 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 an OpenPGP 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. + + + This option is normally only useful for 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; not bring up + any interfaces specified + after 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. + + + + + + + + + + + + + + + + Sets the number of bits to use for the prime number in the + TLS Diffie-Hellman key exchange. Default is 1024. + + + + + + + + + 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 + + This program does not use any 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. + + + + + /lib/mandos/network-hooks.d + + + Directory where network hooks are located. Change this + with the option. See + . + + + + + + + + + + + + + + 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 a custom key: + + + + +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt + + + + + + Run in debug mode, with a custom key, 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 --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 + + + + + + + SECURITY + + This program is set-uid to root, but 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 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, 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 OpenPGP 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 4346: The Transport Layer Security (TLS) + Protocol Version 1.1 + + + + TLS 1.1 is the protocol implemented by GnuTLS. + + + + + + RFC 4880: OpenPGP Message Format + + + + The data received from the server is binary encrypted + OpenPGP data. + + + + + + RFC 5081: Using OpenPGP Keys for Transport Layer + Security + + + + This is implemented by GnuTLS and used by this program so + that OpenPGP keys can be used. + + + + + +
+ + + + + + === renamed file 'plugins.d/passprompt.c' => 'plugins.d/password-prompt.c' --- plugins.d/passprompt.c 2008-07-20 02:52:20 +0000 +++ plugins.d/password-prompt.c 2013-10-05 19:34:40 +0000 @@ -1,94 +1,564 @@ -#define _GNU_SOURCE /* getline() */ -#define _FORTIFY_SOURCE 2 -#include /* struct termios, tcsetattr(), +/* -*- coding: utf-8; mode: c; mode: orgtbl -*- */ +/* + * Password-prompt - Read a password from the terminal and print it + * + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 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 this program. If not, see + * . + * + * Contact the authors at . + */ + +#define _GNU_SOURCE /* getline(), asprintf() */ + +#include /* struct termios, tcsetattr(), TCSAFLUSH, tcgetattr(), ECHO */ #include /* struct termios, tcsetattr(), STDIN_FILENO, TCSAFLUSH, - tcgetattr(), ECHO */ + tcgetattr(), ECHO, readlink() */ #include /* sig_atomic_t, raise(), struct sigaction, sigemptyset(), sigaction(), sigaddset(), SIGINT, - SIGQUIT, SIGHUP, SIGTERM */ -#include /* NULL, size_t */ -#include /* ssize_t */ -#include /* EXIT_SUCCESS, EXIT_FAILURE */ + SIGQUIT, SIGHUP, SIGTERM, + raise() */ +#include /* NULL, size_t, ssize_t */ +#include /* ssize_t, struct dirent, pid_t, + ssize_t, open() */ +#include /* EXIT_SUCCESS, EXIT_FAILURE, + getenv(), free() */ +#include /* scandir(), alphasort() */ #include /* fprintf(), stderr, getline(), - stdin, feof(), perror(), fputc(), - stdout */ -#include /* errno, EINVAL */ + stdin, feof(), fputc(), vfprintf(), + vasprintf() */ +#include /* errno, EBADF, ENOTTY, EINVAL, + EFAULT, EFBIG, EIO, ENOSPC, EINTR + */ +#include /* error() */ #include /* or, not */ #include /* bool, false, true */ - -volatile bool quit_now = false; - -void termination_handler(int signum){ - quit_now = 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(), ... */ + +volatile sig_atomic_t quit_now = 0; +int signal_received; +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"; + +__attribute__((format (gnu_printf, 2, 3), nonnull(1))) +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)); +} + +/* 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. + */ + __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"); + } + free(direntries); + return ret > 0; +} + int main(int argc, char **argv){ - ssize_t ret = -1; + ssize_t sret; + int ret; size_t n; struct termios t_new, t_old; char *buffer = NULL; + char *prefix = 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 = "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 = arg; + break; + case 128: + debug = true; + 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); + case -3: /* --usage */ + argp_state_help(state, state->out_stream, + ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); + 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; + } + + 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: + default: + errno = ret; + error_plus(0, errno, "argp_parse"); + return EX_OSERR; + case EINVAL: + return EX_USAGE; + } + } - if (tcgetattr(STDIN_FILENO, &t_old) != 0){ + 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){ + 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; + } + } + sigemptyset(&new_action.sa_mask); - 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); + 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){ + fprintf(stderr, "Removing echo flag from terminal attributes\n"); + } t_new = t_old; - t_new.c_lflag &= ~ECHO; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ - perror("tcsetattr-echo"); - return EXIT_FAILURE; + 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; + } } + if(debug){ + fprintf(stderr, "Waiting for input from stdin \n"); + } while(true){ - if (quit_now){ + if(quit_now){ + if(debug){ + fprintf(stderr, "Interrupted by signal, exiting.\n"); + } status = EXIT_FAILURE; break; } - fprintf(stderr, "Password: "); - ret = getline(&buffer, &n, stdin); - if (ret > 0){ - fprintf(stdout, "%s", buffer); + + if(prefix){ + fprintf(stderr, "%s ", prefix); + } + { + 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){ 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; } - // 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; + if(sret < 0){ + int e = errno; + if(errno != EINTR and 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; } } - fputc('\n', stderr); - } - - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_old) != 0){ - perror("tcsetattr+echo"); + /* 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){ + 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); } return status; === added file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/password-prompt.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,312 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2011 + 2012 + 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. + + + + + 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. + + + + + + + + + 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, 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 will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. + + + This behavior is meant to exactly mirror the behavior of + askpass, the default password prompter. + + + + + + + + BUGS + + None are known at this time. + + + + + 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 + crypttab + 5 + mandos-client + 8mandos + plugin-runner + 8mandos, + + +
+ + + + + === added file 'plugins.d/plymouth.c' --- plugins.d/plymouth.c 1970-01-01 00:00:00 +0000 +++ plugins.d/plymouth.c 2013-08-27 21:47:35 +0000 @@ -0,0 +1,487 @@ +/* -*- coding: utf-8 -*- */ +/* + * Plymouth - Read a password from Plymouth and output it + * + * Copyright © 2010-2012 Teddy Hogeborn + * Copyright © 2010-2012 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 this program. 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(), ... */ + +sig_atomic_t interrupted_by_signal = 0; + +/* Used by Ubuntu 11.04 (Natty Narwahl) */ +const char plymouth_old_pid[] = "/dev/.initramfs/plymouth.pid"; +/* Used by Ubuntu 11.10 (Oneiric Ocelot) */ +const char plymouth_pid[] = "/run/initramfs/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 }; + +static void termination_handler(__attribute__((unused))int signum){ + if(interrupted_by_signal){ + return; + } + interrupted_by_signal = 1; +} + +/* 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 *argv, bool interruptable, + bool daemonize){ + int status; + int ret; + pid_t pid; + 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 = NULL; + char **tmp; + int i = 0; + for (; argv[i]!=NULL; i++){ + tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 1)); + 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){ + return false; + } + if(ret == -1){ + error_plus(0, errno, "waitpid"); + return false; + } + 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_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){ + ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id); + if (ret < 0){ + error_plus(0, errno, "sscanf"); + } + } + /* 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; +} + +const char * const * 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 (const char * const *)argv; +} + +int main(__attribute__((unused))int argc, + __attribute__((unused))char **argv){ + char *prompt; + char *prompt_arg; + pid_t plymouth_command_pid; + int ret; + bool bret; + + /* 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. */ + 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. */ + exit(EX_UNAVAILABLE); + } + + prompt = makeprompt(); + ret = asprintf(&prompt_arg, "--prompt=%s", prompt); + free(prompt); + if(ret == -1){ + error_plus(EX_OSERR, errno, "asprintf"); + } + + /* plymouth ask-for-password --prompt="$prompt" */ + 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); + + const char * const *plymouthd_argv; + pid_t pid = get_pid(); + if(pid == 0){ + error_plus(0, 0, "plymouthd pid not found"); + plymouthd_argv = plymouthd_default_argv; + } else { + plymouthd_argv = getargv(pid); + } + + bret = exec_and_wait(NULL, plymouth_path, (const char *[]) + { plymouth_path, "quit", NULL }, + false, false); + if(not bret){ + exit(EXIT_FAILURE); + } + bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv, + false, true); + if(not bret){ + exit(EXIT_FAILURE); + } + exec_and_wait(NULL, plymouth_path, (const char *[]) + { plymouth_path, "show-splash", NULL }, + false, false); + exit(EXIT_FAILURE); +} === added file 'plugins.d/plymouth.xml' --- plugins.d/plymouth.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/plymouth.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,282 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2010 + 2011 + 2012 + Teddy Hogeborn + Björn Påhlsson + + +
+ + + &COMMANDNAME; + 8mandos + + + + &COMMANDNAME; + Mandos plugin to use plymouth to get a + password. + + + + + &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 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 will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. + + + This behavior is meant to exactly mirror the behavior of + askpass, the default password prompter. + + + + + + + + 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 + . + + + + 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 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, + crypttab + 5, + plugin-runner + 8mandos, + proc + 5, + plymouth + 8 + + +
+ + + + + === added file 'plugins.d/splashy.c' --- plugins.d/splashy.c 1970-01-01 00:00:00 +0000 +++ plugins.d/splashy.c 2011-12-31 23:05:34 +0000 @@ -0,0 +1,471 @@ +/* -*- coding: utf-8 -*- */ +/* + * Splashy - Read a password from splashy and output it + * + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 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 this program. 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; +} === added file 'plugins.d/splashy.xml' --- plugins.d/splashy.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/splashy.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,286 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2012 + 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 will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. + + + 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, + crypttab + 5, + plugin-runner + 8mandos, + proc + 5, + splashy + 8, + splashy_update + 8 + + +
+ + + + + === added file 'plugins.d/usplash.c' --- plugins.d/usplash.c 1970-01-01 00:00:00 +0000 +++ plugins.d/usplash.c 2011-12-31 23:05:34 +0000 @@ -0,0 +1,684 @@ +/* -*- coding: utf-8 -*- */ +/* + * Usplash - Read a password from usplash and output it + * + * Copyright © 2008-2012 Teddy Hogeborn + * Copyright © 2008-2012 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 this program. 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; + TEMP_FAILURE_RETRY(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; + TEMP_FAILURE_RETRY(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; + } + TEMP_FAILURE_RETRY(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 = (int)TEMP_FAILURE_RETRY(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 = (int)TEMP_FAILURE_RETRY(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 = (int)TEMP_FAILURE_RETRY(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; +} === added file 'plugins.d/usplash.xml' --- plugins.d/usplash.xml 1970-01-01 00:00:00 +0000 +++ plugins.d/usplash.xml 2011-12-31 23:05:34 +0000 @@ -0,0 +1,301 @@ + + + + +%common; +]> + + + + Mandos Manual + + Mandos + &version; + &TIMESTAMP; + + + Björn + Påhlsson +
+ belorn@recompile.se +
+
+ + Teddy + Hogeborn +
+ teddy@recompile.se +
+
+
+ + 2008 + 2009 + 2011 + 2012 + 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 will + normally have inherited them from + /scripts/local-top/cryptroot in the + initial RAM disk environment, which will + have set them from parsing kernel arguments and + /conf/conf.d/cryptroot (also in the + initial RAM disk environment), which in turn will have been + created when the initial RAM disk image was created by + /usr/share/initramfs-tools/hooks/cryptroot, by + extracting the information of the root file system from + /etc/crypttab. + + + 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, + crypttab + 5, + fifo + 7, + plugin-runner + 8mandos, + proc + 5, + usplash + 8 + + +
+ + + + +