[comp.lang.perl] A Global mail script in perl

smfst2@unix.cis.pitt.edu (Seth M Fuller) (06/11/91)

Below is a shell archive of a global mail script I adapted from 
Larry Wall's gsh. I haven't seen anything like it and I thought it
might be useful to others.



Seth M. Fuller

#!/bin/sh
# to extract, remove the header and type "sh filename"
if `test ! -s ./README.gmail`
then
echo "writing ./README.gmail"
cat > ./README.gmail << '\End\Of\Shar\'
			Gmail --- Mail Globally

	This is gmail. Gmail is a perl script that I adapted from
Larry Wall's gsh. It allows you to mail globally to users via an alias
file called gusers. I realize there are other ways of doing this with
many mail programs, but I wanted a neat, simple solution to use at our
offices that wouldn't require a lot of maintenance. Gmail lets you
compose your mail message in your favorite editor (or stdin), then
mails it to the list of users (or aliases) you specify on the command
line.

	Gmail uses an alias file much like ghosts. The main difference
being that the first field is a host name and the second field is a
user name. All other fields are aliases.

	Gmail is also smart enough to know whether your mail program
is BSD mail or ATT mail and from that whether or not to prompt for a
subject. Gmail tests to see if the file /usr/lib/sendmail exists, if
it does it assumes this is a BSD mail program, if not an ATT mail
program. If anybody knows of a better way of doing this let me know.

If anybody has any comments I can be reached at:

Seth M. Fuller
Giant Eagle, Inc.
MIS Dept.
101 Kappa Drive
Pittsburgh, PA 15238

(412)963-6200

e-mail:	unix.cis.pitt.edu!smfst2






\End\Of\Shar\
else
  echo "will not over write ./README.gmail"
fi
if `test ! -s ./gmail`
then
echo "writing ./gmail"
cat > ./gmail << '\End\Of\Shar\'
#! /acct/bin/perl

# gmail --- Mail globally via the gusers file
# Author: Seth M. Fuller
# I adapted this program from gsh, written by Larry Wall.
# This program accesses a file called gusers, similar to ghosts, for its
# aliases. The main difference is that the first two fields are the host
# name for the user and the user's login name. The remaining fields are 
# any mailing groups that the user belongs to.

# Mail globally

$SIG{'QUIT'} = 'quit';                 # install signal handler for SIGQUIT

sub getswitches {
    while ($ARGV[0] =~ /^-/) {         # parse switches
       $ARGV[0] =~ /^-b/ && ($bsdmail++,shift(@ARGV),next);
       $ARGV[0] =~ /^-s/ && ($silent++,shift(@ARGV),next);
       $ARGV[0] =~ /^-d/ && ($from_stdin++,shift(@ARGV),next);
       $ARGV[0] =~ /^-e/ && ($got_editor++,$editor=$ARGV[1], 
			     shift(@ARGV),shift(@ARGV),next);
       last;
    }
}

do getswitches();                      # get any switches before class
$systype = shift;                      # get name representing set of users

$cmd = join(' ',@ARGV);                # remaining args constitute the command
$cmd =~ s/'/'"'"'/g;                   # quote any embedded single quotes

$one_of_these = ":$systype:";          # prepare to expand "macros"
$one_of_these =~ s/\+/:/g;             # we hope to end up with list of
$one_of_these =~ s/-/:-/g;             #  colon separated attributes

if (! $got_editor)
{
    $editor = $ENV{'GMEDIT'}; # Get or set editor
    if (! $editor)
    {
	$editor = $ENV{'EDITOR'}; # Get or set editor
	if (! $editor)
	{
	    $editor = "vi";
	}
    }
}
else
{
    $from_stdin = 0;
}

$remainder = '';
$bsdmail++ if  -f "/usr/lib/sendmail";

if ($bsdmail)
{
    $|=1;			# Turn off buffering for reading from keyboard
    print "Subject: ";
    @subject = &readline();
    $subject = join('', @subject);
}

