Major redesign. All input is handled through XML. Raw input data is first
[gnucomo.git] / src / gcm_input / message.cpp
index 55c1d1a..7e727ff 100644 (file)
@@ -8,7 +8,7 @@
 ***********************
 **      FILE NAME      : message.cpp
 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
-**      VERSION NUMBER : $Revision: 1.9 $
+**      VERSION NUMBER : $Revision: 1.16 $
 **
 **  DESCRIPTION      :  Implementation of the message handling classes
 **
 ********************************
 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
 **      CREATION DATE   : Sep 16, 2002
-**      LAST UPDATE     : Mar 28, 2003
+**      LAST UPDATE     : Nov 28, 2003
 **      MODIFICATIONS   : 
 **************************************************************************/
 
 /*****************************
    $Log: message.cpp,v $
-   Revision 1.9  2003-03-29 09:04:10  arjen
+   Revision 1.16  2003-12-04 10:38:09  arjen
+   Major redesign. All input is handled through XML. Raw input data is first
+   transformed into an XML document for further processing.
+   A collection of polymorphic classes handle the transformation of various
+   input formats into XML.
+   Classifying input data is done with a finite improbability calculation.
+
+   Revision 1.15  2003/10/27 11:28:27  arjen
+   Do not add another parameter_notification record is the notification
+   already exists for that parameter.
+
+   Revision 1.14  2003/09/01 06:57:14  arjen
+   Reject log entries that are found to be invalid.
+
+   Revision 1.13  2003/08/16 15:28:45  arjen
+   Fixed a namespace problem
+
+   Revision 1.12  2003/08/11 16:56:16  arjen
+   Different kinds of log files are parsed by a collection of objects
+   of different classes, derived from the base class line_cooker
+   Depending on the message content or the message_type element in
+   XML, one of these objects is selected.
+
+   Logrunner is integrated with gcm_input. Although its functionality
+   is still limited, a connection between logrunner and gcm_input
+   is beginning to form.
+
+   Revision 1.11  2003/08/05 08:15:00  arjen
+   Debug output to the log stream instead of cerr.
+   Fixed namespace problems in XPath searches of the DOM.
+   Moved string utility functions to a separate file.
+
+   Revision 1.10  2003/04/29 09:16:44  arjen
+   Read XML input,
+   Only cooked log entries for now.
+
+   Revision 1.9  2003/03/29 09:04:10  arjen
    Extract the hostname out of the 'From:' or 'Message-Id:' line
    of an email header.
 
 
 *****************************/
 
-static const char *RCSID = "$Id: message.cpp,v 1.9 2003-03-29 09:04:10 arjen Exp $";
+static const char *RCSID = "$Id: message.cpp,v 1.16 2003-12-04 10:38:09 arjen Exp $";
 
 #include <algorithm>
+#include <libxml/xpath.h>
+#include <libxml/debugXML.h>
 #include "message.h"
 
+//#define DEBUG
+
 extern bool verbose;   /*  Defined in the main application */
 extern bool testmode;
+extern bool incremental;
+extern std::ostream *Log;
 
 /*   Utility functions   */
 
-String SQL_Escape(String s);
+extern String SQL_Escape(String s);
 
 /*=========================================================================
 **  NAME           : operator >>
@@ -116,6 +158,22 @@ bool operator >> (message_buffer &b, String &s)
    return input_ok;
 }
 
+/*=========================================================================
+**  NAME           : client_message
+**  SYNOPSIS       : client_message(std::istream *in, gnucomo_database db)
+**  PARAMETERS     : 
+**  RETURN VALUE   : None
+**
+**  DESCRIPTION    : Client message constructor.
+**
+**  VARS USED      :
+**  VARS CHANGED   :
+**  FUNCTIONS USED :
+**  SEE ALSO       :
+**  LAST MODIFIED  : Nov 04, 2002
+**=========================================================================
+*/
+
 client_message::client_message(std::istream *in, gnucomo_database db)
 {
    input.from(in);
@@ -125,29 +183,126 @@ client_message::client_message(std::istream *in, gnucomo_database db)
    mail_header   = false;
    gpg_encrypted = false;
    classification = UNKNOWN;
+   xmlDom         = NULL;
    certainty      = 0.0;
 }
 
-static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
-static const String mail_date_re("[[:alpha:]]{3}, [ 123]?[0-9] [[:alpha:]]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}");
 static const String unix_date_re("[[:alpha:]]{3} [[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4}");
