Browse Source

Traffic monitor fixes and block exports

* *NEW* dnsblacklist to make "hosts" style block lists
 * *NEW* domblacklist to make dnsmasq resolve entries to block entire
   domains regardless of host or subdomain.
 * Several fixes
 * Refactor CLI apps for more code re-use.
 * More helpful error messages.
 * Add whole (aka wildcard) domain block handling.
master
Jon Foster 2 years ago
parent
commit
d12a3e9f4d
8 changed files with 476 additions and 114 deletions
  1. +113
    -0
      trafficmon/appbase.cpp
  2. +85
    -0
      trafficmon/appbase.h
  3. +12
    -53
      trafficmon/badtrafficrpt.cpp
  4. +1
    -0
      trafficmon/default
  5. +139
    -0
      trafficmon/dnsblacklist.cpp
  6. +61
    -0
      trafficmon/domblacklist.cpp
  7. +18
    -11
      trafficmon/init
  8. +47
    -50
      trafficmon/trafficmon.cpp

+ 113
- 0
trafficmon/appbase.cpp View File

@@ -0,0 +1,113 @@
//////////////////////////////////////////////////////////////////////
// Base CLI app classes for TrafficMon tools
// Written by Jonathan A. Foster <jon@jfpossibilities.com>
// Started December 29th, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//////////////////////////////////////////////////////////////////////
#include <stdexcept>
#include <iostream>
#include <libgen.h>
#include "appbase.h"



//////////////////////////////////////////////////////////////////////
// TrafficMonBaseApp
//////////////////////////////////////////////////////////////////////

cBaseApp &TrafficMonBaseApp::init(int argc, char **argv) {
if(!config) config = new MonitorBaseConf;
return cBaseApp::init(argc, argv);
}



unsigned TrafficMonBaseApp::do_switch(const char *arg) {
if(!arg[1] && *arg=='c') return 1;
return cBaseApp::do_switch(arg);
}



void TrafficMonBaseApp::do_switch_arg(const char *sw, const std::string &val) {
if(!sw[1] && *sw=='c') config->load(val);
}



int TrafficMonBaseApp::main() {
int x;
try {
if(x=cBaseApp::main()) return x; // Parse CLI args
if(!config->traffic_mon.vals.size()) throw CLIerror(
"You need to load a config file with a [Traffic Mon] section"
);
} catch(const CLIerror &e) {
std::cerr << e.what() << "\n\n";
return help();
}
db.open("mysql:user="+qesc(config->traffic_mon.get("db user"))+
";password="+qesc(config->traffic_mon.get("db password"))+
";host="+qesc(config->traffic_mon.get("db host"))+
";database="+qesc(config->traffic_mon.get("db name"))+
";@opt_reconnect=1");
return 0;
}



TrafficMonBaseApp::~TrafficMonBaseApp() {
if(config) delete(config);
}



//////////////////////////////////////////////////////////////////////
// BlackListBaseApp
//////////////////////////////////////////////////////////////////////

int BlackListBaseApp::help() {
std::cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} [-4 {address}] [-6 {address}]\n"
<< '\n'
<< "The config file must have a [Traffic Mon] section with the database\n"
<< "credentials in it. -4 & -6 set the addresses to pin blocked names to.\n"
<< "They default to the 'localhost' address in the respective family. Set\n"
<< "to '' to turn off output of that family." << std::endl;
return ExitCode = 1;
}



unsigned BlackListBaseApp::do_switch(const char *arg) {
if(!arg[1] && (*arg=='4' || *arg=='6')) return 1;
return TrafficMonBaseApp::do_switch(arg);
}



void BlackListBaseApp::do_switch_arg(const char *sw, const std::string &val) {
if(!sw[1]) switch(*sw) {
case '4': ipv4 = val; return;
case '6': ipv6 = val; return;
}
TrafficMonBaseApp::do_switch_arg(sw, val);
}



