added plugin_blacklist (work in progress)

This commit is contained in:
Thomas Ries
2017-02-26 13:41:40 +00:00
parent d8086db776
commit 962f5382c8
2 changed files with 452 additions and 3 deletions
+9 -3
View File
@@ -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
#
+443
View File
@@ -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.