////////////////////////////////////////////////////////////////////// // Basic Access Log report // Written by Jonathan A. Foster // 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 // // 20240319 // Implemented "ignores" in the report. These use the ignore section // used by trafficrpt. But there are some oddities (so far). All // report entries are considered to be TCP connections originating // from 0.0.0.0 and outbound. This is a cheat to prevent // complication in the query process. Its tempting to implement this // in trafficmon... but that is a permanent loss of data... still // debating. ////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "../strutil.h" #include "appbase.h" using namespace std; ////////////////////////////////////////////////////////////////////// // A connection with count ////////////////////////////////////////////////////////////////////// struct ConnWct: public Conn { long count; }; // TODO: connection sql should be global. namespace cppdb { result &operator>>(result &qry, ConnWct &c) { qry >> c.us >> c.us_port >> c.them >> c.them_port >> c.name >> c.protocol >> c.count; return qry; } } // cppcms ////////////////////////////////////////////////////////////////////// // A "line" of a report - a single domain/address ////////////////////////////////////////////////////////////////////// struct ReportLine { int count; map 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::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 { // 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[1024]; l[1023]=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 convert 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"; if(bk.size()>=sizeof(l)-1) throw std::runtime_error( "ReportData::ascii: report row width is too long."); 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(); } ////////////////////////////////////////////////////////////////////// // Connection Report Generator Application Class ////////////////////////////////////////////////////////////////////// struct appConnectionReport: TrafficMonBaseApp { ReportData rpt; 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; return 1; } 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; ConnWct rec; rec.in=0; int x; /// SETUP & VALIDATE CLI /// try { if(x = TrafficMonBaseApp::main()) return x; // Parse CLI args if(cli_mode!=2) throw CLIerror("Invalid arguments"); } catch(const CLIerror &e) { cerr << e.what() << "\n\n"; return help(); } /// Query & load data /// qry = db << "SELECT c.us, c.us_port, c.them, c.them_port, c.them_name, protocol, 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.us, c.us_port, c.them, c.them_port, c.them_name, c.protocol" << start_stamp << (end_stamp + " 23:59:59"); // include to the end of the day while(qry.next()) { qry >> rec; if(config->ignores.vals.find(rec)<0) rpt.add( rec.name=="" ? rec.them : rec.name, str(rec.them_port), rec.count ); } /// 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)