=== 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 really quickly?
+
+ 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 @@
+
+
+
+
+
+
+
+
+
+ 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. Note: a failure to bind to the specified
+ interface is not considered critical, and the server will not
+ exit, but instead continue normally.
+
+
+
+ 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: ::FFFF:192.0.2.3
. (Only if IPv6 usage is
+ disabled (see below) must this be an IPv4
+ address.)
+
+
+
+ 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.
+
+
+
+ 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
+ not run in debug mode.
+
+
+
+ GnuTLS priority string for the TLS handshake.
+ The default is SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224
.
+ See gnutls_priority_init
+ 3 for the syntax.
+ Warning: changing this may make the
+ TLS handshake fail, making server-client
+ communication impossible.
+
+
+
+ Zeroconf service name. The default is
+ Mandos
. This only needs to be
+ changed if for some reason is would be necessary to run more than
+ one server on the same host. This would not
+ normally be useful. If there are name collisions on the same
+ network, the newer server will automatically
+ rename itself to Mandos #2
, and
+ so on; therefore, this option is not needed in that case.
+
+
+
+ This option controls whether the server will provide a D-Bus
+ system bus interface. The default is to provide such an
+ interface.
+
+
+
+ This option controls whether the server will use IPv6 sockets and
+ addresses. The default is to use IPv6. This option should
+ never normally be turned off, even in
+ IPv4-only environments. This is because
+ mandos-client
+ 8mandos 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. Only
+ advanced users should consider changing this option.
+
+
+
+ This option controls whether the server will restore its state
+ from the last time it ran. Default is to restore last state.
+
+
+
+ Directory to save (and restore) state in. Default is
+ /var/lib/mandos
.
+
+
+
+ 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.
+
+
+
+ This option will make the server run in the foreground and not
+ write a PID file. The default is to not run
+ in the foreground, except in mode, which
+ implies this option.
+
+
+
=== 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 @@
+
+
+/etc/mandos/mandos.conf">
+
+
+%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
+
+
+
+
+
+ &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