/*****************************************************************************/
/*
                                  msg.c

Provide a site-configurable message database.  This is particularly directed
towards non-English language sites.  No attempt has been made to completely
internationalize the server, only messages the average client is likely to be
exposed to are currently provided.

The only way of reloading the database is to restart the server. This is not
of great concern for once customized the messages should remain completely
stable between releases.

When multiple languages are configured the message language processing may be
observed using the WATCH response-header item.


MULTIPLE LANGUAGE SPECIFICATIONS
--------------------------------
Language availability is specified through the use of [Language] directives. 
These must be numbered from 1 to the count of those supplied.  The highest
numbered language must have the complete set of messages for this is the
fallback when obtaining any message (this would normally be 'en').  The
[Language] may be specified as a comma-separated list of equivalent or similar
specifications, which during request processing will be matched against a
client specified list of accepted-languages one at a time in specified order. 
A wildcard may be specified which matches all fitting the template.  In this
manner a single language can be used also to match minor variants or language
specification synonyms.

  [Version]  8.0
  [Language]  1  es,es-ES
  [Language]  2  de,de-*
  [Language]  3  en

In the above (rather contrived) example a client request with

  Accept-Language: es-ES,de;q=0.6,en;q=0.3

would have language 1 selected, a client with

  Accept-Language: de-ch,es;q=0.6,en;q=0.3

language 2 selected, with

  Accept-Language: pt-br,de;q=0.6,en;q=0.3

also language 2 selected, with

  Accept-Language: pt

language 3 (the default) selected, etc.

Note that the messages for each language must use the *first* language
specification provided in the [Language] list.  In the example above all
messages for language 1 would be introduced using 'es', for language 2 with
'de' and for language 3 with 'en'.


ADDITIONAL [LANGUAGE] PARAMETERS
--------------------------------
Two optional parameters can follow the language number and language
specification synonym.  The first is a character set to be associated with the
message set.  This is specified using the keyword 'charset=' followed by the
character set specification.  For example

  [Language]  2  charset=ISO-8859-5

The second optional parameter is a comma-separated list of host domain
specifications that the message set can be associated with.  If there is no
"Accept-Language:" list associated with the request then this information will
be used to select the message set.  For example

  [Language]  2  charset=ISO-8859-5  hosts=cz,cz-*


CURRENT MESSAGE GROUPINGS
-------------------------

  [auth]        authentication/authorization
  [dir]         directory listing
  [general]     messages belonging to no particular module or category
  [htadmin]     authentication database administration
  [http]        http status messages
  [ismap]       image mapping module
  [mapping]     mapping rules
  [proxy]       proxy module
  [put]         PUT/POST module
  [request]     request acceptance and initial parsing
  [script]      DCL/scripting module
  [ssi]         Server Side Includes
  [status]      error and other status reporting
  [upd]         update module


VERSION HISTORY
---------------
05-SEP-2003  MGD  optional 'charset=<string>' [language] parameter
15-AUG-2003  MGD  where CDATA constraints make using &#10; entity impossible
                  use a field name of hidden$lf and &#94; substituted for it
04-APR-2003  MGD  wildcard and comma-separated list of languages
                  can be specified (e.g. "[Language] es-ES,es,es-*")
12-OCT-2002  MGD  refine metacon reporting
31-MAR-2002  MGD  MsgForNoRequest() no longer required
22-JAN-2002  MGD  bugfix; MsgFor() Accept-Lang: comparison (jpp@esme.fr)
29-SEP-2001  MGD  instance support
15-SEP-2001  MGD  meta-config
04-AUG-2001  MGD  support module WATCHing
28-FEB-2001  MGD  OdsLoadTextFile(), OdsParseTextFile(), [IncludeFile]
18-JUN-2000  MGD  add MsgRevise()
08-APR-2000  MGD  HTTP status messages, v7.0
04-MAR-2000  MGD  use WriteFaol(), et.al.
02-JAN-2000  MGD  config file opened via ODS module
04-DEC-1999  MGD  additional messages for v6.1
05-MAY-1999  MGD  proxy messages, v6.0
06-JUL-1998  MGD  bugfix; MsgUnload(), VmFree() of 'HostListPtr'
14-FEB-1998  MGD  message file format changed from v4.4 to v5.0
25-OCT-1997  MGD  changes around MsgFor() when no request structure available
09-AUG-1997  MGD  initial development (HTTPd v4.4)
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

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

/* application related header files */
#include "wasd.h"

#define WASD_MODULE "MSG"

/******************/
/* global storage */
/******************/

#define MSG_VERSION "8.0"

char  MsgNotSet [] = "INTERNAL ERROR, please notify the administrator.";

char  ErrorMsgDupLangNumber [] = "duplicate [language] number",
      ErrorMsgDupMsgNumber [] = "duplicate message number",
      ErrorMsgLangDisabled [] = "[language] disabled",
      ErrorMsgLangName [] = "[language] name not specified",
      ErrorMsgLangNotFound [] = "[language] not found",
      ErrorMsgLangNotSpecified [] = "[language] not specified",
      ErrorMsgLangNumber [] = "[language] number out-of-range",
      ErrorMsgLangTooMany [] = "too many [language]s",
      ErrorMsgMessageNumber [] = "message number out-of-range",
      ErrorMsgNoGroup [] = "[group-name] has not been specified",
      ErrorMsgNoLangNumber [] = "no [language] number",
      ErrorMsgNoLangName [] = "no [language] name",
      ErrorMsgNoMessageNumber [] = "no message number",
      ErrorMsgTooFew [] = "too few messages",
      ErrorMsgTooMany [] = "too many messages",
      ErrorMsgUnknownGroup [] = "unknown [group-name]",
      ErrorMsgVersion [] = "message file [version] mismatch",
      ErrorMsgVersionNotChecked [] = "no [version] for checking";

MSG_META  MsgMeta;
MSG_META  *MsgMetaPtr;

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  HttpdServerStartup;

extern char  ServerHostPort[],
             SoftwareID[],
             Utility[],
             ErrorSanityCheck[];

extern CONFIG_STRUCT  Config;
extern META_CONFIG  *MetaGlobalMsgPtr;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
*/ 
 
