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,$smtp_server) = (1,"localhost"); 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"smtp-server=s"=> \$smtp_server, 47); 48 49# Now, let's fill any that aren't set in with defaults: 50 51open(GITVAR,"-|","git-var","-l") 52or die"Failed to open pipe from git-var:$!"; 53 54my($author,$committer); 55while(<GITVAR>) { 56chomp; 57my($var,$data) =split/=/,$_,2; 58my@fields=split/\s+/,$data; 59 60my$ident=join(" ",@fields[0...(@fields-3)]); 61 62if($vareq'GIT_AUTHOR_IDENT') { 63$author=$ident; 64}elsif($vareq'GIT_COMMITTER_IDENT') { 65$committer=$ident; 66} 67} 68close(GITVAR); 69 70 71if(!defined$from) { 72$from=$author||$committer; 73do{ 74$_=$term->readline("Who should the emails appear to be from? ", 75$from); 76while(!defined$_); 77 78$from=$_; 79print"Emails will be sent from: ",$from,"\n"; 80} 81 82if(!@to) { 83do{ 84$_=$term->readline("Who should the emails be sent to? ", 85""); 86}while(!defined$_); 87my$to=$_; 88push@to,split/,/,$to; 89} 90 91if(!defined$initial_subject) { 92do{ 93$_=$term->readline("What subject should the emails start with? ", 94$initial_subject); 95}while(!defined$_); 96$initial_subject=$_; 97} 98 99if(!defined$initial_reply_to) { 100do{ 101$_=$term->readline("Message-ID to be used as In-Reply-To? ", 102$initial_reply_to); 103}while(!defined$_); 104 105$initial_reply_to=$_; 106$initial_reply_to=~s/(^\s+|\s+$)//g; 107} 108 109if(!defined$smtp_server) { 110$smtp_server="localhost"; 111} 112 113# Now that all the defaults are set, process the rest of the command line 114# arguments and collect up the files that need to be processed. 115formy$f(@ARGV) { 116if(-d $f) { 117opendir(DH,$f) 118or die"Failed to opendir$f:$!"; 119 120push@files,map{ +$f."/".$_}grep{ -f $_} 121sort readdir(DH); 122 123}elsif(-f $f) { 124push@files,$f; 125 126}else{ 127print STDERR "Skipping$f- not found.\n"; 128} 129} 130 131if(@files) { 132print$_,"\n"for@files; 133}else{ 134print<<EOT; 135git-send-email-script [options] <file | directory> [... file | directory ] 136Options: 137 --from Specify the "From:" line of the email to be sent. 138 --to Specify the primary "To:" line of the email. 139 --subject Specify the initial "Subject:" line. 140 --in-reply-to Specify the first "In-Reply-To:" header line. 141 --chain-reply-to If set, the replies will all be to the first 142 email sent, rather than to the last email sent. 143 --smtp-server If set, specifies the outgoing SMTP server to use. 144 Defaults to localhost. 145 146Error: Please specify a file or a directory on the command line. 147EOT 148exit(1); 149} 150 151# Variables we set as part of the loop over files 152our($message_id,$cc,%mail,$subject,$reply_to,$message); 153 154 155# Usually don't need to change anything below here. 156 157# we make a "fake" message id by taking the current number 158# of seconds since the beginning of Unix time and tacking on 159# a random number to the end, in case we are called quicker than 160# 1 second since the last time we were called. 161 162# We'll setup a template for the message id, using the "from" address: 163my$message_id_from= Email::Valid->address($from); 164my$message_id_template="<%s-git-send-email-$from>"; 165 166sub make_message_id 167{ 168my$date=`date "+\%s"`; 169chomp($date); 170my$pseudo_rand=int(rand(4200)); 171$message_id=sprintf$message_id_template,"$date$pseudo_rand"; 172#print "new message id = $message_id\n"; # Was useful for debugging 173} 174 175 176 177$cc=""; 178 179sub send_message 180{ 181my%to; 182$to{lc(Email::Valid->address($_))}++for(@to); 183 184my$to=join(",",keys%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 270my%clean_ccs; 271$clean_ccs{lc(Email::Valid->address($_))}++for@cc; 272 273$cc=join(",",keys%clean_ccs); 274 275 send_message(); 276 277# set up for the next message 278if($chain_reply_to||length($reply_to) ==0) { 279$reply_to=$message_id; 280} 281 make_message_id(); 282# $subject = "Re: ".$initial_subject; 283}