#!/usr/bin/perl # change path if necessary; should work with Perl4.036 or Perl5.00[1-3] # ----------------------------------------------------------------------------- # Following line is completely obsolete [GN]. # Id: mailto.pl 1.0 1995/01/03 19:05:15 michael Exp michael $ # # written by Michael Kellen, February 1995 # # MICHAEL KELLEN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO # EVENT SHALL MICHAEL KELLEN BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # # rewritten by Gerhard Niklasch , June 1996, # February 1997, April 1997 # Standard disclaimers continue to apply # ----------------------------------------------------------------------------- # Changes 12 Jun 96: URL decoding neglected to convert '+' to ' ' # Changes 12 Jun 96: Content-Type "application/none" of HTTPHappy caused # chimera to try and save the response to a file, instead of displaying # it; changed this to text/plain # It's still broken. The signature is disrupted en route back from the form. # And Chimera fails to URL-encode +'s before URL-encoding spaces to +'s. # Changes 02 Feb 97: Testing now with Chimera's URL-encoding fixed, lessee... # Also changed the temp file's name to at least attempt to make it # unique. It should live in a tmp directory, really. # Also fixed a stupid little filehandle typo in &Abort. # Changes 02 Feb 97: Duh, URL decoding had character range [0-9A-E]. Fixed. # Changes 02 Feb 97: URL decoding shouldn't choke upon seeing a % sign it # has produced earlier in the loop. Duh^2. Fixed by using a ///ge # pattern substitution operator. # Changes 02 Feb 97: The signature separator is a line consisting of two # hyphens _followed by a single space then newline_. Fixed. Duh^3. # Changes 02 Feb 97: HTML syntax in the form. # Changes 18 Apr 97: Replaced some Perl horrors (poor style and/or badly # inefficient) with more idiomatic constructions. # Made sure it'll work with Perl4. # Made sure addresses like "Joe L. User " and # "luser@foo.com (Joe L. User)" are handled, and make it into # the TITLE field unharmed. This is not much of a problem when # we can rely on sendmail to do the right thing. It is when passing # addresses via a shell to an external Mail User Agent; defused a # few potential security holes here. # Fixed a stupid bug -- $reply was being HTML-entity-escaped before # it had a value. Duh^IveLostCount. # Changes 19 Apr 97: When we handle mail ourselves and the system mailer # call returns, we display the message and headers, so the user can # save it to a file. If the mailer reports an error, we catch its # output and display what we can. Sendmail itself saves stuff to # $HOME/dead.letter so we shouldn't do it again. # Changes 24 Apr 97: Added helpful messages.-- We _don't_ do URL decoding # of addresses although RFC1738 says we should. This is because we # don't dare to mangle "mailto:joe%earth@galaxy" when a user pastes # that into the Open URL popup... # ----------------------------------------------------------------------------- $banner = 'Chimera-1.70 e-mail composition utility (mailto.pl)'; $mailer = '/usr/lib/sendmail -t <'; $HOME = $ENV{'HOME'}; $HOME = "/home/$ENV{'USER'}" if (!$HOME); $HOME = '/tmp' if (!$HOME); # the main program is just four lines: if (&ParseRequest) { &GetCall; # doesn't return } &PostCall; # doesn't return either # subroutines... sub ParseRequest { # Parse incoming URL of the form # GET mailto:address # or # POST mailto:(address field ignored) # followed by Chimera's extra helper headers # (and for a POST, a querystring as a body) ($http_cmd, $http_uri, $http_id) = split (/\s+/, <>, 3); while (<>) { chop; ($type,$value) = split ( '\s*:\s+' , $_, 2); if ($type eq "") { last; } if ( $type eq "X-hostname" ) { $server = $value; # not used } if ( $type eq "X-filename" ) { $address = $value; # (might theoretically contain URL-encoding # [%hex escapes] if the web author actually # followed the specs, but then it might also # contain a % not meant to be URL-decoded, # so we'd better not touch it) } } return 1 if ($http_cmd =~ /[Gg][Ee][Tt]/ ); # otherwise it should be a POST $_ = ; # slurp the querystring chop ; @fields = split('&', $_, 4); # make sure the bare necessities are there: $filled_in{'HEAD'} = "To: nobody\n\n"; $filled_in{'BODY'} = ''; $filled_in{'SIG'} = ''; $filled_in{'SIGN'} = 'off'; # don't rely on them appearing in a particular # order foreach $field (@fields) { ($name, $val) = split('=', $field); $filled_in{$name} = $val; } # ($head,$body,$sig,$sign) = split( '&', $_, 4); $head = $filled_in{'HEAD'}; $body = $filled_in{'BODY'}; $sig = $filled_in{'SIG'}; $sign = $filled_in{'SIGN'}; return 0; # so PostCall will follow } sub GetCall { # produce a mailer or an equivalent FORM $mailer = "$ENV{'MAIL_CLIENT'}"; # (this would need to be an X client; # elm or pine won't work unless run inside # an xterm from a short shell script) if ($mailer) { # we're going to pass the $address to # the mail client via a shell argument, # and the $address presumably came from # any old web page, so we'd better make # sure it doesn't contain any oddities. # The following won't work with X.400 # addresses and all those semicolons, # although it should handle all the common # RFC822 stuff. $address =~ s/\s*\(.*\)\s*//; if ($address =~ /<([\w\-\.\@\%\:]+)>/ ) { $address = "\'$1\'"; } elsif ($address =~ /^([\w\-\.\@\%\:]+)/ ) { $address = "\'$1\'"; } else { # no good address found, let the user add one $address = ''; } system ("$mailer $address"); if ($?) { $error_string = "Mail client invocation error:\n$!\n"; # forcing a string context for $! $message_length = length ($error_string); } else { $message = "Mail Done\n"; $message_length = 10; } print "HTTP/1.0 200 OK\n"; print "Content-type: text/plain\n"; print "Content-length: $message_length\n\n"; print $message; exit 0; } # no MAIL_CLIENT -- we'll do it with a FORM $sigfile = $ENV{'SIGNATURE'}; $sigfile = "$HOME/.signature" unless $sigfile; if (open (SIG, $sigfile)) { @signature = (); while() { push(@signature, $_); } close(SIG); $signature = join('', @signature); } else { $signature = ''; } $replyto = $ENV{'REPLYTO'}; if (defined $replyto) { $reply = "Reply-To: $replyto\n"; } else { $reply = 'Reply-To: '; } $address =~ s/&/&/g; $address =~ s//>/g; $address =~ s/\"/"/g; $reply =~ s/&/&/g; $reply =~ s//>/g; $reply =~ s/\"/"/g; $signature =~ s/&/&/g; $signature =~ s//>/g; $signature =~ s/\"/"/g; @form = (); push(@form, "Mailing to $address\n", "
$banner
\n", "

