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