void BlackListBaseApp::do_arg(const char *arg) {
throw CLIerror("Invalid arguments");
}



int BlackListBaseApp::main() {
int x;

if(x=TrafficMonBaseApp::main()) return x; // Parse CLI args, open conf & db
if(ipv4=="" && ipv6=="") {
std::cerr << "All address families turned off. Nothing to do." << std::endl;
return 1;
}
return 0;
}

+ 85
- 0
trafficmon/appbase.h View File

@@ -0,0 +1,85 @@
//////////////////////////////////////////////////////////////////////
// Base CLI app classes for TrafficMon tools
// Written by Jonathan A. Foster <jon@jfpossibilities.com>
// Started December 29th, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//
//
//////////////////////////////////////////////////////////////////////
// NOTE: since GNU doesn't discard unused classes these two classes should
// probably get put in separate sets of files. :-/
#ifndef __IDS_MONITOR_BASE_APP_H__
#define __IDS_MONITOR_BASE_APP_H__
#include <cppdb/frontend.h>
#include "../cli.h"
#include "../miniini.h"



//////////////////////////////////////////////////////////////////////
// The core configuration file
//
// This is designed so that all parts can use the same config. Tools
// ignore the parts they aren't interested in.
//////////////////////////////////////////////////////////////////////

struct MonitorBaseConf: public MiniINI {
MiniINIvars traffic_mon; // This app's config variables

MonitorBaseConf() { groups["Traffic Mon"] = &traffic_mon; }
};



//////////////////////////////////////////////////////////////////////
// The base CLI application class used by the tools in this directory.
//
// Essentially this is a CLI app with a DB connection and a place
// holder for a config file.
//////////////////////////////////////////////////////////////////////

struct TrafficMonBaseApp: public cBaseApp {
cppdb::session db;
MonitorBaseConf *config;

// this init() will create a MonitorBaseConf if a config hasn't been assigned.
virtual cBaseApp &init(int argc, char **argv);

// process config file switch and load the file
virtual unsigned do_switch(const char *arg);
virtual void do_switch_arg(const char *sw, const std::string &val);

// process CLI args, test for [traffic mon] and connect to DB.
virtual int main();

// close out and free config object.
virtual ~TrafficMonBaseApp();
};



//////////////////////////////////////////////////////////////////////
// Blacklist report base class
//
// This provides generic switch handling
//////////////////////////////////////////////////////////////////////

struct BlackListBaseApp: public TrafficMonBaseApp {
std::string ipv4, ipv6;

BlackListBaseApp():
ipv4("127.0.0.1"),
ipv6("::1")
{}

// Display generic CLI help text
virtual int help();

// process -4 & -6 switches.
virtual unsigned do_switch(const char *sw);
virtual void do_switch_arg(const char *sw, const std::string &val);
virtual void do_arg(const char *arg);
virtual int main();
};

#endif

+ 12
- 53
trafficmon/badtrafficrpt.cpp View File

@@ -20,11 +20,9 @@
#include <iostream>
#include <stdio.h>
#include <libgen.h>
#include <cppdb/frontend.h>

#include "../strutil.h"
#include "../cli.h"
#include "../miniini.h"
#include "appbase.h"
using namespace std;


@@ -132,36 +130,19 @@ namespace cppdb {
}


//////////////////////////////////////////////////////////////////////
// Our config file.
//
// This is designed so that all parts can use the same config. Tools
// ignore the parts they aren't interested in.
//////////////////////////////////////////////////////////////////////

/* TODO: refactor this as a base class... */
struct ReportConf: public MiniINI {
MiniINIvars traffic_mon; // This app's config variables

ReportConf() { groups["Traffic Mon"] = &traffic_mon; }
};



//////////////////////////////////////////////////////////////////////
// Connection Report Generator Application Class
//////////////////////////////////////////////////////////////////////

