/*
** NETSCAPE COMMUNICATIONS CORPORATION 
** 
** Copyright (c) 1998 Netscape Communications Corporation. All Rights
** Reserved. 
** 
** Use of this Source Code is subject to the terms of the applicable license
** agreement from Netscape Communications Corporation. 
** 
** The copyright notice(s) in this Source Code does not indicate actual or
** intended publication of this Source Code. 
** 
** Modified 2003-02-24 by Aleksander Adamowski <olo@altkom.com.pl> - support
** for relay control based on sender e-mail address
**
**  Filename 
**  --------
**  antirelay.c
**
**  Description
**  -----------
**  antirelay sample plug-in
**  
**
*/



#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "protplug.h"

#define MAX_LINE_LENGTH 1024
#define CONFIG_FILE "/config/antirelay.conf"

/* response strings we send over SMTP */
#define NONLOCAL_RESPONSE_STRING \
"551 Delivery not allowed to non-local recipient\r\n"
#define NONLOCAL_ARESPONSE_STRING \
"530 Delivery not allowed to non-local recipient, try authenticating\r\n"
#define NOROUTING_RESPONSE_STRING \
"553 Delivery not allowed to routed addresses\r\n"

/* turn on or off debug logging based on compile time */
/* flag _DEBUG */
#ifdef _DEBUG
#define LOCAL_DEBUG
#endif


/* UNIX and NT have different routines for doing simple */
/* case insensitive string comparisions */
#ifdef XP_UNIX
#define STRCASECMP strcasecmp
#define STRNCASECMP strncasecmp
#else
#define STRCASECMP _stricmp
#define STRNCASECMP _strnicmp
#endif


/* structure used to store domain patterns for */
/* our submission and delivery lists */
typedef struct HostEntryStruct {
   char *pszPattern;
   struct HostEntryStruct *pNext;
} HostEntry;


/* sessions persistant data we want passed to our */
/* plugin each time it's called. */
struct ProtPlugStruct {
   int         iPassed;
   PPSession   *pMySession;
   char     *pszMyPeerAddress;
   char     *pszResp;
};


/* local function prototypes */
static int PatternMatch(const char *text, const char *p);
static char *ARStrdup(char *s);

/* pointer to the plugin system routines */
static ProtPlugSystem *gpMySys;

/* list of domains allowed to relay from */
static HostEntry *gpSubmissionDomains;

/* list of domains we handle delivery for */
static HostEntry *gpDeliveryDomains;

/* commonly used response strings from rejecting relays */
static char *gpNonLocalResponse = NULL;
static char *gpNonLocalAuthResponse = NULL;
static char *gpNoRoutingResponse = NULL;

/* global flag to turn on/off Debugging */
#ifdef LOCAL_DEBUG
static int giDebugMode = 1;
#else
static int giDebugMode = 0;
#endif


/* global flag to turn on/off DNS lookups */
static int giResolveHostnames = 1;

/* global flag to turn on/off using authinfo */
static int giUseAuthinfo = 1;

/* global flag to turn on/off stripping AUTH extensions */
static int giStripAuthCapability = 1;

/* global flag to turn on/off advertising of authinfo */
static int giAdvertiseAuthinfo = 0;

/* global flag to turn on/off allowal of routing */
static int giAllowRouting = 0;


/*
** Function
** --------
** int PPModule_Init(ProtPlugSystem *pSys);
**  
** Description
** -----------
** initialization entry point for a protocol plugin module library
**
** Parameters
** ----------
** ProtPlgSystem *pSys: structure with function pointers for thread
**                         synch functions, memory object, etc.  
**
** Returns
** -------
** 0 if successful, non-zero otherwise
**
** Remarks
** -------
** if Init() fails, Exit() will not by called
**
*/