Have a look at the recipient address first, especially if\n", "it contains funny characters like `%' or `;'.\n", "

\n", # the 'Done' is there only because sth # intelligible should be displayed in the # URL field of the Happy Page :^) "\n", "\n
\n"); if ($signature ne '') { push(@form, "\n

", "", ' Append Signature '); } else { push(@form, '

'); } push(@form, "", "


\n", "

After mailing, you will be shown the message as passed to\n", "sendmail; you could then save it to a file if you\n", "wish. (It won't have From or Date headers yet.)\n", "Alternatively, you can Cc yourself before mailing it.\n", "\n"); $form = join('', @form); $form_length = length ($form); print "HTTP/1.0 200 OK\n"; print "Content-type: text/html\n"; print "Content-length: $form_length\n\n"; print $form; exit 0; # doesn't return } sub PostCall { # handle a filled-in FORM, which we get URL- # encoded from chimera, and pass it to the # mail transfer agent # Undo the URL encoding first $head =~ s/\+/ /g; $head =~ s/%([0-9A-Fa-f]{2})/sprintf("%c",hex($1))/ge; $head =~ s/\s+$/\n/; # and make sure $head ends with an empty line $head .= "X-Mailer: Chimera-1.70 via mailto.pl\n\n"; $body =~ s/\+/ /g; $body =~ s/%([0-9A-Fa-f]{2})/sprintf("%c",hex($1))/ge; $body =~ s/\s+$/\n/; # and the body should end with one newline if ( $sign eq 'on' ) { $sig =~ s/\+/ /g; $sig =~ s/%([0-9A-Fa-f]{2})/sprintf("%c",hex($1))/ge; $sig =~ s/\s+$/\n/; # and same goes for the .signature } open(TMP, ">$HOME/.letter$$"); @letter = ($head, $body); push(@letter, "\n-- \n$sig") if ($sign eq 'on'); $letter = join('', @letter); $letter_length = length ($letter); print TMP $letter; close(TMP); # here the shell never sees the address. $results = `$mailer $HOME/.letter$$ 2>&1`; if ($?) { @message = ("System Mailer error:\n$results\n", "Message text follows:\n$letter"); $message = join('', @message); } else { $message = $letter; } $message_length = length ($message); print "HTTP/1.0 200 OK\n"; print "Content-type: text/plain\n"; print "Content-length: $message_length\n\n"; print $message; unlink "$HOME/.letter$$"; exit 0; }