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