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.6 $
13 ** DESCRIPTION : Implementation of the message handling classes
18 ***************************************************************************
19 ** ADMINISTRATIVE INFORMATION *
20 ********************************
21 ** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
22 ** CREATION DATE : Sep 16, 2002
23 ** LAST UPDATE : Jan 31, 2003
25 **************************************************************************/
27 /*****************************
29 Revision 1.6 2003-02-05 09:37:51 arjen
30 Create notifications when a new package is discovered
31 in a 'rpm -qa' list or when the version of a package is changed.
33 Revision 1.4 2002/12/06 22:26:28 arjen
34 Set the value of log.processed to FALSE when inserting a
35 new log entry into the database
36 When a syslog entry arrives from last year, gcm_input subtracts one from the
37 year of arrival to create the year of the log entry.
38 Read output from "rpm -qa" and enter packages in the parameter table.
40 Revision 1.3 2002/11/09 08:04:27 arjen
41 Added a reference to the GPL
43 Revision 1.2 2002/11/04 10:13:36 arjen
44 Use proper namespace for iostream classes
46 Revision 1.1 2002/10/05 10:25:49 arjen
47 Creation of gcm_input and a first approach to a web interface
49 *****************************/
51 static const char *RCSID = "$Id: message.cpp,v 1.6 2003-02-05 09:37:51 arjen Exp $";
55 extern bool verbose; /* Defined in the main application */
58 /* Utility functions */
60 String SQL_Escape(String s);
62 /*=========================================================================
64 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
66 ** RETURN VALUE : True if input was available.
68 ** DESCRIPTION : Input operator. Read the next line from the message.
74 ** LAST MODIFIED : Nov 04, 2002
75 **=========================================================================
78 bool operator >> (message_buffer &b, String &s)
80 bool input_ok = false;
82 if (b.next_line == b.buffer.end())
88 b.buffer.push_back(l);
90 // next_line keeps pointing to the end.
105 client_message::client_message(std::istream *in, gnucomo_database db)
112 gpg_encrypted = false;
113 classification = UNKNOWN;
117 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
118 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}");
119 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}");
121 static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
122 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
123 static const regex re_dump("^ *DUMP: Date of this level");
124 static const regex re_accesslog("(GET|POST) .+ HTTP");
125 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
126 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
128 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
129 static const regex re_uxmail_from("^From - " + unix_date_re);
130 static const regex re_mail_From("^From:[[:blank:]]+");
131 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
132 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
133 static const regex re_email_user("[[:alnum:]_.-]+@");
135 /*=========================================================================
137 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
139 ** RETURN VALUE : The certainty with which the message is classified.
147 ** LAST MODIFIED : Nov 16, 2002
148 **=========================================================================
151 double client_message::classify(String host, UTC arriv, String serv)
159 /* First, check if the message has a mail header. */
161 if (input >> line && line == re_uxmail_from)
167 /* Scan ahead for the hostname and date of arrival. */
169 while (input >> line && line != "")
171 if (line == re_mail_From)
173 from_address = line(re_email_address);
174 from_address(re_email_user) = ""; // Remove the user part;
175 hostname = from_address;
177 if (line == re_mail_Date)
179 arrival = UTC(line(regex(mail_date_re)));
185 // Push the first line back, we need to read it again.
190 * Now that we have the mail header out of the way, try to figure
191 * out what the content of the message is.
195 while (input >> line && certainty < 0.9)
197 std::cout << " testing: " << line << "\n";
198 if (line == re_syslog)
201 classification = SYSLOG;
204 std::cout << "Syslog detected.\n";
207 else if (line == re_PGP)
210 gpg_encrypted = true;
211 std::cerr << "The message is PGP/GnuPG encrypted.\n";
213 else if (line == re_dump)
218 std::cout << "DUMP output detected.\n";
221 else if (line == re_accesslog)
224 classification = ACCESSLOG;
228 std::cout << "HTTP access log detected.\n";
231 else if (line == re_errorlog)
234 classification = ERRORLOG;
238 std::cout << "HTTP error log detected.\n";
241 else if (line == re_rpm)
244 classification = RPMLIST;
248 std::cout << "RPM package list detected.\n";
256 std::cerr << "Can not determine the hostname where the message came from.\n";
259 else if (!arrival.proper())
261 std::cerr << "Arrival time is not knwon.\n";
272 /*=========================================================================
274 ** SYNOPSIS : int enter()
276 ** RETURN VALUE : The number of lines successfully parsed from the input
284 ** LAST MODIFIED : Jan 31, 2003
285 **=========================================================================
288 int client_message::enter()
293 String change_notification("");
294 String create_notification("");
297 /* Double-check the classification of the message */
299 if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
306 // Skip the mail header.
308 while (input >> line && line != "");
311 /* Try to find the host in the database */
315 objectid = database.find_host(hostname);
318 std::cerr << "Please define the host " << hostname << " in the database.\n";
323 std::cout << "Object id for " << hostname << " is " << objectid << "\n";
326 /* Scan the input line by line, entring records into the database */
328 String rest; // Rest of the line to be parsed
330 while (input >> line)
334 std::cout << line << "\n";
338 /* Check each line if it contains valid information */
342 switch (classification)
348 check = &re_accesslog;
351 check = &re_errorlog;
364 String insertion("insert into log (objectid, servicecode,"
365 " object_timestamp, timestamp, rawdata, processed) values (");
368 switch (classification)
373 if (log_date.Year() < 0 || log_date.Year() > 2500)
375 // The year is not in the log file. Assume the year of arrival,
376 // unless this puts the log entry at a later date than the arrival date.
377 // This happens e.g. when a log entry from December arrives in Januari.
379 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
380 if (log_date > date(arrival))
382 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
388 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
392 if (rest(0,i) == hostname(0,i))
397 std::cout << " Hostname matches.\n";
398 std::cout << " rest = " << rest << "\n";
400 for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
403 std::cout << " Service name = " << rest(0,i) << "\n";
406 /* Insert a new record into the log table */
408 insertion += "'" + objectid + "',";
409 insertion += "'" + rest(0,i) + "',";
410 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
411 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
412 insertion += "'" + SQL_Escape(line) + "',FALSE";
417 std::cout << insertion << "\n";
421 database.Query(insertion);
433 std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
438 datestring = line(regex("\\[.+\\]"));
441 datestring[datestring.index(':')] = ' ';
442 log_date = datestring;
443 log_time = datestring;
446 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
448 insertion += "'" + objectid + "',";
449 insertion += "'" + service + "',";
450 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
451 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
452 insertion += "'" + SQL_Escape(line) + "',FALSE";
457 std::cout << insertion << "\n";
461 database.Query(insertion);
473 datestring = line(regex("\\[.+\\]"));
476 log_date = datestring;
477 log_time = datestring;
480 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
482 insertion += "'" + objectid + "',";
483 insertion += "'" + service + "',";
484 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
485 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
486 insertion += "'" + SQL_Escape(line) + "',FALSE";
491 std::cout << insertion << "\n";
495 database.Query(insertion);
507 // Scan a list of packages and versions from "rpm -a".
508 // A similar listing can be created on IRIX 6.5 by using the
509 // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
510 // |grep -v Version-Description".
512 // We have to separate the package name and the version.
513 // The separation is marked by a '-', followed by a digit.
515 String qry = "select count(paramid) from parameter where objectid='";
516 qry += objectid + "' and class='package'";
519 long n_packages = String(database.Field(0, "count"));
521 bool initial_entry = n_packages == 0;
524 while (!(line[i] == '-' && isdigit(line[i + 1])))
528 String package(line(0,i));
529 String version(line(i+1, ~line));
536 std::cout << "Package is " << package;
537 std::cout << ", version is " << version << "\n";
540 // Construct a qry to check the package's existance
542 qry = "select paramid from parameter where objectid='";
543 qry += objectid + "' and class='package' and name='";
544 qry += package + "'";
546 if (database.Query(qry) == 1)
548 paramid = database.Field(0, "paramid");
549 qry = "select value from property where paramid='";
550 qry += paramid + "' and name='version'";
551 if (database.Query(qry) == 0)
553 std::cerr << "Database corruption: Package " << package;
554 std::cerr << " does not have a 'version' property.\n";
556 else if (database.Field(0, "value") != version)
560 std::cout << " Parameter " << package << " has different version\n";
562 insertion = "update property set value='";
563 insertion += version + "' where paramid='";
564 insertion += paramid + "' and name='version'";
566 insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
567 insert_h += " values ('";
568 insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
569 insert_h += version + "')";
571 database.Query(insertion);
572 database.Query(insert_h);
574 if (change_notification == "")
576 remark = "Gnucomo detected a different version for package parameter(s) ";
577 change_notification = database.new_notification(objectid, "property modified", remark);
580 insertion = "insert into parameter_notification (notificationid, paramid) values ('";
581 insertion += change_notification + "', '";
582 insertion += paramid + "')";
584 database.Query(insertion);
590 std::cout << " Parameter " << package << " has not changed.\n";
599 std::cout << " Parameter " << package << " does not exist.\n";
601 // Create a new package parameter, including version property and history record
603 insertion = "insert into parameter (objectid, name, class, description) values ('";
604 insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
608 std::cout << insertion << "\n";
612 database.Query(insertion);
613 qry = "select paramid from parameter where objectid='";
614 qry += objectid + "' and class='package' and name='";
615 qry += package + "'";
617 paramid = database.Field(0, "paramid");
620 insertion = "insert into property (paramid, name, value, type) values ('";
621 insertion += paramid + "', 'version', '";
622 insertion += version + "', 'STATIC')";
623 insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
624 insert_h += " values ('";
625 insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
626 insert_h += version + "')";
630 std::cout << insertion << "\n" << insert_h << "\n";
634 database.Query(insertion);
635 database.Query(insert_h);
638 if (create_notification == "")
640 remark = "Gnucomo detected new parameter(s) of class package";
641 create_notification = database.new_notification(objectid, "parameter created", remark);
643 insertion = "insert into parameter_notification (notificationid, paramid) values ('";
644 insertion += create_notification + "', '";
645 insertion += paramid + "')";
647 database.Query(insertion);
664 std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
670 std::cout << nr_lines << " lines parsed from the log file.\n";
675 /*=========================================================================
677 ** SYNOPSIS : String SQL_Escape(String)
681 ** DESCRIPTION : Insert backslashes before single quotes.
688 **=========================================================================
691 String SQL_Escape(String s)
695 for (i = 0; i < ~s; i++)