-static const String email_address_re("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
 
-static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
-static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
 static const regex re_dump("^ *DUMP: Date of this level");
-static const regex re_accesslog("(GET|POST) .+ HTTP");
-static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
 
-static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
-static const regex re_mail_From("^From:[[:blank:]]+");
-static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
-static const regex re_mail_MsId("^Message-Id:[[:blank:]]+");
-static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
-static const regex re_email_user("[[:alnum:]_.-]+@");
+static const regex re_xml_header("xml .*\?>$");
+
+/*=========================================================================
+**  NAME           : extractHeader
+**  SYNOPSIS       : void extractHeader()
+**  PARAMETERS     : 
+**  RETURN VALUE   : True if the mandatory header elements are available.
+**
+**  DESCRIPTION    : Extract the header information from the XML DOM tree.
+**
+**  VARS USED      :
+**  VARS CHANGED   :
+**  FUNCTIONS USED :
+**  SEE ALSO       :
+**  LAST MODIFIED  : Nov 26, 2003
+**=========================================================================
+*/
+
+bool client_message::extractHeader()
+{
+   xmlNodePtr       root, item;
+   xmlNsPtr         namespaces[1];
+
+   xmlXPathObjectPtr res;
+   xmlXPathContextPtr pathcontext;
+
+   bool header_OK = true;
+
+   root = xmlDocGetRootElement(xmlDom);
+   namespaces[0] = root->ns;
+
+   //TODO Ought to check  root->name and  root->ns->href 
+
+   pathcontext = xmlXPathNewContext(xmlDom);
+   pathcontext->node = xmlDocGetRootElement(xmlDom);
+   pathcontext->namespaces = namespaces;
+   pathcontext->nsNr       = 1;
+
+#ifdef DEBUG
+   xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
+#endif
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
+   if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+
+      //  Select a line cooker based on the message type.
+
+#ifdef DEBUG
+      *Log << "Looking for a line cooker for " << item->content << "\n";
+#endif
+      pan.lc = 0;
+      std::list<xform>::iterator lci = kitchen.begin();
+      while (pan.lc == 0 && lci != kitchen.end())
+      {
+         if (lci->lc->message_type() == (const char *)(item->content))
+         {
+            pan.lc = lci->lc;
+         }
+         lci++;
+      }
+      if (pan.lc == 0)
+      {
+         *Log << "Can not find a line cooker for message type " << item->content << "\n";
+         header_OK = false;
+      }
+   }
+   else
+   {
+      *Log << "Message type not found in XML header.\n";
+      header_OK = false;
+   }
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
+   if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+      hostname = (const char *)item->content;
+   }
+   else
+   {
+      *Log <<  "Can not determine the hostname where the message came from.\n";
+      header_OK = false;
+   }
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
+   if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+      service = (const char *)item->content;
+   }
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
+   if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+      arrival = String((char *)item->content);
+      if (!arrival.proper())
+      {
+         *Log << "Arrival time is not properly stated.\n";
+         header_OK = false;
+      }
+
+   }
+   //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+
+   return header_OK;
+}
+
 
 /*=========================================================================
 **  NAME           : classify
@@ -161,7 +316,7 @@ static const regex re_email_user("[[:alnum:]_.-]+@");
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Mar 28, 2003
+**  LAST MODIFIED  : Nov 27, 2003
 **=========================================================================
 */
 
@@ -173,6 +328,11 @@ double client_message::classify(String host, UTC arriv, String serv)
    arrival     = arriv;
    service     = serv;
 
+   const double   epsilon = 0.1;   //  Threshold for uncertainty
+   const double   P       = 0.5;   //  Probability of a wrong match
+
+   double uncertainty;
+
    /*  First, check if the message has a mail header. */
 
    if (input >> line && line == re_uxmail_from)
@@ -181,32 +341,10 @@ double client_message::classify(String host, UTC arriv, String serv)
 
       mail_header = true;
 
-      /*  Scan ahead for the hostname and date of arrival.  */
+      /*  Skip the mail header until the first empty line.  */
 
       while (input >> line && line != "")
       {
-         if (line == re_mail_From)
-         {
-            from_address = line(re_email_address);
-            from_address(re_email_user) = "";            //  Remove the user part;
-            if (from_address != "")
-            {
-               hostname = from_address;
-            }
-         }
-         if (line == re_mail_MsId)
-         {
-            from_address = line(re_email_address);
-            from_address(re_email_user) = "";            //  Remove the user part;
-            if (from_address != "")
-            {
-              hostname = from_address;
-            }
-         }
-         if (line == re_mail_Date)
-         {
-            arrival = UTC(line(regex(mail_date_re)));
-         }
       }
    }
    else
@@ -222,595 +360,450 @@ double client_message::classify(String host, UTC arriv, String serv)
     */
 
 
