/*****************************************************************************/
/*
                                  search.c

Search VMS Mail folders and messages.

By default uses it's own internal search, not VMS message select (at least for
TO, FROM, CC, SUBJ and dates).

Search tries to be a good citizen by allowing the administrator to configure
the process priority during searches of the message body.  See CONFIG.C for
detail.

If a search string begins with a caret ('^') character the regular expression
search is invoked, otherwise it is treated as a space-delimitted keyword search
(as with VMS Mail select).  Because of the expense of regular expression
search it can be disabled in soyMAIL configuration.


REGEX EVALUATION
----------------
Uses the GNU RX1.5 regular expression package.
Evaluation is done using case-insensitive, EGREP-compatible matching.

A detailed tutorial on regular expression capabilities and usage is well
beyond the scope of this document.  This summary is only to serve as a quick
mnemonic.  soyMAIL regular expressions support the following set of operators.

  Operator Overview

  Description                    Usage
  -----------                    -----
  Match-self Operator            Ordinary characters
  Match-any-character Operator   .
  Concatenation Operator         Juxtaposition.
  Repetition Operators           *  +  ? {}
  Alternation Operator           |
  List Operators                 [...]  [^...]
  Grouping Operators             (...)
  Back-reference Operator        \digit
  Anchoring Operators            ^  $
  Backslash Operator             Escape meta-character (e.g. \ ^ . $ | [ ()

The following operators are used to match one, or in conjunction with the
repetition operators more, characters of the target string.  These single and
leading characters are reserved meta-characters and must be escaped using a
leading backslash ("\") if required as a literal character in the matching
pattern.

  Matching Operators

  Expression        Purpose
  ----------        -------
  ^                 Match the beginning of the line
  .                 Match any character
  $                 Match the end of the line
  |                 Alternation (or)
  [abc]             Match only a, b or c
  [^abc]            Match anything except a, b and c
  [a-z0-9]          Match any character in the range a to z or 0 to 9
 
Repetition operators control the extent, or number, of whatever the matching
operators match. These are also reserved meta-characters and must be escaped
using a leading backslash if required as a literal character.

  Repetition Operators

  Expression        Function
  ----------        --------
  *                 Match 0 or more times
  +                 Match 1 or more times
  ?                 Match 1 or zero times
  {n}               Match exactly n times
  {n,}              Match at least n times
  {n,m}             Match at least n but not more than m times
 
Regular expression equivalents of these common wildcards

  asterisk ('*')           .*
  question mark ('?')      .
  percentage ('%')         .


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


VERSION HISTORY
---------------
15-AUG-2007  MGD  support searching against message flags
19-APR-2007  MGD  SearchMessage() refine RFC822 header error processing
28-DEC-2006  MGD  SearchGetString() modify to allow significant spaces
04-JUL-2005  MGD  SearchPage() add results every "all hit(s)"
03-APR-2005  MGD  support public access searching
27-JAN-2006  MGD  bugfix; SearchKeyword() lower-case the keyword!
01-FEB-2005  MGD  initial
*/
/*****************************************************************************/

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

#pragma nomember_alignment

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

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

/* application header file */
#include <soymail.h>
#include <config.h>
#include <message.h>
#include <public.h>
#include <request.h>
#include "regex.h"

#define FI_LI "SEARCH", __LINE__

#define MAIL$M_NEWMSG  0x01
#define MAIL$M_REPLIED 0x02
#define MAIL$M_MARKED  0x80

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

int  SearchEpoch = 1978,  /* ;-) */
     SearchRegexCflags = REG_EXTENDED | REG_ICASE | REG_NOSUB,
     SearchRegexEflags = 0,
     SearchPrevPriority = -1;

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

extern BOOL  Debug,
             WatchEnabled;

extern int  VmsVersion;

extern short  CurrentNumTime[];

extern char  CommandLine[],
             SoftwareVn[],
             Utility[];

extern CONFIG_DATA  SoyMailConfig;
extern VMS_MAIL_USER  VmsMailUser;

/**************/
/* prototypes */
/**************/

int lib$sub_times (__unknown_params);
int sys$bintim (__unknown_params);
int sys$setpri (__unknown_params);

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

BOOL SearchPageRequest (REQUEST_DATA *rdptr)

{
   char  *cptr;

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

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

   if (!SoyMailConfig.SearchEnabled)
   {
      StatusMessage (FI_LI, 1, "%s", LangFor("search_disabled"));
      if (rdptr->PrevPageIdent == PAGE_SEARCH)
      {
         SearchPage (rdptr);
         return (TRUE);
      }
      return (FALSE);
   }

   CGIVARNULL (cptr, "FORM_SEARCH_BTN");
   if (cptr)
   {
      if (rdptr->PrevPageIdent != PAGE_SEARCH) ErrorExit (SS$_BUGCHECK, FI_LI);

      if (LangSame ("search_begin", cptr))
      {
         if (rdptr->SearchData.SearchProblem)
            SearchPage (rdptr);
         else
            MessageFolderPage (rdptr);
      }
      else
      if (LangSame ("search_clear", cptr))
      {
         memset (&rdptr->SearchData, 0, sizeof(SEARCH_DATA));
         SearchPage (rdptr);
      }
      else
         ErrorExit (SS$_BUGCHECK, FI_LI);
   }
   else
      SearchPage (rdptr);

   return (TRUE);
}

/****************************************************************************/
/*
Move required search criteria from the search structure into the VMS message
structure to allow callable Mail to select the relevant items.
*/ 

void SearchCriteria (REQUEST_DATA *rdptr)

