/*
* Written by Paul Pomes, University of Illinois, Computing Services Office
* Copyright (c) 1991 by Paul Pomes and the University of Illinois Board
* of Trustees.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by the University of
*      Illinois, Urbana and its contributors.
* 4. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Email:       [email protected]     USMail: Paul Pomes
* ICBM:        40 06 47 N / 88 13 35 W         University of Illinois - CSO
*                                              1304 West Springfield Avenue
*                                              Urbana, Illinois,  61801-2910
*/

#ifndef lint
static char rcsid[] = "@(#)$Id: phquery.c,v 1.54 1995/01/17 23:33:07 p-pomes Exp $";
#endif /* lint */

#include "protos.h"
#ifdef __STDC__
# include <unistd.h>
# include <stdlib.h>
# include <string.h>
#else /* !__STDC__ */
# include <strings.h>
#endif /* __STDC__ */

#include <sysexits.h>
#include <sys/stat.h>
#include <assert.h>
#include <qiapi.h>
#include <qicode.h>
#include "phquery.h"
#include "messages.h"

#define         VERSION         "4.4"

/* How to print/log error messages */
#define         DANGER_WILL_ROBINSON(KateBush) \
       { if (Debug) \
               perror (KateBush); \
       if (Log) { \
               char *xyzzy = malloc(strlen(KateBush)+4); \
               (void) strcpy(xyzzy, KateBush); (void) strcat(xyzzy, ": %m"); \
               syslog (LOG_ERR, xyzzy); free(xyzzy); } \
       finis (); }

/*
**  PHQUERY -- Resolve fuzzy addresses to specific a user@FQDN
**
**      FQDN := Fully Qualified Domain Name
**      Phquery is invoked as a mailer (not a final mailer!) by sendmail
**      to resolve addresses of the form [email protected] where some.domain
**      is one of the m4 ALTERNATENAMES define used in building an IDA
**      sendmail.cf file.  At UIUC this would be [email protected] .
**
**      The user token is interpreted first as a QI alias, then as a full
**      name if that fails.  QI is the CSnet Query Interpreter.  At UIUC it
**      contains the entire campus phone directory plus the unit directory.
**      A user entry has about as many fields as ls has option letters.
**      The most important are alias, name, **  email, phone, department,
**      and curriculum.  In the simplest case, matching an alias (guaranteed
**      unique) returns the email address.
**
**      Since life is seldom simple, the alternate cases/actions are summarized
**
**      a) alias match, email found
**              write a X-PH-To: header with the email address found, copy the
**              rest of the message, and re-invoke sendmail
**           OR
**              write a X-PH: VX.Y@<host> and re-invoke sendmail.  This is
**              useful for sites that don't wish to expand alias lists in the
**              header block.
**      b) alias match, no email field:
**              return public fields of ph entry and suggest phone usage
**      c) alias match, bogus email field:
**              sendmail catches this one.  The user will see the X-PH-To:
**              header.  Not the best so far.....
**      d) alias fail:
**              try name field
**      e) single name match, email present:
**              deliver as in a)
**      f) single name match, no email field:
**              handle as in b)
**      g) single name match, bogus email field:
**              handle as in c)
**      h) multiple (<5) name matches:
**              return alias, name, email, and dept fields of matches
**      i) multiple (>5) name matches:
**              return "too ambiguous" message
**
**      Phquery is also used to create return addresses of the form
**      ph-alias@ALTERNATENAMES.  This is implemented by adding the fields
**
**      Resent-From: postmaster@<host>
**      Reply-To: ph-alias@ALTERNATENAMES
**      Comment: Reply-To: added by phquery (Vx.y)
**
**      N.B., RFC-822, Section 4.4.1 requires that the From / Resent-From
**      fields be a single, authenticated machine address.
*/

FILE    *ToQi =         FILE_NULL;      /* write to the QI */
FILE    *FromQi =       FILE_NULL;      /* read from the QI */

extern int      errno;

/* Set to carbon-copy postmaster on error returns */
int     PostmasterCC =  0;

#ifdef REPLYTO
/* Set if the reply-to: field on outgoing mail is to inserted */
int     ReplyTo = 0;
#endif /* REPLYTO */

/* Hostname of this machine */
char    HostNameBuf[100];

/* How program was invoked (argv[0]) for error messages */
char    *MyName;

/* Exit status for finis() reporting to calling process */
int     ExitStat =      EX_TEMPFAIL;

/* Field values of email and alias from ph/qi server */
int     EmailVal, AliasVal;

/* Temporary message file */
char    TmpFile[] =     "/tmp/PhMailXXXXXXX";

/* Temporary file for creating error messages */
char    ErrorFile[] =   "/tmp/PhErrMailXXXXXXX";

/* Temporary file for rewriting messages */
char    NewFile[] =     "/tmp/PhNewMailXXXXXXX";

/*
* How to report events: Debug set for stderr messages, Log for syslog.
* Setting Debug disables fork/execve in ReMail.
*/
int     Debug =         0;
int     Log =           1;

/* From address supplied by caller */
char    *From =         CPNULL;

/* Name of qi server */
char    *QiHost = NULL;         /* Initial Qi server */

char    *usage[] = {
       "usage: %s [-d] [-p] [-l] [-R] [-i] [-s server] [-x service] [-f FromAddress] address1 [address2]",
       CPNULL
};

void ErrorReturn __P((NADD *, FILE *, char *[]));
void FindFrom __P((FILE *));
void ReMail __P((NADD *, FILE *, char *[]));
char * CodeString __P((int));
FILE * OpenTemp __P((const char *));
QIR * PickField  __P((QIR *, int));
void Query __P((NADD *));
int SendQuery __P((NADD *, const char *, const char *));
void RevQuery __P((NADD *));
char * Malloc __P((unsigned int));
void PrintMsg __P((FILE *, char *[]));
char * Realloc __P((char *, unsigned int));
void PrtUsage();
void finis();

