/*****************************************************************************/ /* 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 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-2024 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 #include #include #include #include #include #include /* VMS related header files */ #include #include #include /* application header file */ #include "soymail.h" #include "address.h" #include "request.h" #include "config.h" #include "contacts.h" #define FI_LI __FILE__, __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, "\n\ \n\
\n\n"); ContactsEdit (rdptr, ImportMerge); fprintf (stdout, "
\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, "\n\ \n\ \ \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, "\n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \ \ \n", EditNumber, EditNumber, HTML_ESCAPE(LDIFcnPtr), *LDIFdescriptionPtr ? " " : "", 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, "\ \ \ \ \ \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, "
\n\ \n\ \ \ \ \ \n\
%s%s%s
\n\
 \ %s:
%s:
%s:
%s:
%s:
 
\n\ %s:  \n\ %s:  \n\ %s:  \n\
%s",
               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,
"
%s
%s
\n\ \n\ \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, "\n\ \ \n\ \ \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, "\n\
\n\  \n\  \n\  \n\  \n\ \ \n\ \n\  \n\  \n\
\ \ %s\
 \n\ \ %s\n\
\n\ \n\ \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, "\n\  \n\  \n\ \ %s\n\ \n\ \n\

\n\ \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\ \n\ \n\ %s\ \n\ \n\ \n", DocType, CgiLibXUACompatible(NULL), SoftwareEnv, CgiEnvironmentPtr, SoftwareCopy); SoyMailStyleStuff (rdptr, "print"); /* setTimeout() gives Firefox 1.5 a chance to render the page */ fprintf (stdout, "%s\n\ \n\n\ \n\ \n\ \n\ \n\ \ \ \ \ \ \n\ \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, "\ \ \ \ \ \n", HTML_ESCAPE(LDIFmailPtr), HTML_ESCAPE(PostalAddress)); } fprintf (stdout, "\n\ \ \n\ \
%s
%s%s%s
\
%d.\
%s",
               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,
"
\
%s
\
%s
\
%s\n\ %s\n\
\n\ \n\ \n\ \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 ("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 ("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 ("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 ("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 ("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 ("LDIF error NO dn:"); continue; } if (!ceptr->LDIFcnPtr) { rdptr->ContactsErrorCount++; memset (ceptr, 0, sizeof(CONTACTS_ENTRY)); if (WatchEnabled) WatchThis ("LDIF error NO cn:"); continue; } if (!ceptr->LDIFmailPtr) { rdptr->ContactsErrorCount++; memset (ceptr, 0, sizeof(CONTACTS_ENTRY)); if (WatchEnabled) WatchThis ("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 ("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 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 ("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 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 ("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 ("SORT |!AZ|!AZ", ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr); **/ if (strcmp (ceptr1->LDIFdnPtr, ceptr2->LDIFdnPtr) == 0) { if (WatchEnabled) WatchThis ("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 ("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 ("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, "\n\ \n\ \n\ \n\ \n\
\n\ \n\ \n\
\n", ContactCount, MaxLength / 2); if (!ContactsList) { /* there is not contact list logical name defined */ fprintf (stdout, "\n\
\n", SOY_CONTACT_PERSONAL); return; } /* generate the selector for the list of lists */ CGIVAR (CurrentList, "FORM_CONTACT_LIST"); fprintf (stdout, " \ \n\ \n\ \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)); } /*****************************************************************************/