__export int PPModule_Init(ProtPlugSystem *pSys) {

   FILE       *hConfigFile;
   char     *pConfigFilePath = NULL;
   char       *pszBuffer = NULL;
   char       *pszString;
   char     *p = NULL;
   HostEntry  *pNewEntry= NULL;
   HostEntry  *pThisEntry= NULL;

   /* set our global pointer to the system stuff */
   /* so plugin instances can find it later */

   gpMySys = pSys;

   /* announce ourselves in the server log */

   gpMySys->PPLogPrintf("Antirelay: installing plugin built " __DATE__ );

   /* initialize our config lists */

   gpSubmissionDomains = NULL;
   gpDeliveryDomains = NULL;


   /* initialize a buffer containing the string we will */
   /* use when denying relays. doing it once now will save */
   /* us time later on */

   if ((gpNonLocalResponse = (char*)ARStrdup(NONLOCAL_RESPONSE_STRING)) 
       == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to allocate memory");
      gpMySys->PPLogPrintf("Antirelay: plugin not installed");
      return -1;
   }

   if ((gpNonLocalAuthResponse = (char*)ARStrdup(NONLOCAL_ARESPONSE_STRING)) 
       == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to allocate memory");
      gpMySys->PPLogPrintf("Antirelay: plugin not installed");
      return -1;
   }

   if ((gpNoRoutingResponse = (char*)ARStrdup(NOROUTING_RESPONSE_STRING)) 
       == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to allocate memory");
      gpMySys->PPLogPrintf("Antirelay: plugin not installed");
      return -1;
   }




   if ((pConfigFilePath = 
        gpMySys->PPRealloc(pConfigFilePath, MAX_LINE_LENGTH)) == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to allocate %d bytes of memory",
                           MAX_LINE_LENGTH);
      gpMySys->PPLogPrintf("Antirelay: plugin not installed");
      return -1;
   }

   /* we need to know the server instance dir to find the config file */

   if ((pConfigFilePath = 
        gpMySys->PPGetIni("local.instancedir", NULL, pConfigFilePath, 
                          MAX_LINE_LENGTH - 24)) == NULL) {

      gpMySys->PPLogPrintf("Antirelay: could not find path to config file");
      gpMySys->PPLogPrintf("Antirelay: plugin not installed");
      return -1;
   }


   /* build up the path to the config file */

   strcat(pConfigFilePath, CONFIG_FILE);


   if ((hConfigFile = fopen(pConfigFilePath, "r")) == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to open config file %s (%d)", 
                           pConfigFilePath, errno);
      gpMySys->PPLogPrintf("Antirelay: antirelay plugin not installed");

      gpMySys->PPRealloc(pConfigFilePath,0);

      return -1;
   }

   /* allocate a buffer to read the config file in with */

   if ((pszBuffer = 
        (char*)(gpMySys->PPRealloc(pszBuffer, MAX_LINE_LENGTH))) == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to allocate %d bytes of memory",
                           MAX_LINE_LENGTH);
      gpMySys->PPLogPrintf("Antirelay: plugin not installed");
      return -1;
   }

   /* read throught he config file */

   while (fgets(pszBuffer, MAX_LINE_LENGTH, hConfigFile) != NULL) {

      /* trim the new line */

      if ((p = strchr(pszBuffer, '\n')) != NULL)
         *p = '\0';

      /* trim the carraige return */

      if ((p = strchr(pszBuffer, '\r')) != NULL)
         *p = '\0';

      if (STRNCASECMP(pszBuffer, "debugmode:", 10) == 0) {

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            giDebugMode = atoi(pszString+1);
            gpMySys->PPLogPrintf("Antirelay: debug mode %d", giDebugMode);
         }
      }

      /* if the line looks like "delivery:*.netscape.com" */

      if (STRNCASECMP(pszBuffer, "delivery:", 9) == 0) {

         /* add an entry to the delivery list */

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            /* allocate and fill in a new record */

            if ((pNewEntry = (HostEntry*)(gpMySys->PPRealloc(pNewEntry, 
                                                             sizeof(HostEntry)))) == NULL) {

               gpMySys->PPLogPrintf("Antirelay: unable to allocate %d ", 
                                    "bytes of memory", sizeof(HostEntry));
               gpMySys->PPLogPrintf("Antirelay: plugin not installed");

               gpMySys->PPRealloc(pszBuffer,0);
               fclose(hConfigFile);

               return -1;
            }

            pNewEntry->pNext = gpDeliveryDomains;
            pNewEntry->pszPattern = ARStrdup(++pszString);
            gpDeliveryDomains = pNewEntry;

            /* make sure we NULL the ptr or else Realloc */
            /* will stomp the buffer we just got */
            pNewEntry = NULL;
         }

         continue;
      }


      /* if the line looks like "submission:*.netscape.com" */

      if (STRNCASECMP(pszBuffer, "submission:", 11) == 0) {

         /* add an entry to the submission list */

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            /* allocate and fill in a new record */

            if ((pNewEntry = (HostEntry*)(gpMySys->PPRealloc(pNewEntry, 
                                                             sizeof(HostEntry)))) == NULL) {

               gpMySys->PPLogPrintf("Antirelay: unable to allocate %d ", 
                                    "bytes of memory", sizeof(HostEntry));
               gpMySys->PPLogPrintf("Antirelay: plugin not installed");

               gpMySys->PPRealloc(pszBuffer,0);
               fclose(hConfigFile);
               return -1;
            }

            pNewEntry->pNext = gpSubmissionDomains;
            pNewEntry->pszPattern = ARStrdup(++pszString);
            gpSubmissionDomains = pNewEntry;

            /* make sure we NULL the ptr or else Realloc */
            /* will stomp the buffer we just got */
            pNewEntry = NULL;
         }

         continue;
      }


      /* if the line looks like "resolvehostnames:0 */

      if (STRNCASECMP(pszBuffer, "resolvehostnames:", 16) == 0) {

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            if (*(++pszString) == '0') {

               giResolveHostnames = 0;
               gpMySys->PPLogPrintf("Antirelay: turning off DNS resolution");
            }
         }
      }

      /* if the line looks like "stripauthcapability:0 */

      if (STRNCASECMP(pszBuffer, "stripauthcapability:", 19) == 0) {

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            if (*(++pszString) == '0') {

               giStripAuthCapability = 0;
               gpMySys->PPLogPrintf("Antirelay: not stripping AUTH extensions for local hosts");
            }
         }
      }

      /* if the line looks like "advertiseauthinfo:0 */

      if (STRNCASECMP(pszBuffer, "advertiseauthinfo:", 18) == 0) {

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            if (*(++pszString) == '1') {

               giAdvertiseAuthinfo = 1;
               gpMySys->PPLogPrintf("Antirelay: not advertising authentication on failure");
            }
         }
      }

      if (STRNCASECMP(pszBuffer, "useauthinfo:", 12) == 0) {

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            if (*(++pszString) == '0') {

               giUseAuthinfo = 0;
               gpMySys->PPLogPrintf("Antirelay: not using authentication");
            }
         }
      }

      if (STRNCASECMP(pszBuffer, "allowrouting:", 13) == 0) {

         if ((pszString = (char*)strchr(pszBuffer, ':')) != NULL) {

            if (*(++pszString) == '1') {

               giAllowRouting = 1;
               gpMySys->PPLogPrintf("Antirelay: allowing routed addresses");
            }
         }
      }

      /* comment line or bad value, just ignore it */
   }

   gpMySys->PPRealloc(pszBuffer,0);
   fclose(hConfigFile);

   if (giDebugMode) {
      /* turn this on if you want the plugin to log it's config tables */
      /* when it starts up ... good for problem tracing */

      pThisEntry = gpSubmissionDomains;
      while (pThisEntry != NULL) {

         gpMySys->PPLogPrintf("Antirelay: submissions allowed from [%s]", 
                              pThisEntry->pszPattern);
         pThisEntry = pThisEntry->pNext;
      }

      pThisEntry = gpDeliveryDomains;
      while (pThisEntry != NULL) {

         gpMySys->PPLogPrintf("Antirelay: delivery allowed to [%s]", 
                              pThisEntry->pszPattern);
         pThisEntry = pThisEntry->pNext;
      }

   }

   /* make a note in the logs that we are live and running */

   gpMySys->PPLogPrintf("Antirelay: installation successful");

   /* return success */
   return 0;
}

