Use proper namespace for iostream classes
[gnucomo.git] / src / gcm_input / message.cpp
1
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.2 $
10 **
11 **  DESCRIPTION      :  Implementation of the message handling classes
12 **
13 **  EXPORTED OBJECTS : 
14 **  LOCAL    OBJECTS : 
15 **  MODULES  USED    :
16 ***************************************************************************
17 **  ADMINISTRATIVE INFORMATION *
18 ********************************
19 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
20 **      CREATION DATE   : Sep 16, 2002
21 **      LAST UPDATE     : Nov 04, 2002
22 **      MODIFICATIONS   : 
23 **************************************************************************/
24
25 /*****************************
26    $Log: message.cpp,v $
27    Revision 1.2  2002-11-04 10:13:36  arjen
28    Use proper namespace for iostream classes
29
30    Revision 1.1  2002/10/05 10:25:49  arjen
31    Creation of gcm_input and a first approach to a web interface
32
33 *****************************/
34
35 static const char *RCSID = "$Id: message.cpp,v 1.2 2002-11-04 10:13:36 arjen Exp $";
36
37 #include "message.h"
38
39 extern bool verbose;   /*  Defined in the main application */
40 extern bool testmode;
41
42 /*   Utility functions   */
43
44 String SQL_Escape(String s);
45
46 /*=========================================================================
47 **  NAME           : operator >>
48 **  SYNOPSIS       : bool operator >> (message_buffer &, String &)
49 **  PARAMETERS     : 
50 **  RETURN VALUE   : True if input was available.
51 **
52 **  DESCRIPTION    : Input operator. Read the next line from the message.
53 **
54 **  VARS USED      :
55 **  VARS CHANGED   :
56 **  FUNCTIONS USED :
57 **  SEE ALSO       :
58 **  LAST MODIFIED  : Nov 04, 2002
59 **=========================================================================
60 */
61
62 bool operator >> (message_buffer &b, String &s)
63 {
64    bool   input_ok = false;
65
66    if (b.next_line == b.buffer.end())
67    {
68       String   l;
69
70       if (*(b.input) >> l)
71       {
72          b.buffer.push_back(l);
73
74          //   next_line keeps pointing to the end.
75  
76          s = l;
77          input_ok = true;
78       }
79    }
80    else
81    {
82       s = *(b.next_line);
83       b.next_line++;
84       input_ok = true;
85    }
86    return input_ok;
87 }
88
89 client_message::client_message(std::istream *in, gnucomo_database db)
90 {
91    input.from(in);
92    database = db;
93
94    hostname = "";
95    mail_header   = false;
96    gpg_encrypted = false;
97    classification = UNKNOWN;
98    certainty      = 0.0;
99 }
100
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}");
104
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)\\] .+");
110
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:]_.-]+@");
117
118 /*=========================================================================
119 **  NAME           : classify
120 **  SYNOPSIS       : double classify(String host, date arriv_d, hour arriv_t, String serv)
121 **  PARAMETERS     : 
122 **  RETURN VALUE   : The certainty with which the message is classified.
123 **
124 **  DESCRIPTION    : 
125 **
126 **  VARS USED      :
127 **  VARS CHANGED   :
128 **  FUNCTIONS USED :
129 **  SEE ALSO       :
130 **  LAST MODIFIED  : Nov 04, 2002
131 **=========================================================================
132 */
133
134 double client_message::classify(String host, UTC arriv, String serv)
135 {
136    String    line;
137
138    hostname    = host;
139    arrival     = arriv;
140    service     = serv;
141
142    /*  First, check if the message has a mail header. */
143
144    if (input >> line && line == re_uxmail_from)
145    {
146       String    from_address;
147
148       mail_header = true;
149
150       /*  Scan ahead for the hostname and date of arrival.  */
151
152       while (input >> line && line != "")
153       {
154          if (line == re_mail_From)
155          {
156             from_address = line(re_email_address);
157             from_address(re_email_user) = "";            //  Remove the user part;
158             hostname = from_address;
159          }
160          if (line == re_mail_Date)
161          {
162             arrival = UTC(line(regex(mail_date_re)));
163          }
164       }
165    }
166    else
167    {
168       //  Push the first line back, we need to read it again.
169       --input;
170    }
171
172    /*
173     *  Now that we have the mail header out of the way, try to figure
174     *  out what the content of the message is.
175     */
176
177
178    while (input >> line && certainty < 0.9)
179    {
180       std::cout << "  testing: " << line << "\n";
181       if (line == re_syslog)
182       {
183          certainty = 1.0;
184          classification = SYSLOG;
185          if (verbose)
186          {
187             std::cout << "Syslog detected.\n";
188          }
189       }
190       else if (line == re_PGP)
191       {
192          certainty = 1.0;
193          gpg_encrypted = true;
194          std::cerr << "The message is PGP/GnuPG encrypted.\n";
195       }
196       else if (line == re_dump)
197       {
198           certainty = 1.0;
199           if (verbose)
200           {
201              std::cout << "DUMP output detected.\n";
202           }
203       }
204       else if (line == re_accesslog)
205       {
206           certainty = 1.0;
207           classification = ACCESSLOG;
208           service = "httpd";
209           if (verbose)
210           {
211              std::cout << "HTTP access log detected.\n";
212           }
213       }
214       else if (line == re_errorlog)
215       {
216           certainty = 1.0;
217           classification = ERRORLOG;
218           service = "httpd";
219           if (verbose)
220           {
221              std::cout << "HTTP error log detected.\n";
222           }
223       }
224    }
225    input.rewind();
226
227    if (hostname == "")
228    {
229       std::cerr <<  "Can not determine the hostname where the message came from.\n";
230       certainty = 0.0;
231    }
232    else if (!arrival.proper())
233    {
234       std::cerr << "Arrival time is not knwon.\n";
235       certainty = 0.0;
236    }
237    else
238    {
239       certainty = 1.0;
240    }
241
242    return certainty;
243 }
244
245 /*=========================================================================
246 **  NAME           : enter
247 **  SYNOPSIS       : int enter()
248 **  PARAMETERS     : 
249 **  RETURN VALUE   : The number of lines successfully parsed from the input
250 **
251 **  DESCRIPTION    : 
252 **
253 **  VARS USED      :
254 **  VARS CHANGED   :
255 **  FUNCTIONS USED :
256 **  SEE ALSO       :
257 **  LAST MODIFIED  : Nov 04, 2002
258 **=========================================================================
259 */
260
261 int client_message::enter()
262 {
263    long   nr_lines = 0;
264    String line;
265
266    /*  Double-check the classification of the message */
267
268    if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
269    {
270       return 0;
271    }
272
273    if (mail_header)
274    {
275       //  Skip the mail header.
276  
277       while (input >> line && line != "");
278    }
279
280    /*  Try to find the host in the database */
281
282    String objectid;
283
284    objectid = database.find_host(hostname);
285    if (objectid == "")
286    {
287       std::cerr << "Please define the host " << hostname << " in the database.\n";
288       return 0;
289    }
290    if (verbose)
291    {
292       std::cout << "Object id for " << hostname << " is " << objectid << "\n";
293    }
294
295    /*  Scan the input line by line, entring records into the database */
296
297    String rest;   //  Rest of the line to be parsed
298
299    while (input >> line)
300    {
301       if (verbose)
302       {
303          std::cout << line << "\n";
304       }
305
306
307       /*  Check each line if it contains valid information */
308
309       const regex *check;
310
311       switch (classification)
312       {
313       case SYSLOG:
314             check = &re_syslog;
315             break;
316       case ACCESSLOG:
317             check = &re_accesslog;
318             break;
319       case ERRORLOG:
320             check = &re_errorlog;
321             break;
322       }
323
324       if (line == *check)
325       {
326          date   log_date;
327          hour   log_time;
328          int    i;
329
330          String insertion("insert into log (objectid, servicecode,"
331                            " object_timestamp, timestamp, rawdata) values (");
332          String datestring;
333
334          switch (classification)
335          {
336          case SYSLOG:
337             log_date = line;
338             log_time = line;
339             if (log_date.Year() < 0 || log_date.Year() > 2500)
340             {
341                //  The year is not in the log file. Assume the year of arrival
342
343                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
344             }
345
346             if (verbose)
347             {
348                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
349             }
350             rest = line << 16;
351             i = rest.index(' ');
352             if (rest(0,i) == hostname(0,i))
353             {
354                rest <<= i + 1;
355                if (verbose)
356                {
357                   std::cout << "   Hostname matches.\n";
358                   std::cout << "   rest = " << rest << "\n";
359                }
360                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
361                if (verbose)
362                {
363                   std::cout << "   Service name = " << rest(0,i) << "\n";
364                }
365
366                /*   Insert a new record into the log table   */
367
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) + "'";
373                insertion += ")";
374             
375                if (testmode)
376                {
377                   std::cout << insertion << "\n";
378                }
379                else
380                {
381                   database.Query(insertion);
382                }
383
384                if (verbose)
385                {
386                   std::cout << "\n\n";
387                }
388
389                nr_lines++;
390             }
391             else
392             {
393                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
394             }
395             break;
396
397          case ACCESSLOG:
398             datestring = line(regex("\\[.+\\]"));
399             datestring <<= 1;
400             datestring >>= 1;
401             datestring[datestring.index(':')] = ' ';
402             log_date = datestring;
403             log_time = datestring;
404             if (verbose)
405             {
406                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
407             }
408             insertion += "'" + objectid + "',";
409             insertion += "'" + service + "',";
410             insertion += "'" + log_date.format() + " " + log_time.format() + "',";
411             insertion += "'" + arrival.format() + "',";
412             insertion += "'" + SQL_Escape(line) + "'";
413             insertion += ")";
414             
415             if (testmode)
416             {
417                std::cout << insertion << "\n";
418             }
419             else
420             {
421                database.Query(insertion);
422             }
423
424             if (verbose)
425             {
426                std::cout << "\n\n";
427             }
428
429             nr_lines++;
430             break;
431
432          case ERRORLOG:
433             datestring = line(regex("\\[.+\\]"));
434             datestring <<= 1;
435             datestring >>= 1;
436             log_date = datestring;
437             log_time = datestring;
438             if (verbose)
439             {
440                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
441             }
442             insertion += "'" + objectid + "',";
443             insertion += "'" + service + "',";
444             insertion += "'" + log_date.format() + " " + log_time.format() + "',";
445             insertion += "'" + arrival.format() + "',";
446             insertion += "'" + SQL_Escape(line) + "'";
447             insertion += ")";
448             
449             if (testmode)
450             {
451                std::cout << insertion << "\n";
452             }
453             else
454             {
455                database.Query(insertion);
456             }
457
458             if (verbose)
459             {
460                std::cout << "\n\n";
461             }
462
463             nr_lines++;
464             break;
465          }
466       }
467       else
468       {
469          std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
470       }
471    }
472
473    if (verbose)
474    {
475       std::cout << nr_lines << " lines parsed from the log file.\n";
476    }
477    return nr_lines;
478 }
479
480 /*=========================================================================
481 **  NAME           : SQL_Escape
482 **  SYNOPSIS       : String SQL_Escape(String)
483 **  PARAMETERS     : 
484 **  RETURN VALUE   : 
485 **
486 **  DESCRIPTION    : Insert backslashes before single quotes.
487 **
488 **  VARS USED      :
489 **  VARS CHANGED   :
490 **  FUNCTIONS USED :
491 **  SEE ALSO       :
492 **  LAST MODIFIED  : 
493 **=========================================================================
494 */
495
496 String SQL_Escape(String s)
497 {
498    int i;
499
500    for (i = 0; i < ~s; i++)
501    {
502       if (s[i] == '\'')
503       {
504          s(i,0) = "\\";
505          i++;
506       }
507    }
508
509    return s;
510 }