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.5 $
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 : Dec 20, 2002
25 **************************************************************************/
27 /*****************************
29 Revision 1.5 2002-12-20 17:42:34 arjen
30 BUGFIX: The hostname in a system log may contain digits as well as letters
32 Revision 1.4 2002/12/06 22:26:28 arjen
33 Set the value of log.processed to FALSE when inserting a
34 new log entry into the database
35 When a syslog entry arrives from last year, gcm_input subtracts one from the
36 year of arrival to create the year of the log entry.
37 Read output from "rpm -qa" and enter packages in the parameter table.
39 Revision 1.3 2002/11/09 08:04:27 arjen
40 Added a reference to the GPL
42 Revision 1.2 2002/11/04 10:13:36 arjen
43 Use proper namespace for iostream classes
45 Revision 1.1 2002/10/05 10:25:49 arjen
46 Creation of gcm_input and a first approach to a web interface
48 *****************************/
50 static const char *RCSID = "$Id: message.cpp,v 1.5 2002-12-20 17:42:34 arjen Exp $";
54 extern bool verbose; /* Defined in the main application */
57 /* Utility functions */
59 String SQL_Escape(String s);
61 /*=========================================================================
63 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
65 ** RETURN VALUE : True if input was available.
67 ** DESCRIPTION : Input operator. Read the next line from the message.
73 ** LAST MODIFIED : Nov 04, 2002
74 **=========================================================================
77 bool operator >> (message_buffer &b, String &s)
79 bool input_ok = false;
81 if (b.next_line == b.buffer.end())
87 b.buffer.push_back(l);
89 // next_line keeps pointing to the end.
104 client_message::client_message(std::istream *in, gnucomo_database db)
111 gpg_encrypted = false;
112 classification = UNKNOWN;
116 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
117 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}");
118 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}");
120 static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
121 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
122 static const regex re_dump("^ *DUMP: Date of this level");
123 static const regex re_accesslog("(GET|POST) .+ HTTP");
124 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
125 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
127 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
128 static const regex re_uxmail_from("^From - " + unix_date_re);
129 static const regex re_mail_From("^From:[[:blank:]]+");
130 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
131 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
132 static const regex re_email_user("[[:alnum:]_.-]+@");
134 /*=========================================================================
136 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
138 ** RETURN VALUE : The certainty with which the message is classified.
146 ** LAST MODIFIED : Nov 16, 2002
147 **=========================================================================
150 double client_message::classify(String host, UTC arriv, String serv)
158 /* First, check if the message has a mail header. */
160 if (input >> line && line == re_uxmail_from)
166 /* Scan ahead for the hostname and date of arrival. */
168 while (input >> line && line != "")
170 if (line == re_mail_From)
172 from_address = line(re_email_address);
173 from_address(re_email_user) = ""; // Remove the user part;
174 hostname = from_address;
176 if (line == re_mail_Date)
178 arrival = UTC(line(regex(mail_date_re)));
184 // Push the first line back, we need to read it again.
189 * Now that we have the mail header out of the way, try to figure
190 * out what the content of the message is.
194 while (input >> line && certainty < 0.9)
196 std::cout << " testing: " << line << "\n";
197 if (line == re_syslog)
200 classification = SYSLOG;
203 std::cout << "Syslog detected.\n";
206 else if (line == re_PGP)
209 gpg_encrypted = true;
210 std::cerr << "The message is PGP/GnuPG encrypted.\n";
212 else if (line == re_dump)
217 std::cout << "DUMP output detected.\n";
220 else if (line == re_accesslog)
223 classification = ACCESSLOG;
227 std::cout << "HTTP access log detected.\n";
230 else if (line == re_errorlog)
233 classification = ERRORLOG;
237 std::cout << "HTTP error log detected.\n";
240 else if (line == re_rpm)
243 classification = RPMLIST;
247 std::cout << "RPM package list detected.\n";
255 std::cerr << "Can not determine the hostname where the message came from.\n";
258 else if (!arrival.proper())
260 std::cerr << "Arrival time is not knwon.\n";
271 /*=========================================================================
273 ** SYNOPSIS : int enter()
275 ** RETURN VALUE : The number of lines successfully parsed from the input
283 ** LAST MODIFIED : Nov 29, 2002
284 **=========================================================================
287 int client_message::enter()
292 /* Double-check the classification of the message */
294 if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
301 // Skip the mail header.
303 while (input >> line && line != "");
306 /* Try to find the host in the database */
310 objectid = database.find_host(hostname);
313 std::cerr << "Please define the host " << hostname << " in the database.\n";
318 std::cout << "Object id for " << hostname << " is " << objectid << "\n";
321 /* Scan the input line by line, entring records into the database */
323 String rest; // Rest of the line to be parsed
325 while (input >> line)
329 std::cout << line << "\n";
333 /* Check each line if it contains valid information */
337 switch (classification)
343 check = &re_accesslog;
346 check = &re_errorlog;
359 String insertion("insert into log (objectid, servicecode,"
360 " object_timestamp, timestamp, rawdata, processed) values (");
363 switch (classification)
368 if (log_date.Year() < 0 || log_date.Year() > 2500)
370 // The year is not in the log file. Assume the year of arrival,
371 // unless this puts the log entry at a later date than the arrival date.
372 // This happens e.g. when a log entry from December arrives in Januari.
374 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
375 if (log_date > date(arrival))
377 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
383 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
387 if (rest(0,i) == hostname(0,i))
392 std::cout << " Hostname matches.\n";
393 std::cout << " rest = " << rest << "\n";
395 for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
398 std::cout << " Service name = " << rest(0,i) << "\n";
401 /* Insert a new record into the log table */
403 insertion += "'" + objectid + "',";
404 insertion += "'" + rest(0,i) + "',";
405 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
406 insertion += "'" + arrival.format() + "',";
407 insertion += "'" + SQL_Escape(line) + "',FALSE";
412 std::cout << insertion << "\n";
416 database.Query(insertion);
428 std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
433 datestring = line(regex("\\[.+\\]"));
436 datestring[datestring.index(':')] = ' ';
437 log_date = datestring;
438 log_time = datestring;
441 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
443 insertion += "'" + objectid + "',";
444 insertion += "'" + service + "',";
445 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
446 insertion += "'" + arrival.format() + "',";
447 insertion += "'" + SQL_Escape(line) + "',FALSE";
452 std::cout << insertion << "\n";
456 database.Query(insertion);
468 datestring = line(regex("\\[.+\\]"));
471 log_date = datestring;
472 log_time = datestring;
475 std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
477 insertion += "'" + objectid + "',";
478 insertion += "'" + service + "',";
479 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
480 insertion += "'" + arrival.format() + "',";
481 insertion += "'" + SQL_Escape(line) + "',FALSE";
486 std::cout << insertion << "\n";
490 database.Query(insertion);
502 // Scan a list of packages and versions from "rpm -a".
503 // A similar listing can be created on IRIX 6.5 by using the
504 // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v ^[-=] \
505 // |grep -v Version-Description".
507 // We have to separate the package name and the version.
508 // The separation is marked by a '-', followed by a digit.
511 while (!(line[i] == '-' && isdigit(line[i + 1])))
515 String package(line(0,i));
516 String version(line(i+1, ~line));
521 std::cout << "Package is " << package;
522 std::cout << ", version is " << version << "\n";
525 // Construct a qry to check the package's existance
527 String qry = "select paramid from parameter where objectid='";
528 qry += objectid + "' and class='package' and name='";
529 qry += package + "'";
531 if (database.Query(qry) == 1)
533 paramid = database.Field(0, "paramid");
534 qry = "select value from property where paramid='";
535 qry += paramid + "' and name='version'";
536 if (database.Query(qry) == 0)
538 std::cerr << "Database corruption: Package " << package;
539 std::cerr << " does not have a 'version' property.\n";
541 else if (database.Field(0, "value") != version)
545 std::cout << " Parameter " << package << " has different version\n";
552 std::cout << " Parameter " << package << " has not changed.\n";
562 std::cout << " Parameter " << package << " does not exist.\n";
564 // Create a new package parameter, including version property and history record
566 insertion = "insert into parameter (objectid, name, class, description) values ('";
567 insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
571 std::cout << insertion << "\n";
575 database.Query(insertion);
576 qry = "select paramid from parameter where objectid='";
577 qry += objectid + "' and class='package' and name='";
578 qry += package + "'";
580 paramid = database.Field(0, "paramid");
583 insertion = "insert into property (paramid, name, value, type) values ('";
584 insertion += paramid + "', 'version', '";
585 insertion += version + "', 'STATIC')";
586 insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
587 insert_h += " values ('";
588 insert_h += paramid + "', '" + arrival.format() + "', 'CREATED', 'version', '";
589 insert_h += version + "')";
593 std::cout << insertion << "\n" << insert_h << "\n";
597 database.Query(insertion);
598 database.Query(insert_h);
614 std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
620 std::cout << nr_lines << " lines parsed from the log file.\n";
625 /*=========================================================================
627 ** SYNOPSIS : String SQL_Escape(String)
631 ** DESCRIPTION : Insert backslashes before single quotes.
638 **=========================================================================
641 String SQL_Escape(String s)
645 for (i = 0; i < ~s; i++)