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

                                 contacts.c

The logical name SOYMAIL_CONTACT_LIST can be used to specify a logical list of
contact/mailing lists.  This works as a multi-value logical name with each
value being the logical name for (or actual file specification of) a soyMAIL
format contacts list (LDIF) or a VMS-style (each line in the file contains a
single address) mailing list.

For example:

  $ DEFINE /SYSTEM SOYMAIL_CONTACT_LIST -
    ALL_USERS_LIST,GROUP1_USERS_LIST,GROUP2_USERS_LIST,GROUP3_USERS_LIST,-
    EXTERNAL_LIST,MAILING_LIST_LIST

Where ALL_USERS_LIST is a VMS-style, so are GROUP1_USERS_LIST and
GROUP2_USERS_LIST.  MAILING_LIST_LIST is a file containing a VMS-style mailing
list of the mailing lists supported by the system.  For the above it might
contain five lines with:

  ALL_USERS_LIST
  GROUP1_USERS_LIST
  GROUP2_USERS_LIST
  GROUP3_USERS_LIST
  EXTERNAL_LIST

These can then be selected to mail to the entire list.

Note that it is better to create a short logical name for each contact/mailing
list and put these into the SOYMAIL_CONTACT_LIST otherwise the full file name
(which can be quite long) chews up a lot of real-estate on the soyMAIL compose
page.


SOYMAIL CONTACT LIST
--------------------
The soyMAIL contact list file is stored in LDAP Data Interchange Format (LDIF)
format (RFC2849).  It is a very simple implementation based on Mozilla 'export
to LDIF', the RFC, and a number of explanations and examples from 'the Web'. 
It is very-much 'monkey-see, monkey-do'.  But never mind.  It should facilitate
a certain amount of exchange with other agents.  In particular it is designed
to facilitate exchange with Mozilla/Firefox.

Attributes supported by the soyMAIL contact list:

  c                          country
  cn                         common name  (e.g. Mark Daniel)
  description                free text  (multi-line)
  dn                         distinguished name
  givenName                  first name  (e.g. Mark)
  facsimileTelephoneNumber   fax telephone number
  l                          location (city)
  mail                       email address  (primary)
  mobile                     mobile telephone number
  postalCode                 post/ZIP code
  sn                         family name  (e.g. Daniel)
  soyMAILlist                additional email addresses  (multi-line)
  telephoneNumber            telephone number
  pager                      pager telephone number
  postalAddress              street address

soyMAIL consolidates some of these discrete fields into composites (e.g. the
cn: and description: fields as the 'entry', the postalAddress:, l:, st:,
postalCode: and c: fields into the 'postal address').


ESSENTIAL FIELDS
----------------
The fields that need to be present for soyMAIL to consider an entry to be
legitimate are dn:, cn:, and mail:.  Others may be eliminated from large
LDIF format mailing lists to improve load processing and latency.


LDIF PURGE
----------
The compose page contacts dialog uses only the cn: and mail: LDIF elements. 
soyMAIL provides a command-line accessable utility to purge all but these
required fields from an LDIF file.  This can significantly improve load times
for large numbers of entries (many hundreds or thousands).

  $ SOYMAIL == "$CGI_EXE:SOYMAIL.EXE"
  $ SOYMAIL /LDIF=PURGE <input-file> <output-file>


NOTES ON SOYMAIL LDIF FILES
---------------------------
For the SOYMAIL_CONTACTS.LDIF file and [export] generated LDIF content
extraneous empty lines should be avoided.  At least Mozilla 1.7.11 interprets
extraneous empty lines, for instance between comments and after comments before
attributes, as an empty record and inserts such into the address book.  I can't
see that RFC2849 prohibits this sort of usage (except of course where it would
terminate an entry).  So, ensure there are no empty lines except where they
terminate an entry!


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-FEB-2010  MGD  adjust textarea cols
10-JUL-2008  MGD  generation of LDIF contact lists for the compose page
                    has been significantly revised resulting in a 6800 entry
                    load dropping from 75 seconds to under 2 seconds!
                  ContactsLdifPurge() command-line utility (/LDIF=PURGE)
19-JUN-2008  MGD  ContactsLoadFile() differentiate LDIF and VMS mailing
                    list files using characteristics unique to LDIF
22-JUL-2006  MGD  bugfix; ContactsLdifRecord() HTML-escape only non-base64
18-JUN-2006  MGD  major revision of contact maintenance behaviour
                    *** all editing now requires an explicit save!! ***
                  bugfix; contact import and merge not broken but brain-dead
                    revision of ContactsImportLdif() and ContactsMergeLdif()
                  bugfix; ContactsLoadFile() do not munge non-personal (i.e.
                    site-specific) contact file names
26-APR-2006  MGD  bugfix; ContactsLoadFile() rdptr->ContactsTextPtr = NULL
                    so that empty LDIF file is not confused with VMS list
19-MAR-2006  MGD  refine contact management wrt duplicate common names
15-MAR-2006  MGD  bugfix; ContactsSave() to empty LDIF file
15-FEB-2005  MGD  bugfix; ContactsEdit() no sanity check on un-specified
                    contact, just memset() zero it and continue as 'new' entry
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 <descrip.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

/* application header file */
#include "soymail.h"
#include "address.h"
#include "request.h"
#include "config.h"
#include "contacts.h"

#define FI_LI "CONTACTS", __LINE__

/* external storage */

extern BOOL  Debug,
             WatchEnabled;

extern int  VmsVersion;

extern char  *CgiEnvironmentPtr;

extern char  CurrentVmsTimeString[],
             DocType[],
             SoftwareCopy[],
             SoftwareEnv[],
             SoftwareId[],
             SoftwareVn[];

extern VMS_MAIL_USER VmsMailUser;
extern CONFIG_DATA  SoyMailConfig;

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

BOOL ContactsPageRequest (REQUEST_DATA *rdptr)

{
   char  *cptr,
         *ContactsState;

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

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

   /* a bit of sanity checking */
   if (!rdptr->PrivateAccess) ErrorExit (SS$_BUGCHECK, FI_LI);

   rdptr->ContactsErrorCount = 
      rdptr->ContactsLoadCount =
      rdptr->ContactsUnknownCount = 0;

   CGIVARNULL (cptr, "FORM_CONTACT_BTN");
   if (cptr)
   {
      /*********************/
      /* a contacts button */
      /*********************/

      if (rdptr->PrevPageIdent != PAGE_CONTACTS)
         ErrorExit (SS$_BUGCHECK, FI_LI);

      /* list of contacts from previous contacts page */
      CGIVARNULL (ContactsState, "FORM_CONTACT_STATE");
      ContactsLoad (rdptr, NULL, ContactsState);

      if (LangSame ("contact_delete", cptr))
         ContactsPage (rdptr, FALSE);
      else
      if (LangSame ("contact_edit", cptr))
         ContactsPage (rdptr, FALSE);
      else
      if (LangSame ("contact_export", cptr))
      {
         if (ContactsExport (rdptr)) return (TRUE);
         ContactsPage (rdptr, FALSE);
      }
      else
      if (LangSame ("contact_import", cptr))
      {
         CGIVARNULL (cptr, "FORM_CONTACT_IMPORT_MERGE_FILE");
         if (cptr) ContactsImportLdif (rdptr, cptr);
         ContactsPage (rdptr, TRUE);
      }
      else
      if (LangSame ("contact_merge", cptr))
      {
         CGIVARNULL (cptr, "FORM_CONTACT_IMPORT_MERGE_FILE");
         if (cptr) ContactsMergeLdif (rdptr, cptr);
         ContactsPage (rdptr, TRUE);
      }
      else
      if (LangSame ("print", cptr))
         ContactsPrintView (rdptr);
      else
      if (LangSame ("contact_save", cptr))
      {
         ContactsSave (rdptr);
         ContactsPage (rdptr, FALSE);
      }
      else
         ErrorExit (SS$_BUGCHECK, FI_LI);

      return (TRUE);
   }

   CGIVARNULL (cptr, "FORM_CONTACT_PRINT_POPUP");
   if (cptr)
   {
      /* list of contacts from previous contacts page */
      CGIVARNULL (ContactsState, "FORM_CONTACT_STATE");
      ContactsLoad (rdptr, NULL, ContactsState);
      ContactsPrintView (rdptr);
      return (TRUE);
   }

   /*************************/
   /* not a contacts button */
   /*************************/

   ContactsLoad (rdptr, NULL, NULL);

   ContactsPage (rdptr, FALSE);

   return (TRUE);
}

/*****************************************************************************/
/*
Generate the contacts maintenance (edit) page.
*/

void ContactsPage
(
REQUEST_DATA *rdptr,
BOOL ImportMerge
)
{
   int  status;
   REQUEST_DATA  *sdptr;
   USER_OPTIONS  *uoptr;

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

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

   uoptr = &rdptr->UserOptions;

   if (uoptr->CharSetDefault[0])
      CgiLibResponseSetCharset (uoptr->CharSetDefault);
   else
   if (SoyMailConfig.CharSetDefault && SoyMailConfig.CharSetDefault[0])
      CgiLibResponseSetCharset (SoyMailConfig.CharSetDefault);
   else
      CgiLibResponseSetCharset (CHARSET_DEFAULT);

   StatusMessage (FI_LI, 0, LangFor("contact_loaded"),
                  rdptr->ContactsLoadCount,
                  rdptr->ContactsErrorCount,
                  rdptr->ContactsUnknownCount);

   rdptr->FieldFocusPtr =
"document.getElementById(\'contact_cn_and_description\').focus();";

   MainMenuPageBegin (rdptr, PAGE_CONTACTS);
   MainMenuBar (rdptr);
   StatusInfoPanel (rdptr);

   fprintf (stdout,
"<table width=\"100%%\" cellpadding=\"1\" cellspacing=\"0\">\n\
<tr><td align=\"center\" valign=\"top\">\n\n");

   ContactsEdit (rdptr, ImportMerge);

   fprintf (stdout,
"</td></tr>\n\
</table>\n");

   MainMenuPageEnd (rdptr);
}

/*****************************************************************************/
/*
Generate the table containing the contacts including the edit fields for the
selected contact (if any).
*/

