1#!/usr/bin/perl -w 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 19use strict; 20use warnings; 21use Term::ReadLine; 22use Getopt::Long; 23use Text::ParseWords; 24use Data::Dumper; 25use Term::ANSIColor; 26use Git; 27 28package FakeTerm; 29sub new { 30my($class,$reason) =@_; 31returnbless \$reason,shift; 32} 33subreadline{ 34my$self=shift; 35die"Cannot use readline on FakeTerm:$$self"; 36} 37package main; 38 39 40sub usage { 41print<<EOT; 42git send-email [options] <file | directory>... 43Options: 44 --from Specify the "From:" line of the email to be sent. 45 46 --to Specify the primary "To:" line of the email. 47 48 --cc Specify an initial "Cc:" list for the entire series 49 of emails. 50 51 --cc-cmd Specify a command to execute per file which adds 52 per file specific cc address entries 53 54 --bcc Specify a list of email addresses that should be Bcc: 55 on all the emails. 56 57 --compose Use \$GIT_EDITOR, core.editor, \$EDITOR, or \$VISUALto edit 58 an introductory message for the patch series. 59 60 --subject Specify the initial "Subject:" line. 61 Only necessary if --compose is also set. If --compose 62 is not set, this will be prompted for. 63 64 --in-reply-to Specify the first "In-Reply-To:" header line. 65 Only used if --compose is also set. If --compose is not 66 set, this will be prompted for. 67 68 --chain-reply-to If set, the replies will all be to the previous 69 email sent, rather than to the first email sent. 70 Defaults to on. 71 72 --signed-off-cc Automatically add email addresses that appear in 73 Signed-off-by: or Cc: lines to the cc: list. Defaults to on. 74 75 --identity The configuration identity, a subsection to prioritise over 76 the default section. 77 78 --smtp-server If set, specifies the outgoing SMTP server to use. 79 Defaults to localhost. Port number can be specified here with 80 hostname:port format or by using --smtp-server-port option. 81 82 --smtp-server-port Specify a port on the outgoing SMTP server to connect to. 83 84 --smtp-user The username for SMTP-AUTH. 85 86 --smtp-pass The password for SMTP-AUTH. 87 88 --smtp-encryption Specify 'tls' for STARTTLS encryption, or 'ssl' for SSL. 89 Any other value disables the feature. 90 91 --smtp-ssl Synonym for '--smtp-encryption=ssl'. Deprecated. 92 93 --suppress-cc Suppress the specified category of auto-CC. The category 94 can be one of 'author' for the patch author, 'self' to 95 avoid copying yourself, 'sob' for Signed-off-by lines, 96 'cccmd' for the output of the cccmd, or 'all' to suppress 97 all of these. 98 99 --suppress-from Suppress sending emails to yourself. Defaults to off. 100 101 --thread Specify that the "In-Reply-To:" header should be set on all 102 emails. Defaults to on. 103 104 --quiet Make git-send-email less verbose. One line per email 105 should be all that is output. 106 107 --dry-run Do everything except actually send the emails. 108 109 --envelope-sender Specify the envelope sender used to send the emails. 110 111 --no-validate Don't perform any sanity checks on patches. 112 113EOT 114exit(1); 115} 116 117# most mail servers generate the Date: header, but not all... 118sub format_2822_time { 119my($time) =@_; 120my@localtm=localtime($time); 121my@gmttm=gmtime($time); 122my$localmin=$localtm[1] +$localtm[2] *60; 123my$gmtmin=$gmttm[1] +$gmttm[2] *60; 124if($localtm[0] !=$gmttm[0]) { 125die"local zone differs from GMT by a non-minute interval\n"; 126} 127if((($gmttm[6] +1) %7) ==$localtm[6]) { 128$localmin+=1440; 129}elsif((($gmttm[6] -1) %7) ==$localtm[6]) { 130$localmin-=1440; 131}elsif($gmttm[6] !=$localtm[6]) { 132die"local time offset greater than or equal to 24 hours\n"; 133} 134my$offset=$localmin-$gmtmin; 135my$offhour=$offset/60; 136my$offmin=abs($offset%60); 137if(abs($offhour) >=24) { 138die("local time offset greater than or equal to 24 hours\n"); 139} 140 141returnsprintf("%s,%2d%s%d%02d:%02d:%02d%s%02d%02d", 142qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]], 143$localtm[3], 144qw(Jan Feb Mar Apr May Jun 145 Jul Aug Sep Oct Nov Dec)[$localtm[4]], 146$localtm[5]+1900, 147$localtm[2], 148$localtm[1], 149$localtm[0], 150($offset>=0) ?'+':'-', 151abs($offhour), 152$offmin, 153); 154} 155 156my$have_email_valid=eval{require Email::Valid;1}; 157my$smtp; 158my$auth; 159 160sub unique_email_list(@); 161sub cleanup_compose_files(); 162 163# Constants (essentially) 164my$compose_filename=".msg.$$"; 165 166# Variables we fill in automatically, or via prompting: 167my(@to,@cc,@initial_cc,@bcclist,@xh, 168$initial_reply_to,$initial_subject,@files,$author,$sender,$smtp_authpass,$compose,$time); 169 170my$envelope_sender; 171 172# Example reply to: 173#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; 174 175my$repo=eval{ Git->repository() }; 176my@repo=$repo? ($repo) : (); 177my$term=eval{ 178$ENV{"GIT_SEND_EMAIL_NOTTY"} 179? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT 180: new Term::ReadLine 'git-send-email'; 181}; 182if($@) { 183$term= new FakeTerm "$@: going non-interactive"; 184} 185 186# Behavior modification variables 187my($quiet,$dry_run) = (0,0); 188 189# Variables with corresponding config settings 190my($thread,$chain_reply_to,$suppress_from,$signed_off_cc,$cc_cmd); 191my($smtp_server,$smtp_server_port,$smtp_authuser,$smtp_encryption); 192my($identity,$aliasfiletype,@alias_files,@smtp_host_parts); 193my($no_validate); 194my(@suppress_cc); 195 196my%config_bool_settings= ( 197"thread"=> [\$thread,1], 198"chainreplyto"=> [\$chain_reply_to,1], 199"suppressfrom"=> [\$suppress_from,undef], 200"signedoffcc"=> [\$signed_off_cc,undef], 201); 202 203my%config_settings= ( 204"smtpserver"=> \$smtp_server, 205"smtpserverport"=> \$smtp_server_port, 206"smtpuser"=> \$smtp_authuser, 207"smtppass"=> \$smtp_authpass, 208"to"=> \@to, 209"cc"=> \@initial_cc, 210"cccmd"=> \$cc_cmd, 211"aliasfiletype"=> \$aliasfiletype, 212"bcc"=> \@bcclist, 213"aliasesfile"=> \@alias_files, 214"suppresscc"=> \@suppress_cc, 215"envelopesender"=> \$envelope_sender, 216); 217 218# Handle Uncouth Termination 219sub signal_handler { 220 221# Make text normal 222print color("reset"),"\n"; 223 224# SMTP password masked 225system"stty echo"; 226 227# tmp files from --compose 228if(-e $compose_filename) { 229print"'$compose_filename' contains an intermediate version of the email you were composing.\n"; 230} 231if(-e ($compose_filename.".final")) { 232print"'$compose_filename.final' contains the composed email.\n" 233} 234 235exit; 236}; 237 238$SIG{TERM} = \&signal_handler; 239$SIG{INT} = \&signal_handler; 240 241# Begin by accumulating all the variables (defined above), that we will end up 242# needing, first, from the command line: 243 244my$rc= GetOptions("sender|from=s"=> \$sender, 245"in-reply-to=s"=> \$initial_reply_to, 246"subject=s"=> \$initial_subject, 247"to=s"=> \@to, 248"cc=s"=> \@initial_cc, 249"bcc=s"=> \@bcclist, 250"chain-reply-to!"=> \$chain_reply_to, 251"smtp-server=s"=> \$smtp_server, 252"smtp-server-port=s"=> \$smtp_server_port, 253"smtp-user=s"=> \$smtp_authuser, 254"smtp-pass:s"=> \$smtp_authpass, 255"smtp-ssl"=>sub{$smtp_encryption='ssl'}, 256"smtp-encryption=s"=> \$smtp_encryption, 257"identity=s"=> \$identity, 258"compose"=> \$compose, 259"quiet"=> \$quiet, 260"cc-cmd=s"=> \$cc_cmd, 261"suppress-from!"=> \$suppress_from, 262"suppress-cc=s"=> \@suppress_cc, 263"signed-off-cc|signed-off-by-cc!"=> \$signed_off_cc, 264"dry-run"=> \$dry_run, 265"envelope-sender=s"=> \$envelope_sender, 266"thread!"=> \$thread, 267"no-validate"=> \$no_validate, 268); 269 270unless($rc) { 271 usage(); 272} 273 274# Now, let's fill any that aren't set in with defaults: 275 276sub read_config { 277my($prefix) =@_; 278 279foreachmy$setting(keys%config_bool_settings) { 280my$target=$config_bool_settings{$setting}->[0]; 281$$target= Git::config_bool(@repo,"$prefix.$setting")unless(defined$$target); 282} 283 284foreachmy$setting(keys%config_settings) { 285my$target=$config_settings{$setting}; 286if(ref($target)eq"ARRAY") { 287unless(@$target) { 288my@values= Git::config(@repo,"$prefix.$setting"); 289@$target=@valuesif(@values&&defined$values[0]); 290} 291} 292else{ 293$$target= Git::config(@repo,"$prefix.$setting")unless(defined$$target); 294} 295} 296 297if(!defined$smtp_encryption) { 298my$enc= Git::config(@repo,"$prefix.smtpencryption"); 299if(defined$enc) { 300$smtp_encryption=$enc; 301}elsif(Git::config_bool(@repo,"$prefix.smtpssl")) { 302$smtp_encryption='ssl'; 303} 304} 305} 306 307# read configuration from [sendemail "$identity"], fall back on [sendemail] 308$identity= Git::config(@repo,"sendemail.identity")unless(defined$identity); 309read_config("sendemail.$identity")if(defined$identity); 310read_config("sendemail"); 311 312# fall back on builtin bool defaults 313foreachmy$setting(values%config_bool_settings) { 314${$setting->[0]} =$setting->[1]unless(defined(${$setting->[0]})); 315} 316 317# 'default' encryption is none -- this only prevents a warning 318$smtp_encryption=''unless(defined$smtp_encryption); 319 320# Set CC suppressions 321my(%suppress_cc); 322if(@suppress_cc) { 323foreachmy$entry(@suppress_cc) { 324die"Unknown --suppress-cc field: '$entry'\n" 325unless$entry=~/^(all|cccmd|cc|author|self|sob)$/; 326$suppress_cc{$entry} =1; 327} 328} 329 330if($suppress_cc{'all'}) { 331foreachmy$entry(qw (ccmd cc author self sob)) { 332$suppress_cc{$entry} =1; 333} 334delete$suppress_cc{'all'}; 335} 336 337# If explicit old-style ones are specified, they trump --suppress-cc. 338$suppress_cc{'self'} =$suppress_fromifdefined$suppress_from; 339$suppress_cc{'sob'} = !$signed_off_ccifdefined$signed_off_cc; 340 341# Debugging, print out the suppressions. 342if(0) { 343print"suppressions:\n"; 344foreachmy$entry(keys%suppress_cc) { 345printf" %-5s ->$suppress_cc{$entry}\n",$entry; 346} 347} 348 349my($repoauthor,$repocommitter); 350($repoauthor) = Git::ident_person(@repo,'author'); 351($repocommitter) = Git::ident_person(@repo,'committer'); 352 353# Verify the user input 354 355foreachmy$entry(@to) { 356die"Comma in --to entry:$entry'\n"unless$entry!~m/,/; 357} 358 359foreachmy$entry(@initial_cc) { 360die"Comma in --cc entry:$entry'\n"unless$entry!~m/,/; 361} 362 363foreachmy$entry(@bcclist) { 364die"Comma in --bcclist entry:$entry'\n"unless$entry!~m/,/; 365} 366 367sub split_addrs { 368return parse_line('\s*,\s*',1,@_); 369} 370 371my%aliases; 372my%parse_alias= ( 373# multiline formats can be supported in the future 374 mutt =>sub{my$fh=shift;while(<$fh>) { 375if(/^\s*alias\s+(\S+)\s+(.*)$/) { 376my($alias,$addr) = ($1,$2); 377$addr=~s/#.*$//;# mutt allows # comments 378# commas delimit multiple addresses 379$aliases{$alias} = [ split_addrs($addr) ]; 380}}}, 381 mailrc =>sub{my$fh=shift;while(<$fh>) { 382if(/^alias\s+(\S+)\s+(.*)$/) { 383# spaces delimit multiple addresses 384$aliases{$1} = [split(/\s+/,$2) ]; 385}}}, 386 pine =>sub{my$fh=shift;while(<$fh>) { 387if(/^(\S+)\t.*\t(.*)$/) { 388$aliases{$1} = [ split_addrs($2) ]; 389}}}, 390 gnus =>sub{my$fh=shift;while(<$fh>) { 391if(/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) { 392$aliases{$1} = [$2]; 393}}} 394); 395 396if(@alias_filesand$aliasfiletypeand defined$parse_alias{$aliasfiletype}) { 397foreachmy$file(@alias_files) { 398open my$fh,'<',$fileor die"opening$file:$!\n"; 399$parse_alias{$aliasfiletype}->($fh); 400close$fh; 401} 402} 403 404($sender) = expand_aliases($sender)ifdefined$sender; 405 406# Now that all the defaults are set, process the rest of the command line 407# arguments and collect up the files that need to be processed. 408formy$f(@ARGV) { 409if(-d $f) { 410opendir(DH,$f) 411or die"Failed to opendir$f:$!"; 412 413push@files,grep{ -f $_}map{ +$f."/".$_} 414sort readdir(DH); 415closedir(DH); 416}elsif(-f $for-p $f) { 417push@files,$f; 418}else{ 419print STDERR "Skipping$f- not found.\n"; 420} 421} 422 423if(!$no_validate) { 424foreachmy$f(@files) { 425unless(-p $f) { 426my$error= validate_patch($f); 427$errorand die"fatal:$f:$error\nwarning: no patches were sent\n"; 428} 429} 430} 431 432if(@files) { 433unless($quiet) { 434print$_,"\n"for(@files); 435} 436}else{ 437print STDERR "\nNo patch files specified!\n\n"; 438 usage(); 439} 440 441my$prompting=0; 442if(!defined$sender) { 443$sender=$repoauthor||$repocommitter||''; 444 445while(1) { 446$_=$term->readline("Who should the emails appear to be from? [$sender] "); 447last ifdefined$_; 448print"\n"; 449} 450 451$sender=$_if($_); 452print"Emails will be sent from: ",$sender,"\n"; 453$prompting++; 454} 455 456if(!@to) { 457 458 459while(1) { 460$_=$term->readline("Who should the emails be sent to? ",""); 461last ifdefined$_; 462print"\n"; 463} 464 465my$to=$_; 466push@to, split_addrs($to); 467$prompting++; 468} 469 470sub expand_aliases { 471my@cur=@_; 472my@last; 473do{ 474@last=@cur; 475@cur=map{$aliases{$_} ? @{$aliases{$_}} :$_}@last; 476}while(join(',',@cur)ne join(',',@last)); 477return@cur; 478} 479 480@to= expand_aliases(@to); 481@to= (map{ sanitize_address($_) }@to); 482@initial_cc= expand_aliases(@initial_cc); 483@bcclist= expand_aliases(@bcclist); 484 485if(!defined$initial_subject&&$compose) { 486while(1) { 487$_=$term->readline("What subject should the initial email start with? ",$initial_subject); 488last ifdefined$_; 489print"\n"; 490} 491 492$initial_subject=$_; 493$prompting++; 494} 495 496if($thread&& !defined$initial_reply_to&&$prompting) { 497while(1) { 498$_=$term->readline("Message-ID to be used as In-Reply-To for the first email? ",$initial_reply_to); 499last ifdefined$_; 500print"\n"; 501} 502 503$initial_reply_to=$_; 504} 505if(defined$initial_reply_to) { 506$initial_reply_to=~s/^\s*<?//; 507$initial_reply_to=~s/>?\s*$//; 508$initial_reply_to="<$initial_reply_to>"if$initial_reply_tone''; 509} 510 511if(!defined$smtp_server) { 512foreach(qw( /usr/sbin/sendmail /usr/lib/sendmail )) { 513if(-x $_) { 514$smtp_server=$_; 515last; 516} 517} 518$smtp_server||='localhost';# could be 127.0.0.1, too... *shrug* 519} 520 521if($compose) { 522# Note that this does not need to be secure, but we will make a small 523# effort to have it be unique 524open(C,">",$compose_filename) 525or die"Failed to open for writing$compose_filename:$!"; 526print C "From$sender# This line is ignored.\n"; 527printf C "Subject:%s\n\n",$initial_subject; 528printf C <<EOT; 529GIT: Please enter your email below. 530GIT: Lines beginning in "GIT: " will be removed. 531GIT: Consider including an overall diffstat or table of contents 532GIT: for the patch you are writing. 533 534EOT 535close(C); 536 537my$editor=$ENV{GIT_EDITOR} || Git::config(@repo,"core.editor") ||$ENV{VISUAL} ||$ENV{EDITOR} ||"vi"; 538system('sh','-c',$editor.' "$@"',$editor,$compose_filename); 539 540open(C2,">",$compose_filename.".final") 541or die"Failed to open$compose_filename.final : ".$!; 542 543open(C,"<",$compose_filename) 544or die"Failed to open$compose_filename: ".$!; 545 546my$need_8bit_cte= file_has_nonascii($compose_filename); 547my$in_body=0; 548while(<C>) { 549next ifm/^GIT: /; 550if(!$in_body&&/^\n$/) { 551$in_body=1; 552if($need_8bit_cte) { 553print C2 "MIME-Version: 1.0\n", 554"Content-Type: text/plain; ", 555"charset=utf-8\n", 556"Content-Transfer-Encoding: 8bit\n"; 557} 558} 559if(!$in_body&&/^MIME-Version:/i) { 560$need_8bit_cte=0; 561} 562if(!$in_body&&/^Subject: ?(.*)/i) { 563my$subject=$1; 564$_="Subject: ". 565($subject=~/[^[:ascii:]]/? 566 quote_rfc2047($subject) : 567$subject) . 568"\n"; 569} 570print C2 $_; 571} 572close(C); 573close(C2); 574 575while(1) { 576$_=$term->readline("Send this email? (y|n) "); 577last ifdefined$_; 578print"\n"; 579} 580 581if(uc substr($_,0,1)ne'Y') { 582 cleanup_compose_files(); 583exit(0); 584} 585 586@files= ($compose_filename.".final",@files); 587} 588 589# Variables we set as part of the loop over files 590our($message_id,%mail,$subject,$reply_to,$references,$message); 591 592sub extract_valid_address { 593my$address=shift; 594my$local_part_regexp='[^<>"\s@]+'; 595my$domain_regexp='[^.<>"\s@]+(?:\.[^.<>"\s@]+)+'; 596 597# check for a local address: 598return$addressif($address=~/^($local_part_regexp)$/); 599 600$address=~s/^\s*<(.*)>\s*$/$1/; 601if($have_email_valid) { 602returnscalar Email::Valid->address($address); 603}else{ 604# less robust/correct than the monster regexp in Email::Valid, 605# but still does a 99% job, and one less dependency 606$address=~/($local_part_regexp\@$domain_regexp)/; 607return$1; 608} 609} 610 611# Usually don't need to change anything below here. 612 613# we make a "fake" message id by taking the current number 614# of seconds since the beginning of Unix time and tacking on 615# a random number to the end, in case we are called quicker than 616# 1 second since the last time we were called. 617 618# We'll setup a template for the message id, using the "from" address: 619 620my($message_id_stamp,$message_id_serial); 621sub make_message_id 622{ 623my$uniq; 624if(!defined$message_id_stamp) { 625$message_id_stamp=sprintf("%s-%s",time,$$); 626$message_id_serial=0; 627} 628$message_id_serial++; 629$uniq="$message_id_stamp-$message_id_serial"; 630 631my$du_part; 632for($sender,$repocommitter,$repoauthor) { 633$du_part= extract_valid_address(sanitize_address($_)); 634last if(defined$du_partand$du_partne''); 635} 636if(not defined$du_partor$du_parteq'') { 637use Sys::Hostname qw(); 638$du_part='user@'. Sys::Hostname::hostname(); 639} 640my$message_id_template="<%s-git-send-email-%s>"; 641$message_id=sprintf($message_id_template,$uniq,$du_part); 642#print "new message id = $message_id\n"; # Was useful for debugging 643} 644 645 646 647$time=time-scalar$#files; 648 649sub unquote_rfc2047 { 650local($_) =@_; 651my$encoding; 652if(s/=\?([^?]+)\?q\?(.*)\?=/$2/g) { 653$encoding=$1; 654s/_/ /g; 655s/=([0-9A-F]{2})/chr(hex($1))/eg; 656} 657returnwantarray? ($_,$encoding) :$_; 658} 659 660sub quote_rfc2047 { 661local$_=shift; 662my$encoding=shift||'utf-8'; 663s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X",ord($1))/eg; 664s/(.*)/=\?$encoding\?q\?$1\?=/; 665return$_; 666} 667 668# use the simplest quoting being able to handle the recipient 669sub sanitize_address 670{ 671my($recipient) =@_; 672my($recipient_name,$recipient_addr) = ($recipient=~/^(.*?)\s*(<.*)/); 673 674if(not$recipient_name) { 675return"$recipient"; 676} 677 678# if recipient_name is already quoted, do nothing 679if($recipient_name=~/^(".*"|=\?utf-8\?q\?.*\?=)$/) { 680return$recipient; 681} 682 683# rfc2047 is needed if a non-ascii char is included 684if($recipient_name=~/[^[:ascii:]]/) { 685$recipient_name= quote_rfc2047($recipient_name); 686} 687 688# double quotes are needed if specials or CTLs are included 689elsif($recipient_name=~/[][()<>@,;:\\".\000-\037\177]/) { 690$recipient_name=~s/(["\\\r])/\\$1/g; 691$recipient_name="\"$recipient_name\""; 692} 693 694return"$recipient_name$recipient_addr"; 695 696} 697 698sub send_message 699{ 700my@recipients= unique_email_list(@to); 701@cc= (grep{my$cc= extract_valid_address($_); 702not grep{$cceq$_}@recipients 703} 704map{ sanitize_address($_) } 705@cc); 706my$to=join(",\n\t",@recipients); 707@recipients= unique_email_list(@recipients,@cc,@bcclist); 708@recipients= (map{ extract_valid_address($_) }@recipients); 709my$date= format_2822_time($time++); 710my$gitversion='@@GIT_VERSION@@'; 711if($gitversion=~m/..GIT_VERSION../) { 712$gitversion= Git::version(); 713} 714 715my$cc=join(", ", unique_email_list(@cc)); 716my$ccline=""; 717if($ccne'') { 718$ccline="\nCc:$cc"; 719} 720my$sanitized_sender= sanitize_address($sender); 721 make_message_id()unlessdefined($message_id); 722 723my$header="From:$sanitized_sender 724To:$to${ccline} 725Subject:$subject 726Date:$date 727Message-Id:$message_id 728X-Mailer: git-send-email$gitversion 729"; 730if($thread&&$reply_to) { 731 732$header.="In-Reply-To:$reply_to\n"; 733$header.="References:$references\n"; 734} 735if(@xh) { 736$header.=join("\n",@xh) ."\n"; 737} 738 739my@sendmail_parameters= ('-i',@recipients); 740my$raw_from=$sanitized_sender; 741$raw_from=$envelope_senderif(defined$envelope_sender); 742$raw_from= extract_valid_address($raw_from); 743unshift(@sendmail_parameters, 744'-f',$raw_from)if(defined$envelope_sender); 745 746if($dry_run) { 747# We don't want to send the email. 748}elsif($smtp_server=~ m#^/#) { 749my$pid=open my$sm,'|-'; 750defined$pidor die$!; 751if(!$pid) { 752exec($smtp_server,@sendmail_parameters)or die$!; 753} 754print$sm"$header\n$message"; 755close$smor die$?; 756}else{ 757 758if(!defined$smtp_server) { 759die"The required SMTP server is not properly defined." 760} 761 762if($smtp_encryptioneq'ssl') { 763$smtp_server_port||=465;# ssmtp 764require Net::SMTP::SSL; 765$smtp||= Net::SMTP::SSL->new($smtp_server, Port =>$smtp_server_port); 766} 767else{ 768require Net::SMTP; 769$smtp||= Net::SMTP->new((defined$smtp_server_port) 770?"$smtp_server:$smtp_server_port" 771:$smtp_server); 772if($smtp_encryptioneq'tls') { 773require Net::SMTP::SSL; 774$smtp->command('STARTTLS'); 775$smtp->response(); 776if($smtp->code==220) { 777$smtp= Net::SMTP::SSL->start_SSL($smtp) 778or die"STARTTLS failed! ".$smtp->message; 779$smtp_encryption=''; 780# Send EHLO again to receive fresh 781# supported commands 782$smtp->hello(); 783}else{ 784die"Server does not support STARTTLS! ".$smtp->message; 785} 786} 787} 788 789if(!$smtp) { 790die"Unable to initialize SMTP properly. Is there something wrong with your config?"; 791} 792 793if(defined$smtp_authuser) { 794 795if(!defined$smtp_authpass) { 796 797system"stty -echo"; 798 799do{ 800print"Password: "; 801$_= <STDIN>; 802print"\n"; 803}while(!defined$_); 804 805chomp($smtp_authpass=$_); 806 807system"stty echo"; 808} 809 810$auth||=$smtp->auth($smtp_authuser,$smtp_authpass)or die$smtp->message; 811} 812 813$smtp->mail($raw_from)or die$smtp->message; 814$smtp->to(@recipients)or die$smtp->message; 815$smtp->dataor die$smtp->message; 816$smtp->datasend("$header\n$message")or die$smtp->message; 817$smtp->dataend()or die$smtp->message; 818$smtp->okor die"Failed to send$subject\n".$smtp->message; 819} 820if($quiet) { 821printf(($dry_run?"Dry-":"")."Sent%s\n",$subject); 822}else{ 823print(($dry_run?"Dry-":"")."OK. Log says:\n"); 824if($smtp_server!~ m#^/#) { 825print"Server:$smtp_server\n"; 826print"MAIL FROM:<$raw_from>\n"; 827print"RCPT TO:".join(',',(map{"<$_>"}@recipients))."\n"; 828}else{ 829print"Sendmail:$smtp_server".join(' ',@sendmail_parameters)."\n"; 830} 831print$header,"\n"; 832if($smtp) { 833print"Result: ",$smtp->code,' ', 834($smtp->message=~/\n([^\n]+\n)$/s),"\n"; 835}else{ 836print"Result: OK\n"; 837} 838} 839} 840 841$reply_to=$initial_reply_to; 842$references=$initial_reply_to||''; 843$subject=$initial_subject; 844 845foreachmy$t(@files) { 846open(F,"<",$t)or die"can't open file$t"; 847 848my$author=undef; 849my$author_encoding; 850my$has_content_type; 851my$body_encoding; 852@cc=@initial_cc; 853@xh= (); 854my$input_format=undef; 855my$header_done=0; 856$message=""; 857while(<F>) { 858if(!$header_done) { 859if(/^From /) { 860$input_format='mbox'; 861next; 862} 863chomp; 864if(!defined$input_format&&/^[-A-Za-z]+:\s/) { 865$input_format='mbox'; 866} 867 868if(defined$input_format&&$input_formateq'mbox') { 869if(/^Subject:\s+(.*)$/) { 870$subject=$1; 871 872}elsif(/^(Cc|From):\s+(.*)$/) { 873if(unquote_rfc2047($2)eq$sender) { 874next if($suppress_cc{'self'}); 875} 876elsif($1eq'From') { 877($author,$author_encoding) 878= unquote_rfc2047($2); 879next if($suppress_cc{'author'}); 880}else{ 881next if($suppress_cc{'cc'}); 882} 883printf("(mbox) Adding cc:%sfrom line '%s'\n", 884$2,$_)unless$quiet; 885push@cc,$2; 886} 887elsif(/^Content-type:/i) { 888$has_content_type=1; 889if(/charset="?([^ "]+)/) { 890$body_encoding=$1; 891} 892push@xh,$_; 893} 894elsif(/^Message-Id: (.*)/i) { 895$message_id=$1; 896} 897elsif(!/^Date:\s/&&/^[-A-Za-z]+:\s+\S/) { 898push@xh,$_; 899} 900 901}else{ 902# In the traditional 903# "send lots of email" format, 904# line 1 = cc 905# line 2 = subject 906# So let's support that, too. 907$input_format='lots'; 908if(@cc==0&& !$suppress_cc{'cc'}) { 909printf("(non-mbox) Adding cc:%sfrom line '%s'\n", 910$_,$_)unless$quiet; 911 912push@cc,$_; 913 914}elsif(!defined$subject) { 915$subject=$_; 916} 917} 918 919# A whitespace line will terminate the headers 920if(m/^\s*$/) { 921$header_done=1; 922} 923}else{ 924$message.=$_; 925if(/^(Signed-off-by|Cc): (.*)$/i) { 926next if($suppress_cc{'sob'}); 927chomp; 928my$c=$2; 929chomp$c; 930next if($ceq$senderand$suppress_cc{'self'}); 931push@cc,$c; 932printf("(sob) Adding cc:%sfrom line '%s'\n", 933$c,$_)unless$quiet; 934} 935} 936} 937close F; 938 939if(defined$cc_cmd&& !$suppress_cc{'cccmd'}) { 940open(F,"$cc_cmd$t|") 941or die"(cc-cmd) Could not execute '$cc_cmd'"; 942while(<F>) { 943my$c=$_; 944$c=~s/^\s*//g; 945$c=~s/\n$//g; 946next if($ceq$senderand$suppress_from); 947push@cc,$c; 948printf("(cc-cmd) Adding cc:%sfrom: '%s'\n", 949$c,$cc_cmd)unless$quiet; 950} 951close F 952or die"(cc-cmd) failed to close pipe to '$cc_cmd'"; 953} 954 955if(defined$author) { 956$message="From:$author\n\n$message"; 957if(defined$author_encoding) { 958if($has_content_type) { 959if($body_encodingeq$author_encoding) { 960# ok, we already have the right encoding 961} 962else{ 963# uh oh, we should re-encode 964} 965} 966else{ 967push@xh, 968'MIME-Version: 1.0', 969"Content-Type: text/plain; charset=$author_encoding", 970'Content-Transfer-Encoding: 8bit'; 971} 972} 973} 974 975 send_message(); 976 977# set up for the next message 978if($chain_reply_to|| !defined$reply_to||length($reply_to) ==0) { 979$reply_to=$message_id; 980if(length$references>0) { 981$references.="\n$message_id"; 982}else{ 983$references="$message_id"; 984} 985} 986$message_id=undef; 987} 988 989if($compose) { 990 cleanup_compose_files(); 991} 992 993sub cleanup_compose_files() { 994unlink($compose_filename,$compose_filename.".final"); 995 996} 997 998$smtp->quitif$smtp; 9991000sub unique_email_list(@) {1001my%seen;1002my@emails;10031004foreachmy$entry(@_) {1005if(my$clean= extract_valid_address($entry)) {1006$seen{$clean} ||=0;1007next if$seen{$clean}++;1008push@emails,$entry;1009}else{1010print STDERR "W: unable to extract a valid address",1011" from:$entry\n";1012}1013}1014return@emails;1015}10161017sub validate_patch {1018my$fn=shift;1019open(my$fh,'<',$fn)1020or die"unable to open$fn:$!\n";1021while(my$line= <$fh>) {1022if(length($line) >998) {1023return"$.: patch contains a line longer than 998 characters";1024}1025}1026returnundef;1027}10281029sub file_has_nonascii {1030my$fn=shift;1031open(my$fh,'<',$fn)1032or die"unable to open$fn:$!\n";1033while(my$line= <$fh>) {1034return1if$line=~/[^[:ascii:]]/;1035}1036return0;1037}