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