int MsgConfigLoad (META_CONFIG **MetaConPtrPtr)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (NULL, FI_LI, WATCH_MOD_MSG,
                 "MsgConfigLoad() !AZ", CONFIG_MSG_FILE_NAME);

   status = MetaConLoad (MetaConPtrPtr, CONFIG_MSG_FILE_NAME,
                         &MsgConfigLoadCallback, false, false);
   if (*MetaConPtrPtr == MetaGlobalMsgPtr)
   {
      /* server startup */
      MetaConStartupReport (MetaGlobalMsgPtr, "MSG");
      if (VMSnok (status)) exit (status);
   }
   return (status);
}

/*****************************************************************************/
/*
Called by MetaConUnload() to free resources allocated during msg
configuration.
*/ 
 
MsgConfigUnload (META_CONFIG *mcptr)

{
   int  Count,
        LanguageCount;
   MSG_META  *mmptr;

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

   if (WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "MsgConfigUnload()");

   mmptr = mcptr->MsgMetaPtr;

   for (LanguageCount = 1;
        LanguageCount <= mmptr->LanguageCount;
        LanguageCount++)
   {
      if (mmptr->Msgs[LanguageCount])
      {
         for (Count = 0; Count <= MSG_RANGE; Count++)
         {
            /* the '\b' backspace is a sentinal, will never occur in a message */
            if (mmptr->Msgs[LanguageCount]->TextPtr[Count] &&
                *((unsigned short*)(mmptr->Msgs[LanguageCount]->TextPtr[Count])) != '\b[')
            {
               /* remember that MsgCreate() returns allocation plus one! */
               VmFree (mmptr->Msgs[LanguageCount]->TextPtr[Count]-1, FI_LI);
            }
         }

         if (mmptr->Msgs[LanguageCount]->CharsetPtr)
            VmFree (mmptr->Msgs[LanguageCount]->CharsetPtr, FI_LI);

         if (mmptr->Msgs[LanguageCount]->HostListPtr)
            VmFree (mmptr->Msgs[LanguageCount]->HostListPtr, FI_LI);

         VmFree (mmptr->Msgs[LanguageCount], FI_LI);
      }
   }

   /* clean it out completely, just in case it's to be reused! */
   memset (mmptr, 0, sizeof(MSG_META));
}

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

BOOL MsgConfigLoadCallback (META_CONFIG *mcptr)

