#!/usr/bin/perl # Filename: forge # Author: David Ljung # Description: Forges email use strict; umask 022; ################################################## # Setup the variables ################################################## my $PROGNAME = $0; $PROGNAME =~ s|.*/||; # Default identification -- EDIT THIS my $DEFAULT_FROM = $ENV{USER} || "root"; my $DEFAULT_FROM_FULL= $ENV{FULLNAME} || "Some Forger"; my $DEFAULT_TO = $ENV{USER} || "root"; # If we create headers based on the "To" address # %d = domain, %D = domain.tld, %l = login, %t = full To my $MAKE_HEADERS_FROM = "%D\@$ENV{DOMAIN}"; # Sig file (or a directory of sigs - we'll pick one randomly) my $SIG="~/.sig"; my $DEFAULT_SIG = "-- \nForged email by $ENV{USER} at ".`hostname`; # My addresses to ignore when figuring out which address we # received email at (for "forge ") my @IGNORE_ADDR = ("$ENV{USER}\@"); # Where to save mail my $OUTBOUND = "$ENV{HOME}/Mail/OUTBOUND/new"; $OUTBOUND = "$ENV{HOME}/Mail/OUTBOUND" unless -f $OUTBOUND; # Where mail comes in my $MAILBOX = $ENV{MAIL}; # Forge pipe method: my $FORGE_PIPE="/usr/bin/forge_pipe"; # Sendmail method: my $SENDMAIL="/usr/lib/sendmail"; # Port 25 method: Where we normally send mail through my $DEFAULT_MAILHOST="localhost"; my $TELNET="/usr/bin/telnet"; # Choose an editor my $EDITOR = "/bin/vim"; $EDITOR = "/usr/bin/vim" if (!$EDITOR || ! -x $EDITOR); $EDITOR = "/bin/vi" if (!$EDITOR || ! -x $EDITOR); $EDITOR = "/usr/bin/vi" if (!$EDITOR || ! -x $EDITOR); die("[$PROGNAME] Couldn't find editor to use\n") if (!$EDITOR || ! -x $EDITOR); ################################################## # Usage ################################################## sub usage { my $msg; foreach $msg (@_) { print "ERROR: $msg\n"; } print "\n"; print "Usage:\t$PROGNAME [num] [-f from] [-F name] [-s subject] [-t to]\n"; print " \t [-d] [-25 [mail_host]] [destination]\n"; print "\n"; print "\tForges a mail message\n"; print "\n"; print "\t Forge reply to given message [$MAILBOX]\n"; print "\t (This is by order in the file, not necessarily the\n"; print "\t order that your mailer shows the messages in\n"; print "\t-f From address\n"; print "\t-F From full name\n"; print "\t-t To address\n"; print "\t-s Subject\n"; print "\n"; print "\tdestination Create to/from based on a destination\n"; print "\n"; print "\t Create to/from based on # message in mbox\n"; print "\t-g - Group reply for \n"; print "\n"; print "\t-d Set debug mode\n"; print "\t-sendmail Use sendmail directly\n"; print "\t-25 Use port 25 method\n"; print "\n"; exit -1; } # Unique push sub add_list { my ($add,$arr) = @_; $add =~ s/\s+//g; push(@$arr,$add) unless !$add || grep(lc($add) eq lc($_), @$arr); } # Read a mail message in the standard mailbox and create headers according # to that my $NEW_MSG_RE = '^From (.+)\s+(\S+\s+\S+\s+\d+\s+[\d:]+\s+\d+)\s*$'; sub get_headers { my ($hdrs,$num,$group) = @_; my %got; open(MBOX,$MAILBOX) || usage("Can't read mail [$MAILBOX]!"); # Find message my $cnt = 0; while () { chomp; last if /$NEW_MSG_RE/ && ++$cnt==$num; } return unless $cnt==$num; # Grab headers my $hdr; while () { chomp; last if /^\s*$/; if (/^(\S+):\s+(\S.*)$/) { $hdr = lc($1); my $val = $2; $hdr =~ s/-/_/g; $got{$hdr}= $got{$hdr} ? "$got{$hdr}\n$val" : $val; } else { # Continuation of previous header s/^\s+/ /; $got{$hdr}.=$_; } } # Grab body while () { last if /$NEW_MSG_RE/; $hdrs->{BODY} .= "> $_"; } close MBOX; # What mail address did we receive at? if ($got{to} && !grep($got{to} =~ /$_/, @IGNORE_ADDR)) { $hdrs->{from} = $got{to}; } else { my $r = $got{received}; while ($r =~ /for <([^>]+)>/) { my $a = $1; $r = $'; unless (grep($a =~ /$_/, @IGNORE_ADDR)) { $hdrs->{from} = $a; last; } } } $hdrs->{from_full} = $1 if ($got{to} =~ /^(.*\S)\s+<.*>/); # Who was the mail for? my @rcpt; add_list($got{'to'},\@rcpt); add_list($got{'cc'},\@rcpt); add_list($got{'x_apparently_to'},\@rcpt); my $reply_to = $got{reply_to} if ($got{reply_to}); my $sender = $1 if ($got{from} =~ /^.*\S\s+<([^>]+\@[^>]+\.[^>]+)>/); add_list($reply_to,\@rcpt); add_list($sender,\@rcpt); my @TO = $group ? @rcpt : ($reply_to || $sender); # Don't send to myself @TO = grep($_ ne $hdrs->{from}, @TO) if $#TO>0; $hdrs->{to} = join(',',@TO) if @TO; $hdrs->{subject} = $got{subject}; $hdrs->{subject} =~ s/^\s?re:?\s?//ig; $hdrs->{subject} = "Re: $hdrs->{subject}"; $hdrs->{in_reply} = $got{message_id}; $hdrs->{references} = $got{references}; $hdrs->{references} .= " ".$got{message_id} if $got{message_id}; usage("Couldn't find message #$num [$MAILBOX]") if ($cnt<$num); } # Make headers based on a "to:" address sub make_headers { my ($hdrs,$to) = @_; my ($login,$domain,$Domain) = ($to); ($login,$domain,$Domain) = ($1,$2,$2) if $to =~ /(.+)\@(.+)/; $domain =~ s/\.[^\.]+$//; $hdrs->{to} = $to; $hdrs->{from} = $MAKE_HEADERS_FROM; $hdrs->{from} =~ s/%d/$domain/g; $hdrs->{from} =~ s/%D/$Domain/g; $hdrs->{from} =~ s/%l/$login/g; $hdrs->{from} =~ s/%t/$to/g; $hdrs->{from_full} = $DEFAULT_FROM_FULL; $hdrs->{MADE} = 1; } sub parse_args { my $host; my %hdrs; my $group=0; # DEFAULTS $hdrs{from} = $DEFAULT_FROM; $hdrs{from_full} = $DEFAULT_FROM_FULL; $hdrs{to} = $DEFAULT_TO; while ($#ARGV>=0) { $a=shift(@ARGV); if ($a =~ /^-h$/) { &usage; } if ($a =~ /^-d$/) { $MAIN::DEBUG=1; next; } if ($a =~ /^-25$/) { $MAIN::PORT25=1; next; } if ($a =~ /^-sendmail$/) { $MAIN::SENDMAIL=1; next; } if ($a =~ /^-f$/) { $hdrs{from}=shift(@ARGV); next; } if ($a =~ /^-F$/) { $hdrs{from_full}=shift(@ARGV); next; } if ($a =~ /^-t$/) { $hdrs{to}=shift(@ARGV); next; } if ($a =~ /^-s$/) { $hdrs{subject}=shift(@ARGV); next; } if ($a =~ /^-g$/) { $group=1; next; } if ($a =~ /^-/) { usage("Unknown option: $a"); } if ($a =~ /^\d+$/) { get_headers(\%hdrs,$a,$group); next; } if ($a =~ /\@/) { make_headers(\%hdrs,$a); next; } usage("You can only specify one mailhost [$a and $host]") if(defined($host)); $host=$a; } $host=$DEFAULT_MAILHOST if(!defined($host)); ($host,\%hdrs); } ################################################## # Char-by-char mode ################################################## sub char_mode { my $ttyname=`/usr/bin/tty`; system "/bin/stty -icanon -echo min 1 < $ttyname " if (! $?); } sub line_mode { my $ttyname=`/usr/bin/tty`; `/usr/bin/tty -s`; system "/bin/stty icanon echo < $ttyname " if (! $? ); } sub get_char { my $ans; read(STDIN,$ans,1); return $ans; } ################################################## # Get the header fields ################################################## sub get_header { my ($query,$default,$just_show) = @_; if ($just_show) { print STDERR "$query: $default\n"; return $default; } my $ret; print STDERR $default ? "$query [$default]: " : "$query: "; chomp($ret = <>); $ret || $default; } ################################################## # Get the signature file ################################################## sub get_sig { my $txt; my $sig_file=$SIG; # Handle '~' in path $sig_file =~ s/\~/$ENV{HOME}/g if (defined($ENV{HOME})); # If it's a directory then pick one file if (-d $sig_file) { if (!opendir(SIG,$sig_file)) { print STDERR "Couldn't open sig directory: $sig_file\n"; sleep 1; return ""; } my @sigs=grep(!/^\.{1,2}$/,readdir(SIG)); closedir(SIGS); $sig_file="$sig_file/".$sigs[int(rand(scalar(@sigs)))]; } # Read the file and return the text if (open(SIG,"<$sig_file")) { $txt=join('',); close(SIG); return $txt; } return $DEFAULT_SIG; # print STDERR "Couldn't open sig file: $sig_file\n"; # sleep 1; # return ""; } ################################################## # Date needs to be of the format: # Date: Fri, 1 Mar 2002 00:05:52 -0800 # Not what 'scalar gmtime()' returns, sadly ################################################## use POSIX qw(strftime); sub get_date { # my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; # $wday = (qw(Sun Mon Tue Wed Thu Fri Sat))[$wday]; # $mon = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mon]; # $year += 1900; # sprintf("$wday, $mday $mon $year %0.2d:%0.2d:%0.2d",$hour,$min,$sec); strftime "%a, %d %b %Y %H:%M:%S %z", localtime; } ################################################## # Main code ################################################## sub main { my ($host,$hdrs) = parse_args(); my $file = "${PROGNAME}.$$"; die unless $file =~ /^([-\w.]+)$/; $file = $1; $file = "/tmp/$file"; ######################### # Get the mail information ######################### $hdrs->{from}=get_header("From address",$hdrs->{from},$hdrs->{MADE}); $hdrs->{from_full}=get_header("From name",$hdrs->{from_full},$hdrs->{MADE}); $hdrs->{to}=get_header("To",$hdrs->{to},$hdrs->{MADE}); $hdrs->{subject}=get_header("Subject",$hdrs->{subject}); $hdrs->{date} = get_date(); my $sig=get_sig(); ######################### # Create the file ######################### open(FILE,">$file") || die("Couldn't write $file\n"); # Headers print FILE "From: $hdrs->{from_full} <$hdrs->{from}>\n"; print FILE "Subject: $hdrs->{subject}\n"; print FILE "To: $hdrs->{to}\n"; print FILE "Date: $hdrs->{date}\n"; print FILE "X-Mailer: DaveMail [version 6.0]\n"; print FILE "In-Reply-To: $hdrs->{in_reply}\n" if ($hdrs->{in_reply}); print FILE "References: $hdrs->{references}\n" if ($hdrs->{references}); print FILE "\n"; # Body print FILE $hdrs->{BODY} || "EMAIL_TEXT_GOES_HERE\n"; # Sig print FILE "\n$sig"; close(FILE); ######################### # Edit loop ######################### while(1) { system("$EDITOR $file"); print STDERR "S)end the message, E)dit it again, F)orget it (saved), X)eXit\n"; print STDERR "\nWhat is your choice? s"; char_mode(); my $ans=get_char; line_mode(); print "$ans\n"; if ($ans =~ /X/i) { unlink($file); exit; } if ($ans =~ /F/i) { print "File saved in: $file\n"; exit; } next if ($ans =~ /E/i); last if ($ans =~ /S/i || $ans =~ /\n/); } ######################### # Get the from/to info from the file, in case it was edited ######################### open(FILE,"<$file") || die("Couldn't read $file\n"); while() { last if (/^$/); ($hdrs->{from_full},$hdrs->{from}) = ($1,$2) if (/^From: (.+) <(.+)>$/); $hdrs->{to} = $1 if (/^To: (.+)/); } close(FILE); ######################### # TO is an email only version of to ######################### my @to = split(/,/,$hdrs->{to}); @to = map { s/.*<(.+)>.*/$1/; s/\s*$//; $_; } @to; $hdrs->{TO} = join(",", @to); ######################### # Mail it ######################### if ($MAIN::PORT25) { open(FILE,"<$file") || die("Couldn't open mail file! $file\n"); open(MAIL,"|$TELNET $host 25 > /dev/null") || die("Couldn't open socket to $host\n"); my $from_machine=$hdrs->{from}; $from_machine =~ s/.*\@//; print MAIL "helo $from_machine\n"; print MAIL "mail from: $hdrs->{from}\n"; print MAIL "rcpt to: $hdrs->{TO}\n"; print MAIL "data\n"; while() { # Make sure we don't die early s/^\.$/. /g; print MAIL; } print MAIL ".\n"; print MAIL "quit\n"; close(MAIL); close(FILE); } elsif ($MAIN::SENDMAIL || ! -x $FORGE_PIPE) { system("$SENDMAIL -i -f $hdrs->{from} -F \"$hdrs->{from_full}\" $hdrs->{TO} < $file"); } else { system("$FORGE_PIPE -f $hdrs->{from} -F \"$hdrs->{from_full}\" $hdrs->{TO} $file"); } # Save a copy (hack!) if ($OUTBOUND && open(OUT,">>$OUTBOUND")) { if (open(MAIL,"<$file")) { print OUT "\nFrom $hdrs->{from} ",(scalar gmtime),"\n"; while () { print OUT; } close(MAIL); } close(OUT); } # Delete the file unlink($file); } main();