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