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