main(argc, argv, envp)
int     argc;
char    *argv[], *envp[];
{
       extern  int     optind;         /* from getopt () */
       extern  char    *optarg;        /* from getopt () */
               int     option;         /* option "letter" */
               int     i;              /* good ol' i */
               char    *Service = CPNULL; /* ph alias from -x */
               FILE    *Msg;           /* stream pointer for temp file */
               NADD    *New, *NewP;    /* translated addresses */
               char    Buf[MAXSTR];
       extern  char    HostNameBuf[];

#ifdef SA_FULLDUMP
       /* AIX does not provide the data section in a core dump by default. */
       struct sigaction handlr;

       handlr.sa_handler = NULL;
       handlr.sa_flags = SA_FULLDUMP;
       sigaction(SIGSEGV, &handlr, NULL);
       sigaction(SIGIOT, &handlr, NULL);
#endif /* SA_FULLDUMP */

       (void) chdir ("/usr/tmp");

       MyName = ((MyName = strrchr (*argv, '/')) == CPNULL)
               ? *argv : (MyName + 1);

       while ((option = getopt (argc, argv, "f:r:s:x:pRdli")) != EOF) {
               switch (option) {
                   case 'f':
                       From = optarg;
                       break;

                   case 's':
                       QiHost = optarg;
                       break;

                   case 'x':
                       Service = optarg;
                       break;

                   case 'R':
#ifdef REPLYTO
                       /* Re-write outgoing address with Reply-To: field */
                       ReplyTo++;
#endif /* REPLYTO */
                       break;

                   case 'r':
                       From = optarg;
                       break;

                   case 'p':
                       PostmasterCC++;
                       break;

                   case 'l':
                       Log++;
                       break;

                   case 'd':
                       Debug++;
                       QiDebug = Debug - 1;
                       Log = 0;
                       break;

                   case 'i':
                   default:
                       PrtUsage ();
                       finis ();
                       break;
               }
       }
       argc -= optind;                 /* skip options */
       argv += optind;

       /* Fire up logging, or not, as the flags may be */
       if (Log)
#ifdef LOG_MAIL
# ifndef SYSLOG
#  define       SYSLOG          LOG_MAIL
# endif
               openlog(MyName, LOG_PID, SYSLOG);
#else
               openlog(MyName, LOG_PID);
#endif

       if (Log && From)
               syslog (LOG_DEBUG, "From %s", From);

       /* fetch our host name, some use will be found for it.... */
       if (gethostname (HostNameBuf, 100-1) != 0)
               DANGER_WILL_ROBINSON("gethostname")

       /* Is the qi server open for business? */
       if (!QiHost)
               QiHost = QI_HOST;
       if ((i = OpenQi(QiHost, &ToQi, &FromQi)) < 0) {
#ifdef QI_ALT
               i = OpenQi(QI_ALT, &ToQi, &FromQi);
#endif /* QI_ALT */
       }
       if (i < 0) {
               DANGER_WILL_ROBINSON("No qi servers available");
       }
       else {
               /* Nail down AliasVal and EmailVal */
               QIF *QFp;

               for (QFp = QiFields; QFp < (QiFields + QiHighKey + 1); QFp++) {
                       if (QFp->value == NULL)
                               continue;
                       if (equal(QFp->value, "alias")) {
                               AliasVal = QFp - QiFields;
                               QFp->returnOK++;
                       }
                       else if (equal(QFp->value, "name"))
                               QFp->returnOK++;
                       else if (equal(QFp->value, "curriculum"))
                               QFp->returnOK++;
                       else if (equal(QFp->value, "phone"))
                               QFp->returnOK++;
                       else if (equal(QFp->value, "department"))
                               QFp->returnOK++;
                       else if (equal(QFp->value, "title"))
                               QFp->returnOK++;
                       else if (equal(QFp->value, "left_uiuc"))
                               QFp->returnOK++;
                       else if (equal(QFp->value, "email"))
                               EmailVal = QFp - QiFields;
               }
       }

       /* Open the temp file, copy the message into it */
       Msg = OpenTemp (TmpFile);
       while ((i = fread (Buf, sizeof (char), MAXSTR, stdin)) != 0)
               if (fwrite (Buf, sizeof (char), i, Msg) != i)
                       DANGER_WILL_ROBINSON("Msg copy")
       if (fflush(Msg) < 0)
               DANGER_WILL_ROBINSON("Msg fflush")

       /*
        * Remaining arguments are addresses.  If From == CHNULL,
        * then submission was done locally and return address has
        * to be on the From: line.
        */
       if (From == CPNULL || (From != CPNULL && From == CHNULL))
               FindFrom (Msg);

#ifdef REPLYTO
       if (ReplyTo) {

               /*
                * Check with QI to see if this person has a email entry.
                * If so add the Resent-From, Reply-To, and Comment fields.
                * Then invoke ReMail with xyzzy appended to the From address
                * so that sendmail won't send it back to us.  If a
                * Reply-To: field is already present, handle as though no
                * email field was found.
                */

               /*
                * Allocate NewAddress structs for from address, to addresses,
                * plus 1 for terminal null.
                */
               New = (NADD *) Malloc ((unsigned) ((argc+2) * sizeof (NADD)));
               (New + argc + 1)->original = CPNULL;
               NewP = New;
               RevQuery (NewP);
               assert (NewP->new != CPNULL);

               /* If a single alias was found, append the domain */
               if (abs (NewP->code) == LR_OK) {
                       NewP->new =
                           Realloc (NewP->new, (unsigned) (strlen (NewP->new)
                                                       + strlen (DOMAIN) + 2));
                       (void) strcat (NewP->new, "@");
                       (void) strcat (NewP->new, DOMAIN);
               }

               /* Add To: addresses to NewP array */
               NewP++;
               while (argc > 0) {
                       NewP->original = *argv;
                       NewP->new = CPNULL;
                       NewP++; argv++; argc--;
               }

               /* ReMail will add the new headers and call sendmail */
               ReMail (New, Msg, envp);

               /* We done good. */
               ExitStat = EX_OK;
               finis ();
       }
#endif /* REPLYTO */

       /*
        * If not a ReplyTo ...
        * Allocate NewAddress structs for addresses (or just one if this
        * is a service forward).
        */
       i = (Service == CPNULL) ? argc : 1;
       New = (NADD *) Malloc ((unsigned) ((i+1) * sizeof (NADD)));
       (New + i)->original = CPNULL;
       NewP = New;

       if (Service != CPNULL) {
               NewP->original = Service;
               NewP->new = CPNULL;
               Query (NewP);
               assert (NewP->new != CPNULL && NewP->field != -1);
               if (Debug)
                       printf ("code %d, (%s) %s --> %s\n",
                           NewP->code, QiFields[NewP->field].value,
                           NewP->original, NewP->new);
               if (Log)
                       syslog (LOG_INFO, "(%s) %s --> %s",
                           QiFields[NewP->field].value, NewP->original, NewP->new);
       }
       else
               /* Loop on addresses in argv building up translation table */
               while (argc > 0) {
                       NewP->original = *argv;
                       NewP->new = CPNULL;
                       Query (NewP);
                       assert (NewP->new != CPNULL && NewP->field != -1);
                       if (Debug)
                               printf ("code %d, (%s) %s --> %s\n",
                                   NewP->code, QiFields[NewP->field].value,
                                   NewP->original, NewP->new);
                       if (Log)
                               syslog (LOG_INFO, "(%s) %s --> %s",
                                   QiFields[NewP->field].value, NewP->original,
                                   NewP->new);
                       NewP++; argv++; argc--;
               }

       /*
        * Now re-invoke sendmail with the translated addresses.
        * Make one pass for collecting error returns into one message.
        */
       for (NewP = New; NewP->original != CPNULL; NewP++)
               if (abs (NewP->code) != LR_OK) {
                       ErrorReturn (NewP, Msg, envp);
                       break;
               }

       /* Any good addresses? */
       for (NewP = New; NewP->original != CPNULL; NewP++)
               if (abs (NewP->code) == LR_OK) {
                       ReMail (NewP, Msg, envp);
                       break;
               }

       /* exit */
       ExitStat = EX_OK;
       finis ();
}
/*
**  ErrorReturn -- Create and send informative mail messages
**
**      The envelope from address should be set to null as per RFC-821
**      in regard to notification messages (Section 3.6).
**
**      Parameters:
**              Addr -- pointer to NewAddress structure with addresses
**                      and messages
**              Omsg -- stream pointer to original message
**              envp -- environment pointer for fork/execve
**
**      Returns:
**              Nothing
**
**      Side Effects:
**              None
*/

