This is an updated version of the multiple-crypt-hook patch for
consideration.  The patch looks big, but a large portion of it is just
indentation changes: if you review it ignoring whitespace it's clearer
what's going on.

This version splits out a _mutt_list_hook() function and changes
mutt_crypt_hook() to use that instead.

The find_keys() functions then have a:
    crypt_hook_list = crypt_hook = mutt_crypt_hook (p);
    do
    {
      ...

      if (crypt_hook != NULL)
        crypt_hook = crypt_hook->next;
    } while (crypt_hook != NULL);
added inside the address loop.

Another change with this version is the behavior when a crypt-hook
is declined.  It would previously revert to the original address and
perform key selection using that.  That made sense when there can be at
most one crypt-hook per address, but doesn't when there can be multiple
of them.

So this version keeps track of when a key has been selected for an
address and adds this:
     else if (r == M_NO)
     {
       if (key_selected || (crypt_hook->next != NULL))
       {
         crypt_hook = crypt_hook->next;
         continue;
       }
     }
So key selection with the original address will only take place for the
last crypt-hook and only if a key hasn't been selected yet.

This patch has a fairly specific use case, but I don't think it's too
intrusive.  One side effect is that crypt-hooks for a regexp can't be
changed, only appended to.  There may be a few cases where a person had
multiple crypt-hooks and were counting on the ordering somehow, and now
they have multiple prompts instead of just the first matching one.

I don't think either of these cases are too likely, but welcome
arguments against inclusion of this patch.

Thank you,

