summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--GPL339
-rw-r--r--Makefile30
-rw-r--r--README90
-rw-r--r--THANKS7
-rw-r--r--debian/NEWS38
-rw-r--r--debian/changelog316
-rw-r--r--debian/compat1
-rw-r--r--debian/control19
-rw-r--r--debian/copyright22
-rw-r--r--debian/kuvert.docs1
-rw-r--r--debian/kuvert.examples1
-rwxr-xr-xdebian/rules64
-rw-r--r--dot-kuvert77
-rwxr-xr-xkuvert2133
-rw-r--r--kuvert_submit.c249
-rw-r--r--kuvert_submit.pod88
-rw-r--r--plainAUTH.pm184
18 files changed, 3660 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2b9e40e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+kuvert_submit
diff --git a/GPL b/GPL
new file mode 100644
index 0000000..e77696a
--- /dev/null
+++ b/GPL
@@ -0,0 +1,339 @@
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ 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.
+
+ <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 Library General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3e060bc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+# well, a simpler makefile is hardly imaginable...
+DESTDIR=
+
+# the version number of the package
+VERSION=$(shell sed -n '1s/^.*(\(.*\)).*$$/\1/p' debian/changelog)
+
+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
+ install -d $(DESTDIR)/usr/bin $(DESTDIR)/usr/share/man/man1 \
+ $(DESTDIR)/usr/share/perl5/Net/Server/Mail/ESMTP/
+ install kuvert_submit $(DESTDIR)/usr/bin
+# fix the version number
+ sed 's/INSERT_VERSION/$(VERSION)/' kuvert > kuvert.tmp
+ install kuvert.tmp $(DESTDIR)/usr/bin/kuvert
+ -rm kuvert.tmp
+ install plainAUTH.pm $(DESTDIR)/usr/share/perl5/Net/Server/Mail/ESMTP/
+ pod2man --center="User Commands" -r Mail kuvert $(DESTDIR)/usr/share/man/man1/kuvert.1
+ pod2man --center="User Commands" -r Mail kuvert_submit.pod $(DESTDIR)/usr/share/man/man1/kuvert_submit.1
+
+test:
+ echo $(VERSION)
diff --git a/README b/README
new file mode 100644
index 0000000..73a2138
--- /dev/null
+++ b/README
@@ -0,0 +1,90 @@
+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.004+, gpg and a raft of perl modules:
+MIME::Parser, Mail::Address, Net::SMTPS, Sys::Hostname, Net::Server::Mail,
+Authen::SASL, IO::Socket::INET, Filehandle, File::Slurp, File::Temp, Fcntl
+and Time::HiRes.
+some of those are part of a standard perl intall, others you'll have to
+get from your nearest CPAN archive and install.
+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, <az@snafu.priv.at>.
+
+The original source can always be found at:
+ http://www.snafu.priv.at/kuvert/
+
+Copyright (C) 1999-2013 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
+
+
+
+
diff --git a/THANKS b/THANKS
new file mode 100644
index 0000000..7a4c89a
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,7 @@
+My thanks go to
+
+Robert Bihlmeyer <robbe@orcus.priv.at>
+Norbert Preining <preining@logic.tuwien.ac.at>
+Robert Waldner <waldner@waldner.priv.at>
+
+for valuable hints and suggestions regarding this piece of software. \ No newline at end of file
diff --git a/debian/NEWS b/debian/NEWS
new file mode 100644
index 0000000..542b830
--- /dev/null
+++ b/debian/NEWS
@@ -0,0 +1,38 @@
+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 <az@debian.org> 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 <az@debian.org> Fri, 4 Nov 2005 16:43:14 +1000
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..fed7ef4
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,316 @@
+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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> Mon, 25 Nov 2013 20:44:19 +1000
+
+kuvert (2.0.8) unstable; urgency=low
+
+ * modified rules to support hardening build flags
+
+ -- Alexander Zangerl <az@debian.org> Tue, 24 Sep 2013 13:32:35 +1000
+
+kuvert (2.0.7) unstable; urgency=low
+
+ * added timeout for gpg invocations
+
+ -- Alexander Zangerl <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> Mon, 16 Mar 2009 17:01:24 +1000
+
+kuvert (2.0.1) unstable; urgency=low
+
+ * fixed generation of default queue/tempdirs
+
+ -- Alexander Zangerl <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> Sat, 23 Jun 2007 12:39:05 +1000
+
+kuvert (1.1.11) unstable; urgency=low
+
+ * lifted standards version
+
+ -- Alexander Zangerl <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> Sat, 26 Feb 2005 08:11:26 +1000
+
+kuvert (1.1.8) unstable; urgency=low
+
+ * lifted standards version
+ * added homepage to description
+
+ -- Alexander Zangerl <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> Fri, 25 Apr 2003 17:34:18 +1000
+
+kuvert (1.1.3) unstable; urgency=low
+
+ * standards version lifted, debhelper cleanup
+
+ -- Alexander Zangerl <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> Sun, 28 Apr 2002 01:50:15 +1000
+
+kuvert (1.0.11) unstable; urgency=high
+
+ * fixed typo that trashed handling of -force
+
+ -- Alexander Zangerl <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@debian.org> 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 <az@snafu.priv.at> 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 <az@snafu.priv.at> 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 <az@snafu.priv.at> 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 <az@snafu.priv.at> 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 <az@snafu.priv.at> Sun, 21 Oct 2001 23:21:16 +1000
+
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..45a4fb7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+8
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..67222a4
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,19 @@
+Source: kuvert
+Section: mail
+Priority: extra
+Maintainer: Alexander Zangerl <az@debian.org>
+Build-Depends: debhelper (>= 8), dpkg-dev (>= 1.16.1~)
+Standards-Version: 3.9.5.0
+
+Package: kuvert
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, gnupg (>= 1.0.6), sendmail | mail-transport-agent, libnet-smtps-perl, ${perl:Depends}, libmailtools-perl, libmime-tools-perl, libfile-slurp-perl, libnet-server-mail-perl, libauthen-sasl-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.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..247a72a
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,22 @@
+This is kuvert, written and maintained by Alexander Zangerl <az@debian.org>
+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-2001 Alexander Zangerl <az@snafu.priv.at>
+
+ 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 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.
diff --git a/debian/kuvert.docs b/debian/kuvert.docs
new file mode 100644
index 0000000..e845566
--- /dev/null
+++ b/debian/kuvert.docs
@@ -0,0 +1 @@
+README
diff --git a/debian/kuvert.examples b/debian/kuvert.examples
new file mode 100644
index 0000000..1a0a8d6
--- /dev/null
+++ b/debian/kuvert.examples
@@ -0,0 +1 @@
+dot-kuvert \ No newline at end of file
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..56022f9
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,64 @@
+#!/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
diff --git a/dot-kuvert b/dot-kuvert
new file mode 100644
index 0000000..007292c
--- /dev/null
+++ b/dot-kuvert
@@ -0,0 +1,77 @@
+# ~/.kuvert: example configuration file for kuvert v2
+
+# options are given without leading whitespace
+
+# which key to sign with by default
+defaultkey 0x1234abcd
+
+# 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
+
+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
+
diff --git a/kuvert b/kuvert
new file mode 100755
index 0000000..c61ad31
--- /dev/null
+++ b/kuvert
@@ -0,0 +1,2133 @@
+#!/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-2014 Alexander Zangerl <az@snafu.priv.at>
+#
+# 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::SMTPS; # for sending via smtp, which ssl
+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::Temp qw(:mktemp);
+use Fcntl qw(:flock);
+use Time::HiRes;
+
+# 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
+
+# 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");
+
+# configuration directives
+my (%config,$debug,%email2key);
+
+my %options;
+if (!getopts("dork",\%options) || @ARGV)
+{
+ die "usage: $progname [-d] [-o] [-r|-k]
+-k: kill running $progname daemon
+-d: debug mode
+-r: reload keyrings and configfile
+-o: one-shot mode, run queue once and exit
+This is: $progname $version.\n";
+}
+$debug=$options{"d"};
+
+# now handle the kill/reload stuff
+my $piddir=($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp");
+my $pidname="$progname.$<";
+
+# kill a already running process
+# TERM for kill or HUP for rereading
+my $pidf="$piddir/$pidname.pid";
+if ($options{"k"} || $options{"r"})
+{
+ my $sig=($options{"r"}?'USR1':'TERM');
+ my $ssig='TERM'; # the smtp listener must die
+ my $pidf="$piddir/$pidname.pid";
+
+ die("no pid file found, can't signal any $progname\n")
+ if (!-r $pidf);
+ my @pids=read_file($pidf);
+ for my $p (@pids)
+ {
+ chomp $p;
+ $p=~s/[^0-9]//g; # only numbers
+ # fixme: this is linux-centric, should be replaced
+ # with proc::processtable
+ my $fn="/proc/$p/cmdline";
+ if (-r $fn && (my $n=read_file($fn))=~/^$progname/)
+ {
+ my $s=($n=~/^$listenername/?$ssig:$sig);
+ dlogit("sending sig $s to $p");
+ logit("can't send signal to process $p: $!\n")
+ if (!kill($s,$p));
+ }
+ }
+ unlink($pidf) if ($options{k}); # remove the pidfile on kills
+ exit 0;
+}
+chdir("/");
+
+# now do the pidfile checking dance
+if (-f "$pidf")
+{
+ open(PIDF,"+<$pidf") || &die("can't rw-open $pidf: $!\n");
+}
+else
+{
+ open(PIDF,">$pidf") || &die("can't w-open $pidf: $!\n");
+}
+die("can't lock $pidf: $!\n") if (!flock(PIDF,LOCK_NB|LOCK_EX));
+my @others=<PIDF>;
+my @badones;
+for my $p (@others)
+{
+ chomp $p;
+ $p=~s/[^0-9]//g; # only numbers
+ # fixme: this is linux-centric, should be replaced
+ # with proc::processtable
+ if (-r "/proc/$p/cmdline"
+ && (my $n=read_file("/proc/$p/cmdline"))=~/^$progname/)
+ {
+ push @badones,$p;
+ }
+}
+die("other instance(s) with pids ".join(", ",@badones)." are running\n")
+ if (@badones);
+# rewind to ready it for writing
+seek(PIDF,0,'SEEK_SET');
+
+die("no configuration file exists. See $progname(1) for details.\n")
+ if (!-e $rcfile);
+
+dlogit("reading config file");
+# read in the config, setup dirs, logging, defaultkey etc.
+%config=&read_config;
+# log startup after config is read and logging prefs are known
+logit("$progname version $version starting");
+
+# 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;
+ # we, parent, update the pidfile with mailserver pid
+ print PIDF "$pid\n";
+}
+
+# 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
+ }
+}
+print PIDF "$$\n";
+close PIDF; # clears the lock
+
+# make things clean and ready. we're in sole command now.
+cleanup($config{tempdir},0);
+%email2key=&read_keyring;
+
+# let's use one parser object only;
+my $parser = MIME::Parser->new()
+ || bailout("can't create mime parser object: $!");
+
+# dump mime object to tempdir
+$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("Problem with $config{queuedir}/$file",
+"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
+Please note that processing may have worked for SOME recipients already!\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);
+
+ # unlock the file
+ bailout("problem closing $config{queuedir}/$file: $!")
+ if (!close(FH));
+ }
+ closedir(D);
+ &handle_term("oneshot mode") if ($options{o});
+ sleep($config{interval});
+}
+
+
+# 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;
+ 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{"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;
+ 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{"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
+sub process_file
+{
+ my ($fh,$file)=@_;
+
+ my $in_ent;
+ eval { $in_ent=$parser->parse(\$fh); };
+ return ("parsing $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 the from
+ 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=([0-9a-fA-FxX]+)/)
+ {
+ $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)\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.")
+ 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);
+
+ # 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 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" or key id
+sub findaction
+{
+ my ($override,$normalref,$bccref)=@_;
+ my(%actions,%specialkeys,$groupfallback);
+
+ # address lookup in configured overrides
+ 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.
+ if ($actions{$addr}=~/^fallback/)
+ {
+ # group fallback is relevant for normal recipients only
+ $groupfallback||=($actions{$addr} eq "fallback-all")
+ if (!grep($_->[0] eq $addr,@{$bccref}));
+ $actions{$addr}=$specialkeys{$addr}||$email2key{$addr}||"signonly";
+ }
+ }
+
+ # 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..." <an@addre.ss>
+ 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;
+ %email2key=&read_keyring;
+ # restart mailserver if required
+ # also update pidfile
+ if ($config{"ma-user"} && $config{"ma-pass"} && $config{"maport"})
+ {
+ # fork off the smtp-to-queue daemon
+ my $pid=&start_mailserver;
+ open(PIDF,">$pidf") || &bailout("can't w-open $pidf: $!\n");
+ print PIDF "$$\n$pid\n";
+ close(PIDF);
+ }
+}
+
+# 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);
+
+ opendir(F,$what) || bailout("cant opendir $what: $!");
+ foreach $name (readdir(F))
+ {
+ next if ($name =~ /^\.{1,2}$/o);
+ (-d "$what/$name")?&cleanup("$what/$name",1):
+ (unlink("$what/$name") || bailout("cant unlink $what/$name: $!"));
+ }
+ closedir(F);
+ $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",
+ tempdir=>($ENV{'TMPDIR'}?$ENV{'TMPDIR'}:"/tmp")."/kuvert.$username.$$",
+ alwaystrust=>undef,
+ 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=<F>;
+ 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)\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;
+ }
+ # 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)$/);
+ $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");
+ }
+ }
+ close F;
+
+ # 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 id-to-key hash, bailout on error
+sub read_keyring
+{
+ my %badcauses=('i'=>'invalid, no selfsig','d'=>'disabled',
+ 'r'=>'revoked','e'=>'expired');
+ my %id2key;
+
+ logit("reading keyring...");
+ my $tf="$config{tempdir}/subproc";
+
+ my @tmp=`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");
+
+ my ($lastkey,$lasttype);
+ foreach (@tmp)
+ {
+ my @info=split(/:/);
+ # only public keys and uids are of interest
+ next if ($info[0] ne "pub" && $info[0] ne "uid");
+ $info[4] =~ s/^.{8}//; # truncate key-id
+
+ $info[9] =~ s/\\x3a/:/g; # re-insert colons, please
+
+ # remember the email address
+ # if no address given: remember this key
+ # but go on to the uid's to get an email address to
+ # work with
+ my $name;
+ if ($info[9] =~ /(\s|<)([^\s<]+\@[^\s>]+)>?/)
+ {
+ $name=lc($2);
+ }
+ # check the key: public part or uid?
+ if ($info[0] eq "pub")
+ {
+ # lets associate this key with the current email address
+ # if an address is known
+ $lastkey=$info[4];
+
+ if ($name)
+ {
+ # ignore expired, revoked and other bad keys
+ if (defined $badcauses{$info[1]})
+ {
+ &logit("ignoring key 0x$info[4], reason: "
+ .$badcauses{$info[1]});
+ next;
+ }
+
+ $id2key{$name}="0x$lastkey";
+ dlogit("got key 0x$lastkey type $info[3] for $name");
+ }
+ else
+ {
+ dlogit("saved key 0x$lastkey, no address known yet");
+ }
+ next;
+ }
+ else
+ {
+ # uid: associate the current address with the key
+ # given in the most recent public key line
+ if ($name)
+ {
+ # ignore expired, revoked and other bad keys
+ if (defined $badcauses{$info[1]})
+ {
+ &logit("ignoring uid $name for 0x$lastkey, "
+ ."reason: ".$badcauses{$info[1]});
+ next;
+ }
+
+ $id2key{$name}="0x$lastkey";
+ dlogit("got key (uid) 0x$lastkey for $name");
+ }
+ else
+ {
+ dlogit("ignoring uid without valid address");
+ }
+ }
+ }
+ return %id2key;
+}
+
+# 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;
+
+ my $s=Net::SMTPS->new( $config{msserver}, Port => $config{msport},
+ Hello => $dom,
+ doSSL => $config{ssl},
+ SSL_key_file => $config{"ssl-key"},
+ SSL_cert_file => $config{"ssl-cert"},
+ SSL_ca_file => $config{"ssl-ca"} );
+ return("cannot connect to mail server ".$config{msserver}.": $!")
+ if (!$s);
+
+ # 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("cant 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/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,hashalg) if ok, (1,undef) if bad passphrase,
+#(2,errorinfo) otherwise
+sub sign_encrypt
+{
+ my ($signkey,$infile,$outfile,@recips)=@_;
+ my @cmd=qw(gpg -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,"Error: 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 (<F>)
+ {
+ if ($l=~/^\[GNUPG:\] NEED_PASSPHRASE ([a-fA-F0-9]+) ([a-fA-F0-9]+) \d+ \d+$/)
+ {
+ $precmd=sprintf($config{"query-secret"},"0x".substr($2,8));
+ 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, "Error: 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=<F>;
+ 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,"Error: gpg terminated with $?",
+ "Detailed error messages:",@output);
+ }
+
+ if (my @infoline = grep(/^\[GNUPG:\] SIG_CREATED/, @output))
+ {
+ # output format:
+ # [GNUPG:] SIG_CREATED <type> <pubkey algo> <hash algo> <class> <timestamp> <key fpr>
+ my @infoparts = split(/\s+/,$infoline[0]);
+ my $hashname = $hashalgos{$infoparts[4]};
+ return (undef,$hashname) if defined $hashname;
+ }
+
+ # cheap catch-all, including unknown hash algo identifiers
+ return (2, "Error: gpg did not complete the operation",
+ "Detailed error messages:",@output);
+}
+
+# logs the argument strings to syslog and/or the logfile
+# uses global %config
+# 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);
+ }
+
+ print { $config{logfh} } scalar(localtime)." ".join("\n\t",@msgs)."\n";
+ }
+
+ if ($config{syslog})
+ {
+ setlogsock('unix');
+ openlog($progname,"pid,cons",$config{syslog});
+ syslog("notice",join("\n",@msgs));
+ closelog;
+ }
+}
+
+# debug log to stderr
+sub dlogit
+{
+ print STDERR join("\n",@_)."\n" 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;
+ close STDIN;
+ close PIDF; # clears the inherited lock
+ 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();
+ }
+}
+
+sub check_auth
+{
+ my ($session,$user,$pwd)=@_;
+ return ($user eq $config{'ma-user'} and $pwd eq $config{'ma-pass'});
+}
+
+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;
+}
+
+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;
+}
+
+sub cont_mail
+{
+ my ($session,$dr)=@_;
+ print {$session->{DATA}->{qfh}} $$dr;
+ undef $$dr;
+ return 1;
+}
+
+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]
+
+=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.
+
+=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<kuvert_submit(1)>) 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<override> 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<overrides> 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<ignored>. 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<without further processing>. (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<overrides> 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<keyctl(1)>).
+
+=head2 How Kuvert Decides What (Not) To Do
+
+For each recipient, kuvert can be told to apply one of
+four different actions:
+
+=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.
+
+=item fallback-all
+
+The email is encrypted and signed if keys are available for B<all>
+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.)
+
+=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/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" <user@some.host>",
+kuvert strips the quoted part and overrides the addressee's
+action with someaction.
+
+=item 5.
+
+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 6.
+
+Recipients which are given in a Bcc: header are always treated independently
+and separately from all others:
+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<last> 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" <user@some.host>
+
+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" <user@some.host>
+
+The signing key can be overridden in a similar fashion: if the From
+address contains a "key=B<keyid>" 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"
+or "fallback-all"; see section L</"How Kuvert Decides What (Not) To Do">
+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 <hexkeyid>
+
+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 <syslog facility or blank>
+
+Whether kuvert should use syslog for logging, and if so, what facility to
+use. Default: nothing. This is independent of the logfile option below.
+
+=item logfile <path or blank>
+
+Whether kuvert should write log messages to a file, appending to it.
+Default: not set. This is independent of the syslog option above.
+
+=item mail-on-error <email address or blank>
+
+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 <path>
+
+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 <path>
+
+Where kuvert stores temporary files. Default: a directory called
+kuvert.<username>.<pid> 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 <boolean>
+
+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 <boolean>
+
+Whether kuvert should include an explanatory preamble in the generated
+MIME mail. Default: true
+
+=item interval <number>
+
+This sets the queue checking interval in seconds. Default: 60 seconds.
+
+=item msserver <hostname-or-address>
+
+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 <portnumber>
+
+The TCP port on which the Mail Submission Server listens. Default: 587.
+Ignored if msserver is not set.
+
+=item ssl <string>
+
+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 <client cert path.pem>
+
+=item ssl-key <client key path.pem>
+
+=item ssl-ca <ca cert path.pem>
+
+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 <username>
+
+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 <password>
+
+The password for SMTP authentication. Ignored if msserver or msuser are not set.
+
+=item mspass-from-query-secret <boolean>
+
+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 <program-path and args>
+
+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<http://refspecs.linux-foundation.org/LSB_2.0.0/LSB-Core/LSB-Core.html#BASELIB-SENDMAIL-1>).
+
+=item can-detach <boolean>
+
+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 <portnumber>
+
+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 <username>
+
+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 <password>
+
+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 <hexkeyid>
+
+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</"Key Selection">.
+
+=item defaultaction <action>
+
+Which action is to be taken if no overrides are found for a recipient.
+Default: none. See section L</"How Kuvert Decides What (Not) To Do"> for recognized actions.
+
+=item alwaystrust <boolean>
+
+Whether gpg should be told to trust all keys for encryption or not.
+Default: false.
+
+=item use-agent <boolean>
+
+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 <program-path and args with %s>
+
+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 <program-path and args with %s>
+
+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.E<lt>uidE<gt>
+
+holds the pid of a running kuvert daemon.
+
+=back
+
+=head1 SEE ALSO
+
+L<gpg(1)>, L<kuvert_submit(1)>, RFC3156, RFC4880, RFC2015
+
+=head1 AUTHOR
+
+Alexander Zangerl <az@snafu.priv.at>
+
+=head1 COPYRIGHT AND LICENCE
+
+copyright 1999-2014 Alexander Zangerl <az@snafu.priv.at>
+
+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
+
diff --git a/kuvert_submit.c b/kuvert_submit.c
new file mode 100644
index 0000000..dc69238
--- /dev/null
+++ b/kuvert_submit.c
@@ -0,0 +1,249 @@
+/*
+ *
+ * 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 <az+kuvert@snafu.priv.at>
+ *
+ * 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 <stdio.h>
+#include <pwd.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#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;i<argc;++i)
+ {
+ if (!strncmp(argv[i],"-bv",3))
+ {
+ testmode=1;
+ syslog(LOG_INFO,"-bv argument present, running sendmail.");
+ break;
+ }
+ }
+
+ if (!testmode)
+ {
+ /* look for config file in $HOME */
+ pwentry=getpwuid(getuid());
+ if (!pwentry)
+ BAILOUT("getpwuid failed: %s",strerror(errno));
+
+ /* open and scan the conffile for an queue-file definition
+ if there is no conffile, kuvert wont work ever */
+ if (snprintf(filen,sizeof(filen),"%s%s",pwentry->pw_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);
+ c<argc;
+ spaceleft-=strlen(argv[c++]))
+ {
+ if (spaceleft <= 0)
+ BAILOUT("overlong command line, suspicious.",NULL);
+ strncat(buffer,argv[c],spaceleft);
+ --spaceleft && c<argc-1 && strcat(buffer," ");
+ }
+
+ syslog(LOG_INFO,"executing MTA '%s' directly",buffer);
+ execv(FALLBACKMTA,argv);
+ /* must not reach here */
+ BAILOUT("execv "FALLBACKMTA" failed: %s",strerror(errno));
+ }
+
+ /* otherwise queue the stuff for kuvert,
+ first check queuedir and create if missing */
+ if (!dirnp)
+ {
+ if(snprintf(dirn,sizeof(dirn),"%s%s",pwentry->pw_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<argc)
+ {
+ fprintf(out,"X-Kuvert-To: ");
+ for(c=optind;c<argc;++c)
+ {
+ fprintf(out,"%s%s",argv[c],(c<argc-1?", ":"\n"));
+ }
+ }
+ fflush(out);
+
+ /* now finally put the data in the queuefile */
+ do
+ {
+ res=fread(buffer,1,BUFLEN,stdin);
+ if (!res && ferror(stdin))
+ BAILOUT("fread failure: %s",strerror(errno));
+ if (fwrite(buffer,1,res,out)!=res && ferror(out))
+ BAILOUT("fwrite failure: %s",strerror(errno));
+ }
+ while (res==BUFLEN);
+
+ if (fflush(out)==EOF)
+ BAILOUT("fflush failed: %s",strerror(errno));
+ if (flock(fileno(out),LOCK_UN))
+ {
+ BAILOUT("flock (unlock) failed: %s",strerror(errno));
+ }
+ if (fclose(out)==EOF)
+ BAILOUT("fclose failed: %s",strerror(errno));
+ return 0;
+}
diff --git a/kuvert_submit.pod b/kuvert_submit.pod
new file mode 100644
index 0000000..5f14145
--- /dev/null
+++ b/kuvert_submit.pod
@@ -0,0 +1,88 @@
+# -*- mode: perl;-*-
+=pod
+
+=head1 NAME
+
+kuvert_submit - MTA wrapper for mail submission to kuvert(1)
+
+=head1 SYNOPSIS
+
+kuvert_submit [sendmail-options] [recipients...]
+
+=head1 DESCRIPTION
+
+Kuvert_submit submits an email either directly to L<sendmail(8)> or
+enqueues it for L<kuvert(1)> 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</usr/sbin/sendmail> 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 <emailaddress>
+
+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<kuvert(1)>
+
+=head1 AUTHOR
+
+Alexander Zangerl <az@snafu.priv.at>
+
+=head1 COPYRIGHT AND LICENCE
+
+copyright 1999-2008 Alexander Zangerl <az@snafu.priv.at>
+
+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
+
diff --git a/plainAUTH.pm b/plainAUTH.pm
new file mode 100644
index 0000000..0de97a8
--- /dev/null
+++ b/plainAUTH.pm
@@ -0,0 +1,184 @@
+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.0';
+
+# 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)\s*(.*)$/ && (($method,$param)=($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;