Read XML input,
[gnucomo.git] / src / gcm_input / message.cpp
1
2 /**************************************************************************
3 **  (c) Copyright 2002, Andromeda Technology & Automation
4 ** This is free software; you can redistribute it and/or modify it under the
5 ** terms of the GNU General Public License, see the file COPYING.
6 ***************************************************************************
7 ** MODULE INFORMATION *
8 ***********************
9 **      FILE NAME      : message.cpp
10 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
11 **      VERSION NUMBER : $Revision: 1.10 $
12 **
13 **  DESCRIPTION      :  Implementation of the message handling classes
14 **
15 **  EXPORTED OBJECTS : 
16 **  LOCAL    OBJECTS : 
17 **  MODULES  USED    :
18 ***************************************************************************
19 **  ADMINISTRATIVE INFORMATION *
20 ********************************
21 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
22 **      CREATION DATE   : Sep 16, 2002
23 **      LAST UPDATE     : Apr 29, 2003
24 **      MODIFICATIONS   : 
25 **************************************************************************/
26
27 /*****************************
28    $Log: message.cpp,v $
29    Revision 1.10  2003-04-29 09:16:44  arjen
30    Read XML input,
31    Only cooked log entries for now.
32
33    Revision 1.9  2003/03/29 09:04:10  arjen
34    Extract the hostname out of the 'From:' or 'Message-Id:' line
35    of an email header.
36
37    Revision 1.8  2003/03/16 09:42:40  arjen
38    Read IRIX system logs.
39
40    Revision 1.7  2003/02/21 08:08:05  arjen
41    Gcm_input also detects packages that are removed from the system.
42    Determining the version number of a package in a RPM
43    list is improved. Only the last one or two parts of the string that
44    begin with a '-' and a number are considered the version.
45
46    Revision 1.6  2003/02/05 09:37:51  arjen
47    Create notifications when a new package is discovered
48    in a 'rpm -qa' list or when the version of a package is changed.
49
50    Revision 1.4  2002/12/06 22:26:28  arjen
51    Set the value of log.processed to FALSE when inserting a
52    new log entry into the database
53    When a syslog entry arrives from last year, gcm_input subtracts one from the
54    year of arrival to create the year of the log entry.
55    Read output from "rpm -qa" and enter packages in the parameter table.
56
57    Revision 1.3  2002/11/09 08:04:27  arjen
58    Added a reference to the GPL
59
60    Revision 1.2  2002/11/04 10:13:36  arjen
61    Use proper namespace for iostream classes
62
63    Revision 1.1  2002/10/05 10:25:49  arjen
64    Creation of gcm_input and a first approach to a web interface
65
66 *****************************/
67
68 static const char *RCSID = "$Id: message.cpp,v 1.10 2003-04-29 09:16:44 arjen Exp $";
69
70 #include <algorithm>
71 #include <libxml/xpath.h>
72 #include <libxml/debugXML.h>
73 #include "message.h"
74
75 extern bool verbose;   /*  Defined in the main application */
76 extern bool testmode;
77
78 /*   Utility functions   */
79
80 String SQL_Escape(String s);
81
82 /*=========================================================================
83 **  NAME           : operator >>
84 **  SYNOPSIS       : bool operator >> (message_buffer &, String &)
85 **  PARAMETERS     : 
86 **  RETURN VALUE   : True if input was available.
87 **
88 **  DESCRIPTION    : Input operator. Read the next line from the message.
89 **
90 **  VARS USED      :
91 **  VARS CHANGED   :
92 **  FUNCTIONS USED :
93 **  SEE ALSO       :
94 **  LAST MODIFIED  : Nov 04, 2002
95 **=========================================================================
96 */
97
98 bool operator >> (message_buffer &b, String &s)
99 {
100    bool   input_ok = false;
101
102    if (b.next_line == b.buffer.end())
103    {
104       String   l;
105
106       if (*(b.input) >> l)
107       {
108          b.buffer.push_back(l);
109
110          //   next_line keeps pointing to the end.
111
112          s = l;
113          input_ok = true;
114       }
115    }
116    else
117    {
118       s = *(b.next_line);
119       b.next_line++;
120       input_ok = true;
121    }
122    return input_ok;
123 }
124
125 client_message::client_message(std::istream *in, gnucomo_database db)
126 {
127    input.from(in);
128    database = db;
129
130    hostname = "";
131    mail_header   = false;
132    gpg_encrypted = false;
133    classification = UNKNOWN;
134    xmlDom         = NULL;
135    certainty      = 0.0;
136 }
137
138 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
139 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}");
140 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}");
141 static const String email_address_re("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
142
143 static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
144 static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
145 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
146 static const regex re_dump("^ *DUMP: Date of this level");
147 static const regex re_accesslog("(GET|POST) .+ HTTP");
148 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
149 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
150
151 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
152 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
153 static const regex re_mail_From("^From:[[:blank:]]+");
154 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
155 static const regex re_mail_MsId("^Message-Id:[[:blank:]]+");
156 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
157 static const regex re_email_user("[[:alnum:]_.-]+@");
158 static const regex re_xml_header("\?xml .*\?>$");
159
160 /*=========================================================================
161 **  NAME           : readXMLinput
162 **  SYNOPSIS       : int readXMLinput(String first_line)
163 **  PARAMETERS     : 
164 **  RETURN VALUE   : Parse the XML input and extract the header information
165 **
166 **  DESCRIPTION    : 
167 **
168 **  VARS USED      :
169 **  VARS CHANGED   :
170 **  FUNCTIONS USED :
171 **  SEE ALSO       :
172 **  LAST MODIFIED  : Apr 28, 2003
173 **=========================================================================
174 */
175
176 int client_message::readXMLinput(String first_line)
177 {
178    xmlParserCtxtPtr ctxt;
179    String           line;
180    xmlNodePtr       root, item;
181
182    xmlXPathObjectPtr res;
183    xmlXPathContextPtr pathcontext;
184
185
186    ctxt = xmlCreatePushParserCtxt(NULL, NULL, first_line, ~first_line, NULL);
187    while (input >> line)
188    {
189       xmlParseChunk(ctxt, line, ~line, 0);
190    }
191    xmlParseChunk(ctxt, "", 0, 1);
192    xmlDom = ctxt->myDoc;
193    xmlFreeParserCtxt(ctxt);
194
195    root = xmlDocGetRootElement(xmlDom);
196    //TODO Ought to check  root->name and  root->ns->href 
197
198    pathcontext = xmlXPathNewContext(xmlDom);
199    pathcontext->node = xmlDocGetRootElement(xmlDom);
200
201    res = xmlXPathEval((const xmlChar *)"header/messagetype/text()", pathcontext);
202    if (res->nodesetval != NULL)
203    {
204       item = *res->nodesetval->nodeTab;
205    }
206    res = xmlXPathEval((const xmlChar *)"header/hostname/text()", pathcontext);
207    if (res->nodesetval != NULL)
208    {
209       item = *res->nodesetval->nodeTab;
210       hostname = (const char *)item->content;
211    }
212    res = xmlXPathEval((const xmlChar *)"header/service/text()", pathcontext);
213    if (res->nodesetval != NULL)
214    {
215       item = *res->nodesetval->nodeTab;
216       service = (const char *)item->content;
217    }
218    res = xmlXPathEval((const xmlChar *)"header/time/text()", pathcontext);
219    if (res->nodesetval != NULL)
220    {
221       item = *res->nodesetval->nodeTab;
222       arrival = String((char *)item->content);
223
224    }
225    //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
226
227 }
228
229 /*=========================================================================
230 **  NAME           : classify
231 **  SYNOPSIS       : double classify(String host, date arriv_d, hour arriv_t, String serv)
232 **  PARAMETERS     : 
233 **  RETURN VALUE   : The certainty with which the message is classified.
234 **
235 **  DESCRIPTION    : 
236 **
237 **  VARS USED      :
238 **  VARS CHANGED   :
239 **  FUNCTIONS USED :
240 **  SEE ALSO       :
241 **  LAST MODIFIED  : Apr 28, 2003
242 **=========================================================================
243 */
244
245 double client_message::classify(String host, UTC arriv, String serv)
246 {
247    String    line;
248
249    hostname    = host;
250    arrival     = arriv;
251    service     = serv;
252
253    /*  First, check if the message has a mail header. */
254
255    if (input >> line && line == re_uxmail_from)
256    {
257       String    from_address;
258
259       mail_header = true;
260
261       /*  Scan ahead for the hostname and date of arrival.  */
262
263       while (input >> line && line != "")
264       {
265          if (line == re_mail_From)
266          {
267             from_address = line(re_email_address);
268             from_address(re_email_user) = "";            //  Remove the user part;
269             if (from_address != "")
270             {
271                hostname = from_address;
272             }
273          }
274          if (line == re_mail_MsId)
275          {
276             from_address = line(re_email_address);
277             from_address(re_email_user) = "";            //  Remove the user part;
278             if (from_address != "")
279             {
280               hostname = from_address;
281             }
282          }
283          if (line == re_mail_Date)
284          {
285             arrival = UTC(line(regex(mail_date_re)));
286          }
287       }
288    }
289    else
290    {
291       //  Push the first line back, we need to read it again.
292       --input;
293
294    }
295
296    /*
297     *  Now that we have the mail header out of the way, try to figure
298     *  out what the content of the message is.
299     */
300
301
302    while (input >> line && certainty < 0.9)
303    {
304       if (verbose)
305       {
306          std::cout << "  testing: " << line << "\n";
307       }
308
309       if (line == re_xml_header)
310       {
311          certainty = 1.0;
312          classification = XML;
313          if (verbose)
314          {
315             std::cout << "XML input detected.\n";
316          }
317          readXMLinput(line);
318       }
319       else if (line == re_syslog)
320       {
321          certainty = 1.0;
322          classification = SYSLOG;
323          if (verbose)
324          {
325             std::cout << "Syslog detected.\n";
326          }
327       }
328       else if (line == re_syslog_irix)
329       {
330          certainty = 1.0;
331          classification = SYSLOG_IRIX;
332          if (verbose)
333          {
334             std::cout << "IRIX Syslog detected.\n";
335          }
336       }
337       else if (line == re_PGP)
338       {
339          certainty = 1.0;
340          gpg_encrypted = true;
341          std::cerr << "The message is PGP/GnuPG encrypted.\n";
342       }
343       else if (line == re_dump)
344       {
345           certainty = 1.0;
346           if (verbose)
347           {
348              std::cout << "DUMP output detected.\n";
349           }
350       }
351       else if (line == re_accesslog)
352       {
353           certainty = 1.0;
354           classification = ACCESSLOG;
355           service = "httpd";
356           if (verbose)
357           {
358              std::cout << "HTTP access log detected.\n";
359           }
360       }
361       else if (line == re_errorlog)
362       {
363           certainty = 1.0;
364           classification = ERRORLOG;
365           service = "httpd";
366           if (verbose)
367           {
368              std::cout << "HTTP error log detected.\n";
369           }
370       }
371       else if (line == re_rpm)
372       {
373           certainty = 1.0;
374           classification = RPMLIST;
375           service = "";
376           if (verbose)
377           {
378              std::cout << "RPM package list detected.\n";
379           }
380       }
381    }
382    input.rewind();
383
384    if (hostname == "")
385    {
386       std::cerr <<  "Can not determine the hostname where the message came from.\n";
387       certainty = 0.0;
388    }
389    else if (!arrival.proper())
390    {
391       std::cerr << "Arrival time is not knwon.\n";
392       certainty = 0.0;
393    }
394    else
395    {
396       certainty = 1.0;
397    }
398
399    return certainty;
400 }
401
402 /*=========================================================================
403 **  NAME           : enterXML
404 **  SYNOPSIS       : int enterXML()
405 **  PARAMETERS     : 
406 **  RETURN VALUE   : None
407 **
408 **  DESCRIPTION    : Analyze the DOM tree from the XML input.
409 **                   The DOM tree was previously parsed by readXMLinput().
410 **
411 **  VARS USED      :
412 **  VARS CHANGED   :
413 **  FUNCTIONS USED :
414 **  SEE ALSO       :
415 **  LAST MODIFIED  : Apr 29, 2003
416 **=========================================================================
417 */
418
419 void client_message::enterXML()
420 {
421    xmlXPathObjectPtr res;
422    xmlXPathContextPtr pathcontext;
423
424    /*  Try to find the host in the database */
425
426    String objectid;
427
428    objectid = database.find_host(hostname);
429    if (objectid == "")
430    {
431       std::cerr << "Please define the host " << hostname << " in the database.\n";
432       return;
433    }
434    if (verbose)
435    {
436       std::cout << "Object id for " << hostname << " is " << objectid << "\n";
437    }
438
439    pathcontext = xmlXPathNewContext(xmlDom);
440    pathcontext->node = xmlDocGetRootElement(xmlDom);
441    res = xmlXPathEval((const xmlChar *)"data/node()", pathcontext);
442
443    if (res->nodesetval != NULL)
444    {
445       //  Find the first child element of the <data> element.
446
447       xmlNodePtr  node = *res->nodesetval->nodeTab;
448       while (node->type != XML_ELEMENT_NODE)
449       {
450          node = node->next;
451       }
452       if (strcmp((char *)node->name, "log") == 0)
453       {
454          //  Each child contains a log entry, raw or cooked.
455
456          node = node->children;
457          while (node != NULL)
458          {
459             if (node->type == XML_ELEMENT_NODE)
460             {
461                if (strcmp((char *)node->name, "raw") == 0)
462                {
463                   std::cerr << "Can not cook <raw> log elements yet.\n";
464                }
465                else if (strcmp((char *)node->name, "cooked") == 0)
466                {
467                   //  Find the parts of the log entry
468
469                   xmlNodePtr  item;
470                   String      log_hostname;
471                   UTC         log_date;
472                   String      raw("");;
473                   String      log_service;
474
475                   if (verbose)
476                   {
477                      std::cout << "Analyzing cooked element.\n";
478                   }
479                   pathcontext->node = node;
480
481                   res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
482                   if (res->nodesetval != NULL)
483                   {
484                      item = *res->nodesetval->nodeTab;
485                      log_hostname = (const char *)item->content;
486                      if (log_hostname != hostname(0, ~log_hostname))
487                      {
488                         std::cerr << "Hostname " << log_hostname << " does not match.\n";
489                         log_hostname = "";
490                      }
491                   }
492                   else
493                   {
494                      log_hostname = hostname;
495                   }
496
497                   res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
498                   if (res->nodesetval != NULL)
499                   {
500                      item = *res->nodesetval->nodeTab;
501                      log_service = (const char *)item->content;
502                   }
503                   else
504                   {
505                      log_service = service;
506                   }
507
508                   res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
509                   if (res->nodesetval != NULL)
510                   {
511                      item = *res->nodesetval->nodeTab;
512                      log_date = String((const char *)item->content);
513                   }
514                   else
515                   {
516                      std::cerr << "<timestamp> missing from cooked log element.\n";
517                   }
518
519                   res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
520                   if (res->nodesetval != NULL)
521                   {
522                      item = *res->nodesetval->nodeTab;
523                      raw = String((const char *)item->content);
524                   }
525                   else
526                   {
527                      std::cerr << "<raw> missing from cooked log element.\n";
528                   }
529
530                   if (raw != "" && log_hostname != "" && log_date.proper())
531                   {
532                      String insertion("insert into log (objectid, servicecode,"
533                            " object_timestamp, timestamp, rawdata, processed) values (");
534
535                      /*   Insert a new record into the log table   */
536
537                      insertion += "'" + objectid + "',";
538                      insertion += "'" + log_service + "',";
539                      insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
540                      insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
541                      insertion += "'" + SQL_Escape(raw) + "',FALSE";
542                      insertion += ")";
543
544                      if (testmode)
545                      {
546                         std::cout << insertion << "\n";
547                      }
548                      else
549                      {
550                         database.Query(insertion);
551                      }
552
553                      if (verbose)
554                      {
555                         std::cout << "\n\n";
556                      }
557                   }
558                }
559             }
560             node = node->next;
561          }
562       }
563       else
564       {
565          std::cerr << "Data element " << node->name << " is not supported.\n";
566       }
567    }
568    else
569    {
570       std::cerr << "Data node not found.\n";
571    }
572 }
573
574 /*=========================================================================
575 **  NAME           : enter
576 **  SYNOPSIS       : int enter()
577 **  PARAMETERS     : 
578 **  RETURN VALUE   : The number of lines successfully parsed from the input
579 **
580 **  DESCRIPTION    : 
581 **
582 **  VARS USED      :
583 **  VARS CHANGED   :
584 **  FUNCTIONS USED :
585 **  SEE ALSO       :
586 **  LAST MODIFIED  : Mar 28, 2003
587 **=========================================================================
588 */
589
590 int client_message::enter()
591 {
592    if (classification == XML)
593    {
594       enterXML();
595       return 1;
596    }
597
598    long   nr_lines = 0;
599    String line;
600    String qry;
601
602    String change_notification("");
603    String create_notification("");
604    bool initial_entry = false;
605
606    std::list<String>  packages;
607
608
609    /*  Double-check the classification of the message */
610
611    if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
612    {
613       return 0;
614    }
615
616    if (mail_header)
617    {
618       //  Skip the mail header.
619
620       while (input >> line && line != "");
621    }
622
623    /*  Try to find the host in the database */
624
625    String objectid;
626
627    objectid = database.find_host(hostname);
628    if (objectid == "")
629    {
630       std::cerr << "Please define the host " << hostname << " in the database.\n";
631       return 0;
632    }
633    if (verbose)
634    {
635       std::cout << "Object id for " << hostname << " is " << objectid << "\n";
636    }
637
638    if (classification == RPMLIST)
639    {
640
641       int n_packages;
642
643       /*   Read all packages, so we will know which ones are  */
644       /*   missing at the end.                                */
645
646       qry = "select name from parameter where objectid='";
647       qry += objectid + "' and class='package'";
648       n_packages = database.Query(qry);
649       initial_entry = n_packages == 0;
650
651       std::cout << n_packages << " packages in database.\n";
652       for (int t = 0; t < n_packages; t++)
653       {
654          packages.push_back(database.Field(t, "name"));
655       }
656       std::cout << "Package list built: " << packages.size() << ".\n";
657    }
658
659    /*  Scan the input line by line, entring records into the database */
660
661    String rest;   //  Rest of the line to be parsed
662
663    while (input >> line)
664    {
665       if (verbose)
666       {
667          std::cout << line << "\n";
668       }
669
670
671       /*  Check each line if it contains valid information */
672
673       const regex *check;
674
675       switch (classification)
676       {
677       case SYSLOG:
678             check = &re_syslog;
679             break;
680       case SYSLOG_IRIX:
681             check = &re_syslog_irix;
682             break;
683       case ACCESSLOG:
684             check = &re_accesslog;
685             break;
686       case ERRORLOG:
687             check = &re_errorlog;
688             break;
689       case RPMLIST:
690             check = &re_rpm;
691             break;
692       }
693
694       if (line == *check)
695       {
696          date   log_date;
697          hour   log_time;
698          int    i;
699
700          String insertion("insert into log (objectid, servicecode,"
701                            " object_timestamp, timestamp, rawdata, processed) values (");
702          String datestring;
703
704          switch (classification)
705          {
706          case SYSLOG:
707             log_date = line;
708             log_time = line;
709             if (log_date.Year() < 0 || log_date.Year() > 2500)
710             {
711                //  The year is not in the log file. Assume the year of arrival,
712                //  unless this puts the log entry at a later date than the arrival date.
713                //  This happens e.g. when a log entry from December arrives in Januari.
714
715                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
716                if (log_date > date(arrival))
717                {
718                   log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
719                }
720             }
721
722             if (verbose)
723             {
724                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
725             }
726             rest = line << 16;
727             i = rest.index(' ');
728             if (rest(0,i) == hostname(0,i))
729             {
730                rest <<= i + 1;
731                if (verbose)
732                {
733                   std::cout << "   Hostname matches.\n";
734                   std::cout << "   rest = " << rest << "\n";
735                }
736                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
737                if (verbose)
738                {
739                   std::cout << "   Service name = " << rest(0,i) << "\n";
740                }
741
742                /*   Insert a new record into the log table   */
743
744                insertion += "'" + objectid + "',";
745                insertion += "'" + rest(0,i) + "',";
746                insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
747                insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
748                insertion += "'" + SQL_Escape(line) + "',FALSE";
749                insertion += ")";
750
751                if (testmode)
752                {
753                   std::cout << insertion << "\n";
754                }
755                else
756                {
757                   database.Query(insertion);
758                }
759
760                if (verbose)
761                {
762                   std::cout << "\n\n";
763                }
764
765                nr_lines++;
766             }
767             else
768             {
769                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
770             }
771             break;
772
773          case SYSLOG_IRIX:
774             log_date = line;
775             log_time = line;
776             if (log_date.Year() < 0 || log_date.Year() > 2500)
777             {
778                //  The year is not in the log file. Assume the year of arrival,
779                //  unless this puts the log entry at a later date than the arrival date.
780                //  This happens e.g. when a log entry from December arrives in Januari.
781
782                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
783                if (log_date > date(arrival))
784                {
785                   log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
786                }
787             }
788
789             if (verbose)
790             {
791                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
792             }
793             rest = line << 19;
794             i = rest.index(' ');
795             if (rest(0,i) == hostname(0,i))
796             {
797                rest <<= i + 1;
798                if (verbose)
799                {
800                   std::cout << "   Hostname matches.\n";
801                   std::cout << "   rest = " << rest << "\n";
802                }
803                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
804                if (verbose)
805                {
806                   std::cout << "   Service name = " << rest(0,i) << "\n";
807                }
808
809                /*   Insert a new record into the log table   */
810
811                insertion += "'" + objectid + "',";
812                insertion += "'" + rest(0,i) + "',";
813                insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
814                insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
815                insertion += "'" + SQL_Escape(line) + "',FALSE";
816                insertion += ")";
817
818                if (testmode)
819                {
820                   std::cout << insertion << "\n";
821                }
822                else
823                {
824                   database.Query(insertion);
825                }
826
827                if (verbose)
828                {
829                   std::cout << "\n\n";
830                }
831
832                nr_lines++;
833             }
834             else
835             {
836                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
837             }
838             break;
839
840          case ACCESSLOG:
841             datestring = line(regex("\\[.+\\]"));
842             datestring <<= 1;
843             datestring >>= 1;
844             datestring[datestring.index(':')] = ' ';
845             log_date = datestring;
846             log_time = datestring;
847             if (verbose)
848             {
849                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
850             }
851             insertion += "'" + objectid + "',";
852             insertion += "'" + service + "',";
853             insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
854             insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
855             insertion += "'" + SQL_Escape(line) + "',FALSE";
856             insertion += ")";
857
858             if (testmode)
859             {
860                std::cout << insertion << "\n";
861             }
862             else
863             {
864                database.Query(insertion);
865             }
866
867             if (verbose)
868             {
869                std::cout << "\n\n";
870             }
871
872             nr_lines++;
873             break;
874
875          case ERRORLOG:
876             datestring = line(regex("\\[.+\\]"));
877             datestring <<= 1;
878             datestring >>= 1;
879             log_date = datestring;
880             log_time = datestring;
881             if (verbose)
882             {
883                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
884             }
885             insertion += "'" + objectid + "',";
886             insertion += "'" + service + "',";
887             insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
888             insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
889             insertion += "'" + SQL_Escape(line) + "',FALSE";
890             insertion += ")";
891
892             if (testmode)
893             {
894                std::cout << insertion << "\n";
895             }
896             else
897             {
898                database.Query(insertion);
899             }
900
901             if (verbose)
902             {
903                std::cout << "\n\n";
904             }
905
906             nr_lines++;
907             break;
908
909          case RPMLIST:
910             //  Scan a list of packages and versions from "rpm -a".
911             //  A similar listing can be created on IRIX 6.5 by using the
912             //  command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
913             //            |grep -v Version-Description".
914             //
915             //  We have to separate the package name and the version.
916             //  The separation is marked by a '-', followed by a digit.
917             //  However, there may be other sequences of '-'digit in the package name,
918             //  do we have to scan ahead until there is at most one such sequence
919             //  left in the version string. The '-'digit seqeunce inside the
920             //  version usually separates the version and the release number.
921
922             int  version_start, next_version_start;
923
924             i = line.index('-');
925             version_start = i;
926             next_version_start = i;
927
928             while (i < ~line - 1)
929             {
930                while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
931                {
932                   i++;
933                }
934                if (i < ~line - 1)
935                {
936                   version_start = next_version_start;
937                   next_version_start = i;
938                }
939                i++;
940             }
941             
942             if (!isdigit(line[version_start + 1]))
943             {
944                version_start = next_version_start;
945             }
946             String package(line(0,version_start));
947             String version(line(version_start + 1, ~line));
948             String paramid;
949             String remark;
950             String insert_h;
951
952             if (verbose)
953             {
954                std::cout << "Package is " << package;
955                std::cout << ", version is " << version << "\n";
956             }
957
958             //  Construct a qry to check the package's existance
959
960             qry = "select paramid from parameter where objectid='";
961             qry += objectid + "' and class='package' and name='";
962             qry += package + "'";
963
964             if (database.Query(qry) == 1)
965             {
966                std::list<String>::iterator  lp;
967
968                lp = find(packages.begin(), packages.end(), package);
969                if (lp != packages.end())
970                {
971                   packages.erase(lp);
972                }
973                else
974                {
975                   std::cerr <<  "Could NOT find " << package << " in list.\n";
976                }
977
978                paramid = database.Field(0, "paramid");
979                qry = "select value from property where paramid='";
980                qry += paramid + "' and name='version'";
981                if (database.Query(qry) == 0)
982                {
983                   std::cerr << "Database corruption: Package " << package;
984                   std::cerr << " does not have a 'version' property.\n";
985                }
986                else if (database.Field(0, "value") != version)
987                {
988                   if (verbose)
989                   {
990                      std::cout << "  Parameter " << package << " has different version\n";
991                   }
992                   insertion = "update property set value='";
993                   insertion += version + "' where paramid='";
994                   insertion += paramid + "' and name='version'";
995
996                   insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
997                   insert_h += " values ('";
998                   insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
999                   insert_h += version + "')";
1000
1001                   database.Query(insertion);
1002                   database.Query(insert_h);
1003
1004                   if (change_notification == "")
1005                   {
1006                      remark = "Gnucomo detected a different version for package parameter(s) ";
1007                      change_notification = database.new_notification(objectid, "property modified", remark);
1008                   }
1009
1010                   if (change_notification != "")
1011                   {
1012                      insertion = "insert into parameter_notification (notificationid, paramid) values ('";
1013                      insertion += change_notification + "', '";
1014                      insertion += paramid + "')";
1015
1016                      database.Query(insertion);
1017                   }
1018                   else
1019                   {
1020                      std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
1021                   }
1022                }
1023                else
1024                {
1025                   if (verbose)
1026                   {
1027                      std::cout << "   Parameter " << package << " has not changed.\n";
1028                   }
1029                }
1030             }
1031             else
1032             {
1033
1034                if (verbose)
1035                {
1036                   std::cout << "  Parameter " << package << " does not exist.\n";
1037                }
1038                //  Create a new package parameter, including version property and history record
1039
1040                insertion = "insert into parameter (objectid, name, class, description) values ('";
1041                insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
1042                if (testmode)
1043                {
1044                   paramid = "0";
1045                   std::cout << insertion << "\n";
1046                }
1047                else
1048                {
1049                   database.Query(insertion);
1050                   qry = "select paramid from parameter where objectid='";
1051                   qry += objectid + "' and class='package' and name='";
1052                   qry += package + "'";
1053                   database.Query(qry);
1054                   paramid = database.Field(0, "paramid");
1055                }
1056
1057                insertion = "insert into property (paramid, name, value, type) values ('";
1058                insertion += paramid + "', 'version', '";
1059                insertion += version + "', 'STATIC')";
1060                insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
1061                insert_h += " values ('";
1062                insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
1063                insert_h += version + "')";
1064
1065                if (testmode)
1066                {
1067                   std::cout << insertion << "\n" << insert_h << "\n";
1068                }
1069                else
1070                {
1071                   database.Query(insertion);
1072                   database.Query(insert_h);
1073                   if (!initial_entry)
1074                   {
1075                      if (create_notification == "")
1076                      {
1077                         remark = "Gnucomo detected new parameter(s) of class package";
1078                         create_notification = database.new_notification(objectid, "parameter created", remark);
1079                      }
1080                      if (create_notification != "")
1081                      {
1082                         insertion = "insert into parameter_notification (notificationid, paramid) values ('";
1083                         insertion += create_notification + "', '";
1084                         insertion += paramid + "')";
1085
1086                         database.Query(insertion);
1087                      }
1088                      else
1089                      {
1090                         std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
1091                      }
1092                   }
1093                }
1094             }
1095
1096             if (verbose)
1097             {
1098                std::cout << "\n";
1099             }
1100
1101             nr_lines++;
1102             break;
1103
1104          }
1105       }
1106       else
1107       {
1108          std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
1109       }
1110    }
1111
1112    if (classification == RPMLIST)
1113    {
1114       std::list<String>::iterator  lp;
1115       String     remove_notification("");
1116
1117       /*
1118        *     If there are any packages left in the list, they seem to have
1119        *     disappeared from the system.
1120        */
1121
1122       for (lp = packages.begin(); lp != packages.end(); lp++)
1123       {
1124          String paramid;
1125          String remark;
1126          String insert;
1127
1128          //  Construct a qry to check the package's existance
1129
1130          qry = "select paramid from parameter where objectid='";
1131          qry += objectid + "' and class='package' and name='";
1132          qry += *lp + "'";
1133
1134          if (database.Query(qry) == 1)
1135          {
1136             paramid = database.Field(0, "paramid");
1137             qry ="select change_nature from history where paramid='";
1138             qry += paramid + "' order by modified desc";
1139             if (database.Query(qry) <= 0)
1140             {
1141                std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
1142             }
1143             else if (database.Field(0, "change_nature") != "REMOVED")
1144             {
1145                if (verbose)
1146                {
1147                  std::cout << "Removing parameter " << *lp << ".\n";
1148                }
1149
1150                insert = "insert into history (paramid, modified, change_nature)";
1151                insert += " values ('";
1152                insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
1153
1154                database.Query(insert);
1155
1156                if (remove_notification == "")
1157                {
1158                   remark = "Gnucomo detected that package(s) have disappeared ";
1159                   remove_notification = database.new_notification(objectid, "parameter removed", remark);
1160                }
1161
1162                if (remove_notification != "")
1163                {
1164                   insert = "insert into parameter_notification (notificationid, paramid) values ('";
1165                   insert += remove_notification + "', '";
1166                   insert += paramid + "')";
1167
1168                   database.Query(insert);
1169                }
1170                else
1171                {
1172                   std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1173                }
1174             }
1175          }
1176       }
1177    }
1178
1179    if (verbose)
1180    {
1181       std::cout << nr_lines << " lines parsed from the log file.\n";
1182    }
1183    return nr_lines;
1184 }
1185
1186 /*=========================================================================
1187 **  NAME           : SQL_Escape
1188 **  SYNOPSIS       : String SQL_Escape(String)
1189 **  PARAMETERS     : 
1190 **  RETURN VALUE   : 
1191 **
1192 **  DESCRIPTION    : Insert backslashes before single quotes.
1193 **
1194 **  VARS USED      :
1195 **  VARS CHANGED   :
1196 **  FUNCTIONS USED :
1197 **  SEE ALSO       :
1198 **  LAST MODIFIED  : 
1199 **=========================================================================
1200 */
1201
1202 String SQL_Escape(String s)
1203 {
1204    int i;
1205
1206    for (i = 0; i < ~s; i++)
1207    {
1208       if (s[i] == '\'')
1209       {
1210          s(i,0) = "\\";
1211          i++;
1212       }
1213    }
1214
1215    return s;
1216 }