-Kevin
# HG changeset patch
# User Kevin McCarthy <ke...@8t8.us>
# Date 1428171540 25200
#      Sat Apr 04 11:19:00 2015 -0700
# Node ID c745291d705178968fbe36d82ccd650b35bd74b6
# Parent  1bd26d871d76dea2191d50a4f852fbf0478ec390
Allow multiple crypt-hooks with the same regexp. (closes #3727).

Changes the crypt-hook to accumulate a LIST of hooks with
the same regexp, as opposed to replacing the hook data.
This is useful for the case of encrypted mailing lists.

Update pgp classic and gpgme to process a LIST of crypt-hook
values instead of just one.

This version of the patch creates a new _mutt_list_hook() function that
(in theory) other hooks could use if they were changed to return a list.
It also changes the behavior when a crypt-hook is declined: previously
it would immediately use the original recipient for key selection.  Now
it will only do that if all crypt-hooks for a recipient are declined.
This allows all, a subset, or none of the hooks to be used.

Thanks to Rejo Zenger, Remco Rijnders, and Dale Woolridge for their work
on various versions of this patch.

diff --git a/crypt-gpgme.c b/crypt-gpgme.c
--- a/crypt-gpgme.c
+++ b/crypt-gpgme.c
@@ -4331,131 +4331,158 @@
 }
 
 /* This routine attempts to find the keyids of the recipients of a
    message.  It returns NULL if any of the keys can not be found.
    If oppenc_mode is true, only keys that can be determined without
    prompting will be used.  */
 static char *find_keys (ADDRESS *adrlist, unsigned int app, int oppenc_mode)
 {
+  LIST *crypt_hook_list, *crypt_hook = NULL;
   char *crypt_hook_val = NULL;
   const char *keyID = NULL;
   char *keylist = NULL, *t;
   size_t keylist_size = 0;
   size_t keylist_used = 0;
   ADDRESS *addr = NULL;
   ADDRESS *p, *q;
   crypt_key_t *k_info;
   const char *fqdn = mutt_fqdn (1);
+  char buf[LONG_STRING];
+  int forced_valid;
+  int r;
+  int key_selected;
 
 #if 0
   *r_application = APPLICATION_PGP|APPLICATION_SMIME;
 #endif
 
   for (p = adrlist; p ; p = p->next)
     {
-      char buf[LONG_STRING];
-      int forced_valid = 0;
-      
-      q = p;
-      k_info = NULL;
-      
-      if ((crypt_hook_val = mutt_crypt_hook (p)) != NULL)
-        {
-          int r = M_NO;
-          if (! oppenc_mode)
-            {
-              snprintf (buf, sizeof (buf), _("Use keyID = \"%s\" for %s?"),
-                        crypt_hook_val, p->mailbox);
-              r = mutt_yesorno (buf, M_YES);
-            }
-          if (oppenc_mode || (r == M_YES))
-            {
-              if (crypt_is_numerical_keyid (crypt_hook_val))
-                {
-                  keyID = crypt_hook_val;
-                  if (strncmp (keyID, "0x", 2) == 0)
-                    keyID += 2;
-                  goto bypass_selection;                /* you don't see this. 
*/
-                }
-
-              /* check for e-mail address */
-              if ((t = strchr (crypt_hook_val, '@')) && 
-                  (addr = rfc822_parse_adrlist (NULL, crypt_hook_val)))
-                {
-                  if (fqdn)
-                    rfc822_qualify (addr, fqdn);
-                  q = addr;
-                }
-              else if (! oppenc_mode)
-               {
+      key_selected = 0;
+      crypt_hook_list = crypt_hook = mutt_crypt_hook (p);
+      do
+      {
+        q = p;
+        forced_valid = 0;
+        k_info = NULL;
+
+        if (crypt_hook != NULL)
+          {
+            crypt_hook_val = crypt_hook->data;
+            r = M_NO;
+            if (! oppenc_mode)
+              {
+                snprintf (buf, sizeof (buf), _("Use keyID = \"%s\" for %s?"),
+                          crypt_hook_val, p->mailbox);
+                r = mutt_yesorno (buf, M_YES);
+              }
+            if (oppenc_mode || (r == M_YES))
+              {
+                if (crypt_is_numerical_keyid (crypt_hook_val))
+                  {
+                    keyID = crypt_hook_val;
+                    if (strncmp (keyID, "0x", 2) == 0)
+                      keyID += 2;
+                    goto bypass_selection;                /* you don't see 
this. */
+                  }
+
+                /* check for e-mail address */
+                if ((t = strchr (crypt_hook_val, '@')) && 
+                    (addr = rfc822_parse_adrlist (NULL, crypt_hook_val)))
+                  {
+                    if (fqdn)
+                      rfc822_qualify (addr, fqdn);
+                    q = addr;
+                  }
+                else if (! oppenc_mode)
+                  {
 #if 0            
-                 k_info = crypt_getkeybystr (crypt_hook_val, 
KEYFLAG_CANENCRYPT, 
-                                             *r_application, &forced_valid);
+                    k_info = crypt_getkeybystr (crypt_hook_val, 
KEYFLAG_CANENCRYPT, 
+                                                *r_application, &forced_valid);
 #else
-                 k_info = crypt_getkeybystr (crypt_hook_val, 
KEYFLAG_CANENCRYPT, 
-                                             app, &forced_valid);
+                    k_info = crypt_getkeybystr (crypt_hook_val, 
KEYFLAG_CANENCRYPT, 
+                                                app, &forced_valid);
 #endif
-               }
-            }
-          else if (r == -1)
-            {
-              FREE (&keylist);
-              rfc822_free_address (&addr);
-              return NULL;
-            }
-        }
-
-      if (k_info == NULL)
-        {
-          k_info = crypt_getkeybyaddr (q, KEYFLAG_CANENCRYPT,
-                                       app, &forced_valid, oppenc_mode);
-        }
-
-      if ((k_info == NULL) && (! oppenc_mode))
-        {
-          snprintf (buf, sizeof (buf), _("Enter keyID for %s: "), q->mailbox);
-          
-          k_info = crypt_ask_for_key (buf, q->mailbox,
-                                      KEYFLAG_CANENCRYPT,
+                  }
+              }
+            else if (r == M_NO)
+              {
+                if (key_selected || (crypt_hook->next != NULL))
+                  {
+                    crypt_hook = crypt_hook->next;
+                    continue;
+                  }
+              }
+            else if (r == -1)
+              {
+                FREE (&keylist);
+                rfc822_free_address (&addr);
+                mutt_free_list (&crypt_hook_list);
+                return NULL;
+              }
+          }
+
+        if (k_info == NULL)
+          {
+            k_info = crypt_getkeybyaddr (q, KEYFLAG_CANENCRYPT,
+                                        app, &forced_valid, oppenc_mode);
+          }
+
+        if ((k_info == NULL) && (! oppenc_mode))
+          {
+            snprintf (buf, sizeof (buf), _("Enter keyID for %s: "), 
q->mailbox);
+            
+            k_info = crypt_ask_for_key (buf, q->mailbox,
+                                        KEYFLAG_CANENCRYPT,
 #if 0
-                                      *r_application,
+                                        *r_application,
 #else
-                                      app,
+                                        app,
 #endif
-                                      &forced_valid);
-        }
-
-      if (k_info == NULL)
-        {
-          FREE (&keylist);
-          rfc822_free_address (&addr);
-          return NULL;
-        }
-
-
-      keyID = crypt_fpr (k_info);
+                                        &forced_valid);
+          }
+
+        if (k_info == NULL)
+          {
+            FREE (&keylist);
+            rfc822_free_address (&addr);
+            mutt_free_list (&crypt_hook_list);
+            return NULL;
+          }
+
+
+        keyID = crypt_fpr (k_info);
 
 #if 0
-      if (k_info->flags & KEYFLAG_ISX509)
-        *r_application &= ~APPLICATION_PGP;
-      if (!(k_info->flags & KEYFLAG_ISX509))
-        *r_application &= ~APPLICATION_SMIME;
+        if (k_info->flags & KEYFLAG_ISX509)
+          *r_application &= ~APPLICATION_PGP;
+        if (!(k_info->flags & KEYFLAG_ISX509))
+          *r_application &= ~APPLICATION_SMIME;
 #endif
-      
-  bypass_selection:
-      keylist_size += mutt_strlen (keyID) + 4 + 1;
-      safe_realloc (&keylist, keylist_size);
-      sprintf (keylist + keylist_used, "%s0x%s%s", /* __SPRINTF_CHECKED__ */
-               keylist_used ? " " : "",  keyID,
-               forced_valid? "!":"");
-      keylist_used = mutt_strlen (keylist);
         
-      crypt_free_key (&k_info);
-      rfc822_free_address (&addr);
+    bypass_selection:
+        keylist_size += mutt_strlen (keyID) + 4 + 1;
+        safe_realloc (&keylist, keylist_size);
+        sprintf (keylist + keylist_used, "%s0x%s%s", /* __SPRINTF_CHECKED__ */
+                keylist_used ? " " : "",  keyID,
+                forced_valid? "!":"");
+        keylist_used = mutt_strlen (keylist);
+
+        key_selected = 1;
+
+        crypt_free_key (&k_info);
+        rfc822_free_address (&addr);
+
+        if (crypt_hook != NULL)
+          crypt_hook = crypt_hook->next;
+
+      } while (crypt_hook != NULL);
+
+      mutt_free_list (&crypt_hook_list);
     }
   return (keylist);
 }
 
 char *pgp_gpgme_findkeys (ADDRESS *adrlist, int oppenc_mode)
 {
   return find_keys (adrlist, APPLICATION_PGP, oppenc_mode);
 }
