Do not add another parameter_notification record is the notification
[gnucomo.git] / src / gcm_input / message.cpp
index 5951f21..1c36a27 100644 (file)
@@ -8,7 +8,7 @@
 ***********************
 **      FILE NAME      : message.cpp
 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
-**      VERSION NUMBER : $Revision: 1.10 $
+**      VERSION NUMBER : $Revision: 1.15 $
 **
 **  DESCRIPTION      :  Implementation of the message handling classes
 **
 ********************************
 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
 **      CREATION DATE   : Sep 16, 2002
-**      LAST UPDATE     : Apr 29, 2003
+**      LAST UPDATE     : Jul 24, 2003
 **      MODIFICATIONS   : 
 **************************************************************************/
 
 /*****************************
    $Log: message.cpp,v $
-   Revision 1.10  2003-04-29 09:16:44  arjen
+   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.
 
 
 *****************************/
 
-static const char *RCSID = "$Id: message.cpp,v 1.10 2003-04-29 09:16:44 arjen Exp $";
+static const char *RCSID = "$Id: message.cpp,v 1.15 2003-10-27 11:28:27 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 >>
@@ -135,27 +164,20 @@ client_message::client_message(std::istream *in, gnucomo_database db)
    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 .*\?>$");
+static const regex re_xml_header("xml .*\?>$");
 
 /*=========================================================================
 **  NAME           : readXMLinput
@@ -169,7 +191,7 @@ static const regex re_xml_header("\?xml .*\?>$");
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Apr 28, 2003
+**  LAST MODIFIED  : Jul 24, 2003
 **=========================================================================
 */
 
@@ -178,6 +200,7 @@ int client_message::readXMLinput(String first_line)
    xmlParserCtxtPtr ctxt;
    String           line;
    xmlNodePtr       root, item;
+   xmlNsPtr         namespaces[1];
 
    xmlXPathObjectPtr res;
    xmlXPathContextPtr pathcontext;
@@ -193,29 +216,74 @@ int client_message::readXMLinput(String first_line)
    xmlFreeParserCtxt(ctxt);
 
    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;
 
-   res = xmlXPathEval((const xmlChar *)"header/messagetype/text()", pathcontext);
+#ifdef DEBUG
+   xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
+#endif
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
    if (res->nodesetval != NULL)
    {
+#ifdef DEBUG
+      xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+#endif
       item = *res->nodesetval->nodeTab;
+
+      //  Select a line cooker based on the message type.
+
+#ifdef DEBUG
+      std::cout << "Looking for a line cooker for " << item->content << "\n";
+#endif
+      std::list<line_cooker *>::iterator lci = kitchen.begin();
+      pan = 0;
+      while (pan == 0 && lci != kitchen.end())
+      {
+         pan = *lci;
+         if (pan->message_type() != (const char *)(item->content))
+         {
+            pan = 0;
+         }
+         lci++;
+      }
+      if (pan == 0)
+      {
+         *log << "Can not find a line cooker for message type " << item->content << "\n";
+      }
    }
-   res = xmlXPathEval((const xmlChar *)"header/hostname/text()", pathcontext);
+   else
+   {
+      *log << "Message type not found in XML header.\n";
+   }
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
    if (res->nodesetval != NULL)
    {
+#ifdef DEBUG
+      xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+#endif
       item = *res->nodesetval->nodeTab;
       hostname = (const char *)item->content;
    }
-   res = xmlXPathEval((const xmlChar *)"header/service/text()", pathcontext);
+   else
+   {
+      *log << "Hostname not found in XML header.\n";
+   }
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
    if (res->nodesetval != NULL)
    {
       item = *res->nodesetval->nodeTab;
       service = (const char *)item->content;
    }
-   res = xmlXPathEval((const xmlChar *)"header/time/text()", pathcontext);
+   res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
    if (res->nodesetval != NULL)
    {
       item = *res->nodesetval->nodeTab;
@@ -238,7 +306,7 @@ int client_message::readXMLinput(String first_line)
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Apr 28, 2003
+**  LAST MODIFIED  : Aug 11, 2003
 **=========================================================================
 */
 
@@ -266,8 +334,9 @@ 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;
-            if (from_address != "")
+            if (from_address != "" && ~hostname < ~from_address)
             {
+               *log << "Detected hostname " << from_address << "\n";
                hostname = from_address;
             }
          }
