|
- //////////////////////////////////////////////////////////////////////
- // Basic Access Log report
- // Written by Jonathan A. Foster <jon@jfpossibilities.com>
- // Started August 20th, 2021
- // Copyright JF Possibilities, Inc. All rights reserved.
- //
- // To start with this gather data for a specific access period and
- // create a report of domain names and addresses that accessed to and
- // from the net. All "accepted" and "blocked" accesses will be
- // ignored. For the moment we're expecting blocked traffic is actually
- // blocked.
- //
- // The report will contain three columns:
- // 1. domain name or address if a domain is not known.
- // 2. list of ports that were connected to.
- // 3. count of total connections
- //////////////////////////////////////////////////////////////////////
- #include <string>
- #include <map>
- #include <iostream>
- #include <stdio.h>
- #include <libgen.h>
- #include <cppdb/frontend.h>
-
- #include "../strutil.h"
- #include "../cli.h"
- #include "../miniini.h"
- using namespace std;
-
-
-
- //////////////////////////////////////////////////////////////////////
- // A "line" of a report - a single domain/address
- //////////////////////////////////////////////////////////////////////
-
- struct ReportLine {
- int count;
- map<string,int> ports;
-
- // initialize
- ReportLine(): count(0) {}
- // add an item
- void add(const string &port, int _count) {
- ports[port]+=_count;
- count+=_count;
- }
- // ports -> string
- string port_list() const {
- string r;
- map<string,int>::const_iterator it = ports.begin();
- if(it==ports.end()) return r;
- r = it->first;
- for(it++; it!=ports.end(); it++) r+=","+it->first;
- return r;
- }
- };
-
-
-
- //////////////////////////////////////////////////////////////////////
- // A full reports worth of data - domain names / addresses and their
- // associated stats.
- //////////////////////////////////////////////////////////////////////
-
- struct ReportData: map<string,ReportLine> {
-
- // Add an item to the report
- void add(const string &address, const string &port, int count) {
- (*this)[address].add(port, count);
- }
-
- // Render report in an ASCII table
- string ascii() const {
- int widths[3] = {0,0,0}; // max column widths: 0: DNS, 1: ports, 3: counts
- int x;
- char l[256]; l[255]=0;
- string s, r, bk;
- ReportData::const_iterator it;
-
- for(it = begin(); it!=end(); it++) {
- if(it->first.size()>widths[0]) widths[0] = it->first.size();
- s = it->second.port_list();
- if(s.size()>widths[1]) widths[1]=s.size();
- if(it->second.count>widths[2]) widths[2] = it->second.count;
- }
- // Now conver count max to cols
- for(x=0; widths[2]; x++) widths[2] /= 10;
- widths[2] = x ? x : 1;
- // min col widths for titles
- if(widths[0]<6) widths[0]=6;
- if(widths[1]<7) widths[1]=7;
- if(widths[2]<2) widths[2]=2;
-
- // render report
- bk = "+"+string(widths[0]+2, '-')+
- "+"+string(widths[1]+2, '-')+
- "+"+string(widths[2]+2, '-')+
- "+\n";
- s = "| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %"+str(widths[2])+"d |\n";
- r = bk;
- snprintf(l, sizeof(l)-1,
- ("| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %-"+str(widths[2])+"s |\n").c_str(),
- (const char *)"Remote",
- (const char *)"Port(s)",
- (const char *)"Ct"
- );
- r+= l;
- r+= bk;
- for(it = begin(); it!=end(); it++) {
- snprintf(l, sizeof(l)-1, s.c_str(),
- it->first.c_str(),
- it->second.port_list().c_str(),
- it->second.count
- );
- r+=l;
- }
- r+=bk;
- return r;
- }
- };
- inline ostream &operator<<(ostream &out, const ReportData &r){
- return out << r.ascii();
- }
- namespace cppdb {
- result &operator>>(result &qry, ::ReportData &rpt) {
- string name, addr, port;
- int ct;
- qry >> name >> addr >> port >> ct;
- if(name=="") name=addr;
- rpt.add(name, port, ct);
- }
- }
-
-
- //////////////////////////////////////////////////////////////////////
- // 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
-
-
- appConnectionReport(): cli_mode(0) {}
-
-
-
- int help() {
- cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} {start} {end}\n"
- << '\n'
- << "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
- }
-
-
-
- virtual void do_arg(const char *arg) {
- switch(cli_mode++) {
- case 0: start_stamp = arg; return;
- case 1: end_stamp = arg; return;
- default: throw CLIerror("Invalid arguments");
- }
- }
-
-
-
- int main() {
- cppdb::result qry;
-
- /// 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(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 <<
- "SELECT c.them_name, c.them, c.them_port, count(*) "
- "FROM connections c LEFT OUTER JOIN dns d ON c.them_name=d.name "
- "WHERE c.inbound=0 AND (d.status IS NULL or d.status=0) "
- "AND tstamp>=? AND tstamp<=? "
- "GROUP BY c.them_name, c.them, c.them_port"
- << start_stamp << (end_stamp + " 23:59:59"); // include to the end of the day
- while(qry.next()) qry >> rpt;
-
- /// spit out the report ///
-
- cout << "Web access report for " << start_stamp << " - " << end_stamp << "\n\n"
- << rpt;
- return 0;
- }
-
- };
-
-
-
- //////////////////////////////////////////////////////////////////////
- // Lets run the report and dump it out
- //////////////////////////////////////////////////////////////////////
-
- MAIN(appConnectionReport)
|