diff --git a/doc/manual.xml.head b/doc/manual.xml.head
--- a/doc/manual.xml.head
+++ b/doc/manual.xml.head
@@ -3628,16 +3628,19 @@
 <para>
 When encrypting messages with PGP/GnuPG or OpenSSL, you may want to
 associate a certain key with a given e-mail address automatically,
 either because the recipient's public key can't be deduced from the
 destination address, or because, for some reasons, you need to override
 the key Mutt would normally use.  The <command>crypt-hook</command>
 command provides a method by which you can specify the ID of the public
 key to be used when encrypting messages to a certain recipient.
+You may use multiple crypt-hooks with the same regexp; multiple
+matching crypt-hooks result in the use of multiple keyids for
+a recipient.
 </para>
 
 <para>
 The meaning of <emphasis>keyid</emphasis> is to be taken broadly in this
 context: You can either put a numerical key ID here, an e-mail address,
 or even just a real name.
 </para>
 
diff --git a/doc/muttrc.man.head b/doc/muttrc.man.head
--- a/doc/muttrc.man.head
+++ b/doc/muttrc.man.head
@@ -343,22 +343,27 @@
 .TP
 \fBreply-hook\fP [\fB!\fP]\fIpattern\fP \fIcommand\fP
 When replying to a message matching \fIpattern\fP, \fIcommand\fP is
 executed.  When multiple \fBreply-hook\fPs match, they are executed
 in the order in which they occur in the configuration file, but all
 \fBreply-hook\fPs are matched and executed before \fBsend-hook\fPs,
 regardless of their order in the configuration file.
 .TP
