Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 26 Mar 1999 17:02:40 -0500 (EST)
From:      David Stoddard <dgs@us.net>
To:        gvbmail@tns.net (GVB)
Cc:        freebsd-net@freebsd.org
Subject:   Re: autoresponder for email..
Message-ID:  <199903262202.RAA26464@us.net>
In-Reply-To: <4.1.19990326103409.02bf7f00@abused.com> from "GVB" at Mar 26, 99 10:36:35 am

next in thread | previous in thread | raw e-mail | index | archive | help
GVB writes:
> I am trying to find a decent auto responder package for a FreeBSD
> serverside sendmail/qpopper setup.  I had looked into using the vacation
> program which works fine, EXCEPT that it requires users to have a shell to
> run it.  I am not going to start giving shells out on a mail server just
> for this application.. does anyone know if there is ANYTHING else out there
> that will fit what I am doing without having to use a new mail server?? 

	I have one that I wrote that you can use.  You will need three
	things: and alias in your mail system (or .forward file) to send
	mail to an executable program, the C source for the executable
	program, and a perl routine that performs the autoresponse
	function.  This is what your alias should look like (assuming
	you want an info responder):

	info:  |"/full/path/here/autorespond sales@your.domain"

	---
	Next, the autorespond program is a C wrapper that actually
	executes a perl program.  Be sure to modify the path names in
	the code below to suit your own installation.  The C wrapper
	looks like this:

/*  C program to wrap /usr/home/info/autorespond.pl */
#include <stdio.h>
char ifs[] = "IFS= \t\n";
char path[] = "PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin";
char shell[] = "/usr/local/bin/perl";
int main(argc,argv)
    int  argc;
    char *argv[];
{   char **p, **pp;
        putenv(ifs);
        putenv(path);
	setgid(getegid()); setuid(geteuid());
        p = pp = (char **) malloc(sizeof(char*) * (argc + 2));
        if (!p) {
            fprintf(stderr,
        	"mkwrap: %s: Cannot malloc parmlist\n",argv[0]);
            return 6;
        }
        *p++ = (argc--, *argv++);
        *p++ = "/usr/home/info/autorespond.pl";
        while (argc > 0)
            *p++ = (argc--, *argv++);
        *p = NULL;
        execv(shell,pp);
        perror(shell);
        return 8;
}

	Compile this program on FreeBSD using:

	  cc autorespond.c -o autorespond

	The program should be flagged via "chmod 755 autorespond".  Note
	that sendmail requires perl routines to be wrapped by natively
	executable binary programs, hence the reason for the wrapper.
	---

	Finally, the actual autoresponder is included below.  This is one
	of several I have written over the years and is the result of much
	frustration I have encountered with broken mail programs all over
	the planet.  It works well, avoids mail loops, logs each person
	that requests info from it, and is very configurable.  It even
	provides a filter mechanism to stop people that abuse it as a test
	facility.  Modify the path names in the code to suit your
	installation.  The perl routine is attached.

	Dave Stoddard
	US Net Incorporated
	301-361-6000
	dgs@us.net
	---

