Read XML input,
[gnucomo.git] / src / gcm_input / message.cpp
index 9b1ef1a..5951f21 100644 (file)
@@ -1,12 +1,14 @@
 
 /**************************************************************************
 **  (c) Copyright 2002, Andromeda Technology & Automation
+** This is free software; you can redistribute it and/or modify it under the
+** terms of the GNU General Public License, see the file COPYING.
 ***************************************************************************
 ** MODULE INFORMATION *
 ***********************
 **      FILE NAME      : message.cpp
 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
-**      VERSION NUMBER : $Revision: 1.1 $
+**      VERSION NUMBER : $Revision: 1.10 $
 **
 **  DESCRIPTION      :  Implementation of the message handling classes
 **
 ********************************
 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
 **      CREATION DATE   : Sep 16, 2002
-**      LAST UPDATE     : Oct 05, 2002
+**      LAST UPDATE     : Apr 29, 2003
 **      MODIFICATIONS   : 
 **************************************************************************/
 
 /*****************************
    $Log: message.cpp,v $
-   Revision 1.1  2002-10-05 10:25:49  arjen
+   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.
+
+   Revision 1.8  2003/03/16 09:42:40  arjen
+   Read IRIX system logs.
+
+   Revision 1.7  2003/02/21 08:08:05  arjen
+   Gcm_input also detects packages that are removed from the system.
+   Determining the version number of a package in a RPM
+   list is improved. Only the last one or two parts of the string that
+   begin with a '-' and a number are considered the version.
+
+   Revision 1.6  2003/02/05 09:37:51  arjen
+   Create notifications when a new package is discovered
+   in a 'rpm -qa' list or when the version of a package is changed.
+
+   Revision 1.4  2002/12/06 22:26:28  arjen
+   Set the value of log.processed to FALSE when inserting a
+   new log entry into the database
+   When a syslog entry arrives from last year, gcm_input subtracts one from the
+   year of arrival to create the year of the log entry.
+   Read output from "rpm -qa" and enter packages in the parameter table.
+
+   Revision 1.3  2002/11/09 08:04:27  arjen
+   Added a reference to the GPL
+
+   Revision 1.2  2002/11/04 10:13:36  arjen
+   Use proper namespace for iostream classes
+
+   Revision 1.1  2002/10/05 10:25:49  arjen
    Creation of gcm_input and a first approach to a web interface
 
 *****************************/
 
-static const char *RCSID = "$Id: message.cpp,v 1.1 2002-10-05 10:25:49 arjen Exp $";
+static const char *RCSID = "$Id: message.cpp,v 1.10 2003-04-29 09:16:44 arjen Exp $";
 
+#include <algorithm>
+#include <libxml/xpath.h>
+#include <libxml/debugXML.h>
 #include "message.h"
 
 extern bool verbose;   /*  Defined in the main application */
@@ -52,7 +91,7 @@ String SQL_Escape(String s);
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Sep 30, 2002
+**  LAST MODIFIED  : Nov 04, 2002
 **=========================================================================
 */
 
@@ -64,21 +103,18 @@ bool operator >> (message_buffer &b, String &s)
    {
       String   l;
 
-      //cout << "     buffer is depleted.\n";
       if (*(b.input) >> l)
       {
          b.buffer.push_back(l);
 
          //   next_line keeps pointing to the end.
+
          s = l;
          input_ok = true;
-         //cout << "     new line from input.\n";
       }
    }
    else
    {
-      //cout << "    reading from cache.\n";
       s = *(b.next_line);
       b.next_line++;
       input_ok = true;
@@ -86,7 +122,7 @@ bool operator >> (message_buffer &b, String &s)
    return input_ok;
 }
 
