/*****************************************************************************/
/*
                                  address.c

An address list is a string containing one or more email addresses (separated
by newline characters).  Any newline character should be preceded by a comma
(making it a comma-separated list) and therefore directly usable in an RFC2822
header field.


COPYRIGHT
---------
Copyright (C) 2005-2025 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.


VERSION HISTORY
---------------
27-JUN-2010  MGD  bugfix; AddressListMassage() comma processing
13-NOV-2007  MGD  bugfix; AddressListMassage() [smtp-default-host]
                    detect VMS Mail override using explicit 0::<username>
30-JUN-2007  MGD  bugfix; AddressIsVms() and AddressIsRfc() requires
                    AddressListExtract() to parse an address from a list
20-JUL-2006  MGD  AddressListMassage() allow for comma-separated addresses
12-JUL-2006  MGD  bugfix; AddressIsVms() allow for empty string
06-JUL-2006  MGD  bugfix; AddressListMassage() better VMS address detection
06-MAY-2006  MGD  bugfix; AddressIsRfc() RFC2822 only allows the double
                    quotation character (0x34) to quote
                  bugfix; allow backslash (0x92) to escape a single character
14-MAR-2006  MGD  AddressListMassage() refine address reduction (yep - again!)
                    also allow for appending a default host/domain if the
                    configuration directive [SMTP-defaulthost] is set
15-FEB-2006  MGD  AddressListMassage() refine "<address>, <address>" reduction
01-FEB-2005  MGD  initial
*/
/*****************************************************************************/

#ifdef SOYMAIL_VMS_V7
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

#pragma nomember_alignment

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <ssdef.h>
#include <stsdef.h>

/* application header file */
#include <soymail.h>
#include <address.h>
#include <config.h>
#include <mta.h>

#define FI_LI "ADDRESS", __LINE__

/* global storage */

/* prototypes */

/* external storage */

extern BOOL  Debug,
             WatchEnabled;
extern CONFIG_DATA SoyMailConfig;

/*****************************************************************************/
/*
This function appends the string supplied as 'AddrPtr' to the list of addresses
pointed to by 'ListPtr'using dynamic memory.  The memory previously allocated
by 'ListPtr' is freed.  Returns a pointer to the new list.  Expects to be
subsequently massaged by AddressListMassage().
*/

char* AddressListAppend
(
char *ListPtr,
char *AddrPtr
)
{
   int  len;
   char  *sptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AddressListAppend() |%s|\n", AddrPtr);

   if (!AddrPtr || !AddrPtr[0]) return (ListPtr);

   if (ListPtr)
      len = strlen(ListPtr) + strlen(AddrPtr) + 2;
   else
      len = strlen(AddrPtr) + 1;
   sptr =  CgiLibVeeMemCalloc (len);
   if (ListPtr && ListPtr[0])
      sprintf (sptr, "%s\n%s", ListPtr, AddrPtr);
   else
      strcpy (sptr, AddrPtr);
   CgiLibVeeMemFree (ListPtr);

   if (Debug) fprintf (stdout, "|%s|\n", sptr);
   return (sptr);
}

/*****************************************************************************/
/*
This function ensures that the string list conforms to the address list
requirements described in the prologue to this module.   Returns a pointer to a
reformatted, compliant string.  Returns NULL if there was a significant problem
(e.g. buffer overflow).  If the parameter string is dynamic it is up to the
calling function to dispose of it.
*/