char    *ap[] = { "-sendmail", "-oi", "-f", "MAILER-DAEMON", "-t", 0};

void
ErrorReturn (Addr, Omsg, envp)
       NADD    *Addr;
       FILE    *Omsg;
       char    *envp[];
{
               int     i;                      /* Good ol' i */
               char    Buf[MAXSTR];            /* Temp for copying msg test */
               FILE    *Emsg;                  /* For creating the error msg */
               int     pid;                    /* For fork() */
               int     flags = 0;              /* Controls printing of msgs */
               int     SubCode;                /* Printing control */
               NADD    *AddrP;                 /* Loop variable */
               QIR     *QRp;                   /* Another loop variable */
       extern  char    *ap[];

       /* Open the error file */
       Emsg = OpenTemp (ErrorFile);

       /* Insert the headers */
       if (fprintf (Emsg, "To: %s\n", From) < 0)
               DANGER_WILL_ROBINSON("ErrorReturn: To")
       if (PostmasterCC)
               if (fprintf (Emsg, "Cc: Postmaster\n") < 0)
                       DANGER_WILL_ROBINSON("ErrorReturn: Cc")
       if (fprintf (Emsg, "Subject: Returned mail - nameserver error report\n\n") < 0)
               DANGER_WILL_ROBINSON("ErrorReturn: Subject")
       if (fprintf (Emsg, " --------Message not delivered to the following:\n\n") < 0)
               DANGER_WILL_ROBINSON("ErrorReturn: Message")
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) != LR_OK)
                       if (fprintf (Emsg, " %15s    %s\n", AddrP->original, AddrP->new) < 0)
                               DANGER_WILL_ROBINSON("ErrorReturn: addr")
       if (fprintf (Emsg, "\n --------Error Detail (phquery V%s):\n\n", VERSION) < 0)
               DANGER_WILL_ROBINSON("ErrorReturn: Detail")

       /* Loop again to insert messages */
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) == LR_NOMATCH) {
                       if (! (flags & NO_MATCH_MSG)) {
                               PrintMsg (Emsg, NoMatchMsg);
                               flags |= NO_MATCH_MSG;
                               break;
                       }
               }
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) == LR_ERROR) {
                       if (! (flags & HARD_MSG)) {
                               PrintMsg (Emsg, HardMsg);
                               flags |= HARD_MSG;
                       }
                       for (QRp = AddrP->QIalt; QRp->code < 0; QRp++)
                               if (fprintf (Emsg, " %d: %s\n", QRp->code, QRp->message) < 0)
                                       DANGER_WILL_ROBINSON("ErrorReturn: hard")
                       if (putc ('\n', Emsg) < 0)
                               DANGER_WILL_ROBINSON("ErrorReturn: hard putc")
               }
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) == LR_ABSENT) {
                       if (! (flags & ABSENT_MSG)) {
                               PrintMsg (Emsg, AbsentMsg);
                               flags |= ABSENT_MSG;
                       }
                       for (QRp = AddrP->QIalt; QRp->code < 0; QRp++)
                               if (abs(QRp->code) == LR_OK && QRp->field > 0 &&
                                   QiFields[QRp->field].returnOK)
                                       if (fprintf (Emsg, " %s: %s\n", QiFields[QRp->field].value, QRp->message) < 0)
                                               DANGER_WILL_ROBINSON("ErrorReturn: absent")
                       if (putc ('\n', Emsg) < 0)
                               DANGER_WILL_ROBINSON("ErrorReturn: absent putc")
               }
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) == LR_TOOMANY) {
                       if (! (flags & TOO_MANY_MSG)) {
                               PrintMsg (Emsg, TooManyMsg);
                               flags |= TOO_MANY_MSG;
                               break;
                       }
               }
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) == LR_AMBIGUOUS) {
                       if (! (flags & MULTI_MSG)) {
                               PrintMsg (Emsg, MultiMsg);
                               flags |= MULTI_MSG;
                       }
                       for (QRp = AddrP->QIalt, SubCode = QRp->subcode;
                           QRp->code < 0; QRp++) {
                               if (QRp->subcode != SubCode) {
                                       SubCode = QRp->subcode;
                                       if (putc ('\n', Emsg) < 0)
                                               DANGER_WILL_ROBINSON("ErrorReturn: multi putc")
                               }
                               if (abs(QRp->code) == LR_OK && QRp->field > 0 &&
                                   QiFields[QRp->field].returnOK)
                                       if (fprintf (Emsg, " %s: %s\n", QiFields[QRp->field].value, QRp->message) < 0)
                                               DANGER_WILL_ROBINSON("ErrorReturn: multi")
                       }
                       if (putc ('\n', Emsg) < 0)
                               DANGER_WILL_ROBINSON("ErrorReturn: multi putc2")
               }
       if (fprintf (Emsg, "\n --------Unsent Message below:\n\n") < 0)
               DANGER_WILL_ROBINSON("ErrorReturn: unsent below")
       rewind (Omsg);
       while ((i = fread (Buf, sizeof (char), MAXSTR, Omsg)) != 0) {
               if (fwrite (Buf, sizeof (char), i, Emsg) != i)
                       DANGER_WILL_ROBINSON("ErrorReturn: Emsg copy")
       }
       if (fprintf (Emsg, "\n --------End of Unsent Message\n") < 0)
               DANGER_WILL_ROBINSON("ErrorReturn: unsent end")
       (void) fflush (Emsg);
       (void) fclose (Emsg);
       if (freopen (ErrorFile, "r", stdin) == FILE_NULL)
               DANGER_WILL_ROBINSON("ErrorReturn: freopen")

       /* Zap file so it disappears automagically */
       if (! Debug)
               (void) unlink (ErrorFile);

       /*
        * fork, then execve sendmail for delivery
        */

       pid = 0;
       if (! Debug && (pid = fork ()) == -1)
               DANGER_WILL_ROBINSON("ErrorReturn: fork")
       if (pid) {
               (void) wait(0);
               return;
       }
       else if (! Debug)
               execve (SENDMAIL, ap, envp);
}
/*
**  FindFrom -- Find From: address in message headers
**
**      Parameters:
**              MsgFile -- stream pointer to message
**
**      Returns:
**              Nothing
**
**      Side Effects:
**              Global From pointer is adjusted to point at either a
**              malloc'ed area containing the address, or to the
**              constant string "Postmaster" if none is found.
*/

