Extract the hostname out of the 'From:' or 'Message-Id:' line
[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.9 $
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     : Mar 28, 2003
24 **      MODIFICATIONS   : 
25 **************************************************************************/
26
27 /*****************************
28    $Log: message.cpp,v $
29    Revision 1.9  2003-03-29 09:04:10  arjen
30    Extract the hostname out of the 'From:' or 'Message-Id:' line
31    of an email header.
32
33    Revision 1.8  2003/03/16 09:42:40  arjen
34    Read IRIX system logs.
35
36    Revision 1.7  2003/02/21 08:08:05  arjen
37    Gcm_input also detects packages that are removed from the system.
38    Determining the version number of a package in a RPM
39    list is improved. Only the last one or two parts of the string that
40    begin with a '-' and a number are considered the version.
41
42    Revision 1.6  2003/02/05 09:37:51  arjen
43    Create notifications when a new package is discovered
44    in a 'rpm -qa' list or when the version of a package is changed.
45
46    Revision 1.4  2002/12/06 22:26:28  arjen
47    Set the value of log.processed to FALSE when inserting a
48    new log entry into the database
49    When a syslog entry arrives from last year, gcm_input subtracts one from the
50    year of arrival to create the year of the log entry.
51    Read output from "rpm -qa" and enter packages in the parameter table.
52
53    Revision 1.3  2002/11/09 08:04:27  arjen
54    Added a reference to the GPL
55
56    Revision 1.2  2002/11/04 10:13:36  arjen
57    Use proper namespace for iostream classes
58
59    Revision 1.1  2002/10/05 10:25:49  arjen
60    Creation of gcm_input and a first approach to a web interface
61
62 *****************************/
63
64 static const char *RCSID = "$Id: message.cpp,v 1.9 2003-03-29 09:04:10 arjen Exp $";
65
66 #include <algorithm>
67 #include "message.h"
68
69 extern bool verbose;   /*  Defined in the main application */
70 extern bool testmode;
71
72 /*   Utility functions   */
73
74 String SQL_Escape(String s);
75
76 /*=========================================================================
77 **  NAME           : operator >>
78 **  SYNOPSIS       : bool operator >> (message_buffer &, String &)
79 **  PARAMETERS     : 
80 **  RETURN VALUE   : True if input was available.
81 **
82 **  DESCRIPTION    : Input operator. Read the next line from the message.
83 **
84 **  VARS USED      :
85 **  VARS CHANGED   :
86 **  FUNCTIONS USED :
87 **  SEE ALSO       :
88 **  LAST MODIFIED  : Nov 04, 2002
89 **=========================================================================
90 */
91
92 bool operator >> (message_buffer &b, String &s)
93 {
94    bool   input_ok = false;
95
96    if (b.next_line == b.buffer.end())
97    {
98       String   l;
99
100       if (*(b.input) >> l)
101       {
102          b.buffer.push_back(l);
103
104          //   next_line keeps pointing to the end.
105
106          s = l;
107          input_ok = true;
108       }
109    }
110    else
111    {
112       s = *(b.next_line);
113       b.next_line++;
114       input_ok = true;
115    }
116    return input_ok;
117 }
118
119 client_message::client_message(std::istream *in, gnucomo_database db)
120 {
121    input.from(in);
122    database = db;
123
124    hostname = "";
125    mail_header   = false;
126    gpg_encrypted = false;
127    classification = UNKNOWN;
128    certainty      = 0.0;
129 }
130
131 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
132 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}");
133 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}");
134 static const String email_address_re("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
135
136 static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
137 static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
138 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
139 static const regex re_dump("^ *DUMP: Date of this level");
140 static const regex re_accesslog("(GET|POST) .+ HTTP");
141 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
142 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
143
144 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
145 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
146 static const regex re_mail_From("^From:[[:blank:]]+");
147 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
148 static const regex re_mail_MsId("^Message-Id:[[:blank:]]+");
149 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
150 static const regex re_email_user("[[:alnum:]_.-]+@");
151
152 /*=========================================================================
153 **  NAME           : classify
154 **  SYNOPSIS       : double classify(String host, date arriv_d, hour arriv_t, String serv)
155 **  PARAMETERS     : 
156 **  RETURN VALUE   : The certainty with which the message is classified.
157 **
158 **  DESCRIPTION    : 
159 **
160 **  VARS USED      :
161 **  VARS CHANGED   :
162 **  FUNCTIONS USED :
163 **  SEE ALSO       :
164 **  LAST MODIFIED  : Mar 28, 2003
165 **=========================================================================
166 */
167
168 double client_message::classify(String host, UTC arriv, String serv)
169 {
170    String    line;
171
172    hostname    = host;
173    arrival     = arriv;
174    service     = serv;
175
176    /*  First, check if the message has a mail header. */
177
178    if (input >> line && line == re_uxmail_from)
179    {
180       String    from_address;
181
182       mail_header = true;
183
184       /*  Scan ahead for the hostname and date of arrival.  */
185
186       while (input >> line && line != "")
187       {
188          if (line == re_mail_From)
189          {
190             from_address = line(re_email_address);
191             from_address(re_email_user) = "";            //  Remove the user part;
192             if (from_address != "")
193             {
194                hostname = from_address;
195             }
196          }
197          if (line == re_mail_MsId)
198          {
199             from_address = line(re_email_address);
200             from_address(re_email_user) = "";            //  Remove the user part;
201             if (from_address != "")
202             {
203               hostname = from_address;
204             }
205          }
206          if (line == re_mail_Date)
207          {
208             arrival = UTC(line(regex(mail_date_re)));
209          }
210       }
211    }
212    else
213    {
214       //  Push the first line back, we need to read it again.
215       --input;
216
217    }
218
219    /*
220     *  Now that we have the mail header out of the way, try to figure
221     *  out what the content of the message is.
222     */
223
224
225    while (input >> line && certainty < 0.9)
226    {
227       std::cout << "  testing: " << line << "\n";
228       if (line == re_syslog)
229       {
230          certainty = 1.0;
231          classification = SYSLOG;
232          if (verbose)
233          {
234             std::cout << "Syslog detected.\n";
235          }
236       }
237       else if (line == re_syslog_irix)
238       {
239          certainty = 1.0;
240          classification = SYSLOG_IRIX;
241          if (verbose)
242          {
243             std::cout << "IRIX Syslog detected.\n";
244          }
245       }
246       else if (line == re_PGP)
247       {
248          certainty = 1.0;
249          gpg_encrypted = true;
250          std::cerr << "The message is PGP/GnuPG encrypted.\n";
251       }
252       else if (line == re_dump)
253       {
254           certainty = 1.0;
255           if (verbose)
256           {
257              std::cout << "DUMP output detected.\n";
258           }
259       }
260       else if (line == re_accesslog)
261       {
262           certainty = 1.0;
263           classification = ACCESSLOG;
264           service = "httpd";
265           if (verbose)
266           {
267              std::cout << "HTTP access log detected.\n";
268           }
269       }
270       else if (line == re_errorlog)
271       {
272           certainty = 1.0;
273           classification = ERRORLOG;
274           service = "httpd";
275           if (verbose)
276           {
277              std::cout << "HTTP error log detected.\n";
278           }
279       }
280       else if (line == re_rpm)
281       {
282           certainty = 1.0;
283           classification = RPMLIST;
284           service = "";
285           if (verbose)
286           {
287              std::cout << "RPM package list detected.\n";
288           }
289       }
290    }
291    input.rewind();
292
293    if (hostname == "")
294    {
295       std::cerr <<  "Can not determine the hostname where the message came from.\n";
296       certainty = 0.0;
297    }
298    else if (!arrival.proper())
299    {
300       std::cerr << "Arrival time is not knwon.\n";
301       certainty = 0.0;
302    }
303    else
304    {
305       certainty = 1.0;
306    }
307
308    return certainty;
309 }
310
311 /*=========================================================================
312 **  NAME           : enter
313 **  SYNOPSIS       : int enter()
314 **  PARAMETERS     : 
315 **  RETURN VALUE   : The number of lines successfully parsed from the input
316 **
317 **  DESCRIPTION    : 
318 **
319 **  VARS USED      :
320 **  VARS CHANGED   :
321 **  FUNCTIONS USED :
322 **  SEE ALSO       :
323 **  LAST MODIFIED  : Mar 28, 2003
324 **=========================================================================
325 */
326
327 int client_message::enter()
328 {
329    long   nr_lines = 0;
330    String line;
331    String qry;
332
333    String change_notification("");
334    String create_notification("");
335    bool initial_entry = false;
336
337    std::list<String>  packages;
338
339
340    /*  Double-check the classification of the message */
341
342    if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
343    {
344       return 0;
345    }
346
347    if (mail_header)
348    {
349       //  Skip the mail header.
350
351       while (input >> line && line != "");
352    }
353
354    /*  Try to find the host in the database */
355
356    String objectid;
357
358    objectid = database.find_host(hostname);
359    if (objectid == "")
360    {
361       std::cerr << "Please define the host " << hostname << " in the database.\n";
362       return 0;
363    }
364    if (verbose)
365    {
366       std::cout << "Object id for " << hostname << " is " << objectid << "\n";
367    }
368
369    if (classification == RPMLIST)
370    {
371
372       int n_packages;
373
374       /*   Read all packages, so we will know which ones are  */
375       /*   missing at the end.                                */
376
377       qry = "select name from parameter where objectid='";
378       qry += objectid + "' and class='package'";
379       n_packages = database.Query(qry);
380       initial_entry = n_packages == 0;
381
382       std::cout << n_packages << " packages in database.\n";
383       for (int t = 0; t < n_packages; t++)
384       {
385          packages.push_back(database.Field(t, "name"));
386       }
387       std::cout << "Package list built: " << packages.size() << ".\n";
388    }
389
390    /*  Scan the input line by line, entring records into the database */
391
392    String rest;   //  Rest of the line to be parsed
393
394    while (input >> line)
395    {
396       if (verbose)
397       {
398          std::cout << line << "\n";
399       }
400
401
402       /*  Check each line if it contains valid information */
403
404       const regex *check;
405
406       switch (classification)
407       {
408       case SYSLOG:
409             check = &re_syslog;
410             break;
411       case SYSLOG_IRIX:
412             check = &re_syslog_irix;
413             break;
414       case ACCESSLOG:
415             check = &re_accesslog;
416             break;
417       case ERRORLOG:
418             check = &re_errorlog;
419             break;
420       case RPMLIST:
421             check = &re_rpm;
422             break;
423       }
424
425       if (line == *check)
426       {
427          date   log_date;
428          hour   log_time;
429          int    i;
430
431          String insertion("insert into log (objectid, servicecode,"
432                            " object_timestamp, timestamp, rawdata, processed) values (");
433          String datestring;
434
435          switch (classification)
436          {
437          case SYSLOG:
438             log_date = line;
439             log_time = line;
440             if (log_date.Year() < 0 || log_date.Year() > 2500)
441             {
442                //  The year is not in the log file. Assume the year of arrival,
443                //  unless this puts the log entry at a later date than the arrival date.
444                //  This happens e.g. when a log entry from December arrives in Januari.
445
446                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
447                if (log_date > date(arrival))
448                {
449                   log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
450                }
451             }
452
453             if (verbose)
454             {
455                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
456             }
457             rest = line << 16;
458             i = rest.index(' ');
459             if (rest(0,i) == hostname(0,i))
460             {
461                rest <<= i + 1;
462                if (verbose)
463                {
464                   std::cout << "   Hostname matches.\n";
465                   std::cout << "   rest = " << rest << "\n";
466                }
467                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
468                if (verbose)
469                {
470                   std::cout << "   Service name = " << rest(0,i) << "\n";
471                }
472
473                /*   Insert a new record into the log table   */
474
475                insertion += "'" + objectid + "',";
476                insertion += "'" + rest(0,i) + "',";
477                insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
478                insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
479                insertion += "'" + SQL_Escape(line) + "',FALSE";
480                insertion += ")";
481
482                if (testmode)
483                {
484                   std::cout << insertion << "\n";
485                }
486                else
487                {
488                   database.Query(insertion);
489                }
490
491                if (verbose)
492                {
493                   std::cout << "\n\n";
494                }
495
496                nr_lines++;
497             }
498             else
499             {
500                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
501             }
502             break;
503
504          case SYSLOG_IRIX:
505             log_date = line;
506             log_time = line;
507             if (log_date.Year() < 0 || log_date.Year() > 2500)
508             {
509                //  The year is not in the log file. Assume the year of arrival,
510                //  unless this puts the log entry at a later date than the arrival date.
511                //  This happens e.g. when a log entry from December arrives in Januari.
512
513                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
514                if (log_date > date(arrival))
515                {
516                   log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
517                }
518             }
519
520             if (verbose)
521             {
522                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
523             }
524             rest = line << 19;
525             i = rest.index(' ');
526             if (rest(0,i) == hostname(0,i))
527             {
528                rest <<= i + 1;
529                if (verbose)
530                {
531                   std::cout << "   Hostname matches.\n";
532                   std::cout << "   rest = " << rest << "\n";
533                }
534                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
535                if (verbose)
536                {
537                   std::cout << "   Service name = " << rest(0,i) << "\n";
538                }
539
540                /*   Insert a new record into the log table   */
541
542                insertion += "'" + objectid + "',";
543                insertion += "'" + rest(0,i) + "',";
544                insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
545                insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
546                insertion += "'" + SQL_Escape(line) + "',FALSE";
547                insertion += ")";
548
549                if (testmode)
550                {
551                   std::cout << insertion << "\n";
552                }
553                else
554                {
555                   database.Query(insertion);
556                }
557
558                if (verbose)
559                {
560                   std::cout << "\n\n";
561                }
562
563                nr_lines++;
564             }
565             else
566             {
567                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
568             }
569             break;
570
571          case ACCESSLOG:
572             datestring = line(regex("\\[.+\\]"));
573             datestring <<= 1;
574             datestring >>= 1;
575             datestring[datestring.index(':')] = ' ';
576             log_date = datestring;
577             log_time = datestring;
578             if (verbose)
579             {
580                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
581             }
582             insertion += "'" + objectid + "',";
583             insertion += "'" + service + "',";
584             insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
585             insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
586             insertion += "'" + SQL_Escape(line) + "',FALSE";
587             insertion += ")";
588
589             if (testmode)
590             {
591                std::cout << insertion << "\n";
592             }
593             else
594             {
595                database.Query(insertion);
596             }
597
598             if (verbose)
599             {
600                std::cout << "\n\n";
601             }
602
603             nr_lines++;
604             break;
605
606          case ERRORLOG:
607             datestring = line(regex("\\[.+\\]"));
608             datestring <<= 1;
609             datestring >>= 1;
610             log_date = datestring;
611             log_time = datestring;
612             if (verbose)
613             {
614                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
615             }
616             insertion += "'" + objectid + "',";
617             insertion += "'" + service + "',";
618             insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
619             insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
620             insertion += "'" + SQL_Escape(line) + "',FALSE";
621             insertion += ")";
622
623             if (testmode)
624             {
625                std::cout << insertion << "\n";
626             }
627             else
628             {
629                database.Query(insertion);
630             }
631
632             if (verbose)
633             {
634                std::cout << "\n\n";
635             }
636
637             nr_lines++;
638             break;
639
640          case RPMLIST:
641             //  Scan a list of packages and versions from "rpm -a".
642             //  A similar listing can be created on IRIX 6.5 by using the
643             //  command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
644             //            |grep -v Version-Description".
645             //
646             //  We have to separate the package name and the version.
647             //  The separation is marked by a '-', followed by a digit.
648             //  However, there may be other sequences of '-'digit in the package name,
649             //  do we have to scan ahead until there is at most one such sequence
650             //  left in the version string. The '-'digit seqeunce inside the
651             //  version usually separates the version and the release number.
652
653             int  version_start, next_version_start;
654
655             i = line.index('-');
656             version_start = i;
657             next_version_start = i;
658
659             while (i < ~line - 1)
660             {
661                while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
662                {
663                   i++;
664                }
665                if (i < ~line - 1)
666                {
667                   version_start = next_version_start;
668                   next_version_start = i;
669                }
670                i++;
671             }
672             
673             if (!isdigit(line[version_start + 1]))
674             {
675                version_start = next_version_start;
676             }
677             String package(line(0,version_start));
678             String version(line(version_start + 1, ~line));
679             String paramid;
680             String remark;
681             String insert_h;
682
683             if (verbose)
684             {
685                std::cout << "Package is " << package;
686                std::cout << ", version is " << version << "\n";
687             }
688
689             //  Construct a qry to check the package's existance
690
691             qry = "select paramid from parameter where objectid='";
692             qry += objectid + "' and class='package' and name='";
693             qry += package + "'";
694
695             if (database.Query(qry) == 1)
696             {
697                std::list<String>::iterator  lp;
698
699                lp = find(packages.begin(), packages.end(), package);
700                if (lp != packages.end())
701                {
702                   packages.erase(lp);
703                }
704                else
705                {
706                   std::cerr <<  "Could NOT find " << package << " in list.\n";
707                }
708
709                paramid = database.Field(0, "paramid");
710                qry = "select value from property where paramid='";
711                qry += paramid + "' and name='version'";
712                if (database.Query(qry) == 0)
713                {
714                   std::cerr << "Database corruption: Package " << package;
715                   std::cerr << " does not have a 'version' property.\n";
716                }
717                else if (database.Field(0, "value") != version)
718                {
719                   if (verbose)
720                   {
721                      std::cout << "  Parameter " << package << " has different version\n";
722                   }
723                   insertion = "update property set value='";
724                   insertion += version + "' where paramid='";
725                   insertion += paramid + "' and name='version'";
726
727                   insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
728                   insert_h += " values ('";
729                   insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
730                   insert_h += version + "')";
731
732                   database.Query(insertion);
733                   database.Query(insert_h);
734
735                   if (change_notification == "")
736                   {
737                      remark = "Gnucomo detected a different version for package parameter(s) ";
738                      change_notification = database.new_notification(objectid, "property modified", remark);
739                   }
740
741                   if (change_notification != "")
742                   {
743                      insertion = "insert into parameter_notification (notificationid, paramid) values ('";
744                      insertion += change_notification + "', '";
745                      insertion += paramid + "')";
746
747                      database.Query(insertion);
748                   }
749                   else
750                   {
751                      std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
752                   }
753                }
754                else
755                {
756                   if (verbose)
757                   {
758                      std::cout << "   Parameter " << package << " has not changed.\n";
759                   }
760                }
761             }
762             else
763             {
764
765                if (verbose)
766                {
767                   std::cout << "  Parameter " << package << " does not exist.\n";
768                }
769                //  Create a new package parameter, including version property and history record
770
771                insertion = "insert into parameter (objectid, name, class, description) values ('";
772                insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
773                if (testmode)
774                {
775                   paramid = "0";
776                   std::cout << insertion << "\n";
777                }
778                else
779                {
780                   database.Query(insertion);
781                   qry = "select paramid from parameter where objectid='";
782                   qry += objectid + "' and class='package' and name='";
783                   qry += package + "'";
784                   database.Query(qry);
785                   paramid = database.Field(0, "paramid");
786                }
787
788                insertion = "insert into property (paramid, name, value, type) values ('";
789                insertion += paramid + "', 'version', '";
790                insertion += version + "', 'STATIC')";
791                insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
792                insert_h += " values ('";
793                insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
794                insert_h += version + "')";
795
796                if (testmode)
797                {
798                   std::cout << insertion << "\n" << insert_h << "\n";
799                }
800                else
801                {
802                   database.Query(insertion);
803                   database.Query(insert_h);
804                   if (!initial_entry)
805                   {
806                      if (create_notification == "")
807                      {
808                         remark = "Gnucomo detected new parameter(s) of class package";
809                         create_notification = database.new_notification(objectid, "parameter created", remark);
810                      }
811                      if (create_notification != "")
812                      {
813                         insertion = "insert into parameter_notification (notificationid, paramid) values ('";
814                         insertion += create_notification + "', '";
815                         insertion += paramid + "')";
816
817                         database.Query(insertion);
818                      }
819                      else
820                      {
821                         std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
822                      }
823                   }
824                }
825             }
826
827             if (verbose)
828             {
829                std::cout << "\n";
830             }
831
832             nr_lines++;
833             break;
834
835          }
836       }
837       else
838       {
839          std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
840       }
841    }
842
843    if (classification == RPMLIST)
844    {
845       std::list<String>::iterator  lp;
846       String     remove_notification("");
847
848       /*
849        *     If there are any packages left in the list, they seem to have
850        *     disappeared from the system.
851        */
852
853       for (lp = packages.begin(); lp != packages.end(); lp++)
854       {
855          String paramid;
856          String remark;
857          String insert;
858
859          //  Construct a qry to check the package's existance
860
861          qry = "select paramid from parameter where objectid='";
862          qry += objectid + "' and class='package' and name='";
863          qry += *lp + "'";
864
865          if (database.Query(qry) == 1)
866          {
867             paramid = database.Field(0, "paramid");
868             qry ="select change_nature from history where paramid='";
869             qry += paramid + "' order by modified desc";
870             if (database.Query(qry) <= 0)
871             {
872                std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
873             }
874             else if (database.Field(0, "change_nature") != "REMOVED")
875             {
876                if (verbose)
877                {
878                  std::cout << "Removing parameter " << *lp << ".\n";
879                }
880
881                insert = "insert into history (paramid, modified, change_nature)";
882                insert += " values ('";
883                insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
884
885                database.Query(insert);
886
887                if (remove_notification == "")
888                {
889                   remark = "Gnucomo detected that package(s) have disappeared ";
890                   remove_notification = database.new_notification(objectid, "parameter removed", remark);
891                }
892
893                if (remove_notification != "")
894                {
895                   insert = "insert into parameter_notification (notificationid, paramid) values ('";
896                   insert += remove_notification + "', '";
897                   insert += paramid + "')";
898
899                   database.Query(insert);
900                }
901                else
902                {
903                   std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
904                }
905             }
906          }
907       }
908    }
909
910    if (verbose)
911    {
912       std::cout << nr_lines << " lines parsed from the log file.\n";
913    }
914    return nr_lines;
915 }
916
917 /*=========================================================================
918 **  NAME           : SQL_Escape
919 **  SYNOPSIS       : String SQL_Escape(String)
920 **  PARAMETERS     : 
921 **  RETURN VALUE   : 
922 **
923 **  DESCRIPTION    : Insert backslashes before single quotes.
924 **
925 **  VARS USED      :
926 **  VARS CHANGED   :
927 **  FUNCTIONS USED :
928 **  SEE ALSO       :
929 **  LAST MODIFIED  : 
930 **=========================================================================
931 */
932
933 String SQL_Escape(String s)
934 {
935    int i;
936
937    for (i = 0; i < ~s; i++)
938    {
939       if (s[i] == '\'')
940       {
941          s(i,0) = "\\";
942          i++;
943       }
944    }
945
946    return s;
947 }