@@ -275,9 +344,10 @@ 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;
-            if (from_address != "")
+            if (from_address != "" && ~hostname < ~from_address)
             {
-              hostname = from_address;
+               *log << "Detected hostname " << from_address << "\n";
+               hostname = from_address;
             }
          }
          if (line == re_mail_Date)
@@ -293,6 +363,8 @@ double client_message::classify(String host, UTC arriv, String serv)
 
    }
 
+   pan = 0;
+
    /*
     *  Now that we have the mail header out of the way, try to figure
     *  out what the content of the message is.
@@ -303,7 +375,7 @@ double client_message::classify(String host, UTC arriv, String serv)
    {
       if (verbose)
       {
-         std::cout << "  testing: " << line << "\n";
+         *log << "  testing: " << line << "\n";
       }
 
       if (line == re_xml_header)
@@ -312,60 +384,22 @@ double client_message::classify(String host, UTC arriv, String serv)
          classification = XML;
          if (verbose)
          {
-            std::cout << "XML input detected.\n";
+            *log << "XML input detected.\n";
          }
          readXMLinput(line);
       }
-      else if (line == re_syslog)
-      {
-         certainty = 1.0;
-         classification = SYSLOG;
-         if (verbose)
-         {
-            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;
-         std::cerr << "The message is PGP/GnuPG encrypted.\n";
+         *log << "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";
-          }
-      }
-      else if (line == re_errorlog)
-      {
-          certainty = 1.0;
-          classification = ERRORLOG;
-          service = "httpd";
-          if (verbose)
-          {
-             std::cout << "HTTP error log detected.\n";
+             *log << "DUMP output detected.\n";
           }
       }
       else if (line == re_rpm)
@@ -375,20 +409,45 @@ double client_message::classify(String host, UTC arriv, String serv)
           service = "";
           if (verbose)
           {
-             std::cout << "RPM package list detected.\n";
+             *log << "RPM package list detected.\n";
           }
       }
+      else
+      {
+         //  Scan the list of line cookers if there is anything familiar.
+
+         std::list<line_cooker *>::iterator lci = kitchen.begin();
+         pan = 0;
+         while (pan == 0 && lci != kitchen.end())
+         {
+            pan = *lci;
+            if (!pan->check_pattern(line))
+            {
+               pan = 0;
+            }
+            lci++;
+         }
+         if (pan != 0)
+         {
+            certainty = 1.0;
+            classification = COOKER_OBJECT;
+            if (verbose)
+            {
+               *log << "Detected message type " << pan->message_type() << "\n";
+            }
+         }
+      }
    }
    input.rewind();
 
    if (hostname == "")
    {
-      std::cerr <<  "Can not determine the hostname where the message came from.\n";
+      *log <<  "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";
+      *log << "Arrival time is not known.\n";
       certainty = 0.0;
    }
    else
@@ -412,7 +471,7 @@ double client_message::classify(String host, UTC arriv, String serv)
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Apr 29, 2003
+**  LAST MODIFIED  : Jul 24, 2003
 **=========================================================================
 */
 