void
FindFrom (MsgFile)
       FILE    *MsgFile;
{
       char            *p1, *p2;
       extern char     *From;
       char            Buf[MAXSTR];

       rewind (MsgFile);
       while (fgets (Buf, MAXSTR, MsgFile) != CPNULL && *Buf != '\n') {
               if (strncasecmp (Buf, "From:", 5))
                       continue;
               else {
                       if ((p1 = strchr (Buf, '<')) != CPNULL) {
                               p1++;
                               if ((p2 = strchr (Buf, '>')) != CPNULL) {
                                       From = Malloc ((unsigned) ((p2-p1)+1));
                                       (void) strncpy (From, p1, (p2-p1));
                               }
                               else {
                                       if (Debug)
                                               fprintf (stderr, "Unbalanced <> in From: address\n");
                                       if (Log)
                                               syslog (LOG_ERR, "Unbalanced <> in From: address");
                                       From = "Postmaster";
                               }
                       }
                       else {
                               /*
                                * Mail from local users may not have the <>
                                * yet.  See what's there anyway.
                                */
                               p1 = &Buf[5];
                               while (*p1 && isspace(*p1))
                                       p1++;
                               p2 = Buf + strlen(Buf);
                               if (p2 > p1) {
                                       From = Malloc ((unsigned) ((p2-p1)+1));
                                       (void) strncpy (From, p1, (p2-p1));
                               }
                       }
                       break;
               }
       }
       if (From == CPNULL || strlen(From) == 0) {
               if (Debug)
                       fprintf (stderr, "No From: address in message\n");
               if (Log)
                       syslog (LOG_ERR, "No From: address in message");
               From = "Postmaster";
       }
       if (Log)
               syslog (LOG_DEBUG, "From %s", From);
}
/*
**  ReMail -- Forward message to recipients after adding phquery headers
**
**      Parameters:
**              Addr -- pointer to NewAddress structure with addresses
**                      and messages
**              Omsg -- stream pointer to original message
**              envp -- environment pointer for fork/execve
**
**      Returns:
**              Nothing
**
**      Side Effects:
**              None
*/

