728 lines
27 KiB
C
728 lines
27 KiB
C
/*
|
|
Copyright (C) 2017 Thomas Ries <tries@gmx.net>
|
|
|
|
This file is part of Siproxd.
|
|
|
|
Siproxd is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Siproxd is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warrantry of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Siproxd; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* must be defined before including <plugin.h> */
|
|
#define PLUGIN_NAME plugin_blacklist
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sqlite3.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <osipparser2/osip_parser.h>
|
|
|
|
#include "siproxd.h"
|
|
#include "plugins.h"
|
|
#include "log.h"
|
|
|
|
/*&&&+++ Workaround sqlite3 3.3.6 (header/symbol errors)*/
|
|
#define sqlite3_clear_bindings UNDEFINED_SYMBOL
|
|
#define sqlite3_prepare_v2 UNDEFINED_SYMBOL
|
|
/*&&&---*/
|
|
|
|
/* Plug-in identification */
|
|
static char name[]="plugin_blacklist";
|
|
static char desc[]="Blacklist client IPs / SIP accounts upon auth failures";
|
|
|
|
/* global configuration storage - required for config file location */
|
|
extern struct siproxd_config configuration;
|
|
|
|
/* plugin configuration storage */
|
|
static struct plugin_config {
|
|
char *dbpath; /* path to sqlite DB file (/var/lib/siproxd/bl.db */
|
|
char *db_sync_mode; /* SQListe synchronous mode (FULL(def), NORMAL, OFF) */
|
|
// int block_mode; /* 0: no, 1: IP based, 2: IP & SIP-user */
|
|
int simulate; /* 0: no, 1: don't block, just log */
|
|
int duration; /* in seconds, 0: forever, dont' expire */
|
|
int hitcount; /* required attempts until blocked */
|
|
int register_window;/* time window for REGISTER reesponse to arrive */
|
|
} plugin_cfg;
|
|
|
|
/* Instructions for config parser */
|
|
static cfgopts_t plugin_cfg_opts[] = {
|
|
{ "plugin_blacklist_dbpath", TYP_STRING, &plugin_cfg.dbpath, {0, "/var/lib/siproxd/blacklist.sqlite"} },
|
|
{ "plugin_blacklist_db_sync_mode", TYP_STRING, &plugin_cfg.db_sync_mode, {0, "OFF"} },
|
|
// { "plugin_blacklist_mode", TYP_INT4, &plugin_cfg.block_mode, {2, NULL} },
|
|
{ "plugin_blacklist_simulate", TYP_INT4, &plugin_cfg.simulate, {0, NULL} },
|
|
{ "plugin_blacklist_duration", TYP_INT4, &plugin_cfg.duration, {3600, NULL} },
|
|
{ "plugin_blacklist_hitcount", TYP_INT4, &plugin_cfg.hitcount, {10, NULL} },
|
|
{ "plugin_blacklist_register_window", TYP_INT4, &plugin_cfg.register_window, {30, NULL} },
|
|
{0, 0, 0}
|
|
};
|
|
|
|
/* SQLITE related variables */
|
|
static sqlite3 *db=NULL;
|
|
|
|
/* prepared SQL statements */
|
|
typedef struct {
|
|
int id;
|
|
sqlite3_stmt *stmt;
|
|
char *sql_query;
|
|
} sql_statement_t;
|
|
|
|
static sql_statement_t sql_statement[] = {
|
|
/* blacklist_check() */
|
|
{ 0, NULL, "SELECT count(*) from blacklist WHERE ip=?001 and sipuri=?002 AND (type=1 or failcount>?003);" },
|
|
{ 1, NULL, "UPDATE OR IGNORE blacklist SET lastseen=?003 WHERE ip=?001 and sipuri=?002;" },
|
|
{ 2, NULL, "UPDATE OR IGNORE requests SET timestamp=?001, callid=?004 WHERE ip=?002 AND sipuri=?003;" },
|
|
{ 3, NULL, "INSERT OR IGNORE INTO requests (timestamp, ip, sipuri, callid) VALUES (?001, ?002, ?003, ?004);" },
|
|
/* blacklist_update() */
|
|
{ 4, NULL, "DELETE FROM requests WHERE timestamp<?001;" },
|
|
{ 5, NULL, "SELECT count(*) from requests WHERE ip=?001 and sipuri=?002 AND callid=?003;" },
|
|
{ 6, NULL, "INSERT OR IGNORE INTO blacklist (ip, sipuri) VALUES (?001, ?002);" },
|
|
{ 7, NULL, "UPDATE OR IGNORE blacklist SET failcount=failcount+1, lastseen=?003, lastfail=?003 WHERE type=0 and ip=?001 and sipuri=?002;" },
|
|
{ 8, NULL, "UPDATE OR IGNORE blacklist SET lastseen=?003 WHERE ip=?001 and sipuri=?002;" },
|
|
{ 9, NULL, "UPDATE OR IGNORE blacklist SET failcount=0, lastseen=?003 WHERE type=0 and ip=?001 and sipuri=?002;" },
|
|
/* blacklist_expire() */
|
|
{ 10, NULL, "UPDATE OR IGNORE blacklist SET failcount=0 WHERE type=0 and failcount>?001 and lastseen<?002;" },
|
|
{ 11, NULL, "DELETE FROM blacklist WHERE type=0 AND failcount=0 AND lastseen<?001;" },
|
|
};
|
|
#define SQL_CHECK_1 0
|
|
#define SQL_CHECK_2 1
|
|
#define SQL_CHECK_3 2
|
|
#define SQL_CHECK_4 3
|
|
|
|
#define SQL_UPDATE_1 4 /* expire old request records */
|
|
#define SQL_UPDATE_2 5 /* check if REGISTER response matches a know record */
|
|
#define SQL_UPDATE_3 6 /* insert new blacklist record to DB */
|
|
#define SQL_UPDATE_4 7 /* increment failcount */
|
|
#define SQL_UPDATE_5 8 /* just update lastseen */
|
|
#define SQL_UPDATE_6 9 /* reset failcount upon successful registration */
|
|
|
|
#define SQL_EXPIRE_1 10 /* reset failcount upon duration timeout */
|
|
#define SQL_EXPIRE_2 11 /* cleanup blacklist table */
|
|
|
|
/* string magic in C preprocessor */
|
|
#define xstr(s) str(s)
|
|
#define str(s) #s
|
|
|
|
/* SQL statements */
|
|
#define CALLID_SIZE 256
|
|
#define DB_SQL_CREATE \
|
|
"CREATE TABLE IF NOT EXISTS "\
|
|
"control ( "\
|
|
"action VARCHAR(32) UNIQUE, "\
|
|
"count INTEGER DEFAULT 0, "\
|
|
"time VARCHAR(32) "\
|
|
");" \
|
|
"CREATE TABLE IF NOT EXISTS "\
|
|
"blacklist ( "\
|
|
"type INTEGER DEFAULT 0, "\
|
|
"ip VARCHAR(" xstr(IPSTRING_SIZE) "), "\
|
|
"sipuri VARCHAR(" xstr(USERNAME_SIZE) "), "\
|
|
"failcount INTEGER DEFAULT 0, "\
|
|
"lastfail INTEGER DEFAULT 0, "\
|
|
"lastseen INTEGER DEFAULT 0, "\
|
|
"CONSTRAINT unique_src UNIQUE (ip, sipuri) " \
|
|
");" \
|
|
"CREATE TABLE IF NOT EXISTS "\
|
|
"requests ( "\
|
|
"timestamp INTEGER DEFAULT 0, "\
|
|
"ip VARCHAR(" xstr(IPSTRING_SIZE) "), "\
|
|
"sipuri VARCHAR(" xstr(USERNAME_SIZE) "), "\
|
|
"callid VARCHAR(" xstr(CALLID_SIZE) "), "\
|
|
"CONSTRAINT unique_req UNIQUE (ip, sipuri) " \
|
|
");"
|
|
|
|
/* tables
|
|
control
|
|
blacklist
|
|
- type 0: automatic entry, 1: manual entry (manually added to DB, will not expire)
|
|
- ip IP address of source (xxx.xxx.xxx.xxx)
|
|
- sipuri SIP authentication username
|
|
- failcount count of failed attempts
|
|
- lastfail UNIX timestamp of last failure activity (last failed auth)
|
|
- lastseen UNIX timestamp of last activity
|
|
requests
|
|
- timestamp timestamp of outgoing REGISTER request
|
|
- ip IP address of source (xxx.xxx.xxx.xxx)
|
|
- sipuri SIP authentication username
|
|
- callid SIP CallID of REGISTER request
|
|
*/
|
|
|
|
|
|
/* local prototypes */
|
|
static int blacklist_check(sip_ticket_t *ticket);
|
|
static int blacklist_update(sip_ticket_t *ticket);
|
|
static int blacklist_expire(sip_ticket_t *ticket);
|
|
/* helpers */
|
|
static int sqlite_begin(void);
|
|
static int sqlite_end(void);
|
|
static int sqlite_exec_stmt_none(sql_statement_t *sql_statement);
|
|
static int sqlite_exec_stmt_int(sql_statement_t *sql_statement, int *retval);
|
|
static int sqlite_begin_transaction(void);
|
|
static int sqlite_end_transaction(void);
|
|
|
|
|
|
/*
|
|
* Initialization.
|
|
* Called once suring siproxd startup.
|
|
*/
|
|
int PLUGIN_INIT(plugin_def_t *plugin_def) {
|
|
/* API version number of siproxd that this plugin is built against.
|
|
* This constant will change whenever changes to the API are made
|
|
* that require adaptions in the plugin. */
|
|
plugin_def->api_version=SIPROXD_API_VERSION;
|
|
|
|
/* Name and descriptive text of the plugin */
|
|
plugin_def->name=name;
|
|
plugin_def->desc=desc;
|
|
|
|
/* Execution mask - during what stages of SIP processing shall
|
|
* the plugin be called. */
|
|
plugin_def->exe_mask=PLUGIN_VALIDATE | PLUGIN_POST_PROXY | PLUGIN_TIMER;
|
|
|
|
/* read the config file */
|
|
if (read_config(configuration.configfile,
|
|
configuration.config_search,
|
|
plugin_cfg_opts, name) == STS_FAILURE) {
|
|
ERROR("Plugin '%s': could not load config file", name);
|
|
return STS_FAILURE;
|
|
}
|
|
|
|
if (sqlite_begin() != STS_SUCCESS) {
|
|
return STS_FAILURE;
|
|
}
|
|
|
|
INFO("plugin_blacklist is initialized (sqlite version %s)", sqlite3_libversion());
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Processing.
|
|
*
|
|
*/
|
|
int PLUGIN_PROCESS(int stage, sip_ticket_t *ticket){
|
|
int sts;
|
|
/* stage contains the PLUGIN_* value - the stage of SIP processing. */
|
|
DEBUGC(DBCLASS_BABBLE, "plugin_blacklist: processing - stage %i",stage);
|
|
if (ticket) {
|
|
DEBUGC(DBCLASS_BABBLE, "plugin_blacklist: MSG_IS_REQUEST %i",MSG_IS_REQUEST(ticket->sipmsg));
|
|
DEBUGC(DBCLASS_BABBLE, "plugin_blacklist: MSG_IS_RESPONSE %i",MSG_IS_RESPONSE(ticket->sipmsg));
|
|
DEBUGC(DBCLASS_BABBLE, "plugin_blacklist: MSG_IS_REGISTER %i",MSG_IS_REGISTER(ticket->sipmsg));
|
|
DEBUGC(DBCLASS_BABBLE, "plugin_blacklist: MSG_IS_RESPONSE_FOR(REGISTER) %i",MSG_IS_RESPONSE_FOR(ticket->sipmsg,"REGISTER"));
|
|
DEBUGC(DBCLASS_BABBLE, "plugin_blacklist: MSG_IS_STATUS_4XX %i",MSG_IS_STATUS_4XX(ticket->sipmsg));
|
|
}
|
|
|
|
if ((stage == PLUGIN_VALIDATE)
|
|
&& ticket
|
|
&& MSG_IS_REQUEST(ticket->sipmsg)) {
|
|
sts = blacklist_check(ticket);
|
|
if (sts != STS_SUCCESS) {
|
|
return STS_FAILURE;
|
|
}
|
|
} else if ((stage == PLUGIN_POST_PROXY)
|
|
&& ticket
|
|
&& MSG_IS_RESPONSE(ticket->sipmsg)
|
|
&& MSG_IS_RESPONSE_FOR(ticket->sipmsg, "REGISTER")) {
|
|
sts = blacklist_update(ticket);
|
|
} else if (stage == PLUGIN_TIMER) {
|
|
static int count=0;
|
|
/*&&&TODO: hmmm, still hardcoded... will be executed once per minute */
|
|
if (++count >= 12) {
|
|
count=0;
|
|
sts = blacklist_expire(ticket);
|
|
}
|
|
}
|
|
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* De-Initialization.
|
|
* Called during shutdown of siproxd. Gives the plugin the chance
|
|
* to clean up its mess (e.g. dynamic memory allocation, database
|
|
* connections, whatever the plugin messes around with)
|
|
*/
|
|
int PLUGIN_END(plugin_def_t *plugin_def){
|
|
int sts;
|
|
|
|
sts = sqlite_end();
|
|
|
|
INFO("plugin_blacklist ends here, sts=%i", sts);
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/* private plugin code */
|
|
static int blacklist_check(sip_ticket_t *ticket) {
|
|
int sts;
|
|
int retval=0;
|
|
sql_statement_t *sql_stmt = NULL;
|
|
char *srcip=NULL; /* IP address from UAC issuing the REGSITER */
|
|
osip_uri_t *from_url = NULL;
|
|
char *from=NULL;
|
|
char *call_id=ticket->sipmsg->call_id->number;
|
|
osip_authorization_t *auth=NULL;
|
|
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "entering blacklist_check");
|
|
|
|
/* get source IP address as string */
|
|
srcip=utils_inet_ntoa(ticket->from.sin_addr);
|
|
|
|
/* From: 1st preference is From header, then try contact header */
|
|
if (ticket->sipmsg->from->url) {
|
|
from_url = ticket->sipmsg->from->url;
|
|
} else {
|
|
DEBUGC(DBCLASS_BABBLE,"no from header in packet, skipping BL handling");
|
|
return STS_SUCCESS;
|
|
}
|
|
osip_uri_to_str(from_url, &from);
|
|
|
|
DEBUGC(DBCLASS_BABBLE,"checking user %s from IP %s (Call-Id=[%s])",from, srcip, call_id);
|
|
|
|
sqlite_begin_transaction();
|
|
|
|
/* Query 1: SELECT for blacklisted entries */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_CHECK_1];
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 001, srcip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 003, plugin_cfg.hitcount);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_int(sql_stmt, &retval); /* retval: nunber of records found that */
|
|
/* the blocked query */
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_int failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
|
|
/* Query 2: UPDATE (last seen TS) */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_CHECK_2];
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 001, srcip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 003, ticket->timestamp);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_none failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
|
|
if (MSG_IS_REGISTER(ticket->sipmsg)) {
|
|
/* Disarm initial REGISTER requests that carry no Authentication header data. */
|
|
/* So if no Auth Header is present, then set CALL-Id=<empty> */
|
|
if (osip_message_get_authorization(ticket->sipmsg, 0, &auth) < 0) {
|
|
DEBUGC(DBCLASS_BABBLE, "REGISTER without Auth data");
|
|
call_id="";
|
|
}
|
|
|
|
/* Query 3: UPDATE OR IGNORE REGISTER request into requests DB */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_CHECK_3];
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 001, ticket->timestamp);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, srcip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 003, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 004, call_id,-1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_none failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
|
|
/* Query 4: INSERT OR IGNORE REGISTER request into requests DB */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_CHECK_4];
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 001, ticket->timestamp);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, srcip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 003, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 004, call_id,-1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_none failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
}
|
|
|
|
sqlite_end_transaction();
|
|
|
|
// not present in sqlite 3.3.6 sts = sqlite3_clear_bindings(stmt1);
|
|
|
|
if ((retval > 0) && (plugin_cfg.simulate==0)) {
|
|
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_check, UAC is blocked");
|
|
INFO ("UAC with IP %s [%s] is blocked", srcip, from);
|
|
osip_free(from);
|
|
return STS_FAILURE;
|
|
} else if (retval > 0) {
|
|
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_check, UAC is blocked");
|
|
INFO ("UAC with IP %s [%s] would be blocked (simulate=1)", srcip, from);
|
|
}
|
|
|
|
/* free resources */
|
|
osip_free(from);
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_check, UAC is permitted");
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int blacklist_update(sip_ticket_t *ticket) {
|
|
int sts;
|
|
int retval=0;
|
|
sql_statement_t *sql_stmt = NULL;
|
|
char *dstip=NULL; /* IP address from UAC issuing the REGSITER */
|
|
osip_uri_t *from_url = NULL;
|
|
char *from=NULL;
|
|
char *call_id=ticket->sipmsg->call_id->number;
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "entering blacklist_update");
|
|
|
|
/* get target IP address as string */
|
|
dstip=utils_inet_ntoa(ticket->next_hop.sin_addr);
|
|
|
|
/* From: 1st preference is From header, then try contact header */
|
|
if (ticket->sipmsg->from->url) {
|
|
from_url = ticket->sipmsg->from->url;
|
|
} else {
|
|
DEBUGC(DBCLASS_BABBLE,"no from header in packet, skipping BL handling");
|
|
return STS_SUCCESS;
|
|
}
|
|
osip_uri_to_str(from_url, &from);
|
|
|
|
|
|
DEBUGC(DBCLASS_BABBLE,"checking user %s at IP %s (Call-Id=[%s])",from, dstip, call_id);
|
|
|
|
sqlite_begin_transaction();
|
|
|
|
/* Query 1: remove old records (> register_window seconds) */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_UPDATE_1];
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 001, ticket->timestamp - plugin_cfg.register_window);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_none failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
|
|
/* Query 2: check if this REGISTER response has a known record in the requests table */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_UPDATE_2];
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 001, dstip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 003, call_id, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_int(sql_stmt, &retval);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_int failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
|
|
if (retval > 0) {
|
|
DEBUGC(DBCLASS_BABBLE, "response to existing query, continue processing");
|
|
|
|
/* a failed request? then instert resp. update DB blacklist record */
|
|
if (MSG_IS_STATUS_4XX(ticket->sipmsg)) {
|
|
DEBUGC(DBCLASS_BABBLE, "inserting blacklist record for user %s at IP %s ", from, dstip);
|
|
/* Query 3: failed REGISTER, add new record in blacklist in not yet existing */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_UPDATE_3];
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 001, dstip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
/* execute & eval result */
|
|
sts = sqlite_exec_stmt_int(sql_stmt, &retval);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_int failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
}
|
|
|
|
|
|
if (MSG_IS_STATUS_4XX(ticket->sipmsg)) {
|
|
/* REGISTER 4xx failure: increment error counter */
|
|
DEBUGC(DBCLASS_BABBLE, "4XX: incrementing error counter for user %s at IP %s ", from, dstip);
|
|
sql_stmt = &sql_statement[SQL_UPDATE_4];
|
|
} else if (MSG_IS_STATUS_2XX(ticket->sipmsg)) {
|
|
/* REGISTER 2xx success: set error counter to 0 */
|
|
DEBUGC(DBCLASS_BABBLE, "2XX: setting error counter=0 for user %s at IP %s ", from, dstip);
|
|
sql_stmt = &sql_statement[SQL_UPDATE_6];
|
|
} else {
|
|
/* update last-seen */
|
|
DEBUGC(DBCLASS_BABBLE, "update last seen for user %s at IP %s ", from, dstip);
|
|
sql_stmt = &sql_statement[SQL_UPDATE_5];
|
|
}
|
|
if (sql_stmt) {
|
|
/* Query 4/5/6 */
|
|
/* bind */
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 001, dstip, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_text(sql_stmt->stmt, 002, from, -1, SQLITE_TRANSIENT);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_text failed with %i", sts); }
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 003, ticket->timestamp);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
/* execute query */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_int failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
}
|
|
|
|
} /* if Q2 true */
|
|
|
|
sqlite_end_transaction();
|
|
|
|
/* free resources */
|
|
osip_free(from);
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_update");
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
|
|
static int blacklist_expire(sip_ticket_t *ticket) {
|
|
int sts;
|
|
sql_statement_t *sql_stmt = NULL;
|
|
time_t now;
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "entering blacklist_expire");
|
|
|
|
time(&now);
|
|
|
|
/* set failcount=0 for all records where last_seen is older than block_period */
|
|
/* or remove records */
|
|
|
|
sqlite_begin_transaction();
|
|
|
|
/* expire old blacklist records (if config.duration > 0)*/
|
|
if (plugin_cfg.duration > 0) {
|
|
/* Query7 */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_EXPIRE_1];
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 001, plugin_cfg.hitcount);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 002, now-plugin_cfg.duration);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
/* execute query */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_int failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
}
|
|
|
|
/* delete old records (failcount=0, lastseen older than one day) */
|
|
/*&&&TODO: I should do this once a day/hour/whatever */
|
|
/* Query8 */
|
|
/* bind */
|
|
sql_stmt = &sql_statement[SQL_EXPIRE_2];
|
|
sts = sqlite3_bind_int(sql_stmt->stmt, 001, now-86400);
|
|
if( sts != SQLITE_OK ){ WARN("sqlite3_bind_int failed with %i", sts); }
|
|
/* execute query */
|
|
sts = sqlite_exec_stmt_none(sql_stmt);
|
|
if( sts != STS_SUCCESS ){ WARN("sqlite_exec_stmt_int failed with %i", sts); }
|
|
sql_stmt = NULL;
|
|
|
|
sqlite_end_transaction();
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_expire");
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/* helper functions */
|
|
static int sqlite_begin(void){
|
|
int sts;
|
|
int i;
|
|
char *zErrMsg = NULL;
|
|
char sql[64];
|
|
|
|
|
|
/* open the database */
|
|
sts = sqlite3_open(plugin_cfg.dbpath, &db);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR("Can't open database: %s\n", sqlite3_errmsg(db));
|
|
sqlite3_close(db);
|
|
return STS_FAILURE;
|
|
}
|
|
|
|
/* create table structure if not existing */
|
|
sts = sqlite3_exec(db, DB_SQL_CREATE, NULL, 0, &zErrMsg);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR( "SQL exec error: %s\n", zErrMsg);
|
|
sqlite3_free(zErrMsg);
|
|
sqlite3_close(db);
|
|
return STS_FAILURE;
|
|
}
|
|
|
|
/* switch to nosync mode (async r/w) */
|
|
strcpy(sql, "PRAGMA synchronous = ");
|
|
strcat(sql, plugin_cfg.db_sync_mode);
|
|
sts = sqlite3_exec(db, sql, NULL, 0, &zErrMsg);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR( "SQL exec error: %s\n", zErrMsg);
|
|
sqlite3_free(zErrMsg);
|
|
sqlite3_close(db);
|
|
return STS_FAILURE;
|
|
}
|
|
|
|
/* perform write check (DB update) */
|
|
#define DB_SQL_STARTUP \
|
|
"INSERT OR IGNORE INTO control (action, count) VALUES ('bl_started', 0); "\
|
|
"UPDATE control set count = count + 1, time = datetime('now') where action ='bl_started';"
|
|
sts = sqlite3_exec(db, DB_SQL_STARTUP, NULL, 0, &zErrMsg);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR( "SQL exec error: %s\n", zErrMsg);
|
|
sqlite3_free(zErrMsg);
|
|
sqlite3_close(db);
|
|
return STS_FAILURE;
|
|
}
|
|
|
|
/* create prepared statements */
|
|
DEBUGC(DBCLASS_BABBLE, "PLUGIN_INIT: preparing %li statements",
|
|
(long int)(sizeof(sql_statement) / sizeof(sql_statement[0])));
|
|
for (i=0; i < sizeof(sql_statement) / sizeof(sql_statement[0]); i++) {
|
|
if (sql_statement[i].sql_query == NULL) {
|
|
DEBUGC(DBCLASS_BABBLE, "PLUGIN_INIT: skiping empty SQL statement");
|
|
continue;
|
|
}
|
|
if (sql_statement[i].stmt == NULL) {
|
|
DEBUGC(DBCLASS_BABBLE, "PLUGIN_INIT: preparing stmt %i [%s]",
|
|
i, sql_statement[i].sql_query);
|
|
sts = sqlite3_prepare(db, sql_statement[i].sql_query, -1,
|
|
&sql_statement[i].stmt, NULL );
|
|
if( sts != SQLITE_OK ){
|
|
ERROR("SQL prepare error [query=%i]: %s\n", i, sqlite3_errmsg(db));
|
|
sqlite3_close(db);
|
|
return STS_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
static int sqlite_end(void){
|
|
int sts;
|
|
int i;
|
|
char *zErrMsg = NULL;
|
|
|
|
/* Mark shutdown in DB (pure informational reasons) */
|
|
#define DB_SQL_SHUTDOWN \
|
|
"INSERT OR IGNORE INTO control (action, count) VALUES ('bl_stopped', 0); "\
|
|
"UPDATE control set count = count + 1, time = datetime('now') where action ='bl_stopped';"
|
|
sts = sqlite3_exec(db, DB_SQL_SHUTDOWN, NULL, 0, &zErrMsg);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR( "SQL exec error: %s\n", zErrMsg);
|
|
sqlite3_free(zErrMsg);
|
|
}
|
|
|
|
/* free ressources of prepared queries */
|
|
for (i=0; i < sizeof(sql_statement) / sizeof(sql_statement[0]); i++) {
|
|
if (sql_statement[i].stmt != NULL) {
|
|
sts = sqlite3_finalize(sql_statement[i].stmt);
|
|
}
|
|
}
|
|
|
|
sqlite3_close(db);
|
|
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
static int sqlite_begin_transaction(void){
|
|
int sts;
|
|
char *zErrMsg = NULL;
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "SQLite: begin transaction");
|
|
sts = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, 0, &zErrMsg);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR( "SQL exec error: %s\n", zErrMsg);
|
|
sqlite3_free(zErrMsg);
|
|
}
|
|
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
static int sqlite_end_transaction(void){
|
|
int sts;
|
|
char *zErrMsg = NULL;
|
|
|
|
DEBUGC(DBCLASS_BABBLE, "SQLite: end transaction - begin");
|
|
sts = sqlite3_exec(db, "END TRANSACTION", NULL, 0, &zErrMsg);
|
|
if( sts != SQLITE_OK ){
|
|
ERROR( "SQL exec error: %s\n", zErrMsg);
|
|
sqlite3_free(zErrMsg);
|
|
}
|
|
DEBUGC(DBCLASS_BABBLE, "SQLite: end transaction - done");
|
|
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
static int sqlite_exec_stmt_none(sql_statement_t *sql_statement){
|
|
int sts;
|
|
|
|
/* execute & eval result */
|
|
DEBUGC(DBCLASS_BABBLE, "executing query [%s]", sql_statement->sql_query);
|
|
do {
|
|
sts = sqlite3_step(sql_statement->stmt);
|
|
} while (sts == SQLITE_ROW);
|
|
if ( sts == SQLITE_ERROR) {
|
|
sts = sqlite3_reset(sql_statement->stmt);
|
|
ERROR("SQL step error [%i]: %s\n", sts, sqlite3_errmsg(db));
|
|
} else if ( sts != SQLITE_DONE ) {
|
|
ERROR("SQL step error [%i]: %s\n", sts, sqlite3_errmsg(db));
|
|
}
|
|
/* cleanup */
|
|
sts = sqlite3_reset(sql_statement->stmt);
|
|
|
|
return STS_SUCCESS;
|
|
}
|
|
|
|
static int sqlite_exec_stmt_int(sql_statement_t *sql_statement, int *retval){
|
|
int sts;
|
|
|
|
/* execute & eval result */
|
|
DEBUGC(DBCLASS_BABBLE, "executing query [%s]", sql_statement->sql_query);
|
|
do {
|
|
sts = sqlite3_step(sql_statement->stmt);
|
|
if (sts == SQLITE_ROW) {
|
|
if (retval != NULL) {
|
|
*retval = sqlite3_column_int(sql_statement->stmt, 0);
|
|
DEBUGC(DBCLASS_BABBLE, "sqlite_exec_stmt_int: query returned INT %i", *retval);
|
|
}
|
|
}
|
|
} while (sts == SQLITE_ROW);
|
|
if ( sts == SQLITE_ERROR) {
|
|
sts = sqlite3_reset(sql_statement->stmt);
|
|
ERROR("SQL step error [%i]: %s\n", sts, sqlite3_errmsg(db));
|
|
} else if ( sts != SQLITE_DONE ) {
|
|
ERROR("SQL step error [%i]: %s\n", sts, sqlite3_errmsg(db));
|
|
}
|
|
/* cleanup */
|
|
sts = sqlite3_reset(sql_statement->stmt);
|
|
|
|
return STS_SUCCESS;
|
|
}
|