Browse Source

Prepare for first PUSH to the 'Shack

* Add README.md
 * Reorder Makefile
 * Remove remarks that don't have any baring outside of here.
 * Add INI based conig file for defining local networks and ignores.
 * Break out data classes into their own module (needed for config).
 * Break out string utilities into their own module.
 * Remove "us" & "ignore" definitions from source.
master
Jon Foster 2 months ago
parent
commit
6a514559fa
10 changed files with 679 additions and 276 deletions
  1. +1
    -0
      .gitignore
  2. +14
    -3
      Makefile
  3. +162
    -0
      README.md
  4. +54
    -0
      config.cpp
  5. +27
    -0
      config.h
  6. +144
    -0
      data.cpp
  7. +68
    -0
      data.h
  8. +12
    -273
      iptraffic.cpp
  9. +93
    -0
      strutil.cpp
  10. +104
    -0
      strutil.h

+ 1
- 0
.gitignore View File

@@ -2,3 +2,4 @@
*.o
/iptraffic
/log
/README.html

+ 14
- 3
Makefile View File

@@ -1,10 +1,21 @@
iptraffic: iptraffic.cpp strutil.o data.o config.o
g++ -o $@ $@.cpp strutil.o data.o config.o

config.o: config.cpp config.h strutil.o data.o
g++ -c -o $@ config.cpp

data.o: data.cpp data.h strutil.o
g++ -c -o $@ data.cpp

strutil.o: strutil.cpp strutil.h
g++ -c -o $@ strutil.cpp



.PHONY: run
run: iptraffic
./iptraffic 2> log # 2>&1 | head -n 20

iptraffic: iptraffic.cpp
g++ -o $@ $@.cpp



.PHONY: clean distclean


+ 162
- 0
README.md View File

@@ -0,0 +1,162 @@
Poor Man's IDS
==============

### (Yes Women can use it too)

The goal of this project is to keep an eye on the requests going in
and out of my network onto the Internet (iNet). This is made
necessary for two reasons:

1. By looking for unusual activity I can get a heads up about
unwanted software or even "spy hardware" on my systems, ie.
"Detect Intrusions".

2. Almost all software now days, especially those created by gigabuck
giants, makes requests out onto the iNet that I did not ask for
and don't want happening. But even Mozilla makes network traffic I
didn't ask for and don't want.

So this tool is my way of "watching the watchers". This is not a
plug-n-play tool that _magically_ grants the user a "suit of
invulnerability". But it is a tool for those looking for more insight
into their iNet traffic either due to security concerns or curiosity.

This software is in the very early stages. Right now it just combines
data from logs from a couple of different software packages. I've
already setup many blocks for traffic I don't want happening, like
g00gle Analytics, some ad servers, ...