/*
** Function
** --------
** void PPModule_Exit(void);
**  
** Description
** -----------
** for resource cleanup in a PPModule, called after all ProtPlugs have
**  been deleted, before the server exits
**
** Parameters
** ----------
** none
**
** Returns
** -------
** nothing
**
** Remarks
** -------
**
*/

__export void PPModule_Exit(void) {

   HostEntry *pCurrent;
   HostEntry *pNext;

   /* free out reject strings */

   gpMySys->PPRealloc(gpNonLocalResponse, 0);
   gpMySys->PPRealloc(gpNonLocalAuthResponse, 0);
   gpMySys->PPRealloc(gpNoRoutingResponse, 0);

   /* free up our submission domain list */

   pCurrent = gpSubmissionDomains;

   while (pCurrent != NULL) {

      pNext = pCurrent->pNext;
      gpMySys->PPRealloc(pCurrent->pszPattern,0);
      gpMySys->PPRealloc(pCurrent,0);
      pCurrent = pNext;
   }

   /* free up our delivery domain list */

   pCurrent = gpDeliveryDomains;

   while (pCurrent != NULL) {

      pNext = pCurrent->pNext;
      gpMySys->PPRealloc(pCurrent->pszPattern,0);
      gpMySys->PPRealloc(pCurrent,0);
      pCurrent = pNext;
   }

   return;
}