{
   static char  ProblemOverflow [] = "Storage overflow",
                ProblemUsage [] = "Cannot use during service configuration";

   int  status,
        ConfigFileIndex,
        Count,
        MessageNumber;
   char  *cptr, *sptr, *zptr;
   char  LanguageName [128],
         StringBuffer [512];
   MSG_META  *mmptr;
   METACON_LINE  *mclptr;

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

   if (WATCH_MODULE(WATCH_MOD_MSG))
   {
      WatchThis (NULL, FI_LI, WATCH_MOD_MSG,
                 "MsgConfigLoadCallback() !&F !&X",
                 &MsgConfigLoadCallback, mcptr);
      if (WATCH_MODULE(WATCH_MOD__DETAIL))
      {
         mclptr = mcptr->ParsePtr;
         WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z !&Z\n",
            mclptr, mclptr->Size, mclptr->Token, mclptr->Number,
            mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr,
            mclptr->InlineTextPtr);
      }
   }

   /* get a pointer to the current "line" */
   mclptr = mcptr->ParsePtr;

   /* if this is during server startup then set the global msg pointer */
   if (HttpdServerStartup)
      mmptr = mcptr->MsgMetaPtr = MsgMetaPtr = &MsgMeta;
   else
   /* if a report then conjure one from the singularity */
   if (!mcptr->MsgMetaPtr)
      mmptr = mcptr->MsgMetaPtr = VmGet (sizeof(MSG_META));
   else
      /* not the first time through */
      mmptr = mcptr->MsgMetaPtr;

   if (mclptr->Token == METACON_TOKEN_PRE)
   {
      /******************/
      /* pre-initialize */
      /******************/

      mmptr->CheckedLanguages = mmptr->VersionChecked = false;
      mmptr->LanguageNumber = mmptr->MessageCount =
         mmptr->LanguageCount = 0;
      mmptr->MessageBase = -1;

      return (true);
   }

   if (mclptr->Token == METACON_TOKEN_POST)
   {
      /****************/
      /* post-process */
      /****************/

      if (mmptr->MessageCount < MSG_TOTAL)
      {
         if (HttpdServerStartup)
            ErrorExitVmsStatus (0, ErrorMsgTooFew, FI_LI);
         else
            MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgTooFew);
      }
      else
      if (mmptr->MessageCount > MSG_TOTAL)
      {
         /* technically, this shouldn't be possible! */
         if (HttpdServerStartup)
            ErrorExitVmsStatus (0, ErrorMsgTooMany, FI_LI);
         else
            MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgTooMany);
      }
      mmptr->LanguageDefault = mmptr->LanguageCount;

      return (true);
   }

   /* if it's not a msg or text then mapping is not interested in it! */
   if (mclptr->Token != METACON_TOKEN_TEXT)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemUsage);
      return (false);
   }

   /***********/
   /* process */
   /***********/

   /* buffer the text associated with the current "line" */
   zptr = (sptr = StringBuffer) + sizeof(StringBuffer);
   cptr = mclptr->TextPtr;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemOverflow);
      return (false);
   }
   if (sptr > StringBuffer)
   {
      sptr--;
      while (sptr > StringBuffer && ISLWS(*sptr)) sptr--;
      sptr++;
   }
   *sptr = '\0';

   cptr = StringBuffer;

   if (*cptr == '[')
   {
      if (strsame (cptr, "[version]", 9))
      {
         /*************/
         /* [version] */
         /*************/

         cptr += 9;
         while (*cptr && ISLWS(*cptr)) cptr++;
         if (!strsame (cptr, MSG_VERSION, -1))
         {
            if (HttpdServerStartup)
               ErrorExitVmsStatus (0, ErrorMsgVersion, FI_LI);
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgVersion);
               return (false);
            }
         }
         mmptr->VersionChecked = true;
         return (true);
      }

      if (strsame (cptr, "[language]", 10))
      {
         /**************/
         /* [language] */
         /**************/

         /* allow for empty [Language] generated by configuration revise */
         for (sptr = cptr+10; *sptr && !isalnum(*sptr); sptr++);
         if (!*sptr) return (true);

         /* step over the [language] and any trailing white-space */
         cptr += 10;
         while (*cptr && ISLWS(*cptr)) cptr++;

         /* allow a dangling "charset="/"hosts=" for on-line configuration  */
         if (strsame (cptr, "charset=", 8) || strsame (cptr, "hosts=", 6))
            return (true);

         mmptr->LanguageCount++;

         if (WATCH_MODULE(WATCH_MOD_MSG))
            WatchThis (NULL, FI_LI, WATCH_MOD_MSG,
                       "!UL", mmptr->LanguageCount);

         /* check if the number of languages specified exceeds limits */
         if (mmptr->LanguageCount > MAX_LANGUAGES)
         {
            if (HttpdServerStartup)
               ErrorExitVmsStatus (0, ErrorMsgLangTooMany, FI_LI);
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              ErrorMsgLangTooMany);
               return (false);
            }
         }

         if (!isdigit(*cptr))
         {
            /* no language number (order) has been specified */
            if (HttpdServerStartup)
               ErrorExitVmsStatus (0, ErrorMsgNoLangNumber, FI_LI);
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              ErrorMsgNoLangNumber);
               return (false);
            }
         }

         /* language number (order) */
         mmptr->LanguageNumber = atol (cptr);

         /* check if the number of languages specified exceeds limits */
         if (mmptr->LanguageNumber < 0 ||
             mmptr->LanguageNumber > MAX_LANGUAGES)
         {
            if (HttpdServerStartup)
               ErrorExitVmsStatus (0, ErrorMsgLangNumber, FI_LI);
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgLangNumber);
               return (false);
            }
         }

         for (Count = 1; Count <= MAX_LANGUAGES; Count++)
         {
            if (!mmptr->Msgs[Count]) continue;
            if (mmptr->Msgs[Count]->LanguageNumber == mmptr->LanguageNumber)
            {
               if (HttpdServerStartup)
                  ErrorExitVmsStatus (0, ErrorMsgDupLangNumber, FI_LI);
               else
               {
                  MetaConReport (mcptr, METACON_REPORT_ERROR,
                                 ErrorMsgDupLangNumber);
                  return (false);
               }
            }
         }

         /* allocate memory for this new language */
         mmptr->Msgs[mmptr->LanguageNumber] =
            (MSG_STRUCT*)VmGet(sizeof(MSG_STRUCT));

         /* set all the messages in the language to NULL */
         for (Count = 0; Count <= MSG_RANGE; Count++)
            mmptr->Msgs[mmptr->LanguageNumber]->TextPtr[Count] = NULL;

         /* set the language number */
         mmptr->Msgs[mmptr->LanguageNumber]->LanguageNumber =
            mmptr->LanguageNumber;

         /* get the language name */
         while (*cptr && !ISLWS(*cptr)) cptr++;
         while (*cptr && ISLWS(*cptr)) cptr++;
         if (!*cptr)
         {
            if (HttpdServerStartup)
               ErrorExitVmsStatus (0, ErrorMsgNoLangName, FI_LI);
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgNoLangName);
               return (false);
            }
         }
         zptr = (sptr = mmptr->Msgs[mmptr->LanguageNumber]->LanguageList) +
                sizeof(mmptr->Msgs[mmptr->LanguageNumber]->LanguageList);
         while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++;
         if (sptr > zptr) sptr--;
         *sptr = '\0';

         while (*cptr && ISLWS(*cptr)) cptr++;
         if (strsame (cptr, "charset=", 8))
         {
            /* character set for the messages */
            cptr += 8;
            sptr = cptr;
            while (*cptr && !ISLWS(*cptr)) cptr++;
            /* empty charsets are permitted to allow on-line revision */
            if (cptr - sptr)
            {
               zptr = mmptr->Msgs[mmptr->LanguageNumber]->CharsetPtr =
                  VmGet (cptr-sptr+1);
               memcpy (zptr, sptr, cptr-sptr);
               zptr[cptr-sptr] = '\0';
            }
         }

         while (*cptr && ISLWS(*cptr)) cptr++;
         if (*cptr)
         {
            /* trailing host list found */
            if (strsame (cptr, "hosts=", 6)) cptr += 6;
            sptr = cptr;
            while (*cptr) cptr++;
            /* trim trailing white-space */
            if (cptr > sptr) cptr--;
            while (cptr > sptr && ISLWS(*cptr)) cptr--;
            if (cptr > sptr) cptr++;
            zptr = mmptr->Msgs[mmptr->LanguageNumber]->HostListPtr =
               VmGet (cptr-sptr+1);
            memcpy (zptr, sptr, cptr-sptr);
            zptr[cptr-sptr] = '\0';
         }

         if (mmptr->Msgs[mmptr->LanguageNumber]->LanguageNumber == 0)
            MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgLangDisabled);

         /* get (any) primary language from (any) list */
         zptr = (sptr = mmptr->Msgs[mmptr->LanguageNumber]->LanguageName) +
                sizeof(mmptr->Msgs[mmptr->LanguageNumber]->LanguageName)-1;
         for (cptr = mmptr->Msgs[mmptr->LanguageNumber]->LanguageList;
              *cptr && *cptr != ',' && sptr < zptr;
              *sptr++ = *cptr++);
         *sptr = '\0';

         if (WATCH_MODULE(WATCH_MOD_MSG))
            WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL !&Z !&Z !&Z !&Z",
                       mmptr->Msgs[mmptr->LanguageNumber]->LanguageNumber,
                       mmptr->Msgs[mmptr->LanguageNumber]->LanguageName,
                       mmptr->Msgs[mmptr->LanguageNumber]->LanguageList,
                       mmptr->Msgs[mmptr->LanguageNumber]->CharsetPtr,
                       mmptr->Msgs[mmptr->LanguageNumber]->HostListPtr);

         return (true);
      }

      /****************************/
      /* check language numbering */
      /****************************/

      if (!mmptr->CheckedLanguages)
      {
         mmptr->CheckedLanguages = true;
         for (Count = 1; Count <= mmptr->LanguageCount; Count++)
         {
            if (mmptr->Msgs[Count]->LanguageNumber < 0 ||
                mmptr->Msgs[Count]->LanguageNumber > mmptr->LanguageCount)
            {
               if (HttpdServerStartup)
                  ErrorExitVmsStatus (0, ErrorMsgLangNumber, FI_LI);
               else
               {
                  MetaConReport (mcptr, METACON_REPORT_ERROR,
                                 ErrorMsgLangNumber);
                  return (false);
               }
            }
         }
      }

      /****************/
      /* [group-name] */
      /****************/

      /* the '\b' backspace is a sentinal, will never occur in a message */
      if (strsame (cptr, "[auth]", 6))
      {
         mmptr->MessageBase = MSG_AUTH__BASE;
         mmptr->MessageMax = MSG_AUTH__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[auth]";
      }
      else
      if (strsame (cptr, "[dir]", 6))
      {
         mmptr->MessageBase = MSG_DIR__BASE;
         mmptr->MessageMax = MSG_DIR__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[dir]";
      }
      else
      if (strsame (cptr, "[general]", 9))
      {
         mmptr->MessageBase = MSG_GENERAL__BASE;
         mmptr->MessageMax = MSG_GENERAL__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[general]";
      }
      else
      if (strsame (cptr, "[htadmin]", 9))
      {
         mmptr->MessageBase = MSG_HTADMIN__BASE;
         mmptr->MessageMax = MSG_HTADMIN__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[htadmin]";
      }
      else
      if (strsame (cptr, "[http]", 6))
      {
         mmptr->MessageBase = MSG_HTTP__BASE;
         mmptr->MessageMax = MSG_HTTP__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[http]";
      }
      else
      if (strsame (cptr, "[ismap]", 7))
      {
         mmptr->MessageBase = MSG_ISMAP__BASE;
         mmptr->MessageMax = MSG_ISMAP__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[ismap]";
      }
      else
      if (strsame (cptr, "[mapping]", 9))
      {
         mmptr->MessageBase = MSG_MAPPING__BASE;
         mmptr->MessageMax = MSG_MAPPING__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[mapping]";
      }
      else
      if (strsame (cptr, "[proxy]", 7))
      {
         mmptr->MessageBase = MSG_PROXY__BASE;
         mmptr->MessageMax = MSG_PROXY__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[proxy]";
      }
      else
      if (strsame (cptr, "[put]", 5))
      {
         mmptr->MessageBase = MSG_PUT__BASE;
         mmptr->MessageMax = MSG_PUT__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[put]";
      }
      else
      if (strsame (cptr, "[request]", 9))
      {
         mmptr->MessageBase = MSG_REQUEST__BASE;
         mmptr->MessageMax = MSG_REQUEST__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[request]";
      }
      else
      if (strsame (cptr, "[script]", 8))
      {
         mmptr->MessageBase = MSG_SCRIPT__BASE;
         mmptr->MessageMax = MSG_SCRIPT__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[script]";
      }
      else
      if (strsame (cptr, "[ssi]", 5))
      {
         mmptr->MessageBase = MSG_SSI__BASE;
         mmptr->MessageMax = MSG_SSI__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[ssi]";
      }
      else
      if (strsame (cptr, "[status]", 8))
      {
         mmptr->MessageBase = MSG_STATUS__BASE;
         mmptr->MessageMax = MSG_STATUS__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[status]";
      }
      else
      if (strsame (cptr, "[upd]", 5))
      {
         mmptr->MessageBase = MSG_UPD__BASE;
         mmptr->MessageMax = MSG_UPD__MAX;
         mmptr->Msgs[1]->TextPtr[mmptr->MessageBase] = "\b[upd]";
      }
      else
      {
         mmptr->MessageBase = -1;
         if (HttpdServerStartup)
            ErrorExitVmsStatus (0, ErrorMsgUnknownGroup, FI_LI);
         else
            MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgUnknownGroup);
      }

      return (true);
   }

   /***********/
   /* message */
   /***********/

   if (!mmptr->VersionChecked)
   {
      if (HttpdServerStartup)
         ErrorExitVmsStatus (0, ErrorMsgVersionNotChecked, FI_LI);
      else
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        ErrorMsgVersionNotChecked);
         return (false);
      }
   }

   if (!mmptr->LanguageCount)
   {
      if (HttpdServerStartup)
         ErrorExitVmsStatus (0, ErrorMsgLangNotSpecified, FI_LI);
      else
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgLangNotSpecified);
         return (false);
      }
   }

   if (mmptr->MessageBase < 0)
   {
      /* before any [goup-name] has been specified */
      MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgNoGroup);
      return (false);
   }

   if (!isalpha(*cptr))
   {
      /* language name is not being specified at start of line */
      MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgNoLangName);
      return (false);
   }

   zptr = (sptr = LanguageName) + sizeof(LanguageName)-1;
   while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   /* find the index for the specified (perhaps leading) language */
   for (Count = 1; Count <= mmptr->LanguageCount; Count++)
   {
      if (WATCH_MODULE(WATCH_MOD_MSG))
         WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL !&Z !&Z",
                    Count, LanguageName,
                    mmptr->Msgs[mmptr->LanguageCount]->LanguageName);
      if (strsame (LanguageName, mmptr->Msgs[Count]->LanguageName, -1))
      {
          mmptr->LanguageNumber = Count;
          break;
      }
   }
   if (Count > mmptr->LanguageCount)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgLangNotFound);
      return (false);
   }

   while (*cptr && !ISLWS(*cptr)) cptr++;
   while (*cptr && ISLWS(*cptr)) cptr++;

   if (!isdigit(*cptr))
   {
      /* no message number has been specified */
      MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgNoMessageNumber);
      return (false);
   }

   MessageNumber = atol(cptr);
   if (WATCH_MODULE(WATCH_MOD_MSG))
       WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL", MessageNumber);

   /* zero disables a message or whole language */
   if (!MessageNumber || !mmptr->Msgs[mmptr->LanguageNumber]->LanguageNumber)
      return (true);

   if (MessageNumber < 0 || MessageNumber > mmptr->MessageMax)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgMessageNumber);
      return (false);
   }

   MessageNumber += mmptr->MessageBase;
   if (WATCH_MODULE(WATCH_MOD_MSG))
       WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL", MessageNumber);

   while (*cptr && !ISLWS(*cptr)) cptr++;
   while (*cptr && ISLWS(*cptr)) cptr++;

   /* empty message (from configuration load) */
   if (*cptr == '\0') return (true);

   /* deliberately empty message */
   if (*(unsigned short*)cptr == '#\0') cptr++;

   if (!mmptr->Msgs[mmptr->LanguageNumber]->TextPtr[MessageNumber])
   {
      mmptr->Msgs[mmptr->LanguageNumber]->TextPtr[MessageNumber] =
         MsgCreate (cptr);
      if (mmptr->LanguageNumber == mmptr->LanguageCount) mmptr->MessageCount++;
   }
   else
      MetaConReport (mcptr, METACON_REPORT_ERROR, ErrorMsgDupMsgNumber);

   return (true);
}