void
ReMail (Addr, Omsg, envp)
       NADD    *Addr;
       FILE    *Omsg;
       char    *envp[];
{
               int     i = 10;
               char    Buf[MAXSTR];
               NADD    *AddrP;
               FILE    *Nmsg;
               int     pid = 0;
               char    **nap, **np, nFrom[100];
       extern  char    *From, HostNameBuf[];

       /* Open the rewrite file */
       Nmsg = OpenTemp (NewFile);

       /* size the argument array */
#ifdef REPLYTO
       if (ReplyTo)
               for (AddrP = Addr, AddrP++; AddrP->original != CPNULL; AddrP++)
                       i++;
       else
#endif /* REPLYTO */
       for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
               if (abs (AddrP->code) == LR_OK)
                       i++;
       np = nap = (char **) Malloc ((unsigned)(i * sizeof (char *)));

       /* Fill out the first portion of the sendmail argument vector */
       *np++ = "-sendmail";
       *np++ = "-oi";
       *np++ = "-f";
#ifdef REPLYTO
       if (ReplyTo) {
               /*
                * Tack on .xyzzy to the From address so sendmail will know
                * it's been here.
                */
               (void) strcpy (nFrom, From);
               (void) strcat (nFrom, ".xyzzy");
               *np++ = nFrom;
       }
       else
#endif /* REPLYTO */
       {
               *np++ = From;
               for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
                       if (abs (AddrP->code) == LR_OK)
                               *np++ = AddrP->new;
       }

       /* Read and copy the header block, adding X-PH-To: or X-PH: header */
       rewind (Omsg);
       while (fgets (Buf, MAXSTR, Omsg) != CPNULL && *Buf != '\n') {
               if ((nequal (Buf, "To:", 3) || nequal (Buf, "Cc:", 3)
                   || nequal (Buf, "From:", 5)) && pid == 0) {
                       int     LineLength;

#ifdef REPLYTO
                       if (ReplyTo) {

                               /* Add the Reply-To: fields */
                               AddrP = Addr;
                               if (fprintf (Nmsg, "Comment: Reply-To: added by phquery (V%s)\n", VERSION) < 0)
                                       DANGER_WILL_ROBINSON("ReMail: comment")
                               if (fprintf (Nmsg, "Resent-From: postmaster@%s\n", HostNameBuf) < 0)
                                       DANGER_WILL_ROBINSON("ReMail: resent")
                               if (fprintf (Nmsg, "Reply-To: %s\n", AddrP->new) < 0)
                                       DANGER_WILL_ROBINSON("ReMail: reply")
                               for (AddrP++; AddrP->original != CPNULL; AddrP++)
                                       *np++ = AddrP->original;
                               pid++;
                       }
                       else
#endif /* REPLYTO */
                       {

                               /* Write the PH header and add to argv */
#ifdef  EXPAND_TO
                               if (fprintf (Nmsg, "X-PH(%s)-To:", VERSION) < 0)
                                       DANGER_WILL_ROBINSON("ReMail: x-pht")
                               LineLength = 8;
                               for (AddrP = Addr; AddrP->original != CPNULL; AddrP++)
                                       if (abs (AddrP->code) == LR_OK) {
                                               if ((LineLength + strlen (AddrP->new)) > 75) {
                                                       if (fprintf (Nmsg, "\n\t") < 0)
                                                               DANGER_WILL_ROBINSON("ReMail: \\n\\t")
                                                       LineLength = 8;
                                               }
                                               if (fprintf (Nmsg, " %s", AddrP->new) < 0)
                                                       DANGER_WILL_ROBINSON("ReMail: x-pht2")
                                       }
                               if (putc('\n', Nmsg) < 0)
                                       DANGER_WILL_ROBINSON("ReMail: putc")
#else /* ! EXPAND_TO */
                               if (fprintf (Nmsg, "X-PH: V%s@%s\n", VERSION, HostNameBuf) < 0)
                                       DANGER_WILL_ROBINSON("ReMail: x-phv")
#endif /* EXPAND_TO */
                               pid++;
                       }
               }
               if (fputs (Buf, Nmsg) < 0)
                       DANGER_WILL_ROBINSON("ReMail: fputs")
       }
       if (fputs (Buf, Nmsg) < 0)
               DANGER_WILL_ROBINSON("ReMail: fputs2")
       *np = CPNULL;

       if (Debug) {
               printf ("Final send vector:");
               for (np = nap; *np != CPNULL; np++)
                       printf (" %s", *np);
               (void) putchar ('\n');
       }

       /* Copy the remainder of the message */
       while ((i = fread (Buf, sizeof (char), MAXSTR, Omsg)) != 0)
               if (fwrite (Buf, sizeof (char), i, Nmsg) != i)
                       DANGER_WILL_ROBINSON("ReMail: nmsg copy")

       /* Re-arrange the stream pointers and invoke sendmail */
       if (fflush (Nmsg) < 0)
               DANGER_WILL_ROBINSON("ReMail: fflush")
       (void) fclose (Nmsg);
       if (freopen (NewFile, "r", stdin) == FILE_NULL)
               DANGER_WILL_ROBINSON("ReMail: NewFile freopen")

       /* Zap file so it disappears automagically */
       if (! Debug)
               (void) unlink (NewFile);

       /*
        * fork, then execve sendmail for delivery
        */

       pid = 0;
       if (! Debug && (pid = fork ()) == -1)
               DANGER_WILL_ROBINSON("ReMail: fork")
       if (pid) {
               (void) wait(0);
               return;
       }
       else if (! Debug)
               execve (SENDMAIL, nap, envp);
}
/*
**  CodeString -- Return text string corresponding to supplied reply code
**
**      Parameters:
**              code -- reply value
**
**      Returns:
**              char pointer to text string or NULL pointer if no matching
**              key is located.
**
**      Side Effects:
**              None
*/