char* AddressListMassage
(
char* ListPtr,
BOOL CommaSeparate
)
{
   BOOL  CommaNext;
   int  ccnt, lcnt,
        NewListSize;
   char  *cptr, *lptr, *nlptr, *sptr, *zptr,
         *NewListPtr;
   char  abuf [256];

   /*********/
   /* begin */
   /*********/

   if (WatchEnabled)
      WatchThis (FI_LI, "MASSAGE from |!AZ", ListPtr ? ListPtr : "(null)");

   if (!(lptr = ListPtr)) return (NULL);

   NewListSize = 0;
   /* allocate an empty list (just in case) */
   NewListPtr = nlptr = CgiLibVeeMemCalloc (8);
   if (!NewListPtr) ErrorExit (vaxc$errno, FI_LI);

   lptr = ListPtr;
   while (*lptr)
   {
      /* skip leading white-space and commas */
      while (*lptr && (*lptr == ' ' || *lptr == '\t' ||
                       *lptr == '\n' || *lptr == '\r' ||
                       *lptr == ',')) lptr++;

      /* copy into the scratch buffer */
      CommaNext = FALSE;
      zptr = (sptr = abuf) + sizeof(abuf);
      while (*lptr && *lptr != '\n' && *lptr != '\r' && sptr < zptr)
      {
         if (*lptr == '\"')
         {
            if (sptr < zptr) *sptr++ = *lptr++;
            while (*lptr && *lptr != '\n' && *lptr != '\r' &&
                   *lptr != '\"' && sptr < zptr)
               if (*lptr == '\\' && !iscntrl(*(lptr+1)))
               {
                  *sptr++ = *lptr++;
                  if (sptr < zptr) *sptr++ = *lptr++;
               }
               else
                  *sptr++ = *lptr++;
            if (*lptr == '\"' && sptr < zptr) *sptr++ = *lptr++;
            CommaNext = TRUE;
         }
         else
         if (*lptr == '<')
         {
            if (sptr < zptr) *sptr++ = *lptr++;
            while (*lptr && *lptr != '\n' && *lptr != '\r' &&
                   *lptr != '>' && sptr < zptr)
               *sptr++ = *lptr++;
            if (*lptr == '>' && sptr < zptr) *sptr++ = *lptr++;
            CommaNext = TRUE;
         }
         else
         if (*lptr == '@')
         {
            *sptr++ = *lptr++;
            CommaNext = TRUE;
         }
         else
         if (*lptr == ',' && CommaNext)
            break;
         else
         if (*lptr == '\\' && !iscntrl(*(lptr+1)))
         {
            *sptr++ = *lptr++;
            if (sptr < zptr) *sptr++ = *lptr++;
         }
         else
            *sptr++ = *lptr++;
      }
      if (sptr > zptr) return (NULL);
      *sptr = '\0';

      /* if an empty line */
      if (!abuf[0]) continue;

      /* if the configuration specifies unqualified username is RFC address */
      if (SoyMailConfig.SmtpDefaultHost)
      {
         /* override to VMS Mail using 0::<username> */
         if (!AddressIsRfc(abuf) && !strstr(abuf,"::"))
         {
            /* no, create a RFC-style address */
            if (sptr < zptr) *sptr++ = '@';
            for (cptr = SoyMailConfig.SmtpDefaultHost;
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
            if (sptr > zptr) return (NULL);
            *sptr = '\0';
         }
      }

      ccnt = sptr - abuf;
      NewListPtr = CgiLibVeeMemRealloc (NewListPtr, NewListSize+ccnt+8);
      if (!NewListPtr) ErrorExit (vaxc$errno, FI_LI);
      nlptr = NewListPtr + NewListSize;

      if (nlptr > NewListPtr)
      {
         /* append a comma and newline to previous entry */
         if (CommaSeparate) *nlptr++ = ',';
         *nlptr++ = '\n';
      }
      for (cptr = abuf; *cptr; *nlptr++ = *cptr++);
      NewListSize = nlptr - NewListPtr;
   }
   *nlptr = '\0';

   AddressListPurge (NewListPtr);

   if (WatchEnabled)
      WatchThis (FI_LI, "MASSAGE to |!AZ", NewListPtr ? NewListPtr : "(null)");

   return (NewListPtr);
}

/*****************************************************************************/
/*
Remove exact duplicates from an address list.
Assumes it has been massaged into an address list format.
Returns the number of characters in the purged list.
*/

int AddressListPurge (char *ListPtr)

{
   char  ch1, ch2;
   char  *cptr, *lptr, *sptr, *tptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AddressListPurge() |%s|\n", ListPtr);

   if (!ListPtr) return (0);
   if (!(lptr = ListPtr)) return (0);

   while (*lptr)
   {
      /* scan to the end of the next address */
      for (cptr = lptr; *cptr && *cptr != '\n'; cptr++);
      /* if the last address in the list */
      if (!*cptr || !*(cptr+1))
      {
         /* eliminate any trailing newline */
         *cptr = '\0';
         break;
      }
      ch1 = *cptr;
      *cptr++ = '\0';
      /* scan through the rest of the list comparing addresses to that one */
      while (*cptr)
      {
         for (sptr = cptr; *cptr && *cptr != '\n'; cptr++);
         ch2 = *cptr;
         *cptr = '\0';
         if (!strcmp (lptr, sptr))
         {
            /* same-same, eliminate by copying the rest over the top of it */
            *cptr = ch2;
            if (*cptr) cptr++;
            for (tptr = sptr; *cptr; *tptr++ = *cptr++);
            *tptr = '\0';
            cptr = sptr;
         }
         else
         {
            /* not the same, just resume */
            *cptr = ch2;
            if (*cptr) cptr++;
            sptr = cptr;
         }
      }
      while (*lptr) lptr++;
      *lptr = ch1;
      /* if the last address in the list */
      if (!*lptr || !*(lptr+1))
      {
         /* eliminate any trailing newline */
         *lptr = '\0';
      }
      if (*lptr) lptr++;
   }
   if (Debug) fprintf (stdout, "AddressListPurge() |%s|\n", ListPtr);

   return (lptr - ListPtr);
}

/*****************************************************************************/
/*
Parse an address from a list.  Return a pointer to a static buffer containing
the address, an empty string if end-of-list, or a NULL if an error was
encountered.  Update the supplied list pointer ready to parse the next address.
*/

char* AddressListExtract (char **ListPtrPtr)

{
   static char  abuf [256];

   char  *cptr, *lptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AddressListExtract()\n");

   if (!ListPtrPtr) return (NULL);
   if (!(lptr = *ListPtrPtr)) return (NULL);

   zptr = (sptr = abuf) + sizeof(abuf)-1;
   *sptr = '\0';

   while (*lptr && isspace(*lptr) && *lptr != '\n') lptr++;
   while (*lptr && *lptr != '\n' && sptr < zptr) *sptr++ = *lptr++;
   *sptr-- = '\0';
   while (sptr > abuf && (isspace(*sptr) || *sptr == ',')) *sptr-- = '\0';

   while (*lptr && *lptr != '\n') lptr++;
   while (*lptr && *lptr == '\n') lptr++;

   /* update the pointer to the list */
   *ListPtrPtr = lptr;

   if (Debug) fprintf (stdout, "|%s|\n", abuf);

   return (abuf);
}

/*****************************************************************************/
/*
Does the supplied address look like VMS Mail style. 
*/

BOOL AddressIsVms (char* cptr)

{
   char  *aptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AddressIsVms() |%s|\n", cptr);

   for (;;)
   {
      aptr = AddressListExtract (&cptr);
      if (!aptr || !*aptr)
      {
         if (Debug) fprintf (stdout, "FALSE\n");
         return (FALSE);
      }

      while (*aptr)
      {
         if (*aptr == '\"')
         {
            /* ignore anything inside of quotes */
            aptr++;
            while (*aptr && *aptr != '\"')
            {
               if (*aptr == '\\' && *(aptr+1)) aptr += 2;
               aptr++;
            }
            if (*aptr) aptr++;
         }
         else
         {
            while (*aptr && (isspace(*aptr) || isalnum(*aptr) ||
                   *aptr == '_' || *aptr == '$' || *aptr == ':'))
            {
               if (*aptr == ':' && *(USHORTPTR)aptr != '::') break;
               if (*(USHORTPTR)aptr == '::') aptr++;
               aptr++;
            }
            if (*aptr && *aptr != '\"') break;
         }
      }

      if (!*aptr)
      {
         /* if through without encountering anything non-VMS */
         if (Debug) fprintf (stdout, "TRUE\n");
         return (TRUE); 
      }
   }
}

/*****************************************************************************/
/*
Does the supplied address look like Internet style? 
*/

BOOL AddressIsRfc (char *cptr)

{
   char  *aptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AddressIsRfc() |%s|\n", cptr);

   for (;;)
   {
      aptr = AddressListExtract (&cptr);
      if (!aptr || !*aptr)
      {
         if (Debug) fprintf (stdout, "FALSE\n");
         return (FALSE);
      }

      while (*aptr)
      {
         if (*aptr == '\"')
         {
            /* ignore anything inside of quotes */
            aptr++;
            while (*aptr && *aptr != '\"')
            {
               if (*aptr == '\\' && *(aptr+1)) aptr += 2;
               aptr++;
            }
            if (*aptr) aptr++;
         }
         else
         {
            if (*aptr == '\\' && *(aptr+1)) aptr += 2;
            /* if it looks like a component of an Internet-style address */
            if (*aptr == '<' || *aptr == '@')
            {
               if (Debug) fprintf (stdout, "TRUE\n");
               return (TRUE); 
            }
            aptr++;
         }
      }
   }
}

/*****************************************************************************/

