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 Mail::Sendmail qw(sendmail%mailcfg); 23use Getopt::Long; 24use Data::Dumper; 25use Email::Valid; 26 27sub unique_email_list(@); 28 29# Variables we fill in automatically, or via prompting: 30my(@to,@cc,$initial_reply_to,$initial_subject,@files,$from); 31 32# Behavior modification variables 33my($chain_reply_to,$smtp_server) = (1,"localhost"); 34 35# Example reply to: 36#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; 37 38my$term= new Term::ReadLine 'git-send-email'; 39 40# Begin by accumulating all the variables (defined above), that we will end up 41# needing, first, from the command line: 42 43my$rc= GetOptions("from=s"=> \$from, 44"in-reply-to=s"=> \$initial_reply_to, 45"subject=s"=> \$initial_subject, 46"to=s"=> \@to, 47"chain-reply-to!"=> \$chain_reply_to, 48"smtp-server=s"=> \$smtp_server, 49); 50 51# Now, let's fill any that aren't set in with defaults: 52 53open(GITVAR,"-|","git-var","-l") 54or die"Failed to open pipe from git-var:$!"; 55 56my($author,$committer); 57while(<GITVAR>) { 58chomp; 59my($var,$data) =split/=/,$_,2; 60my@fields=split/\s+/,$data; 61 62my$ident=join(" ",@fields[0...(@fields-3)]); 63 64if($vareq'GIT_AUTHOR_IDENT') { 65$author=$ident; 66}elsif($vareq'GIT_COMMITTER_IDENT') { 67$committer=$ident; 68} 69} 70close(GITVAR); 71 72 73if(!defined$from) { 74$from=$author||$committer; 75do{ 76$_=$term->readline("Who should the emails appear to be from? ", 77$from); 78}while(!defined$_); 79 80$from=$_; 81print"Emails will be sent from: ",$from,"\n"; 82} 83 84if(!@to) { 85do{ 86$_=$term->readline("Who should the emails be sent to? ", 87""); 88}while(!defined$_); 89my$to=$_; 90push@to,split/,/,$to; 91} 92 93if(!defined$initial_subject) { 94do{ 95$_=$term->readline("What subject should the emails start with? ", 96$initial_subject); 97}while(!defined$_); 98$initial_subject=$_; 99} 100 101if(!defined$initial_reply_to) { 102do{ 103$_=$term->readline("Message-ID to be used as In-Reply-To? ", 104$initial_reply_to); 105}while(!defined$_); 106 107$initial_reply_to=$_; 108$initial_reply_to=~s/(^\s+|\s+$)//g; 109} 110 111if(!defined$smtp_server) { 112$smtp_server="localhost"; 113} 114 115# Now that all the defaults are set, process the rest of the command line 116# arguments and collect up the files that need to be processed. 117formy$f(@ARGV) { 118if(-d $f) { 119opendir(DH,$f) 120or die"Failed to opendir$f:$!"; 121 122push@files,grep{ -f $_}map{ +$f."/".$_} 123sort readdir(DH); 124 125}elsif(-f $f) { 126push@files,$f; 127 128}else{ 129print STDERR "Skipping$f- not found.\n"; 130} 131} 132 133if(@files) { 134print$_,"\n"for@files; 135}else{ 136print<<EOT; 137git-send-email-script [options] <file | directory> [... file | directory ] 138Options: 139 --from Specify the "From:" line of the email to be sent. 140 --to Specify the primary "To:" line of the email. 141 --subject Specify the initial "Subject:" line. 142 --in-reply-to Specify the first "In-Reply-To:" header line. 143 --chain-reply-to If set, the replies will all be to the previous 144 email sent, rather than to the first email sent. 145 Defaults to on. 146 --smtp-server If set, specifies the outgoing SMTP server to use. 147 Defaults to localhost. 148 149Error: Please specify a file or a directory on the command line. 150EOT 151exit(1); 152} 153 154# Variables we set as part of the loop over files 155our($message_id,$cc,%mail,$subject,$reply_to,$message); 156 157 158# Usually don't need to change anything below here. 159 160# we make a "fake" message id by taking the current number 161# of seconds since the beginning of Unix time and tacking on 162# a random number to the end, in case we are called quicker than 163# 1 second since the last time we were called. 164 165# We'll setup a template for the message id, using the "from" address: 166my$message_id_from= Email::Valid->address($from); 167my$message_id_template="<%s-git-send-email-$message_id_from>"; 168 169sub make_message_id 170{ 171my$date=`date "+\%s"`; 172chomp($date); 173my$pseudo_rand=int(rand(4200)); 174$message_id=sprintf$message_id_template,"$date$pseudo_rand"; 175#print "new message id = $message_id\n"; # Was useful for debugging 176} 177 178 179 180$cc=""; 181 182sub send_message 183{ 184my$to=join(", ", unique_email_list(@to)); 185 186%mail= ( To =>$to, 187 From =>$from, 188 CC =>$cc, 189 Subject =>$subject, 190 Message =>$message, 191'Reply-to'=>$from, 192'In-Reply-To'=>$reply_to, 193'Message-ID'=>$message_id, 194'X-Mailer'=>"git-send-email-script", 195); 196 197$mail{smtp} =$smtp_server; 198$mailcfg{mime} =0; 199 200#print Data::Dumper->Dump([\%mail],[qw(*mail)]); 201 202 sendmail(%mail)or die$Mail::Sendmail::error; 203 204print"OK. Log says:\n",$Mail::Sendmail::log; 205print"\n\n" 206} 207 208 209$reply_to=$initial_reply_to; 210make_message_id(); 211$subject=$initial_subject; 212 213foreachmy$t(@files) { 214my$F=$t; 215open(F,"<",$t)or die"can't open file$t"; 216 217@cc= (); 218my$found_mbox=0; 219my$header_done=0; 220$message=""; 221while(<F>) { 222if(!$header_done) { 223$found_mbox=1,next if(/^From /); 224chomp; 225 226if($found_mbox) { 227if(/^Subject:\s+(.*)$/) { 228$subject=$1; 229 230}elsif(/^(Cc|From):\s+(.*)$/) { 231printf("(mbox) Adding cc:%sfrom line '%s'\n", 232$2,$_); 233push@cc,$2; 234} 235 236}else{ 237# In the traditional 238# "send lots of email" format, 239# line 1 = cc 240# line 2 = subject 241# So let's support that, too. 242if(@cc==0) { 243printf("(non-mbox) Adding cc:%sfrom line '%s'\n", 244$_,$_); 245 246push@cc,$_; 247 248}elsif(!defined$subject) { 249$subject=$_; 250} 251} 252 253# A whitespace line will terminate the headers 254if(m/^\s*$/) { 255$header_done=1; 256} 257}else{ 258$message.=$_; 259if(/^Signed-off-by: (.*)$/i) { 260my$c=$1; 261chomp$c; 262push@cc,$c; 263printf("(sob) Adding cc:%sfrom line '%s'\n", 264$c,$_); 265} 266} 267} 268close F; 269 270$cc=join(", ", unique_email_list(@cc)); 271 272 send_message(); 273 274# set up for the next message 275if($chain_reply_to||length($reply_to) ==0) { 276$reply_to=$message_id; 277} 278 make_message_id(); 279} 280 281 282sub unique_email_list(@) { 283my%seen; 284my@emails; 285 286foreachmy$entry(@_) { 287my$clean= Email::Valid->address($entry); 288next if$seen{$clean}++; 289push@emails,$entry; 290} 291return@emails; 292}