-client_message::client_message(istream *in, gnucomo_database db)
+client_message::client_message(std::istream *in, gnucomo_database db)
 {
    input.from(in);
    database = db;
@@ -95,25 +131,100 @@ client_message::client_message(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 + " [a-z]+ [[:alpha:]]+.*:.+");
+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 " + unix_date_re);
+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           : readXMLinput
+**  SYNOPSIS       : int readXMLinput(String first_line)
+**  PARAMETERS     : 
+**  RETURN VALUE   : Parse the XML input and extract the header information
+**
+**  DESCRIPTION    : 
+**
+**  VARS USED      :
+**  VARS CHANGED   :
+**  FUNCTIONS USED :
+**  SEE ALSO       :
+**  LAST MODIFIED  : Apr 28, 2003
+**=========================================================================
+*/
+
+int client_message::readXMLinput(String first_line)
+{
+   xmlParserCtxtPtr ctxt;
+   String           line;
+   xmlNodePtr       root, item;
+
+   xmlXPathObjectPtr res;
+   xmlXPathContextPtr pathcontext;
+
+
+   ctxt = xmlCreatePushParserCtxt(NULL, NULL, first_line, ~first_line, NULL);
+   while (input >> line)
+   {
+      xmlParseChunk(ctxt, line, ~line, 0);
+   }
+   xmlParseChunk(ctxt, "", 0, 1);
+   xmlDom = ctxt->myDoc;
+   xmlFreeParserCtxt(ctxt);
+
+   root = xmlDocGetRootElement(xmlDom);
+   //TODO Ought to check  root->name and  root->ns->href 
+
+   pathcontext = xmlXPathNewContext(xmlDom);
+   pathcontext->node = xmlDocGetRootElement(xmlDom);
+
+   res = xmlXPathEval((const xmlChar *)"header/messagetype/text()", pathcontext);
+   if (res->nodesetval != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+   }
+   res = xmlXPathEval((const xmlChar *)"header/hostname/text()", pathcontext);
+   if (res->nodesetval != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+      hostname = (const char *)item->content;
+   }
+   res = xmlXPathEval((const xmlChar *)"header/service/text()", pathcontext);
+   if (res->nodesetval != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+      service = (const char *)item->content;
+   }
+   res = xmlXPathEval((const xmlChar *)"header/time/text()", pathcontext);
+   if (res->nodesetval != NULL)
+   {
+      item = *res->nodesetval->nodeTab;
+      arrival = String((char *)item->content);
+
+   }
+   //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+
+}
 
 /*=========================================================================
 **  NAME           : classify
@@ -127,7 +238,7 @@ static const regex re_email_user("[[:alnum:]_.-]+@");
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Oct 05, 2002
+**  LAST MODIFIED  : Apr 28, 2003
 **=========================================================================
 */
 
@@ -155,7 +266,19 @@ double client_message::classify(String host, UTC arriv, String serv)
          {
             from_address = line(re_email_address);
             from_address(re_email_user) = "";            //  Remove the user part;
-            hostname = from_address;
+            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)
          {
@@ -167,6 +290,7 @@ double client_message::classify(String host, UTC arriv, String serv)
    {
       //  Push the first line back, we need to read it again.
       --input;
+
    }
 
    /*
@@ -177,28 +301,51 @@ double client_message::classify(String host, UTC arriv, String serv)
 
    while (input >> line && certainty < 0.9)
    {
-      cout << "  testing: " << line << "\n";
-      if (line == re_syslog)
+      if (verbose)
+      {
+         std::cout << "  testing: " << line << "\n";
+      }
+
+      if (line == re_xml_header)
+      {
+         certainty = 1.0;
+         classification = XML;
+         if (verbose)
+         {
+            std::cout << "XML input detected.\n";
+         }
+         readXMLinput(line);
+      }
+      else if (line == re_syslog)
       {
          certainty = 1.0;
          classification = SYSLOG;
          if (verbose)
          {
-            cout << "Syslog detected.\n";
+            std::cout << "Syslog detected.\n";
+         }
+      }
+      else if (line == re_syslog_irix)
+      {
+         certainty = 1.0;
+         classification = SYSLOG_IRIX;
+         if (verbose)
+         {
+            std::cout << "IRIX Syslog detected.\n";
          }
       }
       else if (line == re_PGP)
       {
          certainty = 1.0;
          gpg_encrypted = true;
-         cerr << "The message is PGP/GnuPG encrypted.\n";
+         std::cerr << "The message is PGP/GnuPG encrypted.\n";
       }
       else if (line == re_dump)
       {
           certainty = 1.0;
           if (verbose)
           {
-             cout << "DUMP output detected.\n";
+             std::cout << "DUMP output detected.\n";
           }
       }
       else if (line == re_accesslog)
@@ -208,7 +355,7 @@ double client_message::classify(String host, UTC arriv, String serv)
           service = "httpd";
           if (verbose)
           {
-             cout << "HTTP access log detected.\n";
+             std::cout << "HTTP access log detected.\n";
           }
       }
       else if (line == re_errorlog)
@@ -218,7 +365,17 @@ double client_message::classify(String host, UTC arriv, String serv)
           service = "httpd";
           if (verbose)
           {
-             cout << "HTTP error log detected.\n";
+             std::cout << "HTTP error log detected.\n";
+          }
+      }
+      else if (line == re_rpm)
+      {
+          certainty = 1.0;
+          classification = RPMLIST;
+          service = "";
+          if (verbose)
+          {
+             std::cout << "RPM package list detected.\n";
           }
       }
    }
@@ -226,12 +383,12 @@ double client_message::classify(String host, UTC arriv, String serv)
 
    if (hostname == "")
    {
-      cerr <<  "Can not determine the hostname where the message came from.\n";
+      std::cerr <<  "Can not determine the hostname where the message came from.\n";
       certainty = 0.0;
    }
    else if (!arrival.proper())
    {
-      cerr << "Arrival time is not knwon.\n";
+      std::cerr << "Arrival time is not knwon.\n";
       certainty = 0.0;
    }
    else
@@ -243,6 +400,178 @@ double client_message::classify(String host, UTC arriv, String serv)
 }
 
 /*=========================================================================
+**  NAME           : enterXML
+**  SYNOPSIS       : int enterXML()
+**  PARAMETERS     : 
+**  RETURN VALUE   : None
+**
+**  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  : Apr 29, 2003
+**=========================================================================
+*/
+
+void client_message::enterXML()
+{
+   xmlXPathObjectPtr res;
+   xmlXPathContextPtr pathcontext;
+
+   /*  Try to find the host in the database */
+
+   String objectid;
+
+   objectid = database.find_host(hostname);
+   if (objectid == "")
+   {
+      std::cerr << "Please define the host " << hostname << " in the database.\n";
+      return;
+   }
+   if (verbose)
+   {
+      std::cout << "Object id for " << hostname << " is " << objectid << "\n";
+   }
+
+   pathcontext = xmlXPathNewContext(xmlDom);
+   pathcontext->node = xmlDocGetRootElement(xmlDom);
+   res = xmlXPathEval((const xmlChar *)"data/node()", pathcontext);
+
+   if (res->nodesetval != NULL)
+   {
+      //  Find the first child element of the <data> element.
+
+      xmlNodePtr  node = *res->nodesetval->nodeTab;
+      while (node->type != XML_ELEMENT_NODE)
+      {
+         node = node->next;
+      }
+      if (strcmp((char *)node->name, "log") == 0)
+      {
+         //  Each child contains a log entry, raw or cooked.
+
+         node = node->children;
+         while (node != NULL)
+         {
+            if (node->type == XML_ELEMENT_NODE)
+            {
+               if (strcmp((char *)node->name, "raw") == 0)
+               {
+                  std::cerr << "Can not cook <raw> log elements yet.\n";
+               }
+               else if (strcmp((char *)node->name, "cooked") == 0)
+               {
+                  //  Find the parts of the log entry
+
+                  xmlNodePtr  item;
+                  String      log_hostname;
+                  UTC         log_date;
+                  String      raw("");;
+                  String      log_service;
+
+                  if (verbose)
+                  {
+                     std::cout << "Analyzing cooked element.\n";
+                  }
+                  pathcontext->node = node;
+
+                  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))
+                     {
+                        std::cerr << "Hostname " << log_hostname << " does not match.\n";
+                        log_hostname = "";
+                     }
+                  }
+                  else
+                  {
+                     log_hostname = hostname;
+                  }
+
+                  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;
+                  }
+
+                  res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
+                  if (res->nodesetval != NULL)
+                  {
+                     item = *res->nodesetval->nodeTab;
+                     log_date = String((const char *)item->content);
+                  }
+                  else
+                  {
+                     std::cerr << "<timestamp> missing from cooked log element.\n";
+                  }
+
+                  res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
+                  if (res->nodesetval != NULL)
+                  {
+                     item = *res->nodesetval->nodeTab;
+                     raw = String((const char *)item->content);
+                  }
+                  else
+                  {
+                     std::cerr << "<raw> missing from cooked log element.\n";
+                  }
+
+                  if (raw != "" && log_hostname != "" && log_date.proper())
+                  {
+                     String insertion("insert into log (objectid, servicecode,"
+                           " object_timestamp, timestamp, rawdata, processed) values (");
+
+                     /*   Insert a new record into the log table   */
+
+                     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 (verbose)
+                     {
+                        std::cout << "\n\n";
+                     }
+                  }
+               }
+            }
+            node = node->next;
+         }
+      }
+      else
+      {
+         std::cerr << "Data element " << node->name << " is not supported.\n";
+      }
+   }
+   else
+   {
+      std::cerr << "Data node not found.\n";
+   }
+}
+
+/*=========================================================================
 **  NAME           : enter
 **  SYNOPSIS       : int enter()
 **  PARAMETERS     : 
@@ -254,14 +583,28 @@ double client_message::classify(String host, UTC arriv, String serv)
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Oct 05, 2002
+**  LAST MODIFIED  : Mar 28, 2003
 **=========================================================================
 */
 
 int client_message::enter()
 {
+   if (classification == XML)
+   {
+      enterXML();
+      return 1;
+   }
+
    long   nr_lines = 0;
    String line;
+   String qry;
+
+   String change_notification("");
+   String create_notification("");
+   bool initial_entry = false;
+
+   std::list<String>  packages;
+
 
    /*  Double-check the classification of the message */
 
@@ -273,7 +616,7 @@ int client_message::enter()
    if (mail_header)
    {
       //  Skip the mail header.
+
       while (input >> line && line != "");
    }
 
@@ -284,12 +627,33 @@ int client_message::enter()
    objectid = database.find_host(hostname);
    if (objectid == "")
    {
-      cerr << "Please define the host " << hostname << " in the database.\n";
+      std::cerr << "Please define the host " << hostname << " in the database.\n";
       return 0;
    }
    if (verbose)
    {
-      cout << "Object id for " << hostname << " is " << objectid << "\n";
+      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";
    }
 
    /*  Scan the input line by line, entring records into the database */
@@ -300,7 +664,7 @@ int client_message::enter()
    {
       if (verbose)
       {
-         cout << line << "\n";
+         std::cout << line << "\n";
       }
 
 
@@ -313,12 +677,18 @@ int client_message::enter()
       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;
       }
 
       if (line == *check)
@@ -328,7 +698,7 @@ int client_message::enter()
          int    i;
 
          String insertion("insert into log (objectid, servicecode,"
-                           " object_timestamp, timestamp, rawdata) values (");
+                           " object_timestamp, timestamp, rawdata, processed) values (");
          String datestring;
 
          switch (classification)