@@ -420,6 +479,7 @@ void client_message::enterXML()
 {
    xmlXPathObjectPtr res;
    xmlXPathContextPtr pathcontext;
+   xmlNsPtr           namespaces[1];
 
    /*  Try to find the host in the database */
 
@@ -428,17 +488,21 @@ void client_message::enterXML()
    objectid = database.find_host(hostname);
    if (objectid == "")
    {
-      std::cerr << "Please define the host " << hostname << " in the database.\n";
+      *log << "Please define the host " << hostname << " in the database.\n";
       return;
    }
    if (verbose)
    {
-      std::cout << "Object id for " << hostname << " is " << objectid << "\n";
+      *log << "Object id for " << hostname << " is " << objectid << "\n";
    }
 
    pathcontext = xmlXPathNewContext(xmlDom);
    pathcontext->node = xmlDocGetRootElement(xmlDom);
-   res = xmlXPathEval((const xmlChar *)"data/node()", pathcontext);
+   namespaces[0] = pathcontext->node->ns;
+   pathcontext->namespaces = namespaces;
+   pathcontext->nsNr       = 1;
+
+   res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
 
    if (res->nodesetval != NULL)
    {
@@ -458,23 +522,46 @@ void client_message::enterXML()
          {
             if (node->type == XML_ELEMENT_NODE)
             {
+               xmlNodePtr  item;
+               String      log_hostname;
+               UTC         log_date;
+               String      raw("");;
+               String      log_service;
+
                if (strcmp((char *)node->name, "raw") == 0)
                {
-                  std::cerr << "Can not cook <raw> log elements yet.\n";
+                  item = node->children;
+                  if (pan == 0)
+                  {
+                     *log << "Can not cook this type of <raw> log element.\n";
+                  }
+                  else
+                  {
+                     raw = String((const char *)item->content);
+                     if (pan->cook_this(raw, arrival))
+                     {
+                        log_hostname = pan->hostname();
+                        if (log_hostname == "")
+                        {
+                           log_hostname = hostname;
+                        }
+                        log_service = pan->service();
+                        log_date    = pan->timestamp();
+                     }
+                     else
+                     {
+                        *log << "Log line " << raw << " does not match.\n";
+                        raw = "";
+                     }
+                  }
                }
                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";
+                     *log << "Analyzing cooked element.\n";
                   }
                   pathcontext->node = node;
 
@@ -485,7 +572,7 @@ void client_message::enterXML()
                      log_hostname = (const char *)item->content;
                      if (log_hostname != hostname(0, ~log_hostname))
                      {
-                        std::cerr << "Hostname " << log_hostname << " does not match.\n";
+                        *log << "Hostname " << log_hostname << " does not match.\n";
                         log_hostname = "";
                      }
                   }
@@ -513,7 +600,7 @@ void client_message::enterXML()
                   }
                   else
                   {
-                     std::cerr << "<timestamp> missing from cooked log element.\n";
+                     *log << "<timestamp> missing from cooked log element.\n";
                   }
 
                   res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
@@ -524,50 +611,53 @@ void client_message::enterXML()
                   }
                   else
                   {
-                     std::cerr << "<raw> missing from cooked log element.\n";
+                     *log << "<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   */
+               //   Insert a new log record into the database.
+               if (raw != "" && log_hostname != "" && log_date.proper())
+               {
+                  String insertion("insert into log (objectid, servicecode,"
+                        " object_timestamp, timestamp, rawdata, processed) values (");
 
-                     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 += ")";
+                  /*   Insert a new record into the log table   */
 
-                     if (testmode)
-                     {
-                        std::cout << insertion << "\n";
-                     }
-                     else
-                     {
-                        database.Query(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 (verbose)
-                     {
-                        std::cout << "\n\n";
-                     }
+                  if (testmode)
+                  {
+                     *log << insertion << "\n";
+                  }
+                  else
+                  {
+                     database.Query(insertion);
+                  }
+
+                  if (verbose)
+                  {
+                     *log << "\n\n";
                   }
                }
             }
             node = node->next;
          }
       }
       else
       {
-         std::cerr << "Data element " << node->name << " is not supported.\n";
+         *log << "Data element " << node->name << " is not supported.\n";
       }
    }
    else
    {
-      std::cerr << "Data node not found.\n";
+      *log << "Data node not found.\n";
    }
 }
 
@@ -583,7 +673,7 @@ void client_message::enterXML()
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Mar 28, 2003
+**  LAST MODIFIED  : Jul 24, 2003
 **=========================================================================
 */
 
@@ -627,12 +717,12 @@ int client_message::enter()
    objectid = database.find_host(hostname);
    if (objectid == "")
    {
-      std::cerr << "Please define the host " << hostname << " in the database.\n";
+      *log << "Please define the host " << hostname << " in the database.\n";
       return 0;
    }
    if (verbose)
    {
-      std::cout << "Object id for " << hostname << " is " << objectid << "\n";
+      *log << "Object id for " << hostname << " is " << objectid << "\n";
    }
 
    if (classification == RPMLIST)
