The Poor Man's (or Woman's) Intrusion Detection System
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

251 lines
7.1 KiB

  1. //////////////////////////////////////////////////////////////////////
  2. // Basic Access Log report
  3. // Written by Jonathan A. Foster <jon@jfpossibilities.com>
  4. // Started August 20th, 2021
  5. // Copyright JF Possibilities, Inc. All rights reserved.
  6. //
  7. // To start with this gather data for a specific access period and
  8. // create a report of domain names and addresses that accessed to and
  9. // from the net. All "accepted" and "blocked" accesses will be
  10. // ignored. For the moment we're expecting blocked traffic is actually
  11. // blocked.
  12. //
  13. // The report will contain three columns:
  14. // 1. domain name or address if a domain is not known.
  15. // 2. list of ports that were connected to.
  16. // 3. count of total connections
  17. //////////////////////////////////////////////////////////////////////
  18. #include <string>
  19. #include <map>
  20. #include <iostream>
  21. #include <stdio.h>
  22. #include <libgen.h>
  23. #include <cppdb/frontend.h>
  24. #include "../strutil.h"
  25. #include "../cli.h"
  26. #include "../miniini.h"
  27. using namespace std;
  28. //////////////////////////////////////////////////////////////////////
  29. // A "line" of a report - a single domain/address
  30. //////////////////////////////////////////////////////////////////////
  31. struct ReportLine {
  32. int count;
  33. map<string,int> ports;
  34. // initialize
  35. ReportLine(): count(0) {}
  36. // add an item
  37. void add(const string &port, int _count) {
  38. ports[port]+=_count;
  39. count+=_count;
  40. }
  41. // ports -> string
  42. string port_list() const {
  43. string r;
  44. map<string,int>::const_iterator it = ports.begin();
  45. if(it==ports.end()) return r;
  46. r = it->first;
  47. for(it++; it!=ports.end(); it++) r+=","+it->first;
  48. return r;
  49. }
  50. };
  51. //////////////////////////////////////////////////////////////////////
  52. // A full reports worth of data - domain names / addresses and their
  53. // associated stats.
  54. //////////////////////////////////////////////////////////////////////
  55. struct ReportData: map<string,ReportLine> {
  56. // Add an item to the report
  57. void add(const string &address, const string &port, int count) {
  58. (*this)[address].add(port, count);
  59. }
  60. // Render report in an ASCII table
  61. string ascii() const {
  62. int widths[3] = {0,0,0}; // max column widths: 0: DNS, 1: ports, 3: counts
  63. int x;
  64. char l[256]; l[255]=0;
  65. string s, r, bk;
  66. ReportData::const_iterator it;
  67. for(it = begin(); it!=end(); it++) {
  68. if(it->first.size()>widths[0]) widths[0] = it->first.size();
  69. s = it->second.port_list();
  70. if(s.size()>widths[1]) widths[1]=s.size();
  71. if(it->second.count>widths[2]) widths[2] = it->second.count;
  72. }
  73. // Now conver count max to cols
  74. for(x=0; widths[2]; x++) widths[2] /= 10;
  75. widths[2] = x ? x : 1;
  76. // min col widths for titles
  77. if(widths[0]<6) widths[0]=6;
  78. if(widths[1]<7) widths[1]=7;
  79. if(widths[2]<2) widths[2]=2;
  80. // render report
  81. bk = "+"+string(widths[0]+2, '-')+
  82. "+"+string(widths[1]+2, '-')+
  83. "+"+string(widths[2]+2, '-')+
  84. "+\n";
  85. s = "| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %"+str(widths[2])+"d |\n";
  86. r = bk;
  87. snprintf(l, sizeof(l)-1,
  88. ("| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %-"+str(widths[2])+"s |\n").c_str(),
  89. (const char *)"Remote",
  90. (const char *)"Port(s)",
  91. (const char *)"Ct"
  92. );
  93. r+= l;
  94. r+= bk;
  95. for(it = begin(); it!=end(); it++) {
  96. snprintf(l, sizeof(l)-1, s.c_str(),
  97. it->first.c_str(),
  98. it->second.port_list().c_str(),
  99. it->second.count
  100. );
  101. r+=l;
  102. }
  103. r+=bk;
  104. return r;
  105. }
  106. };
  107. inline ostream &operator<<(ostream &out, const ReportData &r){
  108. return out << r.ascii();
  109. }
  110. namespace cppdb {
  111. result &operator>>(result &qry, ::ReportData &rpt) {
  112. string name, addr, port;
  113. int ct;
  114. qry >> name >> addr >> port >> ct;
  115. if(name=="") name=addr;
  116. rpt.add(name, port, ct);
  117. }
  118. }
  119. //////////////////////////////////////////////////////////////////////
  120. // Our config file.
  121. //
  122. // This is designed so that all parts can use the same config. Tools
  123. // ignore the parts they aren't interested in.
  124. //////////////////////////////////////////////////////////////////////
  125. /* TODO: refactor this as a base class... */
  126. struct ReportConf: public MiniINI {
  127. MiniINIvars traffic_mon; // This app's config variables
  128. ReportConf() { groups["Traffic Mon"] = &traffic_mon; }
  129. };
  130. //////////////////////////////////////////////////////////////////////
  131. // Connection Report Generator Application Class
  132. //////////////////////////////////////////////////////////////////////
  133. struct appConnectionReport: cBaseApp {
  134. ReportConf config;
  135. ReportData rpt;
  136. cppdb::session db;
  137. string start_stamp;
  138. string end_stamp;
  139. int cli_mode; // which non-switch are we processing
  140. appConnectionReport(): cli_mode(0) {}
  141. int help() {
  142. cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} {start} {end}\n"
  143. << '\n'
  144. << "The config file must have a [Traffic Mon] section with the database\n"
  145. << "credentials in it. 'start' and 'stop' are the SQL timestamps for\n"
  146. << "report time span." << endl;
  147. }
  148. virtual unsigned do_switch(const char *arg) {
  149. if(*arg=='c' && !arg[1]) return 1;
  150. throw CLIerror("Invalid switch "+string(arg));
  151. }
  152. virtual void do_switch_arg(const char *sw, const std::string &val) {
  153. // The only way we get here is with -c
  154. config.load(val);
  155. // TODO: config validity checks
  156. }
  157. virtual void do_arg(const char *arg) {
  158. switch(cli_mode++) {
  159. case 0: start_stamp = arg; return;
  160. case 1: end_stamp = arg; return;
  161. default: throw CLIerror("Invalid arguments");
  162. }
  163. }
  164. int main() {
  165. cppdb::result qry;
  166. /// SETUP & VALIDATE CLI ///
  167. try {
  168. cBaseApp::main(); // Parse CLI args
  169. if(!config.traffic_mon.vals.size()) throw CLIerror(
  170. "You need to load a config file with a [Traffic Mon] section"
  171. );
  172. if(cli_mode!=2) throw CLIerror("Invlaid arguments");
  173. } catch(const CLIerror &e) {
  174. cerr << e.what() << "\n\n";
  175. return help();
  176. }
  177. db.open("mysql:user="+qesc(config.traffic_mon.get("db user"))+
  178. ";password="+qesc(config.traffic_mon.get("db password"))+
  179. ";host="+qesc(config.traffic_mon.get("db host"))+
  180. ";database="+qesc(config.traffic_mon.get("db name"))+
  181. ";@opt_reconnect=1");
  182. /// Query & load data ///
  183. qry = db <<
  184. "SELECT c.them_name, c.them, c.them_port, count(*) "
  185. "FROM connections c LEFT OUTER JOIN dns d ON c.them_name=d.name "
  186. "WHERE c.inbound=0 AND (d.status IS NULL or d.status=0) "
  187. "AND tstamp>=? AND tstamp<=? "
  188. "GROUP BY c.them_name, c.them, c.them_port"
  189. << start_stamp << (end_stamp + " 23:59:59"); // include to the end of the day
  190. while(qry.next()) qry >> rpt;
  191. /// spit out the report ///
  192. cout << "Web access report for " << start_stamp << " - " << end_stamp << "\n\n"
  193. << rpt;
  194. return 0;
  195. }
  196. };
  197. //////////////////////////////////////////////////////////////////////
  198. // Lets run the report and dump it out
  199. //////////////////////////////////////////////////////////////////////
  200. MAIN(appConnectionReport)