1#!/usr/bin/perl 2# 3# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com> 4# Copyright 2005 Ryan Anderson <ryan@michonline.com> 5# 6# GPL v2 (See COPYING) 7# 8# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com> 9# 10# Sends a collection of emails to the given email addresses, disturbingly fast. 11# 12# Supports two formats: 13# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches) 14# 2. The original format support by Greg's script: 15# first line of the message is who to CC, 16# and second line is the subject of the message. 17# 18 19use5.008; 20use strict; 21use warnings; 22use POSIX qw/strftime/; 23use Term::ReadLine; 24use Getopt::Long; 25use Text::ParseWords; 26use Term::ANSIColor; 27use File::Temp qw/ tempdir tempfile /; 28use File::Spec::Functions qw(catdir catfile); 29use Git::LoadCPAN::Error qw(:try); 30use Cwd qw(abs_path cwd); 31use Git; 32use Git::I18N; 33use Net::Domain (); 34use Net::SMTP (); 35use Git::LoadCPAN::Mail::Address; 36 37Getopt::Long::Configure qw/ pass_through /; 38 39package FakeTerm; 40sub new { 41my($class,$reason) =@_; 42returnbless \$reason,shift; 43} 44subreadline{ 45my$self=shift; 46die"Cannot use readline on FakeTerm:$$self"; 47} 48package main; 49 50 51sub usage { 52print<<EOT; 53git send-email [options] <file | directory | rev-list options > 54git send-email --dump-aliases 55 56 Composing: 57 --from <str> * Email From: 58 --[no-]to <str> * Email To: 59 --[no-]cc <str> * Email Cc: 60 --[no-]bcc <str> * Email Bcc: 61 --subject <str> * Email "Subject:" 62 --reply-to <str> * Email "Reply-To:" 63 --in-reply-to <str> * Email "In-Reply-To:" 64 --[no-]xmailer * Add "X-Mailer:" header (default). 65 --[no-]annotate * Review each patch that will be sent in an editor. 66 --compose * Open an editor for introduction. 67 --compose-encoding <str> * Encoding to assume for introduction. 68 --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared 69 --transfer-encoding <str> * Transfer encoding to use (quoted-printable, 8bit, base64) 70 71 Sending: 72 --envelope-sender <str> * Email envelope sender. 73 --smtp-server <str:int> * Outgoing SMTP server to use. The port 74 is optional. Default 'localhost'. 75 --smtp-server-option <str> * Outgoing SMTP server option to use. 76 --smtp-server-port <int> * Outgoing SMTP server port. 77 --smtp-user <str> * Username for SMTP-AUTH. 78 --smtp-pass <str> * Password for SMTP-AUTH; not necessary. 79 --smtp-encryption <str> * tls or ssl; anything else disables. 80 --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. 81 --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). 82 Pass an empty string to disable certificate 83 verification. 84 --smtp-domain <str> * The domain name sent to HELO/EHLO handshake 85 --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or 86 "none" to disable authentication. 87 This setting forces to use one of the listed mechanisms. 88 --no-smtp-auth Disable SMTP authentication. Shorthand for 89 `--smtp-auth=none` 90 --smtp-debug <0|1> * Disable, enable Net::SMTP debug. 91 92 --batch-size <int> * send max <int> message per connection. 93 --relogin-delay <int> * delay <int> seconds between two successive login. 94 This option can only be used with --batch-size 95 96 Automating: 97 --identity <str> * Use the sendemail.<id> options. 98 --to-cmd <str> * Email To: via `<str> \$patch_path` 99 --cc-cmd <str> * Email Cc: via `<str> \$patch_path` 100 --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, misc-by, all. 101 --[no-]cc-cover * Email Cc: addresses in the cover letter. 102 --[no-]to-cover * Email To: addresses in the cover letter. 103 --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. 104 --[no-]suppress-from * Send to self. Default off. 105 --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off. 106 --[no-]thread * Use In-Reply-To: field. Default on. 107 108 Administering: 109 --confirm <str> * Confirm recipients before sending; 110 auto, cc, compose, always, or never. 111 --quiet * Output one line of info per email. 112 --dry-run * Don't actually send the emails. 113 --[no-]validate * Perform patch sanity checks. Default on. 114 --[no-]format-patch * understand any non optional arguments as 115 `git format-patch` ones. 116 --force * Send even if safety checks would prevent it. 117 118 Information: 119 --dump-aliases * Dump configured aliases and exit. 120 121EOT 122exit(1); 123} 124 125sub completion_helper { 126print Git::command('format-patch','--git-completion-helper'); 127exit(0); 128} 129 130# most mail servers generate the Date: header, but not all... 131sub format_2822_time { 132my($time) =@_; 133my@localtm=localtime($time); 134my@gmttm=gmtime($time); 135my$localmin=$localtm[1] +$localtm[2] *60; 136my$gmtmin=$gmttm[1] +$gmttm[2] *60; 137if($localtm[0] !=$gmttm[0]) { 138die __("local zone differs from GMT by a non-minute interval\n"); 139} 140if((($gmttm[6] +1) %7) ==$localtm[6]) { 141$localmin+=1440; 142}elsif((($gmttm[6] -1) %7) ==$localtm[6]) { 143$localmin-=1440; 144}elsif($gmttm[6] !=$localtm[6]) { 145die __("local time offset greater than or equal to 24 hours\n"); 146} 147my$offset=$localmin-$gmtmin; 148my$offhour=$offset/60; 149my$offmin=abs($offset%60); 150if(abs($offhour) >=24) { 151die __("local time offset greater than or equal to 24 hours\n"); 152} 153 154returnsprintf("%s,%2d%s%d%02d:%02d:%02d%s%02d%02d", 155qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]], 156$localtm[3], 157qw(Jan Feb Mar Apr May Jun 158 Jul Aug Sep Oct Nov Dec)[$localtm[4]], 159$localtm[5]+1900, 160$localtm[2], 161$localtm[1], 162$localtm[0], 163($offset>=0) ?'+':'-', 164abs($offhour), 165$offmin, 166); 167} 168 169my$have_email_valid=eval{require Email::Valid;1}; 170my$smtp; 171my$auth; 172my$num_sent=0; 173 174# Regexes for RFC 2047 productions. 175my$re_token=qr/[^][()<>@,;:\\"\/?.=\000-\037\177-\377]+/; 176my$re_encoded_text=qr/[^? \000-\037\177-\377]+/; 177my$re_encoded_word=qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/; 178 179# Variables we fill in automatically, or via prompting: 180my(@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh, 181$initial_in_reply_to,$reply_to,$initial_subject,@files, 182$author,$sender,$smtp_authpass,$annotate,$use_xmailer,$compose,$time); 183 184my$envelope_sender; 185 186# Example reply to: 187#$initial_in_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; 188 189my$repo=eval{ Git->repository() }; 190my@repo=$repo? ($repo) : (); 191my$term=eval{ 192$ENV{"GIT_SEND_EMAIL_NOTTY"} 193? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT 194: new Term::ReadLine 'git-send-email'; 195}; 196if($@) { 197$term= new FakeTerm "$@: going non-interactive"; 198} 199 200# Behavior modification variables 201my($quiet,$dry_run) = (0,0); 202my$format_patch; 203my$compose_filename; 204my$force=0; 205my$dump_aliases=0; 206 207# Handle interactive edition of files. 208my$multiedit; 209my$editor; 210 211sub do_edit { 212if(!defined($editor)) { 213$editor= Git::command_oneline('var','GIT_EDITOR'); 214} 215if(defined($multiedit) && !$multiedit) { 216map{ 217system('sh','-c',$editor.' "$@"',$editor,$_); 218if(($?&127) || ($?>>8)) { 219die(__("the editor exited uncleanly, aborting everything")); 220} 221}@_; 222}else{ 223system('sh','-c',$editor.' "$@"',$editor,@_); 224if(($?&127) || ($?>>8)) { 225die(__("the editor exited uncleanly, aborting everything")); 226} 227} 228} 229 230# Variables with corresponding config settings 231my($thread,$chain_reply_to,$suppress_from,$signed_off_by_cc); 232my($cover_cc,$cover_to); 233my($to_cmd,$cc_cmd); 234my($smtp_server,$smtp_server_port,@smtp_server_options); 235my($smtp_authuser,$smtp_encryption,$smtp_ssl_cert_path); 236my($batch_size,$relogin_delay); 237my($identity,$aliasfiletype,@alias_files,$smtp_domain,$smtp_auth); 238my($validate,$confirm); 239my(@suppress_cc); 240my($auto_8bit_encoding); 241my($compose_encoding); 242my$target_xfer_encoding='auto'; 243 244my($debug_net_smtp) =0;# Net::SMTP, see send_message() 245 246my%config_bool_settings= ( 247"thread"=> [\$thread,1], 248"chainreplyto"=> [\$chain_reply_to,0], 249"suppressfrom"=> [\$suppress_from,undef], 250"signedoffbycc"=> [\$signed_off_by_cc,undef], 251"cccover"=> [\$cover_cc,undef], 252"tocover"=> [\$cover_to,undef], 253"signedoffcc"=> [\$signed_off_by_cc,undef],# Deprecated 254"validate"=> [\$validate,1], 255"multiedit"=> [\$multiedit,undef], 256"annotate"=> [\$annotate,undef], 257"xmailer"=> [\$use_xmailer,1] 258); 259 260my%config_settings= ( 261"smtpserver"=> \$smtp_server, 262"smtpserverport"=> \$smtp_server_port, 263"smtpserveroption"=> \@smtp_server_options, 264"smtpuser"=> \$smtp_authuser, 265"smtppass"=> \$smtp_authpass, 266"smtpdomain"=> \$smtp_domain, 267"smtpauth"=> \$smtp_auth, 268"smtpbatchsize"=> \$batch_size, 269"smtprelogindelay"=> \$relogin_delay, 270"to"=> \@initial_to, 271"tocmd"=> \$to_cmd, 272"cc"=> \@initial_cc, 273"cccmd"=> \$cc_cmd, 274"aliasfiletype"=> \$aliasfiletype, 275"bcc"=> \@bcclist, 276"suppresscc"=> \@suppress_cc, 277"envelopesender"=> \$envelope_sender, 278"confirm"=> \$confirm, 279"from"=> \$sender, 280"assume8bitencoding"=> \$auto_8bit_encoding, 281"composeencoding"=> \$compose_encoding, 282"transferencoding"=> \$target_xfer_encoding, 283); 284 285my%config_path_settings= ( 286"aliasesfile"=> \@alias_files, 287"smtpsslcertpath"=> \$smtp_ssl_cert_path, 288); 289 290# Handle Uncouth Termination 291sub signal_handler { 292 293# Make text normal 294print color("reset"),"\n"; 295 296# SMTP password masked 297system"stty echo"; 298 299# tmp files from --compose 300if(defined$compose_filename) { 301if(-e $compose_filename) { 302printf __("'%s' contains an intermediate version ". 303"of the email you were composing.\n"), 304$compose_filename; 305} 306if(-e ($compose_filename.".final")) { 307printf __("'%s.final' contains the composed email.\n"), 308$compose_filename; 309} 310} 311 312exit; 313}; 314 315$SIG{TERM} = \&signal_handler; 316$SIG{INT} = \&signal_handler; 317 318# Begin by accumulating all the variables (defined above), that we will end up 319# needing, first, from the command line: 320 321my$help; 322my$git_completion_helper; 323my$rc= GetOptions("h"=> \$help, 324"dump-aliases"=> \$dump_aliases); 325usage()unless$rc; 326die __("--dump-aliases incompatible with other options\n") 327if!$helpand$dump_aliasesand@ARGV; 328$rc= GetOptions( 329"sender|from=s"=> \$sender, 330"in-reply-to=s"=> \$initial_in_reply_to, 331"reply-to=s"=> \$reply_to, 332"subject=s"=> \$initial_subject, 333"to=s"=> \@initial_to, 334"to-cmd=s"=> \$to_cmd, 335"no-to"=> \$no_to, 336"cc=s"=> \@initial_cc, 337"no-cc"=> \$no_cc, 338"bcc=s"=> \@bcclist, 339"no-bcc"=> \$no_bcc, 340"chain-reply-to!"=> \$chain_reply_to, 341"no-chain-reply-to"=>sub{$chain_reply_to=0}, 342"smtp-server=s"=> \$smtp_server, 343"smtp-server-option=s"=> \@smtp_server_options, 344"smtp-server-port=s"=> \$smtp_server_port, 345"smtp-user=s"=> \$smtp_authuser, 346"smtp-pass:s"=> \$smtp_authpass, 347"smtp-ssl"=>sub{$smtp_encryption='ssl'}, 348"smtp-encryption=s"=> \$smtp_encryption, 349"smtp-ssl-cert-path=s"=> \$smtp_ssl_cert_path, 350"smtp-debug:i"=> \$debug_net_smtp, 351"smtp-domain:s"=> \$smtp_domain, 352"smtp-auth=s"=> \$smtp_auth, 353"no-smtp-auth"=>sub{$smtp_auth='none'}, 354"identity=s"=> \$identity, 355"annotate!"=> \$annotate, 356"no-annotate"=>sub{$annotate=0}, 357"compose"=> \$compose, 358"quiet"=> \$quiet, 359"cc-cmd=s"=> \$cc_cmd, 360"suppress-from!"=> \$suppress_from, 361"no-suppress-from"=>sub{$suppress_from=0}, 362"suppress-cc=s"=> \@suppress_cc, 363"signed-off-cc|signed-off-by-cc!"=> \$signed_off_by_cc, 364"no-signed-off-cc|no-signed-off-by-cc"=>sub{$signed_off_by_cc=0}, 365"cc-cover|cc-cover!"=> \$cover_cc, 366"no-cc-cover"=>sub{$cover_cc=0}, 367"to-cover|to-cover!"=> \$cover_to, 368"no-to-cover"=>sub{$cover_to=0}, 369"confirm=s"=> \$confirm, 370"dry-run"=> \$dry_run, 371"envelope-sender=s"=> \$envelope_sender, 372"thread!"=> \$thread, 373"no-thread"=>sub{$thread=0}, 374"validate!"=> \$validate, 375"no-validate"=>sub{$validate=0}, 376"transfer-encoding=s"=> \$target_xfer_encoding, 377"format-patch!"=> \$format_patch, 378"no-format-patch"=>sub{$format_patch=0}, 379"8bit-encoding=s"=> \$auto_8bit_encoding, 380"compose-encoding=s"=> \$compose_encoding, 381"force"=> \$force, 382"xmailer!"=> \$use_xmailer, 383"no-xmailer"=>sub{$use_xmailer=0}, 384"batch-size=i"=> \$batch_size, 385"relogin-delay=i"=> \$relogin_delay, 386"git-completion-helper"=> \$git_completion_helper, 387); 388 389usage()if$help; 390completion_helper()if$git_completion_helper; 391unless($rc) { 392 usage(); 393} 394 395die __("Cannot run git format-patch from outside a repository\n") 396if$format_patchand not$repo; 397 398die __("`batch-size` and `relogin` must be specified together ". 399"(via command-line or configuration option)\n") 400ifdefined$relogin_delayand not defined$batch_size; 401 402# Now, let's fill any that aren't set in with defaults: 403 404sub read_config { 405my($prefix) =@_; 406 407foreachmy$setting(keys%config_bool_settings) { 408my$target=$config_bool_settings{$setting}->[0]; 409$$target= Git::config_bool(@repo,"$prefix.$setting")unless(defined$$target); 410} 411 412foreachmy$setting(keys%config_path_settings) { 413my$target=$config_path_settings{$setting}; 414if(ref($target)eq"ARRAY") { 415unless(@$target) { 416my@values= Git::config_path(@repo,"$prefix.$setting"); 417@$target=@valuesif(@values&&defined$values[0]); 418} 419} 420else{ 421$$target= Git::config_path(@repo,"$prefix.$setting")unless(defined$$target); 422} 423} 424 425foreachmy$setting(keys%config_settings) { 426my$target=$config_settings{$setting}; 427next if$settingeq"to"and defined$no_to; 428next if$settingeq"cc"and defined$no_cc; 429next if$settingeq"bcc"and defined$no_bcc; 430if(ref($target)eq"ARRAY") { 431unless(@$target) { 432my@values= Git::config(@repo,"$prefix.$setting"); 433@$target=@valuesif(@values&&defined$values[0]); 434} 435} 436else{ 437$$target= Git::config(@repo,"$prefix.$setting")unless(defined$$target); 438} 439} 440 441if(!defined$smtp_encryption) { 442my$enc= Git::config(@repo,"$prefix.smtpencryption"); 443if(defined$enc) { 444$smtp_encryption=$enc; 445}elsif(Git::config_bool(@repo,"$prefix.smtpssl")) { 446$smtp_encryption='ssl'; 447} 448} 449} 450 451# read configuration from [sendemail "$identity"], fall back on [sendemail] 452$identity= Git::config(@repo,"sendemail.identity")unless(defined$identity); 453read_config("sendemail.$identity")if(defined$identity); 454read_config("sendemail"); 455 456# fall back on builtin bool defaults 457foreachmy$setting(values%config_bool_settings) { 458${$setting->[0]} =$setting->[1]unless(defined(${$setting->[0]})); 459} 460 461# 'default' encryption is none -- this only prevents a warning 462$smtp_encryption=''unless(defined$smtp_encryption); 463 464# Set CC suppressions 465my(%suppress_cc); 466if(@suppress_cc) { 467foreachmy$entry(@suppress_cc) { 468die sprintf(__("Unknown --suppress-cc field: '%s'\n"),$entry) 469unless$entry=~/^(?:all|cccmd|cc|author|self|sob|body|bodycc|misc-by)$/; 470$suppress_cc{$entry} =1; 471} 472} 473 474if($suppress_cc{'all'}) { 475foreachmy$entry(qw (cccmd cc author self sob body bodycc misc-by)) { 476$suppress_cc{$entry} =1; 477} 478delete$suppress_cc{'all'}; 479} 480 481# If explicit old-style ones are specified, they trump --suppress-cc. 482$suppress_cc{'self'} =$suppress_fromifdefined$suppress_from; 483$suppress_cc{'sob'} = !$signed_off_by_ccifdefined$signed_off_by_cc; 484 485if($suppress_cc{'body'}) { 486foreachmy$entry(qw (sob bodycc misc-by)) { 487$suppress_cc{$entry} =1; 488} 489delete$suppress_cc{'body'}; 490} 491 492# Set confirm's default value 493my$confirm_unconfigured= !defined$confirm; 494if($confirm_unconfigured) { 495$confirm=scalar%suppress_cc?'compose':'auto'; 496}; 497die sprintf(__("Unknown --confirm setting: '%s'\n"),$confirm) 498unless$confirm=~/^(?:auto|cc|compose|always|never)/; 499 500# Debugging, print out the suppressions. 501if(0) { 502print"suppressions:\n"; 503foreachmy$entry(keys%suppress_cc) { 504printf" %-5s ->$suppress_cc{$entry}\n",$entry; 505} 506} 507 508my($repoauthor,$repocommitter); 509($repoauthor) = Git::ident_person(@repo,'author'); 510($repocommitter) = Git::ident_person(@repo,'committer'); 511 512sub parse_address_line { 513returnmap{$_->format} Mail::Address->parse($_[0]); 514} 515 516sub split_addrs { 517return quotewords('\s*,\s*',1,@_); 518} 519 520my%aliases; 521 522sub parse_sendmail_alias { 523local$_=shift; 524if(/"/) { 525printf STDERR __("warning: sendmail alias with quotes is not supported:%s\n"),$_; 526}elsif(/:include:/) { 527printf STDERR __("warning: `:include:` not supported:%s\n"),$_; 528}elsif(/[\/|]/) { 529printf STDERR __("warning: `/file` or `|pipe` redirection not supported:%s\n"),$_; 530}elsif(/^(\S+?)\s*:\s*(.+)$/) { 531my($alias,$addr) = ($1,$2); 532$aliases{$alias} = [ split_addrs($addr) ]; 533}else{ 534printf STDERR __("warning: sendmail line is not recognized:%s\n"),$_; 535} 536} 537 538sub parse_sendmail_aliases { 539my$fh=shift; 540my$s=''; 541while(<$fh>) { 542chomp; 543next if/^\s*$/||/^\s*#/; 544$s.=$_,next if$s=~s/\\$//||s/^\s+//; 545 parse_sendmail_alias($s)if$s; 546$s=$_; 547} 548$s=~s/\\$//;# silently tolerate stray '\' on last line 549 parse_sendmail_alias($s)if$s; 550} 551 552my%parse_alias= ( 553# multiline formats can be supported in the future 554 mutt =>sub{my$fh=shift;while(<$fh>) { 555if(/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) { 556my($alias,$addr) = ($1,$2); 557$addr=~s/#.*$//;# mutt allows # comments 558# commas delimit multiple addresses 559my@addr= split_addrs($addr); 560 561# quotes may be escaped in the file, 562# unescape them so we do not double-escape them later. 563s/\\"/"/gforeach@addr; 564$aliases{$alias} = \@addr 565}}}, 566 mailrc =>sub{my$fh=shift;while(<$fh>) { 567if(/^alias\s+(\S+)\s+(.*?)\s*$/) { 568# spaces delimit multiple addresses 569$aliases{$1} = [ quotewords('\s+',0,$2) ]; 570}}}, 571 pine =>sub{my$fh=shift;my$f='\t[^\t]*'; 572for(my$x='';defined($x);$x=$_) { 573chomp$x; 574$x.=$1while(defined($_= <$fh>) &&/^ +(.*)$/); 575$x=~/^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ornext; 576$aliases{$1} = [ split_addrs($2) ]; 577}}, 578 elm =>sub{my$fh=shift; 579while(<$fh>) { 580if(/^(\S+)\s+=\s+[^=]+=\s(\S+)/) { 581my($alias,$addr) = ($1,$2); 582$aliases{$alias} = [ split_addrs($addr) ]; 583} 584} }, 585 sendmail => \&parse_sendmail_aliases, 586 gnus =>sub{my$fh=shift;while(<$fh>) { 587if(/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) { 588$aliases{$1} = [$2]; 589}}} 590); 591 592if(@alias_filesand$aliasfiletypeand defined$parse_alias{$aliasfiletype}) { 593foreachmy$file(@alias_files) { 594open my$fh,'<',$fileor die"opening$file:$!\n"; 595$parse_alias{$aliasfiletype}->($fh); 596close$fh; 597} 598} 599 600if($dump_aliases) { 601print"$_\n"for(sort keys%aliases); 602exit(0); 603} 604 605# is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if 606# $f is a revision list specification to be passed to format-patch. 607sub is_format_patch_arg { 608return unless$repo; 609my$f=shift; 610try{ 611$repo->command('rev-parse','--verify','--quiet',$f); 612if(defined($format_patch)) { 613return$format_patch; 614} 615die sprintf(__ <<EOF,$f,$f); 616File '%s' exists but it could also be the range of commits 617to produce patches for. Please disambiguate by... 618 619 * Saying "./%s" if you mean a file; or 620 * Giving --format-patch option if you mean a range. 621EOF 622} catch Git::Error::Command with { 623# Not a valid revision. Treat it as a filename. 624return0; 625} 626} 627 628# Now that all the defaults are set, process the rest of the command line 629# arguments and collect up the files that need to be processed. 630my@rev_list_opts; 631while(defined(my$f=shift@ARGV)) { 632if($feq"--") { 633push@rev_list_opts,"--",@ARGV; 634@ARGV= (); 635}elsif(-d $fand!is_format_patch_arg($f)) { 636opendir my$dh,$f 637or die sprintf(__("Failed to opendir%s:%s"),$f,$!); 638 639push@files,grep{ -f $_}map{ catfile($f,$_) } 640sort readdir$dh; 641closedir$dh; 642}elsif((-f $for-p $f)and!is_format_patch_arg($f)) { 643push@files,$f; 644}else{ 645push@rev_list_opts,$f; 646} 647} 648 649if(@rev_list_opts) { 650die __("Cannot run git format-patch from outside a repository\n") 651unless$repo; 652push@files,$repo->command('format-patch','-o', tempdir(CLEANUP =>1),@rev_list_opts); 653} 654 655@files= handle_backup_files(@files); 656 657if($validate) { 658foreachmy$f(@files) { 659unless(-p $f) { 660my$error= validate_patch($f,$target_xfer_encoding); 661$errorand die sprintf(__("fatal:%s:%s\nwarning: no patches were sent\n"), 662$f,$error); 663} 664} 665} 666 667if(@files) { 668unless($quiet) { 669print$_,"\n"for(@files); 670} 671}else{ 672print STDERR __("\nNo patch files specified!\n\n"); 673 usage(); 674} 675 676sub get_patch_subject { 677my$fn=shift; 678open(my$fh,'<',$fn); 679while(my$line= <$fh>) { 680next unless($line=~/^Subject: (.*)$/); 681close$fh; 682return"GIT:$1\n"; 683} 684close$fh; 685die sprintf(__("No subject line in%s?"),$fn); 686} 687 688if($compose) { 689# Note that this does not need to be secure, but we will make a small 690# effort to have it be unique 691$compose_filename= ($repo? 692 tempfile(".gitsendemail.msg.XXXXXX", DIR =>$repo->repo_path()) : 693 tempfile(".gitsendemail.msg.XXXXXX", DIR =>"."))[1]; 694open my$c,">",$compose_filename 695or die sprintf(__("Failed to open for writing%s:%s"),$compose_filename,$!); 696 697 698my$tpl_sender=$sender||$repoauthor||$repocommitter||''; 699my$tpl_subject=$initial_subject||''; 700my$tpl_in_reply_to=$initial_in_reply_to||''; 701my$tpl_reply_to=$reply_to||''; 702 703print$c<<EOT1, Git::prefix_lines("GIT: ", __ <<EOT2), <<EOT3; 704From$tpl_sender# This line is ignored. 705EOT1 706Lines beginning in "GIT:" will be removed. 707Consider including an overall diffstat or table of contents 708for the patch you are writing. 709 710Clear the body content if you don't wish to send a summary. 711EOT2 712From:$tpl_sender 713Reply-To:$tpl_reply_to 714Subject:$tpl_subject 715In-Reply-To:$tpl_in_reply_to 716 717EOT3 718 for my$f(@files) { 719 print$cget_patch_subject($f); 720 } 721 close$c; 722 723 if ($annotate) { 724 do_edit($compose_filename,@files); 725 } else { 726 do_edit($compose_filename); 727 } 728 729 open$c, "<",$compose_filename 730 or die sprintf(__("Failed to open%s:%s"),$compose_filename,$!); 731 732 if (!defined$compose_encoding) { 733$compose_encoding= "UTF-8"; 734 } 735 736 my%parsed_email; 737 while (my$line= <$c>) { 738 next if$line=~ m/^GIT:/; 739 parse_header_line($line, \%parsed_email); 740 if ($line=~ /^$/) { 741$parsed_email{'body'} = filter_body($c); 742 } 743 } 744 close$c; 745 746 open my$c2, ">",$compose_filename. ".final" 747 or die sprintf(__("Failed to open%s.final:%s"),$compose_filename,$!); 748 749 750 if ($parsed_email{'From'}) { 751$sender= delete($parsed_email{'From'}); 752 } 753 if ($parsed_email{'In-Reply-To'}) { 754$initial_in_reply_to= delete($parsed_email{'In-Reply-To'}); 755 } 756 if ($parsed_email{'Reply-To'}) { 757$reply_to= delete($parsed_email{'Reply-To'}); 758 } 759 if ($parsed_email{'Subject'}) { 760$initial_subject= delete($parsed_email{'Subject'}); 761 print$c2"Subject: " . 762 quote_subject($initial_subject,$compose_encoding) . 763 "\n"; 764 } 765 766 if ($parsed_email{'MIME-Version'}) { 767 print$c2"MIME-Version:$parsed_email{'MIME-Version'}\n", 768 "Content-Type:$parsed_email{'Content-Type'};\n", 769 "Content-Transfer-Encoding:$parsed_email{'Content-Transfer-Encoding'}\n"; 770 delete($parsed_email{'MIME-Version'}); 771 delete($parsed_email{'Content-Type'}); 772 delete($parsed_email{'Content-Transfer-Encoding'}); 773 } elsif (file_has_nonascii($compose_filename)) { 774 my$content_type= (delete($parsed_email{'Content-Type'}) or 775 "text/plain; charset=$compose_encoding"); 776 print$c2"MIME-Version: 1.0\n", 777 "Content-Type:$content_type\n", 778 "Content-Transfer-Encoding: 8bit\n"; 779 } 780 # Preserve unknown headers 781 foreach my$key(keys%parsed_email) { 782 next if$keyeq 'body'; 783 print$c2"$key:$parsed_email{$key}"; 784 } 785 786 if ($parsed_email{'body'}) { 787 print$c2"\n$parsed_email{'body'}\n"; 788 delete($parsed_email{'body'}); 789 } else { 790 print __("Summary email is empty, skipping it\n"); 791$compose= -1; 792 } 793 794 close$c2; 795 796} elsif ($annotate) { 797 do_edit(@files); 798} 799 800sub ask { 801 my ($prompt,%arg) =@_; 802 my$valid_re=$arg{valid_re}; 803 my$default=$arg{default}; 804 my$confirm_only=$arg{confirm_only}; 805 my$resp; 806 my$i= 0; 807 return defined$default?$default: undef 808 unless defined$term->IN and defined fileno($term->IN) and 809 defined$term->OUT and defined fileno($term->OUT); 810 while ($i++< 10) { 811$resp=$term->readline($prompt); 812 if (!defined$resp) { # EOF 813 print "\n"; 814 return defined$default?$default: undef; 815 } 816 if ($respeq '' and defined$default) { 817 return$default; 818 } 819 if (!defined$valid_reor$resp=~ /$valid_re/) { 820 return$resp; 821 } 822 if ($confirm_only) { 823 my$yesno=$term->readline( 824 # TRANSLATORS: please keep [y/N] as is. 825 sprintf(__("Are you sure you want to use <%s> [y/N]? "),$resp)); 826 if (defined$yesno&&$yesno=~ /y/i) { 827 return$resp; 828 } 829 } 830 } 831 return; 832} 833 834sub parse_header_line { 835 my$lines= shift; 836 my$parsed_line= shift; 837 my$addr_pat= join "|", qw(To Cc Bcc); 838 839foreach(split(/\n/,$lines)) { 840if(/^($addr_pat):\s*(.+)$/i) { 841$parsed_line->{$1} = [ parse_address_line($2) ]; 842}elsif(/^([^:]*):\s*(.+)\s*$/i) { 843$parsed_line->{$1} =$2; 844} 845} 846} 847 848sub filter_body { 849my$c=shift; 850my$body=""; 851while(my$body_line= <$c>) { 852if($body_line!~m/^GIT:/) { 853$body.=$body_line; 854} 855} 856return$body; 857} 858 859 860my%broken_encoding; 861 862sub file_declares_8bit_cte { 863my$fn=shift; 864open(my$fh,'<',$fn); 865while(my$line= <$fh>) { 866last if($line=~/^$/); 867return1if($line=~/^Content-Transfer-Encoding: .*8bit.*$/); 868} 869close$fh; 870return0; 871} 872 873foreachmy$f(@files) { 874next unless(body_or_subject_has_nonascii($f) 875&& !file_declares_8bit_cte($f)); 876$broken_encoding{$f} =1; 877} 878 879if(!defined$auto_8bit_encoding&&scalar%broken_encoding) { 880print __("The following files are 8bit, but do not declare ". 881"a Content-Transfer-Encoding.\n"); 882foreachmy$f(sort keys%broken_encoding) { 883print"$f\n"; 884} 885$auto_8bit_encoding= ask(__("Which 8bit encoding should I declare [UTF-8]? "), 886 valid_re =>qr/.{4}/, confirm_only =>1, 887default=>"UTF-8"); 888} 889 890if(!$force) { 891formy$f(@files) { 892if(get_patch_subject($f) =~/\Q*** SUBJECT HERE ***\E/) { 893die sprintf(__("Refusing to send because the patch\n\t%s\n" 894."has the template subject '*** SUBJECT HERE ***'. " 895."Pass --force if you really want to send.\n"),$f); 896} 897} 898} 899 900if(defined$sender) { 901$sender=~s/^\s+|\s+$//g; 902($sender) = expand_aliases($sender); 903}else{ 904$sender=$repoauthor||$repocommitter||''; 905} 906 907# $sender could be an already sanitized address 908# (e.g. sendemail.from could be manually sanitized by user). 909# But it's a no-op to run sanitize_address on an already sanitized address. 910$sender= sanitize_address($sender); 911 912my$to_whom= __("To whom should the emails be sent (if anyone)?"); 913my$prompting=0; 914if(!@initial_to&& !defined$to_cmd) { 915my$to= ask("$to_whom", 916default=>"", 917 valid_re =>qr/\@.*\./, confirm_only =>1); 918push@initial_to, parse_address_line($to)ifdefined$to;# sanitized/validated later 919$prompting++; 920} 921 922sub expand_aliases { 923returnmap{ expand_one_alias($_) }@_; 924} 925 926my%EXPANDED_ALIASES; 927sub expand_one_alias { 928my$alias=shift; 929if($EXPANDED_ALIASES{$alias}) { 930die sprintf(__("fatal: alias '%s' expands to itself\n"),$alias); 931} 932local$EXPANDED_ALIASES{$alias} =1; 933return$aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) :$alias; 934} 935 936@initial_to= process_address_list(@initial_to); 937@initial_cc= process_address_list(@initial_cc); 938@bcclist= process_address_list(@bcclist); 939 940if($thread&& !defined$initial_in_reply_to&&$prompting) { 941$initial_in_reply_to= ask( 942 __("Message-ID to be used as In-Reply-To for the first email (if any)? "), 943default=>"", 944 valid_re =>qr/\@.*\./, confirm_only =>1); 945} 946if(defined$initial_in_reply_to) { 947$initial_in_reply_to=~s/^\s*<?//; 948$initial_in_reply_to=~s/>?\s*$//; 949$initial_in_reply_to="<$initial_in_reply_to>"if$initial_in_reply_tone''; 950} 951 952if(defined$reply_to) { 953$reply_to=~s/^\s+|\s+$//g; 954($reply_to) = expand_aliases($reply_to); 955$reply_to= sanitize_address($reply_to); 956} 957 958if(!defined$smtp_server) { 959my@sendmail_paths=qw( /usr/sbin/sendmail /usr/lib/sendmail ); 960push@sendmail_paths,map{"$_/sendmail"}split/:/,$ENV{PATH}; 961foreach(@sendmail_paths) { 962if(-x $_) { 963$smtp_server=$_; 964last; 965} 966} 967$smtp_server||='localhost';# could be 127.0.0.1, too... *shrug* 968} 969 970if($compose&&$compose>0) { 971@files= ($compose_filename.".final",@files); 972} 973 974# Variables we set as part of the loop over files 975our($message_id,%mail,$subject,$in_reply_to,$references,$message, 976$needs_confirm,$message_num,$ask_default); 977 978sub extract_valid_address { 979my$address=shift; 980my$local_part_regexp=qr/[^<>"\s@]+/; 981my$domain_regexp=qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/; 982 983# check for a local address: 984return$addressif($address=~/^($local_part_regexp)$/); 985 986$address=~s/^\s*<(.*)>\s*$/$1/; 987if($have_email_valid) { 988returnscalar Email::Valid->address($address); 989} 990 991# less robust/correct than the monster regexp in Email::Valid, 992# but still does a 99% job, and one less dependency 993return$1if$address=~/($local_part_regexp\@$domain_regexp)/; 994return; 995} 996 997sub extract_valid_address_or_die { 998my$address=shift; 999$address= extract_valid_address($address);1000die sprintf(__("error: unable to extract a valid address from:%s\n"),$address)1001if!$address;1002return$address;1003}10041005sub validate_address {1006my$address=shift;1007while(!extract_valid_address($address)) {1008printf STDERR __("error: unable to extract a valid address from:%s\n"),$address;1009# TRANSLATORS: Make sure to include [q] [d] [e] in your1010# translation. The program will only accept English input1011# at this point.1012$_= ask(__("What to do with this address? ([q]uit|[d]rop|[e]dit): "),1013 valid_re =>qr/^(?:quit|q|drop|d|edit|e)/i,1014default=>'q');1015if(/^d/i) {1016returnundef;1017}elsif(/^q/i) {1018 cleanup_compose_files();1019exit(0);1020}1021$address= ask("$to_whom",1022default=>"",1023 valid_re =>qr/\@.*\./, confirm_only =>1);1024}1025return$address;1026}10271028sub validate_address_list {1029return(grep{defined$_}1030map{ validate_address($_) }@_);1031}10321033# Usually don't need to change anything below here.10341035# we make a "fake" message id by taking the current number1036# of seconds since the beginning of Unix time and tacking on1037# a random number to the end, in case we are called quicker than1038# 1 second since the last time we were called.10391040# We'll setup a template for the message id, using the "from" address:10411042my($message_id_stamp,$message_id_serial);1043sub make_message_id {1044my$uniq;1045if(!defined$message_id_stamp) {1046$message_id_stamp= strftime("%Y%m%d%H%M%S.$$",gmtime(time));1047$message_id_serial=0;1048}1049$message_id_serial++;1050$uniq="$message_id_stamp-$message_id_serial";10511052my$du_part;1053for($sender,$repocommitter,$repoauthor) {1054$du_part= extract_valid_address(sanitize_address($_));1055last if(defined$du_partand$du_partne'');1056}1057if(not defined$du_partor$du_parteq'') {1058require Sys::Hostname;1059$du_part='user@'. Sys::Hostname::hostname();1060}1061my$message_id_template="<%s-%s>";1062$message_id=sprintf($message_id_template,$uniq,$du_part);1063#print "new message id = $message_id\n"; # Was useful for debugging1064}1065106610671068$time=time-scalar$#files;10691070sub unquote_rfc2047 {1071local($_) =@_;1072my$charset;1073my$sep=qr/[ \t]+/;1074 s{$re_encoded_word(?:$sep$re_encoded_word)*}{1075my@words=split$sep,$&;1076foreach(@words) {1077m/$re_encoded_word/;1078$charset=$1;1079my$encoding=$2;1080my$text=$3;1081if($encodingeq'q'||$encodingeq'Q') {1082$_=$text;1083s/_/ /g;1084s/=([0-9A-F]{2})/chr(hex($1))/egi;1085}else{1086# other encodings not supported yet1087}1088}1089join'',@words;1090}eg;1091returnwantarray? ($_,$charset) :$_;1092}10931094sub quote_rfc2047 {1095local$_=shift;1096my$encoding=shift||'UTF-8';1097s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X",ord($1))/eg;1098s/(.*)/=\?$encoding\?q\?$1\?=/;1099return$_;1100}11011102sub is_rfc2047_quoted {1103my$s=shift;1104length($s) <=75&&1105$s=~m/^(?:"[[:ascii:]]*"|$re_encoded_word)$/o;1106}11071108sub subject_needs_rfc2047_quoting {1109my$s=shift;11101111return($s=~/[^[:ascii:]]/) || ($s=~/=\?/);1112}11131114sub quote_subject {1115local$subject=shift;1116my$encoding=shift||'UTF-8';11171118if(subject_needs_rfc2047_quoting($subject)) {1119return quote_rfc2047($subject,$encoding);1120}1121return$subject;1122}11231124# use the simplest quoting being able to handle the recipient1125sub sanitize_address {1126my($recipient) =@_;11271128# remove garbage after email address1129$recipient=~s/(.*>).*$/$1/;11301131my($recipient_name,$recipient_addr) = ($recipient=~/^(.*?)\s*(<.*)/);11321133if(not$recipient_name) {1134return$recipient;1135}11361137# if recipient_name is already quoted, do nothing1138if(is_rfc2047_quoted($recipient_name)) {1139return$recipient;1140}11411142# remove non-escaped quotes1143$recipient_name=~s/(^|[^\\])"/$1/g;11441145# rfc2047 is needed if a non-ascii char is included1146if($recipient_name=~/[^[:ascii:]]/) {1147$recipient_name= quote_rfc2047($recipient_name);1148}11491150# double quotes are needed if specials or CTLs are included1151elsif($recipient_name=~/[][()<>@,;:\\".\000-\037\177]/) {1152$recipient_name=~s/([\\\r])/\\$1/g;1153$recipient_name=qq["$recipient_name"];1154}11551156return"$recipient_name$recipient_addr";11571158}11591160sub strip_garbage_one_address {1161my($addr) =@_;1162chomp$addr;1163if($addr=~/^(("[^"]*"|[^"<]*)? *<[^>]*>).*/) {1164# "Foo Bar" <foobar@example.com> [possibly garbage here]1165# Foo Bar <foobar@example.com> [possibly garbage here]1166return$1;1167}1168if($addr=~/^(<[^>]*>).*/) {1169# <foo@example.com> [possibly garbage here]1170# if garbage contains other addresses, they are ignored.1171return$1;1172}1173if($addr=~/^([^"#,\s]*)/) {1174# address without quoting: remove anything after the address1175return$1;1176}1177return$addr;1178}11791180sub sanitize_address_list {1181return(map{ sanitize_address($_) }@_);1182}11831184sub process_address_list {1185my@addr_list=map{ parse_address_line($_) }@_;1186@addr_list= expand_aliases(@addr_list);1187@addr_list= sanitize_address_list(@addr_list);1188@addr_list= validate_address_list(@addr_list);1189return@addr_list;1190}11911192# Returns the local Fully Qualified Domain Name (FQDN) if available.1193#1194# Tightly configured MTAa require that a caller sends a real DNS1195# domain name that corresponds the IP address in the HELO/EHLO1196# handshake. This is used to verify the connection and prevent1197# spammers from trying to hide their identity. If the DNS and IP don't1198# match, the receiveing MTA may deny the connection.1199#1200# Here is a deny example of Net::SMTP with the default "localhost.localdomain"1201#1202# Net::SMTP=GLOB(0x267ec28)>>> EHLO localhost.localdomain1203# Net::SMTP=GLOB(0x267ec28)<<< 550 EHLO argument does not match calling host1204#1205# This maildomain*() code is based on ideas in Perl library Test::Reporter1206# /usr/share/perl5/Test/Reporter/Mail/Util.pm ==> sub _maildomain ()12071208sub valid_fqdn {1209my$domain=shift;1210returndefined$domain&& !($^Oeq'darwin'&&$domain=~/\.local$/) &&$domain=~/\./;1211}12121213sub maildomain_net {1214my$maildomain;12151216my$domain= Net::Domain::domainname();1217$maildomain=$domainif valid_fqdn($domain);12181219return$maildomain;1220}12211222sub maildomain_mta {1223my$maildomain;12241225formy$host(qw(mailhost localhost)) {1226my$smtp= Net::SMTP->new($host);1227if(defined$smtp) {1228my$domain=$smtp->domain;1229$smtp->quit;12301231$maildomain=$domainif valid_fqdn($domain);12321233last if$maildomain;1234}1235}12361237return$maildomain;1238}12391240sub maildomain {1241return maildomain_net() || maildomain_mta() ||'localhost.localdomain';1242}12431244sub smtp_host_string {1245if(defined$smtp_server_port) {1246return"$smtp_server:$smtp_server_port";1247}else{1248return$smtp_server;1249}1250}12511252# Returns 1 if authentication succeeded or was not necessary1253# (smtp_user was not specified), and 0 otherwise.12541255sub smtp_auth_maybe {1256if(!defined$smtp_authuser||$auth|| (defined$smtp_auth&&$smtp_autheq"none")) {1257return1;1258}12591260# Workaround AUTH PLAIN/LOGIN interaction defect1261# with Authen::SASL::Cyrus1262eval{1263require Authen::SASL;1264 Authen::SASL->import(qw(Perl));1265};12661267# Check mechanism naming as defined in:1268# https://tools.ietf.org/html/rfc4422#page-81269if($smtp_auth&&$smtp_auth!~/^(\b[A-Z0-9-_]{1,20}\s*)*$/) {1270die"invalid smtp auth: '${smtp_auth}'";1271}12721273# TODO: Authentication may fail not because credentials were1274# invalid but due to other reasons, in which we should not1275# reject credentials.1276$auth= Git::credential({1277'protocol'=>'smtp',1278'host'=> smtp_host_string(),1279'username'=>$smtp_authuser,1280# if there's no password, "git credential fill" will1281# give us one, otherwise it'll just pass this one.1282'password'=>$smtp_authpass1283},sub{1284my$cred=shift;12851286if($smtp_auth) {1287my$sasl= Authen::SASL->new(1288 mechanism =>$smtp_auth,1289 callback => {1290 user =>$cred->{'username'},1291 pass =>$cred->{'password'},1292 authname =>$cred->{'username'},1293}1294);12951296return!!$smtp->auth($sasl);1297}12981299return!!$smtp->auth($cred->{'username'},$cred->{'password'});1300});13011302return$auth;1303}13041305sub ssl_verify_params {1306eval{1307require IO::Socket::SSL;1308 IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/);1309};1310if($@) {1311print STDERR "Not using SSL_VERIFY_PEER due to out-of-date IO::Socket::SSL.\n";1312return;1313}13141315if(!defined$smtp_ssl_cert_path) {1316# use the OpenSSL defaults1317return(SSL_verify_mode => SSL_VERIFY_PEER());1318}13191320if($smtp_ssl_cert_patheq"") {1321return(SSL_verify_mode => SSL_VERIFY_NONE());1322}elsif(-d $smtp_ssl_cert_path) {1323return(SSL_verify_mode => SSL_VERIFY_PEER(),1324 SSL_ca_path =>$smtp_ssl_cert_path);1325}elsif(-f $smtp_ssl_cert_path) {1326return(SSL_verify_mode => SSL_VERIFY_PEER(),1327 SSL_ca_file =>$smtp_ssl_cert_path);1328}else{1329die sprintf(__("CA path\"%s\"does not exist"),$smtp_ssl_cert_path);1330}1331}13321333sub file_name_is_absolute {1334my($path) =@_;13351336# msys does not grok DOS drive-prefixes1337if($^Oeq'msys') {1338return($path=~ m#^/# || $path =~ m#^[a-zA-Z]\:#)1339}13401341require File::Spec::Functions;1342return File::Spec::Functions::file_name_is_absolute($path);1343}13441345# Prepares the email, then asks the user what to do.1346#1347# If the user chooses to send the email, it's sent and 1 is returned.1348# If the user chooses not to send the email, 0 is returned.1349# If the user decides they want to make further edits, -1 is returned and the1350# caller is expected to call send_message again after the edits are performed.1351#1352# If an error occurs sending the email, this just dies.13531354sub send_message {1355my@recipients= unique_email_list(@to);1356@cc= (grep{my$cc= extract_valid_address_or_die($_);1357not grep{$cceq$_||$_=~/<\Q${cc}\E>$/}@recipients1358}1359@cc);1360my$to=join(",\n\t",@recipients);1361@recipients= unique_email_list(@recipients,@cc,@bcclist);1362@recipients= (map{ extract_valid_address_or_die($_) }@recipients);1363my$date= format_2822_time($time++);1364my$gitversion='@@GIT_VERSION@@';1365if($gitversion=~m/..GIT_VERSION../) {1366$gitversion= Git::version();1367}13681369my$cc=join(",\n\t", unique_email_list(@cc));1370my$ccline="";1371if($ccne'') {1372$ccline="\nCc:$cc";1373}1374 make_message_id()unlessdefined($message_id);13751376my$header="From:$sender1377To:$to${ccline}1378Subject:$subject1379Date:$date1380Message-Id:$message_id1381";1382if($use_xmailer) {1383$header.="X-Mailer: git-send-email$gitversion\n";1384}1385if($in_reply_to) {13861387$header.="In-Reply-To:$in_reply_to\n";1388$header.="References:$references\n";1389}1390if($reply_to) {1391$header.="Reply-To:$reply_to\n";1392}1393if(@xh) {1394$header.=join("\n",@xh) ."\n";1395}13961397my@sendmail_parameters= ('-i',@recipients);1398my$raw_from=$sender;1399if(defined$envelope_sender&&$envelope_senderne"auto") {1400$raw_from=$envelope_sender;1401}1402$raw_from= extract_valid_address($raw_from);1403unshift(@sendmail_parameters,1404'-f',$raw_from)if(defined$envelope_sender);14051406if($needs_confirm&& !$dry_run) {1407print"\n$header\n";1408if($needs_confirmeq"inform") {1409$confirm_unconfigured=0;# squelch this message for the rest of this run1410$ask_default="y";# assume yes on EOF since user hasn't explicitly asked for confirmation1411print __ <<EOF ;1412 The Cc list above has been expanded by additional1413 addresses found in the patch commit message. By default1414 send-email prompts before sending whenever this occurs.1415 This behavior is controlled by the sendemail.confirm1416 configuration setting.14171418 For additional information, run 'git send-email --help'.1419 To retain the current behavior, but squelch this message,1420 run 'git config --global sendemail.confirm auto'.14211422EOF1423}1424# TRANSLATORS: Make sure to include [y] [n] [e] [q] [a] in your1425# translation. The program will only accept English input1426# at this point.1427$_= ask(__("Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): "),1428 valid_re =>qr/^(?:yes|y|no|n|edit|e|quit|q|all|a)/i,1429default=>$ask_default);1430die __("Send this email reply required")unlessdefined$_;1431if(/^n/i) {1432return0;1433}elsif(/^e/i) {1434return-1;1435}elsif(/^q/i) {1436 cleanup_compose_files();1437exit(0);1438}elsif(/^a/i) {1439$confirm='never';1440}1441}14421443unshift(@sendmail_parameters,@smtp_server_options);14441445if($dry_run) {1446# We don't want to send the email.1447}elsif(file_name_is_absolute($smtp_server)) {1448my$pid=open my$sm,'|-';1449defined$pidor die$!;1450if(!$pid) {1451exec($smtp_server,@sendmail_parameters)or die$!;1452}1453print$sm"$header\n$message";1454close$smor die$!;1455}else{14561457if(!defined$smtp_server) {1458die __("The required SMTP server is not properly defined.")1459}14601461require Net::SMTP;1462my$use_net_smtp_ssl= version->parse($Net::SMTP::VERSION) < version->parse("2.34");1463$smtp_domain||= maildomain();14641465if($smtp_encryptioneq'ssl') {1466$smtp_server_port||=465;# ssmtp1467require IO::Socket::SSL;14681469# Suppress "variable accessed once" warning.1470{1471no warnings 'once';1472$IO::Socket::SSL::DEBUG =1;1473}14741475# Net::SMTP::SSL->new() does not forward any SSL options1476 IO::Socket::SSL::set_client_defaults(1477 ssl_verify_params());14781479if($use_net_smtp_ssl) {1480require Net::SMTP::SSL;1481$smtp||= Net::SMTP::SSL->new($smtp_server,1482 Hello =>$smtp_domain,1483 Port =>$smtp_server_port,1484 Debug =>$debug_net_smtp);1485}1486else{1487$smtp||= Net::SMTP->new($smtp_server,1488 Hello =>$smtp_domain,1489 Port =>$smtp_server_port,1490 Debug =>$debug_net_smtp,1491 SSL =>1);1492}1493}1494elsif(!$smtp) {1495$smtp_server_port||=25;1496$smtp||= Net::SMTP->new($smtp_server,1497 Hello =>$smtp_domain,1498 Debug =>$debug_net_smtp,1499 Port =>$smtp_server_port);1500if($smtp_encryptioneq'tls'&&$smtp) {1501if($use_net_smtp_ssl) {1502$smtp->command('STARTTLS');1503$smtp->response();1504if($smtp->code!=220) {1505die sprintf(__("Server does not support STARTTLS!%s"),$smtp->message);1506}1507require Net::SMTP::SSL;1508$smtp= Net::SMTP::SSL->start_SSL($smtp,1509 ssl_verify_params())1510or die sprintf(__("STARTTLS failed!%s"), IO::Socket::SSL::errstr());1511}1512else{1513$smtp->starttls(ssl_verify_params())1514or die sprintf(__("STARTTLS failed!%s"), IO::Socket::SSL::errstr());1515}1516# Send EHLO again to receive fresh1517# supported commands1518$smtp->hello($smtp_domain);1519}1520}15211522if(!$smtp) {1523die __("Unable to initialize SMTP properly. Check config and use --smtp-debug."),1524" VALUES: server=$smtp_server",1525"encryption=$smtp_encryption",1526"hello=$smtp_domain",1527defined$smtp_server_port?" port=$smtp_server_port":"";1528}15291530 smtp_auth_maybe or die$smtp->message;15311532$smtp->mail($raw_from)or die$smtp->message;1533$smtp->to(@recipients)or die$smtp->message;1534$smtp->dataor die$smtp->message;1535$smtp->datasend("$header\n")or die$smtp->message;1536my@lines=split/^/,$message;1537foreachmy$line(@lines) {1538$smtp->datasend("$line")or die$smtp->message;1539}1540$smtp->dataend()or die$smtp->message;1541$smtp->code=~/250|200/or die sprintf(__("Failed to send%s\n"),$subject).$smtp->message;1542}1543if($quiet) {1544printf($dry_run? __("Dry-Sent%s\n") : __("Sent%s\n"),$subject);1545}else{1546print($dry_run? __("Dry-OK. Log says:\n") : __("OK. Log says:\n"));1547if(!file_name_is_absolute($smtp_server)) {1548print"Server:$smtp_server\n";1549print"MAIL FROM:<$raw_from>\n";1550foreachmy$entry(@recipients) {1551print"RCPT TO:<$entry>\n";1552}1553}else{1554print"Sendmail:$smtp_server".join(' ',@sendmail_parameters)."\n";1555}1556print$header,"\n";1557if($smtp) {1558print __("Result: "),$smtp->code,' ',1559($smtp->message=~/\n([^\n]+\n)$/s),"\n";1560}else{1561print __("Result: OK\n");1562}1563}15641565return1;1566}15671568$in_reply_to=$initial_in_reply_to;1569$references=$initial_in_reply_to||'';1570$subject=$initial_subject;1571$message_num=0;15721573# Prepares the email, prompts the user, sends it out1574# Returns 0 if an edit was done and the function should be called again, or 11575# otherwise.1576sub process_file {1577my($t) =@_;15781579open my$fh,"<",$tor die sprintf(__("can't open file%s"),$t);15801581my$author=undef;1582my$sauthor=undef;1583my$author_encoding;1584my$has_content_type;1585my$body_encoding;1586my$xfer_encoding;1587my$has_mime_version;1588@to= ();1589@cc= ();1590@xh= ();1591my$input_format=undef;1592my@header= ();1593$message="";1594$message_num++;1595# First unfold multiline header fields1596while(<$fh>) {1597last if/^\s*$/;1598if(/^\s+\S/and@header) {1599chomp($header[$#header]);1600s/^\s+/ /;1601$header[$#header] .=$_;1602}else{1603push(@header,$_);1604}1605}1606# Now parse the header1607foreach(@header) {1608if(/^From /) {1609$input_format='mbox';1610next;1611}1612chomp;1613if(!defined$input_format&&/^[-A-Za-z]+:\s/) {1614$input_format='mbox';1615}16161617if(defined$input_format&&$input_formateq'mbox') {1618if(/^Subject:\s+(.*)$/i) {1619$subject=$1;1620}1621elsif(/^From:\s+(.*)$/i) {1622($author,$author_encoding) = unquote_rfc2047($1);1623$sauthor= sanitize_address($author);1624next if$suppress_cc{'author'};1625next if$suppress_cc{'self'}and$sauthoreq$sender;1626printf(__("(mbox) Adding cc:%sfrom line '%s'\n"),1627$1,$_)unless$quiet;1628push@cc,$1;1629}1630elsif(/^To:\s+(.*)$/i) {1631foreachmy$addr(parse_address_line($1)) {1632printf(__("(mbox) Adding to:%sfrom line '%s'\n"),1633$addr,$_)unless$quiet;1634push@to,$addr;1635}1636}1637elsif(/^Cc:\s+(.*)$/i) {1638foreachmy$addr(parse_address_line($1)) {1639my$qaddr= unquote_rfc2047($addr);1640my$saddr= sanitize_address($qaddr);1641if($saddreq$sender) {1642next if($suppress_cc{'self'});1643}else{1644next if($suppress_cc{'cc'});1645}1646printf(__("(mbox) Adding cc:%sfrom line '%s'\n"),1647$addr,$_)unless$quiet;1648push@cc,$addr;1649}1650}1651elsif(/^Content-type:/i) {1652$has_content_type=1;1653if(/charset="?([^ "]+)/) {1654$body_encoding=$1;1655}1656push@xh,$_;1657}1658elsif(/^MIME-Version/i) {1659$has_mime_version=1;1660push@xh,$_;1661}1662elsif(/^Message-Id: (.*)/i) {1663$message_id=$1;1664}1665elsif(/^Content-Transfer-Encoding: (.*)/i) {1666$xfer_encoding=$1ifnot defined$xfer_encoding;1667}1668elsif(/^In-Reply-To: (.*)/i) {1669$in_reply_to=$1;1670}1671elsif(/^References: (.*)/i) {1672$references=$1;1673}1674elsif(!/^Date:\s/i&&/^[-A-Za-z]+:\s+\S/) {1675push@xh,$_;1676}1677}else{1678# In the traditional1679# "send lots of email" format,1680# line 1 = cc1681# line 2 = subject1682# So let's support that, too.1683$input_format='lots';1684if(@cc==0&& !$suppress_cc{'cc'}) {1685printf(__("(non-mbox) Adding cc:%sfrom line '%s'\n"),1686$_,$_)unless$quiet;1687push@cc,$_;1688}elsif(!defined$subject) {1689$subject=$_;1690}1691}1692}1693# Now parse the message body1694while(<$fh>) {1695$message.=$_;1696if(/^([a-z-]*-by|Cc): (.*)/i) {1697chomp;1698my($what,$c) = ($1,$2);1699# strip garbage for the address we'll use:1700$c= strip_garbage_one_address($c);1701# sanitize a bit more to decide whether to suppress the address:1702my$sc= sanitize_address($c);1703if($sceq$sender) {1704next if($suppress_cc{'self'});1705}else{1706if($what=~/^Signed-off-by$/i) {1707next if$suppress_cc{'sob'};1708}elsif($what=~/-by$/i) {1709next if$suppress_cc{'misc-by'};1710}elsif($what=~/Cc/i) {1711next if$suppress_cc{'bodycc'};1712}1713}1714if($c!~/.+@.+|<.+>/) {1715printf("(body) Ignoring%sfrom line '%s'\n",1716$what,$_)unless$quiet;1717next;1718}1719push@cc,$c;1720printf(__("(body) Adding cc:%sfrom line '%s'\n"),1721$c,$_)unless$quiet;1722}1723}1724close$fh;17251726push@to, recipients_cmd("to-cmd","to",$to_cmd,$t)1727ifdefined$to_cmd;1728push@cc, recipients_cmd("cc-cmd","cc",$cc_cmd,$t)1729ifdefined$cc_cmd&& !$suppress_cc{'cccmd'};17301731if($broken_encoding{$t} && !$has_content_type) {1732$xfer_encoding='8bit'ifnot defined$xfer_encoding;1733$has_content_type=1;1734push@xh,"Content-Type: text/plain; charset=$auto_8bit_encoding";1735$body_encoding=$auto_8bit_encoding;1736}17371738if($broken_encoding{$t} && !is_rfc2047_quoted($subject)) {1739$subject= quote_subject($subject,$auto_8bit_encoding);1740}17411742if(defined$sauthorand$sauthorne$sender) {1743$message="From:$author\n\n$message";1744if(defined$author_encoding) {1745if($has_content_type) {1746if($body_encodingeq$author_encoding) {1747# ok, we already have the right encoding1748}1749else{1750# uh oh, we should re-encode1751}1752}1753else{1754$xfer_encoding='8bit'ifnot defined$xfer_encoding;1755$has_content_type=1;1756push@xh,1757"Content-Type: text/plain; charset=$author_encoding";1758}1759}1760}1761$xfer_encoding='8bit'ifnot defined$xfer_encoding;1762($message,$xfer_encoding) = apply_transfer_encoding(1763$message,$xfer_encoding,$target_xfer_encoding);1764push@xh,"Content-Transfer-Encoding:$xfer_encoding";1765unshift@xh,'MIME-Version: 1.0'unless$has_mime_version;17661767$needs_confirm= (1768$confirmeq"always"or1769($confirm=~/^(?:auto|cc)$/&&@cc)or1770($confirm=~/^(?:auto|compose)$/&&$compose&&$message_num==1));1771$needs_confirm="inform"if($needs_confirm&&$confirm_unconfigured&&@cc);17721773@to= process_address_list(@to);1774@cc= process_address_list(@cc);17751776@to= (@initial_to,@to);1777@cc= (@initial_cc,@cc);17781779if($message_num==1) {1780if(defined$cover_ccand$cover_cc) {1781@initial_cc=@cc;1782}1783if(defined$cover_toand$cover_to) {1784@initial_to=@to;1785}1786}17871788my$message_was_sent= send_message();1789if($message_was_sent== -1) {1790 do_edit($t);1791return0;1792}17931794# set up for the next message1795if($thread&&$message_was_sent&&1796($chain_reply_to|| !defined$in_reply_to||length($in_reply_to) ==0||1797$message_num==1)) {1798$in_reply_to=$message_id;1799if(length$references>0) {1800$references.="\n$message_id";1801}else{1802$references="$message_id";1803}1804}1805$message_id=undef;1806$num_sent++;1807if(defined$batch_size&&$num_sent==$batch_size) {1808$num_sent=0;1809$smtp->quitifdefined$smtp;1810undef$smtp;1811undef$auth;1812sleep($relogin_delay)ifdefined$relogin_delay;1813}18141815return1;1816}18171818foreachmy$t(@files) {1819while(!process_file($t)) {1820# user edited the file1821}1822}18231824# Execute a command (e.g. $to_cmd) to get a list of email addresses1825# and return a results array1826sub recipients_cmd {1827my($prefix,$what,$cmd,$file) =@_;18281829my@addresses= ();1830open my$fh,"-|","$cmd\Q$file\E"1831or die sprintf(__("(%s) Could not execute '%s'"),$prefix,$cmd);1832while(my$address= <$fh>) {1833$address=~s/^\s*//g;1834$address=~s/\s*$//g;1835$address= sanitize_address($address);1836next if($addresseq$senderand$suppress_cc{'self'});1837push@addresses,$address;1838printf(__("(%s) Adding%s:%sfrom: '%s'\n"),1839$prefix,$what,$address,$cmd)unless$quiet;1840}1841close$fh1842or die sprintf(__("(%s) failed to close pipe to '%s'"),$prefix,$cmd);1843return@addresses;1844}18451846cleanup_compose_files();18471848sub cleanup_compose_files {1849unlink($compose_filename,$compose_filename.".final")if$compose;1850}18511852$smtp->quitif$smtp;18531854sub apply_transfer_encoding {1855my$message=shift;1856my$from=shift;1857my$to=shift;18581859return($message,$to)if($fromeq$toand$fromne'7bit');18601861require MIME::QuotedPrint;1862require MIME::Base64;18631864$message= MIME::QuotedPrint::decode($message)1865if($fromeq'quoted-printable');1866$message= MIME::Base64::decode($message)1867if($fromeq'base64');18681869$to= ($message=~/.{999,}/) ?'quoted-printable':'8bit'1870if$toeq'auto';18711872die __("cannot send message as 7bit")1873if($toeq'7bit'and$message=~/[^[:ascii:]]/);1874return($message,$to)1875if($toeq'7bit'or$toeq'8bit');1876return(MIME::QuotedPrint::encode($message,"\n",0),$to)1877if($toeq'quoted-printable');1878return(MIME::Base64::encode($message,"\n"),$to)1879if($toeq'base64');1880die __("invalid transfer encoding");1881}18821883sub unique_email_list {1884my%seen;1885my@emails;18861887foreachmy$entry(@_) {1888my$clean= extract_valid_address_or_die($entry);1889$seen{$clean} ||=0;1890next if$seen{$clean}++;1891push@emails,$entry;1892}1893return@emails;1894}18951896sub validate_patch {1897my($fn,$xfer_encoding) =@_;18981899if($repo) {1900my$validate_hook= catfile(catdir($repo->repo_path(),'hooks'),1901'sendemail-validate');1902my$hook_error;1903if(-x $validate_hook) {1904my$target= abs_path($fn);1905# The hook needs a correct cwd and GIT_DIR.1906my$cwd_save= cwd();1907chdir($repo->wc_path()or$repo->repo_path())1908or die("chdir:$!");1909local$ENV{"GIT_DIR"} =$repo->repo_path();1910$hook_error="rejected by sendemail-validate hook"1911ifsystem($validate_hook,$target);1912chdir($cwd_save)or die("chdir:$!");1913}1914return$hook_errorif$hook_error;1915}19161917# Any long lines will be automatically fixed if we use a suitable transfer1918# encoding.1919unless($xfer_encoding=~/^(?:auto|quoted-printable|base64)$/) {1920open(my$fh,'<',$fn)1921or die sprintf(__("unable to open%s:%s\n"),$fn,$!);1922while(my$line= <$fh>) {1923if(length($line) >998) {1924returnsprintf(__("%s: patch contains a line longer than 998 characters"), $.);1925}1926}1927}1928return;1929}19301931sub handle_backup {1932my($last,$lastlen,$file,$known_suffix) =@_;1933my($suffix,$skip);19341935$skip=0;1936if(defined$last&&1937($lastlen<length($file)) &&1938(substr($file,0,$lastlen)eq$last) &&1939($suffix=substr($file,$lastlen)) !~/^[a-z0-9]/i) {1940if(defined$known_suffix&&$suffixeq$known_suffix) {1941printf(__("Skipping%swith backup suffix '%s'.\n"),$file,$known_suffix);1942$skip=1;1943}else{1944# TRANSLATORS: please keep "[y|N]" as is.1945my$answer= ask(sprintf(__("Do you really want to send%s?[y|N]: "),$file),1946 valid_re =>qr/^(?:y|n)/i,1947default=>'n');1948$skip= ($answerne'y');1949if($skip) {1950$known_suffix=$suffix;1951}1952}1953}1954return($skip,$known_suffix);1955}19561957sub handle_backup_files {1958my@file=@_;1959my($last,$lastlen,$known_suffix,$skip,@result);1960formy$file(@file) {1961($skip,$known_suffix) = handle_backup($last,$lastlen,1962$file,$known_suffix);1963push@result,$fileunless$skip;1964$last=$file;1965$lastlen=length($file);1966}1967return@result;1968}19691970sub file_has_nonascii {1971my$fn=shift;1972open(my$fh,'<',$fn)1973or die sprintf(__("unable to open%s:%s\n"),$fn,$!);1974while(my$line= <$fh>) {1975return1if$line=~/[^[:ascii:]]/;1976}1977return0;1978}19791980sub body_or_subject_has_nonascii {1981my$fn=shift;1982open(my$fh,'<',$fn)1983or die sprintf(__("unable to open%s:%s\n"),$fn,$!);1984while(my$line= <$fh>) {1985last if$line=~/^$/;1986return1if$line=~/^Subject.*[^[:ascii:]]/;1987}1988while(my$line= <$fh>) {1989return1if$line=~/[^[:ascii:]]/;1990}1991return0;1992}