[Pgp-tools-commit] r806 - in trunk: caff debian

Guilhem Moulin guilhem-guest at moszumanska.debian.org
Mon Apr 20 08:33:21 UTC 2015


Author: guilhem-guest
Date: 2015-04-20 08:33:20 +0000 (Mon, 20 Apr 2015)
New Revision: 806

Modified:
   trunk/caff/caff
   trunk/debian/changelog
Log:
caff: Proper RFC 5322 validation of email addresses.

Modified: trunk/caff/caff
===================================================================
--- trunk/caff/caff	2015-04-20 08:33:14 UTC (rev 805)
+++ trunk/caff/caff	2015-04-20 08:33:20 UTC (rev 806)
@@ -416,7 +416,7 @@
 use MIME::Entity;
 use Encode ();
 use I18N::Langinfo qw{langinfo};
-use Net::IDN::Encode ();
+use Net::IDN::Encode qw{email_to_ascii domain_to_ascii};
 use Fcntl;
 use IO::Select;
 use Getopt::Long;
@@ -501,6 +501,50 @@
     return $version;
 }
 
+# See RFC 5322 section 3.4.1; only the pattern for the local part, which
+# doesn't go beyond the ASCII range, is validated.  The domain part is
+# NOT checked against RFC 5322, as it must be encoded to ASCII first;
+# for now any string in the full-range unicode that does not contain
+# U+0040 (commercial at), U+FE6B (small commercial at) and U+FF20
+# (fullwidth commercial at) is accepted.
+my $RE_word = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+ # atom: any ASCII CHAR except specials, SPACE and CTLs
+                 |\x22(?:[\x00-\x21\x23-\x5B\x5D-\x7E]|\x5C\p{ASCII})*\x22       # quoted string
+                /x;
+my $RE_address_spec = qr/(?<l>$RE_word(?:\.$RE_word)*)[\@\N{U+FE6B}\N{U+FF20}](?<d>[^\@\N{U+FE6B}\N{U+FF20}]+)/o;
+
+# A domain label is a non-empty ASCII string of length at most 63
+# characters (RFC 1035 2.3.4).  Valid characters are alphanumeric and
+# hyphen '-', but an hyphen may not appear at the start or end of a
+# label (RFC 952, RFC 1123 2.1).
+my $RE_label = qr/[0-9a-z](?:[0-9a-z\x2D]{0,61}[0-9a-z])?/aai;
+
+# Take a 'mailbox' (RFC 5322 section 3.4) and return its ASCII-encoded
+# 'addr-spec'; or undef if it violates one of RFC 5322/5892/1035/5321.
+# We're not using Email::Valid because it's not unicode-friendly.
+# NOTE: This subroutine should only be used to extract e-mail addresses
+# from UIDs.  The phrase is NOT checked against RFC 5322 (any string
+# containing only characters in the full-unicode printable range are
+# accepted), but we don't care as long as it's not used in email
+# headers.
+sub email_valid($) {
+    local $_ = shift // return;
+    return unless /\A$RE_address_spec\z/ao or                         # addr-spec
+                  /\A(?:\p{Print}*\p{Space})?<$RE_address_spec>\z/ao; # [phrase] "<" addr-spec ">"
+    my ($l,$d) = @+{qw/l d/};
+    if ($d =~ /\P{ASCII}/) {
+        # encode the IDN to ASCII using Punycode for RFC 5321 validation
+        eval { $d = domain_to_ascii($d) };
+        return if $@; # violates RFC 5892
+    }
+    my $address = "$l\@$d";
+    return unless
+        length $d > 0 and length $d <= 255 # violates RFC 1035 2.3.4 "size limits"
+        and length $l <= 64                # violates RFC 5321 4.5.3.1.1
+        and length $address <= 254         # violates RFC 5321 4.5.3.1.3
+        and $d =~ /\A$RE_label(?:\.$RE_label)+\z/o; # ignore non-FQDN
+    return $address;
+}
+
 open NULL, '+<', '/dev/null';
 my $NULL = fileno NULL;
 sub generate_config() {
@@ -536,7 +580,12 @@
             @keys = qw{0123456789abcdef 89abcdef76543210};
             $Ckeys = '#';
         }
