Code

Merge branch 'md/smtp-tls-hello-again'
authorJunio C Hamano <gitster@pobox.com>
Wed, 19 Oct 2011 04:59:10 +0000 (21:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 19 Oct 2011 04:59:10 +0000 (21:59 -0700)
* md/smtp-tls-hello-again:
  send-email: Honour SMTP domain when using TLS

1  2 
git-send-email.perl

diff --combined git-send-email.perl
index 6885dfa1b9b4448a2a75583abcc25ff14a887044,6a17ed6d6149675444a9cc28a7e7300b644974d3..d491db92c9379cf0eea63182dd704feb6582b6e7
@@@ -1,4 -1,4 +1,4 @@@
 -#!/usr/bin/perl -w
 +#!/usr/bin/perl
  #
  # Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
  # Copyright 2005 Ryan Anderson <ryan@michonline.com>
@@@ -16,7 -16,6 +16,7 @@@
  #    and second line is the subject of the message.
  #
  
 +use 5.008;
  use strict;
  use warnings;
  use Term::ReadLine;
@@@ -25,7 -24,6 +25,7 @@@ use Text::ParseWords
  use Data::Dumper;
  use Term::ANSIColor;
  use File::Temp qw/ tempdir tempfile /;
 +use File::Spec::Functions qw(catfile);
  use Error qw(:try);
  use Git;
  
@@@ -49,20 -47,18 +49,20 @@@ git send-email [options] <file | direct
  
    Composing:
      --from                  <str>  * Email From:
 -    --to                    <str>  * Email To:
 -    --cc                    <str>  * Email Cc:
 -    --bcc                   <str>  * Email Bcc:
 +    --[no-]to               <str>  * Email To:
 +    --[no-]cc               <str>  * Email Cc:
 +    --[no-]bcc              <str>  * Email Bcc:
      --subject               <str>  * Email "Subject:"
      --in-reply-to           <str>  * Email "In-Reply-To:"
      --annotate                     * Review each patch that will be sent in an editor.
      --compose                      * Open an editor for introduction.
 +    --8bit-encoding         <str>  * Encoding to assume 8bit mails if undeclared
  
    Sending:
      --envelope-sender       <str>  * Email envelope sender.
      --smtp-server       <str:int>  * Outgoing SMTP server to use. The port
                                       is optional. Default 'localhost'.
 +    --smtp-server-option    <str>  * Outgoing SMTP server option to use.
      --smtp-server-port      <int>  * Outgoing SMTP server port.
      --smtp-user             <str>  * Username for SMTP-AUTH.
      --smtp-pass             <str>  * Password for SMTP-AUTH; not necessary.
@@@ -73,7 -69,6 +73,7 @@@
  
    Automating:
      --identity              <str>  * Use the sendemail.<id> options.
 +    --to-cmd                <str>  * Email To: via `<str> \$patch_path`
      --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`
      --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
      --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
@@@ -89,7 -84,6 +89,7 @@@
      --[no-]validate                * Perform patch sanity checks. Default on.
      --[no-]format-patch            * understand any non optional arguments as
                                       `git format-patch` ones.
 +    --force                        * Send even if safety checks would prevent it.
  
  EOT
        exit(1);
@@@ -139,8 -133,11 +139,8 @@@ my $have_mail_address = eval { require 
  my $smtp;
  my $auth;
  
 -sub unique_email_list(@);
 -sub cleanup_compose_files();
 -
  # Variables we fill in automatically, or via prompting:
 -my (@to,@cc,@initial_cc,@bcclist,@xh,
 +my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
        $initial_reply_to,$initial_subject,@files,
        $author,$sender,$smtp_authpass,$annotate,$compose,$time);
  
@@@ -164,16 -161,12 +164,16 @@@ if ($@) 
  my ($quiet, $dry_run) = (0, 0);
  my $format_patch;
  my $compose_filename;
 +my $force = 0;
  
  # Handle interactive edition of files.
  my $multiedit;
 -my $editor = Git::command_oneline('var', 'GIT_EDITOR');
 +my $editor;
  
  sub do_edit {
 +      if (!defined($editor)) {
 +              $editor = Git::command_oneline('var', 'GIT_EDITOR');
 +      }
        if (defined($multiedit) && !$multiedit) {
                map {
                        system('sh', '-c', $editor.' "$@"', $editor, $_);
  }
  
  # Variables with corresponding config settings
 -my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
 -my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
 -my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain);
 +my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc);
 +my ($to_cmd, $cc_cmd);
 +my ($smtp_server, $smtp_server_port, @smtp_server_options);
 +my ($smtp_authuser, $smtp_encryption);
 +my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
  my ($validate, $confirm);
  my (@suppress_cc);
 +my ($auto_8bit_encoding);
  
  my ($debug_net_smtp) = 0;             # Net::SMTP, see send_message()
  
@@@ -215,26 -205,20 +215,26 @@@ my %config_bool_settings = 
  my %config_settings = (
      "smtpserver" => \$smtp_server,
      "smtpserverport" => \$smtp_server_port,
 +    "smtpserveroption" => \@smtp_server_options,
      "smtpuser" => \$smtp_authuser,
      "smtppass" => \$smtp_authpass,
 -      "smtpdomain" => \$smtp_domain,
 -    "to" => \@to,
 +    "smtpdomain" => \$smtp_domain,
 +    "to" => \@initial_to,
 +    "tocmd" => \$to_cmd,
      "cc" => \@initial_cc,
      "cccmd" => \$cc_cmd,
      "aliasfiletype" => \$aliasfiletype,
      "bcc" => \@bcclist,
 -    "aliasesfile" => \@alias_files,
      "suppresscc" => \@suppress_cc,
      "envelopesender" => \$envelope_sender,
      "multiedit" => \$multiedit,
      "confirm"   => \$confirm,
      "from" => \$sender,
 +    "assume8bitencoding" => \$auto_8bit_encoding,
 +);
 +
 +my %config_path_settings = (
 +    "aliasesfile" => \@alias_files,
  );
  
  # Help users prepare for 1.7.0
@@@ -278,21 -262,14 +278,21 @@@ $SIG{INT}  = \&signal_handler
  # Begin by accumulating all the variables (defined above), that we will end up
  # needing, first, from the command line:
  
 -my $rc = GetOptions("sender|from=s" => \$sender,
 +my $help;
 +my $rc = GetOptions("h" => \$help,
 +                  "sender|from=s" => \$sender,
                      "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
 -                  "to=s" => \@to,
 +                  "to=s" => \@initial_to,
 +                  "to-cmd=s" => \$to_cmd,
 +                  "no-to" => \$no_to,
                    "cc=s" => \@initial_cc,
 +                  "no-cc" => \$no_cc,
                    "bcc=s" => \@bcclist,
 +                  "no-bcc" => \$no_bcc,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
 +                  "smtp-server-option=s" => \@smtp_server_options,
                    "smtp-server-port=s" => \$smtp_server_port,
                    "smtp-user=s" => \$smtp_authuser,
                    "smtp-pass:s" => \$smtp_authpass,
                    "thread!" => \$thread,
                    "validate!" => \$validate,
                    "format-patch!" => \$format_patch,
 +                  "8bit-encoding=s" => \$auto_8bit_encoding,
 +                  "force" => \$force,
         );
  
 +usage() if $help;
  unless ($rc) {
      usage();
  }
@@@ -336,24 -310,8 +336,24 @@@ sub read_config 
                $$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target);
        }
  
 +      foreach my $setting (keys %config_path_settings) {
 +              my $target = $config_path_settings{$setting};
 +              if (ref($target) eq "ARRAY") {
 +                      unless (@$target) {
 +                              my @values = Git::config_path(@repo, "$prefix.$setting");
 +                              @$target = @values if (@values && defined $values[0]);
 +                      }
 +              }
 +              else {
 +                      $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target);
 +              }
 +      }
 +
        foreach my $setting (keys %config_settings) {
                my $target = $config_settings{$setting};
 +              next if $setting eq "to" and defined $no_to;
 +              next if $setting eq "cc" and defined $no_cc;
 +              next if $setting eq "bcc" and defined $no_bcc;
                if (ref($target) eq "ARRAY") {
                        unless (@$target) {
                                my @values = Git::config(@repo, "$prefix.$setting");
@@@ -393,7 -351,7 +393,7 @@@ my(%suppress_cc)
  if (@suppress_cc) {
        foreach my $entry (@suppress_cc) {
                die "Unknown --suppress-cc field: '$entry'\n"
 -                      unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
 +                      unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
                $suppress_cc{$entry} = 1;
        }
  }
@@@ -438,7 -396,7 +438,7 @@@ my ($repoauthor, $repocommitter)
  
  # Verify the user input
  
 -foreach my $entry (@to) {
 +foreach my $entry (@initial_to) {
        die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
  }
  
@@@ -537,12 -495,12 +537,12 @@@ while (defined(my $f = shift @ARGV)) 
                push @rev_list_opts, "--", @ARGV;
                @ARGV = ();
        } elsif (-d $f and !check_file_rev_conflict($f)) {
 -              opendir(DH,$f)
 +              opendir my $dh, $f
                        or die "Failed to opendir $f: $!";
  
 -              push @files, grep { -f $_ } map { +$f . "/" . $_ }
 -                              sort readdir(DH);
 -              closedir(DH);
 +              push @files, grep { -f $_ } map { catfile($f, $_) }
 +                              sort readdir $dh;
 +              closedir $dh;
        } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) {
                push @files, $f;
        } else {
@@@ -574,7 -532,7 +574,7 @@@ if (@files) 
        usage();
  }
  
 -sub get_patch_subject($) {
 +sub get_patch_subject {
        my $fn = shift;
        open (my $fh, '<', $fn);
        while (my $line = <$fh>) {
@@@ -592,7 -550,7 +592,7 @@@ if ($compose) 
        $compose_filename = ($repo ?
                tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
                tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
 -      open(C,">",$compose_filename)
 +      open my $c, ">", $compose_filename
                or die "Failed to open for writing $compose_filename: $!";
  
  
        my $tpl_subject = $initial_subject || '';
        my $tpl_reply_to = $initial_reply_to || '';
  
 -      print C <<EOT;
 +      print $c <<EOT;
  From $tpl_sender # This line is ignored.
  GIT: Lines beginning in "GIT:" will be removed.
  GIT: Consider including an overall diffstat or table of contents
@@@ -613,9 -571,9 +613,9 @@@ In-Reply-To: $tpl_reply_t
  
  EOT
        for my $f (@files) {
 -              print C get_patch_subject($f);
 +              print $c get_patch_subject($f);
        }
 -      close(C);
 +      close $c;
  
        if ($annotate) {
                do_edit($compose_filename, @files);
                do_edit($compose_filename);
        }
  
 -      open(C2,">",$compose_filename . ".final")
 +      open my $c2, ">", $compose_filename . ".final"
                or die "Failed to open $compose_filename.final : " . $!;
  
 -      open(C,"<",$compose_filename)
 +      open $c, "<", $compose_filename
                or die "Failed to open $compose_filename : " . $!;
  
        my $need_8bit_cte = file_has_nonascii($compose_filename);
        my $in_body = 0;
        my $summary_empty = 1;
 -      while(<C>) {
 +      while(<$c>) {
                next if m/^GIT:/;
                if ($in_body) {
                        $summary_empty = 0 unless (/^\n$/);
                } elsif (/^\n$/) {
                        $in_body = 1;
                        if ($need_8bit_cte) {
 -                              print C2 "MIME-Version: 1.0\n",
 +                              print $c2 "MIME-Version: 1.0\n",
                                         "Content-Type: text/plain; ",
                                           "charset=UTF-8\n",
                                         "Content-Transfer-Encoding: 8bit\n";
                        print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
                        next;
                }
 -              print C2 $_;
 +              print $c2 $_;
        }
 -      close(C);
 -      close(C2);
 +      close $c;
 +      close $c2;
  
        if ($summary_empty) {
                print "Summary email is empty, skipping it\n";
@@@ -702,45 -660,6 +702,45 @@@ sub ask 
        return undef;
  }
  
 +my %broken_encoding;
 +
 +sub file_declares_8bit_cte {
 +      my $fn = shift;
 +      open (my $fh, '<', $fn);
 +      while (my $line = <$fh>) {
 +              last if ($line =~ /^$/);
 +              return 1 if ($line =~ /^Content-Transfer-Encoding: .*8bit.*$/);
 +      }
 +      close $fh;
 +      return 0;
 +}
 +
 +foreach my $f (@files) {
 +      next unless (body_or_subject_has_nonascii($f)
 +                   && !file_declares_8bit_cte($f));
 +      $broken_encoding{$f} = 1;
 +}
 +
 +if (!defined $auto_8bit_encoding && scalar %broken_encoding) {
 +      print "The following files are 8bit, but do not declare " .
 +              "a Content-Transfer-Encoding.\n";
 +      foreach my $f (sort keys %broken_encoding) {
 +              print "    $f\n";
 +      }
 +      $auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ",
 +                                default => "UTF-8");
 +}
 +
 +if (!$force) {
 +      for my $f (@files) {
 +              if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) {
 +                      die "Refusing to send because the patch\n\t$f\n"
 +                              . "has the template subject '*** SUBJECT HERE ***'. "
 +                              . "Pass --force if you really want to send.\n";
 +              }
 +      }
 +}
 +
  my $prompting = 0;
  if (!defined $sender) {
        $sender = $repoauthor || $repocommitter || '';
        $prompting++;
  }
  
 -if (!@to) {
 +if (!@initial_to && !defined $to_cmd) {
        my $to = ask("Who should the emails be sent to? ");
 -      push @to, parse_address_line($to) if defined $to; # sanitized/validated later
 +      push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
        $prompting++;
  }
  
@@@ -770,8 -689,8 +770,8 @@@ sub expand_one_alias 
        return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
  }
  
 -@to = expand_aliases(@to);
 -@to = (map { sanitize_address($_) } @to);
 +@initial_to = expand_aliases(@initial_to);
 +@initial_to = (map { sanitize_address($_) } @initial_to);
  @initial_cc = expand_aliases(@initial_cc);
  @bcclist = expand_aliases(@bcclist);
  
@@@ -805,8 -724,8 +805,8 @@@ our ($message_id, %mail, $subject, $rep
  
  sub extract_valid_address {
        my $address = shift;
 -      my $local_part_regexp = '[^<>"\s@]+';
 -      my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+';
 +      my $local_part_regexp = qr/[^<>"\s@]+/;
 +      my $domain_regexp = qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/;
  
        # check for a local address:
        return $address if ($address =~ /^($local_part_regexp)$/);
@@@ -847,7 -766,7 +847,7 @@@ sub make_message_id 
                last if (defined $du_part and $du_part ne '');
        }
        if (not defined $du_part or $du_part eq '') {
 -              use Sys::Hostname qw();
 +              require Sys::Hostname;
                $du_part = 'user@' . Sys::Hostname::hostname();
        }
        my $message_id_template = "<%s-git-send-email-%s>";
@@@ -880,8 -799,8 +880,8 @@@ sub quote_rfc2047 
  
  sub is_rfc2047_quoted {
        my $s = shift;
 -      my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+';
 -      my $encoded_text = '[!->@-~]+';
 +      my $token = qr/[^][()<>@,;:"\/?.= \000-\037\177-\377]+/;
 +      my $encoded_text = qr/[!->@-~]+/;
        length($s) <= 75 &&
        $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o;
  }
@@@ -892,7 -811,7 +892,7 @@@ sub sanitize_address 
        my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
  
        if (not $recipient_name) {
 -              return "$recipient";
 +              return $recipient;
        }
  
        # if recipient_name is already quoted, do nothing
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
                $recipient_name =~ s/(["\\\r])/\\$1/g;
 -              $recipient_name = "\"$recipient_name\"";
 +              $recipient_name = qq["$recipient_name"];
        }
  
        return "$recipient_name $recipient_addr";
  
  sub valid_fqdn {
        my $domain = shift;
 -      return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./;
 +      return defined $domain && !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./;
  }
  
  sub maildomain_net {
@@@ -979,7 -898,7 +979,7 @@@ sub maildomain 
  sub send_message {
        my @recipients = unique_email_list(@to);
        @cc = (grep { my $cc = extract_valid_address($_);
 -                    not grep { $cc eq $_ } @recipients
 +                    not grep { $cc eq $_ || $_ =~ /<\Q${cc}\E>$/ } @recipients
                    }
               map { sanitize_address($_) }
               @cc);
@@@ -1054,8 -973,6 +1054,8 @@@ X-Mailer: git-send-email $gitversio
                }
        }
  
 +      unshift (@sendmail_parameters, @smtp_server_options);
 +
        if ($dry_run) {
                # We don't want to send the email.
        } elsif ($smtp_server =~ m#^/#) {
                        exec($smtp_server, @sendmail_parameters) or die $!;
                }
                print $sm "$header\n$message";
 -              close $sm or die $?;
 +              close $sm or die $!;
        } else {
  
                if (!defined $smtp_server) {
                                        $smtp_encryption = '';
                                        # Send EHLO again to receive fresh
                                        # supported commands
-                                       $smtp->hello();
+                                       $smtp->hello($smtp_domain);
                                } else {
                                        die "Server does not support STARTTLS! ".$smtp->message;
                                }
                            "VALUES: server=$smtp_server ",
                            "encryption=$smtp_encryption ",
                            "hello=$smtp_domain",
 -                          defined $smtp_server_port ? "port=$smtp_server_port" : "";
 +                          defined $smtp_server_port ? " port=$smtp_server_port" : "";
                }
  
                if (defined $smtp_authuser) {
 +                      # Workaround AUTH PLAIN/LOGIN interaction defect
 +                      # with Authen::SASL::Cyrus
 +                      eval {
 +                              require Authen::SASL;
 +                              Authen::SASL->import(qw(Perl));
 +                      };
  
                        if (!defined $smtp_authpass) {
  
@@@ -1177,13 -1088,12 +1177,13 @@@ $subject = $initial_subject
  $message_num = 0;
  
  foreach my $t (@files) {
 -      open(F,"<",$t) or die "can't open file $t";
 +      open my $fh, "<", $t or die "can't open file $t";
  
        my $author = undef;
        my $author_encoding;
        my $has_content_type;
        my $body_encoding;
 +      @to = ();
        @cc = ();
        @xh = ();
        my $input_format = undef;
        $message = "";
        $message_num++;
        # First unfold multiline header fields
 -      while(<F>) {
 +      while(<$fh>) {
                last if /^\s*$/;
                if (/^\s+\S/ and @header) {
                        chomp($header[$#header]);
                                        $1, $_) unless $quiet;
                                push @cc, $1;
                        }
 +                      elsif (/^To:\s+(.*)$/) {
 +                              foreach my $addr (parse_address_line($1)) {
 +                                      printf("(mbox) Adding to: %s from line '%s'\n",
 +                                              $addr, $_) unless $quiet;
 +                                      push @to, sanitize_address($addr);
 +                              }
 +                      }
                        elsif (/^Cc:\s+(.*)$/) {
                                foreach my $addr (parse_address_line($1)) {
                                        if (unquote_rfc2047($addr) eq $sender) {
                }
        }
        # Now parse the message body
 -      while(<F>) {
 +      while(<$fh>) {
                $message .=  $_;
                if (/^(Signed-off-by|Cc): (.*)$/i) {
                        chomp;
                                $c, $_) unless $quiet;
                }
        }
 -      close F;
 -
 -      if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
 -              open(F, "$cc_cmd \Q$t\E |")
 -                      or die "(cc-cmd) Could not execute '$cc_cmd'";
 -              while(<F>) {
 -                      my $c = $_;
 -                      $c =~ s/^\s*//g;
 -                      $c =~ s/\n$//g;
 -                      next if ($c eq $sender and $suppress_from);
 -                      push @cc, $c;
 -                      printf("(cc-cmd) Adding cc: %s from: '%s'\n",
 -                              $c, $cc_cmd) unless $quiet;
 -              }
 -              close F
 -                      or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
 +      close $fh;
 +
 +      push @to, recipients_cmd("to-cmd", "to", $to_cmd, $t)
 +              if defined $to_cmd;
 +      push @cc, recipients_cmd("cc-cmd", "cc", $cc_cmd, $t)
 +              if defined $cc_cmd && !$suppress_cc{'cccmd'};
 +
 +      if ($broken_encoding{$t} && !$has_content_type) {
 +              $has_content_type = 1;
 +              push @xh, "MIME-Version: 1.0",
 +                      "Content-Type: text/plain; charset=$auto_8bit_encoding",
 +                      "Content-Transfer-Encoding: 8bit";
 +              $body_encoding = $auto_8bit_encoding;
 +      }
 +
 +      if ($broken_encoding{$t} && !is_rfc2047_quoted($subject)) {
 +              $subject = quote_rfc2047($subject, $auto_8bit_encoding);
        }
  
        if (defined $author and $author ne $sender) {
                                }
                        }
                        else {
 +                              $has_content_type = 1;
                                push @xh,
                                  'MIME-Version: 1.0',
                                  "Content-Type: text/plain; charset=$author_encoding",
                ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
        $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
  
 +      @to = (@initial_to, @to);
        @cc = (@initial_cc, @cc);
  
        my $message_was_sent = send_message();
  
        # set up for the next message
        if ($thread && $message_was_sent &&
 -              (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
 +              (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 ||
 +              $message_num == 1)) {
                $reply_to = $message_id;
                if (length $references > 0) {
                        $references .= "\n $message_id";
        $message_id = undef;
  }
  
 +# Execute a command (e.g. $to_cmd) to get a list of email addresses
 +# and return a results array
 +sub recipients_cmd {
 +      my ($prefix, $what, $cmd, $file) = @_;
 +
 +      my $sanitized_sender = sanitize_address($sender);
 +      my @addresses = ();
 +      open my $fh, "$cmd \Q$file\E |"
 +          or die "($prefix) Could not execute '$cmd'";
 +      while (my $address = <$fh>) {
 +              $address =~ s/^\s*//g;
 +              $address =~ s/\s*$//g;
 +              $address = sanitize_address($address);
 +              next if ($address eq $sanitized_sender and $suppress_from);
 +              push @addresses, $address;
 +              printf("($prefix) Adding %s: %s from: '%s'\n",
 +                     $what, $address, $cmd) unless $quiet;
 +              }
 +      close $fh
 +          or die "($prefix) failed to close pipe to '$cmd'";
 +      return @addresses;
 +}
 +
  cleanup_compose_files();
  
 -sub cleanup_compose_files() {
 +sub cleanup_compose_files {
        unlink($compose_filename, $compose_filename . ".final") if $compose;
  }
  
  $smtp->quit if $smtp;
  
 -sub unique_email_list(@) {
 +sub unique_email_list {
        my %seen;
        my @emails;
  
@@@ -1425,17 -1301,3 +1425,17 @@@ sub file_has_nonascii 
        }
        return 0;
  }
 +
 +sub body_or_subject_has_nonascii {
 +      my $fn = shift;
 +      open(my $fh, '<', $fn)
 +              or die "unable to open $fn: $!\n";
 +      while (my $line = <$fh>) {
 +              last if $line =~ /^$/;
 +              return 1 if $line =~ /^Subject.*[^[:ascii:]]/;
 +      }
 +      while (my $line = <$fh>) {
 +              return 1 if $line =~ /[^[:ascii:]]/;
 +      }
 +      return 0;
 +}