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.4 $
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 : Nov 29, 2002
25 **************************************************************************/
27 /*****************************
29 Revision 1.4 2002-12-06 22:26:28 arjen
30 Set the value of log.processed to FALSE when inserting a
31 new log entry into the database
32 When a syslog entry arrives from last year, gcm_input subtracts one from the
33 year of arrival to create the year of the log entry.
34 Read output from "rpm -qa" and enter packages in the parameter table.
36 Revision 1.3 2002/11/09 08:04:27 arjen
37 Added a reference to the GPL
39 Revision 1.2 2002/11/04 10:13:36 arjen
40 Use proper namespace for iostream classes
42 Revision 1.1 2002/10/05 10:25:49 arjen
43 Creation of gcm_input and a first approach to a web interface
45 *****************************/
47 static const char *RCSID = "$Id: message.cpp,v 1.4 2002-12-06 22:26:28 arjen Exp $";
51 extern bool verbose; /* Defined in the main application */
54 /* Utility functions */
56 String SQL_Escape(String s);
58 /*=========================================================================
60 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
62 ** RETURN VALUE : True if input was available.
64 ** DESCRIPTION : Input operator. Read the next line from the message.
70 ** LAST MODIFIED : Nov 04, 2002
71 **=========================================================================
74 bool operator >> (message_buffer &b, String &s)
76 bool input_ok = false;
78 if (b.next_line == b.buffer.end())
84 b.buffer.push_back(l);
86 // next_line keeps pointing to the end.
101 client_message::client_message(std::istream *in, gnucomo_database db)
108 gpg_encrypted = false;
109 classification = UNKNOWN;
113 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
114 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}");
115 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}");
117 static const regex re_syslog(syslog_date_re + " [a-z]+ [[:alpha:]]+.*:.+");
118 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
119 static const regex re_dump("^ *DUMP: Date of this level");
120 static const regex re_accesslog("(GET|POST) .+ HTTP");
121 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
122 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
124 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
125 static const regex re_uxmail_from("^From - " + unix_date_re);
126 static const regex re_mail_From("^From:[[:blank:]]+");
127 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
128 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
129 static const regex re_email_user("[[:alnum:]_.-]+@");
131 /*=========================================================================
133 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
135 ** RETURN VALUE : The certainty with which the message is classified.
143 ** LAST MODIFIED : Nov 16, 2002
144 **=========================================================================
147 double client_message::classify(String host, UTC arriv, String serv)
155 /* First, check if the message has a mail header. */
157 if (input >> line && line == re_uxmail_from)
163 /* Scan ahead for the hostname and date of arrival. */
165 while (input >> line && line != "")
167 if (line == re_mail_From)
169 from_address = line(re_email_address);
170 from_address(re_email_user) = ""; // Remove the user part;
171 hostname = from_address;
173 if (line == re_mail_Date)
175 arrival = UTC(line(regex(mail_date_re)));
181 // Push the first line back, we need to read it again.
186 * Now that we have the mail header out of the way, try to figure
187 * out what the content of the message is.
191 while (input >> line && certainty < 0.9)
193 std::cout << " testing: " << line << "\n";
194 if (line == re_syslog)
197 classification = SYSLOG;
200 std::cout << "Syslog detected.\n";
203 else if (line == re_PGP)
206 gpg_encrypted = true;
207 std::cerr << "The message is PGP/GnuPG encrypted.\n";
209 else if (line == re_dump)
214 std::cout << "DUMP output detected.\n";
217 else if (line == re_accesslog)
220 classification = ACCESSLOG;
224 std::cout << "HTTP access log detected.\n";
227 else if (line == re_errorlog)
230 classification = ERRORLOG;
234 std::cout << "HTTP error log detected.\n";
237 else if (line == re_rpm)
240 classification = RPMLIST;
244 std::cout << "RPM package list detected.\n";
252 std::cerr << "Can not determine the hostname where the message came from.\n";
255 else if (!arrival.proper())
257 std::cerr << "Arrival time is not knwon.\n";
268 /*=========================================================================
270 ** SYNOPSIS : int enter()
272 ** RETURN VALUE : The number of lines successfully parsed from the input
280 ** LAST MODIFIED : Nov 29, 2002
281 **=========================================================================
284 int client_message::enter()
289 /* Double-check the classification of the message */
291 if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
298 // Skip the mail header.
300 while (input >> line && line != "");
303 /* Try to find the host in the database */
307 objectid = database.find_host(hostname);
310 std::cerr << "Please define the host " << hostname << " in the database.\n";
315 std::cout << "Object id for " << hostname << " is " << objectid << "\n";
318 /* Scan the input line by line, entring records into the database */
320 String rest; // Rest of the line to be parsed
322 while (input >> line)
326 std::cout << line << "\n";
330 /* Check each line if it contains valid information */
334 switch (classification)
340 check = &re_accesslog;
343 check = &re_errorlog;
356 String insertion("insert into log (objectid, servicecode,"
357 " object_timestamp, timestamp, rawdata, processed) values (");
360 switch (classification)
365 if (log_date.Year() < 0 || log_date.Year() > 2500)
367 // The year is not in the log file. Assume the year of arrival,
368 // unless this puts the log entry at a later date than the arrival date.
369 // This happens e.g. when a log entry from December arrives in Januari.
371 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
372 if (log_date > date(arrival))
374 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
380 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
384 if (rest(0,i) == hostname(0,i))
389 std::cout << " Hostname matches.\n";
390 std::cout << " rest = " << rest << "\n";
392 for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
395 std::cout << " Service name = " << rest(0,i) << "\n";
398 /* Insert a new record into the log table */
400 insertion += "'" + objectid + "',";
401 insertion += "'" + rest(0,i) + "',";
402 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
403 insertion += "'" + arrival.format() + "',";
404 insertion += "'" + SQL_Escape(line) + "',FALSE";
409 std::cout << insertion << "\n";
413 database.Query(insertion);
425 std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
430 datestring = line(regex("\\[.+\\]"));
433 datestring[datestring.index(':')] = ' ';
434 log_date = datestring;
435 log_time = datestring;
438 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
440 insertion += "'" + objectid + "',";
441 insertion += "'" + service + "',";
442 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
443 insertion += "'" + arrival.format() + "',";
444 insertion += "'" + SQL_Escape(line) + "',FALSE";
449 std::cout << insertion << "\n";
453 database.Query(insertion);
465 datestring = line(regex("\\[.+\\]"));
468 log_date = datestring;
469 log_time = datestring;
472 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
474 insertion += "'" + objectid + "',";
475 insertion += "'" + service + "',";
476 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
477 insertion += "'" + arrival.format() + "',";
478 insertion += "'" + SQL_Escape(line) + "',FALSE";
483 std::cout << insertion << "\n";
487 database.Query(insertion);
499 // Scan a list of packages and versions from "rpm -a".
500 // A similar listing can be created on IRIX 6.5 by using the
501 // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v ^[-=] \
502 // |grep -v Version-Description".
504 // We have to separate the package name and the version.
505 // The separation is marked by a '-', followed by a digit.
508 while (!(line[i] == '-' && isdigit(line[i + 1])))
512 String package(line(0,i));
513 String version(line(i+1, ~line));
518 std::cout << "Package is " << package;
519 std::cout << ", version is " << version << "\n";
522 // Construct a qry to check the package's existance
524 String qry = "select paramid from parameter where objectid='";
525 qry += objectid + "' and class='package' and name='";
526 qry += package + "'";
528 if (database.Query(qry) == 1)
530 paramid = database.Field(0, "paramid");
531 qry = "select value from property where paramid='";
532 qry += paramid + "' and name='version'";
533 if (database.Query(qry) == 0)
535 std::cerr << "Database corruption: Package " << package;
536 std::cerr << " does not have a 'version' property.\n";
538 else if (database.Field(0, "value") != version)
542 std::cout << " Parameter " << package << " has different version\n";
549 std::cout << " Parameter " << package << " has not changed.\n";
559 std::cout << " Parameter " << package << " does not exist.\n";
561 // Create a new package parameter, including version property and history record
563 insertion = "insert into parameter (objectid, name, class, description) values ('";
564 insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
568 std::cout << insertion << "\n";
572 database.Query(insertion);
573 qry = "select paramid from parameter where objectid='";
574 qry += objectid + "' and class='package' and name='";
575 qry += package + "'";
577 paramid = database.Field(0, "paramid");
580 insertion = "insert into property (paramid, name, value, type) values ('";
581 insertion += paramid + "', 'version', '";
582 insertion += version + "', 'STATIC')";
583 insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
584 insert_h += " values ('";
585 insert_h += paramid + "', '" + arrival.format() + "', 'CREATED', 'version', '";
586 insert_h += version + "')";
590 std::cout << insertion << "\n" << insert_h << "\n";
594 database.Query(insertion);
595 database.Query(insert_h);
611 std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
617 std::cout << nr_lines << " lines parsed from the log file.\n";
622 /*=========================================================================
624 ** SYNOPSIS : String SQL_Escape(String)
628 ** DESCRIPTION : Insert backslashes before single quotes.
635 **=========================================================================
638 String SQL_Escape(String s)
642 for (i = 0; i < ~s; i++)