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.
 
 
 
 

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