/*
** Function
** --------
** ProtPlug *PPModule_NewProtPlug(void);
**  
** Description
** -----------
** creates a new ProtPlug, the session analogue of a PPModule
**
** Parameters
** ----------
**
** Returns
** -------
** a new ProtPlug
**
** Remarks
** -------
**
*/

__export ProtPlug *PPModule_NewProtPlug(PPSession *ps) {
   HostEntry  *pThisEntry;
   ProtPlug   *pNewPlug = NULL;

   if (giDebugMode > 1) {
      gpMySys->PPLogPrintf("Antirelay: allocating new plugin");
   }

   /* allocate a new ProtPlug object */

   if ((pNewPlug = 
        (ProtPlug*)gpMySys->PPRealloc(pNewPlug, sizeof(ProtPlug))) == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to allocate %d bytes of memory",
                           sizeof(ProtPlug));
      gpMySys->PPLogPrintf("Antirelay: new plugin instance not created");
      return NULL;
   }

   pNewPlug->pMySession = ps;
   pNewPlug->iPassed = 0;
   pNewPlug->pszResp = NULL;

   /* if the user wants they can force the use ip-addrs */

   if (giResolveHostnames) {

      pNewPlug->pszMyPeerAddress = 
      (char*)PPSESSION_GETPROPERTY(ps,"PeerName",NULL);

      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: using peer hostname [%s]", 
                              pNewPlug->pszMyPeerAddress);
      }

   } else {

      pNewPlug->pszMyPeerAddress = 
      (char*)PPSESSION_GETPROPERTY(ps,"PeerIP",NULL);

      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: using ip-address [%s]", 
                              pNewPlug->pszMyPeerAddress);
      }

   }

   /* if we fail to find the peer address, there isn't much point */

   if (pNewPlug->pszMyPeerAddress == NULL) {

      gpMySys->PPLogPrintf("Antirelay: unable to discover peer address");
      gpMySys->PPLogPrintf("Antirelay: new plugin instance not created");

      gpMySys->PPRealloc(pNewPlug,0);
      return NULL;
   }


   /* if we dont have any domains, the default is "*" */
   /* so we match */

   if (gpSubmissionDomains != NULL) {

      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: comparing peer address %s", 
                              pNewPlug->pszMyPeerAddress);
      }

      /* search the submission list for a pattern that matches our */
      /* peer's address or hostname */

      pThisEntry = gpSubmissionDomains;

      while (pThisEntry != NULL) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay:  vs %s", pThisEntry->pszPattern);
         }

         if (PatternMatch((const char*)pNewPlug->pszMyPeerAddress, 
                          (const char*)pThisEntry->pszPattern)) {

            if (giDebugMode) {
               gpMySys->PPLogPrintf("Antirelay: allowing relay from [%s], matched [%s]",
			       pNewPlug->pszMyPeerAddress,
			       pThisEntry->pszPattern);
            }

            /* ok, lets mark them as trusted */

            pNewPlug->iPassed = 1;
            return pNewPlug;
         }

         pThisEntry = pThisEntry->pNext;
      }

   }

   return pNewPlug;
}

/*
  routed_address
*/

int routed_address(register char *s) {
   int c;

   while (*s != '%' && *s != '@' && *s != '\0') {
      ++s;
   }
   if (*s == '%') {
      /* its a routed address */
      return 1;
   }

   return 0; /* No Routing found */
}

