summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/filters.conf2
-rw-r--r--tools/greylist.c50
-rw-r--r--tools/hi-q.c44
-rw-r--r--tools/hi-test.c14
-rw-r--r--tools/libltgrey.c508
-rw-r--r--tools/libltgrey.h51
-rw-r--r--tools/ltgrey.c109
-rw-r--r--tools/mail-scan.c41
-rw-r--r--tools/makefile23
-rw-r--r--tools/qq_exit_codes.h15
-rw-r--r--tools/skrewt.c158
-rw-r--r--tools/utils.c76
-rw-r--r--tools/utils.h7
13 files changed, 913 insertions, 185 deletions
diff --git a/tools/filters.conf b/tools/filters.conf
index bd8eb33..3cbd5bf 100644
--- a/tools/filters.conf
+++ b/tools/filters.conf
@@ -1,5 +1,5 @@
# configuration file for hi-q
-series /var/qmail/bin/skrewt
+series /var/qmail/bin/skrewt -err
stub /var/qmail/bin/greylist -check -v
sa /usr/local/bin/spamc -Y 0 -s 1000000 -x
qq /var/qmail/bin/qmail-queue
diff --git a/tools/greylist.c b/tools/greylist.c
index 89396e7..9af70eb 100644
--- a/tools/greylist.c
+++ b/tools/greylist.c
@@ -22,7 +22,6 @@
#include <string.h> /* for memset() */
#include <arpa/inet.h> /* for inet_ntop() */
-
using namespace std;
const int minute(60);
@@ -33,18 +32,7 @@ const int minimum_age(15*minute);
const int maximum_age(32*day);
const int probation(4*hour);
-// error exit codes, mostly as stated in qmail.c
-#define foo(name, num) const int ex_ ## name = num
-#define bar foo(good, 0) ;\
-foo(spam, 21) ;\
-foo(penaltybox, 22) ;\
-foo(badDNS, 23) ;\
-foo(greylisting, 70) ;\
-foo(syserr, 71) ;\
-foo(comerr, 74) ;
-
-bar
-#undef foo
+#include "qq_exit_codes.h"
pid_t mypid;
string progname;
@@ -58,14 +46,6 @@ void dump(const string var){
else cerr << " is not set." << endl;
}
-
-////////////////
-// little utility to help with argument parsing:
-//
-int prefix(const string shorter, const string longer){
- return shorter == longer.substr(0, shorter.length());
-}
-
void exeunt(const int sts){
if (sts == ex_good) exit(sts);
@@ -93,6 +73,8 @@ void exeunt(const int sts){
exit(sts);
}
+#include "utils.h"
+
class whatsit{
public:
string dirname;
@@ -126,12 +108,6 @@ public:
int check_dns_sub(string &addr, string &host, vector<string> &checked);
};
-string basename(const string path){
- size_t where = path.rfind("/");
- if (where != string::npos) return path.substr(1+where);
- return path;
-}
-
int whatsit::setup(){
stringstream foo;
foo << basename(progname) << suffix
@@ -145,26 +121,6 @@ int whatsit::setup(){
return 0;
}
-string time_out(const int _ttt){
- int ttt(abs(_ttt));
- int sec(ttt % 60);
- int min((ttt / 60) % 60);
- int hr(ttt / 3600);
- stringstream foo;
- int didsome(0);
- if (_ttt < 0) foo << "-";
- if (hr) {
- foo << hr << ":";
- didsome++;
- }
- if (didsome || min){
- foo << setw(didsome?2:1) << setfill('0') << min << ":";
- didsome++;
- }
- foo << setw(didsome?2:1) << setfill('0') << sec;
- return foo.str();
-}
-
void scan(const string progid, const string p, const int copies=1){
timeval now;
gettimeofday(&now, NULL);
diff --git a/tools/hi-q.c b/tools/hi-q.c
index 502de69..59cad5d 100644
--- a/tools/hi-q.c
+++ b/tools/hi-q.c
@@ -144,7 +144,7 @@ int Execve(char const * fn,
int fork_and_wait(const jobber job){
pid_t kidpid = fork();
if (kidpid == -1) {
- cerr << "hi-q: fork failed : ";
+ cerr << progid << " fork failed : ";
perror(0);
exit(ex_syserr);
}
@@ -157,9 +157,9 @@ int fork_and_wait(const jobber job){
if (!kidpid){
/*** child code ***/
- int rslt;
- rslt = Execve(prog[0], prog, environ);
- fprintf(stderr, "hi-q: failed to exec '%s': ", prog[0]);
+ Execve(prog[0], prog, environ);
+ cerr << progid << " failed to exec '"
+ << prog[0] << "' : " << endl;
perror(0);
exit(ex_syserr);
} else {
@@ -167,6 +167,11 @@ int fork_and_wait(const jobber job){
int kidstatus;
pid_t somekid;
somekid = waitpid(kidpid, &kidstatus, WUNTRACED);
+ if (somekid < 0) {
+ cerr << progid << " ??? waitpid failed : ";
+ perror(0);
+ return(ex_syserr);
+ }
if (WIFEXITED(kidstatus)) {
int sts = WEXITSTATUS(kidstatus);
if (sts != ex_good && sts != ex_spam) {
@@ -179,7 +184,7 @@ int fork_and_wait(const jobber job){
} else if (WIFSIGNALED(kidstatus)) {
int sig = WTERMSIG(kidstatus);
if (sig == SIGUSR1) {/* normal, no logging required */}
- else cerr << "hi-q: job " << prog[0]
+ else cerr << progid << " job " << prog[0]
<< " killed by signal " << sig << endl;
return(ex_syserr);
} else {
@@ -199,8 +204,8 @@ int fork_and_wait(vector<jobber> post){
}
void exeunt(const int sts) {
- // FIXME: stop other children
- //xxxx cerr << "hi-q: exeunt called with " << sts << endl;
+ // FIXME: stop other children, maybe?
+ //xxxx cerr << progid << " exeunt called with " << sts << endl;
if (sts == ex_spam) fork_and_wait(post);
if (sts == ex_penaltybox) exit(ex_spam);
exit(sts);
@@ -336,7 +341,9 @@ void attach(const int pipe_end, const int fd, const int kidno){
if (pipe_end != fd) {
int rslt = dup2(pipe_end, fd);
if (rslt < 0) {
- fprintf(stderr, "hi-q: dup2(%d,%d) failed for kid %d : ", pipe_end, fd, kidno);
+ cerr << progid << " dup2(" << pipe_end
+ << "," << fd << ")"
+ " failed for kid " << kidno << " : ";
perror(0);
exit(ex_syserr);
}
@@ -394,7 +401,7 @@ bar
ifstream conf;
conf.open(conf_name);
if (! conf.good()) {
- cerr << "hi-q: could not open filter.conf file '"
+ cerr << progid << " could not open filter.conf file '"
<< conf_name << "'" << endl;
exit(1);
}
@@ -431,7 +438,7 @@ bar
if (nkids == 0) exit(0); // nothing to do
if (verbose) for (unsigned int ii = 0; ii < nkids; ii++) {
- cerr << "hi-q filter[" << ii << "] :; ";
+ cerr << progid << " filter[" << ii << "] :; ";
for (VS::const_iterator token = filter[ii].cmd.begin();
token != filter[ii].cmd.end(); token++){
cerr << *token << " ";
@@ -508,7 +515,7 @@ bar
block_fd(blockme);
rslt = pipe(datapipe);
if (rslt < 0) {
- fprintf(stderr, "hi-q: could not create datapipe: ");
+ cerr << progid << " could not create datapipe : ";
perror(0);
exeunt(ex_syserr);
}
@@ -559,7 +566,7 @@ bar
kidpid[ii] = fork();
if (kidpid[ii] == -1) {
- cerr << "hi-q: fork failed : ";
+ cerr << progid << " fork failed : ";
perror(0);
exit(ex_syserr);
}
@@ -657,14 +664,15 @@ bar
exit(1);
}
rslt = Execve(prog[0], prog, environ);
- fprintf(stderr, "hi-q: failed to exec '%s': ", prog[0]);
+ cerr << progid << " failed to exec '"
+ << prog[0] << "' : ";
perror(0);
exit(ex_syserr);
}
/*** parent code ***/
if (kidpid[ii] < 0) {
- fprintf(stderr, "hi-q: failure to fork kid#%d: ", ii);
+ cerr << " failure to fork kid#" << ii << " : ";
perror(0);
exeunt(ex_syserr);
}
@@ -714,7 +722,7 @@ bar
// so we don't need it.
if (verbose) for (unsigned int ii = 0; ii < nkids; ii++) {
- cerr << "hi-q filter[" << ii << "] "
+ cerr << progid << " filter[" << ii << "] "
<< kidpid[ii]
<< " :; ";
for (VS::const_iterator token = filter[ii].cmd.begin();
@@ -736,7 +744,7 @@ bar
// do not decrement the "alive" counter
// since that only applies to non-special kids
if (WIFEXITED(kidstatus)) {
- cerr << "hi-q: special kid exited early, status "
+ cerr << progid << " special kid exited early, status "
<< WEXITSTATUS(kidstatus)
<< " with " << alive << " kids still alive"
<< endl;
@@ -745,7 +753,7 @@ bar
int sig = WTERMSIG(kidstatus);
if (sig == SIGUSR1) {/* normal, no logging required */}
else {
- cerr << "hi-q: special kid killed by signal "
+ cerr << progid << " special kid killed by signal "
<< sig << endl;
// this is not normal
return(ex_syserr);
@@ -795,7 +803,7 @@ bar
if (sts == 0){
// should never get here
// should be no accounting for blame if there was no blame
- cerr << "hi-q: should never happen: no child to blame" << endl;
+ cerr << progid << " should never happen: no child to blame" << endl;
exeunt(ex_syserr);
}
diff --git a/tools/hi-test.c b/tools/hi-test.c
index 0661ada..cd0152c 100644
--- a/tools/hi-test.c
+++ b/tools/hi-test.c
@@ -6,6 +6,7 @@
#include <sstream>
#include <stdio.h> /* perror() */
+#include "utils.h"
using namespace std;
@@ -16,13 +17,6 @@ const int sa_usage(64);
int verbosity(0);
-////////////////
-// little utility to help with argument parsing:
-//
-int prefix(const string shorter, const string longer){
- return shorter == longer.substr(0, shorter.length());
-}
-
void exeunt(const int sts){
if (sts == sa_good) exit(sts);
@@ -69,12 +63,6 @@ void countsome(const int unit){
<< " read " << total << " bytes from unit " << unit << endl;
}
-string basename(const string path){
- size_t where = path.rfind("/");
- if (where != string::npos) return path.substr(1+where);
- return path;
-}
-
int main(int _argc, const char** _argv){
int snooze(0);
int status(0);
diff --git a/tools/libltgrey.c b/tools/libltgrey.c
new file mode 100644
index 0000000..4e92665
--- /dev/null
+++ b/tools/libltgrey.c
@@ -0,0 +1,508 @@
+#include <stdlib.h> /* for exit(), getenv() */
+#include <iostream>
+#include <iomanip>
+#include <string>
+
+#include <sys/types.h> /* for stat(), getaddrinfo() */
+#include <sys/stat.h> /* for stat() */
+#include <unistd.h> /* for stat() */
+#include <stdio.h> /* for perror */
+#include <errno.h> /* for ENOENT */
+#include <fstream> /* for ofstream() */
+#include <fcntl.h> /* for creat() */
+#include <sys/time.h> /* for gettimeofday() */
+#include <sstream> /* for stringstream */
+#include <signal.h> /* for kill(), SIGUSR1 */
+
+// requires apt-get install libboost-filesystem-dev:
+#include <boost/filesystem.hpp>
+
+#include <sys/socket.h> /* for getaddrinfo() */
+#include <netdb.h> /* for getaddrinfo() */
+#include <string.h> /* for memset() */
+#include <arpa/inet.h> /* for inet_ntop() */
+
+using namespace std;
+
+const int minute(60);
+const int hour(60*minute);
+const int day(24*hour);
+
+const int minimum_age(15*minute);
+const int maximum_age(32*day);
+const int probation(4*hour);
+
+#if 0
+void exeunt(const int sts){
+ if (sts == ex_good) exit(sts);
+
+#ifndef PENALIZE_SPAMMERS
+ if (sts == ex_penaltybox) exit(sts);
+#endif
+
+#ifndef KILL_GROUP
+ exit(sts);
+#endif
+
+ const char* foo = getenv("HI_Q_GROUP");
+ if (!foo) exit(sts);
+
+// No point in signalling ourself:
+ sighandler_t rslt = signal(SIGUSR1, SIG_IGN);
+ if (rslt == SIG_ERR) {
+ cerr << "error setting signal" << endl;
+ }
+ int k = kill(-atoi(foo), SIGUSR1);
+ if (k) {
+ cerr << "kill failed on group " << atoi(foo) << " ... ";
+ perror(0);
+ }
+ exit(sts);
+}
+#endif
+
+#include <sys/time.h> /* for gettimeofday */
+#include <iomanip> /* for setw */
+#include <unistd.h> /* for stat */
+#include <sys/types.h> /* for stat, creat */
+#include <sys/stat.h> /* for stat, creat */
+#include <fcntl.h> /* for creat */
+#include <fstream> /* for ofstream() */
+
+#include "libltgrey.h"
+#include "utils.h"
+#include "qq_exit_codes.h"
+
+// constructor
+whatsit::whatsit(const std::string name, const std::string _parent_dir)
+: parent_dir(_parent_dir), progname(name), mypid(getpid()),
+ verbosity(0)
+{
+// expand the codes to make some <const int> names:
+# define foo(name) decode_40[name] = #name;
+ state_40_macro
+# undef foo
+}
+
+void whatsit::dump(const string var){
+ char* str = getenv(var.c_str());
+ cerr << progname
+ << "[" << mypid << "] "
+ << var;
+ if (str) cerr << " is set to '" << str << "'" << endl;
+ else cerr << " is not set." << endl;
+}
+
+int whatsit::setup(){
+ stringstream foo;
+ foo << basename(progname) << suffix
+ << "[" << mypid << "]";
+ progid = foo.str();
+
+ return 0;
+}
+
+#if 1
+void whatsit::update(const string msg, const timeval new_mod,
+ const timeval new_ac, const int penalty, const int stain){
+}
+#else
+void whatsit::update(const string msg, const timeval new_mod,
+ const timeval new_ac, const int penalty, const int stain){
+ if (verbosity){
+ if (penalty || stain || verbosity>1) cerr << progid << ": ";
+ if (penalty) cerr << " penalty " << penalty;
+ if (stain) cerr << " stain " << stain;
+ if (verbosity > 1) {
+ if (penalty || stain) cerr << "+"; // separation, punctuation
+ cerr << msg << ": " << ipbase;
+ if (hostname.length()) cerr << " " << hostname;
+ cerr << " mod_age: " << time_out(mod_age)
+ << " ac_age: " << time_out(ac_age);
+ }
+ cerr << endl;
+ }
+ timeval pen_mod(new_mod);
+ timeval stain_ac(new_ac);
+ if (penalty) {
+ pen_mod = now;
+ pen_mod.tv_sec += penalty;
+ }
+ if (stain) {
+ stain_ac = now;
+ stain_ac.tv_sec -= stain;
+ }
+ timeval upd[2] = {
+// beware: access illogically comes *before* modification here:
+ stain_ac,
+ pen_mod
+ };
+ if (utimes(ipname.c_str(), upd))
+ cerr << "oops" << endl;
+}
+#endif
+
+int whatsit::maybe_mkdir(const string somedir, const string msg){
+// see if our directory exists:
+ struct stat dirstat;
+ int rslt = stat(somedir.c_str(), &dirstat);
+ if (rslt != 0){
+ if (errno != ENOENT) {
+ cerr << progid << " stat failed for "
+ << msg
+ << " '" << somedir << "' : ";
+ perror(0);
+ }
+ rslt = mkdir(somedir.c_str(), 0755);
+ if (rslt != 0) {
+ cerr << progid
+ << "uid " << getuid()
+ << " mkdir failed for "
+ << msg
+ << "' " << somedir << "' : ";
+ perror(0);
+ return(ex_syserr);
+ }
+ }
+ return 0;
+}
+
+#if 1
+int whatsit::doit(const int penalty, const int stain){
+ return ex_syserr;
+}
+
+#else
+int whatsit::doit(const int penalty, const int stain){
+ if (!ipvar) {
+ cerr << progid
+ << " TCPREMOTEIP not set???" << endl;
+ // should never happen
+ // although you can make it happen using a weird test-harness
+ return(ex_syserr);
+ }
+
+ maybe_mkdir(parent_dir, "parent dir");
+ maybe_mkdir(parent_dir + "/quarante", "quarantine dir");
+ maybe_mkdir(parent_dir + "/repute", "reputation dir");
+
+ ipname = dirname + "/" + ipbase;
+ struct stat ipstat;
+ rslt = stat(ipname.c_str(), &ipstat);
+ if (rslt != 0){
+ if (errno != ENOENT) {
+ cerr << progid << ": stat failed for '"
+ << ipname << "' : ";
+ perror(0);
+ }
+//???!! ofstream foo;
+ int fd = creat(ipname.c_str(), 0644);
+ if (fd < 0){
+ cerr << progid << ": create failed for '"
+ << ipname << "' : ";
+ perror(0);
+ }
+ close(fd);
+ update("new customer", now, now, penalty, stain);
+ return(ex_greylisting);
+ }
+
+// now for really checking the greylist status:
+ mod_age = now.tv_sec - ipstat.st_mtime;
+ ac_age = now.tv_sec - ipstat.st_atime;
+ timeval mod_orig = {ipstat.st_mtime, 0};
+ if (mod_age < 0) {
+ update("penalty box", mod_orig, now, penalty, stain);
+ return(ex_penaltybox);
+ }
+ if (mod_age < ac_age){
+// when he comes out on parole, he starts over with no reputation:
+ update("paroled spammer", now, now, penalty, stain);
+ return(ex_greylisting);
+ }
+ if (mod_age < minimum_age) {
+ update("early bird", mod_orig, now, penalty, stain);
+ return(ex_greylisting);
+ }
+ if (mod_age - ac_age < minimum_age // early bird, or completely unused
+ && mod_age > probation) { // did not diligently resubmit
+ update("disprobation", now, now, penalty, stain);
+ return(ex_greylisting);
+ }
+ if (ac_age > maximum_age) {
+ update("too old, starting over", now, now, penalty, stain);
+ return(ex_greylisting);
+ }
+// if all checks are passed, must be OK:
+ update("returning customer", mod_orig, now, penalty, stain);
+ return 0;
+}
+#endif
+
+typedef vector<unsigned char> VU;
+
+class VUx{
+public:
+ VU addr;
+ sa_family_t fam;
+ string str();
+};
+
+string VUx::str(){
+ char msgbuf[INET6_ADDRSTRLEN];
+ const char* rslt = inet_ntop(fam, &addr[0],
+ msgbuf, sizeof(msgbuf));
+ if (!rslt) rslt = "";
+ return rslt;
+}
+
+VUx parse_sockaddr(const sockaddr* ai_addr) {
+ void* numericAddress;
+ VUx rslt;
+ int addrsize;
+ rslt.addr = VU(0);
+ rslt.fam = ((sockaddr *)ai_addr)->sa_family;
+ switch (rslt.fam) {
+ case AF_INET:
+ numericAddress = &(((sockaddr_in *)ai_addr)->sin_addr.s_addr);
+ addrsize = sizeof(in_addr);
+ break;
+ case AF_INET6:
+ numericAddress = &(((sockaddr_in6 *)ai_addr)->sin6_addr.s6_addr);
+ addrsize = sizeof(in6_addr);
+ break;
+ default:
+ cerr << "?Unknown address family " << rslt.fam << endl;
+ return rslt;
+ }
+ unsigned char* foo = (unsigned char*) numericAddress;
+ rslt.addr = VU(foo, foo+addrsize);
+ return rslt;
+}
+
+int diff(const VU aaa, const VU bbb){
+ if(aaa.size() != bbb.size()) return 1;
+ for (unsigned int ii=0; ii < aaa.size(); ii++){
+ if (aaa[ii] != bbb[ii]) return 1;
+ }
+ return 0;
+}
+
+int whatsit::check_dns(const char* ipvar, const char* namevar){
+ if (!ipvar) {
+ cerr << progid << " check_dns: no addr specified." << endl;
+ return ex_syserr;
+ }
+ string addr("()"), host("()");
+ vector<string> checked;
+ int rslt = check_dns_sub(ipvar, namevar, addr, host, checked);
+ int sts = rslt;
+#if 1
+ sts = 0; // demote badDNS to just a warning
+#endif
+ if (rslt || verbosity) {
+ cerr << progid;
+ if (rslt && !sts) cerr << " (warning)";
+ if (rslt) cerr << " DNS inconsistency: ";
+ else cerr << " DNS OK: ";
+ cerr << addr << " --> "
+ << host << " ==>";
+ if (!checked.size()) cerr << " ()";
+ else for (vector<string>::const_iterator chk = checked.begin();
+ chk != checked.end(); chk++) cerr << " " << *chk;
+ cerr << endl;
+ }
+ return sts;
+}
+
+int whatsit::check_dns_sub(const char* ipvar, const char* namevar,
+ string &addr, string &host, vector<string> &checked){
+
+ struct addrinfo *result;
+ struct addrinfo *ipresult;
+ struct addrinfo *res;
+ addrinfo hints;
+ int error;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+#if 1
+ // restrict to TCP only; otherwise we get N records per address
+ hints.ai_protocol = IPPROTO_TCP;
+#endif
+
+// convert address-as-string to address-as-bits.
+// also get information about family
+ error = getaddrinfo(ipvar, NULL, &hints, &ipresult);
+ if (error == EAI_NONAME) return ex_badDNS;
+ if (error) { // some unexpected error
+ cerr << progid
+ << " odd error " << error
+ << " in getaddrinfo for " << ipvar
+ << " : " << gai_strerror(error) << endl;
+ return ex_syserr;
+ }
+ if (!ipresult) {
+ cerr << "should never happen (addr with no addrs?)" << endl;
+ return ex_syserr;
+ }
+
+// reconvert from bits to string + family info
+ VUx ipAddr = parse_sockaddr(ipresult->ai_addr);
+ addr = ipAddr.str();
+
+ if (namevar) {
+ // inverse lookup already done
+ host = namevar;
+ } else {
+ // namevar not specified; do inverse lookup on our own
+
+ char hostname[NI_MAXHOST] = "";
+ char service[NI_MAXHOST] = "";
+
+#ifdef convert_bits_to_string
+ const char* rslt = inet_ntop(ipAddr.fam, &ipAddr.addr[0],
+ msgbuf, sizeof(msgbuf));
+ if (rslt) fprintf(stdout, "%s addrsize: %2d --> ",
+ msgbuf, addrsize);
+#endif
+ error = getnameinfo(ipresult->ai_addr, ipresult->ai_addrlen,
+ hostname, NI_MAXHOST, service, NI_MAXHOST, 0);
+ if (error != 0) {
+ cerr << progid << " reverse DNS lookup failed: "
+ << gai_strerror(error) << endl;
+ return (ex_badDNS);
+ }
+ namevar = hostname; // a char*, used below
+ host = hostname; // a string, returned to caller
+ }
+
+ error = getaddrinfo(namevar, NULL, &hints, &result);
+ if (error == EAI_NONAME) return ex_badDNS;
+ if (error) {
+ cerr << progid
+ << " error " << error
+ << " compare " << EAI_NONAME
+ << " in getaddrinfo for " << ipvar
+ << " :: " << gai_strerror(error) << endl;
+ return ex_syserr;
+ }
+
+// loop over all returned results and check for a match.
+ for (res = result; res != NULL; res = res->ai_next){
+ VUx hostAddr = parse_sockaddr(res->ai_addr);
+ checked.push_back(hostAddr.str());
+ if (!diff(hostAddr.addr, ipAddr.addr)) {
+ ///// cerr << "match! " << ipAddr.addr.size() << endl;
+ goto done;
+ }
+ }
+ return ex_badDNS;
+
+done:
+ return 0;
+}
+
+state_40 whatsit::get40(const string mid){
+ timeval junk[2];
+ return get40(mid, junk);
+}
+
+state_40 whatsit::get40(const string mid, timeval times[2]){
+ timeval now;
+ gettimeofday(&now, NULL);
+ times[0] = times[1] = now; // in case of early return
+ string fname = parent_dir + "/quarante/mid_" + purify(mid);
+ //xxx cerr << ".... " << fname << endl;
+ struct stat mid_stat;
+ int rslt = stat(fname.c_str(), &mid_stat);
+ if (rslt != 0) {
+ if (errno == ENOENT) return unseen;
+ cerr << progid << " stat: unexpected failure for '"
+ << fname << "' : ";
+ perror(0);
+ return fail;
+ }
+ times[upd_ac ].tv_sec = mid_stat.st_atime;
+ times[upd_ac ].tv_usec = 0;
+ times[upd_mod].tv_sec = mid_stat.st_mtime;
+ times[upd_mod].tv_usec = 0;
+ int mod_age = now.tv_sec - mid_stat.st_mtime;
+// int ac_age = now.tv_sec - mid_stat.st_atime;
+ if (mod_age < minimum_age) return green;
+ if (mod_age < probation) return ripe;
+ return spoiled;
+}
+
+int whatsit::set40(const string mid, const int shift){
+ string fname = parent_dir + "/quarante/mid_" + purify(mid);
+ //xxx cerr << ".... " << fname << endl;
+ int fd = creat(fname.c_str(), 0644);
+ if (fd < 0){
+ cerr << progid << ": create failed for '"
+ << fname << "' : ";
+ perror(0);
+ }
+ if (!shift) {
+ close(fd);
+ return 0;
+ }
+
+ struct stat mid_stat;
+ int rslt = fstat(fd, &mid_stat);
+ close(fd);
+ if (rslt != 0) {
+ cerr << progid << " fstat: unexpected failure for '"
+ << fname << "' : ";
+ perror(0);
+ return ex_syserr;
+ }
+
+ timeval now;
+ gettimeofday(&now, NULL);
+ timeval upd[2] = {
+ now, // access
+ now // modification
+ };
+ upd[upd_mod].tv_sec += shift;
+ if (utimes(fname.c_str(), upd)){
+ cerr << progid << " utimes failed!" << endl;
+ return ex_syserr;
+ }
+ return 0;
+}
+
+void whatsit::scan40(const int copies){
+ timeval now;
+ gettimeofday(&now, NULL);
+ using namespace boost::filesystem;
+
+ string dir40 = parent_dir + "/quarante";
+
+ if (is_directory(dir40)) {
+ for (directory_iterator itr(dir40); itr!=directory_iterator(); ++itr) {
+ string basename = itr->path().filename();
+ if (is_regular_file(itr->status())) {
+ string tag("mid_");
+ if (prefix(tag, basename)) {
+ for (int ii = 0; ii < copies; ii++)
+ cout << setw(20) << left << basename; // display filename only
+
+ timeval times[2];
+ state_40 rslt = get40(basename.substr(tag.length()), times);
+ int mod_age = now.tv_sec - times[upd_mod].tv_sec;
+ int ac_age = now.tv_sec - times[upd_ac ].tv_sec;
+ cout << setw(10) << right << time_out(mod_age)
+ << " " << setw(10) << right << time_out(ac_age);
+ cout << " " << decode_40[rslt];
+ cout << endl;
+ } else {
+ /* filename does not begin with tag "mid_" */
+ }
+ }
+ }
+ }
+ else {
+ // starting point is not a directory:
+ cout << (exists(dir40) ? "Found: " : "Not found: ") << dir40 << '\n';
+ }
+}
diff --git a/tools/libltgrey.h b/tools/libltgrey.h
new file mode 100644
index 0000000..14ed144
--- /dev/null
+++ b/tools/libltgrey.h
@@ -0,0 +1,51 @@
+#include <string>
+#include <sys/time.h> /* for gettimeofday(), timeval */
+#include <vector>
+#include <map>
+
+// beware: access illogically comes *before* modification
+// in the array passed to utimes:
+const int upd_ac(0);
+const int upd_mod(1);
+
+#define state_40_macro \
+foo(unseen) \
+foo(green) \
+foo(ripe) \
+foo(spoiled) \
+foo(fail)
+
+// expand the codes to make some <const int> names:
+#define foo(name) name,
+typedef enum {
+ state_40_macro
+} state_40;
+#undef foo
+
+class whatsit{
+public:
+ std::string parent_dir;
+ std::string progname;
+ pid_t mypid;
+ std::string suffix;
+ std::string progid;
+ int verbosity;
+ std::map<state_40,std::string> decode_40;
+
+ whatsit(const std::string name, const std::string _parent_dir);
+ int doit(const int penalty, const int stain);
+// access comes after modification:
+ void update(const std::string msg, const timeval new_mod,
+ const timeval new_ac, const int penalty, const int stain);
+ int setup();
+ int check_dns(const char* ipvar, const char* namevar);
+ int check_dns_sub(const char* ipvar, const char* namevar,
+ std::string &addr, std::string &host,
+ std::vector<std::string> &checked);
+ void dump(const std::string var);
+ int maybe_mkdir(const std::string somedir, const std::string msg);
+ state_40 get40(const std::string mid);
+ state_40 get40(const std::string mid, timeval times[2]);
+ int set40(const std::string mid, const int shift);
+ void scan40(const int copies);
+};
diff --git a/tools/ltgrey.c b/tools/ltgrey.c
new file mode 100644
index 0000000..1494338
--- /dev/null
+++ b/tools/ltgrey.c
@@ -0,0 +1,109 @@
+#include <iostream>
+#include <stdlib.h> /* for exit(), atoi() */
+
+#include "libltgrey.h"
+#include "utils.h"
+#include "qq_exit_codes.h"
+
+using namespace std;
+pid_t mypid;
+string progname;
+
+#define exeunt exit
+
+// forward reference:
+void scan(const string progid, const string p, const int copies=1);
+
+
+int main(int _argc, char** _argv){
+ std::string hostname;
+ std::string ipname;
+
+ mypid = getpid();
+ int argc(_argc);
+ char** argv(_argv);
+ const string parent_dir("/var/qmail/ltgrey");
+ whatsit foo(argv[0], parent_dir); argc--; argv++;
+ int scanmode(0);
+ int copies(1);
+ int shift(0);
+ int stain(0);
+ int dns_mode(0);
+ string get40_mid;
+ string set40_mid;
+ while (argc > 0) {
+ string arg = argv[0]; argc--; argv++;
+ if (prefix(arg, "-scan40")) {
+ scanmode++;
+ } else if (prefix(arg, "-copy")) {
+ copies++;
+ } else if (prefix(arg, "-verbose")) {
+ foo.verbosity++;
+ } else if (prefix(arg, "-dns_mode")) {
+ dns_mode++;
+ } else if (prefix(arg, "-get40")) {
+ if (!argc){
+ cerr << "Option '" << arg << "' requires an argument" << endl;
+ exeunt(ex_syserr);
+ }
+ get40_mid = *argv++; argc--;
+ } else if (prefix(arg, "-set40")) {
+ if (!argc){
+ cerr << "Option '" << arg << "' requires an argument" << endl;
+ exeunt(ex_syserr);
+ }
+ set40_mid = *argv++; argc--;
+ } else if (prefix(arg, "-shift")
+ || prefix(arg, "-shift")) {
+ if (!argc){
+ cerr << "Option '" << arg << "' requires an argument" << endl;
+ exeunt(ex_syserr);
+ }
+ shift = atoi(*argv++); argc--;
+ } else if (prefix(arg, "-stain")) {
+ if (!argc){
+ cerr << "Option '" << arg << "' requires an argument" << endl;
+ exeunt(ex_syserr);
+ }
+ stain = atoi(*argv++); argc--;
+ } else if (prefix(arg, "-suffix")) {
+ if (!argc){
+ cerr << "Option '" << arg << "' requires an argument" << endl;
+ exeunt(ex_syserr);
+ }
+ foo.suffix += *argv++; argc--;
+ } else {
+ cerr << "Unrecognized arg: " << arg << endl;
+ exeunt(ex_syserr);
+ }
+ }
+ if (foo.setup()) return ex_syserr;
+
+// dns_mode mode ...
+// Probably it would be better to make more thorough DNS checks.
+//
+ if (dns_mode) {
+ char* ipvar = getenv("TCPREMOTEIP");
+ char* namevar = getenv("TCPREMOTEHOST");
+ exeunt(foo.check_dns(ipvar, namevar));
+ }
+
+ if (get40_mid.length()){
+ state_40 rslt = foo.get40(get40_mid);
+ cerr << foo.decode_40[rslt] << endl;
+ return 0;
+ }
+
+ if (set40_mid.length()){
+ return foo.set40(set40_mid, shift);
+ }
+
+ if (scanmode) {
+ foo.scan40(copies);
+ return 0;
+ }
+
+ int sts = foo.doit(shift, stain);
+ return sts;
+
+}
diff --git a/tools/mail-scan.c b/tools/mail-scan.c
index 0cda6e5..b0c4137 100644
--- a/tools/mail-scan.c
+++ b/tools/mail-scan.c
@@ -31,7 +31,7 @@
#include <stdio.h> /* perror */
#include <boost/regex.hpp>
-////#include <boost/lexical_cast.hpp>
+#include "utils.h"
using namespace std;
@@ -105,30 +105,6 @@ int cmp_casefold(const std::string& a, const std::string& b) {
return 0;
}
-
-string toLower(const std::string& a){
- string rslt = a;
- string::iterator rr;
- for (rr = rslt.begin(); rr != rslt.end(); rr++){
- *rr = tolower(*rr);
- }
- return rslt;
-}
-
-////////////////
-string ltrim(string foo){
- size_t where = foo.find_first_not_of(" \t\r\n");
- if (where == foo.npos) return foo;
- return foo.substr(where);
-}
-
-////////////////
-// little utility to help with argument parsing:
-//
-int prefix(const string shorter, const string longer){
- return shorter == longer.substr(0, shorter.length());
-}
-
void exeunt(const int sts){
if (sts == sa_good) exit(sts);
@@ -183,6 +159,16 @@ public:
}
};
+string noCR(const string bar){
+ string foo(bar);
+ int len = foo.length();
+ if (len){
+ if (foo[len-1] == '\r') {
+ foo.erase(len-1);
+ }
+ }
+ return foo;
+}
////////////////////////////////////////////////////////////
int main(int _argc, const char** _argv){
@@ -268,6 +254,7 @@ int main(int _argc, const char** _argv){
return 1;
}
if (getline(infile, line).fail()) continue;
+ line = noCR(line);
Header.push_back(line);
msgsize += line.length()+1;
if (msgsize > maxsize) {
@@ -335,9 +322,9 @@ int main(int _argc, const char** _argv){
break;
}
} // end loop over matching records in this file
+
if (vflag && !foundsome_infile) {
- cout << foundsome_infile
- << " ... " << *file << endl;
+ cout << *file << endl;
didprint++;
}
if (group_flag && didprint) cout << endl;
diff --git a/tools/makefile b/tools/makefile
index 76df23b..6594ca8 100644
--- a/tools/makefile
+++ b/tools/makefile
@@ -15,10 +15,10 @@ qmain = pido.c hi-q.c skrewt.c hi-test.c mail-scan.c greylist.c wripper.c
qprogs = $(qmain:%.c=%)
# sources for other main programs:
-moremain = wripper.c bash-c.c
+moremain = wripper.c bash-c.c ltgrey.c
moreprogs = $(moremain:%.c=%)
-nonmain =
+nonmain = libltgrey.c
sources = $(qmain) $(moremain) $(nonmain)
@@ -37,17 +37,24 @@ all: $(qprogs) $(moreprogs)
show:
: --- $(qprogs) +++ $(moreprogs)
-greylist: greylist.o
- $(CC) $< -lboost_filesystem-mt -lboost_system -o $@
+skrewt: skrewt.o utils.o
+ $(CC) $^ -lboost_filesystem-mt -lboost_system -o $@
-# $(CC) $< -lboost_filesystem -o $@
+greylist: greylist.o utils.o
+ $(CC) $^ -lboost_filesystem-mt -lboost_system -o $@
+
+ltgrey: ltgrey.o utils.o libltgrey.o
+ $(CC) $^ -lboost_filesystem-mt -lboost_system -o $@
wripper: wripper.o
- $(CC) $< -o $@
+ $(CC) $^ -o $@
chgrp daemon $@ && chmod g+s $@ || true
-mail-scan: mail-scan.o
- $(CC) $< -lboost_regex -o $@
+mail-scan: mail-scan.o utils.o
+ $(CC) $^ -lboost_regex -o $@
+
+hi-test: hi-test.o utils.o
+ $(CC) $^ -lboost_regex -o $@
install:
install $(qprogs) /var/qmail/bin/
diff --git a/tools/qq_exit_codes.h b/tools/qq_exit_codes.h
new file mode 100644
index 0000000..2af6848
--- /dev/null
+++ b/tools/qq_exit_codes.h
@@ -0,0 +1,15 @@
+// error exit codes, mostly as stated in qmail.c
+#define qq_exit_codes \
+foo(good, 0) ;\
+foo(spam, 21) ;\
+foo(penaltybox, 22) ;\
+foo(badDNS, 23) ;\
+foo(usage, 39) ;\
+foo(greylisting, 70) ;\
+foo(syserr, 71) ;\
+foo(comerr, 74) ;
+
+// expand the codes to make some <const int> names:
+#define foo(name, num) const int ex_ ## name = num
+qq_exit_codes
+#undef foo
diff --git a/tools/skrewt.c b/tools/skrewt.c
index d2e1bbc..6749a01 100644
--- a/tools/skrewt.c
+++ b/tools/skrewt.c
@@ -1,4 +1,4 @@
-///////////////////
+//////////////////
// skrewt.c
//
// scrutinize email
@@ -13,6 +13,7 @@
#include <stdio.h> /* perror */
#include <sstream>
#include <vector>
+#include <list>
using namespace std;
@@ -28,6 +29,7 @@ void usage(const int sts){
" Options\n"
" -help print this msg (and exit immediately).\n"
" -maxsize ii msg size in bytes; anything bigger will be rejected.\n"
+" -error-exit exit early if errors have been detected.\n"
"\n"
" Messages containing the string '-please-bounce-this-' will be rejected.\n"
" Messages with no date will be rejected.\n"
@@ -35,20 +37,8 @@ void usage(const int sts){
exit(sts);
}
-// error exit codes, mostly as stated in qmail.c
-#define bar \
-foo(good, 0) ;\
-foo(spam, 21) ;\
-foo(permerr, 31) ;\
-foo(usage, 39) ;\
-foo(greylisting, 70) ;\
-foo(syserr, 71) ;\
-foo(comerr, 74) ;
-
-#define foo(name, num) const int ex_ ## name = num
-bar
-#undef foo
-
+#include "qq_exit_codes.h"
+#include "utils.h"
/////////////////////////////////////////////////////////
// Case insensitive comparison of strings
@@ -93,31 +83,19 @@ int cmp_casefold(const std::string& a, const std::string& b) {
return 0;
}
-
-string toLower(const std::string& a){
- string rslt = a;
- string::iterator rr;
- for (rr = rslt.begin(); rr != rslt.end(); rr++){
- *rr = tolower(*rr);
+string noCR(const string bar){
+ string foo(bar);
+ int len = foo.length();
+ if (len){
+ if (foo[len-1] == '\r') {
+ foo.erase(len-1);
+ }
}
- return rslt;
+ return foo;
}
-////////////////
-string ltrim(string foo){
- size_t where = foo.find_first_not_of(" \t\r\n");
- if (where == foo.npos) return foo;
- return foo.substr(where);
-}
-
-////////////////
-// little utility to help with argument parsing:
-//
-int prefix(const string shorter, const string longer){
- return shorter == longer.substr(0, shorter.length());
-}
-
-void exeunt(const int sts){
+void maybe_exeunt(const int sts, const int really){
+ if (!really) return;
if (sts == ex_good) exit(sts);
const char* foo = getenv("HI_Q_GROUP");
@@ -136,10 +114,8 @@ void exeunt(const int sts){
exit(sts);
}
-string basename(const string path){
- size_t where = path.rfind("/");
- if (where != string::npos) return path.substr(1+where);
- return path;
+void exeunt(const int sts){
+ maybe_exeunt(sts, 1);
}
string progname, progid;
@@ -149,7 +125,7 @@ int mypid;
/* Content-Type: multipart/mixed; boundary="1170861315-1262462055-1341954763=:92165" */
//
void parse_content(const string type_spec_line, string &maintype, string &boundary) {
- cerr << "parser called with: " << type_spec_line << endl;
+ //xxx cerr << "parser called with: " << type_spec_line << endl;
string get_type(type_spec_line);
size_t where = get_type.find_first_of(" \t;\n");
@@ -192,6 +168,15 @@ void parse_content(const string type_spec_line, string &maintype, string &bounda
}
}
+string join(const string sep, const list<string> stuff){
+ string rslt;
+ for (list<string>::const_iterator ptr = stuff.begin();
+ ptr != stuff.end(); ptr++){
+ if (rslt.length()) rslt += sep;
+ rslt += *ptr;
+ }
+ return rslt;
+}
////////////////////////////////////////////////////////////
int main(int _argc, const char** _argv){
@@ -209,6 +194,8 @@ int main(int _argc, const char** _argv){
}
int maxsize(1000*1000);
+ int error_exit(0);
+ int mid_required(0);
while (argc) {
string arg(*argv); argv++; argc--;
@@ -216,14 +203,18 @@ int main(int _argc, const char** _argv){
if (prefix(arg, "-help")) {
usage(0);
}
- if (prefix(arg, "-maxsize")) {
+ if (0) {
+ } else if (prefix(arg, "-mid-required")) {
+ mid_required++;
+ } else if (prefix(arg, "-error-exit")) {
+ error_exit++;
+ } else if (prefix(arg, "-maxsize")) {
if (!argc) {
cerr << "Option -maxsize requires an argument" << endl;
exit(ex_usage);
}
maxsize = atoi(*argv); argv++; argc--;
- }
- if (arg.substr(0,1) == "-") {
+ } else if (arg.substr(0,1) == "-") {
cerr << "Unrecognized option '" << arg << "'" << endl;
cerr << "For help, try: " << progname << " -help" << endl;
exit(ex_usage);
@@ -236,28 +227,32 @@ int main(int _argc, const char** _argv){
int saw_blank_line(0);
string boundary("x-xx-x");
- string date;
+ string to;
+ string from;
string subject;
- string content_type;
+ string date;
string message_id;
+ string content_type;
int msgsize(0);
vector<string> bigbuf;
- cerr << "hi there" << endl;
+ int recno(0);
+ //xxxx cerr << progid << " begins" << endl;
for (;;){ // outer loop over all records in the header
if (cin.eof()) break;
if (cin.bad()) return 1;
- string headrec;
+ string line;
// on fail, go back to top of outer loop and check for eof versus bad
- if (getline(cin, headrec).fail()) continue;
- msgsize += headrec.length()+1;
+ if (getline(cin, line).fail()) continue;
+ msgsize += line.length()+1;
if (msgsize > maxsize) {
cerr << progid << " rejection: bigger than " << maxsize << endl;
exeunt(ex_spam);
}
- cout << headrec << endl;
- bigbuf.push_back(headrec); // for a folded record, this is the first line
+ cout << line << endl;
+ bigbuf.push_back(line);
+ string headrec = noCR(line); // for a folded record, this is the first line
for (;;) { // inner loop to build a multi-line record e.g. folded record:
if (cin.eof()) break;
@@ -276,16 +271,11 @@ int main(int _argc, const char** _argv){
}
cout << line << endl;
bigbuf.push_back(line);
- string cooked(line);
- if (cooked.length()){
- string::iterator ptr = cooked.end()-1;
- if (*ptr == '\r') cooked.erase(ptr);
- }
- headrec += "\n" + cooked;
+ headrec += "\n" + noCR(line);
}
// here with a fully assembled header record
+// headrec (unlike line) contains no DOS CR characters
int len = headrec.length();
- if (len && headrec[len-1] == '\r') len--; // reduced length, not counting <cr>
if (len == 0) {
saw_blank_line = 1;
break; // no more headers in this message
@@ -301,6 +291,12 @@ int main(int _argc, const char** _argv){
}
headword = toLower(headword);
if (0){
+ } else if (headword == "from") {
+ from = rest;
+ } else if (headword == "to") {
+ to = rest;
+ } else if (headword == "message-id") {
+ message_id = rest;
} else if (headword == "date") {
date = rest;
} else if (headword == "subject") {
@@ -309,30 +305,50 @@ int main(int _argc, const char** _argv){
content_type = rest;
}
//xxxx cout << headrec.length() << " ... ";
+ recno++;
+ if (0) if (recno <= 6) cerr << progid << "#" << recno
+ << " " << headrec << endl;
}
- cerr << "headers are done. Delimited: " << saw_blank_line << endl;
+ if (saw_blank_line) {/* ignore */}
+ cerr << progid <<" Mid '" << message_id << "'" << endl;
// Headers are done.
// Do some early-stage thinking.
+ list<string> badnews;
+
if (subject.find("-please-bounce-this-") != string::npos) {
- cerr << progid << " rejection: by request" << endl;
- exeunt(ex_spam);
+ badnews.push_back("by request");
}
if (!date.length()) {
- cerr << progid << " rejection: no date" << endl;
- exeunt(ex_spam); // disallow mail with no date
+ badnews.push_back("no date");
+ }
+
+ if (mid_required && !message_id.length()) {
+ badnews.push_back("no message-id");
+ }
+
+ if (badnews.size()){
+ cerr << progid << " " << join(", ", badnews) << endl;
+ if (error_exit){
+ cerr << progid << " '" << from
+ << "' to '" << to
+ << "'" << endl;
+ exeunt(ex_spam);
+ }
}
string main_contype;
- parse_content(content_type, main_contype, boundary);
+ if (content_type.length())
+ parse_content(content_type, main_contype, boundary);
+// some slightly-useful booleans:
int currently_text = main_contype == "text";
int main_multipart = main_contype == "multipart";
// early-stage thinking has been done.
// Now spew the rest of the message
- cerr << "body begins: " << main_contype << " " << currently_text << " " << boundary << endl;
+ //xxxx cerr << "body begins: " << main_contype << " " << currently_text << " " << boundary << endl;
int in_subheads(0);
int textlines(0);
@@ -345,7 +361,7 @@ int main(int _argc, const char** _argv){
msgsize += line.length()+1;
if (msgsize > maxsize) {
cerr << progid << " rejection: bigger than " << maxsize << endl;
- exeunt(ex_spam);
+ maybe_exeunt(ex_spam, error_exit);
}
bigbuf.push_back(line);
cout << line << endl;
@@ -368,7 +384,7 @@ int main(int _argc, const char** _argv){
if (headword == "content-type") {
parse_content(rest, sub_contype, junk);
currently_text = sub_contype == "text";
- cerr << "setting contype '" << sub_contype << "' " << currently_text << " ... " << textlines << endl;
+ //xxxx cerr << "setting contype '" << sub_contype << "' " << currently_text << " ... " << textlines << endl;
}
} else {
if (main_multipart && line == "--" + boundary) {
@@ -380,10 +396,10 @@ int main(int _argc, const char** _argv){
}
}
- if (1) cerr << "textlines: " << textlines << endl;
+ if (0) cerr << "textlines: " << textlines << endl;
if (1 && !textlines) {
cerr << progid << " rejection: no text" << endl;
-// exeunt(ex_spam);
+// maybe_exeunt(ex_spam, error_exit);
}
cerr << progid << " normal completion" << endl;
exit(ex_good);
diff --git a/tools/utils.c b/tools/utils.c
new file mode 100644
index 0000000..c544f20
--- /dev/null
+++ b/tools/utils.c
@@ -0,0 +1,76 @@
+#include <string>
+#include <sstream>
+#include <iomanip>
+//#include <stdlib.h> /* for abs() */
+#include <cmath>
+#include <ctype.h> /* for isalnum() */
+
+// strip off the directory part of a path, leaving just
+// the basic filename
+std::string basename(const std::string path){
+ size_t where = path.rfind("/");
+ if (where != std::string::npos) return path.substr(1+where);
+ return path;
+}
+
+////////////////
+// little utility to help with argument parsing:
+//
+int prefix(const std::string shorter, const std::string longer){
+ return shorter == longer.substr(0, shorter.length());
+}
+
+///////////////
+// print a time as (-)hh:mm:ss
+//
+std::string time_out(const int _ttt){
+using namespace std;
+ int ttt(abs(_ttt));
+ int sec(ttt % 60);
+ int min((ttt / 60) % 60);
+ int hr(ttt / 3600);
+ stringstream foo;
+ int didsome(0);
+ if (_ttt < 0) foo << "-";
+ if (hr) {
+ foo << hr << ":";
+ didsome++;
+ }
+ if (didsome || min){
+ foo << setw(didsome?2:1) << setfill('0') << min << ":";
+ didsome++;
+ }
+ foo << setw(didsome?2:1) << setfill('0') << sec;
+ return foo.str();
+}
+
+std::string toLower(const std::string a){
+ std::string rslt = a;
+ std::string::iterator rr;
+ for (rr = rslt.begin(); rr != rslt.end(); rr++){
+ *rr = tolower(*rr);
+ }
+ return rslt;
+}
+
+////////////////
+std::string ltrim(const std::string foo){
+ size_t where = foo.find_first_not_of(" \t\r\n");
+ if (where == foo.npos) return foo;
+ return foo.substr(where);
+}
+
+static const std::string Pure_Enough("+-_.,@%~");
+
+std::string purify(const std::string arg){
+ using namespace std;
+ string rslt(arg);
+ for (string::iterator ptr = rslt.begin();
+ ptr != rslt.end(); ptr++){
+ char ch = *ptr;
+ if (isalnum(ch)) continue;
+ if (Pure_Enough.find(ch) != string::npos) continue;
+ *ptr = '~';
+ }
+ return rslt;
+}
diff --git a/tools/utils.h b/tools/utils.h
new file mode 100644
index 0000000..cbcb795
--- /dev/null
+++ b/tools/utils.h
@@ -0,0 +1,7 @@
+std::string basename(const std::string path);
+int prefix(const std::string shorter, const std::string longer);
+std::string time_out(const int _ttt);
+
+std::string toLower(const std::string a);
+std::string ltrim(const std::string a);
+std::string purify(const std::string arg);