For the curious I'll post my current block lists in this repository
from time to time. But be **WARNED** that its likely to break your
iNet experience if you use them. I'm a cyber-rebel at heart and tend
to take an "if its doing something I don't want, I have no use for
it" approach. Meaning I'd rather not use a site / program if it
violates my concerns, rather than just "go with the flow". And I will
likely discover I'm breaking stuff I actually want, like: I realized
I've broken my ability to post comments on
[HaD](https://hackaday.com/), but I was curious about what "Server X"
did.

So! If you're not "faint of heart" come join me on my adventure in
iNet security exploration.



Phase 1 - General Setup & Operation
-----------------------------------

In general I feel its necessary to have a **real-world** idea of what
you're dealing with before diving in and writing software to deal
with what you _think_ your dealing with. So the basic plan is simple:

1. Setup my network routing devices, which are running Linux, to log
DNS queries and network connections. Specifically, anything that is
a _new_ unrelated packet gets logged.

2. Collect the logs on a machine, with backups on alternate machines.

3. Run the logs through a filter to combine the DNS query data with
the packet data.

4. Analyze results to determine phase 2 needs.

In this phase I'm jumping the gun a bit, but cyber-thugs are actively
beating on all of our doors, even as I'm getting my stuff together
and there are certain kinds of traffic I know I want to put an end
to. In that light I've already spotted interesting servers I'm
blocking. And I've beefed up my firewall to my mail server with more
permanent blocks from obvious MTA bashers.

In this phase I'm using the most excellent
"[dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html)" to log the
queries **and** block host names I don't want being accessed. I do
this by assigning the bogus address "127.0.0.255" to the names in my
server's "hosts" file, which is used by "dnsmasq" to answer DNS
queries. That address is a **valid** "localhost" address, so will
**immediately** fail requests unless you put a http(s) server. And
I'm sure you can imagine those possibilities.

The other source of information and block capabilities is "iptables"
/ "ip6tables". I added rules to log "new" packets. The parsing
software is what this repository is about right now.



Note on routers
---------------

I'm using Netgear appliances for WiFi and the first tier firewall
connected to my iNet connection. Since a lot of modern ISP connection
devices provide their own idea of security I have to turn off their
firewall stuff and configure them in a transparent bridging
configuration.

I use Netgear because they actively provide tool chains for select
models of their equipment and encourage people to load their own
software mods on them. Have a look at
[My Open Router](https://www.myopenrouter.com/). On my devices I
have settled on [Shibby's Tomato](https://tomato.groov.pl/). Its very
compact, extremely flexible and seems to have everything I've needed
when I've needed it. As an example my dnsmasq & iptables setup for my
gateway router required no changes to the firmware. I just put some
of the lesser used config pages to use.



The future
----------

The goal is an active alert system. This system should provide
immediate feedback on unknown connections allowing the user to either
grant or deny access and maintain the appropriate block lists.

But as "reality" exposes itself things are likely to change.



The CODE
--------

I'm using C++ to write this. I'm targeting C++98 at this time.
Although I think C++11 defines the minimum viable version of C++
there are places that its still not available, which is most likely
to happen with oddball tool chains like those provided by Netgear.
But even my RaspberryPI 2B doesn't support it. Well... maybe with an
OS upgrade... I'm hoping to support a very wide Linux platform
coverage.

I'm doing all of my development with Linux. It may be possible to do
something similar, maybe with very little modification, on BSD based
platforms. I don't have them and therefor won't be personally working
on it. But, unless it makes things really ugly, I'm not opposed to
contributions on that front.



Installation & use
------------------

This is still **VERY** crude and incomplete.

1. Configure the source: the basic setup is hardcoded in the source.
I'm just testing right now. ;-) There are three lines near the
bottom of "iptraffic.cpp" to be concerned with:

* `#define PATH "/srv/backups/iptraffic"`: this is the base path
to the work directory. Its prepended to other paths, read on.

* `ifstream log(PATH "/test.log");`: defines the log file to
process.

* `ofstream out(PATH "/processed.log");`: the file with the
combined data that's written out.

* `config.load(PATH "/iptraffic.conf");`: Path to a configuration
file that lists networks to consider as "us" and connections to
ignore, basically the set that is considered "OK".

2. Type "make" to compile. Hopefully it compiles for you.

3. Run "iptraffic".

4. Check the output.

+ 54
- 0
config.cpp View File

@@ -0,0 +1,54 @@
//////////////////////////////////////////////////////////////////////
// IP traffic analyzer - configuration file
// Written by Jonathan A. Foster <jon@jfpossibilities.com>
// Started May 14th, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//////////////////////////////////////////////////////////////////////
#include <fstream>
#include "config.h"
#include "strutil.h"



void Config::load(const std::string &fname) {
std::string l;
std::ifstream f(fname.c_str());
TSV tsv;
Conn conn;


while(std::getline(f, l)) {
l = strip(l);
if(l=="" || l[0]=='#') continue;
if(l.size()>2 && l[0]=='[' && l.end()[-1]==']') {

heading:
if(l=="[us]") {

/// Read in "us" list ///

while(std::getline(f, l)) {
l=strip(l);
if(l=="" || l[0]=='#') continue;
if(l.size()>2 && l[0]=='[' && l.end()[-1]==']') goto heading;
us.push_back(l);
}

} else if(l=="[ignores]") {

/// Read in ignore list ///

while(std::getline(f, l)) {
if(l=="" || l[0]=='#') continue;
if(l.size()>2 && l[0]=='[' && l.end()[-1]==']') goto heading;
tsv = l;
if(tsv.count>6) {
tsv >> conn;
ignores.push_back(conn);
}
}
}

}
}
}

+ 27
- 0
config.h View File

@@ -0,0 +1,27 @@
//////////////////////////////////////////////////////////////////////
// IP traffic analyzer - configuration file
// Written by Jonathan A. Foster <jon@jfpossibilities.com>
// Started May 14th, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//
// This class reads and holds configuration data for "iptraffic". The
// file is basically an INI format with '#' for remarks instead of ';'
// and sections can be lists, not just name/value pairs. In fact this
// first version is strictly only lists.
//////////////////////////////////////////////////////////////////////
#ifndef __JFP_IPTRAFFIC_CONF_H__
#define __JFP_IPTRAFFIC_CONF_H__
#include <string>
#include <vector>
#include "data.h"



struct Config {
std::vector<std::string> us;
ConnList ignores;

void load(const std::string &fname);
};

#endif

+ 144
- 0
data.cpp View File

@@ -0,0 +1,144 @@
//////////////////////////////////////////////////////////////////////
// IP traffic analyzer - data objects
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net>
// Started April 23rd, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//////////////////////////////////////////////////////////////////////
#include <string.h>
#include <stdlib.h>
#include <stdexcept>
#include "data.h"



//////////////////////////////////////////////////////////////////////
// Conn
//////////////////////////////////////////////////////////////////////

void Conn::clear() {
us = them = name = protocol = "";
in=false;
us_port = them_port = 0;
}



void Conn::swap() {
std::string s;
int x;

s = us;
us = them;
them =s;

x = us_port;
us_port = them_port;
them_port = x;

in=!in;
}



Conn &Conn::operator=(const Splits &sp) {
int x;

clear();
for(x=0; x<sp.count; x++) {
if(!strncmp(sp.fields[x], "SRC=", 4)) {
us = sp.fields[x]+4;
continue;
}
if(!strncmp(sp.fields[x], "DST=", 4)) {
them = sp.fields[x]+4;
continue;
}
if(!strncmp(sp.fields[x], "SPT=", 4)) {
us_port = atoi(sp.fields[x]+4);
continue;
}
if(!strncmp(sp.fields[x], "DPT=", 4)) {
them_port = atoi(sp.fields[x]+4);
continue;
}
if(!strncmp(sp.fields[x], "TYPE=", 5) && protocol=="ICMP") {
us_port = them_port = atoi(sp.fields[x]+5);
continue;
}
if(!strncmp(sp.fields[x], "PROTO=", 6))
protocol = sp.fields[x]+6;
}
}



// TODO: does < > have any actual meaning in this context?
int Conn::cmp(const Conn &gtr) const {
if(us!="*" && gtr.us!="*") {
if(us<gtr.us) return -1;
if(us>gtr.us) return 1;
}
// TODO: auto-wildcard port based on in?
if(us_port && gtr.us_port) { // 0 = no comparison wildcard
if(us_port<gtr.us_port) return -1;
if(us_port>gtr.us_port) return 1;
}
if(them!="*" && gtr.them!="*") {
if(them<gtr.them) return -1;
if(them>gtr.them) return 1;
}
if(them_port && gtr.them_port) { // 0 = no comparison wildcard
if(them_port<gtr.them_port) return -1;
if(them_port>gtr.them_port) return 1;
}
// TODO: do we want to consider the name?
if(name!="*" && gtr.name!="*") {
if(name<gtr.name) return -1;
if(name>gtr.name) return 1;
}
if(protocol<gtr.protocol) return -1;
if(protocol>gtr.protocol) return 1;
if(in<gtr.in) return -1;
if(in>gtr.in) return 1;
return 0;
}



std::ostream &operator<<(std::ostream &out, const Conn &c) {
out << c.us
<< ( c.in ? " <- " : " -> " )
<< c.them
<< " " << c.protocol
<< "[" << ( c.in ? c.us_port : c.them_port ) << "] "
<< c.name;
return out;
}



const Splits &operator>>(const Splits &tsv, Conn &conn) {
if(tsv.count<7) throw std::runtime_error("Conn=TSV: too few columns");
conn.clear();
conn.us = tsv[0];
conn.us_port = atoi(tsv.fields[1]);
conn.them = tsv[2];
conn.them_port = atoi(tsv.fields[3]);
conn.name = tsv[4];
conn.protocol = tsv[5];
conn.in = tsv[6]=="1";
return tsv;
}



//////////////////////////////////////////////////////////////////////
// ConnList
//////////////////////////////////////////////////////////////////////

int ConnList::find(Conn &needle) {
int r;

for(r=0; r<size(); r++) if((*this)[r]==needle) return r;
return -1;
}

+ 68
- 0
data.h View File

@@ -0,0 +1,68 @@
//////////////////////////////////////////////////////////////////////
// IP traffic analyzer - data objects
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net>
// Started April 23rd, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//
// This is useful for breaking a text file line into fields.
//
// 2021-05-14 <ChipMaster@YeOlPiShack.net>
// Restructure: broke out of monolithic iptraffic.cpp and made its
// own module.
//////////////////////////////////////////////////////////////////////
#ifndef __JFP_IPTRAFFIC_DATA_H__
#define __JFP_IPTRAFFIC_DATA_H__
#include <string>
#include <ostream>
#include <vector>
#include "strutil.h"



//////////////////////////////////////////////////////////////////////
// Network connection between "us" and "them"
//////////////////////////////////////////////////////////////////////
typedef unsigned short word;
struct Conn {
std::string us; // address on our side
word us_port; // the port on our side
std::string them; // address on their side
word them_port; // the port on their side
std::string name; // name of the address
std::string protocol; // protocol used to communicate
bool in; // whether this was an inward bound connection.

Conn(): us_port(0), them_port(0), in(false) {}
// clear data
void clear();
// swap polarity of record
void swap();
// scan & copy data from log record in
Conn &operator=(const Splits &sp);
// compare to another Conn
int cmp(const Conn &gtr) const;
inline bool operator<(const Conn &gtr) const { return cmp(gtr) <0; }
inline bool operator<=(const Conn &gtr) const { return cmp(gtr)<=0; }
inline bool operator>(const Conn &gtr) const { return cmp(gtr) >0; }
inline bool operator>=(const Conn &gtr) const { return cmp(gtr)>=0; }
inline bool operator==(const Conn &gtr) const { return cmp(gtr)==0; }
inline bool operator!=(const Conn &gtr) const { return cmp(gtr)!=0; }
};
// A text output of Conn
std::ostream &operator<<(std::ostream &out, const Conn &c);
// Copy data from Splits into Conn
const Splits &operator>>(const Splits &tsv, Conn &conn);



//////////////////////////////////////////////////////////////////////
// List of connections
//////////////////////////////////////////////////////////////////////

struct ConnList: public std::vector<Conn> {
int find(Conn &needle);
};



#endif

+ 12
- 273
iptraffic.cpp View File

@@ -14,271 +14,26 @@
//
// 2021-05-14 <ChipMaster@YeOlPiShack.net>
// Dumbed down for C++ < 11.
// Split into modules
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// Additional Router setup:
//
// ipset -N evilhosts iphash
// ipset -N evilnets nethash
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// Obvious ignores:
//
// 10.10.10.1 -> 134.215.160.1 ICMP[8]
//
//////////////////////////////////////////////////////////////////////
// TODO: map names according to time and host. time is probably automatic

#include <string.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <vector>
#include <map>
#include "strutil.h"
#include "data.h"
#include "config.h"
using namespace std;



//////////////////////////////////////////////////////////////////////
// Splits: a util class to devide a line into space sep pieces
//////////////////////////////////////////////////////////////////////
// TODO: implement begin() + end() to make "for( : )" work
// TODO: implement field enclosing & escaping chars

struct Splits {

/// CONFIG ///

enum { FieldMax=256, LineMax=1024 };

/// properties ///

char line[LineMax]; // Line buffer
int len; // Length of line (after split())
char sep; // Separator character.
bool combine; // Treat multiple consecutive seps as one (combine)
char *fields[FieldMax]; // pointers to fields in line
int count; // How many fields there were

// construct
Splits(): count(0), len(0), sep(' '), combine(true) { line[LineMax-1] = 0; }

// Convert field[] to string
inline string operator[](int i) const { string s(fields[i]); return s; }

// split line. Returns count.
int split() {
len = count = 0;
if(!*line) return count;
fields[0] = line;
while(len<LineMax && line[len]) {
if(line[len]==sep) {
line[len++]=0;
if(combine) while(len<LineMax && line[len]==sep) len++;
if(++count<FieldMax) {
// this shouldn't happen
if(len>=LineMax) throw
runtime_error("Splits::split: end of buffer null missing!");
fields[count] = line+len;
} else
throw runtime_error("Splits::split: Too many fields in the line");
} else
len++;
}
return count++;
}
};

// istream >> operator: getline() + .split()
istream &operator>>(istream &in, Splits &sp) {
if(in.getline(sp.line, sp.LineMax-1)) sp.split();
return in;
}



//////////////////////////////////////////////////////////////////////
// TSV version of Splits
//////////////////////////////////////////////////////////////////////

struct TSV: public Splits {
TSV() { sep='\t'; combine=false; }
};




//////////////////////////////////////////////////////////////////////
// Function to match a list of prefixes against a string
//
// Since C++ < 11 doesn't support constant vector initialization we'll
// do this the old fashioned way with a null terminated char*[].
//////////////////////////////////////////////////////////////////////

bool pre_match(char **list, const string &s) {
const char *p = s.c_str();
for(; *list; list++)
if(!strncmp(*list, p, strlen(*list))) return true;
return false;
}
/* And because I think this may be useful in the future I'll hang on to it.
bool pre_match(const vector<string> &list, const string &s) {
for(
vector<string>::const_iterator p=list.begin();
p!=list.end();
p++
)
if(s.substr(0, p->size())==*p) return true;
return false;
}*/


//////////////////////////////////////////////////////////////////////
// Connection between "us" and "them"
//////////////////////////////////////////////////////////////////////
typedef unsigned short word;
struct Conn {
string us; // address on our side
word us_port; // the port on our side
string them; // address on their side
word them_port; // the port on their side
string name; // name of the address
string protocol; // protocol used to communicate
bool in; // whether this was an inward bound connection.

Conn(): in(false) {}
Conn &clear() { us = them = name = protocol = ""; in=false; us_port = them_port = 0; }

// swap polarity of record
Conn &swap() {
string s;
int x;
s = us;
us = them;
them =s;
x = us_port;
us_port = them_port;
them_port = x;
in=!in;
return *this;
}

// scan & copy data from log record in
Conn &operator=(const Splits &sp) {
int x;
clear();
for(x=0; x<sp.count; x++) {
if(!strncmp(sp.fields[x], "SRC=", 4)) {
us = sp.fields[x]+4;
continue;
}
if(!strncmp(sp.fields[x], "DST=", 4)) {
them = sp.fields[x]+4;
continue;
}
if(!strncmp(sp.fields[x], "SPT=", 4)) {
us_port = atoi(sp.fields[x]+4);
continue;
}
if(!strncmp(sp.fields[x], "DPT=", 4)) {
them_port = atoi(sp.fields[x]+4);
continue;
}
if(!strncmp(sp.fields[x], "TYPE=", 5) && protocol=="ICMP") {
us_port = them_port = atoi(sp.fields[x]+5);
continue;
}
if(!strncmp(sp.fields[x], "PROTO=", 6))
protocol = sp.fields[x]+6;
}
}

// TODO: does < > have any actual meaning in this context?
int cmp(const Conn &gtr) const {
if(us!="*" && gtr.us!="*") {
if(us<gtr.us) return -1;
if(us>gtr.us) return 1;
}
// TODO: auto-wildcard port based on in?
if(us_port && gtr.us_port) { // 0 = no comparison wildcard
if(us_port<gtr.us_port) return -1;
if(us_port>gtr.us_port) return 1;
}
if(them!="*" && gtr.them!="*") {
if(them<gtr.them) return -1;
if(them>gtr.them) return 1;
}
if(them_port && gtr.them_port) { // 0 = no comparison wildcard
if(them_port<gtr.them_port) return -1;
if(them_port>gtr.them_port) return 1;
}
// TODO: do we want to consider the name?
if(name!="*" && gtr.name!="*") {
if(name<gtr.name) return -1;
if(name>gtr.name) return 1;
}
if(protocol<gtr.protocol) return -1;
if(protocol>gtr.protocol) return 1;
if(in<gtr.in) return -1;
if(in>gtr.in) return 1;
return 0;
}

inline bool operator<(const Conn &gtr) const { return cmp(gtr) <0; }
inline bool operator<=(const Conn &gtr) const { return cmp(gtr)<=0; }
inline bool operator>(const Conn &gtr) const { return cmp(gtr) >0; }
inline bool operator>=(const Conn &gtr) const { return cmp(gtr)>=0; }
inline bool operator==(const Conn &gtr) const { return cmp(gtr)==0; }
inline bool operator!=(const Conn &gtr) const { return cmp(gtr)!=0; }

};

// A text output of this record
ostream &operator<<(ostream &out, const Conn &c) {
out << c.us
<< ( c.in ? " <- " : " -> " )
<< c.them
<< " " << c.protocol
<< "[" << ( c.in ? c.us_port : c.them_port ) << "] "
<< c.name;
return out;
}

// Copy data from TSV in
const TSV &operator>>(const TSV &tsv, Conn &conn) {
if(tsv.count<7) throw runtime_error("Conn=TSV: too few columns");
conn.clear();
conn.us = tsv[0];
conn.us_port = atoi(tsv.fields[1]);
conn.them = tsv[2];
conn.them_port = atoi(tsv.fields[3]);
conn.name = tsv[4];
conn.protocol = tsv[5];
conn.in = tsv[6]=="1";
return tsv;
}



//////////////////////////////////////////////////////////////////////
// List of connections
//////////////////////////////////////////////////////////////////////

struct ConnList: public vector<Conn> {
int find(Conn &needle) {
int r;
for(r=0; r<size(); r++) if((*this)[r]==needle) return r;
return -1;
}
};



//////////////////////////////////////////////////////////////////////
// Busy indicator aka. "Live Bug"
//////////////////////////////////////////////////////////////////////

@@ -300,12 +55,9 @@ ostream &operator<<(ostream &o, LiveBug &bug) {
//////////////////////////////////////////////////////////////////////
//#define DEBUG

typedef map<string,string> NameVal;

// TODO: define us[] in conf file
char *us[] = { (char *)"10.10.10.", (char *)"192.168.255.", (char *)"2001:470:a:169:", 0 };
char *dns_ignore[] = { (char *)"v=spf1", (char *)"https:", 0 };
char *dns_del[] = { (char *)"NODATA-", (char *)"NXDOMAIN-", 0 };
// (char *) to silence warnings...
const char *dns_ignore[] = { (char *)"v=spf1", (char *)"https:", 0 };
const char *dns_del[] = { (char *)"NODATA-", (char *)"NXDOMAIN-", 0 };
#define PATH "/srv/backups/iptraffic"
ifstream log(PATH "/test.log");
ofstream out(PATH "/processed.log");
@@ -317,7 +69,7 @@ NameVal::iterator nvp;
string name, address, s;
Conn conn;
bool match;
ConnList ignores;
Config config;



@@ -331,22 +83,9 @@ void dlog(const string msg) {

int main(int argc, char **argv) {

/// Load lists ///

/// Load config ///


/// Read in ignore list ///

{
TSV tsv;
ifstream in(PATH "/ignores.lst");
while(in >> tsv) {
if(tsv.count>6) {
tsv >> conn;
ignores.push_back(conn);
}
}
}
config.load(PATH "/iptraffic.conf");

/// parse log file ///

@@ -384,10 +123,10 @@ int main(int argc, char **argv) {
&& ln[5]=="ACCEPT"
) {
conn = ln;
if(!pre_match(us, conn.us)) conn.swap();
if(!pre_match(config.us, conn.us)) conn.swap();
if((nvp=rdns.find(conn.them))!=rdns.end())
conn.name = nvp->second;
if(ignores.find(conn)<0)
if(config.ignores.find(conn)<0)
out << ln[0] << " " << ln[1] << " " << ln[2] << " " << conn << "\n";
else
ict++;


+ 93
- 0
strutil.cpp View File

@@ -0,0 +1,93 @@
//////////////////////////////////////////////////////////////////////
// String splitter
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net>
// Started April 23rd, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//////////////////////////////////////////////////////////////////////
#include <string.h>
#include <stdexcept>
// Sounds an awful lot like a German pastry
#include "strutil.h"



//////////////////////////////////////////////////////////////////////
// Splits
//////////////////////////////////////////////////////////////////////

Splits &Splits::operator=(const char *_line) {
if(strlen(_line)>=LineMax)
throw std::runtime_error("Splits::Splits(char*): string is longer than buffer");
strncpy(line, _line, LineMax-1);
split();
return *this;
}



int Splits::split() {
len = count = 0;
if(!*line) return count;
fields[0] = line;
while(len<LineMax && line[len]) {
if(line[len]==sep) {
line[len++]=0;
if(combine) while(len<LineMax && line[len]==sep) len++;
if(++count<FieldMax) {
// this shouldn't happen
if(len>=LineMax) throw
std::runtime_error("Splits::split: end of buffer null missing!");
fields[count] = line+len;
} else
throw std::runtime_error("Splits::split: Too many fields in the line");
} else
len++;
}
return count++;
}



std::istream &operator>>(std::istream &in, Splits &sp) {
if(in.getline(sp.line, sp.LineMax-1)) sp.split();
return in;
}



//////////////////////////////////////////////////////////////////////
// pre_match()
//////////////////////////////////////////////////////////////////////

bool pre_match(const char **list, const std::string &s) {
const char *p = s.c_str();
for(; *list; list++)
if(!strncmp(*list, p, strlen(*list))) return true;
return false;
}

// And if vectors can be used...
bool pre_match(const StringList &list, const std::string &s) {
for(
StringList::const_iterator p=list.begin();
p!=list.end();
p++
)
if(s.substr(0, p->size())==*p) return true;
return false;
}



//////////////////////////////////////////////////////////////////////
// misc
//////////////////////////////////////////////////////////////////////

std::string strip(const std::string &s) {
int x, y;

for(x=0; x<s.size() && s[x]<=' '; x++);
for(y=s.size()-1; y>=x && s[y]<=' '; y--);
if(y<x) return "";
return s.substr(x, y-x+1);
}

+ 104
- 0
strutil.h View File

@@ -0,0 +1,104 @@
//////////////////////////////////////////////////////////////////////
// String Utilities
// Written by Jonathan A. Foster <ChipMaster@YeOlPiShack.net>
// Started April 23rd, 2021
// Copyright JF Possibilities, Inc. All rights reserved.
//
// This is useful for breaking a text file line into fields.
//
// 2021-05-14 <ChipMaster@YeOlPiShack.net>
// Restructure: broke out of monolithic iptraffic.cpp and made its
// own module.
//////////////////////////////////////////////////////////////////////
#ifndef __JFP_STRUTIL_H__
#define __JFP_STRUTIL_H__
#include <string>
#include <vector>
#include <map>
#include <istream>



//////////////////////////////////////////////////////////////////////
// Useful typedefs
//////////////////////////////////////////////////////////////////////
typedef std::vector<std::string> StringList;
typedef std::map<std::string,std::string> NameVal;



//////////////////////////////////////////////////////////////////////
// Splits: a util class to divide a line into space sep pieces
//////////////////////////////////////////////////////////////////////
// TODO: implement begin() + end() to make "for( : )" work
// TODO: implement field enclosing & escaping chars

struct Splits {

/// CONFIG ///

enum { FieldMax=256, LineMax=1024 };

/// properties ///

char line[LineMax]; // Line buffer
int len; // Length of line (after split())
char sep; // Separator character.
bool combine; // Treat multiple consecutive seps as one (combine)
char *fields[FieldMax]; // pointers to fields in line
int count; // How many fields there were

// construct
Splits(): count(0), len(0), sep(' '), combine(true) { line[LineMax-1] = 0; }
Splits(const std::string &_line) { *this=_line; }

// Assign from string
inline Splits &operator=(const std::string &_line) { return *this=_line.c_str(); }
Splits &operator=(const char *_line);
// Convert field[] to string
inline std::string operator[](int i) const { std::string s(fields[i]); return s; }

// split line. Returns count.
int split();
};

// istream >> operator: getline() + .split()
std::istream &operator>>(std::istream &in, Splits &sp);



//////////////////////////////////////////////////////////////////////
// TSV version of Splits
//////////////////////////////////////////////////////////////////////
struct TSV: public Splits {
TSV() { sep='\t'; combine=false; }
// Need some weird casties to make C++ remember its base class
TSV(const std::string &_line): Splits(_line) {}
inline TSV &operator=(const std::string &_line) { return *this=_line.c_str(); }
inline TSV &operator=(const char *_line) { *((Splits *)this)=_line; return *this; }
};



//////////////////////////////////////////////////////////////////////
// Function to match a list of prefixes against a string
//
// Since C++ < 11 doesn't support constant vector initialization we'll
// do this the old fashioned way with a null terminated char*[].
//////////////////////////////////////////////////////////////////////

bool pre_match(const char **list, const std::string &s);

// And if vectors can be used...
bool pre_match(const StringList &list, const std::string &s);



//////////////////////////////////////////////////////////////////////
// Strip leading and trailing space from a string
//////////////////////////////////////////////////////////////////////

std::string strip(const std::string &s);


#endif

Loading…
Cancel
Save