2 /**************************************************************************
3 ** (c) Copyright 2002, Andromeda Technology & Automation
4 ***************************************************************************
5 ** MODULE INFORMATION *
6 ***********************
7 ** FILE NAME : message.cpp
8 ** SYSTEM NAME : Gnucomo - Gnu Computer Monitoring
9 ** VERSION NUMBER : $Revision: 1.1 $
11 ** DESCRIPTION : Implementation of the message handling classes
16 ***************************************************************************
17 ** ADMINISTRATIVE INFORMATION *
18 ********************************
19 ** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
20 ** CREATION DATE : Sep 16, 2002
21 ** LAST UPDATE : Oct 05, 2002
23 **************************************************************************/
25 /*****************************
27 Revision 1.1 2002-10-05 10:25:49 arjen
28 Creation of gcm_input and a first approach to a web interface
30 *****************************/
32 static const char *RCSID = "$Id: message.cpp,v 1.1 2002-10-05 10:25:49 arjen Exp $";
36 extern bool verbose; /* Defined in the main application */
39 /* Utility functions */
41 String SQL_Escape(String s);
43 /*=========================================================================
45 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
47 ** RETURN VALUE : True if input was available.
49 ** DESCRIPTION : Input operator. Read the next line from the message.
55 ** LAST MODIFIED : Sep 30, 2002
56 **=========================================================================
59 bool operator >> (message_buffer &b, String &s)
61 bool input_ok = false;
63 if (b.next_line == b.buffer.end())
67 //cout << " buffer is depleted.\n";
70 b.buffer.push_back(l);
72 // next_line keeps pointing to the end.
76 //cout << " new line from input.\n";
81 //cout << " reading from cache.\n";
89 client_message::client_message(istream *in, gnucomo_database db)
96 gpg_encrypted = false;
97 classification = UNKNOWN;
101 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
102 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}");
103 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}");
105 static const regex re_syslog(syslog_date_re + " [a-z]+ [[:alpha:]]+.*:.+");
106 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
107 static const regex re_dump("^ *DUMP: Date of this level");
108 static const regex re_accesslog("(GET|POST) .+ HTTP");
109 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
111 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
112 static const regex re_uxmail_from("^From - " + unix_date_re);
113 static const regex re_mail_From("^From:[[:blank:]]+");
114 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
115 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
116 static const regex re_email_user("[[:alnum:]_.-]+@");
118 /*=========================================================================
120 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
122 ** RETURN VALUE : The certainty with which the message is classified.
130 ** LAST MODIFIED : Oct 05, 2002
131 **=========================================================================
134 double client_message::classify(String host, UTC arriv, String serv)
142 /* First, check if the message has a mail header. */
144 if (input >> line && line == re_uxmail_from)
150 /* Scan ahead for the hostname and date of arrival. */
152 while (input >> line && line != "")
154 if (line == re_mail_From)
156 from_address = line(re_email_address);
157 from_address(re_email_user) = ""; // Remove the user part;
158 hostname = from_address;
160 if (line == re_mail_Date)
162 arrival = UTC(line(regex(mail_date_re)));
168 // Push the first line back, we need to read it again.
173 * Now that we have the mail header out of the way, try to figure
174 * out what the content of the message is.
178 while (input >> line && certainty < 0.9)
180 cout << " testing: " << line << "\n";
181 if (line == re_syslog)
184 classification = SYSLOG;
187 cout << "Syslog detected.\n";
190 else if (line == re_PGP)
193 gpg_encrypted = true;
194 cerr << "The message is PGP/GnuPG encrypted.\n";
196 else if (line == re_dump)
201 cout << "DUMP output detected.\n";
204 else if (line == re_accesslog)
207 classification = ACCESSLOG;
211 cout << "HTTP access log detected.\n";
214 else if (line == re_errorlog)
217 classification = ERRORLOG;
221 cout << "HTTP error log detected.\n";
229 cerr << "Can not determine the hostname where the message came from.\n";
232 else if (!arrival.proper())
234 cerr << "Arrival time is not knwon.\n";
245 /*=========================================================================
247 ** SYNOPSIS : int enter()
249 ** RETURN VALUE : The number of lines successfully parsed from the input
257 ** LAST MODIFIED : Oct 05, 2002
258 **=========================================================================
261 int client_message::enter()
266 /* Double-check the classification of the message */
268 if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
275 // Skip the mail header.
277 while (input >> line && line != "");
280 /* Try to find the host in the database */
284 objectid = database.find_host(hostname);
287 cerr << "Please define the host " << hostname << " in the database.\n";
292 cout << "Object id for " << hostname << " is " << objectid << "\n";
295 /* Scan the input line by line, entring records into the database */
297 String rest; // Rest of the line to be parsed
299 while (input >> line)
303 cout << line << "\n";
307 /* Check each line if it contains valid information */
311 switch (classification)
317 check = &re_accesslog;
320 check = &re_errorlog;
330 String insertion("insert into log (objectid, servicecode,"
331 " object_timestamp, timestamp, rawdata) values (");
334 switch (classification)
339 if (log_date.Year() < 0 || log_date.Year() > 2500)
341 // The year is not in the log file. Assume the year of arrival
343 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
348 cout << " Log timestamp = " << log_date << " " << log_time << "\n";
352 if (rest(0,i) == hostname(0,i))
357 cout << " Hostname matches.\n";
358 cout << " rest = " << rest << "\n";
360 for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
363 cout << " Service name = " << rest(0,i) << "\n";
366 /* Insert a new record into the log table */
368 insertion += "'" + objectid + "',";
369 insertion += "'" + rest(0,i) + "',";
370 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
371 insertion += "'" + arrival.format() + "',";
372 insertion += "'" + SQL_Escape(line) + "'";
377 cout << insertion << "\n";
381 database.Query(insertion);
393 cerr << " Hostname " << rest(0,i) << " does not match.\n";
398 datestring = line(regex("\\[.+\\]"));
401 datestring[datestring.index(':')] = ' ';
402 log_date = datestring;
403 log_time = datestring;
406 cout << " Log timestamp = " << log_date << " " << log_time << "\n";
408 insertion += "'" + objectid + "',";
409 insertion += "'" + service + "',";
410 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
411 insertion += "'" + arrival.format() + "',";
412 insertion += "'" + SQL_Escape(line) + "'";
417 cout << insertion << "\n";
421 database.Query(insertion);
433 datestring = line(regex("\\[.+\\]"));
436 log_date = datestring;
437 log_time = datestring;
440 cout << " Log timestamp = " << log_date << " " << log_time << "\n";
442 insertion += "'" + objectid + "',";
443 insertion += "'" + service + "',";
444 insertion += "'" + log_date.format() + " " + log_time.format() + "',";
445 insertion += "'" + arrival.format() + "',";
446 insertion += "'" + SQL_Escape(line) + "'";
451 cout << insertion << "\n";
455 database.Query(insertion);
469 cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
475 cout << nr_lines << " lines parsed from the log file.\n";
480 /*=========================================================================
482 ** SYNOPSIS : String SQL_Escape(String)
486 ** DESCRIPTION : Insert backslashes before single quotes.
493 **=========================================================================
496 String SQL_Escape(String s)
500 for (i = 0; i < ~s; i++)