struct appConnectionReport: cBaseApp {
ReportConf config;
ReportData rpt;
cppdb::session db;
string start_stamp;
string end_stamp;
int cli_mode; // which non-switch are we processing
struct appConnectionReport: TrafficMonBaseApp {
ReportData rpt;
string start_stamp;
string end_stamp;
int cli_mode; // which non-switch are we processing


appConnectionReport(): cli_mode(0) {}
appConnectionReport(): cli_mode(0) { }



@@ -171,21 +152,7 @@ struct appConnectionReport: cBaseApp {
<< "The config file must have a [Traffic Mon] section with the database\n"
<< "credentials in it. 'start' and 'stop' are the SQL timestamps for\n"
<< "report time span." << endl;
}
virtual unsigned do_switch(const char *arg) {
if(*arg=='c' && !arg[1]) return 1;
throw CLIerror("Invalid switch "+string(arg));
}



virtual void do_switch_arg(const char *sw, const std::string &val) {
// The only way we get here is with -c
config.load(val);
// TODO: config validity checks
return 1;
}


@@ -197,30 +164,22 @@ struct appConnectionReport: cBaseApp {
default: throw CLIerror("Invalid arguments");
}
}

int main() {
cppdb::result qry;
int x;

/// SETUP & VALIDATE CLI ///

try {
cBaseApp::main(); // Parse CLI args
if(!config.traffic_mon.vals.size()) throw CLIerror(
"You need to load a config file with a [Traffic Mon] section"
);
if(x = TrafficMonBaseApp::main()) return x; // Parse CLI args
if(cli_mode!=2) throw CLIerror("Invlaid arguments");
} catch(const CLIerror &e) {
cerr << e.what() << "\n\n";
return help();
}
db.open("mysql:user="+qesc(config.traffic_mon.get("db user"))+
";password="+qesc(config.traffic_mon.get("db password"))+
";host="+qesc(config.traffic_mon.get("db host"))+
";database="+qesc(config.traffic_mon.get("db name"))+
";@opt_reconnect=1");

/// Query & load data ///

qry = db <<


+ 1
- 0
trafficmon/default View File

@@ -2,6 +2,7 @@
# defaults.

# Configuration file for the TrafficMon server
# NOTE: service won't start until this is set
#CONF=/etc/poorman-ids/sample.conf
# Where "run" files are placed. This is the Debian+ default:
#RUN=/run


+ 139
- 0
trafficmon/dnsblacklist.cpp View File

@@ -0,0 +1,139 @@
//////////////////////////////////////////////////////////////////////
// Dump Black Listed DNS entries
// Written by Jonathan A. Foster <jon@jfpossibilities.com>
// Started Ocotber 27th, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//
// Read the "dns" table and dump all black listed host names as
// entries for a "hosts" file. This could also be easily done with a
// script but I want to be able to use the same config file as every-
// thing else and parsing in SH is clumsy at best.
//////////////////////////////////////////////////////////////////////
#include <string>
#include <map>
#include <iostream>
#include <stdio.h>
#include <libgen.h>

#include "../strutil.h"
#include "appbase.h"
using namespace std;



//////////////////////////////////////////////////////////////////////
// Class to manage and test hoset names agains bad domains
//////////////////////////////////////////////////////////////////////

struct DomainList: public StringList {

bool operator==(const std::string host) {
DomainList::const_iterator i;
int dl, hl = host.size();

for(i=begin(); i!=end(); i++) {
if(*i==host) return true;
dl = i->size()+1;
if(hl>dl && host.substr(hl-dl)=="."+*i) return true;
}
return false;
}

inline bool operator!=(const std::string host) { return !(*this==host); }
};



namespace cppdb {
session &operator>>(cppdb::session &db, DomainList &doms) {
cppdb::result qry;
std::string s;

doms.clear();
qry = db << "SELECT name FROM dns_wild WHERE status=2";
while(qry.next()) {
qry >> s;
doms.push_back(s);
}
return db;
}
} // cppdb



//////////////////////////////////////////////////////////////////////
// Connection Report Generator Application Class
//////////////////////////////////////////////////////////////////////

struct DNSblackList: BlackListBaseApp {
bool all;



unsigned do_switch(const char *arg) {
if(*arg=='a' && !arg[1]) { all=1; return 0; }
return BlackListBaseApp::do_switch(arg);
}



int help() {
std::cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} [-a] [-4 {address}] [-6 {address}]\n"
<< '\n'
<< "The config file must have a [Traffic Mon] section with the database\n"
<< "credentials in it. -4 & -6 set the addresses to pin blocked names to.\n"
<< "They default to the 'localhost' address in the respective family. Set\n"
<< "to '' to turn off output of that family. -a dumps all blocked host\n"
<< "names otherwise host names that are covered by a domain block will\n"
<< "not be shown." << std::endl;
return ExitCode = 1;
}



int main() {
DomainList baddoms;
cppdb::result qry;
string s;
int x;

/// SETUP & VALIDATE CLI ///

all = false;
if(x=BlackListBaseApp::main()) return x; // Parse CLI args, open conf & db
if(ipv6!="" && ipv6.size()<8) ipv6+='\t'; // an extra \t to line up columns. :-)

/// Load list of bad domains ///

// These should be excluded from the list below since they should be
// blocked by other means and the point of domain wide blocking is to
// relieve the burden on the blocking tools (dnsmasq).

if(!all) db >> baddoms;

/// Query & load data ///

qry = db <<
"SELECT name "
"FROM dns "
"WHERE status=2 " // 2 = blocked... need this doc'd somewhere...
"ORDER BY name";
while(qry.next()) {
qry >> s;
if(all || baddoms!=s) { // exclude blocked domains
if(ipv4!="") cout << ipv4 << '\t' << s << '\n';
if(ipv6!="") cout << ipv6 << '\t' << s << '\n';
}
}
return 0;
}

};



//////////////////////////////////////////////////////////////////////
// Lets run the report and dump it out
//////////////////////////////////////////////////////////////////////

MAIN(DNSblackList)

+ 61
- 0
trafficmon/domblacklist.cpp View File

@@ -0,0 +1,61 @@
//////////////////////////////////////////////////////////////////////
// Dump Black Listed whole domain (*.domain.tld) entries
// Written by Jonathan A. Foster <jon@jfpossibilities.com>
// Started December 28th, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//
// Read the "dns_wild" table and dump all black listed domain names as
// "address" entries for a dnsmasq.conf file. This will black list the
// whole domain, subdomains, hosts and all.
//////////////////////////////////////////////////////////////////////
#include <string>
#include <map>
#include <iostream>
#include <stdio.h>
#include <libgen.h>

#include "../strutil.h"
#include "appbase.h"
using namespace std;



//////////////////////////////////////////////////////////////////////
// Connection Report Generator Application Class
//////////////////////////////////////////////////////////////////////

struct DomainBlackList: BlackListBaseApp {

int main() {
cppdb::result qry;
string s;
int x;

/// SETUP & VALIDATE CLI ///

if(x=BlackListBaseApp::main()) return x; // Parse CLI args, open conf & db

/// Query & load data ///

qry = db <<
"SELECT name "
"FROM dns_wild "
"WHERE status=2 " // 2 = blocked... need this doc'd somewhere...
"ORDER BY name";
while(qry.next()) {
qry >> s;
if(ipv4!="") cout << "address=/" << s << '/' << ipv4 << '\n';
if(ipv6!="") cout << "address=/" << s << '/' << ipv6 << '\n';
}
return 0;
}

};



//////////////////////////////////////////////////////////////////////
// Lets run the report and dump it out
//////////////////////////////////////////////////////////////////////

MAIN(DomainBlackList)

+ 18
- 11
trafficmon/init View File

@@ -21,7 +21,7 @@
NAME="trafficmon"
DAEMON="/usr/sbin/$NAME"
RUN=/run
CONF=/etc/poorman-ids/sample.conf
CONF=""
SOCK=""

# Pull in config
@@ -52,11 +52,16 @@ CTRL() {

do_start() {
echo -n "Starting Traffic Monitor: "
if [ -z "$CONF" ]; then
echo "NOT CONFIGURED"
return 0
fi
if CTRL --start --oknodo -- -c "$CONF" -b -i "$PID" -p "$SOCK"; then
echo "OK"
return 0 #JIC
else
echo "FAIL"
exit 1
return 1
fi
}

@@ -66,9 +71,10 @@ do_stop() {
echo -n "Stoping Traffic Monitor: "
if CTRL --stop --remove-pidfile; then
echo "OK"
return 0 #JIC
else
echo "FAIL"
exit 1
return 1
fi
}

@@ -78,36 +84,37 @@ do_status() {
echo -n "Traffic Monitor is: "
if CTRL --status; then
echo "Up"
return 0 #JIC
else
echo "Down"
exit 1
return 1
fi
}



### Main()
### Main()

case "$1" in
start)
do_start
;;
stop)
do_stop
;;
restart)
do_status && do_stop
do_start
;;
status)
do_status
;;
*)
echo "$0 {start | stop | restart | status}"
;;
esac
esac