void ContactsEdit
(
REQUEST_DATA *rdptr,
BOOL ImportMerge
)
{
   BOOL  EditDone = FALSE;
   int  status,
        ContactCount,
        EditNumber,
        EditingNumber,
        EntryNumber;
   char  *cptr, *sptr, *zptr,
         *ContactCnPtr,
         *LDIFcnPtr,
         *LDIFdescriptionPtr,
         *LDIFfacsimileTelephoneNumberPtr,
         *LDIFmailPtr,
         *LDIFmobilePtr,
         *LDIFtelephoneNumberPtr;
   char  PostalAddress [256];
   CONTACTS_ENTRY  EmptyEntry,
                   RemoveEntry;
   CONTACTS_ENTRY  *ceptr,
                   *celptr;

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

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

   EditNumber = EditingNumber = 0;
   CGIVARNULL (cptr, "FORM_CONTACT_BTN");
   if (!LangSame ("contact_delete", cptr))
   {
      CGIVARNULL (cptr, "FORM_CONTACT_EDIT_NUMBER");
      if (cptr) EditNumber = atoi(cptr);
      CGIVARNULL (sptr, "FORM_CONTACT_EDITING_NUMBER");
      if (sptr) EditingNumber = atoi(sptr);
      /* hitting [edit] while it's in use creates an empty (new) entry */
      if (EditNumber == EditingNumber) EditNumber = 0;
   }

   fprintf (stdout,
"<!-- contacts begin -->\n\
<table cellpadding=\"3\" cellspacing=\"1\" class=\"css_panel_4\" \
style=\"padding:0.5em 1.0em 1.5em 1.0em;text-align:left;\">\n\
<tr class=\"css_panel_2\">\
<td colspan=\"5\" style=\"padding:5px;\">\n\
<table cellpadding=\"0\" cellspacing=\"0\" \
border=\"0\" width=\"100%%\">\n\
<tr class=\"css_panel_2\" style=\"font-weight:bold;text-align:center;\">\
<td width=\"20%%\">%s</td>\
<td width=\"45%%\">%s</td>\
<td width=\"30%%\">%s</td>\
</tr>\n\
</table>\n\
</td></tr>\n",
            LangFor("contact_entry"), 
            LangFor("contact_email"), 
            LangFor("contact_postal")); 

   CGIVARNULL (cptr, "FORM_CONTACT_BTN");
   if (LangSame ("contact_edit", cptr))
   {
      for (ceptr = rdptr->ContactsListPtr; ceptr; ceptr = ceptr->NextEntry)
         if (ceptr->EntryNumber == EditNumber) break;
      /* if select the empty (new) contact */
      if (!ceptr) memset (ceptr = &EmptyEntry, 0, sizeof(CONTACTS_ENTRY));
   }
   else
   {
      /* new or appended entry */
      memset (ceptr = &EmptyEntry, 0, sizeof(CONTACTS_ENTRY));
      EditNumber = 999999;

      CGIVARNULL (LDIFcnPtr, "FORM_MSG_ADD_CONTACT_AS");
      CGIVARNULL (LDIFmailPtr, "FORM_MSG_ADD_CONTACT_FROM");
      if (LDIFcnPtr && LDIFmailPtr)
      {
         /* editing the one just appended from the message page */
         ceptr->LDIFcnPtr = LDIFcnPtr;
         ceptr->LDIFmailPtr = LDIFmailPtr;
      }
   }

   ContactCount = 0;
   while (ceptr)
   {
      if (!(LDIFcnPtr = ceptr->LDIFcnPtr)) LDIFcnPtr = "";
      if (!(LDIFdescriptionPtr = ceptr->LDIFdescriptionPtr))
         LDIFdescriptionPtr = "";
      if (!(LDIFfacsimileTelephoneNumberPtr =
            ceptr->LDIFfacsimileTelephoneNumberPtr))
         LDIFfacsimileTelephoneNumberPtr = "";
      if (!(LDIFmailPtr = ceptr->LDIFmailPtr)) LDIFmailPtr = "";
      if (!(LDIFmobilePtr = ceptr->LDIFmobilePtr)) LDIFmobilePtr = "";
      if (!(LDIFtelephoneNumberPtr = ceptr->LDIFtelephoneNumberPtr))
         LDIFtelephoneNumberPtr = "";

      ContactsPostalAddress (ceptr, PostalAddress, sizeof(PostalAddress));

      if (!EditDone)
      {
         fprintf (stdout,
"<tr valign=\"top\" style=\"vertical-align:top;\">\n\
<input type=\"hidden\" name=\"contact_editing_number\" value=\"%d\">\n\
<td><input type=\"radio\" style=\"border:none;\" \
name=\"contact_edit_number\" value=\"%d\" checked></td>\n\
<td><textarea class=\"css_text_1\" rows=\"4\" cols=\"20\" \
id=\"contact_cn_and_description\" name=\"contact_cn_and_description\" \
wrap=\"off\" tabindex=\"1\">%s%s%s</textarea>&nbsp;</td>\n\
<td><textarea class=\"css_text_1\" \
name=\"contact_mail\" rows=\"4\" cols=\"45\" \
wrap=\"off\" tabindex=\"2\">%s</textarea></td>\n\
<td style=\"white-space:nowrap;text-align:right;padding-right:0;\">\
%s:<br>%s:<br>%s:<br>%s:<br>%s:</td>\n\
<td><textarea class=\"css_text_1\" \
name=\"contact_postaladdress\" rows=\"4\" cols=\"30\" \
wrap=\"off\"%s tabindex=\"3\">%s</textarea>&nbsp;</td>\n\
</tr>\n\
\
<tr style=\"vertical-align:top;\">\
<td></td><td colspan=\"4\">\n\
%s:&nbsp;<input class=\"css_text_1\" type=\"text\" \
name=\"contact_telephonenumber\" size=\"20\" value=\"%s\" \
tabindex=\"4\">&nbsp;\n\
%s:&nbsp;<input class=\"css_text_1\" type=\"text\" \
name=\"contact_mobile\" size=\"20\" value=\"%s\" tabindex=\"5\">&nbsp;\n\
%s:&nbsp;<input class=\"css_text_1\" type=\"text\" \
name=\"contact_facsimiletelephonenumber\" size=\"20\" value=\"%s\" \
tabindex=\"6\">&nbsp;\n\
</td></tr>\n",
                  EditNumber,
                  EditNumber,
                  HTML_ESCAPE(LDIFcnPtr),
                  *LDIFdescriptionPtr ? "&#10;" : "",
                  HTML_ESCAPE(LDIFdescriptionPtr),
                  HTML_ESCAPE(LDIFmailPtr),
                  LangFor("contact_postal_street"),
                  LangFor("contact_postal_city"),
                  LangFor("contact_postal_state"),
                  LangFor("contact_postal_code"),
                  LangFor("contact_postal_country"),
                  PostalAddress[0] ? "" : " onfocus=\"this.select()\"",
                  PostalAddress[0] ? HTML_ESCAPE(PostalAddress) : "",
                  LangFor("contact_phone"),
                  HTML_ESCAPE(LDIFtelephoneNumberPtr),
                  LangFor("contact_mobile"),
                  HTML_ESCAPE(LDIFmobilePtr),
                  LangFor("contact_fax"),
                  HTML_ESCAPE(LDIFfacsimileTelephoneNumberPtr));

         ContactsEditButtons (rdptr, TRUE, ImportMerge);

         /* 'continue' with  the first 'real' entry */
         ceptr = rdptr->ContactsListPtr;
         EditDone = TRUE;
         continue;
      }

      if (ceptr->EntryNumber == EditNumber)
      {
         /* skip over the one we're editing */
         ceptr = ceptr->NextEntry;
         continue;
      }

      fprintf (stdout,
"<tr style=\"vertical-align:top;white-space:normal;\">\
<td><input type=\"radio\" style=\"border:none;\" \
name=\"contact_edit_number\" value=\"%d\"></td>\
<td><pre style=\"margin:0;\"><u>%s</u>",
               ceptr->EntryNumber,
               HTML_ESCAPE(LDIFcnPtr));

      if (*LDIFdescriptionPtr)
         fprintf (stdout, "\n%s",
                  HTML_ESCAPE(LDIFdescriptionPtr));

      if (*LDIFtelephoneNumberPtr)
         fprintf (stdout, "\n%s: %s",
                  LangFor("contact_phone"),
                  HTML_ESCAPE(LDIFtelephoneNumberPtr));

      if (*LDIFfacsimileTelephoneNumberPtr)
         fprintf (stdout, "\n%s: %s",
                  LangFor("contact_fax"),
                  HTML_ESCAPE(LDIFfacsimileTelephoneNumberPtr));

      if (*LDIFmobilePtr)
         fprintf (stdout, "\n%s: %s",
                  LangFor("contact_mobile"),
                  HTML_ESCAPE(LDIFmobilePtr));

      fprintf (stdout,
"</pre></td>\
<td colspan=\"2\"><pre style=\"margin:0;\">%s</pre></td>\
<td><pre style=\"margin:0;\">%s</pre></td>\
</tr>\n",
               HTML_ESCAPE(LDIFmailPtr),
               HTML_ESCAPE(PostalAddress));

      ContactCount++;
 
      /* on to the next entry */
      ceptr = ceptr->NextEntry;
   }

   if (ContactCount > 15) ContactsEditButtons (rdptr, FALSE, ImportMerge);

   fprintf (stdout,
"</table>\n\
<input type=\"hidden\" name=\"contact_state\" value=\"");

   ContactsWrite (rdptr, stdout, TRUE);

   fprintf (stdout,
"\">\n\
<!--contacts end -->\n\n");
}

/*****************************************************************************/
/*
The [save], [edit], etc., button bar.
*/

