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.
 
 
 
 

210 lines
5.8 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 "../strutil.h"
  24. #include "appbase.h"
  25. using namespace std;
  26. //////////////////////////////////////////////////////////////////////
  27. // A "line" of a report - a single domain/address
  28. //////////////////////////////////////////////////////////////////////
  29. struct ReportLine {
  30. int count;
  31. map<string,int> ports;
  32. // initialize
  33. ReportLine(): count(0) {}
  34. // add an item
  35. void add(const string &port, int _count) {
  36. ports[port]+=_count;
  37. count+=_count;
  38. }
  39. // ports -> string
  40. string port_list() const {
  41. string r;
  42. map<string,int>::const_iterator it = ports.begin();
  43. if(it==ports.end()) return r;
  44. r = it->first;
  45. for(it++; it!=ports.end(); it++) r+=","+it->first;
  46. return r;
  47. }
  48. };
  49. //////////////////////////////////////////////////////////////////////
  50. // A full reports worth of data - domain names / addresses and their
  51. // associated stats.
  52. //////////////////////////////////////////////////////////////////////
  53. struct ReportData: map<string,ReportLine> {
  54. // Add an item to the report
  55. void add(const string &address, const string &port, int count) {
  56. (*this)[address].add(port, count);
  57. }
  58. // Render report in an ASCII table
  59. string ascii() const {
  60. int widths[3] = {0,0,0}; // max column widths: 0: DNS, 1: ports, 3: counts
  61. int x;
  62. char l[256]; l[255]=0;
  63. string s, r, bk;
  64. ReportData::const_iterator it;
  65. for(it = begin(); it!=end(); it++) {
  66. if(it->first.size()>widths[0]) widths[0] = it->first.size();
  67. s = it->second.port_list();
  68. if(s.size()>widths[1]) widths[1]=s.size();
  69. if(it->second.count>widths[2]) widths[2] = it->second.count;
  70. }
  71. // Now conver count max to cols
  72. for(x=0; widths[2]; x++) widths[2] /= 10;
  73. widths[2] = x ? x : 1;
  74. // min col widths for titles
  75. if(widths[0]<6) widths[0]=6;
  76. if(widths[1]<7) widths[1]=7;
  77. if(widths[2]<2) widths[2]=2;
  78. // render report
  79. bk = "+"+string(widths[0]+2, '-')+
  80. "+"+string(widths[1]+2, '-')+
  81. "+"+string(widths[2]+2, '-')+
  82. "+\n";
  83. s = "| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %"+str(widths[2])+"d |\n";
  84. r = bk;
  85. snprintf(l, sizeof(l)-1,
  86. ("| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %-"+str(widths[2])+"s |\n").c_str(),
  87. (const char *)"Remote",
  88. (const char *)"Port(s)",
  89. (const char *)"Ct"
  90. );
  91. r+= l;
  92. r+= bk;
  93. for(it = begin(); it!=end(); it++) {
  94. snprintf(l, sizeof(l)-1, s.c_str(),
  95. it->first.c_str(),
  96. it->second.port_list().c_str(),
  97. it->second.count
  98. );
  99. r+=l;
  100. }
  101. r+=bk;
  102. return r;
  103. }
  104. };
  105. inline ostream &operator<<(ostream &out, const ReportData &r){
  106. return out << r.ascii();
  107. }
  108. namespace cppdb {
  109. result &operator>>(result &qry, ::ReportData &rpt) {
  110. string name, addr, port;
  111. int ct;
  112. qry >> name >> addr >> port >> ct;
  113. if(name=="") name=addr;
  114. rpt.add(name, port, ct);
  115. }
  116. }
  117. //////////////////////////////////////////////////////////////////////
  118. // Connection Report Generator Application Class
  119. //////////////////////////////////////////////////////////////////////
  120. struct appConnectionReport: TrafficMonBaseApp {
  121. ReportData rpt;
  122. string start_stamp;
  123. string end_stamp;
  124. int cli_mode; // which non-switch are we processing
  125. appConnectionReport(): cli_mode(0) { }
  126. int help() {
  127. cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} {start} {end}\n"
  128. << '\n'
  129. << "The config file must have a [Traffic Mon] section with the database\n"
  130. << "credentials in it. 'start' and 'stop' are the SQL timestamps for\n"
  131. << "report time span." << endl;
  132. return 1;
  133. }
  134. virtual void do_arg(const char *arg) {
  135. switch(cli_mode++) {
  136. case 0: start_stamp = arg; return;
  137. case 1: end_stamp = arg; return;
  138. default: throw CLIerror("Invalid arguments");
  139. }
  140. }
  141. int main() {
  142. cppdb::result qry;
  143. int x;
  144. /// SETUP & VALIDATE CLI ///
  145. try {
  146. if(x = TrafficMonBaseApp::main()) return x; // Parse CLI args
  147. if(cli_mode!=2) throw CLIerror("Invlaid arguments");
  148. } catch(const CLIerror &e) {
  149. cerr << e.what() << "\n\n";
  150. return help();
  151. }
  152. /// Query & load data ///
  153. qry = db <<
  154. "SELECT c.them_name, c.them, c.them_port, count(*) "
  155. "FROM connections c LEFT OUTER JOIN dns d ON c.them_name=d.name "
  156. "WHERE c.inbound=0 AND (d.status IS NULL or d.status=0) "
  157. "AND tstamp>=? AND tstamp<=? "
  158. "GROUP BY c.them_name, c.them, c.them_port"
  159. << start_stamp << (end_stamp + " 23:59:59"); // include to the end of the day
  160. while(qry.next()) qry >> rpt;
  161. /// spit out the report ///
  162. cout << "Web access report for " << start_stamp << " - " << end_stamp << "\n\n"
  163. << rpt;
  164. return 0;
  165. }
  166. };
  167. //////////////////////////////////////////////////////////////////////
  168. // Lets run the report and dump it out
  169. //////////////////////////////////////////////////////////////////////
  170. MAIN(appConnectionReport)