{
   static char  *MonthAbrev [] =
{ "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
      "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

   SEARCH_DATA  *srptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;
   srptr = &rdptr->SearchData;
   vmptr = &muptr->VmsMailMsg;

   if (!srptr->SelectNotSearch) return;

   /* there will always be room for these copies */
   if (srptr->From[0])
      vmptr->FromLength = (unsigned long)strcpy (vmptr->From, srptr->From);
   if (srptr->To[0])
      vmptr->ToLength = (unsigned long)strcpy (vmptr->To, srptr->To);
   if (srptr->Cc[0])
      vmptr->CcLength = (unsigned long)strcpy (vmptr->Cc, srptr->Cc);
   if (srptr->Subject[0])
      vmptr->SubjectLength = (unsigned long)strcpy (vmptr->Subject,
                                                    srptr->Subject);

   if (srptr->DateScope == SEARCH_DATE_SCOPE_SINCE)
   {
      vmptr->TimeSinceLength =
         sprintf (vmptr->TimeSince, "%02.02d-%s-%04.04d 00:00:00",
                  srptr->DateDay, MonthAbrev[srptr->DateMonth],
                  srptr->DateYear);
   }
   if (srptr->DateScope == SEARCH_DATE_SCOPE_BEFORE)
   {
      vmptr->TimeBeforeLength =
         sprintf (vmptr->TimeBefore, "%02.02d-%s-%04.04d 00:00:00",
                  srptr->DateDay, MonthAbrev[srptr->DateMonth],
                  srptr->DateYear);
   }
   if (srptr->DateScope == SEARCH_DATE_SCOPE_ON)
   {
      vmptr->TimeSinceLength =
         sprintf (vmptr->TimeSince, "%02.02d-%s-%04.04d 00:00:00",
                  srptr->DateDay, MonthAbrev[srptr->DateMonth],
                  srptr->DateYear);

      vmptr->TimeBeforeLength =
         sprintf (vmptr->TimeBefore, "%02.02d-%s-%04.04d 23:59:59",
                  srptr->DateDay, MonthAbrev[srptr->DateMonth],
                  srptr->DateYear);
   }

   vmptr->SelectMessageFlags = 0;

   if (srptr->Category == SEARCH_CATEGORY_NEWMSG)
      vmptr->SelectMessageFlags |= MAIL$M_NEWMSG;

   if (srptr->Category == SEARCH_CATEGORY_MARKED)
      vmptr->SelectMessageFlags |= MAIL$M_MARKED;

   if (srptr->Category == SEARCH_CATEGORY_REPLIED)
      vmptr->SelectMessageFlags |= MAIL$M_REPLIED;
}

/****************************************************************************/
/*
Check for parameters from the search criteria page.  Populate if present (means
a new search is being initiated), otherwise propagate from previous request
state search data (so they will be the defaults of the next search).
*/ 

void SearchPropagate (REQUEST_DATA *rdptr)

{
   static char  *MonthAbrev [] =
{ "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
      "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

   BOOL  ParamHit = FALSE;
   int  retval, status;
   char  *cptr, *sptr, *zptr,
         *CriPtr;
   char  CriteriaString [512],
         TimeString [21];
   $DESCRIPTOR (TimeStringDsc, TimeString);
   REQUEST_DATA  *sdptr;
   SEARCH_DATA  *srptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_USER  *muptr;

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

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

   if (!SoyMailConfig.SearchEnabled) return;

   muptr = &VmsMailUser;
   srptr = &rdptr->SearchData;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;
   vmptr = &muptr->VmsMailMsg;

   if (rdptr->PrevPageIdent != PAGE_SEARCH)
   {
      /* propagate (any) previous search parameters from request to request */
      memcpy (srptr, &sdptr->SearchData, sizeof(SEARCH_DATA));
      return;
   }

   memset (srptr, 0, sizeof(SEARCH_DATA));

   CriPtr = CriteriaString;

   CGIVARNULL (cptr, "FORM_SEARCH_VMS_SELECT");
   if (cptr)
      srptr->SelectNotSearch = TRUE;
   else
      srptr->SelectNotSearch = FALSE;

   srptr->SearchProblem = FALSE;

   /*********************/
   /* header parameters */
   /*********************/

   CGIVARNULL (cptr, "FORM_SEARCH_FROM");
   if (cptr && *cptr)
   {
      ParamHit = TRUE;
      SearchGetString (srptr, cptr, srptr->From, sizeof(srptr->From),
                       &srptr->RegexFrom, "from");
      CriPtr += sprintf (CriPtr, "%s \"%s\"; ", LangFor("from"), srptr->From);
   }

   CGIVARNULL (cptr, "FORM_SEARCH_TO");
   if (cptr && *cptr)
   {
      ParamHit = TRUE;
      SearchGetString (srptr, cptr, srptr->To, sizeof(srptr->To),
                       &srptr->RegexTo, "to");
      CriPtr += sprintf (CriPtr, "%s \"%s\"; ", LangFor("to"), srptr->To);
   }

   CGIVARNULL (cptr, "FORM_SEARCH_CC");
   if (cptr && *cptr)
   {
      ParamHit = TRUE;
      SearchGetString (srptr, cptr, srptr->Cc, sizeof(srptr->Cc),
                       &srptr->RegexCc, "cc");
      CriPtr += sprintf (CriPtr, "%s \"%s\"; ", LangFor("cc"), srptr->Cc);
   }

   CGIVARNULL (cptr, "FORM_SEARCH_SUBJECT");
   if (cptr && *cptr)
   {
      ParamHit = TRUE;
      SearchGetString (srptr, cptr, srptr->Subject, sizeof(srptr->Subject),
                       &srptr->RegexSubject, "subject");
      CriPtr += sprintf (CriPtr, "%s \"%s\"; ",
                         LangFor("subject"), srptr->Subject);
   }

   /***************************/
   /* message body parameters */
   /***************************/

   if (!SoyMailConfig.SearchHeaderOnly)
   {
      CGIVARNULL (cptr, "FORM_SEARCH_MESSAGE");
      if (cptr && *cptr)
      {
         ParamHit = TRUE;
         SearchGetString (srptr, cptr, srptr->Message, sizeof(srptr->Message),
                          &srptr->RegexMessage, "message");
         CriPtr += sprintf (CriPtr, "%s \"%s\"; ",
                            LangFor("search_message"), srptr->Message);
      }

      CGIVARNULL (cptr, "FORM_SEARCH_ATTACHMENT");
      if (cptr && *cptr)
      {
         ParamHit = TRUE;
         SearchGetString (srptr, cptr,
                          srptr->Attachment, sizeof(srptr->Attachment),
                          &srptr->RegexAttachment, "attachment");
         CriPtr += sprintf (CriPtr, "%s \"%s\"; ",
                            LangFor("search_attachment"), srptr->Attachment);
      }
   }

   if (!SoyMailConfig.SearchHeaderOnly ||
       SoyMailConfig.MessageFlags)
   {
      srptr->Category = 0;
      CGIVARNULL (cptr, "FORM_SEARCH_CATEGORY");
      if (cptr && *cptr)
      {
         srptr->Category = atoi(cptr);
         if (srptr->Category < SEARCH_CATEGORY_ANY ||
             srptr->Category > SEARCH_CATEGORY__LAST)
            srptr->Category = SEARCH_CATEGORY_ANY; 
      }

      switch (srptr->Category)
      {
         case 0 : cptr = NULL; break;
         case SEARCH_CATEGORY_ANY :
              cptr = NULL; break;
         case SEARCH_CATEGORY_HTML :
              cptr = LangFor("search_cat_html"); break;
         case SEARCH_CATEGORY_PLAIN :
              cptr = LangFor("search_cat_plain"); break;
         case SEARCH_CATEGORY_RAW :
              cptr = LangFor("search_cat_raw"); break;
         case SEARCH_CATEGORY_RFC :
              cptr = LangFor("search_cat_rfc"); break;
         case SEARCH_CATEGORY_WITH_ATT :
              cptr = LangFor("search_cat_with_att"); break;
         case SEARCH_CATEGORY_WITHOUT_ATT :
              cptr = LangFor("search_cat_without_att"); break;
         case SEARCH_CATEGORY_VMS :
              cptr = LangFor("search_cat_vms"); break;
         case SEARCH_CATEGORY_NEWMSG :
              /** cptr = LangFor("search_cat_newmsg"); break; **/
              cptr = "Unread"; break;
         case SEARCH_CATEGORY_REPLIED :
              /** cptr = LangFor("search_cat_replied"); break; **/
              cptr = "Replied-to"; break;
         case SEARCH_CATEGORY_MARKED :
              /** cptr = LangFor("search_cat_marked"); break; **/
              cptr = "Marked"; break;
         default : cptr = "?";
      }

      if (cptr)
      {
         ParamHit = TRUE;
         CriPtr += sprintf (CriPtr, "%s \"%s\"; ",
                            LangFor("search_category"), cptr);
      }
   }

   /*******************/
   /* date parameters */
   /*******************/

   CGIVARNULL (cptr, "FORM_SEARCH_DATE_SCOPE");
   if (cptr && *cptr)
   {
      srptr->DateScope = atoi(cptr);
      if (srptr->DateScope < SEARCH_DATE_SCOPE_ANY ||
          srptr->DateScope > SEARCH_DATE_SCOPE_ON)
         srptr->DateScope = SEARCH_DATE_SCOPE_ANY; 
   }

   CGIVARNULL (cptr, "FORM_SEARCH_DATE_DAY");
   if (cptr && *cptr)
   {
      srptr->DateDay = atoi(cptr);
      if (srptr->DateDay < 0 || srptr->DateDay > 31)
         srptr->DateDay = CurrentNumTime[2]; 
   }

   CGIVARNULL (cptr, "FORM_SEARCH_DATE_MONTH");
   if (cptr && *cptr)
   {
      srptr->DateMonth = atoi(cptr);
      if (srptr->DateMonth < 0 || srptr->DateMonth > 12)
         srptr->DateMonth = CurrentNumTime[1]; 
   }

   CGIVARNULL (cptr, "FORM_SEARCH_DATE_YEAR");
   if (cptr && *cptr)
   {
      srptr->DateYear = atoi(cptr);
      if (srptr->DateYear < SearchEpoch ||
          srptr->DateYear > CurrentNumTime[0])
         srptr->DateYear = CurrentNumTime[0]; 
   }

   if (srptr->DateScope == SEARCH_DATE_SCOPE_BEFORE)
   {
      ParamHit = TRUE;
      sprintf (TimeString, "%02.02d-%s-%04.04d 00:00:00",
               srptr->DateDay, MonthAbrev[srptr->DateMonth],
               srptr->DateYear);
      status = sys$bintim (&TimeStringDsc, &srptr->BinTimeBefore);
      CriPtr += sprintf (CriPtr, "%s ", LangFor("search_date_before")); 
      if (VMSok (status))
         CriPtr += sprintf (CriPtr, "%02.02d-%s-%04.04d; ",
                            srptr->DateDay, MonthAbrev[srptr->DateMonth],
                            srptr->DateYear);
      else
         CriPtr += sprintf (CriPtr, "%%%08.08X; ", status);
   }
   else
   if (srptr->DateScope == SEARCH_DATE_SCOPE_SINCE)
   {
      ParamHit = TRUE;
      sprintf (TimeString, "%02.02d-%s-%04.04d 00:00:00",
               srptr->DateDay, MonthAbrev[srptr->DateMonth],
               srptr->DateYear);
      status = sys$bintim (&TimeStringDsc, &srptr->BinTimeSince);
      CriPtr += sprintf (CriPtr, "%s ", LangFor("search_date_since")); 
      if (VMSok (status))
         CriPtr += sprintf (CriPtr, "%02.02d-%s-%04.04d; ",
                            srptr->DateDay, MonthAbrev[srptr->DateMonth],
                            srptr->DateYear);
      else
         CriPtr += sprintf (CriPtr, "%%%08.08X; ", status);
   }
   else
   if (srptr->DateScope == SEARCH_DATE_SCOPE_ON)
   {
      ParamHit = TRUE;
      sprintf (TimeString, "%02.02d-%s-%04.04d 00:00:00",
               srptr->DateDay, MonthAbrev[srptr->DateMonth],
               srptr->DateYear);
      status = sys$bintim (&TimeStringDsc, &srptr->BinTimeSince);
      sprintf (TimeString, "%02.02d-%s-%04.04d 23:59:59",
               srptr->DateDay, MonthAbrev[srptr->DateMonth],
               srptr->DateYear);
      status = sys$bintim (&TimeStringDsc, &srptr->BinTimeBefore);
      CriPtr += sprintf (CriPtr, "%s ", LangFor("search_date_on")); 
      if (VMSok (status))
         CriPtr += sprintf (CriPtr, "%02.02d-%s-%04.04d; ",
                            srptr->DateDay, MonthAbrev[srptr->DateMonth],
                            srptr->DateYear);
      else
         CriPtr += sprintf (CriPtr, "%%%08.08X; ", status);
   }

   /******************/
   /* end parameters */
   /******************/

   if (ParamHit)
   {
      /* not really a *search* parameter as such */
      CGIVARNULL (cptr, "FORM_SEARCH_RESULTS");
      if (cptr && isdigit(*cptr))
         srptr->ResultsEvery = atol(cptr);

      /* stays in-progress until [open] or [newmail] buttons are used */
      srptr->InProgress = TRUE;

      *CriPtr = '\0';
      if (CriteriaString[0])
      {
         /* just trim the trailing "; " */
         *(CriPtr-2) = '\0';
         zptr = (sptr = srptr->Criteria) + sizeof(srptr->Criteria)-1;
         for (cptr = CriteriaString; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';
      }
   }
}

/****************************************************************************/
/*
Get a string search parameter and store it in the supplied search storage.
If a regular expression compile it into the supplied regex structure.  Issue
appropriate status error.
*/ 

int SearchGetString
(
SEARCH_DATA *srptr,
char *StringPtr,
char *SearchStringPtr,
int SizeofSearchString,
regex_t *RegexPtr,
char *ParamName
)
{
   int  retval;
   char  *cptr, *sptr, *zptr;

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

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

   cptr = StringPtr;

   if (*cptr == '^')
   {
      if (SoyMailConfig.SearchKeywordOnly)
      {
         srptr->SearchProblem = TRUE;
         StatusMessage (FI_LI, 1, "%s", LangFor("regex_disabled"));
      }
      else
      {
         retval = regcomp (RegexPtr, cptr+1, SearchRegexCflags);
         if (retval)
         {
            srptr->SearchProblem = TRUE;
            StatusMessage (FI_LI, 1, "%s &quot;%s:&quot;",
                           LangFor("regex_expression"), LangFor(ParamName));
         }
      }
   }

   zptr = (sptr = SearchStringPtr) + SizeofSearchString-1;
   while (*cptr && isspace(*cptr)) cptr++;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   if (sptr > SearchStringPtr) sptr--;
   while (sptr > SearchStringPtr && isspace(*sptr)) sptr--;
   if (*sptr && !isspace(*sptr)) sptr++;
   *sptr = '\0';

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

   return (sptr - SearchStringPtr);
}

/*****************************************************************************/
/*
Provide the search parameters page.
Use any previously entered an propagated from request to request.
*/

void SearchPage (REQUEST_DATA *rdptr)

{
   int  cnt;
   char  *cptr, *sptr, *zptr;
   REQUEST_DATA  *sdptr;

   SEARCH_DATA  *srptr;
   USER_OPTIONS  *uoptr;

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

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

   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;
   srptr = &rdptr->SearchData;
   uoptr = &rdptr->UserOptions;

   if (rdptr->PrivateAccess || rdptr->PublicWildcard)
      cptr = rdptr->FolderName;
   else
      cptr = rdptr->PublicAccessString;

   StatusMessage (FI_LI, 0, "%s &quot;%s&quot; %s",
                  LangFor("search"), HTML_ESCAPE(cptr),
                  LangFor("page_opened"));

   if (rdptr->PrivateAccess)
   {
      MainMenuPageBegin (rdptr, PAGE_SEARCH);
      MainMenuBar (rdptr);
   }
   else
   {
      PublicPageBegin (rdptr, PAGE_SEARCH);
      PublicPageTitle (rdptr);
   }

   StatusInfoPanel (rdptr);

   if (!srptr->Category) srptr->Category = SEARCH_CATEGORY_ANY;
   if (!srptr->DateScope) srptr->DateScope = SEARCH_DATE_SCOPE_ANY;
   if (!srptr->ResultsEvery) srptr->ResultsEvery = uoptr->MessagesPerPage;

   fprintf (stdout,
"<!-- search parameters begin -->\n\
<script language=\"JavaScript\">\n\
function clearParams() {\n\
   document.soymail_form.search_date_scope.selectedIndex = %d;\n\
   document.soymail_form.search_date_day.selectedIndex = %d;\n\
   document.soymail_form.search_date_month.selectedIndex = %d;\n\
   document.soymail_form.search_date_year.selectedIndex = 0;\n\
   document.soymail_form.search_from.value = \'\';\n\
   document.soymail_form.search_to.value = \'\';\n\
   document.soymail_form.search_cc.value = \'\';\n\
   document.soymail_form.search_subject.value = \'\';\n\
   if (typeof document.soymail_form.search_vms_select == \'checkbox\')\n\
      document.soymail_form.search_vms_select.checked = false;\n\
   if (document.soymail_form.search_message)\n\
      document.soymail_form.search_message.value = \'\';\n\
   if (document.soymail_form.search_attachment)\n\
      document.soymail_form.search_attachment.value = \'\';\n\
   if (document.soymail_form.search_category)\n\
      document.soymail_form.search_category.selectedIndex = %d;\n",
            SEARCH_DATE_SCOPE_ANY - 1,
            CurrentNumTime[2] - 1,
            CurrentNumTime[1] - 1,
            SEARCH_CATEGORY_ANY - 1);

   fprintf (stdout,
"   document.soymail_form.search_results.value = \'%d\';\n\
   return false;\n\
}\n\
</script>\n",
            uoptr->MessagesPerPage);

   fprintf (stdout,
"<p class=\"css_p06\">\
<table cellpadding=\"4\" cellspacing=\"1\" \
align=\"center\" class=\"css_panel_4\">\n\
<tr class=\"css_panel_2\"><td colspan=\"2\" \
style=\"font-weight:bold;text-align:center;\">%s</td></tr>\n\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\n\
<select class=\"css_select_1\" name=\"search_date_scope\">\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
</select>&nbsp;\n\
\
<select class=\"css_select_1\" name=\"search_date_day\">\n",
            LangFor("search_parameters"),
            LangFor("search_date"),
            SEARCH_DATE_SCOPE_ANY,
            srptr->DateScope == SEARCH_DATE_SCOPE_ANY ? " selected" : "",
            LangFor("search_any"),
            SEARCH_DATE_SCOPE_SINCE,
            srptr->DateScope == SEARCH_DATE_SCOPE_SINCE ? " selected" : "",
            LangFor("search_date_since"),
            SEARCH_DATE_SCOPE_BEFORE,
            srptr->DateScope == SEARCH_DATE_SCOPE_BEFORE ? " selected" : "",
            LangFor("search_date_before"),
            SEARCH_DATE_SCOPE_ON,
            srptr->DateScope == SEARCH_DATE_SCOPE_ON ? " selected" : "",
            LangFor("search_date_on"));

   if (!srptr->DateDay) srptr->DateDay = CurrentNumTime[2];

   for (cnt = 1; cnt <= 31; cnt++)
      fprintf (stdout, "<option value=\"%d\"%s>&nbsp;%d\n",
               cnt, cnt == srptr->DateDay ? " selected" : "", cnt);

   fprintf (stdout,
"</select>&nbsp;\n\
\
<select class=\"css_select_1\" name=\"search_date_month\">\n");

   if (!srptr->DateMonth) srptr->DateMonth = CurrentNumTime[1];

   cnt = 1;
   cptr = LangFor("months");
   while (*cptr)
   {
      sptr = cptr;
      while (*cptr && *cptr != ' ') cptr++;
      fprintf (stdout, "<option value=\"%d\"%s>&nbsp;%*.*s\n",
               cnt, cnt == srptr->DateMonth ? " selected" : "",
               cptr-sptr, cptr-sptr, sptr);
      while (*cptr && *cptr == ' ') cptr++;
      cnt++;
   }

   fprintf (stdout,
"</select>&nbsp;\n\
\
<select class=\"css_select_1\" name=\"search_date_year\">\n");

   if (!srptr->DateYear) srptr->DateYear = CurrentNumTime[0];

   for (cnt = CurrentNumTime[0]; cnt >= SearchEpoch; cnt--)
      fprintf (stdout, "<option value=\"%d\"%s>&nbsp;%d\n",
               cnt, cnt == srptr->DateYear ? " selected" : "", cnt);

   fprintf (stdout,
"</select>\n\
</td></tr>\n\
\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\
<input class=\"css_text_1\" type=\"text\" size=\"40\" \
name=\"search_from\" value=\"%s\" \
onfocus=\"this.select()\"></td></tr>\n\
\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\
<input class=\"css_text_1\" type=\"text\" size=\"40\" \
name=\"search_to\" value=\"%s\" \
onfocus=\"this.select()\"></td></tr>\n\
\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\
<input class=\"css_text_1\" type=\"text\" size=\"40\" \
name=\"search_cc\" value=\"%s\" \
onfocus=\"this.select()\"></td></tr>\n\
\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\
<input class=\"css_text_1\" type=\"text\" size=\"40\" \
name=\"search_subject\" value=\"%s\" \
onfocus=\"this.select()\"></td></tr>\n",
            LangFor("from"),
            HTML_ESCAPE(srptr->From),
            LangFor("to"),
            HTML_ESCAPE(srptr->To),
            LangFor("cc"),
            HTML_ESCAPE(srptr->Cc),
            LangFor("subject"),
            HTML_ESCAPE(srptr->Subject));

   if (SoyMailConfig.SearchHeaderOnly)
   {
      if (SoyMailConfig.MessageFlags)
      {
         fprintf (stdout,
"<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\n\
<select class=\"css_select_1\" name=\"search_category\">\n\
<option value=\"%d\"%s>&nbsp;%s\n",
            LangFor("search_category"),
            SEARCH_CATEGORY_ANY,
            srptr->Category == SEARCH_CATEGORY_ANY ? " selected" : "",
            LangFor("search_any"));
      }
   }
   else
   {
      fprintf (stdout,
"<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\
<input class=\"css_text_1\" type=\"text\" size=\"40\" \
name=\"search_message\" value=\"%s\" \
onfocus=\"this.select()\"></td></tr>\n\
\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\
<input class=\"css_text_1\" type=\"text\" size=\"40\" \
name=\"search_attachment\" value=\"%s\" \
onfocus=\"this.select()\"></td></tr>\n\
\
<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\n\
<select class=\"css_select_1\" name=\"search_category\">\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n\
<option value=\"%d\"%s>&nbsp;%s\n",
            LangFor("search_message"),
            HTML_ESCAPE(srptr->Message),
            LangFor("search_attachment"),
            HTML_ESCAPE(srptr->Attachment),

            LangFor("search_category"),
            SEARCH_CATEGORY_ANY,
            srptr->Category == SEARCH_CATEGORY_ANY ? " selected" : "",
            LangFor("search_any"),
            SEARCH_CATEGORY_RFC,
            srptr->Category == SEARCH_CATEGORY_RFC ? " selected" : "",
            LangFor("search_cat_rfc"),
            SEARCH_CATEGORY_VMS,
            srptr->Category == SEARCH_CATEGORY_VMS ? " selected" : "",
            LangFor("search_cat_vms"),
            SEARCH_CATEGORY_WITH_ATT,
            srptr->Category == SEARCH_CATEGORY_WITH_ATT ? " selected" : "",
            LangFor("search_cat_with_att"),
            SEARCH_CATEGORY_WITHOUT_ATT,
            srptr->Category == SEARCH_CATEGORY_WITHOUT_ATT ? " selected" : "",
            LangFor("search_cat_without_att"),
            SEARCH_CATEGORY_HTML,
            srptr->Category == SEARCH_CATEGORY_HTML ? " selected" : "",
            LangFor("search_cat_html"),
            SEARCH_CATEGORY_PLAIN,
            srptr->Category == SEARCH_CATEGORY_PLAIN ? " selected" : "",
            LangFor("search_cat_plain"),
            SEARCH_CATEGORY_RAW,
            srptr->Category == SEARCH_CATEGORY_RAW ? " selected" : "",
            LangFor("search_cat_raw"));
   }

   if (SoyMailConfig.MessageFlags)
   {
      if (SoyMailConfig.MessageFlags & MAIL$M_NEWMSG)
         fprintf (stdout, "<option value=\"%d\"%s>&nbsp;%s\n",
                  SEARCH_CATEGORY_NEWMSG,
                  srptr->Category == SEARCH_CATEGORY_NEWMSG ? " selected" : "",
                  /** LangFor("search_cat_newmsg") **/ "Unread (new)");

      if (SoyMailConfig.MessageFlags & MAIL$M_MARKED)
         fprintf (stdout, "<option value=\"%d\"%s>&nbsp;%s\n",
                  SEARCH_CATEGORY_MARKED,
                  srptr->Category == SEARCH_CATEGORY_MARKED ? " selected" : "",
                  /** LangFor("search_cat_marked") **/ "Marked");

      if (SoyMailConfig.MessageFlags & MAIL$M_REPLIED)
         fprintf (stdout, "<option value=\"%d\"%s>&nbsp;%s\n",
                  SEARCH_CATEGORY_REPLIED,
                  srptr->Category == SEARCH_CATEGORY_REPLIED ? " selected" : "",
                  /** LangFor("search_cat_replied") **/ "Replied-to");
   }

   if (!SoyMailConfig.SearchHeaderOnly ||
       SoyMailConfig.MessageFlags)
   {
      fputs ("</select>\n", stdout);

      cptr = TrnLnm ("SOYMAIL_VMS_SELECT", NULL, 0);
      if (cptr && toupper(*cptr) == 'T')
         fputs ("<input type=\"hidden\" \
name=\"search_vms_select\" value=\"1\">", stdout);
      else
      if (cptr)
         fprintf (stdout, "&nbsp;&nbsp;<input type=\"checkbox\" \
style=\"border:none;\" name=\"search_vms_select\" value=\"1\"%s>",
               srptr->SelectNotSearch ? " checked" : "");

      fputs ("</td></tr>\n", stdout);
   }

   fprintf (stdout,
"<tr><td class=\"css_right\">\
%s:</td><td class=\"css_left\">\n\
<select class=\"css_select_1\" name=\"search_results\">\n\
<option value=\"999999\"%s>&nbsp;%s %s\n\
<option value=\"%d\"%s>&nbsp;%d %s\n\
<option value=\"5\"%s>&nbsp;5 %s\n\
<option value=\"1\"%s>&nbsp;1 %s\n\
</select>\n\
</td></tr>\n\
<tr class=\"css_panel_2\"><td class=\"css_right\">\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"search_btn\" value=\"%s\"%s>&nbsp;\n\
<input class=\"css_button_panel_2\" type=\"submit\" \
name=\"search_btn\" value=\"%s\" onclick=\"return clearParams();\">\n\
</td><td></td></tr></tr>\n\
\
</table>\n\
<!-- search parameters end -->\n",
            LangFor("search_results"),
            srptr->ResultsEvery == 999999 ? " selected" : "",
            LangFor("all"),
            LangFor("search_hits"),
            uoptr->MessagesPerPage,
            srptr->ResultsEvery == uoptr->MessagesPerPage ? " selected" : "",
            uoptr->MessagesPerPage,
            LangFor("search_hits"),
            srptr->ResultsEvery == 5 ? " selected" : "",
            LangFor("search_hits"),
            srptr->ResultsEvery == 1 ? " selected" : "",
            LangFor("search_hits"),
            LangFor("search_begin"),
            CLICK_STARTWORKING,
            LangFor("search_clear"));

   if (rdptr->PrivateAccess)
      MainMenuPageEnd (rdptr);
   else
      PublicPageEnd (rdptr);
}

/****************************************************************************/
/*
Apply the search parameters.
*/ 

BOOL SearchMessage
(
VMS_MAIL_MSG *vmptr,
SEARCH_DATA *srptr
)
{
   int  status;
   int64  ScratchTime64;
   MIME_DATA  *mdptr;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "SEARCH !AZ !UL \
\"!AZ\" \"!AZ\" \"!AZ\" \"!AZ\" \"!AZ\" \"!AZ\"",
                srptr->SelectNotSearch ? "false" : "true", srptr->Category,
                srptr->From, srptr->To, srptr->Cc, srptr->Subject,
                srptr->Message, srptr->Attachment);

   if (!SoyMailConfig.SearchEnabled) return (TRUE);

   /* if nothing to search for */
   if (srptr->Category == SEARCH_CATEGORY_ANY &&
       !srptr->Message[0] &&
       !srptr->Attachment[0])
   {
      if (srptr->SelectNotSearch) return (TRUE);

      if (!srptr->Cc[0] &&
          !srptr->From[0] &&
          !srptr->Subject[0] &&
          !srptr->To[0] &&
          !srptr->BinTimeBefore[0] &&
          !srptr->BinTimeBefore[1] &&
          !srptr->BinTimeSince[0] &&
          !srptr->BinTimeSince[1]) return (TRUE);
   }

   /*********/
   /* start */
   /*********/

   srptr->SearchCount++;
   if (!srptr->LastIdSearched) srptr->LastIdSearched = vmptr->MessageId;
   srptr->FirstIdSearched = vmptr->MessageId;

   /**************/
   /* VMS header */
   /**************/

   if (srptr && !srptr->SelectNotSearch)
   {
      if (srptr && srptr->Cc[0])
         if (!SearchKeyword (vmptr->Cc, srptr->Cc, &srptr->RegexCc))
            srptr = NULL;

      if (srptr && srptr->From[0])
         if (!SearchKeyword (vmptr->From, srptr->From, &srptr->RegexFrom))
            srptr = NULL;

      if (srptr && srptr->Subject[0])
         if (!SearchKeyword (vmptr->Subject, srptr->Subject,
                             &srptr->RegexSubject))
            srptr = NULL;

      if (srptr && srptr->To[0])
         if (!SearchKeyword (vmptr->To, srptr->To, &srptr->RegexTo))
            srptr = NULL;

      if (srptr && (srptr->BinTimeBefore[0] || srptr->BinTimeBefore[1]))
      {
         status = lib$sub_times (&srptr->BinTimeBefore,
                                 &vmptr->BinDate,
                                 &ScratchTime64);
         if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status);
         if (VMSnok (status)) srptr = NULL;
      }

      if (srptr && (srptr->BinTimeSince[0] || srptr->BinTimeSince[1]))
      {
         status = lib$sub_times (&vmptr->BinDate,
                                 &srptr->BinTimeSince,
                                 &ScratchTime64);
         if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status);
         if (VMSnok (status)) srptr = NULL;
      }

      if (srptr && srptr->Category == SEARCH_CATEGORY_NEWMSG)
         if (!(vmptr->MessageFlags & MAIL$M_NEWMSG)) srptr = NULL;

      if (srptr && srptr->Category == SEARCH_CATEGORY_MARKED)
         if (!(vmptr->MessageFlags & MAIL$M_MARKED)) srptr = NULL;

      if (srptr && srptr->Category == SEARCH_CATEGORY_REPLIED)
         if (!(vmptr->MessageFlags & MAIL$M_REPLIED)) srptr = NULL;
   }

   /********************/
   /* parse the header */
   /********************/

   if (srptr)
   {
      /* just skip it if there is a problem processing the RFC822 header */
      if (!InetMailParseHeader (vmptr))
         if (vmptr->LooksLikeRfc)
            return (FALSE);
   }

   /*******************/
   /* some categories */
   /*******************/

   /* it is safe to return before any MIME processing */
   if (srptr && srptr->Category == SEARCH_CATEGORY_RFC && !vmptr->LooksLikeRfc)
      srptr = NULL;

   if (srptr && srptr->Category == SEARCH_CATEGORY_VMS && vmptr->LooksLikeRfc)
      srptr = NULL;

   if (srptr && srptr->Category == SEARCH_CATEGORY_RAW)
      if (!SearchKeyword (vmptr->BodyPtr, srptr->Message, &srptr->RegexMessage))
         srptr = NULL;

   /*************/
   /* continue? */
   /*************/

   /* eliminate processing the body by checking if it's necssary */
   if (srptr && srptr->Category == SEARCH_CATEGORY_ANY &&
       !srptr->Message[0] &&
       !srptr->Attachment[0])
   {
      if (srptr) srptr->HitCount++;

      if (WatchEnabled) WatchThis (FI_LI, "!AZHIT!!", srptr == NULL ? "NO " : "");

      /***************/
      /* finish here */
      /***************/

      return (srptr != NULL);
   }

   /*************************/
   /* dealing with the body */
   /*************************/

   if (SearchPrevPriority == -1 &&
       SoyMailConfig.SearchPriority >= 0 &&
       SoyMailConfig.SearchPriority <= 4)
   {
      /* not been set yet and is configured to be */
      status = sys$setpri (0, 0, SoyMailConfig.SearchPriority,
                           &SearchPrevPriority, 0, 0);
      if (VMSnok (status)) ErrorExit (status, FI_LI);
      atexit (SearchRestorePriority);
   }

   if (srptr) MessageBodyProcess (vmptr);

   /*******************/
   /* more categories */
   /*******************/

   if (srptr && srptr->Category != SEARCH_CATEGORY_ANY)
   {
      if (srptr && srptr->Category == SEARCH_CATEGORY_WITH_ATT)
      {
         for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
            if (mdptr->NamePtr) break;
         if (!mdptr) srptr = NULL;
      }

      if (srptr && srptr->Category == SEARCH_CATEGORY_WITHOUT_ATT)
      {
         for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
            if (mdptr->NamePtr) break;
         if (mdptr) srptr = NULL;
      }

      if (srptr && srptr->Category == SEARCH_CATEGORY_HTML)
      {
         mdptr = MessageBodyIdentify (vmptr, OPTIONS_READ_PLAIN0_HTML1);
         if (!mdptr) srptr = NULL;
      }

      if (srptr && srptr->Category == SEARCH_CATEGORY_PLAIN)
      {
         mdptr = MessageBodyIdentify (vmptr, OPTIONS_READ_PLAIN1_HTML0);
         if (!mdptr) srptr = NULL;
      }
   }

   if (srptr && srptr->Category != SEARCH_CATEGORY_RAW)
   {
      if (srptr && srptr->Message[0])
      {
         /* search message text */
         mdptr = MessageBodyIdentify (vmptr, OPTIONS_READ_PLAIN1_HTML0);
         if (mdptr)
         {
            if (MimeDecProcessPart (mdptr))
               if (!SearchKeyword (mdptr->PartContentPtr, srptr->Message,
                                   &srptr->RegexMessage))
                  srptr = NULL;
         }
         if (!mdptr) srptr = NULL;
      }
   }

   /*******************/
   /* attachment name */
   /*******************/

   if (srptr && srptr->Attachment[0])
   {
      /* search for attachment */
      for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr)
      {
         if (!mdptr->NamePtr) continue;
         if (SearchKeyword (mdptr->NamePtr, srptr->Attachment,
                            &srptr->RegexAttachment)) break;
      }
      if (!mdptr) srptr = NULL;
   }

   /************/
   /* finished */
   /************/

   /* free all allocated memory used in the MIME parse */
   if (vmptr->MimeDataPtr) MimeDecParseFree (vmptr->MimeDataPtr);

   if (srptr) srptr->HitCount++;

   if (WatchEnabled) WatchThis (FI_LI, "!AZHIT!!", srptr == NULL ? "NO " : "");
   return (srptr != NULL);
}