void ContactsEditButtons
(
REQUEST_DATA *rdptr,
BOOL UnderEdit,
BOOL ImportMerge
)
{
   char  *cptr, *sptr, *zptr;
   char  ExportUri [256];

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

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

   zptr = (sptr = ExportUri) + sizeof(ExportUri)-1;
   for (cptr = rdptr->FormAction; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = '/';
   for (cptr = SOY_MAIL; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = '_';
   CGIVARNULL (cptr, "HTTP_HOST");
   if (cptr)
   {
      while (*cptr && sptr < zptr)
      {
         if (isalnum(*cptr))
            *sptr++ = *cptr++; 
         else
         {
            *sptr++ = '_';
            cptr++;
         }
      }
   }
   for (cptr = ".ldif?export.ldif"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   fprintf (stdout,
"<tr><td></td></tr>\n\
<tr class=\"css_panel_2\">\
<td style=\"text-align:left;padding:5px;\" colspan=\"5\">\n\
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%%\">\
<tr><td align=\"left\">\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\"%s>&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"reset\" value=\"%s\" \
onclick=\"return confirm(\'%s %s\')\">&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">\
<script language=\"JavaScript\">\
document.write(\'<input class=\"css_button_panel_2\" style=\"border-left-width:0px;\" \
type=\"submit\" name=\"contact_print_popup\" value=\"^\" \
onclick=\"targetBlank();\">\');\
</script>\n\
</td>\n",
            LangFor("contact_save"),
            UnderEdit ? " tabindex=\"7\"" : "",
            LangFor("contact_edit"),
            LangFor("contact_reset"),
            LANG_FOR_CONFIRM("contact_reset_confirm"),
            LANG_FOR_CONFIRM("sure?"), 
            LangFor("contact_delete"),
            LangFor("print"));

   if (ImportMerge)
   {
      fprintf (stdout,
"<td align=\"right\">\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\" \
onclick=\"return confirm(\'%s LDIF ... %s\')\">&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\" \
onclick=\"return confirm(\'%s LDIF ... %s\')\">&nbsp;\n\
<div class=\"css_button_panel_1 css_input_type_file\">\
<input class=\"css_text_1\" id=\"contact_import_merge_file\" \
type=\"file\" name=\"contact_import_merge_file\" size=\"25\">\
<span onclick=\"document.getElementById(\
\'contact_import_merge_file\').click();return false;\">%s</span>\
</div>&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">\
<a style=\"text-decoration:none;\" href=\"%s\" \
onclick=\"return false\">%s</A>\n\
</td></tr>\n\
</table>\n\
</td></tr>\n\
<tr><td></td></tr>\n",
               LangFor("contact_import"),
               LANG_FOR_CONFIRM("contact_import"),
               LANG_FOR_CONFIRM("sure?"),
               LangFor("contact_merge"),
               LANG_FOR_CONFIRM("contact_merge"),
               LANG_FOR_CONFIRM("sure?"),
               LANG_FOR_CONFIRM("select_file"),
               LangFor("contact_export"),
               ExportUri, SAVE_AS_LINK);
   }
   else
   {
      fprintf (stdout,
"<td align=\"right\">\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"contact_btn\" value=\"%s\">\
<a style=\"text-decoration:none;\" href=\"%s\" \
onclick=\"return false\">%s</A>\n\
</td></tr>\n\
</table>\n\
</td></tr><p>\n\
<tr><td></td></tr>\n",
               LangFor("contact_import"),
               LangFor("contact_merge"),
               LangFor("contact_export"),
               ExportUri, SAVE_AS_LINK);
   }
}

/*****************************************************************************/
/*
The function name say it all.
*/

void ContactsPrintView (REQUEST_DATA *rdptr)

{
   int  status,
        ContactCount;
   char  *cptr,
         *LDIFcnPtr,
         *LDIFdescriptionPtr,
         *LDIFfacsimileTelephoneNumberPtr,
         *LDIFmailPtr,
         *LDIFmobilePtr,
         *LDIFtelephoneNumberPtr;
   char  PostalAddress [256];
   CONTACTS_ENTRY  *ceptr;
   USER_OPTIONS  *uoptr;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;

   if (rdptr->CharSetPtr) CgiLibResponseSetCharset (rdptr->CharSetPtr);

   CgiLibResponseHeader (200, "text/html", "%s", rdptr->LoginSetCookiePtr);

   fprintf (stdout,
"%s\
<html>\n\
<head>\n\
%s\
<meta name=\"generator\" content=\"%s\">\n\
<meta name=\"environment\" content=\"%s\">\n\
<meta name=\"copyright\" content=\"%s - Apache-2.0 licensed\">\n",
            DocType,
            CgiLibXUACompatible(NULL),
            SoftwareEnv,
            CgiEnvironmentPtr,
            SoftwareCopy);

   SoyMailStyleStuff (rdptr, "print");

   /* setTimeout() gives Firefox 1.5 a chance to render the page */
   fprintf (stdout,
"<title>%s</title>\n\
</head>\n\n\
<body onload=\"setTimeout(\'print()\',500);\">\n\
\n\
<table cellpadding=\"0\" cellspacing=\"5\" border=\"0\">\n\
<tr><th colspan=\"4\" style=\"font-size:120%%;\">%s</th></tr>\n\
<tr>\
<td style=\"padding-right:1em;\"></td>\
<th align=\"left\" style=\"padding-right:1em;\">%s</th>\
<th align=\"left\" style=\"padding-right:1em;\">%s</th>\
<th align=\"left\">%s</th>\
</tr>\n\
<tr><td colspan=\"4\" class=\"css_hr\">\
<hr align=\"left\" size=\"1\" noshade \
class=\"css_print_hr\" style=\"width:100%%\"></td></tr>\n",
            SoyMailPageTitle (rdptr),
            LangFor("contacts"),
            LangFor("contact_entry"), 
            LangFor("contact_email"),
            LangFor("contact_postal")); 

   ContactCount = 0;
   for (ceptr = rdptr->ContactsListPtr; ceptr; ceptr = ceptr->NextEntry)
   {
      ContactCount++;

      if (!(LDIFcnPtr = ceptr->LDIFcnPtr)) LDIFcnPtr = "";
      if (!(LDIFdescriptionPtr = ceptr->LDIFdescriptionPtr))
         LDIFdescriptionPtr = "";
      if (!(LDIFfacsimileTelephoneNumberPtr =
            ceptr->LDIFfacsimileTelephoneNumberPtr))
         LDIFfacsimileTelephoneNumberPtr = "";
      if (!(LDIFmailPtr = ceptr->LDIFmailPtr)) LDIFmailPtr = "";
      if (!(LDIFmobilePtr = ceptr->LDIFmobilePtr)) LDIFmobilePtr = "";
      if (!(LDIFtelephoneNumberPtr = ceptr->LDIFtelephoneNumberPtr))
         LDIFtelephoneNumberPtr = "";

      ContactsPostalAddress (ceptr, PostalAddress, sizeof(PostalAddress));

      fprintf (stdout,
"<tr>\
<th align=\"right\" valign=\"top\" style=\"padding-right:1em;\">%d.</th>\
<td align=\"left\" valign=\"top\" style=\"padding-right:1em;\">\
<pre style=\"margin:0;\"><u>%s</u>",
               ContactCount,
               HTML_ESCAPE(LDIFcnPtr));

      if (*LDIFdescriptionPtr)
         fprintf (stdout, "\n%s",
                  HTML_ESCAPE(LDIFdescriptionPtr));

      if (*LDIFtelephoneNumberPtr)
         fprintf (stdout, "\n%s: %s",
                  LangFor("contact_phone"),
                  HTML_ESCAPE(LDIFtelephoneNumberPtr));

      if (*LDIFfacsimileTelephoneNumberPtr)
         fprintf (stdout, "\n%s: %s",
                  LangFor("contact_fax"),
                  HTML_ESCAPE(LDIFfacsimileTelephoneNumberPtr));

      if (*LDIFmobilePtr)
         fprintf (stdout, "\n%s: %s",
                  LangFor("contact_mobile"),
                  HTML_ESCAPE(LDIFmobilePtr));

      fprintf (stdout,
"</pre></td>\
<td align=\"left\" valign=\"top\" style=\"padding-right:1em;\">\
<pre style=\"margin:0;\">%s</pre></td>\
<td align=\"left\" valign=\"top\">\
<pre style=\"margin:0;\">%s</pre></td>\
</tr>\n",
               HTML_ESCAPE(LDIFmailPtr),
               HTML_ESCAPE(PostalAddress));
   }

   fprintf (stdout,
"<tr><td colspan=\"4\" class=\"css_hr\" style=\"padding-top:0.8em;\">\
<hr align=\"left\" size=\"1\" noshade \
class=\"css_print_hr\" style=\"width:100%%\"></td></tr>\n\
<tr>\
<th align=\"left\" colspan=\"2\"  class=\"css_footer\">%s</td>\n\
<th align=\"right\" colspan=\"2\" class=\"css_footer\">%s</td>\n\
</tr>\n\
\
</table>\n\
\n\
</body>\n\
</html>\n",
            SOY_MAIL,
            InetMailRfcVmsDate(CurrentVmsTimeString));
}

/*****************************************************************************/
/*
Save the personal contacts to file.
*/

void ContactsSave (REQUEST_DATA *rdptr)

{
   int  status,
        EditNumber;
   char  *cptr, *fnptr, *sptr,
         *ContactCnPtr;
   char  StringBuffer [512];
   FILE  *fp;
   CONTACTS_ENTRY  *ceptr, *neptr;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   fnptr = CallMailFileIn (muptr, NULL, SOY_CONTACTS_LDIF);
   if (WatchEnabled) WatchThis (FI_LI, "CONTACTS !AZ", fnptr);

   fp = fopen (fnptr, "w");
   if (!fp)
   {
      status = vaxc$errno;
      StatusMessage (FI_LI, 1, "%s: %s.", SOY_MAIL, SysGetMsg(status));
      return;
   }

   status = ContactsWrite (rdptr, fp, FALSE);

   fclose (fp);
   if (VMSok (status))
   {
      StatusMessage (FI_LI, 0, LangFor("contact_saved"));
      /* purge the versions back */
      strcat (fnptr, ";-1");
      while (!remove (fnptr));
   }
   else
   {
      StatusMessage (FI_LI, 1, "%s: %s.", SOY_MAIL, SysGetMsg(status));
      /* remove this latest one */
      strcat (fnptr, ";0");
      remove (fnptr);
   }

   /* need to reload after saving */
   if (ContactsLoad (rdptr, NULL, NULL))
      StatusMessage (FI_LI, 0, LangFor("contact_loaded"),
                     rdptr->ContactsLoadCount,
                     rdptr->ContactsErrorCount,
                     rdptr->ContactsUnknownCount);
}

/*****************************************************************************/
/*
Write the personal contacts to the supplied stream, optionally HTML-encoding
the data (for inclusion in a form field).
*/

int ContactsWrite
(
REQUEST_DATA *rdptr,
FILE *fp,
BOOL HtmlEsc
)
{
   int  retval, status;
   char  *cptr, *fnptr, *sptr,
         *AtHost,
         *ContactCnPtr;
   char  StringBuffer [512];
   CONTACTS_ENTRY  *ceptr;

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

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

   CGIVARNULL (AtHost, "HTTP_HOST");
   if (!AtHost) CGIVARNULL (AtHost, "SERVER_NAME");
   if (!AtHost) CGIVARNULL (AtHost, "SERVER_ADDR");
   if (!AtHost) AtHost = "?";

   status = SS$_NORMAL;

   retval = fprintf (fp,
"# soyMAIL contacts in LDIF as at %s\n\
# %s@%s\n\
# [version] %s\n\
#\n",
                     CurrentVmsTimeString,
                     rdptr->UserName, AtHost,
                     SoftwareVn);
   if (retval < 0) status = vaxc$errno;

   for (ceptr = rdptr->ContactsListPtr; ceptr; ceptr = ceptr->NextEntry)
   {
      /* a record with a zero entry number does not get written out */
      if (!ceptr->EntryNumber) continue;

      /* look for additional lines containing the additional addresses */
      for (sptr = ceptr->LDIFmailPtr;
           *sptr && *sptr != '\r' && *sptr != '\n';
           sptr++);
      if (*sptr)
      {
         /* additional, newline separated addresses are a soyMAIL-ism */
         *sptr++ = '\0';
         while (*sptr && isspace(*sptr)) sptr++;
         ceptr->LDIFsoyMAILlistPtr = sptr;
      }

      PrintFao (StringBuffer, sizeof(StringBuffer),
                "cn=!AZ,mail=!AZ",
                HtmlEsc ? HTML_ESCAPE(ceptr->LDIFcnPtr) :
                          ceptr->LDIFcnPtr, 
                HtmlEsc ? HTML_ESCAPE(ceptr->LDIFmailPtr) :
                          ceptr->LDIFmailPtr);
      if (Debug) fprintf (stdout, "|%s|\n", StringBuffer);
      status = ContactsLdifRecord (fp, "dn", StringBuffer, HtmlEsc);
      if (VMSnok (status)) break;

      retval = fprintf (fp,
"objectclass: top\n\
objectclass: person\n\
objectclass: organizationalPerson\n\
objectclass: inetOrgPerson\n");
      if (retval < 0) status = vaxc$errno;
      if (VMSnok (status)) break;

      status = ContactsLdifRecord (fp, "cn", ceptr->LDIFcnPtr, HtmlEsc);
      if (VMSnok (status)) break;

      if (ceptr->LDIFdescriptionPtr && *ceptr->LDIFdescriptionPtr)
      {
         status = ContactsLdifRecord (fp, "description",
                                      ceptr->LDIFdescriptionPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      status = ContactsLdifRecord (fp, "mail", ceptr->LDIFmailPtr, HtmlEsc);
      if (VMSnok (status)) break;

      if (ceptr->LDIFsoyMAILlistPtr && *ceptr->LDIFsoyMAILlistPtr)
      {
         status = ContactsLdifRecord (fp, "soyMAILlist", sptr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFtelephoneNumberPtr && *ceptr->LDIFtelephoneNumberPtr)
      {
         status = ContactsLdifRecord (fp, "telephoneNumber",
                                      ceptr->LDIFtelephoneNumberPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFfacsimileTelephoneNumberPtr &&
          *ceptr->LDIFfacsimileTelephoneNumberPtr)
      {
         status = ContactsLdifRecord (fp, "facsimileTelephoneNumber",
                     ceptr->LDIFfacsimileTelephoneNumberPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFmobilePtr && *ceptr->LDIFmobilePtr)
      {
         status = ContactsLdifRecord (fp, "mobile",
                                      ceptr->LDIFmobilePtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFpostalAddressPtr && *ceptr->LDIFpostalAddressPtr)
      {
         status = ContactsLdifRecord (fp, "postalAddress",
                                      ceptr->LDIFpostalAddressPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFlPtr && *ceptr->LDIFlPtr)
      {
         status = ContactsLdifRecord (fp, "l", ceptr->LDIFlPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFstPtr && *ceptr->LDIFstPtr)
      {
         status = ContactsLdifRecord (fp, "st", ceptr->LDIFstPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFpostalCodePtr && *ceptr->LDIFpostalCodePtr)
      {   status = ContactsLdifRecord (fp, "postalCode",
                                       ceptr->LDIFpostalCodePtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      if (ceptr->LDIFcPtr && *ceptr->LDIFcPtr)
      {
         status = ContactsLdifRecord (fp, "c", ceptr->LDIFcPtr, HtmlEsc);
         if (VMSnok (status)) break;
      }

      /* entry terminating empty line */
      retval = fprintf (fp, "\n");
      if (retval < 0) status = vaxc$errno;
      if (VMSnok (status)) break;
   }

   retval = fprintf (fp, "# end\n");
   if (retval < 0) status = vaxc$errno;

   return (status);
}

/*****************************************************************************/
/*
Append an LDIF record to the contacts file.
Called from the message read page to add a contact name and address derived
from the source address.
*/

void ContactsAppend (REQUEST_DATA *rdptr)

{
   BOOL  NewFile;
   int  retval, status;
   char  *fnptr,
         *LDIFcnPtr,
         *LDIFmailPtr;
   char  StringBuffer [512];
   FILE  *fp;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   CGIVARNULL (LDIFcnPtr, "FORM_MSG_ADD_CONTACT_AS");
   CGIVARNULL (LDIFmailPtr, "FORM_MSG_ADD_CONTACT_FROM");
   if (!LDIFcnPtr || !LDIFmailPtr) ErrorExit (SS$_BUGCHECK, FI_LI);

   fnptr = CallMailFileIn (muptr, NULL, SOY_CONTACTS_LDIF);
   if (WatchEnabled) WatchThis (FI_LI, "CONTACTS !AZ", fnptr);

   fp = fopen (fnptr, "r", "shr=put");
   if (!fp)
      NewFile = TRUE;
   else
   {
      fclose (fp);
      NewFile = FALSE;
   }

   fp = fopen (fnptr, "a");
   if (!fp)
   {
      status = vaxc$errno;
      StatusMessage (FI_LI, 1, "%s: %s.", SOY_MAIL, SysGetMsg(status));
      return;
   }

   status = SS$_NORMAL;

   if (NewFile)
   {
      retval = fprintf (fp, "# soyMAIL contacts as at %s\n# [version] %s\n#\n",
                        CurrentVmsTimeString, SoftwareVn);
      if (retval < 0) status = vaxc$errno;
   }

   if (VMSok (status))
   {
      retval = fprintf (fp, "# appended %s\n", CurrentVmsTimeString);
      if (retval < 0) status = vaxc$errno;
   }

   if (VMSok (status))
   {
      PrintFao (StringBuffer, sizeof(StringBuffer),
                "cn=!AZ,mail=!AZ", LDIFcnPtr, LDIFmailPtr);
      if (Debug) fprintf (stdout, "|%s|\n", StringBuffer);
      status = ContactsLdifRecord (fp, "dn", StringBuffer, FALSE);
   }

   if (VMSok (status))
   {
      retval = fprintf (fp,
"objectclass: top\n\
objectclass: person\n\
objectclass: organizationalPerson\n\
objectclass: inetOrgPerson\n");
      if (retval < 0) status = vaxc$errno;
   }

   if (VMSok (status))
      status = ContactsLdifRecord (fp, "cn", LDIFcnPtr, FALSE);

   if (VMSok (status))
      status = ContactsLdifRecord (fp, "mail", LDIFmailPtr, FALSE);

   if (VMSok (status))
   {
      /* entry terminating empty line */
      retval = fprintf (fp, "\n");
      if (retval < 0) status = vaxc$errno;
   }

   fclose (fp);

   if (VMSok (status))
   {
      /* purge the versions back */
      strcat (fnptr, ";-1");
      while (!remove (fnptr));
      StatusMessage (FI_LI, 0, LangFor("contact_added"));
   }
   else
   {
      /* remove this latest one */
      strcat (fnptr, ";0");
      remove (fnptr);
      StatusMessage (FI_LI, 1, "%s: %s.", SOY_MAIL, SysGetMsg(status));
   }
}

/*****************************************************************************/
/*
Write an LDIF compliant 'name: value' pair.
*/

int ContactsLdifRecord
(
FILE *fp,
char *NamePtr,
char *ValuePtr,
BOOL HtmlEsc
)
{
   int  cnt, retval, status;
   char  *cptr, *sptr;

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

   if (Debug)
      fprintf (stdout, "ContactsLdifRecord() %d |%s|\n", HtmlEsc, NamePtr);

   /* if the first character is acceptable */
   cptr = ValuePtr;
   if (*cptr > 0 && *cptr <= 127 &&
       *cptr != 10 && *cptr != 13 &&
       *cptr != ' ' && *cptr != ':' && *cptr != '<')
   {
      /* have a look through the rest of the string */
      while (*cptr > 0 && *cptr <= 127 &&
             *cptr != 10 && *cptr != 13) cptr++;
      /* snare a trailing space */
      if (*(cptr-1) == ' ') cptr -= 1;
   }

   if (!*cptr)
   {
      /* does not require base64 encoding */
      retval = fprintf (fp, "%s: %s\n",
                        NamePtr, HtmlEsc ? HTML_ESCAPE(ValuePtr) : ValuePtr);
      if (retval >= 0)
         return (SS$_NORMAL);
      else
         return (vaxc$errno);
   }

   /* requires base64 encoding */
   while (*cptr) cptr++;
   ValuePtr = MimeEncBase64 (ValuePtr, cptr - ValuePtr, "");
   if (!ValuePtr) ErrorExit (SS$_BUGCHECK, FI_LI);

   retval = fprintf (fp, "%s:: ", NamePtr);
   cptr = ValuePtr;
   while (*cptr && retval >= 0)
   {
      sptr = cptr;
      for (cnt = 0; cnt < 76 && *cptr; cnt++) cptr++;
      if (cnt)
         retval = fprintf (fp, "%s%*.*s\n",
                           sptr > ValuePtr ? " " : "", cnt, cnt, sptr);
   }
   if (retval >= 0)
      status = SS$_NORMAL;
   else
      status = vaxc$errno;

   /* free the base64 allocated memory (just being neat) */
   CgiLibVeeMemFree (ValuePtr);

   return (status);
}

/*****************************************************************************/
/*
Reset the contact list to empty and load from either state data or on-disk
file.  File can be either LDIF date or VMS type mailing list.  If from
'contacts state' (form field containing previously loaded/edited contact data)
then check for and delete/edit an indicated entry.
*/

BOOL ContactsLoad
(
REQUEST_DATA *rdptr,
char *ContactsList,
char *ContactsState
)
{
   int  EditNumber;
   char  *cptr, *sptr;
   char  EmptyString [2] = "";
   CONTACTS_ENTRY  RemoveEntry;
   CONTACTS_ENTRY  *ceptr;

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

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

   /* empty the list */
   rdptr->ContactsTextPtr = NULL;
   rdptr->ContactsTextLength = 0;
   rdptr->ContactsListPtr = NULL;
   rdptr->ContactsErrorCount = 
      rdptr->ContactsLoadCount =
      rdptr->ContactsUnknownCount = 0;

   if (ContactsState)
   {
      /******************/
      /* contacts state */
      /******************/

      if (WatchEnabled) WatchThis (FI_LI, "CONTACTS state");
      ContactsLoadLdif (rdptr, ContactsState);

      CGIVARNULL (cptr, "FORM_CONTACT_BTN");
      if (LangSame ("contact_delete", cptr))
      {
         /*******************/
         /* delete an entry */
         /*******************/

         EditNumber = 0;
         CGIVARNULL (cptr, "FORM_CONTACT_EDIT_NUMBER");
         if (cptr) EditNumber = atoi(cptr);

         RemoveEntry.EntryNumber = EditNumber;
         ContactsLdifRemove (rdptr, &RemoveEntry);
      }
      else
      {
         EditNumber = 0;
         CGIVARNULL (cptr, "FORM_CONTACT_EDITING_NUMBER");
         if (cptr) EditNumber = atoi(cptr);

         CGIVARNULL (cptr, "FORM_CONTACT_CN_AND_DESCRIPTION");
         if (cptr)
         {
            for (sptr = cptr; *sptr && isspace(*sptr); sptr++);
            if (!*sptr) cptr = NULL;
         }
         if (cptr)
         {
            /****************/
            /* edited entry */
            /****************/

            /* allocate an entry */
            ceptr =  CgiLibVeeMemCalloc (sizeof(CONTACTS_ENTRY));
            if (!ceptr) ErrorExit (vaxc$errno, FI_LI);

            while (*cptr && isspace(*cptr)) cptr++;
            ceptr->LDIFcnPtr = cptr;
            /* look for additional lines containing the entry description */
            for (sptr = cptr; *sptr && *sptr != '\r' && *sptr != '\n'; sptr++);
            if (*sptr == '\r') *sptr++ = '\0';
            if (*sptr)
            {
               *sptr++ = '\0';
               /* any description is line 2 and onwards */
               while (*sptr && isspace(*sptr)) sptr++;
               ceptr->LDIFdescriptionPtr = sptr;
            }

            CGIVARNULL (cptr, "FORM_CONTACT_MAIL");
            if (!cptr) ErrorExit (SS$_BUGCHECK, FI_LI);
            while (*cptr && isspace(*cptr)) cptr++;
            ceptr->LDIFmailPtr = cptr;

            CGIVARNULL (cptr, "FORM_CONTACT_TELEPHONENUMBER");
            if (!cptr) ErrorExit (SS$_BUGCHECK, FI_LI);
            ceptr->LDIFtelephoneNumberPtr = cptr;

            CGIVARNULL (cptr, "FORM_CONTACT_FACSIMILETELEPHONENUMBER");
            if (!cptr) ErrorExit (SS$_BUGCHECK, FI_LI);
            ceptr->LDIFfacsimileTelephoneNumberPtr = cptr;

            CGIVARNULL (cptr, "FORM_CONTACT_MOBILE");
            if (!cptr) ErrorExit (SS$_BUGCHECK, FI_LI);
            ceptr->LDIFmobilePtr = cptr;

            CGIVARNULL (cptr, "FORM_CONTACT_POSTALADDRESS");
            if (!cptr) ErrorExit (SS$_BUGCHECK, FI_LI);
            /* if it's the unmodified template then it's effectively empty */
            if (*cptr == '_') cptr = EmptyString;
            ceptr->LDIFpostalAddressPtr = cptr;
            ceptr->LDIFlPtr = ceptr->LDIFstPtr =
               ceptr->LDIFpostalCodePtr = ceptr->LDIFcPtr = "";

            /* remove that pesky WIN32/DOS carriage-control */
            sptr = cptr;
            while (*cptr)
            {
               while (*cptr == '\r') cptr++;
               if (!*cptr) break;
               *sptr++ = *cptr++;
            }
            *sptr = '\0';

            /* parse this one multi-line text value into components */
            sptr = ceptr->LDIFpostalAddressPtr;
            while (*sptr && *sptr != '\n') sptr++;
            if (*sptr) *sptr++ = '\0';
            /* location (city) */
            ceptr->LDIFlPtr = sptr;
            while (*sptr && *sptr != '\n') sptr++;
            if (*sptr) *sptr++ = '\0';
            /* state */
            ceptr->LDIFstPtr = sptr;
            while (*sptr && *sptr != '\n') sptr++;
            if (*sptr) *sptr++ = '\0';
            /* post/ZIP code */
            ceptr->LDIFpostalCodePtr = sptr;
            while (*sptr && *sptr != '\n') sptr++;
            if (*sptr) *sptr++ = '\0';
            /* country */
            ceptr->LDIFcPtr = sptr;
            while (*sptr && *sptr != '\n') sptr++;
            *sptr = '\0';

            ceptr->LDIFdnPtr =  CgiLibVeeMemCalloc (256);
            PrintFao (ceptr->LDIFdnPtr, 256, "cn=!AZ,mail=!AZ",
                      ceptr->LDIFcnPtr, ceptr->LDIFmailPtr);

            if (EditNumber == 999999)
            {
               /* new or appended entry */
               ContactsLdifAppend (rdptr, ceptr);
               ContactsLdifSort (rdptr);
            }
            else
            if (EditNumber)
            {
               /* existing entry */
               ceptr->EntryNumber = EditNumber;
               ContactsLdifReplace (rdptr, ceptr);
            }
         }
      }
   }
   else
   {
      /*************/
      /* from file */
      /*************/

      ContactsLoadFile (rdptr, ContactsList);
   }

   return (TRUE);
}

/*****************************************************************************/
/*
Populate the contacts list.  The file is either the personal soyMAIL contacts
file or that specified by the contact list CGI variable. If if the file
contents look like LDIF data then the text is parsed into a linked list of data
structures pointing to each entry's components, otherwise it is left as text
and considered to be a VMS mailing list.
*/

int ContactsLoadFile
(
REQUEST_DATA *rdptr,
char *ContactsList
)
{
   int  len, status;
   char  *cptr, *fnptr, *sptr, *zptr;
   char  FileName [256];
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;

   /* if a specific file has been requested and it's not the personal list */
   cptr = ContactsList;
   if (cptr && *cptr && !strsame(cptr,SOY_CONTACT_PERSONAL,-1))
   {
      zptr = (sptr = FileName) + sizeof(FileName)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      fnptr = FileName;
   }
   else
      fnptr = CallMailFileIn (muptr, NULL, SOY_CONTACTS_LDIF);

   if (WatchEnabled) WatchThis (FI_LI, "CONTACTS !AZ", fnptr);

   status = ReadFileIntoMemory (fnptr, &rdptr->ContactsTextPtr,
                                       &rdptr->ContactsTextLength);
   if (VMSok (status))
   {
      /* on the compose page process the flat file rather than linked list */
      if (rdptr->ThisPageIdent != PAGE_COMPOSE)
      {
         /* if it looks like LDIF content */
         if (rdptr->ContactsTextPtr[0] == '#' ||
             strstr (rdptr->ContactsTextPtr, "objectClass:"))
         {
            ContactsLoadLdif (rdptr, rdptr->ContactsTextPtr);
            /* memory is in use so do not free, but indicate not a VMS list */
            rdptr->ContactsTextPtr = NULL;
         }
      }
   }
   else
   if (fnptr == FileName || status != RMS$_FNF)
      StatusMessage (FI_LI, 1, "%s: %s.",
                     LangFor("contacts"), SysGetMsg(status));

   return (status);
}

/*****************************************************************************/
/*
Replace any current list with the entries specified in the supplied LDIF
directives.
*/

void ContactsImportLdif
(
REQUEST_DATA *rdptr,
char *LdifTextPtr
)
{
   int  ContactCount;
   CONTACTS_ENTRY  *ceptr;

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

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

   /* effectively empty the list */
   rdptr->ContactsErrorCount = 
      rdptr->ContactsLoadCount =
      rdptr->ContactsUnknownCount = 0;
   rdptr->ContactsTextPtr = NULL;
   rdptr->ContactsTextLength = 0;
   rdptr->ContactsListPtr = NULL;

   ContactsLoadLdif (rdptr, LdifTextPtr);

   /* just make sure we did import something! */
   ContactCount = 0;
   for (ceptr = rdptr->ContactsListPtr; ceptr; ceptr = ceptr->NextEntry)
      ContactCount++;
   if (!ContactCount) return;

   StatusMessage (FI_LI, 0, LangFor("contact_imported"),
                  rdptr->ContactsLoadCount,
                  rdptr->ContactsErrorCount,
                  rdptr->ContactsUnknownCount);
}

/*****************************************************************************/
/*
Append the entries specified in the supplied LDIF directives to any exisiting
entries in the list.
*/

void ContactsMergeLdif
(
REQUEST_DATA *rdptr,
char *LdifTextPtr
)
{
   /*********/
   /* begin */
   /*********/

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

   /* reset the counters to represent only the merged entries */
   rdptr->ContactsErrorCount = 
      rdptr->ContactsLoadCount =
      rdptr->ContactsUnknownCount = 0;

   ContactsLoadLdif (rdptr, LdifTextPtr);

   StatusMessage (FI_LI, 0, LangFor("contact_merged"),
                  rdptr->ContactsLoadCount,
                  rdptr->ContactsErrorCount,
                  rdptr->ContactsUnknownCount);
}

/*****************************************************************************/
/*
Load the contacts list using LDIF directives contained in the supplied text.
*/

void ContactsLoadLdif
(
REQUEST_DATA *rdptr,
char *LdifTextPtr
)
{
   int  len;
   char  *cptr, *sptr, 
         *NamePtr,
         *ValuePtr;
   CONTACTS_ENTRY  *ceptr;

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

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

   /* allocate an entry */
   ceptr =  CgiLibVeeMemCalloc (sizeof(CONTACTS_ENTRY));
   if (!ceptr) ErrorExit (vaxc$errno, FI_LI);

   while (LdifTextPtr && *LdifTextPtr)
   {
      cptr = ContactsLdifParse (&LdifTextPtr, &NamePtr, &ValuePtr);
      if (!cptr) break;

      if (strsame (NamePtr, "c", -1))   /* country */
         ceptr->LDIFcPtr = ValuePtr;
      else
      if (strsame (NamePtr, "cn", -1))   /* common name */
         ceptr->LDIFcnPtr = ValuePtr;
      else
      if (strsame (NamePtr, "description", -1))   /* free text */
         ceptr->LDIFdescriptionPtr = ValuePtr;
      else
      if (strsame (NamePtr, "dn", -1))   /* distinguished name */
         ceptr->LDIFdnPtr = ValuePtr;
      else
      if (strsame (NamePtr, "facsimileTelephoneNumber", -1))   /* fax */
         ceptr->LDIFfacsimileTelephoneNumberPtr = ValuePtr;
      else
      if (strsame (NamePtr, "givenName", -1))   /* given name */
         ceptr->LDIFgivenNamePtr = ValuePtr;
      else
      if (strsame (NamePtr, "l", -1))   /* location (city) */
         ceptr->LDIFlPtr = ValuePtr;
      else
      if (strsame (NamePtr, "mail", -1))   /* email address */
         ceptr->LDIFmailPtr = ValuePtr;
      else
      if (strsame (NamePtr, "mobile", -1))   /* mobile telephone */
         ceptr->LDIFmobilePtr = ValuePtr;
      else
      if (strsame (NamePtr, "telephoneNumber", -1))   /* fixed telephone */
         ceptr->LDIFtelephoneNumberPtr = ValuePtr;
      else
      if (strsame (NamePtr, "pager", -1))   /* pager number */
         ceptr->LDIFpagerPtr = ValuePtr;
      else
      if (strsame (NamePtr, "postalAddress", -1))   /* street address */
         ceptr->LDIFpostalAddressPtr = ValuePtr;
      else
      if (strsame (NamePtr, "postalCode", -1))   /* post/ZIP code */
         ceptr->LDIFpostalCodePtr = ValuePtr;
      else
      if (strsame (NamePtr, "sn", -1))   /* family name */
         ceptr->LDIFsnPtr = ValuePtr;
      else
      if (strsame (NamePtr, "st", -1))   /* state address */
         ceptr->LDIFstPtr = ValuePtr;
      else
      if (strsame (NamePtr, "soyMAILlist", -1))   /* soyMAIL mailing list */
         ceptr->LDIFsoyMAILlistPtr = ValuePtr;
      else
      if (!strsame (NamePtr, "objectclass", -1) &&
          !strsame (NamePtr, "modifyTimestamp", -1) &&
          !strsame (NamePtr, "objectclass", -1) &&
          !strsame (NamePtr, "workurl", -1) &&
          !strsame (NamePtr, "homeurl", -1) &&
          !strsame (NamePtr, "custom", 6) &&
          !strstr (cptr, "mozilla"))
      {
         /* unknown/non-Mozilla attribute */
         rdptr->ContactsUnknownCount++;
         if (WatchEnabled)
            WatchThis (FI_LI, "LDIF unknown !AZ:!AZ", NamePtr, ValuePtr);
      }

      /* if not the end of the entry */
      if (*LdifTextPtr && *LdifTextPtr != '\n') continue;

      /****************/
      /* end of entry */
      /****************/

      if (!ceptr->LDIFdnPtr)
      {
         rdptr->ContactsErrorCount++;
         memset (ceptr, 0, sizeof(CONTACTS_ENTRY));
         if (WatchEnabled) WatchThis (FI_LI, "LDIF error NO dn:");
         continue;
      }
      if (!ceptr->LDIFcnPtr)
      {
         rdptr->ContactsErrorCount++;
         memset (ceptr, 0, sizeof(CONTACTS_ENTRY));
         if (WatchEnabled) WatchThis (FI_LI, "LDIF error NO cn:");
         continue;
      }
      if (!ceptr->LDIFmailPtr)
      {
         rdptr->ContactsErrorCount++;
         memset (ceptr, 0, sizeof(CONTACTS_ENTRY));
         if (WatchEnabled) WatchThis (FI_LI, "LDIF error NO mail:");
         continue;
      }

      if (ceptr->LDIFsoyMAILlistPtr)
      {
         /* recreate the mailing list by appending it to primary email */
         len = strlen(ceptr->LDIFmailPtr) +
               strlen(ceptr->LDIFsoyMAILlistPtr);
         cptr =  CgiLibVeeMemCalloc (len+2);
         sprintf (cptr, "%s\n%s",
                  ceptr->LDIFmailPtr,
                  ceptr->LDIFsoyMAILlistPtr);
         ceptr->LDIFmailPtr = cptr;
      }

      ContactsLdifAppend (rdptr, ceptr);

/**
      if (WatchEnabled) WatchThis (FI_LI, "LDIF entry !UL", ceptr->EntryNumber);
**/

      /* allocate another entry */
      ceptr =  CgiLibVeeMemCalloc (sizeof(CONTACTS_ENTRY));
      if (!ceptr) ErrorExit (vaxc$errno, FI_LI);
   }

   /* ensure the entries are in order */
   ContactsLdifSort (rdptr);
}

/*****************************************************************************/
/*
Parses the newline delimitted lines of LDIF text, one directive at a time,
into null-terminated LDIF strings with names and values. 
*/

char* ContactsLdifParse
(
char** TextPtrPtr,
char** NamePtrPtr,
char** ValuePtrPtr
)
{
#undef ISLWS
#define ISLWS(ch) (ch == ' ' || ch == '\t')
/* when uploaded from WIN32 each line has <CR><LF> carriage control */
#define ISNL(ch) (ch == '\r' || *cptr == '\n')

   BOOL  Base64Encoded;
   char  *cptr, *sptr,
         *NamePtr,
         *ValuePtr,
         *ErrorPtr;

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

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

   if (!TextPtrPtr) ErrorExit (SS$_BUGCHECK, FI_LI);
   if (!NamePtrPtr) ErrorExit (SS$_BUGCHECK, FI_LI);
   if (!ValuePtrPtr) ErrorExit (SS$_BUGCHECK, FI_LI);

   cptr = *TextPtrPtr;
   if (!*cptr) return (NULL);

   NamePtr = ValuePtr = NULL;
   *NamePtrPtr = NamePtr;
   *ValuePtrPtr = ValuePtr;

   while (*cptr)
   {
      /* ignore leading white-space */
      while (*cptr && (ISLWS(*cptr) || ISNL(*cptr))) cptr++;
      if (!*cptr) break;
      if (*cptr == '#')
      {
         while (*cptr)
         {
            while (*cptr && !ISNL(*cptr)) cptr++;
            while (ISNL(*cptr)) cptr++;
            if (*cptr != ' ') break;
            cptr++;
         }
         continue;
      }
      NamePtr = cptr;
      while (*cptr && *cptr != ':' && !ISNL(*cptr)) cptr++;
      if (*cptr != ':')
      {
         if (WatchEnabled)
            WatchThis (FI_LI, "LDIF PARSE ERROR !#AZ", cptr-NamePtr, NamePtr);
         while (*cptr)
         {
            while (*cptr && !ISNL(*cptr)) cptr++;
            while (ISNL(*cptr)) cptr++;
            if (*cptr != ' ') break;
            cptr++;
         }
         continue;
      }
      /* terminate at the ':' of the name */
      *cptr++ = '\0';
      if (*cptr == ':')
      {
         Base64Encoded = TRUE;
         cptr++;
      }
      else
         Base64Encoded = FALSE;
      /* scan over intervening white-space */
      while (*cptr && ISLWS(*cptr)) cptr++;
      ValuePtr = sptr = cptr;
      while (*cptr)
      {
         while (*cptr && !ISNL(*cptr)) *sptr++ = *cptr++;
         /* when uploaded from WIN32 each line has <CR><LF> carriage control */
         if (*cptr == '\r') cptr++;
         if (*cptr == '\n') cptr++;
         /* ensure we're left pointing at a newline */
         if (*cptr == '\r') cptr++;
         if (*cptr != ' ') break;
         cptr++;
      }
      *sptr = '\0';
      if (Debug) fprintf (stdout, "|%s|\n", ValuePtr);
      if (Base64Encoded) MimeDecBase64 (ValuePtr);
      break;
   }

   *NamePtrPtr = NamePtr;
   *ValuePtrPtr = ValuePtr;
   *TextPtrPtr = cptr;

/**
   if (WatchEnabled && NamePtr && ValuePtr)
      WatchThis (FI_LI, "LDIF !AZ:!AZ", NamePtr, ValuePtr);
**/

   return (NamePtr);
}

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

void ContactsLdifAppend
(
REQUEST_DATA *rdptr,
CONTACTS_ENTRY *nceptr
)
{
   CONTACTS_ENTRY  *lceptr;

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

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

   for (lceptr = rdptr->ContactsListPtr;
        lceptr && lceptr->NextEntry;
        lceptr = lceptr->NextEntry);
   if (lceptr)
   {
      lceptr->NextEntry = nceptr;
      nceptr->PrevEntry = lceptr;
      nceptr->EntryNumber = lceptr->EntryNumber + 1;
   }
   else
   {
      rdptr->ContactsListPtr = nceptr;
      nceptr->EntryNumber = 1;
   }
   nceptr->EntryNumber = ++rdptr->ContactsLoadCount;
}

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

void ContactsLdifRemove
(
REQUEST_DATA *rdptr,
CONTACTS_ENTRY *nceptr
)
{
   CONTACTS_ENTRY  *lceptr;

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

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

   for (lceptr = rdptr->ContactsListPtr; lceptr; lceptr = lceptr->NextEntry)
      if (lceptr->EntryNumber == nceptr->EntryNumber) break;
   if (lceptr)
   {
      if (rdptr->ContactsListPtr == lceptr)
         rdptr->ContactsListPtr = lceptr->NextEntry;
      if (lceptr->NextEntry) lceptr->NextEntry->PrevEntry = lceptr->PrevEntry;
      if (lceptr->PrevEntry) lceptr->PrevEntry->NextEntry = lceptr->NextEntry;
      /* ensure the remaining entries are numbered correctly */
      for (lceptr = lceptr->NextEntry; lceptr; lceptr = lceptr->NextEntry)
         lceptr->EntryNumber--;
      rdptr->ContactsLoadCount--;
   }
}

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

void ContactsLdifReplace
(
REQUEST_DATA *rdptr,
CONTACTS_ENTRY *nceptr
)
{
   CONTACTS_ENTRY  *lceptr;

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

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

   for (lceptr = rdptr->ContactsListPtr; lceptr; lceptr = lceptr->NextEntry)
      if (lceptr->EntryNumber == nceptr->EntryNumber) break;
   if (lceptr)
   {
      /* found, replace it in the linked list */
      nceptr->NextEntry = lceptr->NextEntry;
      nceptr->PrevEntry = lceptr->PrevEntry;
      if (nceptr->NextEntry) nceptr->NextEntry->PrevEntry = nceptr;
      if (nceptr->PrevEntry) nceptr->PrevEntry->NextEntry = nceptr;
      if (rdptr->ContactsListPtr == lceptr) rdptr->ContactsListPtr = nceptr;
   }
}

/*****************************************************************************/
/*
Using a simple bubble sort place the contact list entries in ascending order.
Remove duplicate entries (based on command name).
*/

void ContactsLdifSort (REQUEST_DATA *rdptr)

{
   int  EntryCount;
   CONTACTS_ENTRY  *ceptr1, *ceptr2, *tceptr,
                   *NextEntry1,
                   *PrevEntry1;

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

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

   ceptr1 = rdptr->ContactsListPtr;
   while (ceptr1)
   {
      for (ceptr2 = ceptr1->NextEntry; ceptr2; ceptr2 = ceptr2->NextEntry)
      {
/**
         if (WatchEnabled)
            WatchThis (FI_LI, "SORT |!AZ|!AZ", ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr);
**/

         if (strcmp (ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr) == 0)
         {
            if (WatchEnabled)
               WatchThis (FI_LI, "SORT duplicate |!AZ|!AZ",
                           ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr);

            if (ceptr2->NextEntry)
               ceptr2->NextEntry->PrevEntry = ceptr2->PrevEntry;
            if (ceptr2->PrevEntry)
               ceptr2->PrevEntry->NextEntry = ceptr2->NextEntry;
            continue;
         }

         if (strcmp (ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr) < 0) continue;

/**
         if (WatchEnabled)
            WatchThis (FI_LI, "SORT swap |!AZ|!AZ",
                       ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr);
**/

         if (rdptr->ContactsListPtr == ceptr1) rdptr->ContactsListPtr = ceptr2;

         NextEntry1 = ceptr1->NextEntry;
         PrevEntry1 = ceptr1->PrevEntry;
         ceptr1->NextEntry = ceptr2->NextEntry;
         if (ceptr2->PrevEntry == ceptr1)
            ceptr1->PrevEntry = ceptr2;
         else
            ceptr1->PrevEntry = ceptr2->PrevEntry;
         if (NextEntry1 == ceptr2)
            ceptr2->NextEntry = ceptr1;
         else
            ceptr2->NextEntry = NextEntry1;
         ceptr2->PrevEntry = PrevEntry1;

         if (ceptr1->NextEntry) ceptr1->NextEntry->PrevEntry = ceptr1;
         if (ceptr1->PrevEntry) ceptr1->PrevEntry->NextEntry = ceptr1;
         if (ceptr2->NextEntry) ceptr2->NextEntry->PrevEntry = ceptr2;
         if (ceptr2->PrevEntry) ceptr2->PrevEntry->NextEntry = ceptr2;

         tceptr = ceptr1;
         ceptr1 = ceptr2;
         ceptr2 = tceptr;
         break;
      }

      /* if no entry manipulation occured then go to the next outer entry */
      if (!ceptr2) ceptr1 = ceptr1->NextEntry;
   }

   /* ensure the entries are numbered correctly */
   EntryCount = 0;
   for (tceptr = rdptr->ContactsListPtr; tceptr; tceptr = tceptr->NextEntry)
       tceptr->EntryNumber = ++EntryCount;
}

/*****************************************************************************/
/*
Merely read the LDIF format contacts file and present it as a plain text page.
*/

int ContactsExport (REQUEST_DATA *rdptr)

{
   int  status,
        ContactsLength;
   char  *fnptr,
         *ContactsPtr;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;

   fnptr = CallMailFileIn (muptr, NULL, SOY_CONTACTS_LDIF);

   if (WatchEnabled) WatchThis (FI_LI, "CONTACTS !AZ", fnptr);

   status = ReadFileIntoMemory (fnptr, &ContactsPtr, &ContactsLength);
   if (VMSnok (status))
   {
      StatusMessage (FI_LI, 1, "%s: %s.",
                     LangFor("contacts"), SysGetMsg(status));
      return (FALSE);
   }

   ContactsLoad (rdptr, NULL, ContactsPtr);

   CgiLibResponseHeader (200, "text/plain", "%s", rdptr->LoginSetCookiePtr);

   ContactsWrite (rdptr, stdout, FALSE);

   return (TRUE);
}

/*****************************************************************************/
/*
Try to extract something that can be used as a contact name from the supplied
address.
*/

char* ContactsNameAs (char *AddrPtr)

{
   static char  ContactName [64];

   char  *cptr, *sptr, *zptr;

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

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

   zptr = (sptr = ContactName) + sizeof(ContactName)-1;
   if (AddressIsRfc (AddrPtr))
   {
      /* internet style address */
      for (cptr = AddrPtr; *cptr && *cptr == ' '; cptr++);
      if (*cptr == '\"')
      {
         /* quoted personal name preceded the address */
         cptr++;
         while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++;
      }
      else
      if (strchr (AddrPtr, '<'))
      {
         /* possible unquoted personal name precedes <..> delimitted address */
         while (*cptr && *cptr != '<' && sptr < zptr) *sptr++ = *cptr++;
         *sptr-- = '\0';
         while (sptr > ContactName && *sptr == ' ') sptr--;
         if (*sptr != ' ') sptr++; 
      }
   }
   else
   {
      /* native VMS Mail, look for what might be a personal name */
      for (cptr = AddrPtr; *cptr && *cptr != '\"'; cptr++);
      if (*cptr == '\"')
      {
         /* found a personal name */
         cptr++;
         while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++;
      }
      else
      {
         /* just use the username */
         if (cptr = strstr (AddrPtr, "::")) cptr += 2;
         if (!cptr) cptr = AddrPtr;
         while (*cptr && *cptr != ' ' && sptr < zptr) *sptr++ = *cptr++;
      }
   }
   *sptr = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", ContactName);
   return (ContactName);
}

/*****************************************************************************/
/*
Build the composite postal address from the discrete LDIF attributes.
*/

int ContactsPostalAddress
(
CONTACTS_ENTRY  *ceptr,
char *AddressBuffer,
int SizeofAddressBuffer
)
{
   char  *cptr, *sptr, *zptr;

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

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

   zptr = (sptr = AddressBuffer) + SizeofAddressBuffer - 1;
   if (!(cptr = ceptr->LDIFpostalAddressPtr)) cptr = "";
   while (*cptr && isspace(*cptr)) cptr++;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (!(cptr = ceptr->LDIFlPtr)) cptr = "";
   while (*cptr && isspace(*cptr)) cptr++;
   if (*cptr && sptr < zptr) *sptr++ = '\n';
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (!(cptr = ceptr->LDIFstPtr)) cptr = "";
   while (*cptr && isspace(*cptr)) cptr++;
   if (*cptr && sptr < zptr) *sptr++ = '\n';
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (!(cptr = ceptr->LDIFpostalCodePtr)) cptr = "";
   while (*cptr && isspace(*cptr)) cptr++;
   if (*cptr && sptr < zptr) *sptr++ = '\n';
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (!(cptr = ceptr->LDIFcPtr)) cptr = "";
   while (*cptr && isspace(*cptr)) cptr++;
   if (*cptr && sptr < zptr) *sptr++ = '\n';
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   return (sptr - AddressBuffer);
}

/*****************************************************************************/
/*
For the message composition page generate both the contact list and the list
selector if the logical name is defined.
*/

void ContactsCompose
(
REQUEST_DATA *rdptr,
int ListLength
)
{
   BOOL  Base64Encoded;
   int  idx, lcnt,
        ContactCount,
        MaxLength;
   char  *aptr, *cptr, *sptr, *zptr,
         *CurrentList,
         *ContactsList,
         *ErrorPtr,
         *LdifCnPtr,
         *LdifMailPtr,
         *VmsNamePtr;
   char  ListName [256];
   CONTACTS_ENTRY  *ceptr;

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

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

   ContactCount = MaxLength = 0;

   /* check if the logical name is defined */
   ContactsList = TrnLnm (SOY_CONTACT_LIST, NULL, 0);
   if (ContactsList && ListLength) ListLength--;

   fprintf (stdout,
"<table width=\"100%%\" cellpadding=\"2\" cellspacing=\"0\">\n\
<tr><td>\n\
<select class=\"css_select_1\" name=\"contact_addr\" id=\"contact_addr\" \
size=\"%d\" style=\"min-width:%dem;\" \
onfocus=\"contactListFocus()\">\n\
<option value=\"%s\">%s\n",
            ListLength,
            CONTACTS_LIST_WIDTH,
            HTML_ESCAPE(rdptr->ComposeData.SelfPtr),
            LangFor("contact_self"));

   if (rdptr->ContactsListPtr)
   {
      /****************/
      /* soyMAIL list */
      /****************/

      for (ceptr = rdptr->ContactsListPtr; ceptr; ceptr = ceptr->NextEntry)
         fprintf (stdout, "<option value=\"%s\">%s\n",
                  HTML_ESCAPE(ceptr->LDIFmailPtr),
                  HTML_ESCAPE(ceptr->LDIFcnPtr));
   }
   else
   {
      if (!(cptr = rdptr->ContactsTextPtr)) cptr = "";
      if (strstr (cptr, "\ncn:") &&
          strstr (cptr, "\nmail:"))
      {
         /*************/
         /* LDIF list */
         /*************/

         cptr = ContactsLdifEntry (rdptr->ContactsTextPtr,
                                   rdptr->ContactsTextLength);
         while (cptr = ContactsLdifEntry (NULL, 0))
         {
            /* three null-terminated strings (see ContactsLdifEntry()) */
            ErrorPtr = "";

            if (memcmp (cptr, "cn:", 3)) ErrorExit (SS$_BUGCHECK, FI_LI);
            cptr += 3;
            if (*cptr == ':')
            {
               for (cptr++; isspace(*cptr); cptr++);
               for (LdifCnPtr = cptr; *cptr; cptr++);
               if (MimeDecBase64 (LdifCnPtr) <= 0) ErrorPtr = "ERROR: ";
            }
            else
            {
               while (isspace(*cptr)) cptr++;
               for (LdifCnPtr = cptr; *cptr; cptr++);
            }
            if (cptr - LdifCnPtr > MaxLength) MaxLength = cptr - LdifCnPtr;
            cptr++;

            if (memcmp (cptr, "mail:", 5)) ErrorExit (SS$_BUGCHECK, FI_LI);
            cptr += 5;
            if (*cptr == ':')
            {
               for (cptr++; isspace(*cptr); cptr++);
               for (LdifMailPtr = cptr; *cptr; cptr++);
               if (MimeDecBase64 (LdifMailPtr) <= 0) ErrorPtr = "ERROR: ";
            }
            else
            {
               while (isspace(*cptr)) cptr++;
               for (LdifMailPtr = cptr; *cptr; cptr++);
            }
            cptr++;

            if (!memcmp (cptr, "soyMAILlist:", 12))
            {
               cptr += 12;
               if (*cptr == ':')
               {
                  for (cptr++; isspace(*cptr); cptr++);
                  if (MimeDecBase64 (cptr) <= 0) ErrorPtr = "ERROR: ";
               }
               else
                  while (isspace(*cptr)) cptr++;
            }
            else
               cptr = NULL;

            fprintf (stdout, "<option value=\"%s%s%s\">%s%s\n",
                     HTML_ESCAPE(LdifMailPtr),
                     cptr ? "\n" : "",
                     cptr ? HTML_ESCAPE(cptr) : "",
                     ErrorPtr, HTML_ESCAPE(LdifCnPtr));

            ContactCount++;
         }
      }
      else
      {
         /******************/
         /* VMS-style list */
         /******************/

         while (*cptr)
         {
            VmsNamePtr = cptr;
            while (*cptr == ' ') cptr++;
            while (*cptr && *cptr != '\n') cptr++;
            if (*cptr) *cptr++ = '\0';
            if (!*VmsNamePtr || *VmsNamePtr == '!') continue;

            fprintf (stdout, "<option value=\"%s\">%s\n",
                     HTML_ESCAPE(VmsNamePtr), HTML_ESCAPE(VmsNamePtr));
            ContactCount++;
         }
      }
   }

   if (MaxLength < 15) MaxLength = 15;

   fprintf (stdout,
"</select>\n\
<span id=\"contact_addr_key_span\"></span>\n\
</td></tr>\n\
<input type=\"hidden\" id=\"contact_count\" name=\"contact_count\" \
value=\"%d\">\n\
<script language=\"JavaScript\">\n\
contactListWidth = \"%dem\";\n\
document.getElementById(\'contact_addr\').style.width = contactListWidth;\n\
</script>\n\
<tr><td style=\"white-space:nowrap;padding-right:20px;\">\n",
            ContactCount,
            MaxLength / 2);

   if (!ContactsList)
   {
      /* there is not contact list logical name defined */
      fprintf (stdout,
"<input type=\"hidden\" id=\"contact_list\" \
name=\"contact_list\" value=\"%s\">\n\
</tr></td>\n\
</table>\n",
               SOY_CONTACT_PERSONAL);
      return;
   }

   /* generate the selector for the list of lists */

   CGIVAR (CurrentList, "FORM_CONTACT_LIST");

   fprintf (stdout,
"<select class=\"css_select_1\" style=\"margin-top:2px;\" \
id=\"contact_list\" name=\"contact_list\">\n\
<option value=\"%s\"%s>%s\n",
            SOY_CONTACT_PERSONAL,
            strsame (CurrentList, SOY_CONTACT_PERSONAL, -1) ? " selected" : "",
            LangFor("contact_personal"));

   idx = 1;
   aptr = ContactsList;
   while (aptr)
   {
      for (cptr = aptr; *cptr && *cptr != ']'; cptr++);
      if (!*cptr) for (cptr = aptr; *cptr && *cptr != ':'; cptr++);
      if (*cptr)
      {
         zptr = (sptr = ListName) + sizeof(ListName)-1;
         for (cptr++; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';
         while (sptr > ListName && *sptr != '.') sptr--;
         *sptr = '\0';
         cptr = ListName;
      }
      else
         cptr = aptr;

      fprintf (stdout, "<option value=\"%s\"%s>%s\n",
               HTML_ESCAPE(aptr),
               strsame (aptr, CurrentList, -1) ? " selected" : "",
               HTML_ESCAPE(cptr));

      /* get the next definition (if any) */
      aptr = TrnLnm (SOY_CONTACT_LIST, NULL, idx++);
   }

   fprintf (stdout,
"</select>&nbsp;\
<input class=\"css_button_panel_1\" type=\"submit\" \
name=\"compose_btn\" value=\"%s\">\n\
</td></tr>\n\
</table>\n",
            LangFor("open"));
}

/*****************************************************************************/
/*
This is commandline function designed to read a file containing LDIF content,
parse the cn: and mail: elements, sort them into descending order, and them
write just those to a second file.  This is intended to reduce the load latency
and CPU cycles for large LDIF-based (corporate) contact lists.
*/

int ContactsLdifPurge
(
char *FileNameIn,
char *FileNameOut
)
{
   int  status,
        ContactCount,
        TextLength;
   char  *cptr,
         *LdifCnPtr,
         *LdifMailPtr,
         *TextPtr;
   FILE  *ofp;

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

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

   if (!FileNameIn) exit (229448);
   if (!FileNameOut) FileNameOut = FileNameIn;

   status = ReadFileIntoMemory (FileNameIn, &TextPtr, &TextLength);
   if (VMSnok (status)) exit (status);

   cptr = ContactsLdifEntry (TextPtr, TextLength);
   if (!cptr) return (SS$_NORMAL);

   ofp = fopen (FileNameOut, "w", "rfm=stmlf");
   if (!ofp) return (vaxc$errno);

   fprintf (ofp, "# %s\n# %s\n# %s -> %s\n",
            SoftwareId, CurrentVmsTimeString, FileNameIn, FileNameOut);

   ContactCount = 0;
   while (cptr = ContactsLdifEntry (NULL, 0))
   {
      /* assume it's three null-terminated strings */
      for (LdifCnPtr = cptr; *cptr; cptr++);
      cptr++;
      for (LdifMailPtr = cptr; *cptr; cptr++);
      cptr++;
      if (*cptr)
         fprintf (ofp, "%s\n%s\n%s\n\n", LdifCnPtr, LdifMailPtr, cptr);
      else
         fprintf (ofp, "%s\n%s\n\n", LdifCnPtr, LdifMailPtr);
      ContactCount++;
   }

   fprintf (ofp, "# %d contacts\n", ContactCount);

   fclose (ofp);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
When called with TextPtr and TextLength set to LDIF content parse out the cn:
and mail: (used by the compose page contact dialog) and sort them into
decending order based on the cn: string.

When subsequently called with TextPtr set to NULL the function iterates through
the sorted entries returning one at a time.  The format of the returned entry
comprises three null-terminated strings

   "cn: name\0mail: address\0\0"
or
   "cn: name\0mail: address\0soyMAILlist: address(es)\0"

and this must be parsed as required by the calling routine.  Returns a NULL
when entries exhausted.
*/

char* ContactsLdifEntry
(
char *TextPtr,
int TextLength
)
{
   static int  ContactCount,
               CurrentCount;
   static char  *SortTextPtr;
   static char  **SortArray = NULL;
   static char  ErrorValue [16];

   int  idx, status,
        SortTextLength;
   char  *cptr, *sptr, *stptr, *zptr;
   char  LdifCn [256],
         LdifMail [256],
         LdifSoyMailList [4096];

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

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

   if (!TextPtr)
   {
      /***********/
      /* iterate */
      /***********/

      if (!SortTextPtr) return (NULL);
      if (CurrentCount >= ContactCount)
      {
         /* exhausted */
         CurrentCount = ContactCount = 0;
         CgiLibVeeMemFree (SortTextPtr);
         SortTextPtr = NULL;
         CgiLibVeeMemFree (*(&SortArray));
         SortArray = NULL;
         return (NULL);
      }
      return (SortArray[CurrentCount++]);
   }

   /********/
   /* load */
   /********/

   SortTextLength = TextLength + (TextLength/20);  /* plus 5% */
   SortTextPtr = stptr =  CgiLibVeeMemCalloc (SortTextLength + 32);
   if (!SortTextPtr) exit (vaxc$errno);

   CurrentCount = ContactCount = 0;
   cptr = TextPtr;

   while (*cptr)
   {
      LdifCn[0] = LdifMail[0] = LdifSoyMailList[0] = '\0';

      while (*cptr && (*cptr != '\n' || memcmp(cptr,"\ncn:",4))) cptr++;
      if (!*cptr) break;
      cptr++;
      zptr = (sptr = LdifCn) + sizeof(LdifCn)-1;
      while (*cptr)
      {
         while (*cptr && *cptr != '\n' && sptr < zptr) *sptr++ = *cptr++;
         if (!*cptr || (*(cptr+1) != ' ' && *(cptr+1) != '\t')) break;
         cptr++;
         while (*cptr == ' ' || *cptr == '\t') cptr++;
         continue;
      }
      *sptr = '\0';
      if (Debug) fprintf (stdout, "cn: |%s|\n", LdifCn);

      while (*cptr && (*cptr != '\n' || memcmp(cptr,"\nmail:",6))) cptr++;
      if (!*cptr) break;
      cptr++;
      zptr = (sptr = LdifMail) + sizeof(LdifMail)-1;
      while (*cptr)
      {
         while (*cptr && *cptr != '\n' && sptr < zptr) *sptr++ = *cptr++;
         if (!*cptr || (*(cptr+1) != ' ' && *(cptr+1) != '\t')) break;
         cptr++;
         while (*cptr == ' ' || *cptr == '\t') cptr++;
         continue;
      }
      *sptr = '\0';
      if (Debug) fprintf (stdout, "mail: |%s|\n", LdifMail);

      if (!(LdifCn[0] && LdifMail[0])) continue;

      /* always occurs on the line following mail: */
      if (*(ULONGPTR)cptr == '\nsoy' &&
          !memcmp (cptr, "\nsoyMAILlist:", 13))
      {
         cptr++;
         zptr = (sptr = LdifSoyMailList) + sizeof(LdifSoyMailList)-1;
         while (*cptr)
         {
            while (*cptr && *cptr != '\n' && sptr < zptr) *sptr++ = *cptr++;
            if (!*cptr || (*(cptr+1) != ' ' && *(cptr+1) != '\t')) break;
            cptr++;
            while (*cptr == ' ' || *cptr == '\t') cptr++;
            continue;
         }
         *sptr = '\0';
         if (Debug) fprintf (stdout, "soyMAILlist: |%s|\n", LdifSoyMailList);
      }

      zptr = SortTextPtr + SortTextLength;
      for (sptr = LdifCn; *sptr && stptr < zptr; *stptr++ = *sptr++);
      *stptr++ = '\0';
      for (sptr = LdifMail; *sptr && stptr < zptr; *stptr++ = *sptr++);
      *stptr++ = '\0';
      for (sptr = LdifSoyMailList; *sptr && stptr < zptr; *stptr++ = *sptr++);
      *stptr++ = '\0';
      if (stptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);;
      ContactCount++;
   }
   if (Debug) fprintf (stdout, "%d contacts\n", ContactCount);

   if (!ContactCount)
   {
      ContactCount = 0;
      CgiLibVeeMemFree (SortTextPtr);
      SortTextPtr = NULL;
      return (NULL);
   }

   /*************/
   /* sort list */
   /*************/

   *(&SortArray) = CgiLibVeeMemCalloc (ContactCount * sizeof(char*));
   if (!*(&SortArray)) ErrorExit (vaxc$errno, FI_LI);
   cptr = SortTextPtr;
   for (idx = 0; idx < ContactCount; idx++)
   {
      SortArray[idx] = cptr;
      /* three null-terminated strings */
      while (*cptr) cptr++;
      cptr++;
      while (*cptr) cptr++;
      cptr++;
      while (*cptr) cptr++;
      cptr++;
   }

   /* if it's a result of /LDIF=PURGE assume it doesn't need sorting */
   if (!strncmp (TextPtr, "# SOYMAIL", 9)) return ("");

   qsort (*(&SortArray), ContactCount, sizeof(char*), &ContactsLdifEntryCmp);

   return ("");
}

/*****************************************************************************/
/*
Yes, it's a bit messy!  The arguments are pointers to (pointers to char).
*/

int ContactsLdifEntryCmp
(
const void *ptr1,
const void *ptr2
)
{
   char  *sptr, *str1, *str2, *zptr;
   char  StringOne [256],
         StringTwo [256];

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

   str1 = *(char**)ptr1;
   str2 = *(char**)ptr2;

/**
   if (Debug) fprintf (stdout, "ContactsLdifEntryCmp() |%s|%s|\n", str1, str2);
**/

   /* step over the "cn: " */
   while (*str1 && *str1 != ':') str1++;
   if (*str1 == ':') str1++;
   if (*str1 == ':')
   {
      for (str1++; isspace(*str1); str1++);
      zptr = (sptr = StringOne) + sizeof(StringOne)-1;
      while (*str1 && sptr < zptr) *sptr++ = *str1++;
      *sptr = '\0';
      MimeDecBase64 (str1 = StringOne);
   }
   while (isspace(*str1)) str1++;

   /* step over the "cn: " */
   while (*str2 && *str2 != ':') str2++;
   if (*str2 == ':') str2++;
   if (*str2 == ':')
   {
      for (str2++; isspace(*str2); str2++);
      zptr = (sptr = StringTwo) + sizeof(StringTwo)-1;
      while (*str2 && sptr < zptr) *sptr++ = *str2++;
      *sptr = '\0';
      MimeDecBase64 (str2 = StringTwo);
   }
   while (isspace(*str2)) str2++;

   /* case-insensitive compare */
   while (*str1 && *str2 && tolower(*str1) == tolower(*str2))
   {
      str1++;
      str2++;
   }
   return (tolower(*str1) - tolower(*str2));
}

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