#!/usr/local/bin/perl
# autorespond.pl - an email autoresponder written in perl.
#
#  Dave Stoddard
#  US Net Incorporated
#  dgs@us.net
#
#  Legal Disclaimer
#  ----------------
#  Permission to use and distribute this program is given provided the
#  original author of this program is given credit for his work.  This
#  program is not guaranteed to do anything other than consume space on
#  your disk drive.  Use it at your own risk.
#
#  Description
#  -----------
#  This program is designed to read an email message from stdin, determine
#  who sent it, and mail a response back to the sender.  The person that
#  sent the message is logged to a log file.  Also, a filter file is available
#  to block people who abuse the autoresponder.  The name of the file to
#  send (the response file), the log file name, and the filter file name
#  are all defined in the beginning of this script and should be tailored
#  for each specific installation.
#
#  The program can also be run completely from parameters, allowing someone
#  to use it for multiple purposes on a single site.  The parameters are
#  optional and are defined as follows:
#
#  autorespond.pl  [ -m message ] [ -l log ] [ -f filter ] [ -s -e ] [ email ]
#
#  where,
#
#    -m message : specifies the message file to send
#    -l log     : specifies the name of the file used to log recipients
#    -f filter  : specifies the name of the filter file used to block abusers
#    -e         : suppress error messages from the autoresponder
#    -s         : preserve the Subject: header in the message file
#    email      : a list of email recipients for the original input message
#
#  It is possible to specify any combination of parameters and let other
#  parameters default to internal values.  By specifying one or more email
#  addresses at the end of the line, you can send a copy of the person's
#  message to one or more email addresses.  This is useful for answering
#  specific questions that may be sent to the autoresponder (common for
#  sales information).
#
#  It is important to note that the "message.file" should contain any necessary
#  mail headers in the beginning of the file, followed by a blank line, then
#  followed by the message you want to send.  The mail headers will allow you
#  to control the subject of the message, the reply-to address, and other
#  elements of the message.  This is a typical example for a message file:
#
#    Subject: Information You Requested
#    Reply-To: sales@us.net
#    Errors-To: postmaster@us.net
#
#    This is a test message.
#
#  Note that all of the lines above should be left justified in the text file.
#  If you use a mail transport agent other than sendmail, such as smail or
#  MMDF, this technique of embedding the message headers in the message file
#  may not work for you -- you will need to test this on your own.  A complete
#  compendium of email message headers is available in the Internet standards
#  document RFC-822, available at http://www.internic.net/rfc/rfc822.txt (and
#  other locations too).
#
#  IMPORTANT MODIFICATION:  Because some autoresponders use a subject line
#  to for loop detection, if the original message has a subject line, the
#  autoresponder will preserve that subject line and override the subject
#  line you specify in the message file.  This action can be overridden
#  using the -s option on the command line.
#
#  Also, it may be necessary to add a definition in your mail server to allow
#  the mail server to run this program.  In sendmail.cf, this is called a
#  "trusted user" and is defined with the "Tuserid" macro.  You should make
#  sure your log file can be written to by your mail server.  If you know
#  the userid and group your mail server run under, you can set the ownership
#  of the file accordingly and use chmod to enable write access.  If you don't
#  know how to determine this, then simply "chmod 666" your log file -- if you
#  do this, other users on your system will be able to access your log file as
#  well.  Your best bet is to set it correctly than to use "666" on the file.
#
#  One last point -- all files referenced by autorespond.pl must be world
#  readable.  This is done as a security precaution.  Don't remove this
#  security code, or you run the risk of having any file on your system
#  vulnerable to being mailed.
#
#  Example
#  -------
#  Assuming you have an email alias called "info" that you use for sending
#  information packets, and you want a copy of the original request to be
#  sent to the address "sales" (in case the request has questions), your
#  mail alias definition would look like this:
#
#   info:  "|/usr/local/bin/autorespond -m /etc/myinfo -l /etc/mylog.txt sales"
#
#  Mail arriving for "info" would receive a response using the contents of
#  the file /etc/myinfo, and the response would be logged to the file
#  /etc/mylog.txt.  Note that you could do the same thing with a .forward
#  file that would contain the following entries:
#
#   |/usr/local/bin/autorespond -m /etc/myinfo -l /etc/mylog.txt sales
#
#  An alternative .forward approach is to put the recipient userid outside
#  the autoresponder, however filtering will not be in effect for any email
#  address that is outside the command line for the autoresponder:
#
#   |/usr/local/bin/autorespond -m /etc/myinfo -l /etc/mylog.txt
#   sales
#
#  If you want to simply accept the defaults coded into the script, all
#  you need to refer to is the /usr/local/bin/autorespond program itself.
#  In this case, the following alias would work just fine (assuming filter
#  for recipient copy address):
#
#    info:  "|/usr/local/bin/autorespond sales"
#
#  or without filter for recipient copy address:
#
#    info:  "|/usr/local/bin/autorespond",sales
#
#  This would send the default response file, log to the default log file,
#  apply default filters, and send a copy of the message to "sales".
#
#  Components
#  ----------
#  Because this routine is a perl script, it needs to be executed by a
#  wrapper program that is written in C.  You will need to examine the
#  program autorespond.c and add the correct path info for your perl
#  interpreter on your system, and the path of the autorespond.pl script.
#  To compile sutorespond.c, simply use:
#
#    cc autorespond.c -o autorespond
#
#  The autorespond program simply invokes autorespond.pl using the perl
#  interpreter.  The important elements you will want to modify in this
#  program include:
#
#    $errmail  : email address to use to send responder error messages
#    $sendfile : the default message file to send
#    $logfile  : the default log file to use for logging recipients
#    $filter   : the default filter file to use
#
#  Note that if $errmail, $logfile, or $filter are blank, they will be
#  ignored by the software (the functionality os removed).
#
#  Revision History
#  ----------------
#  1.0 - 02/16/98 - d. stoddard - initial script passed final testing.
#  1.1 - 06/22/98 - d. stoddard - added loop control for error report.
#  2.0 - 02/16/98 - d. stoddard - added senders headers, internal copying
#                                 of message to recipients, and option switches.
#
require "getopts.pl";