-        ($email) = ($output{stdout} =~ /^uid:[^eir:]*:(?:[^:]*:){7}[^:]+ <([^:]+\@[^:]+)>(?::.*)?$/m);
+        my @emails = ($output{stdout} =~ /^uid:[^eir:]*:(?:[^:]*:){7}([^:]+)(?::.*)?$/mg);
+        if (@emails) {
+            s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge foreach @emails;
+            @emails = grep defined, map {email_valid(Encode::decode_utf8($_))} @emails;
+            $email = shift @emails; # take the first valid address
+        }
         unless (defined $email) {
             notice("Error: No email address was found using \"gpg --list-public-keys '$gecos'\"", 0);
             $email = $ENV{'LOGNAME'}.'@'.$hostname;
@@ -971,7 +1020,7 @@
     $message_entity->head->add("From", $from);
     $message_entity->head->add("Date", strfCtime("%a, %e %b %Y %H:%M:%S %z", localtime));
     $message_entity->head->add("Subject", Encode::encode('MIME-Q', $CONFIG{'mail-subject'} =~ s/%k/$key_id/gr));
-    $message_entity->head->add("To", email_to_ascii($address));
+    $message_entity->head->add("To", $address);
     $message_entity->head->add("Sender", $from);
     $message_entity->head->add("Reply-To", $CONFIG{'reply-to'}) if defined $CONFIG{'reply-to'};
     $message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'};
@@ -993,21 +1042,6 @@
     $message_entity->send(@{$CONFIG{'mailer-send'}});
 };
 
-# Net::IDN::Encode::email_to_ascii crashes upon punycode conversion failure:
-# we don't want caff to crash, so upon errors return the input as is and
-# let the MUA handle that
-sub email_to_ascii($) {
-    my $email = shift;
-    my $res;
-
-    eval { $res = Net::IDN::Encode::email_to_ascii($email) };
-    return $res unless $@;
-
-    chomp $@;
-    mywarn($@);
-    return $email;
-}
-
 ######
 # clean up a UID so that it can be used on the FS.
 ######
@@ -1521,10 +1555,7 @@
             $uid->{text} =~ s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge;
             # --with-colons always outputs UTF-8
             $uid->{text} = Encode::decode_utf8($uid->{text});
-            $uid->{address} = $1 if $uid->{type} eq 'uid' and $uid->{text} =~ /.*<([^>]+[\@\N{U+FE6B}\N{U+FF20}][^>]+)>$/;
-            # XXX This does not cover the full RFC 2822 specification:
-            # The local part may contain '>' in a quoted string.
-            # However as of 1.4.18/2.0.26, gpg doesn't allow that either.
+            $uid->{address} = email_valid $uid->{text} if $uid->{type} eq 'uid';
             push @{$KEYS{$keyid}->{uids}}, $uid;
         }
         elsif (!/^(?:rvk|tru):/) {
@@ -1835,7 +1866,7 @@
     delete $_->{key} foreach grep {!$_->{export}} @UIDS;    # delete non-exported keys
 
     if (!grep {defined $_->{address}} @UIDS) {
-        mywarn "No signed RFC 2822 UID on $longkeyid; won't send other signed UID and attributes!"
+        mywarn "No signed RFC 5322 UID on $longkeyid; won't send other signed UID and attributes!"
             if @attached;
     }
     elsif (grep {$_->{export}} @UIDS) {

Modified: trunk/debian/changelog
===================================================================
--- trunk/debian/changelog	2015-04-20 08:33:14 UTC (rev 805)
+++ trunk/debian/changelog	2015-04-20 08:33:20 UTC (rev 806)
@@ -3,6 +3,11 @@
   * caff:
     + Only consider non-expired/invalid/revoked keys and UIDs when generating
       the caffrc.
+    + Proper RFC 5322 validation of email addresses.  Currently gpg(1) only
+      allows accept a subset of RFC 5322-valid addresses (unless
+      --allow-freeform-uid is set).  caff is now able to extract the email
+      address from any UID of the form "addr-spec" or "[phrase] <addr-spec>"
+      with a RFC 5322-valid addr-spec.
 
  -- Guilhem Moulin <guilhem at guilhem.org>  Mon, 20 Apr 2015 10:28:20 +0200
 




More information about the Pgp-tools-commit mailing list