/*****************************************************************************/
/*
Allocate dynamic memory for the text of the message, copy it into it and
return a pointer to it.  The MapUrl.c module (for horrible, historical reasons)
requires a leading null character before the message.  Fudge this by creating
a one character longer string with that leading null and returning a pointer to
the first character.  The MapUrl.c module will the just use the returned
pointer minus one! (Neat huh?  Well it works anyway!)
*/

char* MsgCreate (char *Text)

{
   char  *MsgPtr;

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

   if (WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "MsgCreate() !&Z", Text);

   MsgPtr = VmGet (strlen(Text)+2);
   *MsgPtr = '\0';
   strcpy (MsgPtr+1, Text);
   return (MsgPtr+1);
}

/*****************************************************************************/
/*
Return a pointer to a character string for the message number supplied in
'Message'.  If multiple languages are in use then use any supplied client list
of accepted languages ("Accept-Language:" request header field) to see if the
message can be supplied in a prefered language.  If none supplied or if none
match then check if the language has geographical information against it (a
list of host/domain specifications that can be used to determine if a specific
language would be more appropriate.  If none of the "hit" then return the
configuration-prefered language.
*/

char* MsgFor
(
REQUEST_STRUCT *rqptr,
int Message
)
{
   BOOL  LangMatch;
   int  Count,
        Language;
   char  *cptr, *lptr, *sptr, *tptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MSG, "MsgFor() !UL", Message);

   if (!rqptr)
   {
      /* no request structure, use the default language */
      Language = MsgMeta.LanguageDefault;
   }
   else
   if (rqptr->MsgLanguage)
   {
      /* request message language has previously been set */
      Language = rqptr->MsgLanguage;
   }
   else
   if (MsgMeta.LanguageCount == 1)
   {
      /* only one language in use, use it! */
      Language = rqptr->MsgLanguage = MsgMeta.LanguageDefault;
   }
   else
   {
      if (rqptr->rqHeader.AcceptLangPtr)
      {
         /*******************************************/
         /* look for the client's prefered language */
         /*******************************************/

         lptr = rqptr->rqHeader.AcceptLangPtr;
         while (*lptr)
         {
            for (Count = 1; Count <= MsgMeta.LanguageCount; Count++)
            {
               sptr = MsgMeta.Msgs[Count]->LanguageList;
               /* can be in the comma-separated form "es-ES,es,es-*" */
               while (*sptr)
               {
                  if (WATCH_MODULE(WATCH_MOD_MSG))
                     WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL !&Z !&Z",
                                Count, sptr, lptr);

                  /* compare to this language specified by the client */
                  tptr = sptr;
                  cptr = lptr;
                  while (*cptr && toupper(*cptr) == toupper(*sptr) &&
                         *cptr != ';' && *cptr != ',' && !ISLWS(*cptr))
                  {
                     cptr++;
                     sptr++;
                  }
                  LangMatch = true;
                  if (*sptr && *sptr != ',' && !ISLWS(*sptr) && *sptr != '*')
                     LangMatch = false;
                  else
                  if (*sptr != '*' &&
                      *cptr && *cptr != ';' && *cptr != ',' && !ISLWS(*cptr))
                     LangMatch = false;
                  if (!LangMatch)
                  {
                     while (*sptr && *sptr != ',') sptr++;
                     while (*sptr && (*sptr == ',' || ISLWS(*sptr))) sptr++;
                     continue;
                  }

                  /*********/
                  /* match */
                  /*********/

                  if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
                  {
                     while (*sptr && *sptr != ',') sptr++;
                     WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER,
                        "LANG !UL:!AZ (\"!#AZ\" in \"!AZ\")",
                         MsgMeta.Msgs[Count]->LanguageNumber,
                         MsgMeta.Msgs[Count]->LanguageList,
                         sptr-tptr, tptr, rqptr->rqHeader.AcceptLangPtr);
                  }

                  Language = rqptr->MsgLanguage =
                     MsgMeta.Msgs[Count]->LanguageNumber;
                  break;
               }
               if (rqptr->MsgLanguage) break;
            }
            /* if we've found one */
            if (rqptr->MsgLanguage) break;

            /* step to the next language (if any) in the client list */
            while (*lptr && *lptr != ',' && !ISLWS(*lptr)) lptr++;
            while (*lptr && (*lptr == ',' || ISLWS(*lptr))) lptr++;
         }
      }

      if (!rqptr->MsgLanguage)
      {
         /************************/
         /* look for a host list */
         /************************/

         for (Count = 1; Count <= MsgMeta.LanguageCount; Count++)
         {
            if (WATCH_MODULE(WATCH_MOD_MSG))
               WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL !&Z",
                          Count, MsgMeta.Msgs[Count]->LanguageList);

            if (MsgMeta.Msgs[Count]->HostListPtr)
            {
               if (MsgInHostList (rqptr, MsgMeta.Msgs[Count]->HostListPtr))
               {
                  if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
                     WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER,
                                "LANG !UL:!AZ (!AZ)",
                                MsgMeta.Msgs[Count]->LanguageNumber,
                                MsgMeta.Msgs[Count]->LanguageList,
                                MsgMeta.Msgs[Count]->HostListPtr);

                  Language = rqptr->MsgLanguage =
                     MsgMeta.Msgs[Count]->LanguageNumber;
                  break;
               }
            }
         }
      }

      /* if none matching then fall back to the default language */
      if (!rqptr->MsgLanguage)
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
             WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER,
                        "LANG !UL:!AZ (default)",
                        MsgMeta.Msgs[MsgMeta.LanguageDefault]->LanguageNumber,
                        MsgMeta.Msgs[MsgMeta.LanguageDefault]->LanguageList);

         Language = rqptr->MsgLanguage = MsgMeta.LanguageDefault;
      }
   }

   if (Message <= 0 || Message > MSG_RANGE ||
       Language < 1 || Language > MsgMeta.LanguageCount)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /*******************/
   /* get the message */
   /*******************/

   /* if a message has been assigned then return it */
   if (cptr = MsgMeta.Msgs[Language]->TextPtr[Message])
   {
      if (rqptr && MsgMeta.Msgs[Language]->CharsetPtr)
         rqptr->rqResponse.MsgCharsetPtr = MsgMeta.Msgs[Language]->CharsetPtr;
      if (MsgMeta.LanguageCount == 1) return (cptr);
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
      {
         WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER, "LANG !UL:!AZ",
                    Language, MsgMeta.Msgs[Language]->LanguageList);
         WatchDataFormatted ("!AZ\n", cptr);
      }
      return (cptr);
   }

   /*********************************/
   /* fallback to the base language */
   /*********************************/

   if (cptr = MsgMeta.Msgs[MsgMeta.LanguageCount]->TextPtr[Message])
   {
      if (rqptr && MsgMeta.Msgs[MsgMeta.LanguageCount]->CharsetPtr)
         rqptr->rqResponse.MsgCharsetPtr =
            MsgMeta.Msgs[MsgMeta.LanguageCount]->CharsetPtr;
      if (MsgMeta.LanguageCount == 1) return (cptr);
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
      {
         WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER, "LANG !UL:!AZ",
                    MsgMeta.LanguageCount,
                    MsgMeta.Msgs[MsgMeta.LanguageCount]->LanguageList);
         WatchDataFormatted ("!AZ\n", cptr);
      }
      return (cptr);
   }

   /**************************************/
   /* no message was set for this event! */
   /**************************************/

   if (WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!UL !&Z", Language, MsgNotSet);
   return (MsgNotSet);
}