/*
** Function
** --------
** int ProtPlug_ProcessLine(ProtPlug *ppp,char *pszLine,char **ppszResp);
**  
** Description
** -----------
** function called on a ProtPlug when a line has been received in the 
**  protocol
**
** Parameters
** ----------
** ProtPlug *ppp: the session pointer
** char *pszLine: the line to be processed, null terminated, 
**                  includes "\r\n"
** char *pszPrevResp: the response given by the previous ProtPlug
** char **ppszResp: (input/output)a pointer a pointer to the desired 
**                   response, must be null-terminated and include 
**                   "\r\n"
**
** Returns
** -------
** PP_SKIP       the command is not handled
** PP_CLOSE       to terminate the connection after sending the response
**
** Remarks
** -------
** The memory pointed to by pszLine and pszPrevResp is owned by the 
**  server and may not be modified or freed by the ProtPlug
**
** The memory pointed to by *ppszResp belongs to the ProtPlug and need only
**  be valid until the next call to ProcessLine or Delete, the server
**  will not modify or free it.
**
** NULL for pszLine is a request for a protocol banner
**
*/

__export int ProtPlug_ProcessLine(ProtPlug *ppp, char *pszLine, char **ppszResp) {

   char    *pszUser;
   char     *p;
   char     *q;
   char     *r;
   char     *t;
   char     *pAddress;
   char     *pStartOfString;
   HostEntry   *pThisEntry;
   int         iRv = PP_SKIP;
   int         iLength = 0;
   int         iThisLength = 0;


   if (giDebugMode > 1) {
      gpMySys->PPLogPrintf("Antirelay: processing line");
   }

   /* if pszLine is NULL, this is the banner */

   if (pszLine == NULL)
      return PP_SKIP;

   /* by OLO pon lut 24 15:33:51 CET 2003: */

   /* first see if this is a MAIL FROM line */

   if (((*pszLine == 'm') || (*pszLine == 'M')) && 
       (! STRNCASECMP(pszLine, "mail from", 9))) {

      /* B381967 Additional checks on pszLine */
      if ((t = strchr(pszLine, ':')) == NULL)
         return PP_SKIP;


      /* if we've already validated them as trusted */
      /* just return */

      if (ppp->iPassed == 1)
         return PP_SKIP;

      /* if we are using authentication as a criteria and */
      /* the client has authenticated, trust them */

      if ((giUseAuthinfo == 1) &&
          ((pszUser = 
            (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"AuthSender",NULL))
           != NULL) &&
          (*pszUser != '\0')) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay: authenticated user [%s], skipping", pszUser);
         }

         ppp->iPassed = 1;

         return PP_SKIP;
      }



      /* if we dont have any domains, the default is "*" */
      /* so we match */

      if (gpDeliveryDomains == NULL)
         return PP_SKIP;


      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: comparing senders to delivery domains");
      }

      /* we need to dup the line since the memory doesn't belong */
      /* to us and we'll need to muck with it below */

      /* B381967 Can no longer use fixed offset into buffer */
      if ((p = ARStrdup( (char*)(t+1))) == NULL) {
         gpMySys->PPLogPrintf("Antirelay: failed to copy buffer, skipping");
         return PP_SKIP;
      }

      /* we basically walk the list breaking it up into addresses */
      /* on whitespace and commas */

      pStartOfString = p;
      pAddress = NULL;


      /* the address will look like one of the following:
   **
   ** foo@moof.com\r\n
   ** <foo@moof.com>\r\n
   ** <@HOSTA.ARPA,@HOSTB.ARPA:foo@moof.com>\r\n
   **
   */


      while (isspace(*p)) {
         ++p;
      }

      q = p;
      while (*q != '\0') {
         if (*q == '\r' || *q == '\n') {
            *q = '\0';
            break;
         }
         ++q;
      }

      if (giDebugMode > 1) {
         gpMySys->PPLogPrintf("Antirelay: pre-processed sender {%s}", p);
      }

      /* skip to the next non-whitespace, non '<' character */

      while ((*p != '\0') && ((*p == '<') || (isspace(*p)))) {
         p++;
      }

      /* skip over the relay host stuff */

      if ((q = strchr(p, ':')) != NULL)
         p = ++q;

      /* mark this... it's where the address starts */

      pAddress = p;

      /* now find the end... a null char, a comma or whitespace */

      while ((*p != '\0') && (*p != '>') && (! isspace(*p))) {
         p++;
      }

      /* make a substring out of this piece */

      *p = '\0';

      if (giDebugMode > 1) {
         gpMySys->PPLogPrintf("Antirelay: post-processed sender {%s}", pAddress);
      }

      /* Check & eliminate routed addresses if set */
      if (!giAllowRouting) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay: checking sender '%s' for routed address", pAddress);
         }

         if (routed_address(pAddress)) { /* we have a match */

            gpMySys->PPLogPrintf("Antirelay: refusing routed delivery from %s [%s] to '%s'", 
                                 (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerName",NULL),
                                 (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerIP",NULL),
                                 pAddress);
            gpMySys->PPRealloc(pStartOfString,0);

            *ppszResp = gpNoRoutingResponse;

            return PP_DONE;
         }
      }

      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: verifying sender [%s]", pAddress);
      }

      /* see if address matches any domains in our list */

      pThisEntry = gpDeliveryDomains;

      while (pThisEntry != NULL) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay:  vs [%s]", 
                                 pThisEntry->pszPattern);
         }

         if (PatternMatch((const char*)pAddress, 
                          (const char*)pThisEntry->pszPattern)) {

            /* by OLO: Trust the sender */
            if (giDebugMode) {
               gpMySys->PPLogPrintf("Antirelay: Sender [%s] matched [%s], trusting.", 
                                    pAddress, pThisEntry->pszPattern);
            }
            ppp->iPassed = 1;
            /* free up that copy we made of the line */

            gpMySys->PPRealloc(pStartOfString,0);

            return PP_SKIP;
            /* return PP_DONE; */

         }

         pThisEntry = pThisEntry->pNext;
      }

      /* if it falls through to here, they didn't match any pattern */

      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: didnt match local domain from %s [%s] from sender '%s'", 
                           (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerName",NULL),
                           (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerIP",NULL),
                           pAddress);
      }

      gpMySys->PPRealloc(pStartOfString,0);

   }
   /* :end by OLO */

   /* then second: see if this is a RCPT TO line */

   if (((*pszLine == 'r') || (*pszLine == 'R')) && 
       (! STRNCASECMP(pszLine, "rcpt to", 7))) {

      /* B381967 Additional checks on pszLine */
      if ((t = strchr(pszLine, ':')) == NULL)
         return PP_SKIP;


      /* if we've already validated them as trusted */
      /* just return */

      if (ppp->iPassed == 1)
         return PP_SKIP;

      /* if we are using authentcation as a criteria and */
      /* the client has authenticated, trust them */

      if ((giUseAuthinfo == 1) &&
          ((pszUser = 
            (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"AuthSender",NULL))
           != NULL) &&
          (*pszUser != '\0')) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay: authenticated user [%s], skipping", pszUser);
         }

         ppp->iPassed = 1;

         return PP_SKIP;
      }



      /* if we dont have any domains, the default is "*" */
      /* so we match */

      if (gpDeliveryDomains == NULL)
         return PP_SKIP;


      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: comparing recipients to delivery domains");
      }

      /* we need to dup the line since the memory doesn't belong */
      /* to us and we'll need to muck with it below */

      /* B381967 Can no longer use fixed offset into buffer */
      if ((p = ARStrdup( (char*)(t+1))) == NULL) {

         gpMySys->PPLogPrintf("Antirelay: failed to copy buffer, skipping");
         return PP_SKIP;
      }

      /* we basically walk the list breaking it up into addresses */
      /* on whitespace and commas */

      pStartOfString = p;
      pAddress = NULL;


      /* the address will look like one of the following:
   **
   ** foo@moof.com\r\n
   ** <foo@moof.com>\r\n
   ** <@HOSTA.ARPA,@HOSTB.ARPA:foo@moof.com>\r\n
   **
   */


      while (isspace(*p)) {
         ++p;
      }

      q = p;
      while (*q != '\0') {
         if (*q == '\r' || *q == '\n') {
            *q = '\0';
            break;
         }
         ++q;
      }

      if (giDebugMode > 1) {
         gpMySys->PPLogPrintf("Antirelay: pre-processed recipient {%s}", p);
      }

      /* skip to the next non-whitespace, non '<' character */

      while ((*p != '\0') && ((*p == '<') || (isspace(*p)))) {
         p++;
      }

      /* skip over the relay host stuff */

      if ((q = strchr(p, ':')) != NULL)
         p = ++q;

      /* mark this... it's where the address starts */

      pAddress = p;

      /* now find the end... a null char, a comma or whitespace */

      while ((*p != '\0') && (*p != '>') && (! isspace(*p))) {
         p++;
      }

      /* make a substring out of this piece */

      *p = '\0';

      if (giDebugMode > 1) {
         gpMySys->PPLogPrintf("Antirelay: post-processed recipient {%s}", pAddress);
      }

      /* Check & eliminate routed addresses if set */
      if (!giAllowRouting) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay: checking recipient '%s' for routed address", pAddress);
         }

         if (routed_address(pAddress)) { /* we have a match */

            gpMySys->PPLogPrintf("Antirelay: refusing routed delivery from %s [%s] to '%s'", 
                                 (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerName",NULL),
                                 (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerIP",NULL),
                                 pAddress);
            gpMySys->PPRealloc(pStartOfString,0);

            *ppszResp = gpNoRoutingResponse;

            return PP_DONE;
         }
      }

      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: verifying recipient [%s]", pAddress);
      }

      /* see if address matches any domains in our list */

      pThisEntry = gpDeliveryDomains;

      while (pThisEntry != NULL) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay:  vs [%s]", 
                                 pThisEntry->pszPattern);
         }

         if (PatternMatch((const char*)pAddress, 
                          (const char*)pThisEntry->pszPattern)) {

         if (giDebugMode) {
            gpMySys->PPLogPrintf("Antirelay:  recipient [%s] matched [%s], accepting.",
			    pAddress,  pThisEntry->pszPattern);
	 }
            /* free up that copy we made of the line */

            gpMySys->PPRealloc(pStartOfString,0);

            return PP_SKIP;

         }

         pThisEntry = pThisEntry->pNext;
      }

      /* if it falls through to here, they didn't match any pattern */

      gpMySys->PPLogPrintf("Antirelay: refusing delivery from %s [%s] to recipient '%s'", 
                           (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerName",NULL),
                           (char*)PPSESSION_GETPROPERTY(ppp->pMySession,"PeerIP",NULL),
                           pAddress);

      gpMySys->PPRealloc(pStartOfString,0);


      /* if they are not authenticated and we might trust */
      /* them if they were, say so */

      if ((giAdvertiseAuthinfo == 1) && 
          (PPSESSION_GETPROPERTY(ppp->pMySession,"AuthName",NULL) 
           == NULL))
         *ppszResp = gpNonLocalAuthResponse;
      else
         *ppszResp = gpNonLocalResponse;

      return PP_DONE;
   }
   
   /* now see if this is a EHLO line */

   if (((*pszLine == 'e') || (*pszLine == 'E')) && 
       (! STRNCASECMP(pszLine, "EHLO", 4))) {

      if (giStripAuthCapability == 0)
         return PP_SKIP;

      if (ppp->iPassed == 0)
         return PP_SKIP;


      if (giDebugMode) {
         gpMySys->PPLogPrintf("Antirelay: stripping AUTH extensions");
      }

      iRv = PPSESSION_DEFERLINE(ppp->pMySession,pszLine,ppszResp);

      ppp->pszResp = (char *)gpMySys->PPRealloc(ppp->pszResp,
                                                strlen(*ppszResp)+1);
      ppp->pszResp[0] = '\0';

      /* remove the AUTH stuff we dont want to show */
      p = *ppszResp;
      while ((p != NULL) && ((q = strstr(p, "\r\n")) != NULL)) {

         q+=2; /* move past the CRLF */

         /* if it's not an AUTH line copy it */
         if ((strncmp(p+4, "AUTH=LOGIN", 10)) &&
             (strncmp(p+4, "AUTH ", 5))) {

            iThisLength = q-p;
            iLength += iThisLength;

            strncat(ppp->pszResp, p, iThisLength);
         }
         p = q;
      }


      /* now do fixup in case we eliminated the last 250 response */
      /* to turn a '250-' into a '250 ' */

      r = ppp->pszResp + (iLength-iThisLength);

      if ((r != NULL) && (! strncmp(r, "250-", 4)))
         r[3] = ' ';

      *ppszResp = ppp->pszResp;

      return iRv;
   }


   return PP_SKIP;
}

