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.
 
 
 
 

233 lines
6.7 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. // 20240319 <jon@jfpossibilities.com>
  19. // Implemented "ignores" in the report. These use the ignore section
  20. // used by trafficrpt. But there are some oddities (so far). All
  21. // report entries are considered to be TCP connections originating
  22. // from 0.0.0.0 and outbound. This is a cheat to prevent
  23. // complication in the query process. Its tempting to implement this
  24. // in trafficmon... but that is a permanent loss of data... still
  25. // debating.
  26. //////////////////////////////////////////////////////////////////////
  27. #include <string>
  28. #include <map>
  29. #include <iostream>
  30. #include <stdio.h>
  31. #include <libgen.h>
  32. #include "../strutil.h"
  33. #include "appbase.h"
  34. using namespace std;
  35. //////////////////////////////////////////////////////////////////////
  36. // A "line" of a report - a single domain/address
  37. //////////////////////////////////////////////////////////////////////
  38. struct ReportLine {
  39. int count;
  40. map<string,int> ports;
  41. // initialize
  42. ReportLine(): count(0) {}
  43. // add an item
  44. void add(const string &port, int _count) {
  45. ports[port]+=_count;
  46. count+=_count;
  47. }
  48. // ports -> string
  49. string port_list() const {
  50. string r;
  51. map<string,int>::const_iterator it = ports.begin();
  52. if(it==ports.end()) return r;
  53. r = it->first;
  54. for(it++; it!=ports.end(); it++) r+=","+it->first;
  55. return r;
  56. }
  57. };
  58. //////////////////////////////////////////////////////////////////////
  59. // A full reports worth of data - domain names / addresses and their
  60. // associated stats.
  61. //////////////////////////////////////////////////////////////////////
  62. struct ReportData: map<string,ReportLine> {
  63. // Add an item to the report
  64. void add(const string &address, const string &port, int count) {
  65. (*this)[address].add(port, count);
  66. }
  67. // Render report in an ASCII table
  68. string ascii() const {
  69. int widths[3] = {0,0,0}; // max column widths: 0: DNS, 1: ports, 3: counts
  70. int x;
  71. char l[256]; l[255]=0;
  72. string s, r, bk;
  73. ReportData::const_iterator it;
  74. for(it = begin(); it!=end(); it++) {
  75. if(it->first.size()>widths[0]) widths[0] = it->first.size();
  76. s = it->second.port_list();
  77. if(s.size()>widths[1]) widths[1]=s.size();
  78. if(it->second.count>widths[2]) widths[2] = it->second.count;
  79. }
  80. // Now conver count max to cols
  81. for(x=0; widths[2]; x++) widths[2] /= 10;
  82. widths[2] = x ? x : 1;
  83. // min col widths for titles
  84. if(widths[0]<6) widths[0]=6;
  85. if(widths[1]<7) widths[1]=7;
  86. if(widths[2]<2) widths[2]=2;
  87. // render report
  88. bk = "+"+string(widths[0]+2, '-')+
  89. "+"+string(widths[1]+2, '-')+
  90. "+"+string(widths[2]+2, '-')+
  91. "+\n";
  92. s = "| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %"+str(widths[2])+"d |\n";
  93. r = bk;
  94. snprintf(l, sizeof(l)-1,
  95. ("| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %-"+str(widths[2])+"s |\n").c_str(),
  96. (const char *)"Remote",
  97. (const char *)"Port(s)",
  98. (const char *)"Ct"
  99. );
  100. r+= l;
  101. r+= bk;
  102. for(it = begin(); it!=end(); it++) {
  103. snprintf(l, sizeof(l)-1, s.c_str(),
  104. it->first.c_str(),
  105. it->second.port_list().c_str(),
  106. it->second.count
  107. );
  108. r+=l;
  109. }
  110. r+=bk;
  111. return r;
  112. }
  113. };
  114. inline ostream &operator<<(ostream &out, const ReportData &r){
  115. return out << r.ascii();
  116. }
  117. // NOTE: implementation at bottom.
  118. namespace cppdb {
  119. result &operator>>(result &qry, ::ReportData &rpt);
  120. }
  121. //////////////////////////////////////////////////////////////////////
  122. // Connection Report Generator Application Class
  123. //////////////////////////////////////////////////////////////////////
  124. struct appConnectionReport: TrafficMonBaseApp {
  125. ReportData rpt;
  126. string start_stamp;
  127. string end_stamp;
  128. int cli_mode; // which non-switch are we processing
  129. appConnectionReport(): cli_mode(0) { }
  130. int help() {
  131. cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} {start} {end}\n"
  132. << '\n'
  133. << "The config file must have a [Traffic Mon] section with the database\n"
  134. << "credentials in it. 'start' and 'stop' are the SQL timestamps for\n"
  135. << "report time span." << endl;
  136. return 1;
  137. }
  138. virtual void do_arg(const char *arg) {
  139. switch(cli_mode++) {
  140. case 0: start_stamp = arg; return;
  141. case 1: end_stamp = arg; return;
  142. default: throw CLIerror("Invalid arguments");
  143. }
  144. }
  145. int main() {
  146. cppdb::result qry;
  147. int x;
  148. /// SETUP & VALIDATE CLI ///
  149. try {
  150. if(x = TrafficMonBaseApp::main()) return x; // Parse CLI args
  151. if(cli_mode!=2) throw CLIerror("Invlaid arguments");
  152. } catch(const CLIerror &e) {
  153. cerr << e.what() << "\n\n";
  154. return help();
  155. }
  156. /// Query & load data ///
  157. qry = db <<
  158. "SELECT c.them_name, c.them, c.them_port, count(*) "
  159. "FROM connections c LEFT OUTER JOIN dns d ON c.them_name=d.name "
  160. "WHERE c.inbound=0 AND (d.status IS NULL or d.status=0) "
  161. "AND tstamp>=? AND tstamp<=? "
  162. "GROUP BY c.them_name, c.them, c.them_port"
  163. << start_stamp << (end_stamp + " 23:59:59"); // include to the end of the day
  164. while(qry.next()) qry >> rpt;
  165. /// spit out the report ///
  166. cout << "Web access report for " << start_stamp << " - " << end_stamp << "\n\n"
  167. << rpt;
  168. return 0;
  169. }
  170. };
  171. //////////////////////////////////////////////////////////////////////
  172. // Lets run the report and dump it out
  173. //////////////////////////////////////////////////////////////////////
  174. MAIN(appConnectionReport)
  175. // NOTE: This needs to be down here so it knows of "app", defined by MAIN.
  176. namespace cppdb {
  177. result &operator>>(result &qry, ::ReportData &rpt) {
  178. Conn rec;
  179. int ct;
  180. rec.us="0.0.0.0";
  181. rec.protocol="TCP";
  182. rec.in=0;
  183. qry >> rec.name >> rec.them >> rec.them_port >> ct;
  184. // NOTE: ignores can only work from remote addresses.
  185. if(app.config->ignores.vals.find(rec)<0) {
  186. if(rec.name=="") rec.name=rec.them;
  187. rpt.add(rec.name, str(rec.them_port), ct);
  188. }
  189. else cerr << "ignored" << endl;
  190. }
  191. }