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.
 
 
 
 

234 lines
6.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. // 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[1024]; l[1023]=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 convert 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. if(bk.size()>=sizeof(l)-1) throw std::runtime_error(
  93. "ReportData::ascii: report row width is too long.");
  94. s = "| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %"+str(widths[2])+"d |\n";
  95. r = bk;
  96. snprintf(l, sizeof(l)-1,
  97. ("| %-"+str(widths[0])+"s | %-"+str(widths[1])+"s | %-"+str(widths[2])+"s |\n").c_str(),
  98. (const char *)"Remote",
  99. (const char *)"Port(s)",
  100. (const char *)"Ct"
  101. );
  102. r+= l;
  103. r+= bk;
  104. for(it = begin(); it!=end(); it++) {
  105. snprintf(l, sizeof(l)-1, s.c_str(),
  106. it->first.c_str(),
  107. it->second.port_list().c_str(),
  108. it->second.count
  109. );
  110. r+=l;
  111. }
  112. r+=bk;
  113. return r;
  114. }
  115. };
  116. inline ostream &operator<<(ostream &out, const ReportData &r){
  117. return out << r.ascii();
  118. }
  119. // NOTE: implementation at bottom.
  120. namespace cppdb {
  121. result &operator>>(result &qry, ::ReportData &rpt);
  122. }
  123. //////////////////////////////////////////////////////////////////////
  124. // Connection Report Generator Application Class
  125. //////////////////////////////////////////////////////////////////////
  126. struct appConnectionReport: TrafficMonBaseApp {
  127. ReportData rpt;
  128. string start_stamp;
  129. string end_stamp;
  130. int cli_mode; // which non-switch are we processing
  131. appConnectionReport(): cli_mode(0) { }
  132. int help() {
  133. cerr << " FORMAT: " << basename(command_args[0]) << " -c {config} {start} {end}\n"
  134. << '\n'
  135. << "The config file must have a [Traffic Mon] section with the database\n"
  136. << "credentials in it. 'start' and 'stop' are the SQL timestamps for\n"
  137. << "report time span." << endl;
  138. return 1;
  139. }
  140. virtual void do_arg(const char *arg) {
  141. switch(cli_mode++) {
  142. case 0: start_stamp = arg; return;
  143. case 1: end_stamp = arg; return;
  144. default: throw CLIerror("Invalid arguments");
  145. }
  146. }
  147. int main() {
  148. cppdb::result qry;
  149. int x;
  150. /// SETUP & VALIDATE CLI ///
  151. try {
  152. if(x = TrafficMonBaseApp::main()) return x; // Parse CLI args
  153. if(cli_mode!=2) throw CLIerror("Invlaid arguments");
  154. } catch(const CLIerror &e) {
  155. cerr << e.what() << "\n\n";
  156. return help();
  157. }
  158. /// Query & load data ///
  159. qry = db <<
  160. "SELECT c.them_name, c.them, c.them_port, count(*) "
  161. "FROM connections c LEFT OUTER JOIN dns d ON c.them_name=d.name "
  162. "WHERE c.inbound=0 AND (d.status IS NULL or d.status=0) "
  163. "AND tstamp>=? AND tstamp<=? "
  164. "GROUP BY c.them_name, c.them, c.them_port"
  165. << start_stamp << (end_stamp + " 23:59:59"); // include to the end of the day
  166. while(qry.next()) qry >> rpt;
  167. /// spit out the report ///
  168. cout << "Web access report for " << start_stamp << " - " << end_stamp << "\n\n"
  169. << rpt;
  170. return 0;
  171. }
  172. };
  173. //////////////////////////////////////////////////////////////////////
  174. // Lets run the report and dump it out
  175. //////////////////////////////////////////////////////////////////////
  176. MAIN(appConnectionReport)
  177. // NOTE: This needs to be down here so it knows of "app", defined by MAIN.
  178. namespace cppdb {
  179. result &operator>>(result &qry, ::ReportData &rpt) {
  180. Conn rec;
  181. int ct;
  182. rec.us="0.0.0.0";
  183. rec.protocol="TCP";
  184. rec.in=0;
  185. qry >> rec.name >> rec.them >> rec.them_port >> ct;
  186. // NOTE: ignores can only work from remote addresses.
  187. if(app.config->ignores.vals.find(rec)<0) {
  188. if(rec.name=="") rec.name=rec.them;
  189. rpt.add(rec.name, str(rec.them_port), ct);
  190. }
  191. }
  192. }