@@ -648,23 +738,28 @@ int client_message::enter()
       n_packages = database.Query(qry);
       initial_entry = n_packages == 0;
 
-      std::cout << n_packages << " packages in database.\n";
+#ifdef DEBUG
+      *log << n_packages << " packages in database.\n";
+#endif
       for (int t = 0; t < n_packages; t++)
       {
          packages.push_back(database.Field(t, "name"));
       }
-      std::cout << "Package list built: " << packages.size() << ".\n";
+#ifdef DEBUG
+      *log << "Package list built: " << packages.size() << ".\n";
+#endif
    }
 
    /*  Scan the input line by line, entring records into the database */
 
    String rest;   //  Rest of the line to be parsed
+   regex  re_any(".*");
 
    while (input >> line)
    {
       if (verbose)
       {
-         std::cout << line << "\n";
+         *log << line << "\n";
       }
 
 
@@ -674,21 +769,12 @@ int client_message::enter()
 
       switch (classification)
       {
-      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;
+      case COOKER_OBJECT:
+            check = &re_any;
+            break;
       }
 
       if (line == *check)
@@ -703,208 +789,57 @@ int client_message::enter()
 
          switch (classification)
          {
-         case SYSLOG:
-            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 << 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";
-               }
-               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)
-               {
-                  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))
+         case COOKER_OBJECT:
+#ifdef DEBUG
+            std::cerr << "\ncooker check: " << pan->check_pattern(line) << "\n";
+#endif
+            if (pan->cook_this(line, arrival))
             {
-               rest <<= i + 1;
-               if (verbose)
+               if (pan->hostname() == hostname(0,~pan->hostname()))
                {
-                  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   */
+#ifdef DEBUG
+                  std::cerr << " Information from cooker:\n";
+                  std::cerr << "     timestamp = " << pan->timestamp() << "\n";
+                  std::cerr << "     hostname  = " << pan->hostname() << "\n";
+                  std::cerr << "     service   = " << pan->service() << "\n";
+#endif
+                  /*   Insert a new record into the log table   */
+
+                  insertion += "'" + objectid + "',";
+                  insertion += "'" + pan->service() + "',";
+                  insertion += "'" + pan->timestamp().format("%Y-%m-%d %T") + "',";
+                  insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+                  insertion += "'" + SQL_Escape(line) + "',FALSE";
+                  insertion += ")";
+
+                  if (testmode)
+                  {
+                     *log << insertion << "\n";
+                  }
+                  else
+                  {
+                     database.Query(insertion);
+                  }
 
-               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 << "\n\n";
+                  }
 
-               if (testmode)
-               {
-                  std::cout << insertion << "\n";
+                  nr_lines++;
                }
                else
                {
-                  database.Query(insertion);
-               }
-
-               if (verbose)
-               {
-                  std::cout << "\n\n";
+                  *log << "   Hostname " << pan->hostname() << " does not match.\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);
+               *log << "gcm_input WARNING: Not a valid line: " << line << "\n";
             }
-
-            if (verbose)
-            {
-               std::cout << "\n\n";
-            }
-
-            nr_lines++;
             break;
 
-         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 (verbose)
-            {
-               std::cout << "\n\n";
-            }
-
-            nr_lines++;
-            break;
 
          case RPMLIST:
             //  Scan a list of packages and versions from "rpm -a".
@@ -951,8 +886,8 @@ int client_message::enter()
 
             if (verbose)
             {
-               std::cout << "Package is " << package;
-               std::cout << ", version is " << version << "\n";
+               *log << "Package is " << package;
+               *log << ", version is " << version << "\n";
             }
 
             //  Construct a qry to check the package's existance
@@ -972,7 +907,7 @@ int client_message::enter()
                }
                else
                {
-                  std::cerr <<  "Could NOT find " << package << " in list.\n";
+                  *log <<  "Could NOT find " << package << " in list.\n";
                }
 
                paramid = database.Field(0, "paramid");
@@ -980,14 +915,14 @@ int client_message::enter()
                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";