$errmail  = "support\@your.domain";		# where to send errors
$sendfile = "/usr/home/info/autorespond.txt";	# the default file to send
$logfile  = "/usr/home/info/autorespond.log";	# the log file for each request
$filter   = "/usr/home/info/autorespond.filter"; # addresses to block
$tmpfile  = "/tmp/autorespond.$$";		# temporary file
$inbody   = 0;					# email message body flag

# UNIX commands
$sendmail = "/usr/sbin/sendmail -odi";	# the path to sendmail
$datecmd  = "/bin/date";		# the path to the date command


###
###  Step 1:  Process command line parameters.
###

#    -m message : specifies the message file to send
#    -l log     : specifies the name of the file used to log recipients
#    -f filter  : specifies the name of the filter file used to block abusers
#    -e         : suppress autoresponder email error reporting
#    -s         : preserve the Subject: header in the message file
#    email      : a list of email recipients for the original input message

&Getopts ('def:l:m:s');

# debugging option
if ($opt_d) {
  $debug = 1;
}

# error suppression option
if ($opt_e) {
  $noerrout = 1;
}

# preserve message file subject option
if ($opt_s) {
  $keepsubj = 1;
}

# if they specified a filter file, use it
if ($opt_f) {
  $filter = $opt_f;
}

# if they specified a log file, use it
if ($opt_l) {
  $logfile = $opt_l;
}

# if they specified a response file, use it
if ($opt_m) {
  $sendfile = $opt_m;
}

# save any optional email recipients
@emailrcp = @ARGV;

# format a date stamp for the log file
$date = `$datecmd "+%D %T"`;		# get a date stamp


###
###  Step 2:  Read through the message header and find the return address.
###           *** This saves the incoming message to a temporary file ***
###

# open stdin
open (INP, "-")		|| &HandleError ("Error $|: can not open stdin");
open (TMP, ">$tmpfile")	|| &HandleError ("Error $|: can not open $tmpfile");

