[git] GpgOL - branch, async-enc, updated. gpgol-2.0.6-45-g0853344
by Andre Heinecke
cvs at cvs.gnupg.org
Thu Feb 22 09:58:19 CET 2018
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GnuPG extension for MS Outlook".
The branch, async-enc has been updated
via 0853344d1dcf520ea657d7208661214f69b59dad (commit)
from 9017cf6fb9c78a83d9d5d08d886c41878444369b (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 0853344d1dcf520ea657d7208661214f69b59dad
Author: Andre Heinecke <aheinecke at intevation.de>
Date: Thu Feb 22 09:45:46 2018 +0100
Implement handling of WKS-Confirmation mails
* src/common_indep.h (msgtype_t): New messagtype for WKS_Confirm
mails.
* src/mail.cpp (Mail::decrypt_verify): Handle new message type.
(Mail::is_smime): Clarify an error message.
* src/mapihelp.cpp
(change_message_class_ipm_note_smime_multipartsigned),
(string_to_type, mapi_change_message_class),
(mapi_get_message_content_type): Handle
wks confirmation mails.
* src/message.cpp: Default / ignore new message type in old code.
* src/wks-helper.cpp: Lots of changes.
diff --git a/src/common_indep.h b/src/common_indep.h
index 10b14d6..adfc8bf 100644
--- a/src/common_indep.h
+++ b/src/common_indep.h
@@ -98,7 +98,8 @@ typedef enum
MSGTYPE_GPGOL_OPAQUE_SIGNED,
MSGTYPE_GPGOL_OPAQUE_ENCRYPTED,
MSGTYPE_GPGOL_CLEAR_SIGNED,
- MSGTYPE_GPGOL_PGP_MESSAGE
+ MSGTYPE_GPGOL_PGP_MESSAGE,
+ MSGTYPE_GPGOL_WKS_CONFIRMATION
}
msgtype_t;
diff --git a/src/mail.cpp b/src/mail.cpp
index 00b42d3..e9a8d59 100644
--- a/src/mail.cpp
+++ b/src/mail.cpp
@@ -37,6 +37,7 @@
#include "gpgolstr.h"
#include "windowmessages.h"
#include "mlang-charset.h"
+#include "wks-helper.h"
#include <gpgme++/configuration.h>
#include <gpgme++/tofuinfo.h>
@@ -841,9 +842,22 @@ Mail::decrypt_verify()
}
set_uuid ();
m_processed = true;
+
+
/* Insert placeholder */
char *placeholder_buf;
- if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
+ if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
+ {
+ gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
+ decrypt_template,
+ "OpenPGP",
+ _("Pubkey directory confirmation"),
+ _("This is a confirmation request to publish your Pubkey in the "
+ "directory for your domain.\n\n"
+ "<p>If you did not request to publish your Pubkey in your providers "
+ "directory, simply ignore this message.</p>\n"));
+ }
+ else if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
is_smime() ? "S/MIME" : "OpenPGP",
_("Encrypted message"),
@@ -877,6 +891,12 @@ Mail::decrypt_verify()
/* Do the actual parsing */
auto cipherstream = get_attachment_stream (m_mailitem, m_moss_position);
+ if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
+ {
+ WKSHelper::instance ()->handle_confirmation_read (this, cipherstream);
+ return 0;
+ }
+
if (!cipherstream)
{
log_debug ("%s:%s: Failed to get cipherstream.",
@@ -1504,7 +1524,8 @@ Mail::is_smime ()
}
else
{
- log_error ("Protocol in multipart signed mail.");
+ log_error ("%s:%s: No protocol in multipart / signed mail.",
+ SRCNAME, __func__);
}
xfree (proto);
xfree (ct);
diff --git a/src/mapihelp.cpp b/src/mapihelp.cpp
index a9c69c3..5ff9ad1 100644
--- a/src/mapihelp.cpp
+++ b/src/mapihelp.cpp
@@ -1047,7 +1047,7 @@ change_message_class_ipm_note_smime (LPMESSAGE message)
if (ct)
{
log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
- if (proto
+ if (proto
&& !strcmp (ct, "multipart/signed")
&& !strcmp (proto, "application/pgp-signature"))
{
@@ -1140,6 +1140,10 @@ change_message_class_ipm_note_smime_multipartsigned (LPMESSAGE message)
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
}
+ else if (!strcmp (ct, "wks.confirmation.mail"))
+ {
+ newvalue = xstrdup ("IPM.Note.GpgOL.WKSConfirmation");
+ }
xfree (proto);
xfree (ct);
}
@@ -1283,6 +1287,8 @@ string_to_type (const char *s)
return MSGTYPE_GPGOL_CLEAR_SIGNED;
else if (!strcmp (s, ".PGPMessage"))
return MSGTYPE_GPGOL_PGP_MESSAGE;
+ else if (!strcmp (s, ".WKSConfirmation"))
+ return MSGTYPE_GPGOL_WKS_CONFIRMATION;
else
log_debug ("%s:%s: message class `%s' not supported",
SRCNAME, __func__, s-14);
@@ -1357,8 +1363,23 @@ mapi_change_message_class (LPMESSAGE message, int sync_override,
keep the SMIME; we need to change the SMIME part of the
class name so that Outlook does not process it as an
SMIME message. */
- newvalue = (char*)xmalloc (strlen (s) + 1);
- strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
+
+ char *tmp = change_message_class_ipm_note_smime_multipartsigned
+ (message);
+ /* This case happens even for PGP/MIME mails but that is ok
+ as we later fiddle out the protocol. But we have to
+ check if this is a WKS Mail now so that we can do the
+ special handling for that. */
+ if (tmp && !strcmp (tmp, "IPM.Note.GpgOL.WKSConfirmation"))
+ {
+ newvalue = tmp;
+ }
+ else
+ {
+ xfree (tmp);
+ newvalue = (char*)xmalloc (strlen (s) + 1);
+ strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
+ }
}
else if (!strcmp (s, "IPM.Note.SMIME.MultipartSigned"))
{
@@ -3038,6 +3059,16 @@ mapi_get_message_content_type (LPMESSAGE message,
length = (s - header_lines);
if (length && s[-1] == '\r')
length--;
+
+ if (!strncmp ("Wks-Phase: confirm", header_lines, length))
+ {
+ log_debug ("%s:%s: detected wks confirmation mail",
+ SRCNAME, __func__);
+ retstr = xstrdup ("wks.confirmation.mail");
+ rfc822parse_close (msg);
+ return retstr;
+ }
+
rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
header_lines = s+1;
}
diff --git a/src/mapihelp.h b/src/mapihelp.h
index 0355618..1f2f35c 100644
--- a/src/mapihelp.h
+++ b/src/mapihelp.h
@@ -136,5 +136,7 @@ int mapi_body_to_attachment (LPMESSAGE message);
char * mapi_get_uid (LPMESSAGE message);
#ifdef __cplusplus
}
+#include <string>
+std::string mapi_get_header (LPMESSAGE message);
#endif
#endif /*MAPIHELP_H*/
diff --git a/src/message.cpp b/src/message.cpp
index 8e63c0d..c33ad1b 100644
--- a/src/message.cpp
+++ b/src/message.cpp
@@ -135,6 +135,8 @@ message_incoming_handler (LPMESSAGE message, HWND hwnd, bool force)
retval = 2;
message_decrypt (message, msgtype, force, hwnd);
break;
+ default:
+ break;
}
return retval;
@@ -537,6 +539,7 @@ message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
"that S/MIME processing has been enabled."));
else
show_message (hwnd, _("This message has no signature."));
+ default:
return 0; /* Nothing to do. */
}
@@ -712,6 +715,7 @@ message_decrypt (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
is_opaque = 1;
break;
case MSGTYPE_GPGOL_PGP_MESSAGE:
+ default:
break;
}
diff --git a/src/wks-helper.cpp b/src/wks-helper.cpp
index 753de8d..6d9f9e2 100644
--- a/src/wks-helper.cpp
+++ b/src/wks-helper.cpp
@@ -23,8 +23,8 @@
#include "cpphelp.h"
#include "oomhelp.h"
#include "windowmessages.h"
-#include "overlay.h"
#include "mail.h"
+#include "mapihelp.h"
#include <map>
#include <sstream>
@@ -38,13 +38,16 @@
#define CHECK_MIN_INTERVAL (60 * 60 * 24 * 7)
+#define DEBUG_WKS 1
+
#undef _
#define _(a) utf8_gettext (a)
static std::map <std::string, WKSHelper::WKSState> s_states;
static std::map <std::string, time_t> s_last_checked;
+static std::map <std::string, std::pair <GpgME::Data *, Mail *> > s_confirmation_cache;
-static WKSHelper* singleton = NULL;
+static WKSHelper* singleton = nullptr;
GPGRT_LOCK_DEFINE (wks_lock);
@@ -99,6 +102,24 @@ WKSHelper::get_check_time (const std::string &mbox) const
return it->second;
}
+std::pair <GpgME::Data *, Mail *>
+WKSHelper::get_cached_confirmation (const std::string &mbox) const
+{
+ gpgrt_lock_lock (&wks_lock);
+ const auto it = s_confirmation_cache.find(mbox);
+ const auto dataEnd = s_confirmation_cache.end();
+
+ if (it == dataEnd)
+ {
+ gpgrt_lock_unlock (&wks_lock);
+ return std::make_pair (nullptr, nullptr);
+ }
+ auto ret = it->second;
+ s_confirmation_cache.erase (it);
+ gpgrt_lock_unlock (&wks_lock);
+ return ret;
+}
+
static std::string
get_wks_client_path ()
{
@@ -218,8 +239,8 @@ WKSHelper::start_check (const std::string &mbox, bool forced) const
log_debug ("%s:%s: WKSHelper starting check",
SRCNAME, __func__);
/* Start the actual work that can be done in a background thread. */
- CloseHandle (CreateThread (NULL, 0, do_check, strdup (mbox.c_str ()), 0,
- NULL));
+ CloseHandle (CreateThread (nullptr, 0, do_check, strdup (mbox.c_str ()), 0,
+ nullptr));
return;
}
@@ -239,23 +260,28 @@ static DWORD WINAPI
do_notify (LPVOID arg)
{
/** Wait till a message was sent */
- //Sleep (5000);
- do_in_ui_thread (WKS_NOTIFY, arg);
+ std::pair<char *, int> *args = (std::pair<char *, int> *) arg;
+
+ Sleep (args->second);
+ do_in_ui_thread (WKS_NOTIFY, args->first);
+ delete args;
return 0;
}
void
-WKSHelper::allow_notify () const
+WKSHelper::allow_notify (int sleepTimeMS) const
{
gpgrt_lock_lock (&wks_lock);
for (auto &pair: s_states)
{
- if (pair.second == NeedsPublish)
+ if (pair.second == ConfirmationSeen ||
+ pair.second == NeedsPublish)
{
- CloseHandle (CreateThread (NULL, 0, do_notify,
- strdup (pair.first.c_str ()), 0,
- NULL));
+ auto *args = new std::pair<char *, int> (strdup (pair.first.c_str()), sleepTimeMS);
+ CloseHandle (CreateThread (nullptr, 0, do_notify,
+ args, 0,
+ nullptr));
break;
}
}
@@ -272,11 +298,12 @@ WKSHelper::notify (const char *cBox) const
if (state == NeedsPublish)
{
if (gpgol_message_box (get_active_hwnd (),
- _("Your mail provider supports a key directory.\n\n"
- "Register your key in that directory to make\n"
- "it easier for others to send you encrypted mail.\n\n\n"
- "Register Key?"),
- _("GpgOL: Key directory available!"), MB_YESNO) == IDYES)
+ _("A Pubkey directory is available for your domain.\n\n"
+ "Register your Pubkey in that directory to make\n"
+ "it easy for others to send you encrypted mail.\n\n"
+ "It's secure and free!\n\n"
+ "Register automatically?"),
+ _("GpgOL: Pubkey directory available!"), MB_YESNO) == IDYES)
{
start_publish (mbox);
}
@@ -286,20 +313,20 @@ WKSHelper::notify (const char *cBox) const
}
return;
}
- else
+ if (state == ConfirmationSeen)
{
- log_debug ("%s:%s: Unhandled notify state: %i for '%s'",
- SRCNAME, __func__, state, cBox);
+ handle_confirmation_notify (mbox);
return;
}
+
+ log_debug ("%s:%s: Unhandled notify state: %i for '%s'",
+ SRCNAME, __func__, state, cBox);
+ return;
}
void
WKSHelper::start_publish (const std::string &mbox) const
{
-// Overlay (get_active_hwnd (),
-// std::string (_("Creating registration request...")));
-
log_debug ("%s:%s: Start publish for '%s'",
SRCNAME, __func__, mbox.c_str ());
@@ -357,21 +384,27 @@ WKSHelper::start_publish (const std::string &mbox) const
if (data.empty ())
{
gpgol_message_box (get_active_hwnd (),
- "WKS client failed to create publishing request.",
- _("GpgOL"),
+ mystderr.toString().c_str (),
+ _("GpgOL: Directory request failed"),
MB_OK);
return;
}
+#ifdef DEBUG_WKS
log_debug ("%s:%s: WKS client: returned '%s'",
SRCNAME, __func__, data.c_str ());
+#endif
- send_mail (data);
-
+ if (!send_mail (data))
+ {
+ gpgol_message_box (get_active_hwnd (),
+ _("You might receive a confirmation challenge from\n"
+ "your provider to finish the registration."),
+ _("GpgOL: Registration request sent!"), MB_OK);
+ }
return;
}
-
void
WKSHelper::update_state (const std::string &mbox, WKSState state) const
{
@@ -389,7 +422,7 @@ WKSHelper::update_state (const std::string &mbox, WKSState state) const
gpgrt_lock_unlock (&wks_lock);
}
-void
+int
WKSHelper::send_mail (const std::string &mimeData) const
{
std::istringstream ss(mimeData);
@@ -408,7 +441,7 @@ WKSHelper::send_mail (const std::string &mimeData) const
{
log_error ("%s:%s: Invalid mime data..",
SRCNAME, __func__);
- return;
+ return -1;
}
std::getline (ss, withoutHeaders, '\0');
@@ -427,21 +460,21 @@ WKSHelper::send_mail (const std::string &mimeData) const
{
log_error ("%s:%s: Failed to create mail for request.",
SRCNAME, __func__);
- return;
+ return -1;
}
if (put_oom_string (mail, "Subject", subject.c_str ()))
{
TRACEPOINT;
gpgol_release (mail);
- return;
+ return -1;
}
if (put_oom_string (mail, "To", to.c_str ()))
{
TRACEPOINT;
gpgol_release (mail);
- return;
+ return -1;
}
LPDISPATCH account = get_account_for_mail (from.c_str ());
@@ -462,9 +495,207 @@ WKSHelper::send_mail (const std::string &mimeData) const
last_mail->set_override_mime_data (mimeData);
last_mail->set_crypt_state (Mail::NeedsSecondAfterWrite);
- invoke_oom_method (mail, "Save", NULL);
- invoke_oom_method (mail, "Send", NULL);
-
+ if (invoke_oom_method (mail, "Save", nullptr))
+ {
+ // Should not happen.
+ log_error ("%s:%s: Failed to save mail.",
+ SRCNAME, __func__);
+ return -1;
+ }
+ if (invoke_oom_method (mail, "Send", nullptr))
+ {
+ log_error ("%s:%s: Failed to send mail.",
+ SRCNAME, __func__);
+ return -1;
+ }
log_debug ("%s:%s: Done send mail.",
SRCNAME, __func__);
+ return 0;
+}
+
+static void
+copy_stream_to_data (LPSTREAM stream, GpgME::Data *data)
+{
+ HRESULT hr;
+ char buf[4096];
+ ULONG bRead;
+ while ((hr = stream->Read (buf, 4096, &bRead)) == S_OK ||
+ hr == S_FALSE)
+ {
+ if (!bRead)
+ {
+ // EOF
+ return;
+ }
+ data->write (buf, (size_t) bRead);
+ }
+}
+
+void
+WKSHelper::handle_confirmation_notify (const std::string &mbox) const
+{
+ auto pair = get_cached_confirmation (mbox);
+ GpgME::Data *mimeData = pair.first;
+ Mail *mail = pair.second;
+
+ if (!mail)
+ {
+ log_debug ("%s:%s: Confirmation notify without cached mail.",
+ SRCNAME, __func__);
+ }
+
+ if (!mimeData)
+ {
+ log_error ("%s:%s: Confirmation notify without cached data.",
+ SRCNAME, __func__);
+ return;
+ }
+
+ /* First ask the user if he wants to confirm */
+ if (gpgol_message_box (get_active_hwnd (),
+ _("Confirm registration?"),
+ _("GpgOL: Pubkey directory confirmation"), MB_YESNO) != IDYES)
+ {
+ log_debug ("%s:%s: User aborted confirmation.",
+ SRCNAME, __func__);
+ delete mimeData;
+
+ /* Next time we read the confirmation we ask again. */
+ update_state (mbox, RequestSent);
+ return;
+ }
+
+ /* Do the confirmation */
+ const auto wksPath = get_wks_client_path ();
+
+ if (wksPath.empty())
+ {
+ TRACEPOINT;
+ return;
+ }
+
+ std::vector<std::string> args;
+
+ args.push_back (wksPath);
+ args.push_back (std::string ("--receive"));
+
+ // Spawn the process
+ auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
+ if (!ctx)
+ {
+ TRACEPOINT;
+ return;
+ }
+ GpgME::Data mystdout, mystderr;
+
+ char **cargs = vector_to_cArray (args);
+
+ GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char **> (cargs),
+ *mimeData, mystdout, mystderr,
+ GpgME::Context::SpawnNone);
+ release_cArray (cargs);
+
+ if (err)
+ {
+ log_debug ("%s:%s: WKS client spawn code: %i asString: %s",
+ SRCNAME, __func__, err.code(), err.asString());
+ return;
+ }
+ const auto data = mystdout.toString ();
+
+ if (data.empty ())
+ {
+ gpgol_message_box (get_active_hwnd (),
+ mystderr.toString().c_str (),
+ _("GpgOL: Confirmation failed"),
+ MB_OK);
+ return;
+ }
+
+#ifdef DEBUG_WKS
+ log_debug ("%s:%s: WKS client: returned '%s'",
+ SRCNAME, __func__, data.c_str ());
+#endif
+ if (!send_mail (data))
+ {
+ gpgol_message_box (get_active_hwnd (),
+ _("Your Pubkey can soon be retrieved from your domain."),
+ _("GpgOL: Request confirmed!"), MB_OK);
+ }
+
+ if (mail && Mail::is_valid_ptr (mail))
+ {
+ invoke_oom_method (mail->item(), "Delete", nullptr);
+ }
+
+ update_state (mbox, ConfirmationSent);
+}
+
+void
+WKSHelper::handle_confirmation_read (Mail *mail, LPSTREAM stream) const
+{
+ /* We get the handle_confirmation in the Read event. To do sending
+ etc. we have to move out of that event. For this we prepare
+ the data for later usage. */
+
+ if (!mail || !stream)
+ {
+ TRACEPOINT;
+ return;
+ }
+
+ /* Get the recipient of the confirmation mail */
+ char **recipients = mail->get_recipients ();
+
+ /* We assert that we have one recipient as the mail should have been
+ sent by the wks-server. */
+ if (!recipients || !recipients[0] || recipients[1])
+ {
+ log_error ("%s:%s: invalid recipients",
+ SRCNAME, __func__);
+ release_cArray (recipients);
+ gpgol_release (stream);
+ return;
+ }
+
+ std::string mbox = recipients[0];
+ release_cArray (recipients);
+
+ /* Prepare stdin for the wks-client process */
+
+ /* First we need to write the headers */
+ LPMESSAGE message = get_oom_base_message (mail->item());
+ if (!message)
+ {
+ log_error ("%s:%s: Failed to obtain message.",
+ SRCNAME, __func__);
+ gpgol_release (stream);
+ return;
+ }
+
+ const auto headers = mapi_get_header (message);
+ gpgol_release (message);
+
+ GpgME::Data *mystdin = new GpgME::Data();
+
+ mystdin->write (headers.c_str (), headers.size ());
+
+ /* Then the MIME data */
+ copy_stream_to_data (stream, mystdin);
+ gpgol_release (stream);
+
+ /* Then lets make sure its flushy */
+ mystdin->write (nullptr, 0);
+
+ /* And reset it to start */
+ mystdin->seek (0, SEEK_SET);
+
+ gpgrt_lock_lock (&wks_lock);
+ s_confirmation_cache.insert (std::make_pair (mbox, std::make_pair (mystdin, mail)));
+ gpgrt_lock_unlock (&wks_lock);
+
+ update_state (mbox, ConfirmationSeen);
+
+ /* Send the window message for notify. */
+ allow_notify (5000);
}
diff --git a/src/wks-helper.h b/src/wks-helper.h
index efd36f5..a6429f7 100644
--- a/src/wks-helper.h
+++ b/src/wks-helper.h
@@ -21,11 +21,22 @@
#include "config.h"
#include <string>
+#include "oomhelp.h"
+
+#include <utility>
+
+class Mail;
+namespace GpgME
+{
+ class Data;
+} // namespace GpgME
/** @brief Helper for web key services.
*
* Everything is public to make it easy to access data
* members from another windows thread. Don't mess with them.
+ *
+ * This is all a bit weird, don't look at it too much as it works ;-)
*/
class WKSHelper
{
@@ -39,9 +50,11 @@ public:
NotSupported, /* <-- WKS is not supported for this address */
Supported, /* <-- WKS is supported for this address */
NeedsPublish, /* <-- There was no key published for this address */
+ ConfirmationSeen, /* A confirmation request was seen for this mail addres. */
NeedsUpdate, /* <-- Not yet implemeted. */
RequestSent, /* <-- A publishing request has been sent. */
PublishDenied, /* <-- A user denied publishing. */
+ ConfirmationSent, /* <-- The confirmation response was sent. */
};
~WKSHelper ();
@@ -76,8 +89,8 @@ public:
/** Starts gpg-wks-client --create */
void start_publish (const std::string &mbox) const;
- /** Allow queueing a notification. */
- void allow_notify () const;
+ /** Allow queueing a notification after a sleepTime */
+ void allow_notify (int sleepTimeMS = 0) const;
/** Send a notification and start publishing accordingly */
void notify (const char *mbox) const;
@@ -88,9 +101,20 @@ public:
/** Update or insert a state in the static maps. */
void update_state (const std::string &mbox, WKSState state) const;
- /** Create / Build Mail */
- void send_mail (const std::string &mimeData) const;
+ /** Create / Build / Send Mail
+ returns 0 on success.
+ */
+ int send_mail (const std::string &mimeData) const;
+
+ /** Handle a confirmation mail read event */
+ void handle_confirmation_read (Mail *mail, LPSTREAM msgstream) const;
+
+ /** Handle the notifcation following the read. */
+ void handle_confirmation_notify (const std::string &mbox) const;
+ /** Get the cached confirmation data. Caller takes ownership of
+ the data object and has to delete it. It is removed from the cache. */
+ std::pair <GpgME::Data *, Mail *> get_cached_confirmation (const std::string &mbox) const;
private:
time_t get_check_time (const std::string &mbox) const;
-----------------------------------------------------------------------
Summary of changes:
src/common_indep.h | 3 +-
src/mail.cpp | 25 ++++-
src/mapihelp.cpp | 37 ++++++-
src/mapihelp.h | 2 +
src/message.cpp | 4 +
src/wks-helper.cpp | 301 ++++++++++++++++++++++++++++++++++++++++++++++-------
src/wks-helper.h | 32 +++++-
7 files changed, 359 insertions(+), 45 deletions(-)
hooks/post-receive
--
GnuPG extension for MS Outlook
http://git.gnupg.org
More information about the Gnupg-commits
mailing list