added plugin_blacklist (work in progress)
This commit is contained in:
+9
-3
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (C) 2002-2009 Thomas Ries <tries@gmx.net>
|
||||
# Copyright (C) 2002-2017 Thomas Ries <tries@gmx.net>
|
||||
#
|
||||
# This file is part of Siproxd.
|
||||
#
|
||||
@@ -24,6 +24,7 @@ if use_convenience_ltdl
|
||||
endif
|
||||
|
||||
AM_CFLAGS = -D_GNU_SOURCE $(LTDLDEF) \
|
||||
-Werror-implicit-function-declaration \
|
||||
-DBUILDSTR="\"`cat .buildno`\"" \
|
||||
-DBUILDDATE="\"`date -u '+%Y-%m-%dT%H:%M:%S'`\""
|
||||
|
||||
@@ -43,7 +44,8 @@ pkglib_LTLIBRARIES = plugin_demo.la \
|
||||
plugin_stripheader.la \
|
||||
plugin_siptrunk.la \
|
||||
plugin_fix_fbox_anoncall.la \
|
||||
plugin_stats.la
|
||||
plugin_stats.la \
|
||||
plugin_blacklist.la
|
||||
|
||||
DLOPENPLUGINS = -dlopen plugin_demo.la \
|
||||
-dlopen plugin_shortdial.la \
|
||||
@@ -58,7 +60,8 @@ DLOPENPLUGINS = -dlopen plugin_demo.la \
|
||||
-dlopen plugin_stripheader.la \
|
||||
-dlopen plugin_siptrunk.la \
|
||||
-dlopen plugin_fix_fbox_anoncall.la \
|
||||
-dlopen plugin_stats.la
|
||||
-dlopen plugin_stats.la \
|
||||
-dlopen plugin_blacklist.la
|
||||
#
|
||||
plugin_demo_la_SOURCES = plugin_demo.c
|
||||
plugin_demo_la_LDFLAGS = -module -avoid-version -shrext '.so'
|
||||
@@ -101,6 +104,9 @@ plugin_fix_fbox_anoncall_la_LDFLAGS = -module -avoid-version -shrext '.so'
|
||||
#
|
||||
plugin_stats_la_SOURCES = plugin_stats.c
|
||||
plugin_stats_la_LDFLAGS = -module -avoid-version -shrext '.so'
|
||||
#
|
||||
plugin_blacklist_la_SOURCES = plugin_blacklist.c
|
||||
plugin_blacklist_la_LDFLAGS = -module -avoid-version -shrext '.so' -lsqlite3
|
||||
|
||||
|
||||
#
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
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 <time.h>
|
||||
#include <string.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osipparser2/osip_parser.h>
|
||||
|
||||
#include "siproxd.h"
|
||||
#include "plugins.h"
|
||||
#include "log.h"
|
||||
|
||||
static char const ident[]="$Id$";
|
||||
|
||||
/*&&&+++ 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[]="Blacklists 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 */
|
||||
int block_mode; /* 0: no, 1: IP based, 2: IP & SIP-user */
|
||||
int block_duration; /* in seconds, 0: forever, dont' expire */
|
||||
int block_hitcount; /* required attempts until blocked */
|
||||
} 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_mode", TYP_INT4, &plugin_cfg.block_mode, {0, NULL} },
|
||||
{ "plugin_blacklist_duration", TYP_INT4, &plugin_cfg.block_duration, {3600, NULL} },
|
||||
{ "plugin_blacklist_hitcount", TYP_INT4, &plugin_cfg.block_hitcount, {10, 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(id) from blacklist WHERE ip=?001 and sipuri=?002 AND failcount>?003;" },
|
||||
{ 1, NULL, "UPDATE OR IGNORE blacklist SET lastseen=?003 WHERE ip=?001 and sipuri=?002;" },
|
||||
/* blacklist_update_fail() */
|
||||
{ 2, NULL, "INSERT OR IGNORE INTO blacklist (ip, sipuri, failcount, lastseen, lastfail) VALUES (?001, ?002, 0, ?003, ?003);" },
|
||||
{ 3, NULL, "UPDATE OR IGNORE blacklist SET failcount=failcount+1, lastseen=?003, lastfail=?003 WHERE ip=?001 and sipuri=?002;" },
|
||||
};
|
||||
|
||||
/* string magic in C preprocessor */
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
|
||||
/* SQL statements */
|
||||
#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 ( "\
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "\
|
||||
"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) " \
|
||||
");"
|
||||
|
||||
/* tables
|
||||
control
|
||||
blacklist
|
||||
- id
|
||||
- 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
|
||||
*/
|
||||
|
||||
|
||||
/* local prototypes */
|
||||
static int blacklist_check(sip_ticket_t *ticket);
|
||||
static int blacklist_update_fail(sip_ticket_t *ticket);
|
||||
static int blacklist_expire(sip_ticket_t *ticket);
|
||||
|
||||
static int blacklist_sqlite_begin(void);
|
||||
static int blacklist_sqlite_end(void);
|
||||
static int blacklict_exec_prep_query_int(sqlite3_stmt *stmt1, int *retval);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/* 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 (blacklist_sqlite_begin() != STS_SUCCESS) {
|
||||
return STS_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
blacklist_check(NULL);
|
||||
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 ((stage == PLUGIN_VALIDATE)
|
||||
&& MSG_IS_REQUEST(ticket->sipmsg)) {
|
||||
sts = blacklist_check(ticket);
|
||||
if (sts != STS_SUCCESS) {
|
||||
return STS_FAILURE;
|
||||
}
|
||||
} else if ((stage == PLUGIN_POST_PROXY)
|
||||
&& MSG_IS_RESPONSE(ticket->sipmsg)
|
||||
&& MSG_IS_REGISTER(ticket->sipmsg)) {
|
||||
sts = blacklist_update_fail(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 = blacklist_sqlite_end();
|
||||
|
||||
INFO("plugin_blacklist ends here");
|
||||
return STS_SUCCESS;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------*/
|
||||
/* private plugin code */
|
||||
static int blacklist_check(sip_ticket_t *ticket) {
|
||||
int sts;
|
||||
sqlite3_stmt *stmt1 = sql_statement[0].stmt;
|
||||
sqlite3_stmt *stmt2 = sql_statement[1].stmt;
|
||||
|
||||
DEBUGC(DBCLASS_BABBLE, "entering blacklist_check");
|
||||
|
||||
/* bind */
|
||||
sts = sqlite3_bind_text(stmt1, 001, "1.2.3.4", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_text(stmt1, 002, "foo@bar.org", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_int(stmt1, 003, plugin_cfg.block_hitcount);
|
||||
/* execute & eval result */
|
||||
do {
|
||||
sts = sqlite3_step(stmt1);
|
||||
if (sts == SQLITE_ROW) {
|
||||
INFO("Blacklist Lookup returned %i matches", sqlite3_column_int(stmt1, 0));
|
||||
}
|
||||
} while (sts == SQLITE_ROW);
|
||||
if ( sts == SQLITE_ERROR) {
|
||||
sts = sqlite3_reset(stmt1);
|
||||
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(stmt1);
|
||||
|
||||
|
||||
/* bind */
|
||||
sts = sqlite3_bind_text(stmt2, 001, "1.2.3.4", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_text(stmt2, 002, "foo@bar.org", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_int(stmt2, 003, time(NULL));
|
||||
/* execute & eval result */
|
||||
do {
|
||||
sts = sqlite3_step(stmt2);
|
||||
} while (sts == SQLITE_ROW);
|
||||
if ( sts == SQLITE_ERROR) {
|
||||
sts = sqlite3_reset(stmt2);
|
||||
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(stmt2);
|
||||
|
||||
// not present in 3.3.6 sts = sqlite3_clear_bindings(stmt1);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* SELECT and find if a record exists
|
||||
last_seen no older than block_duration
|
||||
failcount >0
|
||||
return failcount */
|
||||
|
||||
/* update last_seen */
|
||||
|
||||
/* if failcount > maxfail, block */
|
||||
|
||||
|
||||
|
||||
#define DB_SQL_BL_CHECK \
|
||||
"SELECT * from blacklist where "
|
||||
|
||||
|
||||
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_check");
|
||||
return STS_SUCCESS;
|
||||
}
|
||||
|
||||
static int blacklist_update_fail(sip_ticket_t *ticket) {
|
||||
int sts;
|
||||
sqlite3_stmt *stmt11 = sql_statement[2].stmt;
|
||||
sqlite3_stmt *stmt12 = sql_statement[3].stmt;
|
||||
|
||||
DEBUGC(DBCLASS_BABBLE, "entering blacklist_update_fail");
|
||||
|
||||
|
||||
/* bind */
|
||||
//&&& fetch UAC IP from telegram
|
||||
//&&& fetch SIP URI (contact?) from telegram
|
||||
sts = sqlite3_bind_text(stmt11, 001, "1.2.3.4", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_text(stmt11, 002, "foo@bar.org", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_int(stmt11, 003, time(NULL));
|
||||
/* execute & eval result */
|
||||
do {
|
||||
sts = sqlite3_step(stmt11);
|
||||
} while (sts == SQLITE_ROW);
|
||||
if ( sts == SQLITE_ERROR) {
|
||||
sts = sqlite3_reset(stmt11);
|
||||
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(stmt11);
|
||||
|
||||
|
||||
/* bind */
|
||||
//&&& fetch UAC IP from telegram
|
||||
//&&& fetch SIP URI (contact?) from telegram
|
||||
sts = sqlite3_bind_text(stmt12, 001, "1.2.3.4", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_text(stmt12, 002, "foo@bar.org", -1, SQLITE_STATIC);
|
||||
sts = sqlite3_bind_int(stmt12, 003, time(NULL));
|
||||
/* execute & eval result */
|
||||
do {
|
||||
sts = sqlite3_step(stmt12);
|
||||
} while (sts == SQLITE_ROW);
|
||||
if ( sts == SQLITE_ERROR) {
|
||||
sts = sqlite3_reset(stmt12);
|
||||
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(stmt12);
|
||||
|
||||
|
||||
/* INSERT OR IGNORE records to DB: IP, sipuri */
|
||||
/* UPDATE records failcount=failcount+1, lastseen=now, lastfail=now */
|
||||
|
||||
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_update_fail");
|
||||
return STS_SUCCESS;
|
||||
}
|
||||
|
||||
static int blacklist_expire(sip_ticket_t *ticket) {
|
||||
int sts;
|
||||
char *zErrMsg = NULL;
|
||||
|
||||
DEBUGC(DBCLASS_BABBLE, "entering blacklist_expire");
|
||||
/* set failcount=0 for all records where last_seen is older than block_period */
|
||||
/* or remove records */
|
||||
|
||||
DEBUGC(DBCLASS_BABBLE, "leaving blacklist_expire");
|
||||
return STS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------*/
|
||||
/* helper functions */
|
||||
static int blacklist_sqlite_begin(void){
|
||||
int sts;
|
||||
int i;
|
||||
char *zErrMsg = NULL;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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 %i statements",
|
||||
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 blacklist_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 blacklict_exec_prep_query_int(sqlite3_stmt *stmt1, int *retval){
|
||||
}
|
||||
|
||||
//&&& implement cache of open REGISTER requests, only honor failed REGISTERS responses where a
|
||||
//&&& REQUEST has been sent before from one of our clients.
|
||||
Reference in New Issue
Block a user