From 5549c4d1885ea447cd3fe6b37df30d258aa5a864 Mon Sep 17 00:00:00 2001 From: john Date: Tue, 3 Dec 2019 21:38:51 -0500 Subject: [PATCH] Lots of new reporting features --- Jmakefile | 2 + ban2fail.c | 128 ++++++++++++++++++++--------------------- ban2fail.h | 32 ++++++++++- ez_libc.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++- ez_libc.h | 90 +++++++++++++++++++++++++++++ ez_libdb.c | 20 ++++++- ez_libdb.h | 10 ++++ iptables.c | 10 +++- logFile.c | 32 ++++++----- logType.c | 60 +++++++++++++------- logType.h | 13 ++--- offEntry.c | 94 +++++++++++++++++++++++++++--- offEntry.h | 24 ++++---- target.c | 140 +++++++++++++++++++++++++++++++++++++++++++++ target.h | 82 +++++++++++++++++++++++++++ timestamp.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++ timestamp.h | 71 +++++++++++++++++++++++ 17 files changed, 984 insertions(+), 130 deletions(-) create mode 100644 target.c create mode 100644 target.h create mode 100644 timestamp.c create mode 100644 timestamp.h diff --git a/Jmakefile b/Jmakefile index 608fe89..30e215a 100644 --- a/Jmakefile +++ b/Jmakefile @@ -32,6 +32,8 @@ src := \ pdns.c \ ptrvec.c \ str.c \ + target.c \ + timestamp.c \ util.c \ libs := z crypto GeoIP pthread db diff --git a/ban2fail.c b/ban2fail.c index ec30acd..a178389 100644 --- a/ban2fail.c +++ b/ban2fail.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "addrRpt.h" @@ -38,13 +39,6 @@ #include "str.h" #include "util.h" -enum { - BLOCKED_FLG =1<<0, - WOULD_BLOCK_FLG =1<<1, - UNJUST_BLOCK_FLG =1<<2, - WHITELIST_FLG =1<<3 -}; - /*==================================================================*/ /*=================== Support structs ==============================*/ /*==================================================================*/ @@ -67,6 +61,7 @@ static int addrRpt_serial_qsort(const void *p1, const void *p2); static int cntryStat_count_qsort(const void *p1, const void *p2); static int configure(CFGMAP *h_cfgmap, const char *pfix); static int logentry_count_qsort(const void *p1, const void *p2); +static int logentry_latest_qsort(const void *p1, const void *p2); static int map_byCountries(OFFENTRY *e, MAP *h_map); static int stub_init(CFGMAP *map, char *symStr); @@ -83,13 +78,21 @@ static const struct bitTuple GlobalFlagBitTuples[]= { }; struct Global G= { - .cacheDir= CACHEDIR, - .lockDir= LOCKDIR, + .cache= { + .dir= CACHEDIR, + .dir_mode=0770, + .file_mode=0660 + }, + .lock= { + .dir= LOCKDIR, + .dir_mode= 0770, + .file_mode=0660 + }, .version= { .major= 0, .minor= 13, - .patch= 3 + .patch= 4 }, .bitTuples.flags= GlobalFlagBitTuples @@ -101,14 +104,6 @@ const static struct initInfo S_initInfo_arr[] = { {/* Terminating member */} }; -static const struct bitTuple BlockBitTuples[]= { - {.name= "BLK", .bit= BLOCKED_FLG}, - {.name= "+blk+", .bit= WOULD_BLOCK_FLG}, - {.name= "-blk-", .bit= UNJUST_BLOCK_FLG}, - {.name= "WL", .bit= WHITELIST_FLG}, - {/* Terminating member */} -}; - /*================ Local only static struct ======================*/ static struct { @@ -169,6 +164,11 @@ main(int argc, char **argv) /* Prepare static data */ // global + struct group *gr= ez_getgrnam(GROUP_NAME); + G.gid= gr->gr_gid; + + /* Default sending listing to stdout */ + G.rpt.fh= stdout; MAP_constructor(&G.logType_map, 10, 10); MAP_constructor(&G.rpt.AddrRPT_map, 10, 10); @@ -232,8 +232,8 @@ main(int argc, char **argv) case 't': G.flags |= GLB_DONT_IPTABLE_FLG; - G.cacheDir= CACHEDIR "-test"; - G.lockDir= LOCKDIR "-test"; + G.cache.dir= CACHEDIR "-test"; + G.lock.dir= LOCKDIR "-test"; confFile= optarg; break; @@ -300,11 +300,13 @@ main(int argc, char **argv) } /* Done with command line arguments */ - /* Make sure we will be able to run iptables */ - if(getuid()) { - eprintf("ERROR: You must be root to run iptables!"); - goto abort; - } + /* So we can run iptables */ + ez_setuid(0); + ez_setgid(G.gid); + + /* Get a time when the scan began */ + G.begin.time_t= time(NULL); + G.begin.tm= *localtime(&G.begin.time_t); /* Read the configuration file */ { /*=========================================================*/ @@ -313,6 +315,9 @@ main(int argc, char **argv) goto abort; } + /* For debugging this can be useful */ +// CFGMAP_print(&S.cfgmap, G.rpt.fh); + /* Just leave the S.cfgmap in place, so all the value strings * don't need to be copied. */ @@ -321,21 +326,21 @@ main(int argc, char **argv) /* Obtain a file lock to protect cache files */ /*===========================================================*/ { - if(-1 == ez_access(G.lockDir, F_OK)) - ez_mkdir(G.lockDir, 0750); + if(-1 == ez_access(G.lock.dir, F_OK)) { + ez_mkdir(G.lock.dir, G.lock.dir_mode); + ez_chown(G.lock.dir, getuid(), G.gid); + } - snprintf(S.fnameBuf, sizeof(S.fnameBuf), "%s/cache", G.lockDir); + snprintf(S.fnameBuf, sizeof(S.fnameBuf), "%s/cache", G.lock.dir); /* Make sure the file exists by open()'ing */ - S.cacheLock_fd= ez_open(S.fnameBuf, O_CREAT|O_WRONLY|O_CLOEXEC, 0640); - assert(-1 != S.cacheLock_fd); + S.cacheLock_fd= ez_open(S.fnameBuf, O_CREAT|O_RDONLY|O_CLOEXEC, G.lock.file_mode); + ez_fchown(S.cacheLock_fd, getuid(), G.gid); /* Let's get a exclusive lock */ // TODO: set SIGALRM to knock us out of blocked wait? int rc= ez_flock(S.cacheLock_fd, LOCK_EX); } - /* Default sending listing to stdout */ - G.rpt.fh= stdout; #ifndef DEBUG /* if stdout is a tty, and listing is likely * to be long, then use $PAGER. @@ -349,16 +354,19 @@ main(int argc, char **argv) /* Open our cache, instance file-specific LOGTYPE objects */ { /*=============================================================*/ - if(G.flags & GLB_FLUSH_CACHE_FLG && !access(G.cacheDir, F_OK)) { - ez_rmdir_recursive(G.cacheDir); + if(G.flags & GLB_FLUSH_CACHE_FLG && + !ez_access(G.cache.dir, F_OK)) + { + ez_rmdir_recursive(G.cache.dir); } /* Make the directory if needed */ - if(access(G.cacheDir, F_OK)) { + if(ez_access(G.cache.dir, F_OK)) { /* errno will be set if access() fails */ errno= 0; - ez_mkdir(G.cacheDir, 0750); + ez_mkdir(G.cache.dir, G.cache.dir_mode); + ez_chown(G.cache.dir, getuid(), G.gid); } if(G.flags & GLB_LONG_LISTING_MASK) { @@ -398,7 +406,7 @@ main(int argc, char **argv) /* Check cache for logType directories not in our current map, and remove them */ { /*---------------------------------------------------------------------*/ - DIR *dir= ez_opendir(G.cacheDir); + DIR *dir= ez_opendir(G.cache.dir); struct dirent *entry; while((entry= ez_readdir(dir))) { @@ -412,7 +420,7 @@ main(int argc, char **argv) continue; /* Make the path with filename */ - snprintf(S.fnameBuf, sizeof(S.fnameBuf), "%s/%s", G.cacheDir, entry->d_name); + snprintf(S.fnameBuf, sizeof(S.fnameBuf), "%s/%s", G.cache.dir, entry->d_name); /* Remove unused directory & contents. */ ez_rmdir_recursive(S.fnameBuf); @@ -475,7 +483,7 @@ main(int argc, char **argv) assert(S.lePtrArr); MAP_fetchAllItems(&S.addr2logEntry_map, (void**)S.lePtrArr); - qsort(S.lePtrArr, nItems, sizeof(OFFENTRY*), logentry_count_qsort); + qsort(S.lePtrArr, nItems, sizeof(OFFENTRY*), logentry_latest_qsort); /* Special processing for DNS lookups */ if(G.flags & GLB_DNS_LOOKUP_FLG) { @@ -525,27 +533,7 @@ main(int argc, char **argv) if(G.flags & GLB_LIST_ADDR_FLG && !(G.flags & GLB_DNS_FILTER_BAD_FLG && e->dns.flags & PDNS_BAD_MASK)) { - - const static struct bitTuple dns_flagsArr[]= { - {.name= "~", .bit= PDNS_FWD_FAIL_FLG}, - {.name= "!!", .bit= PDNS_FWD_NONE_FLG}, - {.name= "NXDOMAIN", .bit= PDNS_NXDOMAIN_FLG}, - {.name= "SERVFAIL", .bit= PDNS_SERVFAIL_FLG}, - {} - }; - - const static char *dns_fmt= "%-15s\t%5u/%-4d offenses %s [%s] %s %s\n", - *fmt= "%-15s\t%5u/%-4d offenses %s [%s]\n"; - - ez_fprintf(G.rpt.fh, e->dns.flags ? dns_fmt : fmt - , e->addr - , e->count - , nAllowed - , e->cntry[0] ? e->cntry : "--" - , bits2str(flags, BlockBitTuples) - , e->dns.name ? e->dns.name : "" - , bits2str(e->dns.flags, dns_flagsArr) - ); + OFFENTRY_list(e, G.rpt.fh, flags, nAllowed); } } /*--- End of OFFENTRY processing ---*/ @@ -599,10 +587,10 @@ main(int argc, char **argv) if(!(G.flags & GLB_DONT_IPTABLE_FLG)) { if(n2Block || n2Unblock) { - snprintf(S.fnameBuf, sizeof(S.fnameBuf), "%s/iptables", G.lockDir); + snprintf(S.fnameBuf, sizeof(S.fnameBuf), "%s/iptables", G.lock.dir); /* Make sure the file exists by open()'ing */ - S.iptablesLock_fd= ez_open(S.fnameBuf, O_CREAT|O_WRONLY|O_CLOEXEC, 0640); - assert(-1 != S.iptablesLock_fd); + S.iptablesLock_fd= ez_open(S.fnameBuf, O_CREAT|O_WRONLY|O_CLOEXEC, G.lock.file_mode); + ez_fchown(S.iptablesLock_fd, getuid(), G.gid); /* Get an exclusive lock on the lockfile */ ez_flock(S.iptablesLock_fd, LOCK_EX); } @@ -684,6 +672,20 @@ abort: /*==================================================================*/ /*============== Supporting functions ==============================*/ /*==================================================================*/ +static int +logentry_latest_qsort(const void *p1, const void *p2) +/*************************************************************** + * qsort functor puts large counts on top. + */ +{ + const OFFENTRY *le1= *(const OFFENTRY *const*)p1, + *le2= *(const OFFENTRY *const*)p2; + + if(le1->latest > le2->latest) return -1; + if(le1->latest < le2->latest) return 1; + return logentry_count_qsort(p1, p2); +} + static int logentry_count_qsort(const void *p1, const void *p2) diff --git a/ban2fail.h b/ban2fail.h index 14922ee..b9a8765 100644 --- a/ban2fail.h +++ b/ban2fail.h @@ -26,6 +26,8 @@ #define _GNU_SOURCE #include #include +#include +#include #include "cfgmap.h" @@ -59,6 +61,7 @@ #define IP6TABLES "/usr/sbin/ip6tables" #define GEOIP_DB "/usr/share/GeoIP/GeoIP.dat" #define GEOIP6_DB "/usr/share/GeoIP/GeoIPv6.dat" +#define GROUP_NAME "adm" enum GlobalFlg_enum { GLB_VERBOSE_FLG =1<<0, @@ -74,6 +77,13 @@ enum GlobalFlg_enum { GLB_LONG_LISTING_MASK = GLB_LIST_CNTRY_FLG|GLB_LIST_ADDR_FLG }; +enum BlockedFlg_enum { + BLOCKED_FLG =1<<0, + WOULD_BLOCK_FLG =1<<1, + UNJUST_BLOCK_FLG =1<<2, + WHITELIST_FLG =1<<3 +}; + /* Singleton static object with global visibility */ extern struct Global { @@ -81,8 +91,21 @@ extern struct Global { MAP logType_map; - char *cacheDir, - *lockDir; + struct { + char *dir; + mode_t dir_mode, + file_mode; + } cache; + + struct { + char *dir; + mode_t dir_mode, + file_mode; + } lock; + + /* This should be set to adm */ + gid_t gid; + struct { FILE *fh; @@ -99,6 +122,11 @@ extern struct Global { const struct bitTuple *flags; } bitTuples; + struct { + time_t time_t; + struct tm tm; + } begin; + } G; diff --git a/ez_libc.c b/ez_libc.c index 52a6e67..a6f38d5 100644 --- a/ez_libc.c +++ b/ez_libc.c @@ -92,8 +92,9 @@ FILE* _ez_popen ( const char *type ) { + errno= 0; FILE *rtn= popen (command, type); - if (!rtn) { + if (!rtn || errno) { _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "popen(\"%s\", \"%s\") failed", command, type); abort(); } @@ -524,6 +525,7 @@ int _ez_open( } +/***************************************************/ int _ez_access( const char *fileName, int lineNo, @@ -547,3 +549,159 @@ int _ez_access( } +/***************************************************/ +char *_ez_strptime( + const char *fileName, + int lineNo, + const char *funcName, + const char *s, + const char *format, + struct tm *tm + ) +{ + char *rtn= strptime (s, format, tm); + if(rtn) return rtn; + + _eprintf(fileName, lineNo, funcName, "strptime(\"%s\",\"%s\") failed", s, format); + abort(); + +} + +/***************************************************/ +int _ez_seteuid( + const char *fileName, + int lineNo, + const char *funcName, + uid_t euid + ) +{ + int rtn= seteuid (euid); + if(0 == rtn) return 0; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "seteuid(%d) failed", (int)euid); + abort(); +} + +/***************************************************/ +int _ez_setegid( + const char *fileName, + int lineNo, + const char *funcName, + gid_t egid + ) +{ + int rtn= setegid (egid); + if(0 == rtn) return 0; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "setegid(%d) failed", (int)egid); + abort(); +} + +/***************************************************/ +struct group* _ez_getgrnam( + const char *fileName, + int lineNo, + const char *funcName, + const char *name + ) +{ + errno= 0; + struct group *rtn= getgrnam (name); + + if(rtn) return rtn; + + switch(errno) { + case EINTR: + case EIO: + case EMFILE: + case ENFILE: + case ENOMEM: + case ERANGE: + return NULL; + + } + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "getgrnam(\"%s\") failed", name); + abort(); + +} + +/***************************************************/ +int _ez_chown( + const char *fileName, + int lineNo, + const char *funcName, + const char *pathname, + uid_t owner, + gid_t group + ) +{ + int rtn= chown (pathname, owner, group); + if(0 == rtn) return rtn; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "chown(\"%s\", %d, %d) failed", pathname, (int)owner, (int)group); + abort(); +} + +/***************************************************/ +int _ez_fchown( + const char *fileName, + int lineNo, + const char *funcName, + int fd, + uid_t owner, + gid_t group + ) +{ + int rtn= fchown (fd, owner, group); + if(0 == rtn) return rtn; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "fchown() failed"); + abort(); +} + +/***************************************************/ +int _ez_fchmod( + const char *fileName, + int lineNo, + const char *funcName, + int fd, + mode_t mode + ) +{ + int rtn= fchmod (fd, mode); + if(0 == rtn) return rtn; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "fchmod() failed"); + abort(); +} + +/***************************************************/ +int _ez_setuid( + const char *fileName, + int lineNo, + const char *funcName, + uid_t uid + ) +{ + int rtn= setuid (uid); + if(0 == rtn) return 0; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "setuid(%d) failed", (int)uid); + abort(); +} + +/***************************************************/ +int _ez_setgid( + const char *fileName, + int lineNo, + const char *funcName, + gid_t gid + ) +{ + int rtn= setgid (gid); + if(0 == rtn) return 0; + + _sys_eprintf((const char*(*)(int))strerror, fileName, lineNo, funcName, "setgid(%d) failed", (int)gid); + abort(); +} diff --git a/ez_libc.h b/ez_libc.h index 385627d..612cb76 100644 --- a/ez_libc.h +++ b/ez_libc.h @@ -30,17 +30,30 @@ glibc calls with boilerplate error handling. #define _GNU_SOURCE #include +#include #include #include #include #include #include #include +#include #ifdef __cplusplus extern "C" { #endif +#define ez_strptime(s, format, tm) \ + _ez_strptime(__FILE__, __LINE__, __FUNCTION__, s, format, tm) +char *_ez_strptime( + const char *fileName, + int lineNo, + const char *funcName, + const char *s, + const char *format, + struct tm *tm + ); + #define ez_access(pathname, mode) \ _ez_access(__FILE__, __LINE__, __FUNCTION__, pathname, mode) int _ez_access( @@ -341,6 +354,83 @@ int _ez_flock ( int operation ); +#define ez_setuid(uid) \ + _ez_setuid(__FILE__, __LINE__, __FUNCTION__, uid) +int _ez_setuid( + const char *fileName, + int lineNo, + const char *funcName, + uid_t uid + ); + +#define ez_setgid(gid) \ + _ez_setgid(__FILE__, __LINE__, __FUNCTION__, gid) +int _ez_setgid( + const char *fileName, + int lineNo, + const char *funcName, + gid_t gid + ); + + +#define ez_seteuid(euid) \ + _ez_seteuid(__FILE__, __LINE__, __FUNCTION__, euid) +int _ez_seteuid( + const char *fileName, + int lineNo, + const char *funcName, + uid_t euid + ); + +#define ez_setegid(egid) \ + _ez_setegid(__FILE__, __LINE__, __FUNCTION__, egid) +int _ez_setegid( + const char *fileName, + int lineNo, + const char *funcName, + gid_t egid + ); + +#define ez_getgrnam(name) \ + _ez_getgrnam(__FILE__, __LINE__, __FUNCTION__, name) +struct group* _ez_getgrnam( + const char *fileName, + int lineNo, + const char *funcName, + const char *name + ); + +#define ez_chown(pathname, owner, group) \ + _ez_chown(__FILE__, __LINE__, __FUNCTION__, pathname, owner, group) +int _ez_chown( + const char *fileName, + int lineNo, + const char *funcName, + const char *pathname, + uid_t owner, + gid_t group + ); + +#define ez_fchown(fd, owner, group) \ + _ez_fchown(__FILE__, __LINE__, __FUNCTION__, fd, owner, group) +int _ez_fchown( + const char *fileName, + int lineNo, + const char *funcName, + int fd, + uid_t owner, + gid_t group + ); + +#define ez_fchmod(fd, mode) \ + _ez_fchmod(__FILE__, __LINE__, __FUNCTION__, fd, mode) +int _ez_fchmod( + const char *fileName, + int lineNo, + const char *funcName, + int fd, + mode_t mode + ); #ifdef __cplusplus } diff --git a/ez_libdb.c b/ez_libdb.c index 8439c38..d795e4c 100644 --- a/ez_libdb.c +++ b/ez_libdb.c @@ -145,7 +145,6 @@ int _ez_db_close( u_int32_t flags ) { -//eprintf("Closing database (%p)", db); int rtn= db->close(db, flags); if(!rtn) return 0; @@ -155,3 +154,22 @@ int _ez_db_close( _sys_eprintf((const char*(*)(int))db_strerror, fileName, lineNo, funcName, "DB->close() failed"); abort(); } + +/***************************************************/ +int _ez_db_fd( + const char *fileName, + int lineNo, + const char *funcName, + DB *db, + int *fdp + ) +{ + int rtn= db->fd(db, fdp); + + if(!rtn) return 0; + + /* _sys_eprintf() will pass errno to db_sterror */ + errno= rtn; + _sys_eprintf((const char*(*)(int))db_strerror, fileName, lineNo, funcName, "DB->fd() failed"); + abort(); +} diff --git a/ez_libdb.h b/ez_libdb.h index ff90f0d..e7aedd4 100644 --- a/ez_libdb.h +++ b/ez_libdb.h @@ -101,6 +101,16 @@ int _ez_db_close( u_int32_t flags ); +#define ez_db_fd(db, fdp) \ + _ez_db_fd(__FILE__, __LINE__, __FUNCTION__, db, fdp) +int _ez_db_fd( + const char *fileName, + int lineNo, + const char *funcName, + DB *db, + int *fdp + ); + #ifdef __cplusplus } #endif diff --git a/iptables.c b/iptables.c index 9c819f7..90c0ae3 100644 --- a/iptables.c +++ b/iptables.c @@ -76,7 +76,9 @@ initialize (void) } fh= ez_popen(ipv->cmd, "r"); - +#ifdef qqDEBUG + unsigned count= 0; +#endif while(ez_fgets(lbuf, sizeof(lbuf)-1, fh)) { /* Filter all that looks uninteresting */ @@ -93,9 +95,15 @@ initialize (void) eprintf("WARNING: duplicate iptable entry for %s", addr); else MAP_addStrKey(&S.addr_map, addr, strdup(addr)); +#ifdef qqDEBUG + ++count; +#endif } ez_pclose(fh); regfree(&re); +#ifdef qqDEBUG +eprintf("%s got %u entries", ipv->cmd, count); +#endif } } diff --git a/logFile.c b/logFile.c index a194c40..49901c3 100644 --- a/logFile.c +++ b/logFile.c @@ -81,7 +81,7 @@ LOGFILE_cache_constructor( static char dbFname[PATH_MAX]; snprintf(dbFname, sizeof(dbFname), "%s.db", cacheFname); - if(0 == access(dbFname, F_OK)) { + if(0 == ez_access(dbFname, F_OK)) { ez_db_create(&db, NULL, 0); ez_db_open(db, NULL, dbFname, NULL, DB_BTREE, DB_RDONLY, 0664); } @@ -157,18 +157,10 @@ LOGFILE_log_constructor( lbuf[--line_len]= '\0'; /* Search for a match in the targets */ - for(struct target *tg= h_protoType->targetArr; tg->pattern; ++tg) { - - /* If there is no match, continue looking */ - regmatch_t matchArr[2]; - if(0 != regexec(&tg->re, lbuf, 2, matchArr, 0) || -1 == matchArr[1].rm_so) - continue; + for(Target *tg= h_protoType->targetArr; Target_is_init(tg); ++tg) { static char addr[128]; - unsigned len; - len= matchArr[1].rm_eo - matchArr[1].rm_so; - strncpy(addr, lbuf+matchArr[1].rm_so, sizeof(addr)-1); - addr[MIN(len, sizeof(addr)-1)]= '\0'; + if(Target_scan(tg, addr, sizeof(addr), lbuf)) continue; OFFENTRY *e= MAP_findStrItem(&self->addr.offEntry_map, addr); if(!e) { @@ -179,7 +171,15 @@ LOGFILE_log_constructor( MAP_addStrKey(&self->addr.offEntry_map, e->addr, e); } - OFFENTRY_register(e); + /* See if we can extract the date */ + time_t when= 0; + if(TS_is_prepared(&h_protoType->ts)) { + if(TS_scan(&h_protoType->ts, &when, lbuf, &G.begin.tm)) { + eprintf("WARNING: TS_scan() failed."); + } + } + + OFFENTRY_register(e, Target_severity(tg), when); { /* Keep ObsvTpl record of offense line in log file */ ObsvTpl *ot= MAP_findStrItem(&self->addr.obsvTpl_map, e->addr); @@ -190,9 +190,8 @@ LOGFILE_log_constructor( } ObsvTpl_addObsv(ot, pos, line_len); - } - } + } /* End of Target loop */ } /* End of line reading loop */ /* Take car of possible address reporting */ @@ -244,6 +243,8 @@ LOGFILE_writeCache(LOGFILE *self, const char *fname) DB *dbh= NULL; FILE *fh= ez_fopen(fname, "w"); + ez_fchown(fileno(fh), getuid(), G.gid); + ez_fchmod(fileno(fh), G.cache.file_mode); /* Writes all OFFENTRY object to fh */ rc= MAP_visitAllEntries(&self->addr.offEntry_map, (int(*)(void*,void*))OFFENTRY_cacheWrite, fh); @@ -258,7 +259,8 @@ LOGFILE_writeCache(LOGFILE *self, const char *fname) ez_db_create(&dbh, NULL, 0); assert(dbh); - ez_db_open(dbh, NULL, dbFname, NULL, DB_BTREE, DB_CREATE, 0664); + ez_db_open(dbh, NULL, dbFname, NULL, DB_BTREE, DB_CREATE, G.cache.file_mode); + ez_chown(dbFname, getuid(), G.gid); /* This will write all entries in the map do the database */ MAP_visitAllEntries(&self->addr.obsvTpl_map, (int(*)(void*,void*))ObsvTpl_db_put, dbh); diff --git a/logType.c b/logType.c index 4fcfd28..ac36022 100644 --- a/logType.c +++ b/logType.c @@ -118,9 +118,9 @@ LOGTYPE_proto_constructor(LOGTYPE *self, const struct logProtoType *proto) { /* Compute md5sum of all patterns put together */ MD5_CTX md5ctx; MD5_Init(&md5ctx); - const struct target *t; - for(t= proto->targetArr; t->pattern; ++t) { - MD5_Update(&md5ctx, t->pattern, strlen(t->pattern)); + const Target *t; + for(t= proto->targetArr; t->rxArr; ++t) { + Target_MD5_update(t, &md5ctx); } static unsigned char sum[16]; MD5_Final(sum, &md5ctx); @@ -131,7 +131,7 @@ LOGTYPE_proto_constructor(LOGTYPE *self, const struct logProtoType *proto) /* At this point we can call LOGTYPE_cacheName() */ /* Keep the name of this logType's cache directory */ - rc= snprintf(CacheDname, sizeof(CacheDname), "%s/%s", G.cacheDir, LOGTYPE_cacheName(self)); + rc= snprintf(CacheDname, sizeof(CacheDname), "%s/%s", G.cache.dir, LOGTYPE_cacheName(self)); { /*** Compute md5sum for each log file, then scan of read from cache ***/ @@ -206,7 +206,7 @@ LOGTYPE_proto_constructor(LOGTYPE *self, const struct logProtoType *proto) LOGFILE *f; /* Use the cache, if available */ - if(!access(CacheFname, F_OK)) + if(!ez_access(CacheFname, F_OK)) { /* Construct object from cache file */ @@ -215,8 +215,9 @@ LOGTYPE_proto_constructor(LOGTYPE *self, const struct logProtoType *proto) } else { /* Scan the log file, write to new cache */ - if(access(CacheDname, F_OK)) { - ez_mkdir(CacheDname, 0770); + if(ez_access(CacheDname, F_OK)) { + ez_mkdir(CacheDname, G.cache.dir_mode); + ez_chown(CacheDname, getuid(), G.gid); } /* Construct object from log file */ @@ -328,26 +329,43 @@ LOGTYPE_init(CFGMAP *h_map, char *pfix) } } - { /*--- Get all regex entries ---*/ - snprintf(symBuf, len, "%s\\REGEX", pfix); + { /*--- Get the TIMESTAMP entry ---*/ + snprintf(symBuf, len, "%s\\TIMESTAMP", pfix); + const char *val= CFGMAP_find_last_value(h_map, symBuf); + + if(val) { + + snprintf(symBuf, len, "%s\\%s", pfix, val); + + if(TS_init(&proto.ts, h_map, symBuf)) { + eprintf("ERROR: TS_init() failed."); + goto abort; + } + } + } + + + { /*--- Get all TARGET entries ---*/ + + snprintf(symBuf, len, "%s\\TARGET", pfix); unsigned nFound= CFGMAP_find_tuples(h_map, rtn_arr, symBuf); /* Get enough object to include a terminating entry */ - struct target targetArr[nFound+1]; + Target targetArr[nFound+1]; /* Clear all bits in array */ - memset(targetArr, 0, sizeof(struct target)*(nFound+1)); + memset(targetArr, 0, sizeof(Target)*(nFound+1)); proto.targetArr= targetArr; for(unsigned i= 0; i < nFound; ++i) { const struct CFGMAP_tuple *tpl= rtn_arr + i; - struct target *tg= targetArr + i; - tg->pattern= tpl->value; - if(regex_compile(&tg->re, tg->pattern, REG_EXTENDED)) { - eprintf("ERROR: regex_compile(\"%s\") failed.", tg->pattern); + Target *tg= targetArr + i; + snprintf(symBuf, len, "%s\\%s", pfix, tpl->value); + if(Target_init(tg, h_map, symBuf)) { + eprintf("ERROR: Target_init() failed."); goto abort; - } + } } /* Create the LOGTYPE object, place in global map */ @@ -362,13 +380,17 @@ LOGTYPE_init(CFGMAP *h_map, char *pfix) /* Place int the global map */ MAP_addStrKey(&G.logType_map, LOGTYPE_cacheName(obj), obj); - /* Free regex pattern data */ + /* Free regex stuff */ for(unsigned i= 0; i < nFound; ++i) { - struct target *tg= targetArr + i; - regfree(&tg->re); + Target *tg= targetArr + i; + Target_destructor(tg); } } + if(TS_is_prepared(&proto.ts)) { + TS_destructor(&proto.ts); + } + rtn= 0; abort: return rtn; diff --git a/logType.h b/logType.h index fb22268..58b99ff 100644 --- a/logType.h +++ b/logType.h @@ -24,14 +24,8 @@ #include "ban2fail.h" #include "cfgmap.h" #include "map.h" - -/*==================================================================*/ -/*===================== target =====================================*/ -/*==================================================================*/ -struct target { - const char *pattern; - regex_t re; -}; +#include "target.h" +#include "timestamp.h" /*==================================================================*/ /*===================== log file prototype =========================*/ @@ -39,7 +33,8 @@ struct target { struct logProtoType { const char *dir, *pfix; - struct target *targetArr; + TS ts; /* For log entry timestamps */ + Target *targetArr; }; /*==================================================================*/ diff --git a/offEntry.c b/offEntry.c index 86aa339..00a691c 100644 --- a/offEntry.c +++ b/offEntry.c @@ -74,16 +74,29 @@ OFFENTRY_cache_constructor(OFFENTRY *self, const char *cacheFileEntry) common_constructor(self); - int rc= sscanf(cacheFileEntry, "%u %45s %2s" - ,&self->count - ,self->addr - ,self->cntry); + long long ll; - if(2 > rc) { + int rc= sscanf(cacheFileEntry, "%u %u %lld %45s %2s" + , &self->count + , &self->severity + , &ll + , self->addr + , self->cntry + ); + + if(4 > rc) { eprintf("ERROR: failed to interpret \"%s\"", cacheFileEntry); goto abort; } + self->latest= ll; + +#ifdef qqDEBUG + if(self->severity) { +eprintf("%s : %u", self->addr, self->severity); + } +#endif + rtn= self; abort: return rtn; @@ -102,13 +115,28 @@ OFFENTRY_destructor(OFFENTRY *self) } void -OFFENTRY_register(OFFENTRY *self) +OFFENTRY_register(OFFENTRY *self, unsigned severity, time_t when) /******************************************************** * Register the current failure try. */ { /* Keep track of count */ ++self->count; + +#ifdef qqDEBUG + if(severity) { + eprintf("Severity= %u", severity); + } +#endif + + /* Keep track of most severe match */ + if(self->severity < severity) + self->severity= severity; + + /* Keep track of the most recent offense time */ + if(self->latest < when) + self->latest= when; + } @@ -118,8 +146,10 @@ OFFENTRY_cacheWrite(OFFENTRY *self, FILE *fh) * Write to the cache file in a form we can read later. */ { - ez_fprintf(fh, "%u %s %s\n" + ez_fprintf(fh, "%u %u %lld %s %s\n" , self->count + , self->severity + , (long long)self->latest , self->addr , self->cntry ); @@ -133,11 +163,12 @@ OFFENTRY_print(OFFENTRY *self, FILE *fh) */ { ez_fprintf(fh, -"\tLOGENTRY %p { addr= \"%s\", cntry= \"%2s\" count= %u }\n" +"\tLOGENTRY %p { addr= \"%s\", cntry= \"%2s\" count= %u, severity= %u }\n" , self , self->addr , self->cntry , self->count + , self->severity ); return 0; } @@ -158,6 +189,12 @@ OFFENTRY_map_addr(OFFENTRY *self, MAP *h_rtnMap) } e->count += self->count; + + if(e->severity < self->severity) + e->severity= self->severity; + + if(e->latest < self->latest) + e->latest= self->latest; return 0; } @@ -171,3 +208,44 @@ OFFENTRY_offenseCount(OFFENTRY *self, unsigned *h_sum) //eprintf("%s numItems= %u", self->addr, PTRVEC_numItems(&self->rptObj_vec)); return 0; } + +int +OFFENTRY_list(OFFENTRY *self, FILE *fh, int flags, unsigned nAllowed) +/******************************************************** + * Print in listing form + */ +{ + static const struct bitTuple BlockBitTuples[]= { + {.name= "BLK", .bit= BLOCKED_FLG}, + {.name= "+blk+", .bit= WOULD_BLOCK_FLG}, + {.name= "-blk-", .bit= UNJUST_BLOCK_FLG}, + {.name= "WL", .bit= WHITELIST_FLG}, + {/* Terminating member */} + }; + + const static struct bitTuple dns_flagsArr[]= { + {.name= "~", .bit= PDNS_FWD_FAIL_FLG}, + {.name= "!!", .bit= PDNS_FWD_NONE_FLG}, + {.name= "NXDOMAIN", .bit= PDNS_NXDOMAIN_FLG}, + {.name= "SERVFAIL", .bit= PDNS_SERVFAIL_FLG}, + {} + }; + + + const static char *dns_fmt= "%u %-13s %-15s\t%5u/%-4d offenses %s [%s] %s %s\n", + *fmt= "%u %-13s %-15s\t%5u/%-4d offenses %s [%s]\n"; + + ez_fprintf(fh, self->dns.flags ? dns_fmt : fmt + , self->severity + , self->latest ? local_strftime(&self->latest, "%b %d %H:%M") : "" + , self->addr + , self->count + , nAllowed + , self->cntry[0] ? self->cntry : "--" + , bits2str(flags, BlockBitTuples) + , self->dns.name ? self->dns.name : "" + , bits2str(self->dns.flags, dns_flagsArr) + ); + + return 0; +} diff --git a/offEntry.h b/offEntry.h index 75cc02c..8027eb7 100644 --- a/offEntry.h +++ b/offEntry.h @@ -29,10 +29,14 @@ /* One of these for each offense found in a log file */ typedef struct _OFFENTRY { - unsigned logfile_ndx; + + unsigned count, + severity; + + time_t latest; + char addr[46], cntry[3]; - unsigned count; /* This data populated by PDNS_lookup() */ struct { @@ -78,19 +82,11 @@ OFFENTRY_destructor(OFFENTRY *self); */ void -OFFENTRY_register(OFFENTRY *self); +OFFENTRY_register(OFFENTRY *self, unsigned severity, time_t when); /******************************************************** * Register the current failure try. */ -#if 0 -int -OFFENTRY_is_blocked_country(const OFFENTRY *self); -/******************************************************** - * Return 1 if the country is blocked, or 0. - */ -#endif - int OFFENTRY_cacheWrite(OFFENTRY *self, FILE *fh); /******************************************************** @@ -103,6 +99,12 @@ OFFENTRY_print(OFFENTRY *self, FILE *fh); * Print a human readable representation of *self. */ +int +OFFENTRY_list(OFFENTRY *self, FILE *fh, int flags, unsigned nAllowed); +/******************************************************** + * Print in listing form + */ + int OFFENTRY_map_addr(OFFENTRY *self, MAP *h_rtnMap); /******************************************************** diff --git a/target.c b/target.c new file mode 100644 index 0000000..fa2cc9a --- /dev/null +++ b/target.c @@ -0,0 +1,140 @@ +/*************************************************************************** + * Copyright (C) 2019 by John D. Robertson * + * john@rrci.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define _GNU_SOURCE +#include +#include +#include + +#include "target.h" +#include "util.h" + +int +Target_init(Target *self, CFGMAP *h_map, const char *pfix) +/******************************************************** + * Prepare for use from config map + */ +{ + int rtn= -1; + memset(self, 0, sizeof(*self)); + + self->flags |= TARGET_INIT_FLG; + + unsigned arr_sz= CFGMAP_numTuples(h_map); + struct CFGMAP_tuple rtn_arr[arr_sz]; + + size_t len= strlen(pfix)+1024; + char symBuf[len]; + + { /*--- Check for "SEVERITY" symbol ---*/ + snprintf(symBuf, len, "%s\\SEVERITY", pfix); + + if(CFGMAP_query_last_uint(h_map, &self->severity, 0, symBuf)) { + eprintf("ERROR: cannot interpret \"SEVERITY\" entry for REGEX %s", pfix); + goto abort; + } +#ifdef qqDEBUG +eprintf("%s = %u", symBuf, self->severity); +#endif + } + + { /*--- Get all REGEX entries ---*/ + snprintf(symBuf, len, "%s\\REGEX", pfix); + self->nRx= CFGMAP_find_tuples(h_map, rtn_arr, symBuf); + + self->rxArr= calloc(self->nRx, sizeof(struct TargetRx)); + assert(self->rxArr); + + for(unsigned i= 0; i < self->nRx; ++i) { + + const struct CFGMAP_tuple *tpl= rtn_arr + i; + struct TargetRx *rx= self->rxArr + i; + + /* Compile regular expression */ + rx->pattern= tpl->value; + if(regex_compile(&rx->re, rx->pattern, REG_EXTENDED)) { + eprintf("ERROR: regex_compile(\"%s\") failed.", rx->pattern); + goto abort; + } + } + } + + rtn= 0; +abort: + return rtn; +} + +void* +Target_destructor(Target *self) +/******************************************************** + * Free resources. + */ +{ + if(self->nRx && self->rxArr) { + for(unsigned i= 0; i < self->nRx; ++i) { + struct TargetRx *rx= self->rxArr + i; + regfree(&rx->re); + } + free(self->rxArr); + } + + return self; +} + +int +Target_scan(const Target *self, char *rsltBuf, unsigned buf_sz, const char *str) +/******************************************************** + * Scan a string to obtain the address. + */ +{ + int rtn= EOF; + + /* Exit on first match */ + for(unsigned i= 0; i < self->nRx; ++i) { + + const struct TargetRx *rx= self->rxArr+i; + + regmatch_t matchArr[2]; + if(0 != regexec(&rx->re, str, 2, matchArr, 0) || -1 == matchArr[1].rm_so) + continue; + + unsigned len= matchArr[1].rm_eo - matchArr[1].rm_so; + + strncpy(rsltBuf, str+matchArr[1].rm_so, buf_sz-1); + rsltBuf[MIN(len, buf_sz-1)]= '\0'; + rtn= 0; + break; + } + +abort: + return rtn; +} + +int +Target_MD5_update(const Target *self, MD5_CTX *ctx) +/******************************************************** + * For computing MD5 checksum of cumulative patterns. + */ +{ + for(unsigned i= 0; i < self->nRx; ++i) { + const struct TargetRx *rx= self->rxArr+i; + MD5_Update(ctx, rx->pattern, strlen(rx->pattern)); + } + return 0; +} diff --git a/target.h b/target.h new file mode 100644 index 0000000..16b59c0 --- /dev/null +++ b/target.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2019 by John D. Robertson * + * john@rrci.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef TARGET_H +#define TARGET_H + +#define _GNU_SOURCE +#include +#include "cfgmap.h" + +struct TargetRx { + const char *pattern; + regex_t re; +}; + +typedef struct _Target { + enum { + TARGET_INIT_FLG=1<<0 + } flags; + unsigned severity, + nRx; + struct TargetRx *rxArr; +} Target; + +#define Target_severity(s) \ + ((const unsigned)(s)->severity) + +#define Target_is_init(s) \ + ((const int)(s)->flags) + + +#ifdef __cplusplus +extern "C" { +#endif + +int +Target_init(Target *self, CFGMAP *h_map, const char *pfix); +/******************************************************** + * Prepare for use from config map + */ + +void* +Target_destructor(Target *self); +/******************************************************** + * Free resources. + */ + +int +Target_scan(const Target *self, char *rsltBuf, unsigned buf_sz, const char *str); +/******************************************************** + * Scan a string to obtain the address. + */ + +int +Target_MD5_update(const Target *self, MD5_CTX *ctx); +/******************************************************** + * For computing MD5 checksum of cumulative patterns. + */ + + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/timestamp.c b/timestamp.c new file mode 100644 index 0000000..e695fa0 --- /dev/null +++ b/timestamp.c @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2019 by John D. Robertson * + * john@rrci.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define _GNU_SOURCE +#include "ez_libc.h" +#include "timestamp.h" +#include "util.h" + + +enum Flags { + GUESS_YR_FLG= 1<<0 +}; + +const static struct bitTuple FlagsBitTupleArr[]= { + {.name= "GUESS_YEAR", .bit= GUESS_YR_FLG}, + {/* Terminating member */} +}; + +int +TS_init(TS *self, CFGMAP *h_map, const char *pfix) +/******************************************************** + * Initialize timestamp from config map. + */ +{ + int rtn= -1; + memset(self, 0, sizeof(*self)); + + size_t len= strlen(pfix)+1024; + char symBuf[len]; + + { /*--- Check for "REGEX" symbol ---*/ + snprintf(symBuf, len, "%s\\REGEX", pfix); + /* CFGMAP is left in place, so we don't need to strdup() */ + self->pattern= CFGMAP_find_last_value(h_map, symBuf); + + if(!self->pattern) { + eprintf("ERROR: cannot find \"REGEX\" entry for ENTRY_TIMESTAMP %s", pfix); + goto abort; + } + + if(regex_compile(&self->re, self->pattern, REG_EXTENDED)) { + eprintf("ERROR: regex_compile(\"%s\") failed.", self->pattern); + goto abort; + } + } + + { /*--- Check for "STRPTIME" symbol ---*/ + snprintf(symBuf, len, "%s\\STRPTIME", pfix); + /* CFGMAP is left in place, so we don't need to strdup() */ + self->strptime_fmt= CFGMAP_find_last_value(h_map, symBuf); + + if(!self->strptime_fmt) { + eprintf("ERROR: cannot find \"STRPTIME\" entry for ENTRY_TIMESTAMP %s", pfix); + goto abort; + } + } + + { /*--- Check for "FLAGS" symbol ---*/ + const char *flagStr; + snprintf(symBuf, len, "%s\\FLAGS", pfix); + + flagStr= CFGMAP_find_last_value(h_map, symBuf); + /* This is optional */ + if(flagStr) { + int rc= str2bits(&self->flags, flagStr, FlagsBitTupleArr); + if(rc) { + eprintf("ERROR: cannot interpret \"FLAGS\" entry for ENTRY_TIMESTAMP %s", pfix); + goto abort; + } + } + } + + rtn= 0; +abort: + return rtn; +} + +void* +TS_destructor(TS *self) +/******************************************************** + * Free resources. + */ +{ + if(self->pattern) + regfree(&self->re); + return self; +} + +int +TS_scan(const TS *self, time_t *rslt, const char *str, const struct tm *pTmRef) +/******************************************************** + * Scan a string to obtain the timestamp. + */ +{ + int rtn= -1; + + /* If there is no match, continue looking */ + regmatch_t matchArr[2]; + if(0 != regexec(&self->re, str, 2, matchArr, 0) || -1 == matchArr[1].rm_so) { + eprintf("ERROR: failed to identify date in \"%s\"", str); + goto abort; + } + + unsigned len= matchArr[1].rm_eo - matchArr[1].rm_so; + static char date[128]; + strncpy(date, str+matchArr[1].rm_so, sizeof(date)-1); + date[MIN(len, sizeof(date)-1)]= '\0'; + static struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst= -1; + + const char *lc= ez_strptime(date, self->strptime_fmt, &tm); + + /* We may have to guess the year */ + if(self->flags & GUESS_YR_FLG) { + + tm.tm_year= pTmRef->tm_year; + + if(tm.tm_mon > pTmRef->tm_mon) + --tm.tm_year; + } + + *rslt= mktime(&tm); +//eprintf("Date string= \"%s\", lc= \"%s\", =? \"%s\"", date, lc, ctime(rslt)); + + rtn= 0; +abort: + return rtn; +} + diff --git a/timestamp.h b/timestamp.h new file mode 100644 index 0000000..3e34db8 --- /dev/null +++ b/timestamp.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2019 by John D. Robertson * + * john@rrci.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef TIMESTAMP_H +#define TIMESTAMP_H + +#define _GNU_SOURCE +#include +#include + +#include "cfgmap.h" + +typedef struct _TS { + const char *pattern; + regex_t re; + const char *strptime_fmt; + int64_t flags; +} TS; + +#ifdef __cplusplus +extern "C" { +#endif + +#define TS_is_prepared(s) \ + ((s)->pattern ? 1 : 0) + +int +TS_init(TS *self, CFGMAP *h_map, const char *pfix); +/******************************************************** + * Initialize timestamp from config map. + */ + +void* +TS_destructor(TS *self); +/******************************************************** + * Free resources. + */ + +int +TS_scan(const TS *self, time_t *rslt, const char *str, const struct tm *pTmRef); +/******************************************************** + * Scan a string to obtain the timestamp. + */ + + + +#ifdef __cplusplus +} +#endif + +#endif + + + + +