/*
** Function
** --------
** void ProtPlug_Delete(ProtPlug *ppp);
**  
** Description
** -----------
** fries a ProtPlug
**
** Parameters
** ----------
** ProtPlug *ppp: the session to be deleted
**
** Returns
** -------
** nothing
**
** Remarks
** -------
** this function must free all memory associated with the ProtPlug
**
*/

__export void ProtPlug_Delete(ProtPlug *ppp) {

   /* remove anything plugin specific we created */

   gpMySys->PPRealloc(ppp->pszResp,0);
   gpMySys->PPRealloc(ppp,0);

   return;
}



/* 
** Function
** --------
** static int DoMatch(register const char *text, register const char *p);
**  
** Description
** -----------
** internal pattern matching routine
**
** Parameters
** ----------
** const char *text: the text to match against
** const chap *p: the pattern to match to
**
** Returns
** -------
** a weight indicating the match or 0 indicating no match or -1 indicating 
** an abort
**
** Remarks
** -------
** Based on the wildmat code written by Rich $alz <rsalz@bbn.com>.
**
** Match text and p, return match weight, FALSE, or ABORT.
**
** The match "weight" is two times the count of literally matched
** characters plus one times the count of class matched characters
** plus one.  Matches on * and ? * don't count towards the weight.
**
** Therefore, when matching "test.foo.bar", the weights would be:
**
**	Matching Pattern	Weight
**
**	*			1
**	test.*			11
**	test.[abcdef]??.*	14
**	test.f??.*		15
**	*.foo.bar		17
**	test.foo.bar		25
**
*/