/****************************************************************************/
/*
Check the first characters of the keyword for a caret ('^').  If so invoke the
independent regular expression search implemented by the GNU REGEX.C module.
If not a caret search for the supplied keyword using our own inimitable,
lightweight, keyword search code (which should behave the same way as VMS
Mail's keyword select).
*/ 

BOOL SearchKeyword
(
char *TextPtr,
char *KeywordPtr,
regex_t *RegexPtr
)
{
   int  retval;
   char  ch;
   char  *cptr, *sptr, *tptr, *zptr;
   char  KeywordLoCase [64];
   regex_t  regexp;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "!AZ \'!AZ\'",
                 *KeywordPtr == '^' ? "REGEX" : "KEYWORD", KeywordPtr);

   if (*KeywordPtr == '^')
   {
      if (!RegexPtr)
      {
         /* only ConfigLoad() use, should not happen with message searches! */
         RegexPtr = &regexp;
         retval = regcomp (RegexPtr, KeywordPtr+1, SearchRegexCflags);
         if (retval)
         {
            if (WatchEnabled) WatchThis (FI_LI, "REGEX PROBLEM");
            return (FALSE);
         }
      }
      retval = regexec (RegexPtr, TextPtr, 0, 0, SearchRegexEflags);
      if (retval) return (FALSE);
      return (TRUE);
   }

   zptr = (sptr = KeywordLoCase) + sizeof(KeywordLoCase) - 1;
   for (cptr = KeywordPtr; *cptr && sptr < zptr; *sptr++ = tolower(*cptr++));
   *sptr = '\0';
   ch = KeywordLoCase[0];
   for (tptr = TextPtr; *tptr; tptr++)
   {
      if (tolower(*tptr) != ch) continue;
      sptr = tptr + 1;
      cptr = KeywordLoCase + 1;
      while (*cptr && *sptr && *cptr == tolower(*sptr))
      {
         sptr++;
         cptr++;
      }
      if (*cptr) continue;
      return (TRUE);
   }
   return (FALSE);
}

