2
** Copyright (C) 2009-2011 Softwink, Inc.
3
** Copyright (C) 2009-2011 Champ Clark III <champ@softwink.com>
5
** This program is free software; you can redistribute it and/or modify
6
** it under the terms of the GNU General Public License Version 2 as
7
** published by the Free Software Foundation. You may not use, modify or
8
** distribute this program under any other version of the GNU General
11
** This program is distributed in the hope that it will be useful,
12
** but WITHOUT ANY WARRANTY; without even the implied warranty of
13
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
** GNU General Public License for more details.
16
** You should have received a copy of the GNU General Public License
17
** along with this program; if not, write to the Free Software
18
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
* Threaded function for database support. These functions are for both
24
* MySQL and PostgreSQL. These allow Sagan to report to Snort databases
25
* where we'll attempt to correlate the events.
30
#include "config.h" /* From autoconf */
34
#if defined(HAVE_LIBMYSQLCLIENT_R) || defined(HAVE_LIBPQ)
37
#include <netinet/in.h>
38
#include <arpa/inet.h>
46
#include "sagan-snort.h"
52
#ifdef HAVE_LIBMYSQLCLIENT_R
53
#include <mysql/mysql.h>
54
#include <mysql/errmsg.h>
55
MYSQL *connection, *mysql;
65
struct _SaganConfig *config;
66
struct _SaganDebug *debug;
67
struct _SaganCounters *counters;
69
struct rule_struct *rulestruct;
71
pthread_mutex_t db_mutex;
74
/********************************************/
75
/* Connection to various types of databases */
76
/********************************************/
78
int db_connect( void ) {
87
dbp = config->dbpassword;
90
/********************/
91
/* MySQL connection */
92
/********************/
94
#ifdef HAVE_LIBMYSQLCLIENT_R
95
if ( config->dbtype == 1 ) {
98
mysql = mysql_init(NULL);
100
if ( mysql == NULL ) {
102
sagan_log(1, "[%s, line %d] Error initializing MySQL", __FILE__, __LINE__);
106
my_bool reconnect = 1;
107
mysql_options(mysql,MYSQL_READ_DEFAULT_GROUP,config->dbname);
109
/* Re-connect to the database if the connection is lost */
111
mysql_options(mysql,MYSQL_OPT_RECONNECT, &reconnect);
113
if (!mysql_real_connect(mysql, dbh, dbu, dbp, dbn, MYSQL_PORT, NULL, 0)) {
114
sagan_log(1, "[%s, line %d] MySQL Error %u: \"%s\"", __FILE__, __LINE__, mysql_errno(mysql), mysql_error(mysql));
120
/*************************/
121
/* PostgreSQL connection */
122
/*************************/
125
if ( config->dbtype == 2 ) {
127
//isthreadsafe = PQisthreadsafe(); // check
129
snprintf(pgconnect, sizeof(pgconnect), "hostaddr = '%s' port = '%d' dbname = '%s' user = '%s' password = '%s' connect_timeout = '30'", dbh, 5432 , dbn, dbu, dbp);
131
psql = PQconnectdb(pgconnect);
135
sagan_log(1, "[%s, line %d] PostgreSQL: PQconnect Error", __FILE__, __LINE__);
138
if (PQstatus(psql) != CONNECTION_OK) {
140
sagan_log(1, "[%s, line %d] PostgreSQL status not OK", __FILE__, __LINE__);
147
} /* End of db_connect */
149
/****************************************************************************
150
* Query Database | iorq == 0 (SELECT) iorq == 1 (INSERT) *
151
* For SELECT, we typically only want one value back (row[0]) so return it *
152
* For INSERT, we don't need or get any results back *
153
****************************************************************************/
155
char *db_query ( int dbtype, char *sql ) {
157
char sqltmp[MAXSQL]; /* Make this a MAXSQL or something */
158
char *re=NULL; /* "return" point for row */
160
int mysql_last_errno = 0;
161
int mysql_reconnect_count = 0;
163
pthread_mutex_lock( &db_mutex );
165
strlcpy(sqltmp, sql, sizeof(sqltmp));
167
if ( debug->debugsql ) sagan_log(0, "%s", sqltmp);
169
#ifdef HAVE_LIBMYSQLCLIENT_R
175
while ( mysql_real_query(mysql, sqltmp, strlen(sqltmp)) != 0 ) {
177
mysql_last_errno = mysql_errno(mysql);
179
if ( mysql_last_errno == CR_CONNECTION_ERROR ||
180
mysql_last_errno == CR_CONN_HOST_ERROR ||
181
mysql_last_errno == CR_SERVER_GONE_ERROR ) {
182
mysql_reconnect_count++;
183
sagan_log(0, "[%s, line %d] Lost connection to MySQL database. Trying %d", __FILE__, __LINE__, mysql_reconnect_count);
184
sleep(2); // Give the DB time to recover
188
sagan_log(0, "[%s, line %d] MySQL Error [%u:] \"%s\"\nOffending SQL statement: %s\n", __FILE__, __LINE__, mysql_errno(mysql), mysql_error(mysql), sqltmp);
194
if ( mysql_reconnect_count != 0 ) { /* If there's a reconnect_count, we must of lost connection */
195
sagan_log(0, "MySQL connection re-established!"); /* Log it */
196
mysql_reconnect_count=0; /* Reset the counter */
199
res = mysql_use_result(mysql);
202
while((row = mysql_fetch_row(res))) {
203
snprintf(sqltmp, sizeof(sqltmp), "%s", row[0]);
208
mysql_free_result(res);
209
pthread_mutex_unlock( &db_mutex );
215
sagan_log(1, "Sagan was not compiled with MySQL support. Aborting!");
222
if (( result = PQexec(psql, sqltmp )) == NULL ) {
224
sagan_log(0, "[%s, line %d] PostgreSQL Error: %s", __FILE__, __LINE__, PQerrorMessage( psql ));
227
if (PQresultStatus(result) != PGRES_COMMAND_OK &&
228
PQresultStatus(result) != PGRES_TUPLES_OK) {
229
sagan_log(0, "[%s, line %d] PostgreSQL Error: %s", __FILE__, __LINE__, PQerrorMessage( psql ));
232
sagan_log(0, "DB Query failed: %s", sqltmp);
235
if ( PQntuples(result) != 0 ) {
236
re = PQgetvalue(result,0,0);
240
pthread_mutex_unlock( &db_mutex);
247
sagan_log(1, "[%s, line %d] Sagan was not compiled with PostgreSQL support. Aborting!", __FILE__, __LINE__);
254
/*****************************************************************************/
255
/* Get's the current sensor ID or creates a new one if this is the first run */
256
/*****************************************************************************/
258
int get_sensor_id ( char *hostname, char *interface, char *filter, int detail, int dbtype ) {
264
snprintf(sqltmp, sizeof(sqltmp), "SELECT sid FROM sensor WHERE hostname='%s' AND interface='%s' AND filter='%s' AND detail='%d' AND encoding='0'", hostname, interface, filter, detail);
266
sqlout = db_query( dbtype, sql );
268
if ( sqlout == NULL ) {
270
/* Insert new sensor ID */
271
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO sensor (hostname, interface, filter, detail, encoding, last_cid) VALUES ('%s', '%s', '%s', '%u', '0', '0')", hostname, interface, filter, detail);
273
db_query(dbtype, sql);
275
/* Get new sensor ID */
276
snprintf(sqltmp, sizeof(sqltmp), "SELECT sid FROM sensor WHERE hostname='%s' AND interface='%s' AND filter='%s' AND detail='%d' AND encoding='0'", hostname, interface, filter, detail);
278
sqlout = db_query( dbtype, sql );
281
config->sensor_id = atoi(sqlout);
287
/******************************************/
288
/* Get the last used CID and increment it */
289
/******************************************/
291
uint64_t get_cid ( int sensor_sid, int dbtype ) {
299
snprintf(sqltmp, sizeof(sqltmp), "SELECT last_cid from sensor where sid=%d and hostname='%s' and interface='%s' and filter='%s' and detail=%d", sensor_sid, config->sagan_hostname, config->sagan_interface, config->sagan_filter, config->sagan_detail);
302
sqlout = db_query( dbtype, sql );
304
if ( sqlout == NULL ) {
305
t_cid = 0; /* Returned NULL, no CID found */
307
t_cid = atol(sqlout);
314
/*********************************************************/
315
/* Get signature ID. If on doesn't exsist, put one in. */
316
/*********************************************************/
319
int get_sig_sid(char *t_msg, char *t_sig_rev, char *t_sig_sid, char *classtype, int t_sig_pri, int dbtype ) {
328
snprintf(sqltmp, sizeof(sqltmp), "SELECT sig_class_id from sig_class where sig_class_name='%s'", classtype);
330
sqlout = db_query( dbtype, sql );
332
if ( sqlout == NULL ) {
334
/* classification hasn't been recorded in sig_class, so put it in */
336
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO sig_class(sig_class_id, sig_class_name) VALUES (DEFAULT, '%s')", classtype);
338
db_query( dbtype, sql);
342
snprintf(sqltmp, sizeof(sqltmp), "SELECT sig_class_id from sig_class where sig_class_name='%s'", classtype);
344
sqlout = db_query( dbtype, sql );
347
sig_class_id = atoi(sqlout);
349
/* Look for the signature id */
351
snprintf(sqltmp, sizeof(sqltmp), "SELECT sig_id FROM signature WHERE sig_name='%s' AND sig_rev=%s AND sig_sid=%s", t_msg, t_sig_rev, t_sig_sid);
355
sqlout = db_query( dbtype, sql );
357
/* If not found, create a new entry for it */
359
if ( sqlout == NULL ) {
361
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO signature(sig_name, sig_class_id, sig_priority, sig_rev, sig_sid) VALUES ('%s', '%d', '%d', '%s', '%s' )", t_msg, sig_class_id, t_sig_pri, t_sig_rev, t_sig_sid);
363
db_query( dbtype, sql );
365
/* Get the new ID of the new entry */
366
snprintf(sqltmp, sizeof(sqltmp), "SELECT sig_id FROM signature WHERE sig_name='%s' AND sig_rev=%s AND sig_sid=%s", t_msg, t_sig_rev, t_sig_sid);
368
sqlout = db_query( dbtype, sql );;
371
t_sig_id = atoi(sqlout);
377
/***************************/
378
/* Insert into event table */
379
/***************************/
381
void insert_event (int t_sid, uint64_t t_cid, int t_sig_sid, int dbtype, char *date, char *time ) {
386
pthread_mutex_lock( &db_mutex );
388
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO event(sid, cid, signature, timestamp) VALUES ('%d', '%" PRIu64 "', '%d', '%s %s')", t_sid, t_cid, t_sig_sid, date, time );
391
pthread_mutex_unlock( &db_mutex );
393
db_query( dbtype, sql );
398
/****************************************************************************************/
399
/* Insert data into iphdr and tcphdr - most of this is bogus as we're not really TCP/IP */
400
/****************************************************************************************/
402
void insert_hdr(int t_sid, uint64_t t_cid, char *t_ipsrc, char *t_ipdst, int t_ipproto, int endian, int dbtype, int dst_port, int src_port) {
408
/* Temp. store 32bit IP address for DB insertion */
412
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO iphdr VALUES ( '%d', '%" PRIu64 "', '%d', '%d', '4', '0', '0', '0', '0', '0', '0', '0', '%d', '0' )", t_sid, t_cid, ip2bit(t_ipsrc, endian), ip2bit(t_ipdst, endian), t_ipproto );
415
db_query( dbtype, sql );
418
if ( t_ipproto == 6 ) {
419
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO tcphdr VALUES ( '%d', '%" PRIu64 "', '%d', '%d', '0', '0', '0', '0', '0', '0', '0', '0' )", t_sid, t_cid, src_port, dst_port );
421
db_query( dbtype, sql );
426
if ( t_ipproto == 17 ) {
427
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO udphdr VALUES ( '%d', '%" PRIu64 "', '%d', '%d', '0', '0' )", t_sid, t_cid, src_port, dst_port );
429
db_query( dbtype, sql );
432
/* Basic ICMP - Set to type 8 (echo) , code of 8 */
433
/* May expand on this if there's actually a use for it */
435
if ( t_ipproto == 1 ) {
436
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO icmphdr VALUES ( '%d', '%" PRIu64 "', '8', '8', '0', '0', '0' )", t_sid, t_cid );
438
db_query( dbtype, sql );
444
/*****************************/
445
/* Insert into payload table */
446
/*****************************/
448
void insert_payload ( int t_sid, uint64_t t_cid, char *t_hex_data, int dbtype ) {
453
pthread_mutex_lock( &db_mutex );
454
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO data(sid, cid, data_payload) VALUES ('%d', '%" PRIu64 "', '%s')", t_sid, t_cid, t_hex_data);
456
pthread_mutex_unlock( &db_mutex );
457
db_query( dbtype, sql );
461
/*******************/
462
/* Record last cid */
463
/*******************/
465
void record_last_cid ( void ) {
470
snprintf(sqltmp, sizeof(sqltmp), "UPDATE sensor SET last_cid='%" PRIu64 "' where sid=%d and hostname='%s' and interface='%s' and filter='%s' and detail=%d", counters->sigcid, config->sensor_id, config->sagan_hostname, config->sagan_interface, config->sagan_filter, config->sagan_detail);
472
db_query( config->dbtype, sql );
476
/********************/
477
/* Reference system */
478
/********************/
480
void query_reference ( char *ref, char *rule_sid, int sig_sid, int seq )
484
char *tmptoken1=NULL;
485
char *tmptoken2=NULL;
496
strlcpy(reference, ref, sizeof(reference));
498
tmptoken1 = strtok_r(reference, ",", &saveptr);
499
tmptoken2 = strtok_r(NULL, "," , &saveptr);
501
/* Look for improperly formated references */
503
if (tmptoken1 == NULL || tmptoken2 == NULL )
505
sagan_log(0, "Warning: \"reference:\" contains a NULL value. Check sid: %s", rule_sid);
509
snprintf(sqltmp, sizeof(sqltmp), "SELECT ref_system_id from reference_system where ref_system_name='%s'", tmptoken1);
511
sqlout = db_query( config->dbtype, sql );
513
/* reference_system hasn't been entered into the DB. Do so now */
515
if ( sqlout == NULL ) {
516
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO reference_system (ref_system_id, ref_system_name) VALUES (DEFAULT, '%s')", tmptoken1);
518
db_query( config->dbtype, sql );
520
snprintf(sqltmp, sizeof(sqltmp), "SELECT ref_system_id from reference_system where ref_system_name='%s'", tmptoken1);
522
sqlout = db_query( config->dbtype, sql );
525
ref_system_id = atoi(sqlout);
527
snprintf(sqltmp, sizeof(sqltmp), "SELECT ref_id from reference where ref_system_id='%d' and ref_tag='%s'", ref_system_id, tmptoken2);
529
sqlout = db_query( config->dbtype, sql );
531
if ( sqlout == NULL ) {
532
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO reference (ref_id, ref_system_id, ref_tag) VALUES (DEFAULT, '%d', '%s')", ref_system_id, tmptoken2);
534
sqlout = db_query( config->dbtype, sql );
536
snprintf(sqltmp, sizeof(sqltmp), "SELECT ref_id from reference where ref_system_id='%d' and ref_tag='%s'", ref_system_id, tmptoken2);
538
sqlout = db_query( config->dbtype, sql );
542
ref_id = atoi(sqlout);
544
snprintf(sqltmp, sizeof(sqltmp), "SELECT sig_id from sig_reference where sig_id='%d' and ref_id='%d'", sig_sid, ref_id);
546
sqlout = db_query( config->dbtype, sql );
548
if ( sqlout == NULL ) {
549
snprintf(sqltmp, sizeof(sqltmp), "INSERT INTO sig_reference (sig_id, ref_seq, ref_id) VALUES ('%d', '%d', '%d')", sig_sid, seq, ref_id);
551
sqlout = db_query( config->dbtype, sql );
558
/***************************************************************************/
559
/* Snort specific thread code */
560
/***************************************************************************/
562
void sagan_db_thread( SaganEvent *Event ) {
566
char *hex_data = NULL;
567
char message[MAX_SYSLOGMSG];
575
snprintf(message, sizeof(message), "%s", Event->message);
576
snprintf(ip_srctmp, sizeof(ip_srctmp), "%s", Event->ip_src);
577
snprintf(ip_dsttmp, sizeof(ip_dsttmp), "%s", Event->ip_dst);
578
snprintf(time, sizeof(time), "%s", Event->time);
579
snprintf(date, sizeof(date), "%s", Event->date);
581
sig_sid = get_sig_sid(rulestruct[Event->found].s_msg, rulestruct[Event->found].s_rev, rulestruct[Event->found].s_sid, rulestruct[Event->found].s_classtype, rulestruct[Event->found].s_pri , config->dbtype );
583
insert_event( config->sensor_id, Event->cid, sig_sid, config->dbtype, date, time );
584
insert_hdr(config->sensor_id, Event->cid, ip_srctmp, ip_dsttmp, rulestruct[Event->found].ip_proto, Event->endian, config->dbtype, Event->dst_port, Event->src_port );
586
hex_data = fasthex(message, strlen(message));
587
insert_payload ( config->sensor_id, Event->cid, hex_data, config->dbtype ) ;
589
for (i = 0; i < rulestruct[Event->found].ref_count; i++ ) {
590
query_reference( rulestruct[Event->found].s_reference[i], rulestruct[Event->found].s_sid, sig_sid, i );
593
pthread_mutex_lock( &db_mutex );
594
counters->threaddbc--;
595
pthread_mutex_unlock( &db_mutex );