Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Postman: A Bridge to the UNIX Mail System


JUL94: Postman: A Bridge to the UNIX Mail System

Postman: A Bridge to the UNIX Mail System

Automating the mail process

Zongnan H. Lu

Henry is a system analyst with the University of Michigan School of Business Administration. He can be contacted at [email protected].


Postman is an interface program that sits between PIS, our in-house UNIX-based personal-information system (which currently supports more than 3000 registered users) and the UNIX sendmail program, which allows access to the outside world. Postman checks incoming mail delivered by sendmail, putting it into the PIS, and checks outgoing mail in the PIS, sending it to sendmail. In short, postman provides a way to exchange mail between user-application programs and the outside world through the existing UNIX Mail System.

Communication between sendmail and postman is handled by the UNIX Mail command. If a user has an Internet mailing address, any mail coming from outside will not only go into the PIS, but will also be forwarded to the Internet mailing address; see Figure 1.

Before running postman or PIS, your application program must be running on a UNIX computer which is set to be either mail master, or using the DNS.

A special user group must be created for the application users. The system-specific mail-spooling directory is used in the postman program. Each user has his own mailbox bearing the user's name. In our case, /usr/spool/mail is used as a mail-spooling directory. Postman is owned by a superuser--root--and can handle only mail formatted as ASCII text.

Communication between postman and the PIS is flexible. For example, I put mail read from a mailbox directly into a database accessed by PIS. Conversely, the notes in the database are read by the postman program and then sent out.

Adding a User

Whenever a new user is registered in PIS, the user's name should be added into the UNIX-system password file /etc/passwd, which will be checked by the sendmail program when mail comes in. All users in PIS are set to be in the same group; that is, they have the same group ID and an entry in the /etc/passwd file as follows: henry_smith:ABC123XYZ:101:66:henry_smith:/pis_mailuser:/bin/csh, where henry_smith is the user's mailing address known to outside senders. ABC123XYZ is the user's password for logging onto the UNIX system. 101 is a unique user ID, assigned by the postman program. Under UNIX, a user ID could be as large as 32767 or 65534. The group's ID registered in the /etc./group file is 66, /pis_mailuser is the user's home directory used by the postman program, and /bin/csh is the login shell. For simplicity and to save disk space, group ID, home directory, login shell, and even password are shared by all PIS users. When the postman is alerted that a user has been denied access to PIS, the user's entry in the /etc/passwd will be deleted.

If a user has an Internet address set in PIS, then the user's name and Internet address will be added into the UNIX alias file etc/aliases. The entry looks like: henry_smith:pis_user, [email protected], where the first part of the line is the user's name registered in /etc/passwd. The third part is an Internet mailing address. This could be a number of forwarding addresses separated by commas. The second part, pis_user, is shared by all PIS users in the /etc/aliases file. This forces incoming mail not only to go into PIS but also to forward it to its Internet address. Depending on how sendmail searches the file /etc/aliases, if henry_smith was placed in the position of pis_user, then the search would go into an infinite loop. Ignoring this problem, I created another user in /etc/passwd, pis_user. This caused all incoming mail to be put into pis_user's mailbox instead of the mailboxes belonging to those users who have set their Internet addresses. The postman will open the pis_user's mailbox, find the name of sender and recipient, and put them into PIS properly. The system command newaliases should be called every time /etc/aliases is updated.

Reading Incoming Mail

Before running the postman program, I create a reading mail format shell-command file, /pis_mailuser/d that looks like this:

p 1
d 1
q

The first line tells the Mail command to read the first piece of mail in a special mailbox, d 1 deletes the piece after reading, and q exits from Mail command.

When mail is delivered by sendmail, it is added to its recipient's mailbox (a text-format file) in the /usr/spool/mail directory. The postman program scans all mailboxes in the mail-spooling directory. To read one piece of mail for each user, the postman calls: system("Mail --N >/pis_mailuser/a --u henry_smith </pis_mailuser/d"), where --N tells the Mail command to read mail without the initial header summary; /pis_mailuser/a is a temporary file for Mail to put mail into and later for postman to read; the name following --u (henry_smith, in this case) is a user's name; and d is the format file for Mail to use. Before calling system(), /pis_mailuser/a must be deleted. After system(), postman calls another function to read /pis_mailuser/a and put it into PIS. This whole process is in two loops, and it continues until all mailboxes are empty. If a recipient has a forwarding address in /etc/aliases, then the sendmail program will forward the incoming mail to its Internet address and deliver it into the pis_user mailbox.