-\fBcrypt-hook\fP \fIpattern\fP \fIkey-id\fP
+\fBcrypt-hook\fP \fIregexp\fP \fIkey-id\fP
 The crypt-hook command provides a method by which you can
 specify the ID of the public key to be used when encrypting messages
 to a certain recipient.  The meaning of "key ID" is to be taken
 broadly: This can be a different e-mail address, a numerical key ID,
 or even just an arbitrary search string.
+You may use multiple
+\fBcrypt-hook\fPs with the same \fIregexp\fP; multiple matching
+\fBcrypt-hook\fPs result in the use of multiple \fIkey-id\fPs for
+a recipient.
+
 .TP
 \fBpush\fP \fIstring\fP
 This command adds the named \fIstring\fP to the keyboard buffer.
 .PP
 .nf
 \fBset\fP [\fBno\fP|\fBinv\fP|\fB&\fP|\fB?\fP]\fIvariable\fP[=\fIvalue\fP] [ 
... ]
 \fBtoggle\fP \fIvariable\fP [ ... ]
 \fBunset\fP \fIvariable\fP [ ... ]
diff --git a/hook.c b/hook.c
--- a/hook.c
+++ b/hook.c
@@ -120,17 +120,17 @@
 
   /* check to make sure that a matching hook doesn't already exist */
   for (ptr = Hooks; ptr; ptr = ptr->next)
   {
     if (ptr->type == data &&
        ptr->rx.not == not &&
        !mutt_strcmp (pattern.data, ptr->rx.pattern))
     {
-      if (data & (M_FOLDERHOOK | M_SENDHOOK | M_SEND2HOOK | M_MESSAGEHOOK | 
M_ACCOUNTHOOK | M_REPLYHOOK))
+      if (data & (M_FOLDERHOOK | M_SENDHOOK | M_SEND2HOOK | M_MESSAGEHOOK | 
M_ACCOUNTHOOK | M_REPLYHOOK | M_CRYPTHOOK))
       {
        /* these hooks allow multiple commands with the same
         * pattern, so if we've already seen this pattern/command pair, just
         * ignore it instead of creating a duplicate */
        if (!mutt_strcmp (ptr->command, command.data))
        {
          FREE (&command.data);
          FREE (&pattern.data);
@@ -443,29 +443,43 @@
   {
     if ((tmp->type & hook) && ((match &&
         regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
       return (tmp->command);
   }
   return (NULL);
 }
 
+static LIST *_mutt_list_hook (const char *match, int hook)
+{
+  HOOK *tmp = Hooks;
+  LIST *matches = NULL;
+
+  for (; tmp; tmp = tmp->next)
+  {
+    if ((tmp->type & hook) &&
+        ((match && regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ 
tmp->rx.not))
+      matches = mutt_add_list (matches, tmp->command);
+  }
+  return (matches);
+}
+
 char *mutt_charset_hook (const char *chs)
 {
   return _mutt_string_hook (chs, M_CHARSETHOOK);
 }
 
 char *mutt_iconv_hook (const char *chs)
 {
   return _mutt_string_hook (chs, M_ICONVHOOK);
 }
 
-char *mutt_crypt_hook (ADDRESS *adr)
+LIST *mutt_crypt_hook (ADDRESS *adr)
 {
-  return _mutt_string_hook (adr->mailbox, M_CRYPTHOOK);
+  return _mutt_list_hook (adr->mailbox, M_CRYPTHOOK);
 }
 
 #ifdef USE_SOCKET
 void mutt_account_hook (const char* url)
 {
   /* parsing commands with URLs in an account hook can cause a recursive
    * call. We just skip processing if this occurs. Typically such commands
    * belong in a folder-hook -- perhaps we should warn the user. */
diff --git a/pgp.c b/pgp.c
--- a/pgp.c
+++ b/pgp.c
@@ -1169,101 +1169,126 @@
 
 /* This routine attempts to find the keyids of the recipients of a message.
  * It returns NULL if any of the keys can not be found.
  * If oppenc_mode is true, only keys that can be determined without
  * prompting will be used.
  */
 char *pgp_findKeys (ADDRESS *adrlist, int oppenc_mode)
 {
+  LIST *crypt_hook_list, *crypt_hook = NULL;
   char *keyID, *keylist = NULL;
   size_t keylist_size = 0;
   size_t keylist_used = 0;
   ADDRESS *addr = NULL;
   ADDRESS *p, *q;
   pgp_key_t k_info = NULL;
+  char buf[LONG_STRING];
+  int r;
+  int key_selected;
 
   const char *fqdn = mutt_fqdn (1);
 
   for (p = adrlist; p ; p = p->next)
   {
-    char buf[LONG_STRING];
+    key_selected = 0;
+    crypt_hook_list = crypt_hook = mutt_crypt_hook (p);
+    do
+    {
+      q = p;
+      k_info = NULL;
 
-    q = p;
-    k_info = NULL;
+      if (crypt_hook != NULL)
+      {
+        keyID = crypt_hook->data;
+        r = M_NO;
+        if (! oppenc_mode)
+        {
+          snprintf (buf, sizeof (buf), _("Use keyID = \"%s\" for %s?"), keyID, 
p->mailbox);
+          r = mutt_yesorno (buf, M_YES);
+        }
+        if (oppenc_mode || (r == M_YES))
+        {
+          if (crypt_is_numerical_keyid (keyID))
+          {
+            if (strncmp (keyID, "0x", 2) == 0)
+              keyID += 2;
+            goto bypass_selection;             /* you don't see this. */
+          }
 
-    if ((keyID = mutt_crypt_hook (p)) != NULL)
-    {
-      int r = M_NO;
-      if (! oppenc_mode)
+          /* check for e-mail address */
+          if (strchr (keyID, '@') &&
+              (addr = rfc822_parse_adrlist (NULL, keyID)))
+          {
+            if (fqdn) rfc822_qualify (addr, fqdn);
+            q = addr;
+          }
+          else if (! oppenc_mode)
+          {
+            k_info = pgp_getkeybystr (keyID, KEYFLAG_CANENCRYPT, PGP_PUBRING);
+          }
+        }
+        else if (r == M_NO)
+        {
+          if (key_selected || (crypt_hook->next != NULL))
+          {
+            crypt_hook = crypt_hook->next;
+            continue;
+          }
+        }
+        else if (r == -1)
+        {
+          FREE (&keylist);
+          rfc822_free_address (&addr);
+          mutt_free_list (&crypt_hook_list);
+          return NULL;
+        }
+      }
+
+      if (k_info == NULL)
       {
-        snprintf (buf, sizeof (buf), _("Use keyID = \"%s\" for %s?"), keyID, 
p->mailbox);
-        r = mutt_yesorno (buf, M_YES);
+        pgp_invoke_getkeys (q);
+        k_info = pgp_getkeybyaddr (q, KEYFLAG_CANENCRYPT, PGP_PUBRING, 
oppenc_mode);
       }
-      if (oppenc_mode || (r == M_YES))
+
+      if ((k_info == NULL) && (! oppenc_mode))
       {
-       if (crypt_is_numerical_keyid (keyID))
-       {
-         if (strncmp (keyID, "0x", 2) == 0)
-           keyID += 2;
-         goto bypass_selection;                /* you don't see this. */
-       }
-       
-       /* check for e-mail address */
-       if (strchr (keyID, '@') && 
-           (addr = rfc822_parse_adrlist (NULL, keyID)))
-       {
-         if (fqdn) rfc822_qualify (addr, fqdn);
-         q = addr;
-       }
-       else if (! oppenc_mode)
-       {
-         k_info = pgp_getkeybystr (keyID, KEYFLAG_CANENCRYPT, PGP_PUBRING);
-       }
+        snprintf (buf, sizeof (buf), _("Enter keyID for %s: "), q->mailbox);
+        k_info = pgp_ask_for_key (buf, q->mailbox,
+                              KEYFLAG_CANENCRYPT, PGP_PUBRING);
       }
-      else if (r == -1)
+
+      if (k_info == NULL)
       {
-       FREE (&keylist);
-       rfc822_free_address (&addr);
-       return NULL;
+        FREE (&keylist);
+        rfc822_free_address (&addr);
+        mutt_free_list (&crypt_hook_list);
+        return NULL;
       }
-    }
 
-    if (k_info == NULL)
-    {
-      pgp_invoke_getkeys (q);
-      k_info = pgp_getkeybyaddr (q, KEYFLAG_CANENCRYPT, PGP_PUBRING, 
oppenc_mode);
-    }
+      keyID = pgp_keyid (k_info);
 
-    if ((k_info == NULL) && (! oppenc_mode))
-    {
-      snprintf (buf, sizeof (buf), _("Enter keyID for %s: "), q->mailbox);
-      k_info = pgp_ask_for_key (buf, q->mailbox,
-                             KEYFLAG_CANENCRYPT, PGP_PUBRING);
-    }
+    bypass_selection:
+      keylist_size += mutt_strlen (keyID) + 4;
+      safe_realloc (&keylist, keylist_size);
+      sprintf (keylist + keylist_used, "%s0x%s", keylist_used ? " " : "",      
/* __SPRINTF_CHECKED__ */
+              keyID);
+      keylist_used = mutt_strlen (keylist);
 
-    if (k_info == NULL)
-    {
-      FREE (&keylist);
+      key_selected = 1;
+
+      pgp_free_key (&k_info);
       rfc822_free_address (&addr);
-      return NULL;
-    }
 
-    keyID = pgp_keyid (k_info);
-    
-  bypass_selection:
-    keylist_size += mutt_strlen (keyID) + 4;
-    safe_realloc (&keylist, keylist_size);
-    sprintf (keylist + keylist_used, "%s0x%s", keylist_used ? " " : "",        
/* __SPRINTF_CHECKED__ */
-            keyID);
-    keylist_used = mutt_strlen (keylist);
+      if (crypt_hook != NULL)
+        crypt_hook = crypt_hook->next;
 
-    pgp_free_key (&k_info);
-    rfc822_free_address (&addr);
+    } while (crypt_hook != NULL);
 
+    mutt_free_list (&crypt_hook_list);
   }
   return (keylist);
 }
 
 /* Warning: "a" is no longer freed in this routine, you need
  * to free it later.  This is necessary for $fcc_attach. */
 
 BODY *pgp_encrypt_message (BODY *a, char *keylist, int sign)
diff --git a/protos.h b/protos.h
--- a/protos.h
+++ b/protos.h
@@ -141,17 +141,17 @@
 char *mutt_expand_path (char *, size_t);
 char *_mutt_expand_path (char *, size_t, int);
 char *mutt_find_hook (int, const char *);
 char *mutt_gecos_name (char *, size_t, struct passwd *);
 char *mutt_gen_msgid (void);
 char *mutt_get_body_charset (char *, size_t, BODY *);
 const char *mutt_get_name (ADDRESS *);
 char *mutt_get_parameter (const char *, PARAMETER *);
-char *mutt_crypt_hook (ADDRESS *);
+LIST *mutt_crypt_hook (ADDRESS *);
 char *mutt_make_date (char *, size_t);
 
 const char *mutt_make_version (void);
 
 const char *mutt_fqdn(short);
 
 group_t *mutt_pattern_group (const char *);
 

Attachment: signature.asc
Description: PGP signature

Reply via email to