+ 47
- 50
trafficmon/trafficmon.cpp View File

@@ -22,12 +22,11 @@
#include <stdexcept>
#include <vector>
#include <map>
#include <cppdb/frontend.h>

#include "../cli.h"
#include "../data.h"
#include "../miniini.h"
#include "../config.h"
#include "appbase.h"
using namespace std;


@@ -36,11 +35,10 @@ using namespace std;
// Monitor Config
//////////////////////////////////////////////////////////////////////

struct MonitorConf: public MiniINI {
MiniINIvars traffic_mon; // This app's config variables
struct MonitorConf: public MonitorBaseConf {
INIusList us;

MonitorConf() { groups["Traffic Mon"] = &traffic_mon; groups["us"] = &us; }
MonitorConf() { groups["us"] = &us; }
};


@@ -51,11 +49,9 @@ struct MonitorConf: public MiniINI {
//////////////////////////////////////////////////////////////////////
//#define DEBUG

struct TrafficMon: public cBaseApp {
MonitorConf config;
struct TrafficMon: public TrafficMonBaseApp {
LogAnalyzer analyze;
istream *log;
cppdb::session db;
LiveBug bug;
bool setup_db;
bool piping; // are we sucking on a pipe?
@@ -76,13 +72,14 @@ struct TrafficMon: public cBaseApp {
running( false),
line_no( 0)
{
analyze.us = &(config.us.vals);
config = new MonitorConf;
analyze.us = &(((MonitorConf *)config)->us.vals);
}



void help() {
cerr <<
int help() {
cerr <<
"\n"
"trafficmon -c {config file} [-i] [-d] [-b] [-p {pipe name} | [{log name} ...]]\n"
"\n"
@@ -96,7 +93,7 @@ struct TrafficMon: public cBaseApp {
"Other arguments are log files to be processed. This can be used to bulk\n"
"load data prior to going live with an always on daemon or for catching\n"
"up if the daemon stopped for some reason.";
ExitCode = 1;
return ExitCode = 1;
}


@@ -105,13 +102,12 @@ struct TrafficMon: public cBaseApp {
if(sw[1]==0) {
switch(*sw) {
case 'b': background_me = true; return 0;
case 'c': return 1;
case 'd': setup_db = true; return 0;
case 'i': return 1;
case 'p': piping = true; return !running; // second pass treats it as a file
}
}
throw CLIerror("Unrecognized Switch");
return TrafficMonBaseApp::do_switch(sw);
}


@@ -120,40 +116,16 @@ struct TrafficMon: public cBaseApp {
// If "running" is set then we've already done this. So skip it!
if(running) return;

switch(*sw) {
if(sw[1]==0) switch(*sw) {

/// Load config file ///

case 'c':
// This is only called with "c". See above
config.load(val);
if(!config.us.vals.size()) throw CLIerror(
"The configuration files MUST contain an [us] section with "
"appropriate values"
);
if(!config.traffic_mon.vals.size()) throw CLIerror(
"The configuration files MUST contain an [Traffic Mon] section with "
"appropriate values"
);

/// configure resources from config file ///

// TODO: pre-validate some of these?
db.open("mysql:user="+qesc(config.traffic_mon.get("db user"))+
";password="+qesc(config.traffic_mon.get("db password"))+
";host="+qesc(config.traffic_mon.get("db host"))+
";database="+qesc(config.traffic_mon.get("db name"))+
";@opt_reconnect=1");
return;
/// PID file to write ID in if we're daemonizing ///

case 'i':
pid_file = val;
return;

/// Make pipe if requested ///

case 'p':
// This can't wait for "running" since we want it to fail in foreground
// Make read/write by process owner (root) only
@@ -163,6 +135,8 @@ struct TrafficMon: public cBaseApp {
}
inp_ct++;
}

TrafficMonBaseApp::do_switch_arg(sw, val);
}


@@ -200,7 +174,8 @@ restart:
// probably should daemonize around here.
int run() {
string l;
cppdb::statement db_add = db << // prepare insert statement
// prepare insert statement
cppdb::statement db_add = db <<
"INSERT INTO connections "
"(us, us_port, them, them_port, them_name, protocol, inbound) "
"VALUES (?,?,?,?,?,?,?)";
@@ -262,20 +237,42 @@ restart:
db << "CREATE TABLE dns ("
"name CHAR(128) NOT NULL PRIMARY KEY,"
"decided TIMESTAMP NOT NULL,"
"status TINYINT(1) NOT NULL DEFAULT 0"
"status TINYINT(1) NOT NULL DEFAULT 0,"
"note CHAR(128) NOT NULL DEFAULT ''"
") Engine=MyISAM"
<< cppdb::exec;

/// wild card DNS list ///

// NOTE: All of these names are treated as if prefixed with "*.". At this
// time there are only plans to implement this as a black list.
// Many domains are sevices with threat potential and discovering
// all of the host and subdomains may not be viable. Think ad and
// tracking services.
db << "CREATE TABLE dns_wild ("
"name CHAR(128) NOT NULL PRIMARY KEY,"
"decided TIMESTAMP NOT NULL,"
"status TINYINT(1) NOT NULL DEFAULT 2,"
"note CHAR(128) NOT NULL DEFAULT ''"
") Engine=MyISAM"
<< cppdb::exec;

}



int main() {
int x;
try {
cBaseApp::main();
if(!config.us.vals.size())
// Since -c requires something in [us] we'll only get here if a config
// wasn't loaded.
throw CLIerror("You must specify a config file to load");
if(x=TrafficMonBaseApp::main()) return x;
if(!((MonitorConf*)config)->us.vals.size()) throw CLIerror(
"The configuration files MUST contain an [us] section with "
"appropriate values"
);
if(!config->traffic_mon.vals.size()) throw CLIerror(
"The configuration files MUST contain an [Traffic Mon] section with "
"appropriate values"
);
if(piping && inp_ct!=1) throw CLIerror(
"Pipe requires one and only one file name to read from."
);
@@ -304,6 +301,8 @@ restart:
try {
// CLI processed now lets analyze data.
running = true;
// TODO: this is going to break things? Probably should prevent reloading conf. DB is opened in TrafficMonBaseApp::main()
// Actually I think it may actualy work...
cBaseApp::main(); // re-run CLI args for inputs
if(!log) {
// no inputs spec'd on CLI assume stdin (we're not a daemon)
@@ -322,8 +321,6 @@ restart:
return ExitCode;
}



};




Loading…
Cancel
Save