#ifndef TRUE
#define TRUE			1
#define FALSE			0
#endif
#define ABORT			-1
#define NEGATE_CLASS		'^'

static int DoMatch(register const char *text, register const char *p)
{
   register int  last;
   register int  matched;
   register int  reverse;
   register int  weight;

   /*
    * Weight must be non-zero to begin with so that matches on just "*"
    * will work.
    */
   weight = TRUE;
   for (; *p; text++, p++) {
      if (*text == '\0' && *p != '*')
         return ABORT;
      switch (*p) {
         case '\\':
            /* Literal match with following character. */
            p++;
            /* FALLTHROUGH */
         default:
            if (tolower(*text) != tolower(*p))
               return FALSE;
            weight += 2; /* extra credit for exact matches */
            continue;
         case '?':
            /* Match anything. */
            continue;
         case '*':
            while (*++p == '*')
               /* Consecutive stars act just like one. */
               continue;
            if (*p == '\0')
               /* Trailing star matches everything. */
               return weight;
            while (*text)
               if ((matched = DoMatch(text++, p)) > 0) {
                  matched += weight-1;
                  return matched;
               }
            return ABORT;
         case '[':
            reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
            if (reverse)
               /* Inverted character class. */
               p++;
            matched = FALSE;
            if (p[1] == ']' || p[1] == '-')
               if (*++p == *text) {
                  weight++;
                  matched = TRUE;
               }
            for (last = *p; *++p && *p != ']'; last = *p)
               /* This next line requires a good C compiler. */
               if (*p == '-' && p[1] != ']'
                   ? *text <= *++p && *text >= last : *text == *p)
                  matched = TRUE;
            if (matched == reverse)
               return FALSE;
            weight++;
            continue;
      }
   }

   if (*text == '\0')
      return weight;
   return FALSE;
}


