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