/**************************************************************************
** (c) Copyright 2002, Andromeda Technology & Automation
+** This is free software; you can redistribute it and/or modify it under the
+** terms of the GNU General Public License, see the file COPYING.
***************************************************************************
** MODULE INFORMATION *
***********************
** FILE NAME : message.cpp
** SYSTEM NAME : Gnucomo - Gnu Computer Monitoring
-** VERSION NUMBER : $Revision: 1.1 $
+** VERSION NUMBER : $Revision: 1.8 $
**
** DESCRIPTION : Implementation of the message handling classes
**
********************************
** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
** CREATION DATE : Sep 16, 2002
-** LAST UPDATE : Oct 05, 2002
+** LAST UPDATE : Feb 28, 2003
** MODIFICATIONS :
**************************************************************************/
/*****************************
$Log: message.cpp,v $
- Revision 1.1 2002-10-05 10:25:49 arjen
+ Revision 1.8 2003-03-16 09:42:40 arjen
+ Read IRIX system logs.
+
+ Revision 1.7 2003/02/21 08:08:05 arjen
+ Gcm_input also detects packages that are removed from the system.
+ Determining the version number of a package in a RPM
+ list is improved. Only the last one or two parts of the string that
+ begin with a '-' and a number are considered the version.
+
+ Revision 1.6 2003/02/05 09:37:51 arjen
+ Create notifications when a new package is discovered
+ in a 'rpm -qa' list or when the version of a package is changed.
+
+ Revision 1.4 2002/12/06 22:26:28 arjen
+ Set the value of log.processed to FALSE when inserting a
+ new log entry into the database
+ When a syslog entry arrives from last year, gcm_input subtracts one from the
+ year of arrival to create the year of the log entry.
+ Read output from "rpm -qa" and enter packages in the parameter table.
+
+ Revision 1.3 2002/11/09 08:04:27 arjen
+ Added a reference to the GPL
+
+ Revision 1.2 2002/11/04 10:13:36 arjen
+ Use proper namespace for iostream classes
+
+ Revision 1.1 2002/10/05 10:25:49 arjen
Creation of gcm_input and a first approach to a web interface
*****************************/
-static const char *RCSID = "$Id: message.cpp,v 1.1 2002-10-05 10:25:49 arjen Exp $";
+static const char *RCSID = "$Id: message.cpp,v 1.8 2003-03-16 09:42:40 arjen Exp $";
+#include <algorithm>
#include "message.h"
extern bool verbose; /* Defined in the main application */
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Sep 30, 2002
+** LAST MODIFIED : Nov 04, 2002
**=========================================================================
*/
{
String l;
- //cout << " buffer is depleted.\n";
if (*(b.input) >> l)
{
b.buffer.push_back(l);
// next_line keeps pointing to the end.
-
+
s = l;
input_ok = true;
- //cout << " new line from input.\n";
}
}
else
{
- //cout << " reading from cache.\n";
s = *(b.next_line);
b.next_line++;
input_ok = true;
return input_ok;
}
-client_message::client_message(istream *in, gnucomo_database db)
+client_message::client_message(std::istream *in, gnucomo_database db)
{
input.from(in);
database = db;
static const String mail_date_re("[[:alpha:]]{3}, [ 123]?[0-9] [[:alpha:]]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}");
static const String unix_date_re("[[:alpha:]]{3} [[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4}");
-static const regex re_syslog(syslog_date_re + " [a-z]+ [[:alpha:]]+.*:.+");
+static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
+static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
static const regex re_dump("^ *DUMP: Date of this level");
static const regex re_accesslog("(GET|POST) .+ HTTP");
static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
+static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
static const regex re_uxmail_from("^From - " + unix_date_re);
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Oct 05, 2002
+** LAST MODIFIED : Nov 16, 2002
**=========================================================================
*/
while (input >> line && certainty < 0.9)
{
- cout << " testing: " << line << "\n";
+ std::cout << " testing: " << line << "\n";
if (line == re_syslog)
{
certainty = 1.0;
classification = SYSLOG;
if (verbose)
{
- cout << "Syslog detected.\n";
+ std::cout << "Syslog detected.\n";
+ }
+ }
+ else if (line == re_syslog_irix)
+ {
+ certainty = 1.0;
+ classification = SYSLOG_IRIX;
+ if (verbose)
+ {
+ std::cout << "IRIX Syslog detected.\n";
}
}
else if (line == re_PGP)
{
certainty = 1.0;
gpg_encrypted = true;
- cerr << "The message is PGP/GnuPG encrypted.\n";
+ std::cerr << "The message is PGP/GnuPG encrypted.\n";
}
else if (line == re_dump)
{
certainty = 1.0;
if (verbose)
{
- cout << "DUMP output detected.\n";
+ std::cout << "DUMP output detected.\n";
}
}
else if (line == re_accesslog)
service = "httpd";
if (verbose)
{
- cout << "HTTP access log detected.\n";
+ std::cout << "HTTP access log detected.\n";
}
}
else if (line == re_errorlog)
service = "httpd";
if (verbose)
{
- cout << "HTTP error log detected.\n";
+ std::cout << "HTTP error log detected.\n";
+ }
+ }
+ else if (line == re_rpm)
+ {
+ certainty = 1.0;
+ classification = RPMLIST;
+ service = "";
+ if (verbose)
+ {
+ std::cout << "RPM package list detected.\n";
}
}
}
if (hostname == "")
{
- cerr << "Can not determine the hostname where the message came from.\n";
+ std::cerr << "Can not determine the hostname where the message came from.\n";
certainty = 0.0;
}
else if (!arrival.proper())
{
- cerr << "Arrival time is not knwon.\n";
+ std::cerr << "Arrival time is not knwon.\n";
certainty = 0.0;
}
else
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Oct 05, 2002
+** LAST MODIFIED : Feb 19, 2003
**=========================================================================
*/
{
long nr_lines = 0;
String line;
+ String qry;
+
+ String change_notification("");
+ String create_notification("");
+ bool initial_entry = false;
+
+ std::list<String> packages;
+
/* Double-check the classification of the message */
if (mail_header)
{
// Skip the mail header.
-
+
while (input >> line && line != "");
}
objectid = database.find_host(hostname);
if (objectid == "")
{
- cerr << "Please define the host " << hostname << " in the database.\n";
+ std::cerr << "Please define the host " << hostname << " in the database.\n";
return 0;
}
if (verbose)
{
- cout << "Object id for " << hostname << " is " << objectid << "\n";
+ std::cout << "Object id for " << hostname << " is " << objectid << "\n";
+ }
+
+ if (classification == RPMLIST)
+ {
+
+ int n_packages;
+
+ /* Read all packages, so we will know which ones are */
+ /* missing at the end. */
+
+ qry = "select name from parameter where objectid='";
+ qry += objectid + "' and class='package'";
+ n_packages = database.Query(qry);
+ initial_entry = n_packages == 0;
+
+ std::cout << n_packages << " packages in database.\n";
+ for (int t = 0; t < n_packages; t++)
+ {
+ packages.push_back(database.Field(t, "name"));
+ }
+ std::cout << "Package list built: " << packages.size() << ".\n";
}
/* Scan the input line by line, entring records into the database */
{
if (verbose)
{
- cout << line << "\n";
+ std::cout << line << "\n";
}
case SYSLOG:
check = &re_syslog;
break;
+ case SYSLOG_IRIX:
+ check = &re_syslog_irix;
+ break;
case ACCESSLOG:
check = &re_accesslog;
break;
case ERRORLOG:
check = &re_errorlog;
break;
+ case RPMLIST:
+ check = &re_rpm;
+ break;
}
if (line == *check)
int i;
String insertion("insert into log (objectid, servicecode,"
- " object_timestamp, timestamp, rawdata) values (");
+ " object_timestamp, timestamp, rawdata, processed) values (");
String datestring;
switch (classification)
log_time = line;
if (log_date.Year() < 0 || log_date.Year() > 2500)
{
- // The year is not in the log file. Assume the year of arrival
+ // The year is not in the log file. Assume the year of arrival,
+ // unless this puts the log entry at a later date than the arrival date.
+ // This happens e.g. when a log entry from December arrives in Januari.
log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
+ if (log_date > date(arrival))
+ {
+ log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
+ }
}
if (verbose)
{
- cout << " Log timestamp = " << log_date << " " << log_time << "\n";
+ std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
}
rest = line << 16;
i = rest.index(' ');
rest <<= i + 1;
if (verbose)
{
- cout << " Hostname matches.\n";
- cout << " rest = " << rest << "\n";
+ std::cout << " Hostname matches.\n";
+ std::cout << " rest = " << rest << "\n";
}
for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
if (verbose)
{
- cout << " Service name = " << rest(0,i) << "\n";
+ std::cout << " Service name = " << rest(0,i) << "\n";
}
/* Insert a new record into the log table */
insertion += "'" + objectid + "',";
insertion += "'" + rest(0,i) + "',";
- insertion += "'" + log_date.format() + " " + log_time.format() + "',";
- insertion += "'" + arrival.format() + "',";
- insertion += "'" + SQL_Escape(line) + "'";
+ insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
+ insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + SQL_Escape(line) + "',FALSE";
insertion += ")";
-
+
+ if (testmode)
+ {
+ std::cout << insertion << "\n";
+ }
+ else
+ {
+ database.Query(insertion);
+ }
+
+ if (verbose)
+ {
+ std::cout << "\n\n";
+ }
+
+ nr_lines++;
+ }
+ else
+ {
+ std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
+ }
+ break;
+
+ case SYSLOG_IRIX:
+ log_date = line;
+ log_time = line;
+ if (log_date.Year() < 0 || log_date.Year() > 2500)
+ {
+ // The year is not in the log file. Assume the year of arrival,
+ // unless this puts the log entry at a later date than the arrival date.
+ // This happens e.g. when a log entry from December arrives in Januari.
+
+ log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
+ if (log_date > date(arrival))
+ {
+ log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
+ }
+ }
+
+ if (verbose)
+ {
+ std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
+ }
+ rest = line << 19;
+ i = rest.index(' ');
+ if (rest(0,i) == hostname(0,i))
+ {
+ rest <<= i + 1;
+ if (verbose)
+ {
+ std::cout << " Hostname matches.\n";
+ std::cout << " rest = " << rest << "\n";
+ }
+ for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
+ if (verbose)
+ {
+ std::cout << " Service name = " << rest(0,i) << "\n";
+ }
+
+ /* Insert a new record into the log table */
+
+ insertion += "'" + objectid + "',";
+ insertion += "'" + rest(0,i) + "',";
+ insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
+ insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + SQL_Escape(line) + "',FALSE";
+ insertion += ")";
+
if (testmode)
{
- cout << insertion << "\n";
+ std::cout << insertion << "\n";
}
else
{
if (verbose)
{
- cout << "\n\n";
+ std::cout << "\n\n";
}
nr_lines++;
}
else
{
- cerr << " Hostname " << rest(0,i) << " does not match.\n";
+ std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
}
break;
log_time = datestring;
if (verbose)
{
- cout << " Log timestamp = " << log_date << " " << log_time << "\n";
+ std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
}
insertion += "'" + objectid + "',";
insertion += "'" + service + "',";
- insertion += "'" + log_date.format() + " " + log_time.format() + "',";
- insertion += "'" + arrival.format() + "',";
- insertion += "'" + SQL_Escape(line) + "'";
+ insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
+ insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + SQL_Escape(line) + "',FALSE";
insertion += ")";
-
+
if (testmode)
{
- cout << insertion << "\n";
+ std::cout << insertion << "\n";
}
else
{
if (verbose)
{
- cout << "\n\n";
+ std::cout << "\n\n";
}
nr_lines++;
log_time = datestring;
if (verbose)
{
- cout << " Log timestamp = " << log_date << " " << log_time << "\n";
+ std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
}
insertion += "'" + objectid + "',";
insertion += "'" + service + "',";
- insertion += "'" + log_date.format() + " " + log_time.format() + "',";
- insertion += "'" + arrival.format() + "',";
- insertion += "'" + SQL_Escape(line) + "'";
+ insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
+ insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + SQL_Escape(line) + "',FALSE";
insertion += ")";
-
+
if (testmode)
{
- cout << insertion << "\n";
+ std::cout << insertion << "\n";
}
else
{
if (verbose)
{
- cout << "\n\n";
+ std::cout << "\n\n";
+ }
+
+ nr_lines++;
+ break;
+
+ case RPMLIST:
+ // Scan a list of packages and versions from "rpm -a".
+ // A similar listing can be created on IRIX 6.5 by using the
+ // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
+ // |grep -v Version-Description".
+ //
+ // We have to separate the package name and the version.
+ // The separation is marked by a '-', followed by a digit.
+ // However, there may be other sequences of '-'digit in the package name,
+ // do we have to scan ahead until there is at most one such sequence
+ // left in the version string. The '-'digit seqeunce inside the
+ // version usually separates the version and the release number.
+
+ int version_start, next_version_start;
+
+ i = line.index('-');
+ version_start = i;
+ next_version_start = i;
+
+ while (i < ~line - 1)
+ {
+ while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
+ {
+ i++;
+ }
+ if (i < ~line - 1)
+ {
+ version_start = next_version_start;
+ next_version_start = i;
+ }
+ i++;
+ }
+
+ String package(line(0,version_start));
+ String version(line(version_start + 1, ~line));
+ String paramid;
+ String remark;
+ String insert_h;
+
+ if (verbose)
+ {
+ std::cout << "Package is " << package;
+ std::cout << ", version is " << version << "\n";
+ }
+
+ // Construct a qry to check the package's existance
+
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='package' and name='";
+ qry += package + "'";
+
+ if (database.Query(qry) == 1)
+ {
+ std::list<String>::iterator lp;
+
+ lp = find(packages.begin(), packages.end(), package);
+ if (lp != packages.end())
+ {
+ packages.erase(lp);
+ }
+ else
+ {
+ std::cerr << "Could NOT find " << package << " in list.\n";
+ }
+
+ paramid = database.Field(0, "paramid");
+ qry = "select value from property where paramid='";
+ qry += paramid + "' and name='version'";
+ if (database.Query(qry) == 0)
+ {
+ std::cerr << "Database corruption: Package " << package;
+ std::cerr << " does not have a 'version' property.\n";
+ }
+ else if (database.Field(0, "value") != version)
+ {
+ if (verbose)
+ {
+ std::cout << " Parameter " << package << " has different version\n";
+ }
+ insertion = "update property set value='";
+ insertion += version + "' where paramid='";
+ insertion += paramid + "' and name='version'";
+
+ insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
+ insert_h += " values ('";
+ insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
+ insert_h += version + "')";
+
+ database.Query(insertion);
+ database.Query(insert_h);
+
+ if (change_notification == "")
+ {
+ remark = "Gnucomo detected a different version for package parameter(s) ";
+ change_notification = database.new_notification(objectid, "property modified", remark);
+ }
+
+ if (change_notification != "")
+ {
+ insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+ insertion += change_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ else
+ {
+ std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+ }
+ }
+ else
+ {
+ if (verbose)
+ {
+ std::cout << " Parameter " << package << " has not changed.\n";
+ }
+ }
+ }
+ else
+ {
+
+ if (verbose)
+ {
+ std::cout << " Parameter " << package << " does not exist.\n";
+ }
+ // Create a new package parameter, including version property and history record
+
+ insertion = "insert into parameter (objectid, name, class, description) values ('";
+ insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
+ if (testmode)
+ {
+ paramid = "0";
+ std::cout << insertion << "\n";
+ }
+ else
+ {
+ database.Query(insertion);
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='package' and name='";
+ qry += package + "'";
+ database.Query(qry);
+ paramid = database.Field(0, "paramid");
+ }
+
+ insertion = "insert into property (paramid, name, value, type) values ('";
+ insertion += paramid + "', 'version', '";
+ insertion += version + "', 'STATIC')";
+ insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
+ insert_h += " values ('";
+ insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
+ insert_h += version + "')";
+
+ if (testmode)
+ {
+ std::cout << insertion << "\n" << insert_h << "\n";
+ }
+ else
+ {
+ database.Query(insertion);
+ database.Query(insert_h);
+ if (!initial_entry)
+ {
+ if (create_notification == "")
+ {
+ remark = "Gnucomo detected new parameter(s) of class package";
+ create_notification = database.new_notification(objectid, "parameter created", remark);
+ }
+ if (create_notification != "")
+ {
+ insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+ insertion += create_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ else
+ {
+ std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
+ }
+ }
+ }
+ }
+
+ if (verbose)
+ {
+ std::cout << "\n";
}
nr_lines++;
break;
+
}
}
else
{
- cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
+ std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
+ }
+ }
+
+ if (classification == RPMLIST)
+ {
+ std::list<String>::iterator lp;
+ String remove_notification("");
+
+ /*
+ * If there are any packages left in the list, they seem to have
+ * disappeared from the system.
+ */
+
+ for (lp = packages.begin(); lp != packages.end(); lp++)
+ {
+ String paramid;
+ String remark;
+ String insert;
+
+ // Construct a qry to check the package's existance
+
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='package' and name='";
+ qry += *lp + "'";
+
+ if (database.Query(qry) == 1)
+ {
+ paramid = database.Field(0, "paramid");
+ qry ="select change_nature from history where paramid='";
+ qry += paramid + "' order by modified desc";
+ if (database.Query(qry) <= 0)
+ {
+ std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
+ }
+ else if (database.Field(0, "change_nature") != "REMOVED")
+ {
+ if (verbose)
+ {
+ std::cout << "Removing parameter " << *lp << ".\n";
+ }
+
+ insert = "insert into history (paramid, modified, change_nature)";
+ insert += " values ('";
+ insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
+
+ database.Query(insert);
+
+ if (remove_notification == "")
+ {
+ remark = "Gnucomo detected that package(s) have disappeared ";
+ remove_notification = database.new_notification(objectid, "parameter removed", remark);
+ }
+
+ if (remove_notification != "")
+ {
+ insert = "insert into parameter_notification (notificationid, paramid) values ('";
+ insert += remove_notification + "', '";
+ insert += paramid + "')";
+
+ database.Query(insert);
+ }
+ else
+ {
+ std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+ }
+ }
+ }
}
}
if (verbose)
{
- cout << nr_lines << " lines parsed from the log file.\n";
+ std::cout << nr_lines << " lines parsed from the log file.\n";
}
return nr_lines;
}