@@ -338,14 +708,20 @@ int client_message::enter()
             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
+               //  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.
 
                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)
             {
-               cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
+               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
             }
             rest = line << 16;
             i = rest.index(' ');
@@ -354,27 +730,94 @@ int client_message::enter()
                rest <<= i + 1;
                if (verbose)
                {
-                  cout << "   Hostname matches.\n";
-                  cout << "   rest = " << rest << "\n";
+                  std::cout << "   Hostname matches.\n";
+                  std::cout << "   rest = " << rest << "\n";
                }
                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
                if (verbose)
                {
-                  cout << "   Service name = " << rest(0,i) << "\n";
+                  std::cout << "   Service name = " << rest(0,i) << "\n";
                }
 
                /*   Insert a new record into the log table   */
 
                insertion += "'" + objectid + "',";
                insertion += "'" + rest(0,i) + "',";
-               insertion += "'" + log_date.format() + " " + log_time.format() + "',";
-               insertion += "'" + arrival.format() + "',";
-               insertion += "'" + SQL_Escape(line) + "'";
+               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 (verbose)
+               {
+                  std::cout << "\n\n";
+               }
+
+               nr_lines++;
+            }
+            else
+            {
+               std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
+            }
+            break;
+
+         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.
+
+               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)
+               {
+                  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";
+               }
+
+               /*   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 (testmode)
                {
-                  cout << insertion << "\n";
+                  std::cout << insertion << "\n";
                }
                else
                {
@@ -383,14 +826,14 @@ int client_message::enter()
 
                if (verbose)
                {
-                  cout << "\n\n";
+                  std::cout << "\n\n";
                }
 
                nr_lines++;
             }
             else
             {
-               cerr << "   Hostname " << rest(0,i) << " does not match.\n";
+               std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
             }
             break;
 
@@ -403,18 +846,18 @@ int client_message::enter()
             log_time = datestring;
             if (verbose)
             {
-               cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
+               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
             }
             insertion += "'" + objectid + "',";
             insertion += "'" + service + "',";
-            insertion += "'" + log_date.format() + " " + log_time.format() + "',";
-            insertion += "'" + arrival.format() + "',";
-            insertion += "'" + SQL_Escape(line) + "'";
+            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)
             {
-               cout << insertion << "\n";
+               std::cout << insertion << "\n";
             }
             else
             {
@@ -423,7 +866,7 @@ int client_message::enter()
 
             if (verbose)
             {
-               cout << "\n\n";
+               std::cout << "\n\n";
             }
 
             nr_lines++;
@@ -437,18 +880,18 @@ int client_message::enter()
             log_time = datestring;
             if (verbose)
             {
-               cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
+               std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
             }
             insertion += "'" + objectid + "',";
             insertion += "'" + service + "',";
-            insertion += "'" + log_date.format() + " " + log_time.format() + "',";
-            insertion += "'" + arrival.format() + "',";
-            insertion += "'" + SQL_Escape(line) + "'";
+            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)
             {
-               cout << insertion << "\n";
+               std::cout << insertion << "\n";
             }
             else
             {
@@ -457,22 +900,285 @@ int client_message::enter()
 
             if (verbose)
             {
-               cout << "\n\n";
+               std::cout << "\n\n";
             }
 
             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]))
+            {
+               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;
+
+            if (verbose)
+            {
+               std::cout << "Package is " << package;
+               std::cout << ", version is " << version << "\n";
+            }
+
+            //  Construct a qry to check the package's existance
+
+            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;
+
+               lp = find(packages.begin(), packages.end(), package);
+               if (lp != packages.end())
+               {
+                  packages.erase(lp);
+               }
+               else
+               {
+                  std::cerr <<  "Could NOT find " << package << " in list.\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";
+               }
+               else if (database.Field(0, "value") != version)
+               {
+                  if (verbose)
+                  {
+                     std::cout << "  Parameter " << package << " has different version\n";
+                  }
+                  insertion = "update property set value='";
+                  insertion += version + "' where paramid='";
+                  insertion += paramid + "' and name='version'";
+
+                  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 + "')";
+
+                  database.Query(insertion);
+                  database.Query(insert_h);
+
+                  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 != "")
+                  {
+                     insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+                     insertion += change_notification + "', '";
+                     insertion += paramid + "')";
+
+                     database.Query(insertion);
+                  }
+                  else
+                  {
+                     std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+                  }
+               }
+               else
+               {
+                  if (verbose)
+                  {
+                     std::cout << "   Parameter " << package << " has not changed.\n";
+                  }
+               }
+            }
+            else
+            {
+
+               if (verbose)
+               {
+                  std::cout << "  Parameter " << package << " does not exist.\n";
+               }
+               //  Create a new package parameter, including version property and history record
+
+               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
+               {
+                  database.Query(insertion);
+                  qry = "select paramid from parameter where objectid='";
+                  qry += objectid + "' and class='package' and name='";
+                  qry += package + "'";
+                  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 + "')";
+
+               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);
+                     }
+                     if (create_notification != "")
+                     {
+                        insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+                        insertion += create_notification + "', '";
+                        insertion += paramid + "')";
+
+                        database.Query(insertion);
+                     }
+                     else
+                     {
+                        std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
+                     }
+                  }
+               }
+            }
+
+            if (verbose)
+            {
+               std::cout << "\n";
+            }
+
+            nr_lines++;
+            break;
+
          }
       }
       else
       {
-         cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
+         std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
+      }
+   }
+
+   if (classification == RPMLIST)
+   {
+      std::list<String>::iterator  lp;
+      String     remove_notification("");
+
+      /*
+       *     If there are any packages left in the list, they seem to have
+       *     disappeared from the system.
+       */
+
+      for (lp = packages.begin(); lp != packages.end(); lp++)
+      {
+         String paramid;
+         String remark;
+         String insert;
+
+         //  Construct a qry to check the package's existance
+
+         qry = "select paramid from parameter where objectid='";
+         qry += objectid + "' and class='package' and name='";
+         qry += *lp + "'";
+
+         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)
+               {
+                 std::cout << "Removing parameter " << *lp << ".\n";
+               }
+
+               insert = "insert into history (paramid, modified, change_nature)";
+               insert += " values ('";
+               insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
+
+               database.Query(insert);
+
+               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 + "')";
+
+                  database.Query(insert);
+               }
+               else
+               {
+                  std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+               }
+            }
+         }
       }
    }
 
    if (verbose)
    {
-      cout << nr_lines << " lines parsed from the log file.\n";
+      std::cout << nr_lines << " lines parsed from the log file.\n";
    }
    return nr_lines;
 }