/*****************************************************************************/
/*
If the client's IP host name or address matches the wildcard string in
'HostList' then return true, else return false.
*/ 

BOOL MsgInHostList
(
REQUEST_STRUCT *rqptr,
char *HostList
)
{
   char  ch;
   char  *cptr, *hptr, *sptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MSG,
                 "MsgInHostList() !&Z !&Z !&Z",
                 rqptr->rqClient.Lookup.HostName,
                 rqptr->rqClient.IpAddressString,
                 HostList);

   hptr = HostList;
   while (*hptr)
   {
      while (*hptr && (*hptr == ',' || ISLWS(*hptr))) hptr++;
      sptr = hptr;
      while (*hptr && *hptr != ',' && !ISLWS(*hptr)) hptr++;
      ch = *hptr;
      *hptr = '\0';
      /* match against host address or name */
      if (isdigit(*sptr))
         cptr = rqptr->rqClient.IpAddressString;
      else
         cptr = rqptr->rqClient.Lookup.HostName;
      if (WATCH_MODULE(WATCH_MOD_MSG))
          WatchThis (NULL, FI_LI, WATCH_MOD_MSG, "!&Z !&Z", cptr, sptr);
      if (StringMatch (rqptr, cptr, sptr))
      {
         *hptr = ch;
         return (true);
      }
      *hptr = ch;
      if (*hptr) hptr++;
   }

   return (false);
}                             