Sending Mail

In sending mail, the postman program reads the mail sender's name, recipient's address, and mail text from PIS and puts the mail itself into /pis_mailuser/a. It then changes the owner of /pis_mailuser/a to the sender's name, and root creates a file that changes the mode of /pis_mailuser/a to 0777 (executable). For root to send the mail using the original mail sender's name, the postman program creates a one-line shell-command file: /pis_mailuser/m: Mail --s "this is a test" [email protected] </pis_mailuser/a, where "this is a test" is the mail's subject, [email protected] is the destination address, and /pis_mailuser/a is the mail file. The owner and mode of /pis_mailuser/m must then be changed to the sender's name and 0777. Finally, postman issues the function call: system("su --henry_smith c /pis_mailuser/m") to post the mail.

The Postman Program

The postman program (see Listing One and Listing Two) was written in Sun C++ 2.1 under SunOS 4.1.3. Letter is a base class that includes five members: mail title, date, sender, recipient, and the name of the file in which mail text is stored. Two pure virtual functions send() and read(), are created to send mail out and read mail in. Users must create their own version of send() and read() based on different applications and environments. Class myletter is derived from letter. As mentioned earlier, if a user has a forwarding address set in the /etc/aliases file, then his or her mail will be put into pis_user's mailbox. When myletter::read() reads the pis_user mailbox, it may see mail for different users each time. To find the recipient's name, read() scans mail text, reads the line with the first substring "To: ", and calls _getusrname() to separate the user name from its mailing address for later use.

A simple error-handling class, Errors, reports error messages when a critical error occurs and terminates the program. Class dirf is used for reading the mailbox in the mail-spool directory. Postman resides in the default directory. The member function resetdir() should be called before starting another search if dirf is declared as static or global.

The main body of the program is in a loop that checks incoming mail and passes it to PIS, checks outgoing mail from PIS and sends it out to the sendmail, and goes to sleep for a certain period of time until the alarm wakes it up.

Figure 1 Postman sits between the personal-information system and sendmail.

Listing One


//////////////////////////////////////////
//    ml.h - header file for postman    //
//////////////////////////////////////////

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <signal.h>

#include <sysent.h>
#include <iostream.h>
#include <fstream.h>
#include <stdarg.h>

const int WAITTIME = 60;
const int MAXDAT   = 24;
const int MAXNAM   = 128;
const int MAXFNM   = 32;
const int MAXID    = 12;

const int FRSTUSRID = 200; // PIS users' ID start from 200

const char* A_FILE = "a";
const char* M_FILE = "m";
const char* D_FILE = "d";

#define MAILDIR  "/usr/spool/mail"
#define ERRLOG   "/tmp/postman.err"
#define HOSTADDR "@my_pis.my_corp.com"

class Errors {
   enum {bsize = 256};
   char fname[bsize];          // error log file
 public:
   Errors(char *fn) {strcpy(fname, fn);};
   void seterr(const char *,...);
};
class letter {
 public:
   char title[MAXNAM];
   char date[MAXDAT];
   char sender[MAXNAM];
   char recipient[MAXNAM];
   char textf[MAXFNM];
 public:
   virtual int send() = 0;
   virtual int read(char *) = 0;
};
class myletter : public letter {
   char *hostaddr;
   char usrdir[MAXFNM];
   int  usrid;
   int  grpid;
 public:
   int  letterId;
 public:
   myletter(char *hst) : letterId(-1), hostaddr(hst) {
      usrdir[0]=0;
   }
   int  isinPIS(char *);
   int  send();
   int  read(char *);
};
class dirf {
   DIR  *dirp;
   char *dirname;
   struct direct *ds;
 public:
   dirf (char *drnm=".") { dirname=drnm; dirp=NULL;}
   ~dirf() {dirp=NULL;}
   void resetdir(char *drnm=".") {
      if (dirp)
         closedir(dirp);
      dirp=NULL;
      dirname=drnm;
   }
   int  rddir(char *);
};
extern Errors _syserr;
extern "C" {
   int  _getusrname(char *, char *, char *);
   void check_inmail();
   void check_outmail();
   int  _read_outmail(myletter *);
   void _readmail(char *);
   void wakeup();
   void _mail2note(myletter *);
}


