From 6a514559fa4c40a868f1e30931cd764163ad5cf3 Mon Sep 17 00:00:00 2001 From: Jon Foster Date: Sat, 22 May 2021 12:53:37 -0700 Subject: [PATCH] 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. --- .gitignore | 1 + Makefile | 17 +++- README.md | 162 +++++++++++++++++++++++++++++++++ config.cpp | 54 +++++++++++ config.h | 27 ++++++ data.cpp | 144 +++++++++++++++++++++++++++++ data.h | 68 ++++++++++++++ iptraffic.cpp | 285 +++------------------------------------------------------- strutil.cpp | 93 +++++++++++++++++++ strutil.h | 104 +++++++++++++++++++++ 10 files changed, 679 insertions(+), 276 deletions(-) create mode 100644 README.md create mode 100644 config.cpp create mode 100644 config.h create mode 100644 data.cpp create mode 100644 data.h create mode 100644 strutil.cpp create mode 100644 strutil.h diff --git a/.gitignore b/.gitignore index ec9d440..911aeb4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.o /iptraffic /log +/README.html diff --git a/Makefile b/Makefile index 6ffb7d7..fec1f4b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7295b36 --- /dev/null +++ b/README.md @@ -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. diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..1f5ba22 --- /dev/null +++ b/config.cpp @@ -0,0 +1,54 @@ +////////////////////////////////////////////////////////////////////// +// IP traffic analyzer - configuration file +// Written by Jonathan A. Foster +// Started May 14th, 2021 +// Copyright JF Possibilities, Inc. All rights reserved. +////////////////////////////////////////////////////////////////////// +#include +#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); + } + } + } + + } + } +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..bfee5e1 --- /dev/null +++ b/config.h @@ -0,0 +1,27 @@ +////////////////////////////////////////////////////////////////////// +// IP traffic analyzer - configuration file +// Written by Jonathan A. Foster +// 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 +#include +#include "data.h" + + + +struct Config { + std::vector us; + ConnList ignores; + + void load(const std::string &fname); +}; + +#endif diff --git a/data.cpp b/data.cpp new file mode 100644 index 0000000..e5119e5 --- /dev/null +++ b/data.cpp @@ -0,0 +1,144 @@ +////////////////////////////////////////////////////////////////////// +// IP traffic analyzer - data objects +// Written by Jonathan A. Foster +// Started April 23rd, 2021 +// Copyright JF Possibilities, Inc. All rights reserved. +////////////////////////////////////////////////////////////////////// +#include +#include +#include +#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; xgtr.us) return 1; + } + // TODO: auto-wildcard port based on in? + if(us_port && gtr.us_port) { // 0 = no comparison wildcard + if(us_portgtr.us_port) return 1; + } + if(them!="*" && gtr.them!="*") { + if(themgtr.them) return 1; + } + if(them_port && gtr.them_port) { // 0 = no comparison wildcard + if(them_portgtr.them_port) return 1; + } + // TODO: do we want to consider the name? + if(name!="*" && gtr.name!="*") { + if(namegtr.name) return 1; + } + if(protocolgtr.protocol) return 1; + if(ingtr.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 +// 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 +// Restructure: broke out of monolithic iptraffic.cpp and made its +// own module. +////////////////////////////////////////////////////////////////////// +#ifndef __JFP_IPTRAFFIC_DATA_H__ +#define __JFP_IPTRAFFIC_DATA_H__ +#include +#include +#include +#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 >r) const; + inline bool operator<(const Conn >r) const { return cmp(gtr) <0; } + inline bool operator<=(const Conn >r) const { return cmp(gtr)<=0; } + inline bool operator>(const Conn >r) const { return cmp(gtr) >0; } + inline bool operator>=(const Conn >r) const { return cmp(gtr)>=0; } + inline bool operator==(const Conn >r) const { return cmp(gtr)==0; } + inline bool operator!=(const Conn >r) 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 { + int find(Conn &needle); +}; + + + +#endif diff --git a/iptraffic.cpp b/iptraffic.cpp index dee0d3b..77859da 100644 --- a/iptraffic.cpp +++ b/iptraffic.cpp @@ -14,271 +14,26 @@ // // 2021-05-14 // 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 -#include #include #include #include #include #include #include +#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) 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 &list, const string &s) { - for( - vector::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; xgtr.us) return 1; - } - // TODO: auto-wildcard port based on in? - if(us_port && gtr.us_port) { // 0 = no comparison wildcard - if(us_portgtr.us_port) return 1; - } - if(them!="*" && gtr.them!="*") { - if(themgtr.them) return 1; - } - if(them_port && gtr.them_port) { // 0 = no comparison wildcard - if(them_portgtr.them_port) return 1; - } - // TODO: do we want to consider the name? - if(name!="*" && gtr.name!="*") { - if(namegtr.name) return 1; - } - if(protocolgtr.protocol) return 1; - if(ingtr.in) return 1; - return 0; - } - - inline bool operator<(const Conn >r) const { return cmp(gtr) <0; } - inline bool operator<=(const Conn >r) const { return cmp(gtr)<=0; } - inline bool operator>(const Conn >r) const { return cmp(gtr) >0; } - inline bool operator>=(const Conn >r) const { return cmp(gtr)>=0; } - inline bool operator==(const Conn >r) const { return cmp(gtr)==0; } - inline bool operator!=(const Conn >r) 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 { - int find(Conn &needle) { - int r; - for(r=0; r 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++; diff --git a/strutil.cpp b/strutil.cpp new file mode 100644 index 0000000..db2258f --- /dev/null +++ b/strutil.cpp @@ -0,0 +1,93 @@ +////////////////////////////////////////////////////////////////////// +// String splitter +// Written by Jonathan A. Foster +// Started April 23rd, 2021 +// Copyright JF Possibilities, Inc. All rights reserved. +////////////////////////////////////////////////////////////////////// +#include +#include +// 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) 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=x && s[y]<=' '; y--); + if(y +// 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 +// Restructure: broke out of monolithic iptraffic.cpp and made its +// own module. +////////////////////////////////////////////////////////////////////// +#ifndef __JFP_STRUTIL_H__ +#define __JFP_STRUTIL_H__ +#include +#include +#include +#include + + + +////////////////////////////////////////////////////////////////////// +// Useful typedefs +////////////////////////////////////////////////////////////////////// +typedef std::vector StringList; +typedef std::map 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