/////////////// // lightweight connection from qmail to filters e.g. spamassassin // (hi-q filter, get it?) // TODO: Panic stop should signal all children. // TODO: Possibly: Wait for all kids in parallel? // That's because they might finish out of order. #include #include /* for malloc() */ #include #include #include /* for fork(), wait() */ #include #include using namespace std; #include #include #include #include #include // error exit codes, as stated in qmail.c const int ex_spam = 21; const int ex_syserr = 71; const int ex_comerr = 74; #define bufsize 16384 void panic(const int sts) { // FIXME: stop other children exit(sts); } void slurp(const int inch, const int ouch){ char buf[bufsize]; ssize_t todo; for (;;) { ssize_t got = read(inch, buf, bufsize); if (got == 0) { // EoF break; } if (got < 0) { fprintf(stderr, "hi-q: input error: "); perror(0); panic(ex_comerr); } todo = got; while (todo) { ssize_t sent = write(ouch, buf, todo); if (sent < 0 && errno != EINTR) { fprintf(stderr, "hi-q: output error: "); perror(0); panic(ex_comerr); } todo -= sent; } } } void probe_fd(){ int ii; struct stat buf; for (ii = 0; ii < 16; ii++) { int rslt = fstat(ii, &buf); fprintf(stderr, "fd %2d status %2d", ii, rslt); if (rslt==0) fprintf(stderr, " : %d", (int)buf.st_dev); fprintf(stderr, "\n"); } fprintf(stderr, "============\n"); } void blurb(const int ii, const pid_t* kidpid) { int kidstatus; /*pid_t somekid = */ waitpid(kidpid[ii], &kidstatus, WUNTRACED); if (WIFEXITED(kidstatus)) fprintf(stderr, "kid #%d (%d) exited with status %d\n", ii, kidpid[ii], WEXITSTATUS(kidstatus)); if (WIFSIGNALED(kidstatus)) fprintf(stderr, "kid #%d (%d) killed by signal %d\n", ii, kidpid[ii], WTERMSIG(kidstatus)); } // We are fussy about the argument types because we want // this to compile cleanly under g++ as well as gcc, // and each is strict about different things, such that // one or the other will complain unless everything is // done just right. // This is the way execve really behaves: // the characters are held constant // and the (char*) pointers are held constant: int Execve(char const * fn, char const * const * argv, char const * const * env) { // coerce the arg types to match the unwise declaration in unistd.h : return execve(fn, (char*const*) argv, (char*const*) env); } //////////////////////////////////////// // we have data coming in on fd 0. // and control coming in on fd 1. int main(int argc, char** argv, char const * const * env) { int verbose(1); int kidstatus; pid_t somekid; int rslt; int loose_end = 0; #ifdef SpareStuff char* slurp2_args[] = {"/home/jsd/hack/slurp2", 0}; char* echo_args[] = {"/bin/echo", "hi there", 0}; char* wc_args[] = {"/usr/bin/wc", 0}; char* cat_args[] = {"/bin/cat", 0}; char* spama_args[] = {"/usr/local/bin/spamassassin", "-e", 0}; char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0}; char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0}; const char** joblist[] = { cat_args, slurp2_args, 0 // required: zero terminates the list }; #endif typedef vector VS; vector filter; if (argc != 2) { cerr << "Usage: hi-q filter.conf" << endl; exit(1); } char* conf_name = argv[1]; ifstream conf; conf.open(conf_name); if (! conf.good()) { cerr << "hi-q: could not open filter.conf file '" << conf_name << "'" << endl; exit(1); } for (;;) { string line; if (!getline(conf, line).good()) break; istringstream parse(line); vector job; while (parse.good()){ string token; parse >> token; if (parse.fail()) break; if (token[0] == '#') break; job.push_back(token); } if (job.size()) filter.push_back(job); } if (verbose) for (vector::const_iterator job = filter.begin(); job != filter.end(); job++) { for (VS::const_iterator token = job->begin(); token != job->end(); token++){ cerr << *token << " "; } cerr << endl; } int nkids = filter.size(); vector kidpid(nkids); // indexed by kid number // At this point, there are some loop invariants; // (a) fd0 is open and ready for the next child to read, and // (b) fd1 is open but is something children can (and should) // throw away as soon as convenient. {int ii; for (ii=0; ii < nkids; ii++){ /* loop over all kids */ int datapipe[2]; int kid_end; int lastkid = (ii == nkids-1); #define flip(a,b) (lastkid ? b : a) //xx fprintf(stderr, "Top of loop %d loose: %d\n", ii, loose_end); // Create a pipe, which will be used to connect // this child's fd1 to the next child's fd0 ... // except for the last kid, which reads fd1 rather // than writing it. rslt = pipe(datapipe); if (rslt < 0) { fprintf(stderr, "hi-q: could not create datapipe: "); perror(0); panic(ex_syserr); } //xx fprintf(stderr, "pipe: %d %d\n", datapipe[0], datapipe[1]); if (loose_end) { close(0); dup2(loose_end, 0); close(loose_end); } loose_end = datapipe[flip(0,1)]; kid_end = datapipe[flip(1,0)]; kidpid[ii] = fork(); if (!kidpid[ii]) { /*** child code ***/ // Now that we are through creating pipes, get rid // of the placeholder: close(1); close(loose_end); // the reading end is none of our business rslt = dup2(kid_end, 1); if (rslt < 0) { fprintf(stderr, "hi-q: dup2(kid(%d),1) failed: ", kid_end); perror(0); exit(ex_syserr); } close(kid_end); // use fd1 instead now // OK, at this point we are set up to read fd0 // and write fd1 (except last kid reads fd1). //// probe_fd(); int ntok = filter[ii].size(); const char* prog[1+ntok]; for (int jj = 0; jj < ntok; jj++){ prog[jj] = filter[ii][jj].c_str(); } prog[ntok] = 0; rslt = Execve(prog[0], prog, env); fprintf(stderr, "hi-q: failed to exec '%s': ", prog[0]); perror(0); exit(ex_syserr); } /*** parent code ***/ if (kidpid[ii] < 0) { fprintf(stderr, "hi-q: failure to fork kid#%d: ", ii); perror(0); panic(ex_syserr); } close(kid_end); #ifdef more_testing fprintf(stderr, "forked kid #%d (%d) piping %d\n", ii, kidpid[ii], kid_end); // sleep(1); /* let kid run a while */ #endif }} // here with the whole pipeline of kids running close(0); // the reading end of stdin was // delegated to the first child {int ii; for (ii=0; ii