Listing Two


//////////////////////////////////////////
//              postman.c               //
//////////////////////////////////////////

#include "ml.h"
//---------------------------------------//
// dirf::rddir() - class member function //
//---------------------------------------//
int dirf::rddir(char *fname)
{
   if (dirp == NULL) {
      if ((dirp = opendir(dirname)) == NULL)
         _syserr.seterr("open %s failed, errno = %d\n", dirname, errno);
   }
   errno = 0;
   if ((ds = readdir(dirp)) == NULL) {
      if (errno) {
         _syserr.seterr("readdir failed, errno = %d\n", errno);
      }
      return 0;
   }
   strcpy(fname, ds->d_name);
   return 1;
}
//------------------------------------------//
// Errors::seterr() - class member function //
//------------------------------------------//
void Errors::seterr(const char *format_str,...)
{
   FILE *fp;
   va_list arg_ptr;

   va_start(arg_ptr, format_str);
   if ((fp=fopen(fname,"w"))!=NULL) {
      vfprintf(fp, format_str, arg_ptr);
      fclose(fp);
   }
   va_end(arg_ptr);
   exit (-1);
}
//--------------------------------------------------------------------//
// _find_usrid() - find a user's login I.D. in the  /etc/passwd file. //
// -------------------------------------------------------------------//
int myletter::isinPIS(char *usrnam)
{
   struct passwd *p;
   
   setpwent();
   while (p=getpwent()) {
      if (!strcmp(usrnam,p->pw_name)) {
         if (p->pw_uid < FRSTUSRID)
            return 0;
         strcpy (usrdir, p->pw_dir);
         usrid = p->pw_uid;
         grpid = p->pw_gid;
         return 1;
      }
   }
   endpwent();
   return 0;
}
//------------------------------------------//
// myletter::send() - class member function //
//------------------------------------------//
int  myletter::send()
{
   char mfile[256], buf[256];
   FILE *fp;
   int  ret, forward=0;

   if (!isinPIS(sender)) {
      _syserr.seterr("send:isinPIS:[%s]",sender);
   }
   sprintf(mfile,"%s/%s", usrdir, M_FILE);
   if ((fp=fopen(mfile,"w"))== NULL) {
      return -1;
   }
   fprintf(fp,
      "Mail -s \"%s\" %s <%s\n", title, recipient, textf),
   fclose(fp);

   if (chmod(mfile, 0777))
      _syserr.seterr("send:chmod:%s",mfile);
   if (chmod(textf, 0777))
      _syserr.seterr("send:chmod:%s",textf);
   if (chown(mfile, usrid, grpid))
      _syserr.seterr("send:chown:%s",mfile);
   if (chown(textf, usrid, grpid))
      _syserr.seterr("send:chown:%s",textf);

   sprintf(buf,"su - %s -c %s", sender, mfile);
   ret=system(buf); 
   if (ret)
      _syserr.seterr("send:system:%s(%d)\n",buf,ret);

   return 1;
}
//------------------------------------------//
// myletter::read() - class member function //
//------------------------------------------//
int  myletter::read(char *usr)
{
   short frnm=0, tonm=0, subj=0, datm=0, ret;
   char buf[256];
   ifstream  inf;

   sprintf(buf, "%s/%s", usrdir, A_FILE);
   unlink(buf);
   sprintf(buf,"Mail -N >%s/%s -u %s <%s/%s",
               usrdir, A_FILE, usr, usrdir, D_FILE);
   ret = system(buf);
   if (ret) {
      _syserr.seterr("read:system:%s:ret=%d\n",buf, ret);
   }
   sprintf(textf, "%s/%s", usrdir, A_FILE);
   inf.open(textf);
   while (inf.getline(buf,256)) {
      if (!datm && !strncmp(buf,"Date:",5)){
         strcpy(date,(char *)&(buf[5]));
         datm++;
      } else if (!frnm && !strncmp(buf,"From",4)) {
         strcpy(sender,strtok((char *)&(buf[5])," "));
         frnm=1;
      } else if (!tonm && !strncmp(buf,"To: ",4)) {
         if (strstr(buf,usr)) {
            // mail is in the user's mailbox //
            strcpy(recipient, usr);
         } else {
            // mail is in the pis_user mailbox //
            if (!_getusrname((char *)&(buf[4]),
                              recipient, hostaddr))
               _syserr.seterr("_getusrname:[%s]\n",buf);
         }
         tonm++;
      } else if (!subj && !strncmp(buf,"Subject: ",9)) {
         strcpy(title, (char *)&(buf[9]));
         subj++;
      } else if (datm && frnm && tonm && datm &&
                 !strncmp(buf,"Status:",7)) {
         break;
      }
   }
   inf.close();
   return 1;
}
//-------------------------------------//
// wakeup() - signal handling function //
//-------------------------------------//
void wakeup()
{
   alarm(0);
   signal(SIGALRM, (SIG_PF)wakeup);
}
Errors _syserr(ERRLOG);
void main()
{
   signal(SIGALRM, (SIG_PF)wakeup);
   alarm(1);

   do {
      pause();

      check_inmail();

      check_outmail();

      alarm(WAITTIME);
   } while (1);
}
//-------------------------------------------------------//
// check_outmail() - check outgoing mail and send it out //
//-------------------------------------------------------//
void check_outmail()
{
   myletter ml(HOSTADDR);
   do {
      if (!_read_outmail(&ml)) {
         break;
      }
      ml.send();
   } while (1);
}
//---------------------------------------------------------//
// check_inmail() - check incoming mail and pass it to PIS //
//---------------------------------------------------------//
void check_inmail()
{
   char fname[256];
   dirf df(MAILDIR);

   while (df.rddir(fname)) {
      if (strcmp(fname, ".") && strcmp(fname, "..") &&
          strcmp(fname,"root")) {
         _readmail(fname);
      }
   }
}
//------------------------------------------------------------------//
// _readmail() - read title, sender, recipient, and date from mail. //
//------------------------------------------------------------------//
void _readmail(char *usr)
{
   char     tmp[256];
   myletter ml(HOSTADDR);
   int      ret;

   if (!ml.isinPIS(usr))
      return;

   do {
      ml.read(usr);

      _mail2note(&ml);

      sprintf(tmp,"%s/%s", MAILDIR, usr);
      ret= access(tmp, F_OK);
   } while (!ret);
}
//-------------------------------------------------------------//
// _getusrname() - find a user's name from a recipient string. //
//-------------------------------------------------------------//
int  _getusrname(char *s, char *usr, char *hst)
{
   char tmp[256], *p;

   usr[0]=0;
   strcpy(tmp, s);
   if (strchr(s,'@')==NULL) {
      p = strtok(tmp," \"<>");
      p = strtok(NULL," \"<>");
      if (p == NULL)
         return 0;
      strcpy(usr,p);
      return (strlen(usr));
   }
   p = strstr(tmp,hst);
   int i = strlen(s)-strlen(p);
   tmp[i]=0;
   for (i=strlen(tmp)-1;i>=0;i--) {
      if (strchr(" <,[\"",tmp[i]))
         break;
   }
   strcpy(usr,(char *)&(tmp[i+1]));
   return (strlen(usr));
}
//---------------------------------------//
// _read_outmail() - read mail from PIS. //
//---------------------------------------//
int  _read_outmail(myletter *ml)
{
   strcpy(ml->textf,"");
   //
   // user application function getone_inpis()
   // assigns mail title, date, sender, recipient,
   // and text file name to ml.
   //
   // if(getone_inpis(ml->title, ml->date, ml->sender,
   //                 ml->recipient, ml->textf)) {
   //    return 1;
   // else
   //    return 0;
   //
   return 0;
}
//---------------------------------//
// _mail2note() - pass mail to PIS //
//---------------------------------//
int  _getletterId(void)
{
   static i=0;

   return (++i);
}
void _mail2note(myletter *ml)
{
   ml->letterId = _getletterId();
   //
   // pass2pis(ml->title, ml->date,
   //          ml->sender, ml->recipient, ml->textf);
   //
}

Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.