while (<INP>) {

  # save the input lines
  print TMP "$_";

  # look for our loop control element ANYWHERE in the message
  if (/X-Loop-Control: Autoresponder/) {
    $errmsg = "Error: Message loop detected!";
    $error  = 1;
  }

  # detect the change from message header to message body
  if ($_ eq "\n") {
    $inbody = 1;
  }

  # if we are in the message body, get another line
  $inbody && next;

  # handle From (from-space)
  if (/^from \s*(\S+)\s*/i) {
    $fromspace = $1;
    next;
  }

  # handle From:
  if (/^from: \s*(\S+)\s*/i) {
    $from = $1;

    # if it is embedded in angle brackets, strip it out
    if (/\<(\S+)\>/) {
      $from = $1;
      next;
    }

    if (/\s+(\S+)\@(\S+)\s*/) {
      $from = $1 . "\@" . $2;
      next;
    }
  }

  # handle Reply-To:
  if (/^reply-to: \s*(\S+)\s*/i) {
    $replyto = $1;

    # if it is embedded in angle brackets, strip it out
    if (/\<(\S+)\>/) {
      $replyto = $1;
      next;
    }

    if (/\s+(\S+)\@(\S+)\s*/) {
      $replyto = $1 . "\@" . $2;
      next;
    }
  }

  #
  # Code added here to save most of the senders header (for loop control).
  #

  # save the subject line
  if (/^subject:(.*)$/i) {
    if ($keepsubj) {			# check for subject override
      next;
    }

    if (/^subject:\s*$/i) {		# blank subject line -- ignore
      next;
    }

    push (@extrahdrs,"Subject: Re:$1");
    $hassubject = 1;
    next;
  }

  # dump received headers
  if (/^received:/i) {
    next;
  }

  # handle continuation lines
  if (/^\s+/) {
    next;
  }

  # dump provider headers
  if (/^x-provider:/i) {
    next;
  }

  # dump organization headers
  if (/^organization:/i) {
    next;
  }

  # dump message-id headers
  if (/^message-id:/i) {
    next;
  }

  # dump to headers
  if (/^to:/i) {
    next;
  }

  # dump apparently-to headers
  if (/^apparently-to:/i) {
    next;
  }

  # dump date headers
  if (/^date:/i) {
    next;
  }

  # dump x-mailer headers
  if (/^x-mailer:/i) {
    next;
  }

  # dump content-type headers
  if (/^content-type:/i) {
    next;
  }

  chop;
  push (@extrahdrs,$_);
}

close (INP);
close (TMP);

# if we encountered an error during processing, blast out of the message
if ($error) {
  &HandleError ($errmsg);
}

###
###  Step 3:  Check file attributes to avoid security holes.
###           *** Done after the original message is saved to temp file ***
###

# stat the message reply file and retrieve its permissions
($dev,$inode,$mode,$nlink,$uid,$gid,$rdev,
 $size,$atime,$mtime,$ctime,$blksz,$blocks) = stat ($sendfile);

# convert the mode into a binary value
$val = pack ("I",$mode);

# convert the mode bits into a bit array
foreach $i (0 .. 31) {
  @bits[$i] = vec ($val,$i,1);
}

$othx = $bits[0];		# other execute
$othw = $bits[1];		# other write
$othr = $bits[2];		# other read
$grpx = $bits[3];		# group execute
$grpw = $bits[4];		# group write
$grpr = $bits[5];		# group read
$usrx = $bits[6];		# owner execute
$usrw = $bits[7];		# owner write
$usrr = $bits[8];		# owner read
$sett = $bits[9];		# sticky bit
$setg = $bits[10];		# set gid bit
$setu = $bits[11];		# set uid bit

# if the file is not world readable, head south ...
if ($othr != 1) {
  &HandleError ("Permission Error: $sendfile is not world readable");
  # never returns ...
}

# uncomment if you want to prevent files owned by root from download
#if ($uid == 0) {
#  &HandleError ("Permission Error: $sendfile is owned by root");
#  # never returns ...
#}

#if ($logfile ne "") {
#  ($dev,$inode,$mode,$nlink,$uid,$gid) = stat ($logfile);
#  if ($uid == 0) {
#    &HandleError ("Permission Error: $logfile is owned by root");
#    # never returns ...
#  }
#}

#if ($filter ne "") {
#  ($dev,$inode,$mode,$nlink,$uid,$gid) = stat ($filter);
#  if ($uid == 0) {
#    &HandleError ("Permission Error: $filter is owned by root");
#    # never returns ...
#  }
#}


###
###  Step 4:  Determine which email return address to use.
###

if ("$replyto" ne "") {
  $dest = $replyto;
} elsif ("$from" ne "") {
  $dest = $from;
} elsif ("$fromspace" ne "") {
  $dest = $fromspace;
} else {
  &HandleError("Unable to determine originator of message");
}