if ($from_stdin) {                     # Get mail from stdin
    print "Enter body of mail message then Ctrl-D (on a line by itself)\n";
    `cat >/tmp/gmail$$`;               #  get input into a handy place
}
else
{
    system("$editor /tmp/gmail$$");
    if ( -f "/tmp/gmail$$" == 0)
    {	
	unlink "/tmp/gmail$$";	
	exit 0;
    }
}

@ARGV = ();
push(@ARGV,'.gurem') if -f '.gurem';
push(@ARGV,'.gusers') if -f '.gusers';
push(@ARGV,'/etc/gusers') if -f '/etc/gusers';
push(@ARGV,'gusers') if -f 'gusers';

line: while (<>) {             # for each line of gusers

    s/[ \t]*\n//;                      # trim trailing whitespace
    if (!$_ || /^#/) {                 # skip blank line or comment
       next line;
    }

    if (/^(\w+)=(.+)/) {               # a macro line?
       $name = $1; $repl = $2;
       $repl =~ s/\+/:/g;
       $repl =~ s/-/:-/g;
       $one_of_these =~ s/:$name:/:$repl:/;    # do expansion in "wanted" list
       $repl =~ s/:/:-/g;
       $one_of_these =~ s/:-$name:/:-$repl:/;
       next line;
    }

    # we have a normal line

    @attr = split(' ');                # a list of attributes to match against
                                       #   which we put into an array
    $host = $attr[0];                  # the first attribute is the host name
    if ($host eq "local")
    {
	$host = "";
    }
    else
    {
	$host = $host . "!";
    }

    $user = $attr[1];                  # the second attribute is the user name

    $wanted = 0;
    foreach $attr (@attr) {            # iterate over attribute array
       $wanted++ if index($one_of_these,":$attr:") >= 0;
       $wanted = -9999 if index($one_of_these,":-$attr:") >= 0;
    }
    if ($wanted > 0) {
       if ($bsdmail)
       {
          print "mail -s \"$subject\" $host$user\n" unless $silent;
       }
       else
       {
          print "mail $host$user\n" unless $silent;
       }
       $SIG{'INT'} = 'DEFAULT';
       if ($bsdmail)
       {
          if (open(PIPE, # mail the msg.
	  "mail -s \"$subject\" $host$user < /tmp/gmail$$ 2>&1|")) {  
              $SIG{'INT'} = 'cont';
              close(PIPE);
          } 
	  else {
              print "(Can't execute mail: $!)\n";
              $SIG{'INT'} = 'cont';
          }
       }
       else
       {
          if (open(PIPE,
	  "mail $host$user < /tmp/gmail$$ 2>&1|")) {  # mail the msg.
              $SIG{'INT'} = 'cont';
              close(PIPE);
	  }
	  else {
              print "(Can't execute mail: $!)\n";
              $SIG{'INT'} = 'cont';
          }
       }
    }
}

unlink "/tmp/gmail$$";

if ($remainder) {
    chop($remainder);
    open(gurem,">.gurem") || (printf stderr "Can't make a .gurem file: $!\n");
    print gurem 'urem=', $remainder, "\n";
    close(gurem);
    print 'urem=', $remainder, "\n";
}

# here are a couple of subroutines that serve as signal handlers

sub cont {
    print "\rContinuing...\n";
    $remainder .= "$host+";
}

sub quit {
    $| = 1;
    print "\r";
    $SIG{'INT'} = '';
    kill 2, $$;
}

# This subroutine reads a line from the standard input and returns an
# array with all of characters typed by the user up to the newline.
sub readline
{
   $more_line = 1;
   while ($more_line)
   {
       $ch = getc;
       if ($ch lt ' ')
       {
	   $more_line = 0;
       }
       else
       {
	   push(@line, $ch);
       }
   }
   return @line;
}
\End\Of\Shar\
else
  echo "will not over write ./gmail"