char *
CodeString (code)
       int     code;
{
               struct QiReplyCode      *Cpnt;
       extern  struct QiReplyCode      QiCodes[];

       for (Cpnt = QiCodes; Cpnt->key != -1; Cpnt++)
               if (Cpnt->key == abs (code))
                       return (Cpnt->value);
       return (CPNULL);
}
/*
**  OpenTemp -- Create and open a temporary file
**
**      For the supplied file name, create, open, and chmod the file
**
**      Parameters:
**              Name -- pathname of file to create in mkstemp format
**
**      Returns:
**              Stream descriptor of resulting file, or NULL if error
**
**      Side Effects:
**              mkstemp modifies calling argument
*/

FILE *
OpenTemp (Name)
       const char *Name;
{
       int     fd;
       FILE    *Stream;

       if ((fd = mkstemp ((char *)Name)) == -1)
               DANGER_WILL_ROBINSON("OpenTemp: mkstemp")

       /* Protect it */
       if (fchmod (fd, S_IREAD|S_IWRITE) == -1)
               DANGER_WILL_ROBINSON("OpenTemp: fchmod")

       /* Make fd a stream */
       if ((Stream = fdopen (fd, "r+")) == FILE_NULL)
               DANGER_WILL_ROBINSON("OpenTemp: fdopen")
       return (Stream);
}
/*
**  Query -- Create queries to send to the CSnet central server
**
**      Using the alias, call-sign, and full name fields, as known by the
**      CSnet central name server Query Interpreter, Query creates variants
**      of the supplied name (New->original) if a straight alias lookup fails.
**      For each variant, SendQuery() is called until either one succeeds or
**      all variants are exhausted.
**
**      Parameters:
**              New -- pointer to NewAddress struct
**
**      Returns:
**              None
**
**      Side Effects:
**              Modifies contents under New pointer.
*/

void
Query(New)
       NADD    *New;
{
       char    scratch[MAXSTR];        /* copy of FullName w.o. punct */
       char    *sp;                    /* work ptrs for scratch */
#ifdef  WILDNAMES
       char    *sp2;                   /* work ptrs for scratch */
#endif /* WILDNAMES */
       char    **Lpnt = TryList;       /* Loop pointer for TryList */
       int     NoMore = -1;            /* set if all name variants done */

       /* Unquote the address if needed */
       while (*New->original == '"' &&
              New->original[strlen(New->original)-1] == '"') {
               New->original[strlen(New->original)-1] = CHNULL;
               New->original++;
       }

       /*
        * Try the query as an alias lookup first, then as a full name lookup.
        */

       do {
               /*
                * Convert punctuation/separators in scratch to space
                * characters one at a time if testing for name.  If
                * WILDNAMES is #define'd, a wildcard char '*' will be
                * appended after each single character name, e.g. p-pomes
                * is tried as p* pomes.  This has risks as follows:  assume
                * Duncan Lawrie sets his alias to "lawrie".  A query for
                * d-lawrie will fail as a alias lookup but succeed as a
                * name lookup when written as "d* lawrie".  This works until
                * Joe Student sets his alias to "d-lawrie".  Whoops.
                * Still in a non-hostile environment, this function may be
                * more useful than dangerous.
                */
               if ((New->field = FieldValue(*Lpnt)) ==  -1)
               {
                       if (Debug)
                               printf ("%s not a server field\n", *Lpnt);
                       if (Log)
                               syslog (LOG_ERR, "%s not a server field",*Lpnt);
                       Lpnt++;
                       continue;
               }
               if (equal (*Lpnt, "name")) {

                       /* Try as is first time for hyphenated names */
                       if (NoMore == -1) {
                               (void) strcpy (scratch, New->original);
                               if (SendQuery (New, *Lpnt, scratch))
                                       return;
                               NoMore = 0;
                       }
                       else {
                               char stemp[MAXSTR], *st = stemp;

                               for (sp = scratch; *sp != CHNULL; ) {

                                       /* copy until non-space punct char */
                                       if (!ispunct (*sp) || *sp == ' ' || *sp == '*') {
#ifdef  WILDNAMES
                                               sp2 = sp;
#endif /* WILDNAMES */
                                               *st++ = *sp++;
                                               if (*sp == CHNULL)
                                                       NoMore++;
                                               continue;
                                       }

#ifdef  WILDNAMES
                                       /* if one non-punct char, append * */
                                       if ((sp - sp2) == 1)
                                               *st++ = '*';
#endif /* WILDNAMES */
                                       *st++ = ' ';
                                       sp++;
                                       break;
                               }
                               while (*sp != CHNULL)
                                       *st++ = *sp++;
                               *st = CHNULL;
                               (void) strcpy (scratch, stemp);
                               if (SendQuery (New, *Lpnt, scratch))
                                       return;
                               if (NoMore > 0)
                                       Lpnt++;
                               continue;
                       }
               }

               /*
                * Convert punctuation/separators in scratch to hyphen
                * characters if testing for alias.
                */
               else if (equal (*Lpnt, "alias")) {
                       (void) strcpy (scratch, New->original);
                       for (sp = scratch; *sp != CHNULL; sp++)
                               if (ispunct(*sp))
                                       *sp = '-';
                       if (SendQuery (New, *Lpnt, scratch))
                               return;
                       Lpnt++;
               }
               else {
                       (void) strcpy (scratch, New->original);
                       if (SendQuery (New, *Lpnt, scratch))
                               return;
                       Lpnt++;
               }
       } while (*Lpnt != CPNULL);
}
/*
**  SendQuery -- Send queries to the local CSnet central name server
**
**      Takes a field type (alias, call-sign, full name, etc), as known by
**      the CSnet central name server Query Interpreter, and looks up the
**      corresponding email address "usercode@host".  Cases where the
**      alias/name aren't found, are ambiguous, or lack an email address
**      return a message instead of the address.  Additional information is
**      returned as an array of QIR records pointed to by New->QIalt.
**
**      Parameters:
**              New -- pointer to NewAddress struct
**              Field -- type of field (name, alias, etc) for Value
**              Value -- name to lookup
**
**      Returns:
**              1 if a match(es) is found including too many
**              0 otherwise
**
**      Side Effects:
**              Modifies contents under New pointer.
*/