/****************************************************************************/
/*
If changed during search then restore it (explicitly called or via atexit()).
*/ 

void SearchRestorePriority ()

{
   int  status;

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

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

   if (SearchPrevPriority >= 0 &&
       SearchPrevPriority <= 6)
   {
      status = sys$setpri (0, 0, SearchPrevPriority, 0, 0, 0);
      if (VMSnok (status)) ErrorExit (status, FI_LI);
      SearchPrevPriority = -1;
   }
}

/****************************************************************************/
/*
Called from ParseCommandLine() to provide a command-line regular expression
checker.  Try your matches out before before putting them into the
configuration!  $ SOYMAIL /REGEX "<textutal content>" "<expression>"
*/ 

void SearchRegexCheck (char *StringPtr)

{
   int  retval;
   char  *cptr, *sptr, *zptr;
   char  expbuf [256],
         txtbuf [256];
   regex_t  regexp;

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

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

   for (cptr = StringPtr; *cptr && isspace(*cptr); cptr++);
   zptr = (sptr = txtbuf) + sizeof(txtbuf)-1;
   if (*cptr != '\"') exit (SS$_BADPARAM);
   cptr++;
   while (*cptr && *cptr != '\"' && sptr < zptr)
   {
      if (*cptr == '\\' && *(cptr+1)) cptr++;
      *sptr++ = *cptr++;
   }
   *sptr = '\0';
   if (*cptr) cptr++;
   while (*cptr && isspace(*cptr)) cptr++;
   zptr = (sptr = expbuf) + sizeof(expbuf)-1;
   if (*cptr != '\"') exit (SS$_BADPARAM);
   cptr++;
   if (*cptr != '^') *sptr++ = '^';
   while (*cptr && *cptr != '\"' && sptr < zptr)
   {
      if (*cptr == '\\' && *(cptr+1)) cptr++;
      *sptr++ = *cptr++;
   }
   *sptr = '\0';

   retval = regcomp (&regexp, expbuf+1, SearchRegexCflags);
   if (retval)
   {
      fprintf (stdout, "%%%s-E-REGEX, invalid regular expression\n", Utility);
      exit (SS$_BADPARAM | STS$M_INHIB_MSG);
   }

   fprintf (stdout, "%%%s-I-REGEX, \"%s\" by \"%s\" %s\n",
            Utility, txtbuf, expbuf,
            SearchKeyword (txtbuf, expbuf, &regexp) ? "TRUE" : "FALSE");

   exit (SS$_NORMAL);
}

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