/*
** Function
** --------
** static int PatternMatch(const char *text, const char *p)
**  
** Description
** -----------
** simple wrapper for pattern patching routine
**
** Parameters
** ----------
** const char *text: the text to match against
** const chap *p: the pattern to match to
**
** Returns
** -------
** an int indicating TRUE(1) or FALSE(0)
**
** Remarks
** -------
** Based on the wildmat code written by Rich $alz <rsalz@bbn.com>.
*/
static int PatternMatch(const char *text, const char *p)
{
   int i;

   if ((i = DoMatch(text, p)) > 0)
      return i;
   return FALSE;
}


/*
** Function
** --------
** static char *ARStrdup(char *s) {
**  
** Description
** -----------
** a plugin safe version of strdup
**
** Parameters
** ----------
** char *s: the null-terminated string to be duplicated
**
** Returns
** -------
** a pointer to a newly allocated copy of the string s
**
** Remarks
** -------
** this function uses the plugin systems memory allocation 
** routines
**
** 
*/
static char *ARStrdup(char *s) {
   char      *r;
   char      *pszNewString = NULL;
   int       iLength = 1;

   r = s;
   while (*r != '\0') {
      iLength++;
      r++;
   }
   pszNewString = (char*)(gpMySys->PPRealloc(pszNewString, iLength));

   if (pszNewString == NULL)
      return NULL;

   strcpy(pszNewString, s);

   return(pszNewString);
}