fi
if `test ! -s ./gmail.man`
then
echo "writing ./gmail.man"
cat > ./gmail.man << '\End\Of\Shar\'
.TH GMAIL L "10 Jun 1991"
.SH NAME
gmail \- globally mail to aliases
.SH SYNOPSIS
.B gmail
[options]
.I user-alias
.SH DESCRIPTION
.I Gmail
works similarly to mail(1) except that you may specify a set of users mail to.
The host/user sets are defined in the file /etc/gusers. Each line of gusers
file is of the form of:

host user [alias] [alias] [alias] ...

If the host is the word "local" no host is specified in the mail command and
the message goes to the user on the local system.
(An individual user name can be used as a set containing one member.)
You can give a command like

       gmail mis 

to mail to all people in the MIS Department.
.P
Gmail starts up an editor to allow you to type in your message. Once you 
save the message and exit the editor, gmail starts sending it out to all 
users in the list. Gmail will first check to see if the environment variable
GMEDIT is set. If set gmail will use the value of GMEDIT as the editor. If 
there is no value for GMEDIT, gmail will check to see if the environment 
variable EDITOR is set and will use that editor, otherwise, gmail defaults to 
vi as the editor. The -e option allows you to specify the editor to use on the
command line (full path not needed). The -e option overrides the envronment 
variables. 
You may instead use the -d option to accept the mail message from standard 
input until end of file.
.P
You may specify the union of two or more sets by using + as follows:

       gmail pos+store 

which will mail to all members of the POS and store groups.
.P
Commonly used sets should be defined in /etc/gusers.
For example, you could add a line that says

       mis=admin+pos+store

Another way to do that would be to add the word "mis" after each of the 
host/user entries:

       calvin  chas    admin  mis 
.br
       mickey  seth    retail  store mis 
.br
       pluto  johnc   retail  pos

Users and sets of user can also be excluded:

       retail=mis-admin

Any user so excluded will never be included, even if a subsequent set on the
line includes it:

       foo=abc+def
       bar=xyz-abc+foo

comes out to xyz+def.

You can define private user sets by creating .gusers in your current directory
with entries just like /etc/gusers.
Also, if there is a file .gurem, it defines "urem" to be the remaining users
from the last gmail that didn't succeed everywhere.

Options are

.IP "\-b" 8
Requests a subject for BSD style mail programs. Gmail will try to determine
if your mail program is BSD or ATT and request or not request a subject 
accordingly (in other words you probably won't need to use this option if
gmail can figure out whether or not you use BSD mail by whether or not your
system has /usr/lib/sendmail).
.IP "\-d" 8
Causes gmail to collect input till end of file, and then distribute that input
to each invokation of mail.
.IP "\-e" 8
Specify the editor to use for the mail message. Incompatible with the -d option.
.IP "\-s" 8
Do work silently.
.PP
Interrupting with a SIGINT will cause the mail to the current user to be 
skipped and execution resumed with the next user.
To stop completely, send a SIGQUIT.
.SH RESTRICTIONS
Gmail only sends mail. It is not for receiving mail. 
No provision is made carbon copying, but that should not be needed with
well defined sets of users.
.SH SEE ALSO
mail(1)
\End\Of\Shar\
else
  echo "will not over write ./gmail.man"
fi
if `test ! -s ./gusers`
then
echo "writing ./gusers"
cat > ./gusers << '\End\Of\Shar\'

# This first section gives alternate sets defined in terms of the sets given
# by the second section.  The order is important--all references must be
# forward references.

mis=store+pos+admin

# This second section defines the basic sets.  Each host/user combination
# should have a line that specifies which sets it is a member of.  
# Extra sets should be separated by white space.  (The first section isn't 
# strictly necessary, since all sets could be defined in the second section, 
# but then it wouldn't be so readable.)
# Each line must be of the form of:
# host user [alias] [alias] [alias] ...

local seth    retail  store  
calvin  chas    admin   
pluto  Jon     retail  pos   
pluto  anne    retail  store
pluto  floyd   retail  store
cricket fredb   retail  store
pluto  joe     retail  
pluto  johnc   retail  pos
pluto  maryann retail  pos
\End\Of\Shar\
else
  echo "will not over write ./gusers"
fi
echo "Finished archive 1 of 1"
exit