###
###  Step 5:  Don't respond to postmaster, mailer-daemon, daemon, or maiser
###           in order to avoid loops.
###

$dest =~ y/A-Z/a-z/;
($userid,$junk) = split (/\@/,$dest);
if ("$userid" eq "postmaster") {
  &HandleError("Error: Message originator is postmaster\!");
} elsif ("$userid" eq "mailer-daemon") {
  &HandleError("Error: Message originator is mailer-daemon\!");
} elsif ("$userid" eq "daemon") {
  &HandleError("Error: Message originator is daemon\!");
} elsif ("$fromspace" eq "daemon") {
  &HandleError("Error: Message originator is daemon\!");
} elsif ("$userid" eq "maiser") {
  &HandleError("Error: Message originator is maiser\!");
}


###
### Step 6:  If we have a filter file, apply the filter rules
###

if ($filter ne "") {
  open (FLT, "$filter")	|| &HandleError ("Error $|: can not open $filter");
  while (<FLT>) {
    chop;
    next if /^$/;                       # dump blank lines
    next if /^[\s]*[\#]/;               # dump comments

    if ($dest eq $_) {
      &LogEntry ("blocked by filter");
      close (FLT);
      unlink ($tmpfile);
      exit 0;
    }
  }
  close (FLT);
}


###
###  Step 7:  Send the response file back to the originator of the request.
###

open (MSG,"$sendfile") ||
  &HandleError("error $|: can not open $sendfile");
open (OUT,"| $sendmail $dest") ||
  &HandleError("error $|: invoking sendmail");

# check for option to preserve message file subject line
if ($keepsubj) {
  $hassubject = 0;
}

# drop in our loop control header
print OUT "X-Loop-Control: Autoresponder\n";

# print the additional user headers (for the sender's loop control)
foreach $header (@extrahdrs) {
  print OUT "$header\n";
}

while (<MSG>) {
  if ($hassubject == 1) {
    if (/^Subject: /) {
      next;
    }
  }
  print OUT "$_";
}

close MSG;
close OUT;


###
###  Step 8:  If email recipients exist, send a copy of the message to them.
###

if (@emailrcp) {
  open (TMP,"$tmpfile") ||
    &HandleError("error $|: can not open $tmpfile");
  open (OUT,"| $sendmail @emailrcp") ||
    &HandleError("error $|: invoking sendmail");

  while (<TMP>) {
    print OUT "$_";
  }

  close TMP;
  close OUT;
}


###
###  Step 9:  Log the request to the log file.
###

&LogEntry ();
unlink ($tmpfile);

exit 0;

### END MAIN PROGRAM ###


###
###  Generic Error Handler
###

sub
HandleError {
  local ($error) = @_;

  # if they disabled error reporting, just get out
  if ($noerrout == 1) {
    unlink ($tmpfile);
    &LogEntry ($error);
    exit 0;
  }

  if ($errmail ne "") {

    open (OUT,"| $sendmail $errmail");

    print OUT <<EOF;
X-Loop-Control: Autoresponder
Subject: Autoresponder Error Detected

$error

The autoresponder received a message that encountered an error during
the processing of that message.  This may be an error on the part of the
autoresponder, or could simply indicate the sender has a misconfigured
email client.  The complete message header for the problem message is
shown below:

EOF

    open (TMP,"$tmpfile");
    while (<TMP>) {
      print OUT "$_";
    }
    close (TMP);
    close (OUT);
  }

  unlink ($tmpfile);
  &LogEntry ($error);
  exit 0;
}

sub
LogEntry {
  local ($msg) = @_;

  if ($logfile ne "") {
    open (LOG,">>$logfile") || print "error $|: can not open $logfile\n";
    if ($msg ne "") {
      print LOG "$date $dest $sendfile : $msg\n";
    } else {
      print LOG "$date $dest $sendfile\n";
    }
    close (LOG);
  }
}



To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-net" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199903262202.RAA26464>