kuvert/0000755000000000000000000000000013174757747007312 5ustar kuvert/GPL0000644000000000000000000004307012503154002007625 0ustar GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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) 19yy 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 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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 Library General Public License instead of this License. kuvert/LICENSE0000644000000000000000000004315312650626301010300 0ustar GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} 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 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This 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. kuvert/Makefile0000644000000000000000000000270113163154356010733 0ustar # well, a simpler makefile is hardly imaginable... # if destdir is set, debian package making is assumed # so we install relative to that and don't touch /usr/local # # if destdir is unset, we install into /usr/local: bin and the perl sitelib dir DESTDIR= ifneq "$(DESTDIR)" "" # the debian case PERLDIR:=$(DESTDIR)$(shell perl -MConfig -e 'print $$Config{vendorlib}') VERSION:=$(shell sed -n '1s/^.*(\(.*\)).*$$/\1/p' debian/changelog) PREFIX:=$(DESTDIR)/usr else # the non-debian case PERLDIR:=$(shell perl -MConfig -e 'print $$Config{sitelib}') VERSION:=$(shell git tag --sort=v:refname | tail -1 | cut -f 2 -d v) PREFIX:=/usr/local endif CPPFLAGS:=$(shell dpkg-buildflags --get CPPFLAGS) CFLAGS:=$(shell dpkg-buildflags --get CFLAGS) CXXFLAGS:=$(shell dpkg-buildflags --get CXXFLAGS) LDFLAGS:=$(shell dpkg-buildflags --get LDFLAGS) all: kuvert_submit clean: -rm -f kuvert_submit kuvert.tmp install: kuvert_submit kuvert # ensure all needed dirs are present install -d $(PREFIX)/bin $(PREFIX)/share/man/man1 $(PERLDIR)/Net/Server/Mail/ESMTP install kuvert_submit $(PREFIX)/bin # insert the version number sed 's/INSERT_VERSION/$(VERSION)/' kuvert > kuvert.tmp install kuvert.tmp $(PREFIX)/bin/kuvert -rm kuvert.tmp install plainAUTH.pm $(PERLDIR)/Net/Server/Mail/ESMTP/ pod2man --center="User Commands" -r Mail kuvert $(PREFIX)/share/man/man1/kuvert.1 pod2man --center="User Commands" -r Mail kuvert_submit.pod $(PREFIX)/share/man/man1/kuvert_submit.1 kuvert/README0000644000000000000000000000706013163143052010145 0ustar this is kuvert, a wrapper around sendmail or other MTAs that does gpg signing/signing+encrypting transparently, based on the content of your public keyring(s) and your preferences. how it works: ------------- you need to configure your MUA to submit mails to kuvert instead of directly. you configure kuvert either to present an SMTP server to your MUA, or you make your MUA to use kuvert_submit instead of executing /usr/sbin/sendmail. kuvert_submit will spool the mail in kuvert's queue iff there is a suitable configuration file. kuvert is the tool that takes care of mangling the email. it reads the queue periodically and handles emails in the queue: signing or encrypting the mail, then handing it over to /usr/lib/sendmail or an external SMTP server for transport. (why a queue? because i thought it might be useful to make sure that none of your emails leaves your system without kuvert handing it. you might be very paranoid, and kill kuvert whenever you leave your box (and remove the keyrings as well).) installation: ------------- on debian systems you simply install the kuvert package, construct a suitable .kuvert configuration file and off you go. an example config file is provided at /usr/share/doc/kuvert/examples/dot-kuvert. on other systems you need to do the following: you need perl perl 5.10+, gpg and some of non-core perl modules: MIME::Parser, Mail::Address, Net::SMTP 1.28 (in core but not new enough with some distributions), IO::Socket::SSL 2.007, Net::Server::Mail::ESMTP, File::Slurp, Proc::ProcessTable and Encode::Locale. optional: get linux-kernel keyutils package, the gpg-agent or some other passphrase cache of your choice. run make, make install DESTDIR=/ as root -> kuvert, kuvert_submit, the manpages and one helper module will be installed in /usr/bin, /usr/share/man/man1 and /usr/share/perl5/Net/Server/Mail/ESMTP/, respectively. configuration: -------------- read the manpages for kuvert(1) and kuvert_submit(1) and consult the example config file "dot-kuvert". you will need to create your own config file as ~/.kuvert. sorry, no autoconfig here: this step is too crucial for a mere robot to perform. then start kuvert and inject a testmail, look at the logs to check if everything works correctly. (historical note: kuvert came into existence in 1996 as pgpmail and was used only privately until 99, when it was extended and renamed to guard. some of my friends started using this software, and in 2001 it was finally re-christened kuvert, extended even further and debianized. in 2008 it received a major overhaul to also provide inbound smtp as submission mechanism, outbound smtp transport and better controllability via email addresses. until 2008 kuvert supported pgp2.x.) please report bugs to me, Alexander Zangerl, . The original source can always be found at: http://www.snafu.priv.at/kuvert/ Copyright (C) 1999-2017 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA kuvert/THANKS0000644000000000000000000000032512503154002010167 0ustar My thanks go to Robert Bihlmeyer Norbert Preining Robert Waldner for valuable hints and suggestions regarding this piece of software.kuvert/debian/0000755000000000000000000000000013174757747010534 5ustar kuvert/debian/NEWS0000644000000000000000000000273413174757572011235 0ustar kuvert (2.0.0) unstable; urgency=low kuvert has been completely revamped, with a number of new features and minus some legacy stuff. New: full inbound and outbound support for SMTP support for gpg-agent simplified action settings overridable keys and actions in the comments of From: and To: simpler submission wrapper program Gone: support for pgp2 (but RSA keys continue to work fine, with gpg) kuvert no longer stores passphrases itself, ever. Because of these changes the new config file format is different. The new kuvert will not run with an old-style config file, but does a rough auto-conversion on startup. It'll dump that skeleton in /tmp and tell you about it. You will need to go over that and the kuvert manpage and adjust the config to your liking. -- Alexander Zangerl Sun, 29 Jun 2008 21:32:14 +1000 kuvert (1.1.10) unstable; urgency=low The configuration options AGENTPATH and CLIENTPATH have been deprecated and support for a private q-agent process was dropped; while kuvert still suggests quintuple-agent, it is no longer favouring it and will work with any (reasonable) external passphrase store. kuvert will not start until you update your personal .kuvert configuration file and remove AGENTPATH/CLIENTPATH and use the new GETSECRET and DELSECRET directives if you want to use an external passphrase store. -- Alexander Zangerl Fri, 4 Nov 2005 16:43:14 +1000 kuvert/debian/changelog0000644000000000000000000002721213174757747012412 0ustar kuvert (2.2.2) unstable; urgency=medium * kill/reload code now runs earlier, which makes -k/-r work without (explicit or default) config file * lifted standards version, fixed priority optional -- Alexander Zangerl Sat, 28 Oct 2017 11:39:51 +1000 kuvert (2.2.1) unstable; urgency=medium * new runtime option -c for different config file * switched to net::smtp (from perlcore), away from net::smtps. * updated dependencies, we need stretch's perl or newer because jessie's came with a much too ancient net::smtp. * lifted debhelper compat version -- Alexander Zangerl Thu, 28 Sep 2017 20:02:27 +1000 kuvert (2.2.0) unstable; urgency=low * new config option to make kuvert use your preferred gpg binary (e.g. /usr/bin/gpg1) * updated copyright (updated year, FSF address) -- Alexander Zangerl Tue, 19 Sep 2017 21:42:00 +1000 kuvert (2.1.1) unstable; urgency=high * fix encoding issues for log output (closes: #859903) * updated dependencies, now need encode::locale -- Alexander Zangerl Mon, 10 Apr 2017 21:11:52 +1000 kuvert (2.1.0) unstable; urgency=low * new action 'mustencrypt' * various bug fixes and robustness improvements * updated dependencies -- Alexander Zangerl Sat, 01 Apr 2017 21:01:58 +1000 kuvert (2.0.14) unstable; urgency=low * use full 16 digit key id when interacting with external passhrase cache * lifted standards version -- Alexander Zangerl Fri, 24 Mar 2017 19:27:41 +1000 kuvert (2.0.13) unstable; urgency=low * kuvert now backs off for up to 8 seconds when re-requesting an invalid password from an external program * lifted standards version -- Alexander Zangerl Sat, 23 Jan 2016 17:35:52 +1000 kuvert (2.0.12) unstable; urgency=low * fixed hardcoded micalg mime header component (closes: #754820) * added small cosmetic header improvements (closes: #754821) * reworded manual entry for can-detach for better clarity (closes: #754823) * updated build script for better policy compliance -- Alexander Zangerl Tue, 15 Jul 2014 20:22:34 +1000 kuvert (2.0.11) unstable; urgency=low * fixed clash between my lazy logfile handling and perl 5.18's increased fastidiousness (closes: #736978) -- Alexander Zangerl Wed, 29 Jan 2014 21:36:37 +1000 kuvert (2.0.10) unstable; urgency=medium * fixed one-character typo in control that marked dependency as optional (closes: #736774) -- Alexander Zangerl Mon, 27 Jan 2014 11:44:35 +1000 kuvert (2.0.9) unstable; urgency=low * now supports STARTTLS for outbound SMTP submission * lifted standards version, adjusted dependencies accordingly -- Alexander Zangerl Mon, 25 Nov 2013 20:44:19 +1000 kuvert (2.0.8) unstable; urgency=low * modified rules to support hardening build flags -- Alexander Zangerl Tue, 24 Sep 2013 13:32:35 +1000 kuvert (2.0.7) unstable; urgency=low * added timeout for gpg invocations -- Alexander Zangerl Tue, 04 Sep 2012 20:28:39 +1000 kuvert (2.0.6) unstable; urgency=low * updated standards version * cleaned up dependencies (closes: #665044) -- Alexander Zangerl Thu, 22 Mar 2012 22:09:12 +1000 kuvert (2.0.5) unstable; urgency=low * added option to control whether the explanatory mime preamble is generated or not. -- Alexander Zangerl Tue, 21 Feb 2012 11:44:03 +1000 kuvert (2.0.4) unstable; urgency=low * lifted standards version * added support for optional smtp authentication (for outbound mail) this requires authen::sasl which was added to the dependencies. -- Alexander Zangerl Thu, 16 Sep 2010 15:14:10 +1000 kuvert (2.0.3) unstable; urgency=low * fixed silly case-sensitivity bug: keys were downcased, but not email addresses, which may have caused kuvert to fall back to signing instead of encrypting. * lifted standards version -- Alexander Zangerl Tue, 20 Oct 2009 16:45:33 +1000 kuvert (2.0.2) unstable; urgency=low * modified kuvert_submit to honour the -bv option by running sendmail directly instead of enqueueing an email. -- Alexander Zangerl Mon, 16 Mar 2009 17:01:24 +1000 kuvert (2.0.1) unstable; urgency=low * fixed generation of default queue/tempdirs -- Alexander Zangerl Sun, 31 Aug 2008 16:39:52 +1000 kuvert (2.0.0) unstable; urgency=low * the next generation of kuvert: better SMTP support, no more pgp2, more precise control over keys, gpg agent support etc. * updated dependencies * updated standards version -- Alexander Zangerl Sun, 29 Jun 2008 21:25:51 +1000 kuvert (1.1.14) unstable; urgency=low * don't strip kuvert_mta_wrapper if DEB_BUILD_OPTIONS asks for that (closes: #437288) -- Alexander Zangerl Sun, 12 Aug 2007 13:24:20 +1000 kuvert (1.1.13) unstable; urgency=high * fixed stupid mistake wrt. variable init in kuvert_mta_wrapper which caused the wrapper to fall back to sendmail most of the time. -- Alexander Zangerl Sat, 23 Jun 2007 13:16:42 +1000 kuvert (1.1.12) unstable; urgency=low * the signature MIME-part is now tagged a bit more extensively (as per hint from Andreas Labres/Andreas Kreuzinger) -- Alexander Zangerl Sat, 23 Jun 2007 12:39:05 +1000 kuvert (1.1.11) unstable; urgency=low * lifted standards version -- Alexander Zangerl Tue, 10 Apr 2007 18:24:40 +1000 kuvert (1.1.10) unstable; urgency=low * added libproc-pid-file-perl dependency * deprecated AGENTPATH/CLIENTPATH configuration setting and cleaned up secret on demand stuff * lifted standards version -- Alexander Zangerl Fri, 4 Nov 2005 16:20:20 +1000 kuvert (1.1.9) unstable; urgency=high * the "don't trust input. do be conservative and paranoid." release. fixed a potential security problem re email addresses that are borderline rfc2822-compliant by calling the mta without shell interference. * updated homepage url -- Alexander Zangerl Sat, 26 Feb 2005 08:11:26 +1000 kuvert (1.1.8) unstable; urgency=low * lifted standards version * added homepage to description -- Alexander Zangerl Thu, 11 Dec 2003 12:52:40 +1000 kuvert (1.1.7) unstable; urgency=low * added example ~/.kuvert * kuvert now produces a blank ~/.kuvert if none is found on startup -- Alexander Zangerl Sun, 3 Aug 2003 11:53:19 +1000 kuvert (1.1.6) unstable; urgency=high * fixed bad problem with mixture of raw and signed/encr'd mails: the raw mail would lose most of its headers due to an overzealous "code improvement". problem was only present with multi-recipient mails. -- Alexander Zangerl Wed, 11 Jun 2003 19:45:51 +1000 kuvert (1.1.5) unstable; urgency=low * fixed variable length macro (problem only with gcc 3.3) (closes: #194944) * bumped standards version -- Alexander Zangerl Wed, 28 May 2003 20:12:44 +1000 kuvert (1.1.4) unstable; urgency=low * bumped standards version, now only suggests old-style pgp * fixed error behaviour (sent multiple error messages with -b) -- Alexander Zangerl Fri, 25 Apr 2003 17:34:18 +1000 kuvert (1.1.3) unstable; urgency=low * standards version lifted, debhelper cleanup -- Alexander Zangerl Sun, 9 Mar 2003 11:23:30 +1000 kuvert (1.1.2) unstable; urgency=low * added IDENTIFY directive: adds an X-Mailer header (closes: Bug#181868) -- Alexander Zangerl Sat, 22 Feb 2003 14:58:52 +1000 kuvert (1.1.1) unstable; urgency=low * fixed problem with duplicate entries in pidfile on unclean shutdown. * pidfile honors $TMPDIR now. * added option "-b": sends barfmail when fatal error encountered (closes: Bug179345) -- Alexander Zangerl Sun, 16 Feb 2003 23:44:15 +1000 kuvert (1.1.0) unstable; urgency=low * complete overhaul of the code, streamlined, better error handling * added new dependency on libterm-readkey-perl * fixed handling of idea-extension (closes: Bug#162279) * added Bcc handling (closes: Bug#162024) * removed /usr/doc transition stuff * fallback option now falls back down to std key if no ng private key av. -- Alexander Zangerl Tue, 21 Jan 2003 22:33:16 +1000 kuvert (1.0.13) unstable; urgency=low * default tempdir now honors $TMPDIR (closes: Bug#161326) * does not start anymore without config file (closes: Bug#161327) * some new sanity checks and clarifications in the manpages and README * added option MTA * SECRETONDEMAND option extended and clarified -- Alexander Zangerl Thu, 19 Sep 2002 18:04:18 +0200 kuvert (1.0.12) unstable; urgency=high * finally fixed typo trashing the handling of the 'fallback' action -- Alexander Zangerl Sun, 28 Apr 2002 01:50:15 +1000 kuvert (1.0.11) unstable; urgency=high * fixed typo that trashed handling of -force -- Alexander Zangerl Fri, 26 Apr 2002 12:11:54 +1000 kuvert (1.0.10) unstable; urgency=medium * manpage improvements * -force handling fixed (did not work as DEFAULT action) * -force semantics finetuned: action=none is not overrideable by -force or the override header -- Alexander Zangerl Fri, 26 Apr 2002 00:43:03 +1000 kuvert (1.0.9) unstable; urgency=low * moved into main/mail as per recent announcement -- Alexander Zangerl Sun, 24 Mar 2002 17:20:40 +1000 kuvert (1.0.8) unstable; urgency=low * changed mail addressing in send_bounce: no angle brackets. note: this does not fulfil rfc2822, 3.4.1, but as this is local mail it does not really concern me. (closes: Bug#136619) -- Alexander Zangerl Tue, 5 Mar 2002 23:19:00 +1000 kuvert (1.0.7) unstable; urgency=low * fixed dependency mailtools -> libmailtools-perl * fixed version-number generation -- Alexander Zangerl Sat, 16 Feb 2002 21:06:52 +1000 kuvert (1.0.6) unstable; urgency=low * gpg-idea has been removed due to patent problems (#126506), removing suggestion -- Alexander Zangerl Thu, 31 Jan 2002 20:46:13 +1000 kuvert (1.0.5) unstable; urgency=low * added config of queue check interval * added -v version command, added version logging at start -- Alexander Zangerl Thu, 31 Jan 2002 00:24:56 +1000 kuvert (1.0.4) unstable; urgency=low * fixed bug: handling of no-std-pgp works now -- Alexander Zangerl Sun, 27 Jan 2002 22:32:42 +1000 kuvert (1.0.3) unstable; urgency=low * added nofork option (-n) * fixed behaviour in debugging mode * improved manpage * improved handling of expired, revoked, invalid keys * added -force actions -- Alexander Zangerl Wed, 2 Jan 2002 16:43:30 +1000 kuvert (1.0.2) unstable; urgency=low * fixed tempdir and queuedir generation for default dirs * fixed handling of setups with pgp XOR gpg * changed sendmail error mode to -oem (mailback, but with return code) * some manpage clarifications * fixed reporting for garbled mails * added logging to file -- Alexander Zangerl Sun, 11 Nov 2001 19:24:05 +1000 kuvert (1.0.1) unstable; urgency=low * fixed problems with missing .kuvert config file. * added more options for sendmail to be recognized (-oi,-od*,-oe*) -- Alexander Zangerl Tue, 6 Nov 2001 23:23:39 +1000 kuvert (1.0.0) unstable; urgency=low * Initial Release. * Renamed from 'guard' to 'kuvert', debianized. -- Alexander Zangerl Sun, 21 Oct 2001 23:21:16 +1000 kuvert/debian/compat0000644000000000000000000000000213174757572011726 0ustar 9 kuvert/debian/control0000644000000000000000000000171613174757747012144 0ustar Source: kuvert Section: mail Priority: optional Maintainer: Alexander Zangerl Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~) Standards-Version: 4.1.3.0 Package: kuvert Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, gnupg | gnupg1, sendmail | mail-transport-agent, libnet-smtps-perl, perl (>= 5.24), libmailtools-perl, libmime-tools-perl, libfile-slurp-perl, libnet-server-mail-perl, libauthen-sasl-perl, libproc-processtable-perl, libencode-locale-perl, libio-socket-ssl-perl Suggests: keyutils Homepage: http://www.snafu.priv.at/mystuff/kuvert/ Description: wrapper that encrypts or signs outgoing mail kuvert automatically signs and/or encrypts outgoing mail using the PGP/MIME standard (RFC3156), based on the availability of the recipient's key in your keyring. Other than similar wrappers, kuvert does not store key passphrases itself, ever. kuvert works as a wrapper around your MTA but can be fed mails via SMTP, too. kuvert/debian/copyright0000644000000000000000000000166413174757572012472 0ustar This is kuvert, written and maintained by Alexander Zangerl on Sun, 21 Oct 2001 23:21:16 +1000. The original source can always be found at: http://www.snafu.priv.at/kuvert/ Copyright (C) 1999-2017 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. kuvert/debian/kuvert.docs0000644000000000000000000000000713174757572012717 0ustar README kuvert/debian/kuvert.examples0000644000000000000000000000001213174757572013601 0ustar dot-kuvertkuvert/debian/rules0000755000000000000000000000204213174757572011606 0ustar #!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 export DESTDIR=$(CURDIR)/debian/kuvert DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/buildflags.mk build: build-arch build-indep build-arch: build-stamp build-indep: build-stamp build-stamp: dh_testdir $(MAKE) touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp $(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_prep dh_installdirs $(MAKE) install DESTDIR=$(DESTDIR) # Build architecture-independent files here. binary-indep: build install # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installdocs -n dh_installexamples dh_installchangelogs dh_strip dh_compress dh_fixperms dh_installdeb dh_perl dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install build-arch build-indep kuvert/dot-kuvert0000644000000000000000000000356013160200760011312 0ustar # ~/.kuvert: example configuration file for kuvert v2 # options are given without leading whitespace # which key to sign with by default, long keyid recommended defaultkey 0x1234abcd8765fedba # logging to syslog, which facility? defaults to no syslog syslog mail # no separate logfile logfile "" # who gets error reports mail-on-error you@some.domain # where to spool mails and temporary files queuedir /home/az/kuvert_queue tempdir /tmp/kuvert_temp # how often to check the queue, in seconds interval 60 # add an x-mailer header? identify f # add the explanatory mime preamble? preamble f # how to submit outbound mail: # # 1. via smtp # settings: msserver, msport, ssl, # ssl-cert, ssl-key, ssl-ca; # authenticating as msuser, mspass # # msserver some.server.com # msport 587 # ssl starttls # ssl-key mycerts/my.key.pem # ssl-cert mycerts/my.cert.pem # msuser smtp-username # mspass smtp-password # mspass-from-query-secret f # # 2. by using the msp program # msp /usr/sbin/sendmail -om -oi -oem can-detach t # maport 2587 # ma-user yourname # ma-pass somethingSECRET defaultaction fallback-all gpg /usr/bin/gpg alwaystrust t use-agent t query-secret /usr/bin/q-agent get %s flush-secret /usr/bin/q-agent delete %s # action specifications for recipients # are given with some leading whitespace # multiple keys for somebody and you want a specific one? somebody@with.many.keys fallback,0x1234abcd # those don't want gpg-signed stuff @somewhere.com none # signed but not encrypted (he|they|others)@there.com signonly # majordomo and similar mailinglist systems get plain mail (majordomo|-request)@ none # if you want no mail involving this address to go out unencrypted, # set mustencrypt. any addresses with mustencrypt but no key cause the # hole mail to be rejected. mustencrypt overrides fallback and fallback-all. somebody@with.a.key.and.healthy.paranoia mustencrypt kuvert/kuvert0000755000000000000000000020447713174757574010574 0ustar #!/usr/bin/perl # # this file is part of kuvert, a mailer wrapper that # does gpg signing/signing+encrypting transparently, based # on the content of your public keyring(s) and your preferences. # # copyright (c) 1999-2017 Alexander Zangerl # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # #-- use strict; use Sys::Syslog qw(setlogsock openlog syslog closelog); use Fcntl qw(:flock); use Getopt::Std; use MIME::Parser; # for parsing the mime-stream use Mail::Address; # for parsing to and cc-headers use Net::SMTP 1.28; # for sending email, smtp, smtps and starttls use IO::Socket::SSL; # ssl is not on always but less hassle to depend on the module here use Sys::Hostname; # ditto use Net::Server::Mail::ESMTP; # for receiving via smtp use IO::Socket::INET; # ditto use FileHandle; use File::Slurp; use File::Find; use File::Temp qw(:mktemp); use Time::HiRes; use Proc::ProcessTable; use Encode; use Encode::Locale; use POSIX qw(); # some global stuff # the version number is inserted by make install my $version="INSERT_VERSION"; my $progname="kuvert"; $0=$progname; my $listenername="$progname-smtp"; # who are we gonna pretend to be today? my($username,$home)=(getpwuid($<))[0,7]; # where is the configuration file my $rcfile="$home/.kuvert"; my $timeout=600; # seconds to wait for gpg # configuration directives my (%config,$debug,%email2key); my %options; if (!getopts("dorkc:",\%options) || @ARGV) { die "usage: $progname [-d] [-o] [-r|-k] [-c configfile] -k: kill running $progname daemon -d: debug mode -r: reload keyrings and configfile -o: one-shot mode, run queue once and exit -c : use alternate config file instead of ~/.kuvert This is: $progname $version.\n"; } # now handle the kill/reload stuff my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp"); my $pidname="$progname.$<"; my $pidf="$piddir/$pidname.pid"; $debug=$options{"d"}; # who's there? my %instances; if (-f $pidf && (my $knownpids = read_file($pidf))) { my $proctable = Proc::ProcessTable->new->table; $knownpids =~ s/\D+/ /g; for my $maybe (split(/\s/, $knownpids)) { next if ($maybe == $$); # not likely, but... # really mine? my ($ptentry) = grep($_->pid eq $maybe, @$proctable); if ($ptentry && $ptentry->cmndline =~ /^$progname/ && $ptentry->uid == $<) { $instances{$maybe} = $ptentry->cmndline; } } } if ($options{"k"} || $options{"r"}) { my $sig=($options{"r"}?'USR1':'TERM'); # -r reload, -k kill my $ssig='TERM'; # the smtp listener must always die for my $mine (keys %instances) { my $signal = ($instances{$mine} =~ /^$listenername/? $ssig:$sig); dlogit("sending sig $signal to $mine ($instances{$mine})"); logit("can't send signal to process $mine: $!\n") if (!kill($signal,$mine)); } unlink($pidf) if ($options{k}); # remove the pidfile on kills exit 0; } # anybody in the way? die("other instance(s) with pids ".join(", ",keys %instances) ." are running!\n") if (keys %instances); # read in the config, setup dirs, logging, defaultkey etc. $rcfile = $options{c} if ($options{c}); die("no configuration file exists. See man $progname(1) for details.\n") if (!-e $rcfile); %config=&read_config; dlogit("reading config file"); # log startup after config is read and logging prefs are known logit("$progname version $version starting"); # install the handlers for conf reread $SIG{'USR1'}=\&handle_reload; # and the termination-handler map { $SIG{$_}=\&handle_term; } qw(HUP INT QUIT TERM); if (!$options{o} && $config{"can-detach"}) { my $pid=fork; if (!defined $pid) { &bailout("fork failed: $!"); } elsif ($pid) { exit 0; # parent is done } chdir("/"); POSIX::setsid(); } # fire up smtp server, iff not oneshot if (!$options{o} && $config{"ma-user"} && $config{"ma-pass"} && $config{"maport"}) { # fork off the smtp-to-queue daemon my $pid = &start_mailserver; $instances{$pid} = 1; } # now we're daemon or sole process write_file($pidf, join("\n", keys %instances, $$)); cleanup($config{tempdir},0); # bsts %email2key=&read_keyring; # let's (re)use just one parser object my $parser = MIME::Parser->new() || bailout("can't create mime parser object: $!"); # dump mime objects to tempdir - reset on reload signal $parser->output_dir($config{tempdir}); # retain rfc1522-encoded headers, please $parser->decode_headers(0); # make the parser ignore all filename info and just invent filenames. $parser->filer->ignore_filename(1); # the main loop, left only via signal handler handle_term while (1) { &bailout("cant open $config{queuedir}: $!") if (!opendir(D,"$config{queuedir}")); my $file; foreach $file (sort grep(/^\d+$/,readdir(D))) { if (!open(FH,"$config{queuedir}/$file")) { logit("huh? $file suddenly disappeared? $!"); next; } # lock it if possible if (!flock(FH,LOCK_NB|LOCK_EX)) { close(FH); logit("$file is locked, skipping."); next; } #ok, open & locked, let's proceed logit("processing $file for $username"); my @res=process_file(*FH,"$config{queuedir}/$file"); if (@res) { rename("$config{queuedir}/$file","$config{queuedir}/.$file") || &bailout("cant rename $config{queuedir}/$file: $!"); alert($res[0], "Your mail \"$config{queuedir}/$file\" could not be processed and $progname has given up on it. Please review the following error details to determine what went wrong:\n\n", @res, "\n$progname has renamed the problematic mail to \"$config{queuedir}/.$file\"; if you want $progname to retry, rename it to an all-numeric filename. Otherwise you should delete the file.\n"); } else { logit("done handling file $file"); unlink("$config{queuedir}/$file") || &bailout("cant unlink $config{queuedir}/$file: $!"); } # and clean up the cruft left behind, please! cleanup("$config{tempdir}",0); # close and thus unlock the file bailout("problem closing $config{queuedir}/$file: $!") if (!close(FH)); } closedir(D); &handle_term("oneshot mode") if ($options{o}); sleep($config{interval}); } exit(1); # shouldn't reach here # sign an entity and send the resulting email to the listed recipients # args: entity, location of dump of entity, outermost headers, envelope from, # signkey and recipients # returns nothing if fine, @error msgs otherwise sub sign_send { my ($ent,$dumpfile,$header,$from,$signkey,@recips)=@_; my $output=mktemp($config{tempdir}."/cryptoout.XXXX"); # generate a new top-entity to be mailed my $newent=new MIME::Entity; # make a private copy of the main header and set this one $newent->head($header->dup); # make it a multipart/signed # and set the needed content-type-fields on this top entity $newent->head->mime_attr("MIME-Version"=>"1.0"); $newent->head->mime_attr("Content-Type"=>"multipart/signed"); $newent->head->mime_attr("Content-Type.Boundary"=> &MIME::Entity::make_boundary); $newent->head->mime_attr("Content-Type.Protocol"=> "application/pgp-signature"); # set/suppress the preamble $newent->preamble($config{"preamble"}? ["This is a multi-part message in MIME format.\n", "It has been signed conforming to RFC3156.\n", "You need GPG to check the signature.\n"]:[]); # add the passed entity as part $newent->add_part($ent); # generate the signature, repeat until proper passphrase given # or until gpg gives up with a different error indication my @res; my $backoff = 0; while (1) { @res=&sign_encrypt($signkey,$dumpfile,$output,()); last if ($res[0]!=1); # no error or fatal error dlogit("gpg reported bad passphrase, retrying."); if ($config{"query-secret"}) { # be polite to the query tool and back off up to 8 sec sleep($backoff); $backoff += 2 if ($backoff < 8); } if (!$config{"use-agent"} && $config{"flush-secret"} && $signkey) { dlogit("invalidating passphrase for $signkey"); my $cmd=sprintf($config{"flush-secret"},$signkey); system($cmd); # ignore the flushing result; best effort only } } my $hashname = $res[1]; return @res[1..$#res] if ($res[0]); # fatal error: give up $newent->head->mime_attr("Content-Type.Micalg"=>$hashname); # attach the signature $newent->attach(Type => "application/pgp-signature", Path => $output, Filename => "signature.asc", Disposition => "inline", Description=> "Digital Signature", Encoding => "7bit"); # and send the resulting thing, not cleaning up return &send_entity($newent,$from,@recips); } # encrypt and sign an entity, send the resulting email to the listed recipients # args: entity, location of dump of entity, outermost headers, # envelope from address, recipient keys arrayref, recipient addresses # returns nothing if fine, @error msgs otherwise sub crypt_send { my ($ent,$dumpfile,$header,$from,$signkey,$rec_keys,@recips)=@_; my $output=mktemp($config{tempdir}."/cryptoout.XXXX"); # generate a new top-entity to be mailed my $newent=new MIME::Entity; # make a private copy of the main header and set this one $newent->head($header->dup); # make it a multipart/encrypted # and set the needed content-type-fields on this top entity $newent->head->mime_attr("MIME-Version"=>"1.0"); $newent->head->mime_attr("Content-Type"=>"multipart/encrypted"); $newent->head->mime_attr("Content-Type.Boundary"=> &MIME::Entity::make_boundary); $newent->head->mime_attr("Content-Type.Protocol"=> "application/pgp-encrypted"); # set/suppress the new preamble $newent->preamble($config{"preamble"}? ["This is a multi-part message in MIME format.\n", "It has been encrypted conforming to RFC3156.\n", "You need GPG to view the content.\n"]:[]); # attach the needed dummy-part $newent->attach(Type=>"application/pgp-encrypted", Data=>"Version: 1\n", Encoding=>"7bit"); # generate the encrypted data, repeat until proper passphrase given my @res; my $backoff = 0; while (1) { @res=&sign_encrypt($signkey,$dumpfile,$output,@{$rec_keys}); last if ($res[0]!=1); # no error or fatal error dlogit("gpg reported bad passphrase, retrying."); if ($config{"query-secret"}) { # be polite to the query tool and back off up to 8 sec sleep($backoff); $backoff += 2 if ($backoff < 8); } if (!$config{"use-agent"} && $config{"flush-secret"} && $signkey) { dlogit("invalidating passphrase for $signkey"); my $cmd=sprintf($config{"flush-secret"},$signkey); system($cmd); # ignore the flushing result; best effort only } } return @res[1..$#res] if ($res[0]); # fatal error: give up # attach the encrypted data $newent->attach(Type => "application/octet-stream", Path => $output, Filename => undef, Disposition => "inline", Encoding=>"7bit"); # and send the resulting thing return &send_entity($newent,$from,@recips); } # processes a file in the queue, # leaves the file in the queue # returns nothing if ok or @error msgs - first one short enough for subject sub process_file { my ($fh,$file)=@_; my $in_ent; eval { $in_ent=$parser->parse(\$fh); }; return ("Parsing of $file failed!","parser errors: $@",$parser->last_error) if ($@); # extract and clean envelope x-kuvert-from and -to my @erecips=extract_addresses($in_ent->head->get("x-kuvert-to")); my @efrom=extract_addresses($in_ent->head->get("x-kuvert-from")); $in_ent->head->delete("x-kuvert-to"); $in_ent->head->delete("x-kuvert-from"); # extract and parse the from # each is addr, phrase, comment, formatted, directive my @froms=extract_addresses($in_ent->head->get("from")); return "Could not parse From: header!" if (!@froms); # envelope from is: x-kuvert-from if present or from my $fromaddr=@efrom?$efrom[0]->[0]:$froms[0]->[3]; my $signkey=$config{defaultkey}; # do we have a key override if ($froms[0]->[4]=~/key=((0x)?[0-9a-f]+)/i) { $signkey=$1; dlogit("local signkey override: $signkey"); $in_ent->head->replace("from",$froms[0]->[3]); } # add version header $in_ent->head->add('X-Mailer',"$progname $version") if ($config{identify}); # extract and delete blanket instruction header my $override; if (lc($in_ent->head->get("x-kuvert"))=~ /^\s*(none|fallback|fallback-all|signonly|mustencrypt)\s*$/) { $override=$1; } $in_ent->head->delete("x-kuvert"); # resend-request-header present and no more specific recipients given? # then send this as-it-is if (!@erecips && (my $rsto=$in_ent->head->get("resent-to"))) { logit("resending requested, doing so."); my @prstos=Mail::Address->parse($rsto); return "Could not parse Resent-To: header!" if (!@prstos); my @rstos=map { $_->address } (@prstos); return send_entity($in_ent,$fromaddr,@rstos); } # extract and analyze normal and bcc recipients my @tos=extract_addresses($in_ent->head->get("to")); my @ccs=extract_addresses($in_ent->head->get("cc")); my @recips=(@tos,@ccs); my @recip_bcc=extract_addresses($in_ent->head->get("bcc")); # and don't leak Bcc... $in_ent->head->delete("bcc"); # replace to and cc with cleaned headers: we don't want to # leak directives my $newto=join(", ",map { $_->[3] } (@tos)); my $newcc=join(", ",map { $_->[3] } (@ccs)); $in_ent->head->replace("To",$newto); $in_ent->head->replace("Cc",$newcc) if ($newcc); # cry out loud if there is a problem with the submitted mail # and no recipients were distinguishable... # happens sometimes, with mbox-style 'From bla' lines in the headers... return("No recipients found!","The mail headers seem to be garbled, and no To, Cc or Bcc headers could be identified.") if (!@erecips && !@recips && !@recip_bcc); # remember the addresses' nature my (%is_bcc); map { $is_bcc{$_->[0]}=1; } (@recip_bcc); # now deal with envelope-vs-mailheader recipients: # whatever the envelope says, wins. if (@erecips) { # no need to distinguish these otherwise my (%is_normal,%is_envelope); map { $is_normal{$_->[0]}=1; } (@recips); map { $is_envelope{$_->[0]}=1; } (@erecips); for my $e (@erecips) { # in the envelope but not the headers -> fake bcc if (!$is_normal{$e->[0]} && !$is_bcc{$e->[0]}) { push @recip_bcc,$e; $is_bcc{$e->[0]}=1; } } # in the headers but not the envelope -> ignore it my @reallyr; for my $n (@recips) { push @reallyr,$n if ($is_envelope{$n->[0]}); } @recips=@reallyr; } # figure out what to do for specific recipients my %actions=findaction($override,\@recips,\@recip_bcc); # handle reject indications if (my @unsatisfied = grep($actions{$_} eq "reject", keys %actions)) { return ("Config prohibits unencrypted email!", "The configuration for ". join(", ", @unsatisfied). " requires that email must be encrypted, but no keys were known or given for these addresses."); } # send out unsigned mails first my @rawrecips=grep($actions{$_} eq "none",keys %actions); if (@rawrecips) { logit("sending mail (unchanged) to ".join(", ",@rawrecips)); my @res=send_entity($in_ent,$fromaddr,@rawrecips); return @res if (@res); } my ($orig_header,$cryptoin); # prepare various stuff we need only when signing+encrypting or signing if(grep($_ ne "none", values(%actions))) { # copy (mail)header, split header info # in mime-related (remains with the entity) and non-mime # (is saved in the new, outermost header-object) $orig_header=$in_ent->head->dup; # content-* stays with the entity and the rest moves to orig_header foreach my $headername ($in_ent->head->tags) { if ($headername !~ /^content-/i) { # remove the stuff from the entity $in_ent->head->delete($headername); } else { # remove this stuff from the orig_header $orig_header->delete($headername); } } # any text/plain parts of the entity have to be fixed with the # correct content-transfer-encoding (qp), since any transfer 8->7bit # on the way otherwise will break the signature. # this is not necessary if encrypting, but done anyways since # it doesnt hurt and we want to be on the safe side. my $res=qp_fix_parts($in_ent); return $res if ($res); # now we've got a in entity which is ready to be encrypted/signed # and the mail-headers are saved in $orig_header # next we dump this entity into a file for crypto ops my $fh; ($fh,$cryptoin)=mkstemp($config{tempdir}."/cryptoin.XXXX"); return("Can't create file $cryptoin: $!") if (!$fh); $in_ent->print($fh); close($fh); } # send the mail signed to the appropriate recips my @signto=grep($actions{$_} eq "signonly",keys %actions); if (@signto) { logit("sending mail (signed with $signkey) to ".join(", ",@signto)); my @res=&sign_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, @signto); return @res if (@res); } # send mail encrypted+signed to appropriate recips. # note: bcc's must be handled separately! my @encto=grep($actions{$_}!~/^(none|signonly)$/ && !$is_bcc{$_}, keys %actions); if (@encto) { logit("sending mail (encrypted+signed with $signkey) to " .join(", ",@encto)); my @enckeys = map { $actions{$_} } (@encto); my @res=&crypt_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, \@enckeys,@encto); return @res if (@res); } for my $bcc (grep($actions{$_}!~/^(none|signonly)$/ && $is_bcc{$_}, keys %actions)) { logit("sending mail (bcc,encrypted+signed with $signkey) to $bcc"); my @res=&crypt_send($in_ent,$cryptoin,$orig_header,$fromaddr,$signkey, [$actions{$bcc}],$bcc); return @res if (@res); } return; } # find the correct action for the given email addresses # input: override header, normal and bcc-addresses # returns hash with address as key, value is "none", "signonly", "reject" # or recipient key id sub findaction { my ($override,$normalref,$bccref)=@_; my(%actions,%specialkeys,$groupfallback); # address lookup in the list of configured choices foreach my $a (@{$normalref},@{$bccref}) { my $addr=$a->[0]; foreach (@{$config{overrides}}) { if ($addr =~ $_->{re}) { $actions{$addr}=$_->{action}; # remember config-file key overrides $specialkeys{$addr}=$_->{key} if ($_->{key}); last; } } # nothing configured? then default action $actions{$addr}||=($config{defaultaction}||"none"); dlogit("action $actions{$addr} for $addr"); # blanket override? then override the config but not where # "none" is specified if ($override && $actions{$addr} ne "none") { dlogit("override header: $override for $addr"); $actions{$addr}=$override; } # next: check individual action=x directives if ($a->[4] =~/action=(none|fallback-all|fallback|signonly)/) { my $thisaction=$1; $actions{$addr}=$thisaction; dlogit("local override: action $thisaction for $addr"); } if ($a->[4] =~/key=([0-9a-fA-FxX]+)/) { $specialkeys{$addr}=$1; dlogit("local key override: $specialkeys{$addr} for $addr"); } # now test for key existence and downgrade action to signonly # where necessary - or abort is mustencrypt is selected if ($actions{$addr}=~/^(fallback|mustencrypt)/) { # group fallback is relevant for normal recipients only $groupfallback ||= ($actions{$addr} eq "fallback-all") if (!grep($_->[0] eq $addr, @{$bccref})); my $cando = $specialkeys{$addr}||$email2key{$addr}||"signonly"; if ($cando eq "signonly" && $actions{$addr} eq "mustencrypt") { $actions{$addr} = "reject"; dlogit("$addr requires encryption but no key known or given!"); } else { $actions{$addr}= $cando; } } } return %actions if (grep($_ eq "reject", values %actions)); # were there any fallback-all? if so and also none or signonly present, # then all recips are downgraded. my @allactions=values %actions; if ($groupfallback && grep(/^(none|signonly)$/,@allactions)) { # time to downgrade everybody to signing... for my $a (@{$normalref}) { my $addr=$a->[0]; if ($actions{$addr} ne "none") { $actions{$addr}="signonly"; dlogit("downgrading to signonly for $addr"); } } } return %actions; } # parses an address-line, extracts all addresses from it # and splits them into address, phrase, comment, full and directive # returns array of arrays sub extract_addresses { my (@lines)=@_; my @details; for my $a (Mail::Address->parse(@lines)) { my ($addr,$comment,$phrase)=(lc($a->address),$a->comment,$a->phrase); # some name "directive,directive..." if ($phrase=~s/\s*\"([^\"]+)\"\s*//) { my $directive=$1; # clean the phrase up my $newa=Mail::Address->new($phrase,$addr,$comment); push @details,[$addr,$phrase,$comment,$newa->format,$directive]; } else { push @details,[$addr,$phrase,$comment,$a->format,undef]; } } return @details; } # traverses a mime entity and changes all parts with # type == text/plain, charset != us-ascii, transfer-encoding 8bit # to transfer-encoding qp. # input: entity, retval: undef if ok, error message otherwise sub qp_fix_parts { my ($entity)=@_; if ($entity->is_multipart) { foreach ($entity->parts) { my $res=&qp_fix_parts($_); return $res if ($res); } } else { if ($entity->head->mime_type eq "text/plain" && $entity->head->mime_encoding eq "8bit" && lc($entity->head->mime_attr("content-type.charset")) ne "us-ascii") { return("Changing Content-Transfer-Encoding failed!") if ($entity->head->mime_attr("Content-Transfer-Encoding" => "quoted-printable") !="quoted-printable"); } } return; } # log termination, cleanup, exit sub handle_term { my ($sig)=@_; $sig="SIG$sig" if (!$options{o}); logit("Termination requested ($sig), cleaning up"); &cleanup($config{tempdir},1); close $config{logfh} if ($config{logfh}); exit 0; } # reread configuration file and keyrings # no args or return value; intended as a sighandler. sub handle_reload { my ($sig)=@_; logit("received SIG$sig, reloading"); %config=&read_config; $parser->output_dir($config{tempdir}); # that thing persists otherwise %email2key=&read_keyring; # restart mailserver if required # also update pidfile if ($config{"ma-user"} && $config{"ma-pass"} && $config{"maport"}) { # fork off a new smtp-to-queue daemon my $pid=&start_mailserver; write_file($pidf, join("\n", $pid, $$)); } } # remove temporary stuff left behind in directory $what # remove_what set: remove the dir, too. # exception on error, no retval sub cleanup { my ($what,$remove_what)=@_; my ($name,$res); finddepth(sub { next if $_ =~ /^\.{1,2}$/; my $fn = $File::Find::name; if (-d $fn) { rmdir($fn) || bailout("cannod rmdir $fn: $!"); } else { unlink($fn) || bailout("cannot unlink $fn: $!"); } }, $what); $remove_what && (rmdir("$what") || bailout("cant rmdir $what: $!")); return; } # (re)reads the configuration file # calls bailout on problems # needs user-specific vars to be setup # returns %options on success, bailout on error sub read_config { my %options = ( defaultkey=>undef, identify=>undef, defaultaction=>"none", msserver=>undef, msuser=>undef, mspass=>undef, ssl=>undef, "ssl-cert"=>undef, "ssl-key"=>undef, "ssl-ca"=>undef, 'mspass-from-query-secret'=>undef, msport=>587, msp=>"/usr/sbin/sendmail -om -oi -oem", "use-agent"=>undef, syslog=>undef, logfile=>undef, queuedir=>"$home/.kuvert_queue", # $$ not good in tempdir: config is read before fork, then possibly again on -r but by a different process tempdir=>($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$username", alwaystrust=>undef, gpg => "gpg", interval=>60, "query-secret"=>"/bin/sh -c 'stty -echo; read -p \"Passphrase %s: \" X; stty echo; echo \$X'", "flush-secret"=>undef, "mail-on-error"=>undef, "can-detach"=>0, maport=>2587, "ma-user"=>undef, "ma-pass"=>undef, preamble=>1, ); my @over; &bailout("cant open $rcfile: $!") if (!open (F,$rcfile)); logit("reading config file"); my @stuff=; close F; for (@stuff) { chomp; next if (/^\s*\#/ || /^\s*$/); # strip comments and empty lines # trigger on old config-file style if (/^([[:upper:]]+)\s+(\S.*)\s*$/) { my $nf=rewrite_conf(@stuff); die("Can't work with old config, terminating! $progname has found an old config file and attempted a ROUGH auto-conversion. The result has been left in $nf and likely needs to be adjusted for ${progname}'s new features. Please do so and restart $progname with the new config file in place.\n"); } if (/^\s+(\S+)\s+(fallback(-all)?(,(0x)?[a-fA-F0-9]+)?|signonly|none|mustencrypt)\s*(\#.*)?$/) { my ($who,$action)=($1,$2); my $key; if ($action =~ s/^(fallback(-all)?),((0x)?[a-fA-F0-9]+)/$1/) { $key=$3; } push @over, { "who"=>$who, "re"=>qr/$who/, "action"=>$action, "key"=>$key }; dlogit("got override $action " .($key?"key $key ":"")."for $who"); next; } if (/^\S/) { my ($key,$value)=split(/\s+/,$_,2); $key=lc($key); $value=~s/^(\"|\')(.*)\1$/$2/; bailout("unknown config key \"$key\"") if (!exists $options{$key}); # booleans if ($key =~ /^(identify|use-agent|alwaystrust|can-detach|mspass-from-query-secret|preamble)$/) { bailout("bad value \"$value\" for key \"$key\"") if ($value !~ /^(0|1|t|f|on|off)$/i); $options{$key}=($value=~/^(1|on|t)$/); } # numbers elsif ($key =~ /^(msport|interval|maport)$/) { bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^\d+$/); $options{$key}=$value; } # nothing or string elsif ($key =~ /^(ma-pass|ma-user|mail-on-error|msserver|ssl(-cert|-key|-ca)?|msuser|mspass)$/) { $options{$key}=$value; } # nothing or program and args elsif ($key eq "msp") { bailout("bad value \"$value\" for key \"$key\"") if ($value && !-x (split(/\s+/,$value))[0]); $options{$key}=$value; } # full program path elsif ($key eq "gpg") { bailout("bad value \"$value\" for key \"$key\": not executable") if (!-x $value); $options{$key} = $value; } # program with %s escape elsif ($key =~ /^(query-secret|flush-secret)$/) { my ($cmd,$args)=split(/\s+/,$value,2); bailout("bad value \"$value\" for key \"$key\"") if (!-x $cmd || $args!~/%s/); $options{$key}=$value; } # dirs to create elsif ($key=~/^(queuedir|tempdir)$/) { $options{$key}=$value; } # the rest are special cases elsif ($key eq "defaultkey") { bailout("bad value \"$value\" for key \"$key\"") if ($value !~ /^(0x)?[a-f0-9]+$/i); $options{$key}=$value; } elsif ($key eq "defaultaction") { bailout("bad value \"$value\" for key \"$key\"") if ($value!~/^(fallback|fallback-all|signonly|none|mustencrypt)$/); $options{$key}=$value; } elsif ($key eq "syslog") { # syslog: nothing or a facility bailout("bad value \"$value\" for key \"$key\"") if ($value && $value!~/^(authpriv|cron|daemon|ftp|kern|local[0-7]|lpr|mail|news|syslog|user|uucp)$/); $options{$key}=$value; } elsif ($key eq "logfile") { bailout("bad value \"$value\" for key \"$key\"") if (-e $value && !-w $value); if ($config{$key} ne $value) # deal with changing logfiles { close($config{logfh}) if (defined $config{logfh}); delete $config{logfh}; } $options{$key}=$value; } dlogit("got config $key=$value"); } } # post-config-reading sanity checking if ($options{msserver} && $options{msuser}) { bailout("smtp auth requires mspass or mspass-from-query-secret options") if (!$options{mspass} && !$options{"mspass-from-query-secret"}); } # post-config-reading directory fixes for my $v ($options{queuedir}, $options{tempdir}) { if (!-d $v) { mkdir($v,0700) or bailout("cannot create directory $v: $!\n"); } my @stat=stat($v); if ($stat[4] != $< or ($stat[2]&0777) != 0700) { bailout("directory $v does not belong to you or has bad mode."); } } $options{overrides}=\@over; return %options; } sub rewrite_conf { my @old=@_; my ($fh,$fn)=mkstemp(($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/config.XXXX"); my %xlat=qw(NGKEY defaultkey GETSECRET query-secret DELSECRET flush-secret MTA msp ALWAYSTRUST alwaystrust INTERVAL interval TEMPDIR tempdir QUEUEDIR queuedir LOGFILE logfile IDENTIFY identify); for (@old) { chomp; next if (/^\#/ || /^\s*$/); # strip comments and empty lines if (/^(\S+)\s+((none|std(sign)?|ng(sign)?|fallback)(-force)?)\s*$/) { my ($k,$v)=($1,$2); $v=~s/(std|ng)sign/signonly/; $v=~s/(std|ng)/fallback/; $v=~s/fallback-force/fallback-all/; print $fh ($k eq "DEFAULT"?"defaultaction":" $k")." $v\n\n"; } elsif (/^([[:upper:]]+)\s+(\S.*)\s*$/) { my ($k,$v)=($1,$2); if ($xlat{$k}) { $k=$xlat{$k}; print $fh "$k $v\n\n"; } } } close $fh; return $fn; } # read keyring # needs global %config,$debug # returns email-to-keyid hash, bails out on error sub read_keyring { my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled', 'r'=>'revoked','e'=>'expired'); my %email2key; # email address to key id logit("reading keyring..."); my $tf="$config{tempdir}/subproc"; my @tmp=`$config{gpg} -q --batch --list-keys --with-colons --no-expensive-trust-checks 2>$tf`; bailout("keyring reading failed: $?",(-r $tf && readfile($tf))) if ($? or $? >> 8); logit("finished reading keyring"); unlink($tf); my $lastkey; foreach (@tmp) { # gpg docs say output is utf8-encoded my $native = Encode::decode('utf-8', $_, Encode::FB_DEFAULT); my @info=split(/:/, $native); # only public keys and uids are of interest next if ($info[0] ne "pub" && $info[0] ne "uid"); undef $lastkey if ($info[0] eq "pub"); # clean slate, before the skipping begins $info[9] =~ s/\\x3a/:/g; # re-insert colons, please # ignore expired, revoked and other bad keys if (my $skip = $badcauses{$info[1]}) { my $msg = "ignoring $info[0] " . ($info[4]? "key 0x$info[4] " : "") . ($info[9]? "address $info[9] " : "") . ", reason: $skip"; &logit($msg); dlogit($msg); next; } # if no address given: remember this key and look at the uids for addresses my $address = lc($2) if ($info[9] =~ /(^|\s|<)([^\s<]+\@[^\s>]+)>?/); # check the key: public part or uid? if ($info[0] eq "pub") { $lastkey = $info[4]; if ($address) { $email2key{$address}="0x$lastkey"; dlogit("got key 0x$lastkey for $address"); } else { dlogit("memorised key 0x$lastkey, no address known yet"); } } else { # uid: associate the current address with the key # given in the most recent public key line if ($address && $lastkey) { $email2key{$address}="0x$lastkey"; dlogit("got key (uid) 0x$lastkey for $address"); } else { dlogit("ignoring uid without valid address or valid key: $native"); } } } return %email2key; } # send this mime entity out # if msserver+port known: use smtp, envelope from is $from # otherwise use local msp program with @recips # uses global %config # returns nothing if ok, @error messages otherwise sub send_entity { my ($ent,$from,@recips,)=@_; if ($config{msserver} && $config{msport}) { my $dom=hostname; $IO::Socket::SSL::DEBUG = 3 if ($config{ssl} && $debug); my $s=Net::SMTP->new( $config{msserver}, Port => $config{msport}, Hello => $dom, SSL => (defined $config{ssl} && $config{ssl} eq "ssl"? 1 : undef), SSL_key_file => $config{"ssl-key"}, SSL_cert_file => $config{"ssl-cert"}, SSL_ca_file => $config{"ssl-ca"}, Debug => $debug); return("Cannot connect to mail server ".$config{msserver}.": $@") if (!$s); # do negotiate starttls if asked to if (defined $config{ssl} && $config{ssl} eq "starttls") { $s->starttls or return ("Cannot negotiate STARTTLS: $@"); } # do smtp auth if asked to if ($config{msuser}) { my $authed; while (!$authed) { if (!$config{mspass} && $config{"mspass-from-query-secret"}) { my $cmd=sprintf($config{"query-secret"},"smtp-password"); $config{mspass}=`$cmd`; return("Couldn't get smtp password via query-secret: $!") if (!$config{mspass}); chomp($config{mspass}); } $authed=$s->auth($config{msuser},$config{mspass}); # bailout if we can't requery if (!$authed) { # get rid of the apparently dud password and try again delete $config{mspass}; if ($config{"mspass-from-query-secret"}) { my $cmd=sprintf($config{"flush-secret"},"smtp-password"); system($cmd); # ignore the flushing result; best effort only } else { return("SMTP auth failed: ".$s->code." ".$s->message); } } } } $s->mail($from) or return("Mailserver rejected our from address \"$from\": ".$s->code." ".$s->message); my @okrecips=$s->to(@recips, { SkipBad => 1 }); if (@okrecips != @recips) { my %seen; map { $seen{$_}=1; } (@recips); map { ++$seen{$_}; } (@okrecips); my @missed=grep $seen{$_}==1, keys %seen; return ("Mailserver rejected some recipients!", "rejected: ".join(", ",@missed), "info: ".$s->code." ".$s->message); } $s->data($ent->as_string) or return("Mailserver rejected our data: ".$s->code." ".$s->message); $s->quit; } else { # pipeline to msp, but we do it ourselves: safe cmd handling my $pid=open(TOMTA,"|-"); return("Can't open pipe to MSP: $!") if (!defined $pid); if ($pid) { $ent->print(\*TOMTA); close(TOMTA) || return("Error talking to MSP: $?"); } else { my @cmd=split(/\s+/,$config{msp}); push @cmd,'-f',$from; push @cmd,@recips; exec(@cmd) or return("Error executing MSP: $!"); } } return; } # sign or sign+encrypt a file # input: sign key, infile and outfile path, recipient keys if encryption wanted. # input must be existing filename, outfile must not exist. # signkey overrides config-defaultkey, and is optional. # uses global %config # returns: (undef,hashalgname) if ok, (1,undef) if bad passphrase, #(2,errorinfo) otherwise sub sign_encrypt { my ($signkey,$infile,$outfile,@recips)=@_; my @cmd = ($config{gpg},qw(-q -t -a --batch --status-fd 2)); my ($precmd,$pid); push @cmd,"--always-trust" if ($config{alwaystrust}); $signkey=$config{defaultkey} if ($config{defaultkey} && !$signkey); push @cmd,"--default-key",$signkey if ($signkey); # should we leave the passphrase handling to gpg/gpg-agent? # otherwise, we run a query program in a pipeline # after determining what passphrase gpg is looking for if ($config{"use-agent"}) { push @cmd,"--use-agent"; } if (@recips) { push @cmd, qw(--encrypt --sign), map { ("-r",$_) } (@recips); } else { push @cmd,"--detach-sign"; } if (!$config{"use-agent"}) { # now determine which passphrase to query for: # run gpg once without data, and analyze the status text $pid=open(F,"-|"); if (!defined $pid) { return (2,"Could not run gpg: $!"); } elsif (!$pid) { # child: dup stderr to stdout and exec gpg open STDERR, ">&",\*STDOUT or bailout("Can't dup2 stderr onto stdout: $!\n"); exec(@cmd) or bailout("exec gpg failed: $!\n"); } # read the status stuff in and determine the passphrase required for my $l () { if ($l=~/^\[GNUPG:\] NEED_PASSPHRASE ([a-fA-F0-9]+) ([a-fA-F0-9]+) \d+ \d+$/) { $precmd=sprintf($config{"query-secret"},"0x$2"); push @cmd,"--passphrase-fd",0; last; } } close(F); } push @cmd,"-o",$outfile,$infile; # now run gpg, read back stdout/stderr $pid=open(F,"-|"); if (!defined $pid) { return (2, "Could not run gpg: $!"); } elsif (!$pid) { # with agent: simply run gpg if ($config{"use-agent"}) { # collapse stderr and stdout open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); exec(@cmd) or bailout("exec gpg failed: $!\n"); } else { # without agent: run the query program # in yet another pipeline to gpg # read from child: query prog in child # whereas we run gpg my $pidc=open(G,"-|"); if (!defined($pidc)) { bailout("Error: couldn't fork: $!\n"); } elsif (!$pidc) { # child: run query prog with stderr separated exec($precmd) or die("exec $precmd failed: $!\n"); } # parent: we run gpg # dup stderr to stdout and exec gpg open STDERR, ">&",\*STDOUT or bailout("can't dup2 stderr onto stdout: $!\n"); open STDIN, ">&", \*G or bailout("can't dup stdin onto child-pipe: $!\n"); exec(@cmd) or bailout("exec gpg failed: $!\n"); } } # outermost parent: read gpg status info my @output; eval { local $SIG{ALRM}=sub { die "alarm\n"; }; alarm $timeout; @output=; alarm 0; close(F); }; if ($@) { logit("gpg timeout!"); kill("TERM",$pid); return (1, undef); } elsif ($? or $?>>8) { # no complaints if gpg just dislikes the passphrase return (1,undef) if (grep(/(MISSING|BAD)_PASSPHRASE/,@output)); return (2,"gpg terminated with $?", "Detailed error messages:",@output); } if (my @infoline = grep(/^\[GNUPG:\] SIG_CREATED/, @output)) { # output format: # [GNUPG:] SIG_CREATED my @infoparts = split(/\s+/,$infoline[0]); # from rfc4880, section 9.4. required for multipart/signed # to translate gpg's numeric status output into names that # rfc3156 likes my %hashalgos = ( 1 => "pgp-md5", 2 => "pgp-sha1", 3 => "pgp-ripemd160", 8 => "pgp-sha256", 9 => "pgp-sha384", 10 => "pgp-sha512", 11 => "pgp-sha224", ); my $hashname = $hashalgos{$infoparts[4]}; return (undef,$hashname) if defined $hashname; } # cheap catch-all, including unknown hash algo identifiers return (2, "gpg did not complete the operation", "Detailed error messages:",@output); } # logs the argument strings to syslog and/or the logfile # uses global %config # output encoding is whatever the locale specifies, fallback is to use replacement characters # returns nothing sub logit { my (@msgs)=@_; if ($config{logfile}) # our own logfile? { if (!$config{logfh}) # not open yet? { $config{logfh}=FileHandle->new(">> $config{logfile}"); die "can't open logfile $config{logfile}: $!\n" if (!$config{logfh}); $config{logfh}->autoflush(1); } # replacement chars, please. print { $config{logfh} } scalar(localtime) ." " .Encode::encode(locale => join("\n\t",@msgs)."\n", Encode::FB_DEFAULT); } if ($config{syslog}) { setlogsock('unix'); openlog($progname,"pid,cons",$config{syslog}); syslog("notice", Encode::encode(locale => join("\n",@msgs), Encode::FB_DEFAULT)); closelog; } } # debug log to stderr # uses the locale-sourced encoding, with replacement character where the encoding doesn't cover the data sub dlogit { print STDERR Encode::encode(locale => join("\n",@_)."\n", Encode::FB_DEFAULT) if ($debug); } # alerts the user of some problem # this is done via the normal logging channels, # plus: stderr if can-detach is not set # plus: email if mail-on-error is set to some email addy # for email the program name plus first message line are used as subject # sender and recipient are set to mail-on-error config entry sub alert { my (@msgs)=@_; logit(@msgs); if (!$config{"can-detach"}) { print STDERR join("\n\t",@msgs)."\n"; } if ($config{"mail-on-error"}) { my $heading=shift @msgs; my $out=join("\n",@msgs); my $ent=MIME::Entity->build(From=>$config{"mail-on-error"}, To=>$config{"mail-on-error"}, Subject=>($progname.": $heading"), Data=>\$out); send_entity($ent,$config{"mail-on-error"},$config{"mail-on-error"}); } } # alert of a problem and die sub bailout { my (@msgs)=@_; $msgs[0]="Fatal: ".$msgs[0]; alert(@msgs); # don't bother writing to stderr if alert already took care of that exit(1) if (!$config{"can-detach"}); die(scalar(localtime).join("\n\t",@msgs)."\n"); } # returns pid of new mailserver process # dies if unsuccessful sub start_mailserver { # fork off the smtp-to-queue daemon my $pid=fork; if (!defined($pid)) { bailout("cannot fork: $!\n"); } elsif (!$pid) { # run mailserver, which does never reload the config $0=$listenername; open(STDIN, "/dev/null") or die "cannot open /dev/null: $!\n"; # default sighandlers map { $SIG{$_}='DEFAULT'; } qw(USR1 HUP INT QUIT TERM); &accept_mail; } # parent return $pid; } # run a receive-only mailserver on localhost and spool to queue # does not terminate except signalled sub accept_mail { my $server = IO::Socket::INET->new(Listen=>1, ReuseAddr=>1, LocalAddr=>"127.0.0.1", LocalPort=>$config{"maport"},); bailout("setting up listening port failed: $!") if (!$server); while(my $conn = $server->accept) { my $esmtp = Net::Server::Mail::ESMTP->new(socket=>$conn); $esmtp->register('Net::Server::Mail::ESMTP::plainAUTH'); $esmtp->set_callback(MAIL=>\&req_auth); $esmtp->set_callback(RCPT=>\&req_auth); $esmtp->set_callback(AUTH=>\&check_auth); $esmtp->set_callback("DATA-INIT"=>\&start_mail); $esmtp->set_callback("DATA-PART"=>\&cont_mail); $esmtp->set_callback(DATA => \&finish_mail); $esmtp->process(); $conn->close(); } } # small helper for accept_mail, to enforce our submitter credentials # has to return 0/1 sub check_auth { my ($session,$user,$pwd)=@_; return ($user eq $config{'ma-user'} and $pwd eq $config{'ma-pass'}); } # small helper for accept_mail, to enforce auth in general # has to return (ok or not, smtp error code, message) sub req_auth { my ($session,$input)=@_; if (!$session->{AUTH}->{completed}) { return(0,530,"5.7.0 Authentication Required"); } return(0,550,"Invalid Address.") if (!extract_addresses($input)); return 1; } # small helper for accept_mail, checks the things # when the DATA command is issued # has to return (ok or not, smtp error code, message) sub start_mail { my($session,$data) = @_; my @recipients = $session->get_recipients(); my $sender = $session->get_sender(); return(0,554,'No recipients given.') if (!@recipients); return(0,554,'No sender given.') if (!$sender); my $qid=join("",Time::HiRes::gettimeofday); my $fn=$config{queuedir}."/".$qid; if (!open(F,">$fn")) { alert("can't open new queuefile $fn: $!"); return(0,450,"can't create queuefile. please try again later."); } if (!flock(F,LOCK_NB|LOCK_EX)) { alert("can't lock queuefile $qid: $!"); return(0,450,"can't lock queuefile. please try again later."); } print F "X-Kuvert-From: $sender\nX-Kuvert-To: " .join(", ",@recipients)."\n"; logit("queueing email from $sender to ".join(", ",@recipients)); $session->{DATA}->{qfh}=\*F; $session->{DATA}->{qid}=$qid; return 1; } # small helper for accept_mail, handles interim DATA blobs sub cont_mail { my ($session,$dr)=@_; print {$session->{DATA}->{qfh}} $$dr; undef $$dr; return 1; } # small helper for accept_mail, handles the end # of the DATA phase # has to return (ok or not, smtp error code, message) sub finish_mail { my ($session,$dr)=@_; print {$session->{DATA}->{qfh}} $$dr; undef $$dr; my $qid=$session->{DATA}->{qid}; if (!close($session->{DATA}->{qfh})) { alert("could not close queuefile $qid: $!"); return(0,450,"could not close queuefile"); } logit("finished enqueueing mail $qid"); return(1,250,"Mail enqueued as $qid"); } __END__ =pod =head1 NAME kuvert - Automatically sign and/or encrypt emails based on the recipients =head1 SYNOPSIS kuvert [-d] [-o] [-r|-k] [-c configfile] =head1 DESCRIPTION Kuvert is a tool to protect the integrity and secrecy of your outgoing email independent of your mail client and with minimal user interaction. It reads mails from its queue (or accepts SMTP submissions), analyzes the recipients and decides to whom it should encrypt and/or sign the mail. The resulting mail is coerced into the PGP-MIME framework defined in RFC3156 and finally sent to your outbound mail server. Kuvert uses GnuPG for all cryptographic tasks and is designed to interface cleanly with external secret caching tools. =head1 OPTIONS After startup kuvert periodically scans its queue directory and processes mails from there; depending on your GnuPG passphrase setup kuvert may daemonize itself. In either case, kuvert runs forever until actively terminated. Kuvert's behaviour is configured primarily using a configuration file, with exception of the following commandline options: =over =item -d Enables debugging mode: extra debugging information is written to STDERR. (This is independent of normal logging.) =item -o Enables one-shot mode: kuvert does not loop forever but processes only the current queue contents and then exits. Kuvert does also not start an SMTP listener in this mode. =item -r Tells a running kuvert daemon to reload the configuration file and the gpg keyring. This is equivalent to sending a SIGUSR1 to the respective process. =item -k Tells a running kuvert daemon to terminate cleanly. This is equivalent to sending a SIGTERM to the respective process. =item -c Tells kuvert to use the given config file instead of ~/.kuvert. =back =head1 OPERATION At startup kuvert reads its configuration file and your gnugp keyring and remembers the association of email addresses to keys. Kuvert then works as a wrapper around your mail transfer agent (MTA): you author your emails like always but instead of sending them out directly you submit them to kuvert. Periodically kuvert scans its queue and processes any email therein. If your keyring contains a key for a recipient, kuvert will encrypt and sign the email to that recipient. If no key is available, kuvert will only (clear/detached-)sign the email. Subsequently, the email is sent onwards using your MTA program or SMTP. Emails to be processed can have any valid MIME structure; kuvert unpacks the MIME structure losslessly and repacks the (encrypted/signed) mail into a PGP/MIME object as described in RFC3156. The mail's structure is preserved. Signature and encryption cover all of the mail content with the exception of the top-level headers: for example the "Subject" header will be passed in clear, whereas any body or attached MIME object will be signed/encrypted. The encrypt-or-sign decision can be overridden on a per-address basis using the configuration file or, even more fine-grainedly, by using directives in the actual email. Kuvert can also be told not to modify an email at all. =head2 Submitting Emails to Kuvert Kuvert primarily relies on mails being dumped into its queue directory. Kuvert operates on files with numeric file names only. Anything that you store in its queue directory with such a filename will be treated as containing a single RFC2822-formatted email. However, no mainstream MUA supports such a drop-your-files-somewhere scheme, and therefore kuvert comes with a helper program called kuvert_submit (see L) which mimics sendmail's mail submission behaviour but feeds to the kuvert queue. If your MUA can be instructed to run a program for mail submission, kuvert_submit can be used. Alternatively, you can send your email to kuvert via SMTP. Kuvert comes with a built-in receive-only mail server, which feeds to the queue directory. As allowing others to submit emails for your signature would be silly and dangerous, kuvert's mail server only listens on the localhost IP address and requires that your MUA uses SMTP Authentication to ensure that only your submissions are accepted. If your MUA supports SMTP AUTH PLAIN or LOGIN and can be told to use localhost and a specific port for outbound email, then you can use this mechanism. =head2 Transporting Emails Onwards Kuvert can send outbound emails either by running a local MTA program or by speaking SMTP to some (fixed) outbound mail server of your choice. =head2 Recipients, Identities and the SMTP Envelope In general kuvert identifies recipients using the To, Cc, Bcc and Resent-To headers of the queued email. If the mechanism you used to submit the mail to kuvert did explicitely set recipients, then these B the headers within the email. This is the case if kuvert_submit is called with a list of recipients and no -t option and for SMTP submission. If kuvert enqueues email via inbound SMTP, the SMTP envelope B the email headers: recipients that are present in the envelope but not the headers are treated as Bcc'd, and recipients listed in the headers but not the envelope are B. Any Resent-To header is ignored for SMTP-submitted email. Only if no overriding recipients are given, kuvert checks the mail for a Resent-To header. If present, the email is sent out immediately to the Resent-To addresses I. (This is the standard "bounce" behaviour for MUAs that don't pass recipients on to an MSP/MTA directly.) When sending outbound email, kuvert usually uses the From header from the queued email as identity. If the email was queued via SMTP, the envelope again B the mail headers. Note that kuvert sets the envelope sender using "-f" if sending email via a local MTA program; if you are not sufficiently trusted by your MTA to do such, your mail may get an X-Authentication-Warning header tacked on that indicates your username and the fact that the envelope was set explicitely. =head2 Passphrase Handling Kuvert does not handle your precious keys' passphrases. You can either elect to use gpg-agent as an (on-demand or caching) passphrase store, or you can tell kuvert what program it should run to query for a passphrase when required. Such a query program will be run in a pipeline to GnuPG, and kuvert will not access, store or cache the passphrases themselves: there are better options available for secret caching, for example the Linux in-kernel keystorage (L). =head2 How Kuvert Decides What (Not) To Do For each recipient, kuvert can be told to apply one of five different action overrides: =over =item none The email is sent as-is (except for configuration directive removal). =item signonly The email is (clear/detached-) signed. =item fallback The email is encrypted and signed if there is a key available for this recipient or only signed if not. =item fallback-all The email is encrypted and signed if keys are available for B recipients, or only signed otherwise. Recipients whose action is set to "none" and Bcc'd recipients are not affected by this action. The fallback-all action is an "all-or-nothing" action as far as encryption is concerned and ensures that no mix of encrypted or unencrypted versions of this email are sent out: if we can we use encryption for everybody, or otherwise everybody gets it signed (or even unsigned). (Bcc'd recipients are the exception.) =item mustencrypt The mustencrypt action is an "all-or-nothing" action and ensures that any emails involving such addresses are only sent out if the emmail can be encrypted for every recipient. The email is rejected and B, if any addresses are present that have the mustencrypt directive set but where no key is known. Bcc'd recipients I included in this decision. This action overrides fallback-all. =back =head2 Specifying Actions Kuvert uses four sources for action specifications: directives in the individual email addresses, action directives in the configuration file, an X-Kuvert header in your email, and finally the default action given in the configuration file. =over =item 1. First kuvert looks for action directives in your configuration file. Such directives are given as action plus regular expression to be matched against an address, and the first matching directive is used. =item 2. If no matching directive is found, the default action given in the configuration file is applied. =item 3. Kuvert now checks for the presence of an X-Kuvert header: its content must be an action keyword, which is applied to all recipients of this email except the ones whose action at this stage is "none". (In other words: if you specify "no encryption and no signing" for some addresses, then this cannot be overridden in a blanket fashion.) =item 4. Kuvert then analyzes each recipient email address. If an address has the format Some Text "action=someaction" ", kuvert strips the quoted part and overrides the addressee's action with someaction. =item 5. If any "mustencrypt" recipient action is present, but no key is known (or given in an override, see section L), then the whole mail is rejected and not sent to anybody. =item 6. Finally kuvert checks if any recipient has action "fallback-all". If so, kuvert =over =item a) checks if any recipients (except Bcc'd) have action "signonly" or "none". If this is the case, all "fallback" and "fallback-all" actions are downgraded to "signonly". =item b) checks if keys for all recipients (except Bcc'd) are available. If not, all "fallback" and "fallback-all" actions are downgraded to "signonly". =back =item 7. Recipients which are given in a Bcc: header are always treated independently and separately from all others (except for mustencrypt): any "fallback-all" action is downgraded to "fallback" for Bcc'd addresses, and if encryption is used, the email is encrypted separately so that no record of the Bcc'd recipient is visible in the email as sent out to the "normal" recipients. Also, any Bcc: header is removed before sending an email onwards. =back =head2 Key Selection Kuvert depends on the order of keys in your keyring to determine which key (of potentially many) with a given address should be used for encryption. By default kuvert uses the B key that it encounters for a given address. For people who have multiple keys for a single address this can cause problems, and therefore kuvert has override mechanisms for encryption key selection: You can specify a key to encrypt to for an address in the configuration file (see below), or you can override the key selection for and within a single mail: If the recipient address is given in the format Some Name "key=keyid" Kuvert will strip the double-quoted part and use this particular key for this recipient and for this single email. The keyid must be given as the hex key identifier. This mechanism overrides whatever associations your keyring contains and should be used with caution. Note that both key and action overrides can be given concurrently as a single comma-separated entry like this: Some Name "action=fallback,key=0x12345" The signing key can be overridden in a similar fashion: if the From address contains a "key=B" stanza, kuvert will use this key for signing this single email. =head1 CONFIGURATION The kuvert configuration file is plain text, blank lines and lines that start with "#" are ignored. The configuration has of two categories: options and address/action specifications. =head2 Address and Action Address+action specifications are given one per line. Such lines must start with some whitespace, followed by an address regexp, followed by some whitespace and the action keyword. For actions "fallback" and "fallback-all" kuvert also allows you to specify a single key identifier like this: "fallback,0x42BD645D". The remainder of the line is ignored. The address regexp is a full Perl regular expression and will be applied to the raw SMTP address (i.e. not to the comment or name in the email address), case-insensitively. The regular expression may need to be anchored with ^ and $; kuvert does not do that for you. You must give just the core of the regexp (no m// or //), like in this example: # don't confuse mailing list robots ^.*-request@.*$ none The action keyword must be one of "none", "signonly", "fallback", "fallback-all" or "mustencrypt"; see section L for semantics. Order of action specifications in the config file is significant: the search terminates on first match. =head2 Options Options are given one per line, and option lines must start with the option name followed by some whitespace. All options are case-sensitive. Depending on the option content, some or all of the remainder of the option line will be assigned as option value. Inline comments are not supported. In the following list of options angle brackets denote required arguments like this: defaultkey Options that have boolean arguments recognize "1", "on" and "t" as true and "0", "off", "f" as false (plus their upper-case versions). Other options have more restricted argument types; kuvert generally sanity-checks options at startup. =head2 Known Options =over =item syslog Whether kuvert should use syslog for logging, and if so, what facility to use. Default: nothing. This is independent of the logfile option below. kuvert uses your locale's encoding for all messages sent to syslog. =item logfile Whether kuvert should write log messages to a file, appending to it. Default: not set. This is independent of the syslog option above. kuvert uses your locale's encoding for saving data to the logfile. =item mail-on-error If kuvert encounters serious or fatal errors, an email is sent back to this address if set. Default: undef. This email is sent in addition to the normal logging via syslog or logfile. =item queuedir Where kuvert and its helper programs store mails to be processed. Default: ~/.kuvert_queue. The directory is created if necessary. The directory must be owned by the user running kuvert and have mode 0700. =item tempdir Where kuvert stores temporary files. Default: a directory called kuvert. in $TMPDIR or /tmp. The directory is created if necessary, and must be owned by the user running kuvert and have mode 0700. This directory is completely emptied after processing an email. =item identify Whether kuvert should add an X-Mailer header to outbound emails. Default: false. The X-Mailer header consists of the program name and version. =item preamble Whether kuvert should include an explanatory preamble in the generated MIME mail. Default: true =item interval This sets the queue checking interval in seconds. Default: 60 seconds. =item msserver Mail Submission Server for outbound email. Default: unset. If this is set, kuvert will use SMTP to send outbound emails. If not set, kuvert uses the mail submission program on the local machine. See msp below. =item msport The TCP port on which the Mail Submission Server listens. Default: 587. Ignored if msserver is not set. =item ssl Whether SSL or STARTTLS are to be used for outbound SMTP submission. The value must be either "starttls" to use STARTTLS or "ssl" for raw SSL. SSL encryption is not used if this option is unset. =item ssl-cert =item ssl-key =item ssl-ca If an SSL client certificate is to be presented to the SMTP server, set both ssl-cert and ssl-key. If your system-wide CA certificate setup doesn't include the certificate your SMTP server uses, set ssl-ca to point to a PEM file containing all the relevant CA certificates. All these are ignored if the ssl option isn't set. =item msuser The username to use for SMTP authentication at the Mail Submission Server. SMTP Auth is not attempted if msuser isn't set. Ignored if msserver is not set. =item mspass The password for SMTP authentication. Ignored if msserver or msuser are not set. =item mspass-from-query-secret Whether the mspass should be retrieved using the query-secret program instead of giving the mspass in the config file. Ignored if msserver or msuser are not set. If this option is set, the query-secret program will be used to ask for the "smtp-password" when the first mail is processed. The password will be cached if authentication succeeds or you will be asked again, until authentication succeeds. =item msp Defines the program kuvert should use to deliver email. Default: "/usr/sbin/sendmail -om -oi -oem". This is ignored if msserver is set. The argument must include the full path to the program, and the program must accept the common mail transfer agent arguments as defined in the Linux Standards Base (see L). =item gpg Defines which custom GnuPG executable kuvert should use. If this option is given the argument must be a full path to an executable. Default: "gpg", i.e. anywhere in the path. =item can-detach Indicates to kuvert that it can background itself on startup, detaching from the terminal. Default: false. Detaching works only if your chosen mechanism for passphrase entry doesn't require interaction via the original terminal. This is the case if you delegate passphrase handling to gpg-agent and configure it for X11 pinentry, or if your secret-query program is an X11 program with its own window. =item maport Kuvert can accept email for processing via SMTP. This option sets the TCP port kuvert listens on (localhost only). Default: 2587. Ignored if ma-user and ma-pass are not both set. If you want to use this mechanism, tell your mail program to use localhost or 127.0.0.1 as outgoing mail server and enable SMTP Authentication (see below). =item ma-user This option sets the required SMTP authentication username for accepting mails via SMTP. Default: undef. Kuvert does not listen for SMTP submissions unless both ma-user and ma-pass are set. Kuvert does not accept emails for processing via SMTP unless you prove your identity with SMTP Authentication (or anybody on your local machine could use kuvert to send emails signed by you!). Kuvert currently supports only AUTH PLAIN and LOGIN (which is not a major problem as we listen on the loopback interface only). This option sets the username kuvert recognizes as yours. This can be anything and doesn't have to be a real account name. =item ma-pass This option sets the password your mail user agent must use for SMTP Authentication if submitting mails via SMTP. Default: unset. Kuvert does not listen for SMTP submissions unless both ma-user and ma-pass are set. This password does not have to be (actually shouldn't be) your real account's password. Note that using SMTP submission requires that you protect your kuvert configuration file with strict permissions (0600 is suggested). =item defaultkey Specifies a default key to use as signing key. Default: unset, which means GnuPG gets to choose (usually the first available secret key). Can be overridden in the From: address, see section L. =item defaultaction Which action is to be taken if no overrides are found for a recipient. Default: none. See section L for recognized actions. =item alwaystrust Whether gpg should be told to trust all keys for encryption or not. Default: false. =item use-agent Whether kuvert should delegate all passphrase handling to the gpg-agent and call gpg with appropriate options. Default: false. If not set, kuvert will ask the user (or some nominated passphrase store) for passphrases on demand. =item query-secret Tells kuvert which program to use for passphrase retrieval. Default: "/bin/sh -c 'stty -echo; read -p \"Passphrase %s: \" X; \ stty echo; echo $X'" Ignored if use-agent is set. Kuvert does not store passphrases internally but rather runs the indicated program in a pipeline with gpg when signing. If you use a passphrase store (like the Linux-kernel keyutils or secret-agent or the like), enter your retrieval program here. The program is run with kuvert's environment, the first %s in the argument spec is replaced with the hex keyid and the passphrase is expected on stdout. The exit code is ignored. If can-detach is not set, the program has access to kuvert's terminal. Note that the default query program prohibits kuvert from backgrounding itself. =item flush-secret This program is called to invalidate an external passphrase cache if kuvert is notified by gpg of the passphrase being invalid. Default: undef. Ignored if use-agent is set. The program is run with kuvert's environment and with the first %s of its argument spec being replaced by the hex keyid in question. Its exit code is ignored. If can-detach is not set, the program has access to kuvert's terminal. =back =head1 DIAGNOSTICS Kuvert usually logs informational messages to syslog and/or its own logfile, both of which can be disabled and adjusted. If kuvert detects a fault that makes successful processing of a particular email impossible, kuvert will report that on STDERR (if not detached) and also email an error report if the option mail-on-error is enabled. Such partially or completely unprocessed mails are left in the queue but are renamed (the name is prefixed with "failed."); it is up to you to either remove such leftovers or rename them to something all-numeric once the problem has been resolved. The behaviour is similar if fatal problems are encountered; after alerting kuvert will terminate with exit code 1. =head1 ENVIRONMENT AND SIGNALS Kuvert itself uses only on environment variable: $TMPDIR provides the fallback location for kuvert's temporary directory. Kuvert passes its complete environment to child processes, namely gpg and any passphrase-query programs. On reception of SIGUSR1, kuvert reloads its configuration file and keyring. Any one of SIGHUP, SIGINT, SIGQUIT and SIGTERM causes kuvert to terminate cleanly, invalidating the passphrases if a query program is used. All other signals are ignored. =head1 FILES =over =item ~/.kuvert The configuration file read by kuvert and kuvert_submit. =item ~/.kuvert_queue The default queue directory. =item /tmp/kuvert.pid.EuidE holds the pid of a running kuvert daemon. =back =head1 SEE ALSO L, L, RFC3156, RFC4880, RFC2015 =head1 AUTHOR Alexander Zangerl =head1 COPYRIGHT AND LICENCE copyright 1999-2014 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. =cut kuvert/kuvert_submit.c0000644000000000000000000001526112503154002012330 0ustar /* * * this file is part of kuvert, a wrapper around your mta that * does pgp/gpg signing/signing+encrypting transparently, based * on the content of your public keyring(s) and your preferences. * * copyright (c) 1999-2008 Alexander Zangerl * * 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 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include #include #define CONFFILE "/.kuvert" #define DEFAULT_QUEUEDIR "/.kuvert_queue" #define BUFLEN 65536 #define FALLBACKMTA "/usr/sbin/sendmail" #define BAILOUT(a,...) {fprintf(stderr,"%s: ",argv[0]); fprintf(stderr, a "\n",##__VA_ARGS__);syslog(LOG_ERR,a,##__VA_ARGS__); exit(1);} int main(int argc,char **argv) { struct passwd *pwentry; /* fixme sizes */ char filen[256],buffer[BUFLEN],dirn[256]; int res,c,spaceleft; char *p,*dirnp; FILE *out; FILE *cf; struct stat statbuf; int direct=1,norecips=0,testmode=0,i; /* determine whether to queue stuff or to call sendmail directly: if there is no proper config file for kuvert in $HOME or if given -bv go direct, otherwise we enqueue. */ openlog(argv[0],LOG_NDELAY|LOG_PID,LOG_MAIL); for(i=1;ipw_dir,CONFFILE)==-1) BAILOUT("overlong filename, suspicious",NULL); if (!(cf=fopen(filen,"r"))) { /* no config file -> exec sendmail */ syslog(LOG_INFO,"user has no "CONFFILE" config file, running sendmail"); } else { direct=0; /* scan the lines for ^QUEUEDIR\s+ */ dirnp=NULL; while(!feof(cf)) { p=fgets(buffer,sizeof(buffer)-1,cf); /* empty file? ok, we'll ignore it */ if (!p) break; if (!strncasecmp(buffer,"QUEUEDIR",sizeof("QUEUEDIR")-1)) { p=buffer+sizeof("QUEUEDIR")-1; for(;*p && isspace(*p);++p) ; if (*p) { dirnp=p; /* strip the newline from the string */ for(;*p && *p != '\n';++p) ; if (*p == '\n') *p=0; /* strip eventual trailing whitespace */ for(--p;p>dirnp && isspace(*p);--p) *p=0; } /* empty dir? ignore it */ if (strlen(dirnp)<2) dirnp=NULL; break; } } fclose(cf); } } /* direct to sendmail requested? */ if (direct) { /* mangle argv[0], so that it gets recognizeable by sendmail */ argv[0]=FALLBACKMTA; *buffer=0; /* bah, c stringhandling is ugly... i just want all args in one string for a nice syslog line... */ for(c=0,spaceleft=sizeof(buffer); cpw_dir,DEFAULT_QUEUEDIR) ==-1) BAILOUT("overlong dirname, suspicous.",NULL); dirnp=dirn; } res=stat(dirnp,&statbuf); if (res) { if (errno == ENOENT) { /* seems to be missing -> try to create it */ if (mkdir(dirnp,0700)) BAILOUT("mkdir %s failed: %s\n",dirnp,strerror(errno)); } else BAILOUT("stat %s failed: %s\n",dirnp,strerror(errno)); } else if (!S_ISDIR(statbuf.st_mode)) { BAILOUT("%s is not a directory",dirnp); } else if (statbuf.st_uid != getuid()) { BAILOUT("%s is not owned by you - refusing to run",dirnp); } else if ((statbuf.st_mode & 0777) != 0700) { BAILOUT("%s does not have mode 0700 - refusing to run",dirnp); } umask(066); /* absolutely no access for group/others... */ /* dir does exist now */ snprintf(filen,sizeof(filen),"%s/%d",dirnp,getpid()); /* file create and lock */ if (!(out=fopen(filen,"a"))) { BAILOUT("fopen %s failed: %s\n",filen,strerror(errno)); } if (flock(fileno(out),LOCK_EX)) { BAILOUT("flock failed: %s\n",strerror(errno)); } /* scan the arguments for the LSB-mandated options: we ignore any options but -f, -t. /* no getopt error messages, please! */ opterr=0; while ((c=getopt(argc,argv,"f:t"))!=-1) { if (c=='?') continue; /* we simply ignore uninteresting options */ else if (c=='f') { /* pass the intended envelope sender */ fprintf(out,"X-Kuvert-From: %s\n",optarg); } else if (c=='t') { /* no recipients given, so we don't need to pass any recips */ norecips=1; } } if (!norecips && optind or enqueues it for L for further processing. kuvert_submit should be called by your MUA instead of your usual MTA to enable kuvert to intercept and process the outgoing mails. Please see your MUA's documentation about how to override the MTA to be used. Kuvert_submit transparently invokes C directly if it cannot find a ~/.kuvert configuration file, or if the -bv option is given. Otherwise, it enqueues the email in the queue directory specified in the configuration file. If that fails or if the configuration file is invalid, kuvert_submit prints an error message to STDERR and terminates with exit code 1. On successful submission, kuvert_submit terminates with exit code 0. Kuvert_submit also logs messages to syslog with the facility "mail". =head1 OPTIONS If it runs the MTA directly then kuvert_submit passes all options through to /usr/sbin/sendmail. Otherwise, it ignores all options except -f and -t (and -bv which triggers a direct sendmail pass-through). =over =item -f Sets the envelope sender. Kuvert_submit passes this on to kuvert. =item -t Tells an MTA to use the recipients in the mail instead of any commandline arguments. If -t is not given then kuvert_submit passes the recipients from the commandline on to kuvert. =back =head1 FILES =over =item ~/.kuvert The configuration file read by kuvert and kuvert_submit. If not present, kuvert_submit calls /usr/sbin/sendmail directly. =item ~/.kuvert_queue The default queue directory. =back =head1 SEE ALSO L =head1 AUTHOR Alexander Zangerl =head1 COPYRIGHT AND LICENCE copyright 1999-2008 Alexander Zangerl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. =cut kuvert/plainAUTH.pm0000644000000000000000000000762613163143052011420 0ustar package Net::Server::Mail::ESMTP::plainAUTH; use strict; use base qw(Net::Server::Mail::ESMTP::Extension); use MIME::Base64; use vars qw( $VERSION ); $VERSION = '1.1'; # the following are required by nsme::extension # but not documented :( sub init { my ($self,$parent)=@_; $self->{AUTH}=(); return $self; } # the smtp operations we add sub verb { return ( [ 'AUTH' => \&handle_auth, ],); } # what to add to the esmtp capabilities response sub keyword { return 'AUTH LOGIN PLAIN'; } # what options to allow for mail from: auth sub option { return (['MAIL', 'AUTH' => sub { return; }]); } # and the actual auth handler sub handle_auth { my ($self,$args)=@_; my ($method,$param); $args=~/^(LOGIN|PLAIN|login|plain)\s*(.*)$/ && (($method,$param)=(uc($1),$2)); if ($self->{AUTH}->{active}) { delete $self->{AUTH}->{active}; $self->reply(535, "Authentication phases mixed up."); return undef; # if rv given, server shuts conn! } elsif ($self->{AUTH}->{completed}) { $self->reply(504,"Already authenticated."); return undef; } elsif (!$method) { $self->reply(501,"Unknown authentication method."); return undef; } $self->{AUTH}->{active}=$method; if ($param eq '*') { delete $self->{AUTH}->{active}; $self->reply(501, "Authentication cancelled."); return undef; } if ($method eq 'PLAIN') { if ($param) # plain: immediate with args { my (undef,$user,$pwd)=split(/\0/,decode_base64($param),3); if (!$user) { delete $self->{AUTH}->{active}; $self->reply(535, "5.7.8 Authentication failed."); return undef; } return run_callback($self,$user,$pwd); } else # plain: or empty challenge and then response { $self->reply(334," "); # undocumented but crucial: direct stuff to this method $self->next_input_to(\&process_response); return undef; } } elsif ($method eq 'LOGIN') { # login is always two challenges $self->reply(334, "VXNlcm5hbWU6"); # username $self->next_input_to(\&process_response); return undef; } } # runs user-supplied callback on username and password # responds success if callback succeeds # sets complete if ok, clears active either way sub run_callback { my ($self,$user,$pass)=@_; my $ok; my $ref=$self->{callback}->{AUTH}; if (ref $ref eq 'ARRAY' && ref $ref->[0] eq 'CODE') { my $c=$ref->[0]; $ok=&$c($self,$user,$pass); } if ($ok) { $self->reply(235, "Authentication successful"); $self->{AUTH}->{completed}=1; } else { $self->reply(535,"Authentication failed."); } delete $self->{AUTH}->{active}; return undef; } # deals with any response, based on active method sub process_response { my ($self,$args)=@_; if (!$self->{AUTH}->{active} || $self->{AUTH}->{completed}) { delete $self->{AUTH}->{active}; $self->reply(535, "Authentication phases mixed up."); return undef; } if (!$args) { delete $self->{AUTH}->{active}; $self->reply(535, "5.7.8 Authentication failed."); return undef; } if ($self->{AUTH}->{active} eq "PLAIN") { # plain is easy: only one response containing everything my (undef,$user,$pwd)=split(/\0/,decode_base64($args),3); if (!$user) { delete $self->{AUTH}->{active}; $self->reply(535, "5.7.8 Authentication failed."); return undef; } return run_callback($self,$user,$pwd); } elsif ($self->{AUTH}->{active} eq "LOGIN") { # uglier: two challenges for username+password my ($input)=split(/\0/,decode_base64($args)); # is this the second time round? if ($self->{AUTH}->{user}) { return run_callback($self,$self->{AUTH}->{user},$input); } else { # nope, first time: save username and challenge # for password $self->{AUTH}->{user}=$input; $self->reply(334, "UGFzc3dvcmQ6"); # password $self->next_input_to(\&process_response); return undef; } } else { delete $self->{AUTH}->{active}; $self->reply(535, "Authentication mixed up."); return undef; } } 1;