SendQuery(New, Field, Value)
       NADD    *New;
       const char *Field, *Value;
{
       QIR     *EmailQ, *QRp;  /* For handling ReadQi() responses */
       int     i;              /* good ol' i */

       /* Make a query out of the arguments */
       if (Debug)
               printf ("querying for %s \"%s\"\n", Field, Value);
       if (Log)
               syslog (LOG_DEBUG, "querying for %s \"%s\"", Field, Value);
       if (fprintf (ToQi, "query %s=%s return all\n", Field, Value) < 0)
               DANGER_WILL_ROBINSON("SendQuery: qi query")
       if (fflush (ToQi) < 0)
               DANGER_WILL_ROBINSON("SendQuery: qi fflush")

       /*
        * Grab the responses and let the fun begin.
        * The possibilities are:
        *
        * 102:There were N matches to your query
        * -200:1:         alias: Paul-Pomes
        * -200:1:          name: pomes paul b
        * -200:1:      callsign: See Figure 1
        * -508:1:    curriculum: Not present in entry.
        * -200:1:    department: Computing Services Office
        * -200:1:         email: [email protected]
        * 200:Ok.
        *
        * 501:No matches to your query.
        *
        * 502:Too many matches to request.
        *
        * -515:no non-null key field in query.
        * -515:Initial metas may be used as qualifiers only.
        * 500:Did not understand query.
        */
       EmailQ = ReadQi (FromQi, NULL);

       /*
        * If we read a temporary error, be a nice program and defer.
        */
       if (EmailQ->code > 399 && EmailQ->code < 500)
               finis ();

       /*
        * No matches at all?  Too many?  Note that single line errors
        * will have code > 0.
        */
       if (EmailQ->code > 0) {
               New->new = CodeString (EmailQ->code);
               New->code = EmailQ->code;
               New->QIalt = QIR_NULL;
               FreeQIR (EmailQ);
               if (New->code == LR_TOOMANY)
                       return (1);
               return (0);
       }

       /* anything else must be multi-line */
       assert (EmailQ->code < 0);

       /* Are there multiple responses (subcode > 1)? */
       for (QRp = EmailQ; QRp->code < 0; QRp++)
               if (QRp->subcode > 1) {
                       New->code = LR_AMBIGUOUS;
                       New->new = CodeString (LR_AMBIGUOUS);
                       New->QIalt = EmailQ;
                       return (1);
               }

       /* a multi-line error? */
       New->QIalt = EmailQ;
       if (abs (QRp->code) >= 500) {
               for (QRp = EmailQ; QRp->code < 0; QRp++)
                       ;
               New->code = QRp->code;
               New->new = CodeString (QRp->code);
               return (1);
       }

       /* If one person, handle as single match alias */
       QRp = PickField (EmailQ, EmailVal);
       if (QRp == QIR_NULL) {
               New->code = LR_ABSENT;
               New->new = CodeString (LR_ABSENT);
               return (1);
       }
       if (QRp->field != EmailVal) {
               if (Log)
                       syslog (LOG_ERR, "Email field for %s (%s) in ph/qi database is present but null",
                               Value, Field);
               if (Debug)
                       fprintf (stderr, "Email field for %s (%s) in ph/qi database is present but null\n",
                               Value, Field);
               New->code = LR_ABSENT;
               New->new = CodeString (LR_ABSENT);
               return (1);
       }
       New->code = abs (QRp->code);
       switch (abs (QRp->code)) {
           case LR_ABSENT:
               New->new = CodeString (QRp->code);
               return (1);

           case LR_OK:
               New->new = QRp->message;

               /* chop at first address */
               {
                       char *cp = New->new;

                       /* skip any white space */
                       while (*cp != CHNULL && isspace(*cp))
                               cp++;

                       while (*cp != CHNULL) {
                               if (isspace(*cp) || *cp == ',') {
                                       *cp = CHNULL;
                                       break;
                               }
                               cp++;
                       }
               }
               return (1);

           default:
               if (Debug)
                       fprintf (stderr, "unexpected code %d\n",
                           QRp->code);
               if (Log)
                       syslog (LOG_ERR, "Query: %s: unexpected code %d", Field, QRp->code);
               finis ();
       }
       FreeQIR (EmailQ);
       return (0);
}
/*
**  RevQuery -- Reverse query, email to ph alias
**
**      Takes a email address as known by the CSnet central name server
**      Query Interpreter, and looks up the corresponding alias. Cases
**      where the email address matches multiple aliases return the
**      original address.  In addition the global variable ReplyTo is
**      set to -1.
**
**      Parameters:
**              New -- pointer to NewAddress struct
**
**      Returns:
**              None
**
**      Side Effects:
**              Modifies contents under New pointer.
**              ReplyTo set to -1 if QI returns multiple aliases or
**              no match.
*/