/*****************************************************************************/
/*
A server administration report on the server's configuration. This function
just wraps the reporting function, loading a temporary database if necessary
for reporting from the configuration file.
*/ 

MsgConfigReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
BOOL UseServerDatabase
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MSG,
                 "MsgConfigReport() !&F !&A !UL",
                 &MsgConfigReport, NextTaskFunction, UseServerDatabase);

   if (UseServerDatabase)
      MsgConfigReportNow (rqptr, MetaGlobalMsgPtr);
   else
   {
      status = MsgConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         MsgConfigReportNow (rqptr, mcptr);
      MetaConUnload (&mcptr, NULL);
   }

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
A server administration report on the message database.
*/ 

MsgConfigReportNow
(
REQUEST_STRUCT *rqptr,
META_CONFIG *mcptr
)
{
   static char  LanguageTable [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>Languages</TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=0>\n\
<TR>\
<TH ALIGN=left><U>Order</U>&nbsp;</TH>\
<TH ALIGN=left><U>Language</U>&nbsp;</TH>\
<TH ALIGN=left><U>Charset</U>&nbsp;</TH>\
<TH ALIGN=left><U>Host List</U></TH>\
</TR>\n";

   static char  EndLanguageTable [] =
"</TABLE>\n\
</TD></TR>\n\
</TABLE>\n";

   static char  OneLanguageFao [] =
"<TR>\
<TD ALIGN=left>!UL&nbsp;</TD>\
<TD ALIGN=left>!AZ&nbsp;</TD>\
<TD ALIGN=left>!AZ&nbsp;</TD>\
<TD ALIGN=left>!AZ&nbsp;</TD>\
</TR>\n";

   static char  GroupFao [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH><FONT SIZE=+1>!AZ</FONT></TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=2 BORDER=0>\n";

   static char  MessageFao [] =
"<TR>!&@<TH VALIGN=top ALIGN=left>!&;AZ&nbsp;&nbsp;</TH><TD>!&@</TD></TR>\n";
   
   char  EndOfGroup [] =
"</TABLE>\n\
</TD></TR>\n\
</TABLE>";

   char  EndOfPage [] =
"</TABLE>\n\
</BODY>\n\
</HTML>\n";

   BOOL  MultipleLines;
   int  status,
        GroupCount,
        Count,
        Language,
        RowCount;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr;
   MSG_META  *mmptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MSG, "MsgConfigReportNow()");

   /* get a pointer to the meta-config data */
   mmptr = mcptr->MsgMetaPtr;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Server Messages");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalMsgPtr);
   AdminMetaConSource (rqptr, mcptr, MetaGlobalMsgPtr, mcptr->IncludeFile);

   /********************/
   /* language summary */
   /********************/

   status = NetWriteFaol (rqptr, LanguageTable, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   for (Count = 1; Count <= mmptr->LanguageCount; Count++)
   {
      vecptr = FaoVector;
      *vecptr++ = Count;
      *vecptr++ = mmptr->Msgs[Count]->LanguageList;
      if (mmptr->Msgs[Count]->CharsetPtr)
         *vecptr++ = mmptr->Msgs[Count]->CharsetPtr;
      else
         *vecptr++ = "<I>(default)</I>";
      if (mmptr->Msgs[Count]->HostListPtr)
         *vecptr++ = mmptr->Msgs[Count]->HostListPtr;
      else
         *vecptr++ = "<I>(none)</I>";

      status = NetWriteFaol (rqptr, OneLanguageFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   status = NetWriteFaol (rqptr, EndLanguageTable, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   /*****************************/
   /* loop through all messages */
   /*****************************/

   Count = GroupCount = 0;

   for (Count = 0; Count < MSG_RANGE; Count++)
   {
      if (mmptr->Msgs[1]->TextPtr[Count])
      {
         /* the '\b' backspace is a sentinal, will never occur in a message */
         if (*((unsigned short*)(mmptr->Msgs[1]->TextPtr[Count])) == '\b[')
         {
            if (GroupCount++)
            {
               status = NetWriteFaol (rqptr, EndOfGroup, NULL);
               if (VMSnok (status))
                  ErrorNoticed (status, "NetWriteFaol()", FI_LI);
            }

            vecptr = FaoVector;
            *vecptr++ = mmptr->Msgs[1]->TextPtr[Count]+1;

            status = NetWriteFaol (rqptr, GroupFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

            Count++;
            mmptr->MessageCount = 0;
         }
      }

      RowCount = 0;
      for (Language = 1; Language <= mmptr->LanguageCount; Language++)
         if (mmptr->Msgs[Language]->TextPtr[Count]) RowCount++;

      for (Language = 1; Language <= mmptr->LanguageCount; Language++)
      {
         if (!mmptr->Msgs[Language]->TextPtr[Count]) continue;

         /* '*cptr' will not be a pointer to a null character if linefeeds */
         for (cptr = mmptr->Msgs[Language]->TextPtr[Count];
              *cptr && *cptr != '\n';
              cptr++);
         if (*cptr)
            MultipleLines = true;
         else
            MultipleLines = false;

         vecptr = FaoVector;

         if (RowCount > 1)
         {
            *vecptr++ = "<TH VALIGN=top ROWSPAN=!UL>!2ZL&nbsp;&nbsp;</TH>";
            *vecptr++ = RowCount;
            *vecptr++ = ++mmptr->MessageCount;
            RowCount = 0;
         }
         else
         if (RowCount == 1)
         {
            *vecptr++ = "<TH VALIGN=top>!2ZL&nbsp;&nbsp;</TH>";
            *vecptr++ = ++mmptr->MessageCount;
         }
         else
            *vecptr++ = "";

         *vecptr++ = mmptr->Msgs[Language]->LanguageName;
         if (MultipleLines)
            *vecptr++ = "<PRE>!&;AZ</PRE>";
         else
            *vecptr++ = "<TT>!&;AZ</TT>";
         *vecptr++ = mmptr->Msgs[Language]->TextPtr[Count];

         status = NetWriteFaol (rqptr, MessageFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }
   }

   /**************/
   /* end report */
   /**************/

   status = NetWriteFaol (rqptr, EndOfPage, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
}

/*****************************************************************************/
/*
A server administration menu for message configuration. This function just
wraps the revision function, loading a temporary database if necessary for
reporting from the message configuration file.
*/ 

MsgConfigRevise
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
BOOL UseServerDatabase
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MSG,
                 "MsgConfigRevise() !&F !&A !UL",
                 &MsgConfigRevise, NextTaskFunction, UseServerDatabase);

   if (UseServerDatabase)
      MsgConfigReviseNow (rqptr, MetaGlobalMsgPtr);
   else
   {
      status = MsgConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         MsgConfigReviseNow (rqptr, mcptr);
      MetaConUnload (&mcptr, NULL);
   }

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
A server administration revision for the message database.
*/ 

MsgConfigReviseNow
(
REQUEST_STRUCT *rqptr,
META_CONFIG *mcptr
)
{
   static char  LanguageTable [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>Languages</TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=0>\n\
<TR><TH ALIGN=left><U>Order</U></TH>\
<TH ALIGN=left><U>Language</U></TH>\
<TH ALIGN=left><U>Charset</U></TH>\
<TH ALIGN=left><U>Host List</U></TH></TR>\n\
<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#94;[Version]  !AZ\">\n";

   static char  EndLanguageTable [] =
"<TR><TD COLSPAN=3>\n\
<FONT COLOR=\"#ff0000\"><B>IMPORTANT:</B>&nbsp;\n\
<I>The primary language (that with <U>all</U> message texts complete \
- usually &quot;en&quot;) must be the <U>highest numbered</U> (order) \
language.\n\
Failure to ensure this will render the server unable to start!!</I>\n\
</FONT>\n\
</TD></TR>\n\
</TABLE>\n</TD></TR>\n</TABLE>\n";

   static char  OneLanguageFao [] =
"<TR><TD>\n\
<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#94;[Language]&#32;&#32;\">\n\
<INPUT TYPE=text size=3 NAME=Order VALUE=\"!&@\">\n\
</TD><TD>\n\
<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#32;&#32;\">\n\
<INPUT TYPE=text size=25 NAME=Language VALUE=\"!AZ\">\n\
</TD><TD>\n\
<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#32;&#32;charset&#61;\">\n\
<INPUT TYPE=text size=25 NAME=Charset VALUE=\"!AZ\">\n\
</TD><TD>\n\
<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#32;&#32;hosts&#61;\">\n\
<INPUT TYPE=text size=35 NAME=HostList VALUE=\"!AZ\">\n\
</TD></TR>\n";

   static char  GroupFao [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#94;&#94;!AZ&#94;\">\n\
<TR><TH><FONT SIZE=+1>!AZ</FONT></TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=0>\n";

   static char  MessageFao [] =
"<TR><TD VALIGN=top>!2ZL&nbsp;<TD VALIGN=top>!&;AZ&nbsp;</TD><TD>!&@</TD></TR>\n";

   char  EndOfGroup [] = "</TABLE>\n</TD></TR>\n</TABLE>";

   int  status,
        GroupCount,
        Count,
        Language,
        LineCount;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr, *sptr, *zptr,
         *MsgGroupPtr,
         *MsgTextPtr;
   char  MultiLineBuffer [2048];
   MSG_META  *mmptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_MSG))
      WatchThis (rqptr, FI_LI, WATCH_MOD_MSG, "MsgConfigReviseNow()");

   /* get a pointer to the meta-config data */
   mmptr = mcptr->MsgMetaPtr;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Server Messages");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalMsgPtr);
   AdminMetaConSource (rqptr, mcptr, MetaGlobalMsgPtr, mcptr->IncludeFile);

   AdminMetaConBeginUpdateForm (rqptr);

   /********************/
   /* language summary */
   /********************/

   vecptr = FaoVector;
   *vecptr++ = MSG_VERSION;

   status = NetWriteFaol (rqptr, LanguageTable, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   for (Count = 1; Count <= mmptr->LanguageCount; Count++)
   {
      vecptr = FaoVector;
      *vecptr++ = "!UL";
      *vecptr++ = Count;
      *vecptr++ = mmptr->Msgs[Count]->LanguageList;
      if (mmptr->Msgs[Count]->CharsetPtr)
         *vecptr++ = mmptr->Msgs[Count]->CharsetPtr;
      else
         *vecptr++ = "";
      if (mmptr->Msgs[Count]->HostListPtr)
         *vecptr++ = mmptr->Msgs[Count]->HostListPtr;
      else
         *vecptr++ = "";

      status = NetWriteFaol (rqptr, OneLanguageFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }
   vecptr = FaoVector;
   *vecptr++ = "";
   *vecptr++ = "";
   *vecptr++ = "";
   *vecptr++ = "";
   status = NetWriteFaol (rqptr, OneLanguageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   status = NetWriteFaol (rqptr, EndLanguageTable, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   /*****************************/
   /* loop through all messages */
   /*****************************/

   Count = GroupCount = 0;

   for (Count = 0; Count < MSG_RANGE; Count++)
   {
      if (mmptr->Msgs[1]->TextPtr[Count])
      {
         /* the '\b' backspace is a sentinal, will never occur in a message */
         if (*((unsigned short*)(mmptr->Msgs[1]->TextPtr[Count])) == '\b[')
         {
            if (GroupCount++)
            {
               status = NetWriteFaol (rqptr, EndOfGroup, NULL);
               if (VMSnok (status))
                  ErrorNoticed (status, "NetWriteFaol()", FI_LI);
            }

            vecptr = FaoVector;
            *vecptr++ = MsgGroupPtr = mmptr->Msgs[1]->TextPtr[Count]+1;
            *vecptr++ = mmptr->Msgs[1]->TextPtr[Count]+1;

            status = NetWriteFaol (rqptr, GroupFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

            Count++;
            mmptr->MessageCount = 0;
         }
      }

      mmptr->MessageCount++;
      for (Language = 1; Language <= mmptr->LanguageCount; Language++)
      {
         if (!mmptr->Msgs[Language]->TextPtr[Count])
         {
            MsgTextPtr = "";
            /* fall back to the size of the message in the base language */
            cptr = mmptr->Msgs[mmptr->LanguageCount]->TextPtr[Count];
         }
         else
            MsgTextPtr = cptr = mmptr->Msgs[Language]->TextPtr[Count];

         /* count number of lines in message */
         LineCount = 1;
         for ( /*above*/ ; *cptr; cptr++) if (*cptr == '\n') LineCount++;

         vecptr = FaoVector;

         *vecptr++ = mmptr->MessageCount;
         *vecptr++ = mmptr->Msgs[Language]->LanguageName;

         if (LineCount > 1)
         {
            /* add a '\' (line continuation) character at each end-of-line */
            cptr = MsgTextPtr;
            zptr = (sptr = MultiLineBuffer) + sizeof(MultiLineBuffer);
            while (*cptr && sptr < zptr)
            {
               if (*cptr == '\n' && sptr < zptr) *sptr++ = '\\';
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            if (sptr >= zptr)
            {
               ErrorGeneralOverflow (rqptr, FI_LI);
               break;
            }
            if (sptr > MultiLineBuffer) sptr--;
            while (sptr > MultiLineBuffer &&
                   (ISLWS(*sptr) || *sptr == '\n' || *sptr == '\\')) sptr--;
            if (sptr > MultiLineBuffer) sptr++;
            *sptr = '\0';

            *vecptr++ =
"<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#94;!AZ !2ZL  \">\n\
<TEXTAREA NAME=\"!AZ!AZ!2ZL\" ROWS=!UL COLS=60>!&;AZ</TEXTAREA>";
            *vecptr++ = mmptr->Msgs[Language]->LanguageList;
            *vecptr++ = mmptr->MessageCount;
            *vecptr++ = MsgGroupPtr;
            *vecptr++ = mmptr->Msgs[Language]->LanguageList;
            *vecptr++ = mmptr->MessageCount;
            *vecptr++ = LineCount;
            *vecptr++ = MultiLineBuffer;
         }
         else
         {
            *vecptr++ =
"<INPUT TYPE=hidden name=hidden$lf VALUE=\"&#94;!AZ !2ZL  \">\n\
<INPUT NAME=\"!AZ!AZ!2ZL\" TYPE=text SIZE=60 VALUE=\"!&;AZ\">";
            *vecptr++ = mmptr->Msgs[Language]->LanguageList;
            *vecptr++ = mmptr->MessageCount;
            *vecptr++ = MsgGroupPtr;
            *vecptr++ = mmptr->Msgs[Language]->LanguageList;
            *vecptr++ = mmptr->MessageCount;
            *vecptr++ = MsgTextPtr;
         }

         status = NetWriteFaol (rqptr, MessageFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }
   }

   /**************/
   /* end revise */
   /**************/

   status = NetWriteFaol (rqptr, "</TABLE>\n</TD></TR>\n</TABLE>\n", NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   AdminMetaConEndUpdateForm (rqptr);

   status = NetWriteFaol (rqptr, "</BODY>\n</HTML>\n", NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
}

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