-   while (input >> line && certainty < 0.9)
+   uncertainty = 1.0;
+
+   while (input >> line && uncertainty > 0.1)
    {
-      std::cout << "  testing: " << line << "\n";
-      if (line == re_syslog)
-      {
-         certainty = 1.0;
-         classification = SYSLOG;
-         if (verbose)
-         {
-            std::cout << "Syslog detected.\n";
-         }
-      }
-      else if (line == re_syslog_irix)
+      if (verbose)
       {
-         certainty = 1.0;
-         classification = SYSLOG_IRIX;
-         if (verbose)
-         {
-            std::cout << "IRIX Syslog detected.\n";
-         }
+         *Log << "  testing: " << line << "\n";
       }
-      else if (line == re_PGP)
+
+      if (line == re_PGP)
       {
-         certainty = 1.0;
+         uncertainty = 0.0;
          gpg_encrypted = true;
-         std::cerr << "The message is PGP/GnuPG encrypted.\n";
-      }
-      else if (line == re_dump)
-      {
-          certainty = 1.0;
-          if (verbose)
-          {
-             std::cout << "DUMP output detected.\n";
-          }
-      }
-      else if (line == re_accesslog)
-      {
-          certainty = 1.0;
-          classification = ACCESSLOG;
-          service = "httpd";
-          if (verbose)
-          {
-             std::cout << "HTTP access log detected.\n";
-          }
+         *Log << "The message is PGP/GnuPG encrypted.\n";
       }
-      else if (line == re_errorlog)
-      {
-          certainty = 1.0;
-          classification = ERRORLOG;
-          service = "httpd";
-          if (verbose)
-          {
-             std::cout << "HTTP error log detected.\n";
-          }
-      }
-      else if (line == re_rpm)
+      else
       {
-          certainty = 1.0;
-          classification = RPMLIST;
-          service = "";
-          if (verbose)
-          {
-             std::cout << "RPM package list detected.\n";
-          }
+         //  Scan the list of line cookers if there is anything familiar.
+
+         std::list<xform>::iterator lci = kitchen.begin();
+         uncertainty = 1.0;
+         while (lci != kitchen.end())
+         {
+            if (lci->lc->check_pattern(line))
+            {
+               //  We have a match; decrease the uncertainty
+
+               lci->uncertainty *= P;
+               if (uncertainty > lci->uncertainty)
+               {
+                  uncertainty = lci->uncertainty;
+                  pan = *lci;
+               }
+               if (verbose)
+               {
+                  *Log << lci->lc->message_type() << " detected with "
+                       << lci->uncertainty << " uncertainty.\n";
+               }
+            }
+            lci++;
+         }
+         classification = COOKER_OBJECT;
       }
    }
    input.rewind();
 
-   if (hostname == "")
-   {
-      std::cerr <<  "Can not determine the hostname where the message came from.\n";
-      certainty = 0.0;
-   }
-   else if (!arrival.proper())
-   {
-      std::cerr << "Arrival time is not knwon.\n";
-      certainty = 0.0;
-   }
-   else
-   {
-      certainty = 1.0;
-   }
+   certainty = 1.0 - uncertainty;
 
    return certainty;
 }
 
 /*=========================================================================
-**  NAME           : enter
-**  SYNOPSIS       : int enter()
+**  NAME           : enterXML
+**  SYNOPSIS       : int enterXML()
 **  PARAMETERS     : 
-**  RETURN VALUE   : The number of lines successfully parsed from the input
+**  RETURN VALUE   : None
 **
-**  DESCRIPTION    : 
+**  DESCRIPTION    : Analyze the DOM tree from the XML input.
+**                   The DOM tree was previously parsed by readXMLinput().
 **
 **  VARS USED      :
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Mar 28, 2003
+**  LAST MODIFIED  : Nov 28, 2003
 **=========================================================================
 */
 
-int client_message::enter()
+struct param_property
 {
-   long   nr_lines = 0;
-   String line;
-   String qry;
-
-   String change_notification("");
-   String create_notification("");
-   bool initial_entry = false;
-
-   std::list<String>  packages;
+   String name;
+   String value;
+};
 