void
RevQuery(New)
       NADD    *New;
{
               int     i;
               QIR     *AliasQ, *QRp;
       extern  char    *From, HostNameBuf[];
       extern  FILE    *ToQi, *FromQi;

       /*
        * We have to have a from address here.  If it doesn't have
        * a fully qualified form, convert it to name@domain by
        * appending our Fully Qualified Domain Name.  FQDN, the
        * litany of the new Internet Age.
        */

       assert (From != CPNULL);
       if (strchr (From, '@') == CPNULL) {
               char    *nFrom;

               /*
                * We can't Realloc(From) since it may point to
                * an area on the stack.
                */
               nFrom = Malloc ((unsigned)(strlen (From) + 1));
               (void) strcpy (nFrom, From);
               From = Realloc (nFrom, (unsigned)(strlen(nFrom) +
                                      strlen(HostNameBuf) + 5));
               (void) strcat (From, "@");
               (void) strcat (From, HostNameBuf);
       }
       New->original = From;

       /* Send the query
        * I'd check for a -1 here, but am unsure how network errors really
        * are manifested.
        */
       if (Debug)
               printf ("querying alias corresponding to \"%s\"\n", From);
       if (Log)
               syslog (LOG_DEBUG, "querying alias for \"%s\"", From);
       if (fprintf (ToQi, "query email=%s return alias \n", From) < 0)
               DANGER_WILL_ROBINSON("RevQuery: qi query")
       if (fflush (ToQi) < 0)
               DANGER_WILL_ROBINSON("RevQuery: qi fflush")

       /*
        * Grab the responses and let the fun begin.
        * The possibilities are:
        *
        * 102:There was N matches to your query.
        *
        * -200:1:         alias: rrv
        * 200:Ok.
        *
        * -200:1:         alias: Paul-Pomes
        * -200:2:         alias: PostMaster
        * 200:Ok.
        *
        * 501:No matches to your query.
        *
        * 502:Too many matches to request.
        *
        * 5XX:Other error
        *
        * For anything other than the first case, set ReplyTo to -1 and
        * set New->new = New->original .
        */
       AliasQ = ReadQi (FromQi, NULL);

       /* Handle the 501, 502, 5XX codes */
       if (AliasQ->code > 0) {
#ifdef REPLYTO
               ReplyTo = -1;
#endif /* REPLYTO */
               New->new = New->original;
               FreeQIR (AliasQ);
               return;
       }

       /* Are there multiple responses (subcode > 1)? */
       for (QRp = AliasQ; QRp->code < 0; QRp++)
               if (QRp->subcode > 1) {
#ifdef REPLYTO
                       ReplyTo = -1;
#endif /* REPLYTO */
                       New->new = New->original;
                       FreeQIR (AliasQ);
                       return;
               }

       QRp = AliasQ;
       assert (abs (QRp->code) == LR_OK && QRp->field == AliasVal);
       New->code = abs (QRp->code);
       New->new = QRp->message;
       return;
}
/*
** Malloc -- malloc with error checking
**
**      Parameters:
**              size -- number of bytes to get
**
**      Returns:
**              (char *) of first char of block, or
**              finis() if any error
**
**      Side Effects:
**              none
*/

char *
Malloc (size)
       unsigned        size;                   /* Bytes to get */
{
               char    *cp;            /* Pointer to memory */

       if ((cp = (char *) malloc (size)) == CPNULL) {
               if (Debug) {
                       fprintf (stderr, "Malloc: %u bytes failed:", size);
                       perror("");
               }
               if (Log)
                       syslog (LOG_ERR, "Malloc: %u bytes failed: %m", size);
               finis ();
       }
       return (cp);
}
/*
** PrintMsg -- Print a message on the named stream
**
**      Parameters:
**              OutFile -- stream to print message to
**              Msg - array of char pointers that make up message,
**                    null terminated
**
**      Returns:
**              None
**
**      Side Effects:
**              none
*/

void
PrintMsg (OutFile, Msg)
       FILE    *OutFile;
       char    *Msg[];
{
       while (*Msg != CPNULL) {
               if (fprintf (OutFile, "%s\n", *Msg) < 0)
                       DANGER_WILL_ROBINSON("PrintMsg")
               Msg++;
       }
}
/*
** Realloc -- realloc with error checking
**
**      Parameters:
**              ptr -- pointer to existing data
**              size -- number of bytes to get
**
**      Returns:
**              (char *) of first char of block, or
**              finis() if any error
**
**      Side Effects:
**              none
*/

char *
Realloc (ptr, size)
       char            *ptr;
       unsigned        size;
{
               char    *cp;            /* pointer to memory */

       if ((cp = (char *) realloc (ptr, size)) == CPNULL) {
               if (Debug) {
                       fprintf (stderr, "Realloc: %u bytes failed:", size);
                       perror("");
               }
               if (Log)
                       syslog (LOG_ERR, "Realloc: %u bytes failed: %m", size);
               finis ();
       }
       return (cp);
}
/*
** PrtUsage -- Print how to use message
**
**      Print usage messages (char *usage[]) to stderr and exit nonzero.
**      Each message is followed by a newline.
**
**      Parameters:
**              none
**
**      Returns:
**              none
**
**      Side Effects:
**              none
*/

void
PrtUsage ()
{
       int     which = 0;              /* current line */

       fprintf (stderr, "%s: version %s\n", MyName, VERSION);
       while (usage[which] != CPNULL) {
               fprintf (stderr, usage[which++], MyName);
               (void) putc ('\n', stderr);
       }
       (void) fflush (stdout);
}
/*
**  finis -- Clean up and exit.
**
**      Parameters:
**              none
**
**      Returns:
**              never
**
**      Side Effects:
**              exits sendmail
*/

void
finis()
{
       extern  FILE    *ToQi, *FromQi;

       if (ToQi)
               CloseQi(ToQi, FromQi);

       /* clean up temp files */
       if (! Debug) {
               (void) unlink (TmpFile);
               (void) unlink (ErrorFile);
               (void) unlink (NewFile);
       }

       /* and exit */
       exit (ExitStat);
}