+                  *log << "Database corruption: Package " << package;
+                  *log << " does not have a 'version' property.\n";
                }
                else if (database.Field(0, "value") != version)
                {
                   if (verbose)
                   {
-                     std::cout << "  Parameter " << package << " has different version\n";
+                     *log << "  Parameter " << package << " has different version\n";
                   }
                   insertion = "update property set value='";
                   insertion += version + "' where paramid='";
@@ -1005,26 +940,34 @@ int client_message::enter()
                   {
                      remark = "Gnucomo detected a different version for package parameter(s) ";
                      change_notification = database.new_notification(objectid, "property modified", remark);
+                     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 + "')";
+                     qry = "select * from parameter_notification where notificationid='";
+                     qry += change_notification + "' and paramid='";
+                     qry += paramid + "'";
 
-                     database.Query(insertion);
+                     if (database.Query(qry) == 0)
+                     {
+                        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";
+                     *log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
                   }
                }
                else
                {
                   if (verbose)
                   {
-                     std::cout << "   Parameter " << package << " has not changed.\n";
+                     *log << "   Parameter " << package << " has not changed.\n";
                   }
                }
             }
@@ -1033,7 +976,7 @@ int client_message::enter()
 
                if (verbose)
                {
-                  std::cout << "  Parameter " << package << " does not exist.\n";
+                  *log << "  Parameter " << package << " does not exist.\n";
                }
                //  Create a new package parameter, including version property and history record
 
@@ -1042,7 +985,7 @@ int client_message::enter()
                if (testmode)
                {
                   paramid = "0";
-                  std::cout << insertion << "\n";
+                  *log << insertion << "\n";
                }
                else
                {
@@ -1064,7 +1007,7 @@ int client_message::enter()
 
                if (testmode)
                {
-                  std::cout << insertion << "\n" << insert_h << "\n";
+                  *log << insertion << "\n" << insert_h << "\n";
                }
                else
                {
@@ -1087,7 +1030,7 @@ 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";
                      }
                   }
                }
@@ -1095,7 +1038,7 @@ int client_message::enter()
 
             if (verbose)
             {
-               std::cout << "\n";
+               *log << "\n";
             }
 
             nr_lines++;
@@ -1105,11 +1048,11 @@ int client_message::enter()
       }
       else
       {
-         std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
+         *log << "gcm_input WARNING: Not a valid line: " << line << "\n";
       }
    }
 
-   if (classification == RPMLIST)
+   if (classification == RPMLIST && !incremental)
    {
       std::list<String>::iterator  lp;
       String     remove_notification("");
@@ -1138,13 +1081,13 @@ int client_message::enter()
             qry += paramid + "' order by modified desc";
             if (database.Query(qry) <= 0)
             {
-               std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
+               *log << "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";
+                 *log << "Removing parameter " << *lp << ".\n";
                }
 
                insert = "insert into history (paramid, modified, change_nature)";
@@ -1169,7 +1112,7 @@ int client_message::enter()
                }
                else
                {
-                  std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+                  *log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
                }
             }
          }
@@ -1178,39 +1121,7 @@ int client_message::enter()
 
    if (verbose)
    {
-      std::cout << nr_lines << " lines parsed from the log file.\n";
+      *log << nr_lines << " lines parsed from the log file.\n";
    }
    return nr_lines;
 }
-
-/*=========================================================================
-**  NAME           : SQL_Escape
-**  SYNOPSIS       : String SQL_Escape(String)
-**  PARAMETERS     : 
-**  RETURN VALUE   : 
-**
-**  DESCRIPTION    : Insert backslashes before single quotes.
-**
-**  VARS USED      :
-**  VARS CHANGED   :
-**  FUNCTIONS USED :
-**  SEE ALSO       :
-**  LAST MODIFIED  : 
-**=========================================================================
-*/
-
-String SQL_Escape(String s)
-{
-   int i;
-
-   for (i = 0; i < ~s; i++)
-   {
-      if (s[i] == '\'')
-      {
-         s(i,0) = "\\";
-         i++;
-      }
-   }
-
-   return s;
-}