+void client_message::enterXML()
+{
+   //TODO : return the number of elements that are handled.
 
-   /*  Double-check the classification of the message */
-
-   if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
-   {
-      return 0;
-   }
-
-   if (mail_header)
-   {
-      //  Skip the mail header.
-
-      while (input >> line && line != "");
-   }
+   xmlXPathObjectPtr res;
+   xmlXPathContextPtr pathcontext;
+   xmlNsPtr           namespaces[1];
 
    /*  Try to find the host in the database */
 
    String objectid;
+   String remark;        //  For notifications
 
    objectid = database.find_host(hostname);
    if (objectid == "")
    {
-      std::cerr << "Please define the host " << hostname << " in the database.\n";
-      return 0;
+      *Log << "Please define the host " << hostname << " in the database.\n";
+      return;
    }
    if (verbose)
    {
-      std::cout << "Object id for " << hostname << " is " << objectid << "\n";
-   }
-
-   if (classification == RPMLIST)
-   {
-
-      int n_packages;
-
-      /*   Read all packages, so we will know which ones are  */
-      /*   missing at the end.                                */
-
-      qry = "select name from parameter where objectid='";
-      qry += objectid + "' and class='package'";
-      n_packages = database.Query(qry);
-      initial_entry = n_packages == 0;
-
-      std::cout << n_packages << " packages in database.\n";
-      for (int t = 0; t < n_packages; t++)
-      {
-         packages.push_back(database.Field(t, "name"));
-      }
-      std::cout << "Package list built: " << packages.size() << ".\n";
+      *Log << "Object id for " << hostname << " is " << objectid << "\n";
    }
 
-   /*  Scan the input line by line, entring records into the database */
+   pathcontext = xmlXPathNewContext(xmlDom);
+   pathcontext->node = xmlDocGetRootElement(xmlDom);
+   namespaces[0] = pathcontext->node->ns;
+   pathcontext->namespaces = namespaces;
+   pathcontext->nsNr       = 1;
 
-   String rest;   //  Rest of the line to be parsed
+   res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
 
-   while (input >> line)
+   if (res->nodesetval != NULL)
    {
-      if (verbose)
-      {
-         std::cout << line << "\n";
-      }
-
+      //  Find the first child element of the <data> element.
 
-      /*  Check each line if it contains valid information */
-
-      const regex *check;
-
-      switch (classification)
+      xmlNodePtr  node = *res->nodesetval->nodeTab;
+      while (node->type != XML_ELEMENT_NODE)
       {
-      case SYSLOG:
-            check = &re_syslog;
-            break;
-      case SYSLOG_IRIX:
-            check = &re_syslog_irix;
-            break;
-      case ACCESSLOG:
-            check = &re_accesslog;
-            break;
-      case ERRORLOG:
-            check = &re_errorlog;
-            break;
-      case RPMLIST:
-            check = &re_rpm;
-            break;
+         node = node->next;
       }
-
-      if (line == *check)
+      if (strcmp((char *)node->name, "log") == 0)
       {
-         date   log_date;
-         hour   log_time;
-         int    i;
+         //  Each child contains a log entry, raw or cooked.
 
-         String insertion("insert into log (objectid, servicecode,"
-                           " object_timestamp, timestamp, rawdata, processed) values (");
-         String datestring;
-
-         switch (classification)
+         node = node->children;
+         while (node != NULL)
          {
-         case SYSLOG:
-            log_date = line;
-            log_time = line;
-            if (log_date.Year() < 0 || log_date.Year() > 2500)
+            if (node->type == XML_ELEMENT_NODE)
             {
-               //  The year is not in the log file. Assume the year of arrival,
-               //  unless this puts the log entry at a later date than the arrival date.
-               //  This happens e.g. when a log entry from December arrives in Januari.
+               xmlNodePtr  item;
+               String      log_hostname;
+               UTC         log_date;
+               String      raw("");;
+               String      log_service;
 
-               log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
-               if (log_date > date(arrival))
+               if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
                {
-                  log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
-               }
-            }
-
-            if (verbose)
-            {
-               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
-            }
-            rest = line << 16;
-            i = rest.index(' ');
-            if (rest(0,i) == hostname(0,i))
-            {
-               rest <<= i + 1;
-               if (verbose)
-               {
-                  std::cout << "   Hostname matches.\n";
-                  std::cout << "   rest = " << rest << "\n";
+                  item = node->children;
+                  if (pan.lc == 0)
+                  {
+                     *Log << "Can not cook this type of <raw> log element.\n";
+                  }
+                  else
+                  {
+                     raw = String((const char *)item->content);
+                     if (pan.lc->cook_this(raw, arrival))
+                     {
+                        log_hostname = pan.lc->hostname();
+                        if (log_hostname == "")
+                        {
+                           log_hostname = hostname;
+                        }
+                        log_service = pan.lc->service();
+                        log_date    = pan.lc->timestamp();
+                     }
+                     else
+                     {
+                        *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
+                        raw = "";
+                     }
+                  }
                }
-               for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
-               if (verbose)
+               else if (strcmp((char *)node->name, "cooked") == 0)
                {
-                  std::cout << "   Service name = " << rest(0,i) << "\n";
-               }
+                  //  Find the parts of the log entry
 
-               /*   Insert a new record into the log table   */
-
-               insertion += "'" + objectid + "',";
-               insertion += "'" + rest(0,i) + "',";
-               insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
-               insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
-               insertion += "'" + SQL_Escape(line) + "',FALSE";
-               insertion += ")";
+                  if (verbose)
+                  {
+                     *Log << "Analyzing cooked element.\n";
+                  }
+                  pathcontext->node = node;
 
-               if (testmode)
-               {
-                  std::cout << insertion << "\n";
-               }
-               else
-               {
-                  database.Query(insertion);
-               }
+                  res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
+                  if (res->nodesetval != NULL)
+                  {
+                     item = *res->nodesetval->nodeTab;
+                     log_hostname = (const char *)item->content;
+                     if (log_hostname != hostname(0, ~log_hostname))
+                     {
+                        *Log << "Hostname " << log_hostname << " does not match.\n";
+                        log_hostname = "";
+                     }
+                  }
+                  else
+                  {
+                     log_hostname = hostname;
+                  }
 
-               if (verbose)
-               {
-                  std::cout << "\n\n";
-               }
+                  res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
+                  if (res->nodesetval != NULL)
+                  {
+                     item = *res->nodesetval->nodeTab;
+                     log_service = (const char *)item->content;
+                  }
+                  else
+                  {
+                     log_service = service;
+                  }
 
-               nr_lines++;
-            }
-            else
-            {
-               std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
-            }
-            break;
+                  res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
+                  if (res->nodesetval != NULL)
+                  {
+                     item = *res->nodesetval->nodeTab;
+                     log_date = String((const char *)item->content);
+                  }
+                  else
+                  {
+                     *Log << "<timestamp> missing from cooked log element.\n";
+                  }
 
-         case SYSLOG_IRIX:
-            log_date = line;
-            log_time = line;
-            if (log_date.Year() < 0 || log_date.Year() > 2500)
-            {
-               //  The year is not in the log file. Assume the year of arrival,
-               //  unless this puts the log entry at a later date than the arrival date.
-               //  This happens e.g. when a log entry from December arrives in Januari.
+                  res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
+                  if (res->nodesetval != NULL)
+                  {
+                     item = *res->nodesetval->nodeTab;
+                     raw = String((const char *)item->content);
+                  }
+                  else
+                  {
+                     *Log << "<raw> missing from cooked log element.\n";
+                  }
 
-               log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
-               if (log_date > date(arrival))
-               {
-                  log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
                }
-            }
 
-            if (verbose)
-            {
-               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
-            }
-            rest = line << 19;
-            i = rest.index(' ');
-            if (rest(0,i) == hostname(0,i))
-            {
-               rest <<= i + 1;
-               if (verbose)
+               //   Insert a new log record into the database.
+               if (raw != "" && log_hostname != "" && log_date.proper())
                {
-                  std::cout << "   Hostname matches.\n";
-                  std::cout << "   rest = " << rest << "\n";
-               }
-               for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
-               if (verbose)
-               {
-                  std::cout << "   Service name = " << rest(0,i) << "\n";
-               }
+                  String insertion("insert into log (objectid, servicecode,"
+                        " object_timestamp, timestamp, rawdata, processed) values (");
 
-               /*   Insert a new record into the log table   */
+                  /*   Insert a new record into the log table   */
 
-               insertion += "'" + objectid + "',";
-               insertion += "'" + rest(0,i) + "',";
-               insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
-               insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
-               insertion += "'" + SQL_Escape(line) + "',FALSE";
-               insertion += ")";
+                  insertion += "'" + objectid + "',";
+                  insertion += "'" + log_service + "',";
+                  insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
+                  insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+                  insertion += "'" + SQL_Escape(raw) + "',FALSE";
+                  insertion += ")";
 
-               if (testmode)
-               {
-                  std::cout << insertion << "\n";
-               }
-               else
-               {
-                  database.Query(insertion);
-               }
+                  if (testmode)
+                  {
+                     *Log << insertion << "\n";
+                  }
+                  else
+                  {
+                     database.Query(insertion);
+                  }
 
-               if (verbose)
-               {
-                  std::cout << "\n\n";
                }
-
-               nr_lines++;
-            }
-            else
-            {
-               std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
-            }
-            break;
-
-         case ACCESSLOG:
-            datestring = line(regex("\\[.+\\]"));
-            datestring <<= 1;
-            datestring >>= 1;
-            datestring[datestring.index(':')] = ' ';
-            log_date = datestring;
-            log_time = datestring;
-            if (verbose)
-            {
-               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
-            }
-            insertion += "'" + objectid + "',";
-            insertion += "'" + service + "',";
-            insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
-            insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
-            insertion += "'" + SQL_Escape(line) + "',FALSE";
-            insertion += ")";
-
-            if (testmode)
-            {
-               std::cout << insertion << "\n";
-            }
-            else
-            {
-               database.Query(insertion);
             }
+            node = node->next;
+         }
+      }
+      else if (strcmp((char *)node->name, "parameters") == 0)
+      {
+         //  Each child contains a parameter entry, with at least one property
 
-            if (verbose)
-            {
-               std::cout << "\n\n";
-            }
+         String   qry;
+         String   insertion;
+         String   change_notification("");
+         String   create_notification("");
+         String   remove_notification("");
+         bool     initial_entry = false;
+         String   param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
 
-            nr_lines++;
-            break;
+         pathcontext->node = node;
 
-         case ERRORLOG:
-            datestring = line(regex("\\[.+\\]"));
-            datestring <<= 1;
-            datestring >>= 1;
-            log_date = datestring;
-            log_time = datestring;
-            if (verbose)
-            {
-               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
-            }
-            insertion += "'" + objectid + "',";
-            insertion += "'" + service + "',";
-            insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
-            insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
-            insertion += "'" + SQL_Escape(line) + "',FALSE";
-            insertion += ")";
-
-            if (testmode)
-            {
-               std::cout << insertion << "\n";
-            }
-            else
-            {
-               database.Query(insertion);
-            }
+         //  If we don;t have any parameters of this class, this will be
+         //  an initial entry.
 
-            if (verbose)
-            {
-               std::cout << "\n\n";
-            }
+         qry = "select name from parameter where objectid='";
+         qry += objectid + "' and class='" + param_class + "'";
+         initial_entry = database.Query(qry) == 0;
 
-            nr_lines++;
-            break;
-
-         case RPMLIST:
-            //  Scan a list of packages and versions from "rpm -a".
-            //  A similar listing can be created on IRIX 6.5 by using the
-            //  command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
-            //            |grep -v Version-Description".
-            //
-            //  We have to separate the package name and the version.
-            //  The separation is marked by a '-', followed by a digit.
-            //  However, there may be other sequences of '-'digit in the package name,
-            //  do we have to scan ahead until there is at most one such sequence
-            //  left in the version string. The '-'digit seqeunce inside the
-            //  version usually separates the version and the release number.
-
-            int  version_start, next_version_start;
-
-            i = line.index('-');
-            version_start = i;
-            next_version_start = i;
-
-            while (i < ~line - 1)
-            {
-               while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
-               {
-                  i++;
-               }
-               if (i < ~line - 1)
-               {
-                  version_start = next_version_start;
-                  next_version_start = i;
-               }
-               i++;
-            }
-            
-            if (!isdigit(line[version_start + 1]))
+         node = node->children;
+         while (node != NULL)
+         {
+            if (node->type == XML_ELEMENT_NODE &&
+                strcmp((char *)node->name, "parameter") == 0)
             {
-               version_start = next_version_start;
-            }
-            String package(line(0,version_start));
-            String version(line(version_start + 1, ~line));
-            String paramid;
-            String remark;
-            String insert_h;
+               String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
 
-            if (verbose)
-            {
-               std::cout << "Package is " << package;
-               std::cout << ", version is " << version << "\n";
-            }
+               std::list<param_property>  properties;
+               param_property             prop;
+               xmlNodePtr                 item;
 
-            //  Construct a qry to check the package's existance
+               String                     paramid;
 
-            qry = "select paramid from parameter where objectid='";
-            qry += objectid + "' and class='package' and name='";
-            qry += package + "'";
 
-            if (database.Query(qry) == 1)
-            {
-               std::list<String>::iterator  lp;
+               //  Collect the parameter's properties.
 
-               lp = find(packages.begin(), packages.end(), package);
-               if (lp != packages.end())
-               {
-                  packages.erase(lp);
-               }
-               else
+               item = node->children;
+               while (item != NULL)
                {
-                  std::cerr <<  "Could NOT find " << package << " in list.\n";
-               }
+                  if (item->type == XML_ELEMENT_NODE &&
+                      strcmp((char *)item->name, "property") == 0)
+                  {
+                     prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
+                     if (item->children != NULL)
+                     {
+                        prop.value = (const char *)item->children->content;
+                        properties.push_back(prop);
+                     }
+                     else
+                     {
+                        *Log << "WARNING: Property " << prop.name << " has no value.\n";
+                     }
+                  }
 
-               paramid = database.Field(0, "paramid");
-               qry = "select value from property where paramid='";
-               qry += paramid + "' and name='version'";
-               if (database.Query(qry) == 0)
-               {
-                  std::cerr << "Database corruption: Package " << package;
-                  std::cerr << " does not have a 'version' property.\n";
+                  //  TODO: Hanlde description element
+
+                  item = item->next;
                }
-               else if (database.Field(0, "value") != version)
+
+               //  Check the parameter in the database.
+
+               std::list<param_property>::iterator pi = properties.begin();
+
+               qry = "select paramid from parameter where objectid='";
+               qry += objectid + "' and class='";
+               qry += param_class + "' and name='";
+               qry += param_name + "'";
+
+               if (database.Query(qry) == 1)
                {
-                  if (verbose)
+                  //  The parameter exists in the database; check all properties.
+
+                  bool  param_changed = false;
+
+                  paramid = database.Field(0, "paramid");
+                  while (pi != properties.end())
                   {
-                     std::cout << "  Parameter " << package << " has different version\n";
-                  }
-                  insertion = "update property set value='";
-                  insertion += version + "' where paramid='";
-                  insertion += paramid + "' and name='version'";
+                     qry = "select value from property where paramid='";
+                     qry += paramid + "' and name='";
+                     qry += pi->name + "'";
+                     if (database.Query(qry) == 0)
+                     {
+                        *Log << "Property " << pi->name << " of "
+                             << param_name << " does not exist.\n";
+                     }
+                     else if (database.Field(0, "value") != pi->value)
+                     {
+                        *Log << "Property " << pi->name << " of "
+                             << param_name << " is different.\n";
 
-                  insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
-                  insert_h += " values ('";
-                  insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
-                  insert_h += version + "')";
+                        insertion = "update property set value='";
+                        insertion += pi->value + "' where paramid='";
+                        insertion += paramid + "' and name='";
+                        insertion += pi->name + "'";
 
-                  database.Query(insertion);
-                  database.Query(insert_h);
+                        database.Query(insertion);
 
-                  if (change_notification == "")
-                  {
-                     remark = "Gnucomo detected a different version for package parameter(s) ";
-                     change_notification = database.new_notification(objectid, "property modified", remark);
-                  }
+                        insertion = "insert into history (paramid, modified,";
+                        insertion += " change_nature, changed_property, new_value)";
+                        insertion += " values ('";
+                        insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+                                             + "', 'MODIFIED', '";
+                        insertion += pi->name + "', '";
+                        insertion += pi->value + "')";
 
-                  if (change_notification != "")
-                  {
-                     insertion = "insert into parameter_notification (notificationid, paramid) values ('";
-                     insertion += change_notification + "', '";
-                     insertion += paramid + "')";
+                        database.Query(insertion);
 
-                     database.Query(insertion);
+                        param_changed = true;
+
+
+                     }
+                     pi++;
                   }
-                  else
+
+                  if (param_changed)
                   {
-                     std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+                     if (change_notification == "")
+                     {
+                        remark = "Gnucomo detected a different version for package parameter(s) ";
+                        change_notification = database.new_notification(objectid,
+                                                           "property modified", remark);
+                     }
+
+                     if (change_notification != "")
+                     {
+                        qry = "select * from parameter_notification where notificationid='";
+                        qry += change_notification + "' and paramid='";
+                        qry += paramid + "'";
+
+                        if (database.Query(qry) == 0)
+                        {
+                           insertion = "insert into parameter_notification";
+                           insertion += " (notificationid, paramid) values ('";
+                           insertion += change_notification + "', '";
+                           insertion += paramid + "')";
+
+                           database.Query(insertion);
+                        }
+                     }
+                     else
+                     {
+                        *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+                     }
                   }
                }
                else
                {
-                  if (verbose)
-                  {
-                     std::cout << "   Parameter " << package << " has not changed.\n";
-                  }
-               }
-            }
-            else
-            {
+                  //   The parameter does not exist; create anew.
 
-               if (verbose)
-               {
-                  std::cout << "  Parameter " << package << " does not exist.\n";
-               }
-               //  Create a new package parameter, including version property and history record
+                  // TODO: Insert description
 
-               insertion = "insert into parameter (objectid, name, class, description) values ('";
-               insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
-               if (testmode)
-               {
-                  paramid = "0";
-                  std::cout << insertion << "\n";
-               }
-               else
-               {
+                  insertion = "insert into parameter (objectid, name, class, description) values ('";
+                  insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
                   database.Query(insertion);
+
                   qry = "select paramid from parameter where objectid='";
-                  qry += objectid + "' and class='package' and name='";
-                  qry += package + "'";
+                  qry += objectid + "' and class='";
+                  qry += param_class + "' and name='";
+                  qry += param_name + "'";
                   database.Query(qry);
                   paramid = database.Field(0, "paramid");
-               }
 
-               insertion = "insert into property (paramid, name, value, type) values ('";
-               insertion += paramid + "', 'version', '";
-               insertion += version + "', 'STATIC')";
-               insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
-               insert_h += " values ('";
-               insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
-               insert_h += version + "')";
+                  while (pi != properties.end())
+                  {
+                     insertion = "insert into property (paramid, name, value, type) values ('";
+                     insertion += paramid + "', '";
+                     insertion += pi->name + "', '";
+                     insertion += pi->value + "', 'STATIC')";
+                     database.Query(insertion);
+
+                     insertion = "insert into history (paramid, modified,";
+                     insertion += " change_nature, changed_property, new_value)";
+                     insertion += " values ('";
+                     insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+                                          + "', 'CREATED', '";
+                     insertion += pi->name + "', '";
+                     insertion += pi->value + "')";
+                     database.Query(insertion);
+
+                     pi++;
+                  }
 
-               if (testmode)
-               {
-                  std::cout << insertion << "\n" << insert_h << "\n";
-               }
-               else
-               {
-                  database.Query(insertion);
-                  database.Query(insert_h);
                   if (!initial_entry)
                   {
                      if (create_notification == "")
                      {
                         remark = "Gnucomo detected new parameter(s) of class package";
-                        create_notification = database.new_notification(objectid, "parameter created", remark);
+                        create_notification = database.new_notification(objectid,
+                                                         "parameter created", remark);
                      }
                      if (create_notification != "")
                      {
-                        insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+                        insertion = "insert into parameter_notification";
+                        insertion += " (notificationid, paramid) values ('";
                         insertion += create_notification + "', '";
                         insertion += paramid + "')";
 
@@ -818,130 +811,138 @@ int client_message::enter()
                      }
                      else
                      {
-                        std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
+                        *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
                      }
                   }
                }
             }
 
-            if (verbose)
-            {
-               std::cout << "\n";
-            }
-
-            nr_lines++;
-            break;
-
+            node = node->next;
          }
-      }
-      else
-      {
-         std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
-      }
-   }
 
-   if (classification == RPMLIST)
-   {
-      std::list<String>::iterator  lp;
-      String     remove_notification("");
+         if (!incremental)
+         {
+            //  Check if any parameters in this class have disappeared.
 
-      /*
-       *     If there are any packages left in the list, they seem to have
-       *     disappeared from the system.
-       */
+            qry = "select name, paramid from parameter where objectid='";
+            qry += objectid + "' and class='" + param_class + "'";
 
-      for (lp = packages.begin(); lp != packages.end(); lp++)
-      {
-         String paramid;
-         String remark;
-         String insert;
+            int          nr_parameters = database.Query(qry);
+            pqxx::Result parameter_set = database.Result();
 
-         //  Construct a qry to check the package's existance
+            for (int i = 0; i < nr_parameters; i++)
+            {
+               String XPath;
+               String param_name, paramid;
 
-         qry = "select paramid from parameter where objectid='";
-         qry += objectid + "' and class='package' and name='";
-         qry += *lp + "'";
+               param_name = database.Field(parameter_set, i, "name");
+               XPath = "gcmt:parameter[@name='" + param_name + "']";
 
-         if (database.Query(qry) == 1)
-         {
-            paramid = database.Field(0, "paramid");
-            qry ="select change_nature from history where paramid='";
-            qry += paramid + "' order by modified desc";
-            if (database.Query(qry) <= 0)
-            {
-               std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
-            }
-            else if (database.Field(0, "change_nature") != "REMOVED")
-            {
-               if (verbose)
+               res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
+               if (res->nodesetval->nodeTab == NULL)
                {
-                 std::cout << "Removing parameter " << *lp << ".\n";
-               }
+                  // The parameter is in the database but not in the report
+
+                  paramid = database.Field(parameter_set, i, "paramid");
+                  qry ="select change_nature from history where paramid='";
+                  qry += paramid + "' order by modified desc";
+                  if (database.Query(qry) <= 0)
+                  {
+                     *Log << "Database ERROR: no history record for parameter "
+                          << param_name << ".\n";
+                  }
+                  else if (database.Field(0, "change_nature") != "REMOVED")
+                  {
+                     if (verbose)
+                     {
+                       *Log << "Removing parameter " << param_name << ".\n";
+                     }
 
-               insert = "insert into history (paramid, modified, change_nature)";
-               insert += " values ('";
-               insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
+                     insertion = "insert into history (paramid, modified, change_nature)";
+                     insertion += " values ('";
+                     insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+                                          + "', 'REMOVED')";
 
-               database.Query(insert);
+                     database.Query(insertion);
 
-               if (remove_notification == "")
-               {
-                  remark = "Gnucomo detected that package(s) have disappeared ";
-                  remove_notification = database.new_notification(objectid, "parameter removed", remark);
-               }
+                     if (remove_notification == "")
+                     {
+                        remark = "Gnucomo detected that package(s) have disappeared ";
+                        remove_notification = database.new_notification(objectid,
+                                                         "parameter removed", remark);
+                     }
 
-               if (remove_notification != "")
-               {
-                  insert = "insert into parameter_notification (notificationid, paramid) values ('";
-                  insert += remove_notification + "', '";
-                  insert += paramid + "')";
+                     if (remove_notification != "")
+                     {
+                        insertion = "insert into parameter_notification";
+                        insertion += " (notificationid, paramid) values ('";
+                        insertion += remove_notification + "', '";
+                        insertion += paramid + "')";
 
-                  database.Query(insert);
-               }
-               else
-               {
-                  std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+                        database.Query(insertion);
+                     }
+                     else
+                     {
+                        *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+                     }
+                  }
                }
             }
          }
       }
+      else
+      {
+         *Log << "Data element " << node->name << " is not supported.\n";
+      }
    }
-
-   if (verbose)
+   else
    {
-      std::cout << nr_lines << " lines parsed from the log file.\n";
+      *Log << "Data node not found.\n";
    }
-   return nr_lines;
 }
 
 /*=========================================================================
-**  NAME           : SQL_Escape
-**  SYNOPSIS       : String SQL_Escape(String)
+**  NAME           : enter
+**  SYNOPSIS       : int enter()
 **  PARAMETERS     : 
-**  RETURN VALUE   : 
+**  RETURN VALUE   : The number of lines successfully parsed from the input
 **
-**  DESCRIPTION    : Insert backslashes before single quotes.
+**  DESCRIPTION    : 
 **
 **  VARS USED      :
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : 
+**  LAST MODIFIED  : Nov 26, 2003
 **=========================================================================
 */
 
-String SQL_Escape(String s)
+int client_message::enter()
 {
-   int i;
+   pan.mf->set_message_type(pan.lc->message_type());
+
+   pan.mf->construct_XML(input, xmlBuffer);
 
-   for (i = 0; i < ~s; i++)
+#ifdef DEBUG
+   *Log << "Constructed XML document:\n\n";
+   *Log << xmlBuffer.str();
+   *Log << "\n";
+#endif
+
+   xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
+
+   if (xmlDom)
    {
-      if (s[i] == '\'')
+      if (extractHeader())
       {
-         s(i,0) = "\\";
-         i++;
+         enterXML();
       }
    }
+   else
+   {
+      *Log << "XML parser FAILED.\n";
+   }
+
+   return 0;
 
-   return s;
 }