/* Sieve: A proxy server filter for the Scoop web log system. Copyright (C) 2005 Richard Heurtley 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 2 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 GNU General Public License for more details. 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 History: 2005-02-09: v0.01: Initial release 2005-02-09: v0.02: Fixed some bugs 2005-02-10: v0.03: Added configuration file 2005-02-15: v0.04: Added (sieve) link 2005-03-23: v0.05: Increased main buffer size to 2MB */ /**---------------------------------------------------------------------------*/ #ifdef __WATCOMC__ #define WINDOWS #endif #ifdef _MSC_VER #define WINDOWS #endif #ifndef WINDOWS #define LINUX #endif /**---------------------------------------------------------------------------*/ #ifdef WINDOWS #include #endif #include #include #include #include #include #include #ifdef LINUX #include #include #include #include #include #include #include #include #endif /**---------------------------------------------------------------------------*/ //#define DEBUG #define memeq !memcmp #define streq !strcmp #ifdef LINUX #define SOCKET int #define WSAGetLastError() errno #define closesocket close #endif /**---------------------------------------------------------------------------*/ typedef struct { double score; int len; char name[64+1]; } FILTER; #define NFILTERS (64) typedef struct { int bufferlen; int headerlen; int bodylen; int nlines; char *bodyp; char *lines[1024]; char header[65536]; char buffer[2 * 1024 * 1024]; } LOCAL; static LOCAL l; /**---------------------------------------------------------------------------*/ int BigSend(SOCKET socket, char *bufferp, int bufferlen, int *nsentp) { int rv = 0; int n; int nbytes; int nsent; int remain; char *p; p = bufferp; remain = bufferlen; nsent = 0; while (remain) { if (remain > 32768) nbytes = 32786; else nbytes = remain; n = send(socket, p, nbytes, 0); if (n != nbytes) { printf("Error while transmitting.\n"); printf("Tried to send %d bytes, sent %d bytes.\n", nbytes, n); rv = 1; goto EGRESS; } nsent += n; p += n; remain -= n; } EGRESS: if (nsentp) *nsentp = nsent; return(rv); } /**---------------------------------------------------------------------------*/ char *TimeStamp(void) { time_t timet; static char text[4+1+2+1+2 +1+ 2+1+2+1+2 +1]; timet = time(0); strftime(text, sizeof(text), "%Y-%m-%d %H:%M:%S", localtime(&timet)); return(text); } /**---------------------------------------------------------------------------*/ // parse a buffer into an array of pointers to lines void Parse(char *bufferp, int bufferlen, char *lines[], int *nlinesp) { int nlines; char *p; char *endp; endp = &bufferp[bufferlen]; // get end of buffer *endp = 0; // terminate buffer for (p = bufferp; p < endp; p++) // null out CRs { if (*p == '\r') *p = 0; } nlines = 0; for (p = bufferp; p < endp; p++) { lines[nlines++] = p; // note line for (; p < endp && *p != '\n'; p++); // find terminator *p = 0; // terminate line } *nlinesp = nlines; return; } /**---------------------------------------------------------------------------*/ int Receive(SOCKET socket, char *remotep, char *filenamep) { int rv = 0; int bufferfree; int packetlen; int line; int remain; char *bufferp; char *linep; #ifdef DEBUG FILE *filep = 0; #endif #ifndef DEBUG filenamep = 0; #endif #ifdef DEBUG filep = fopen(filenamep, "ab"); fwrite("\r\n--------\r\n", 1, 2+8+2, filep); #endif remain = -1; l.bufferlen = 0; l.headerlen = 0; bufferp = l.buffer; bufferfree = sizeof(l.buffer); for (;;) // multiple packets { packetlen = recv(socket, bufferp, bufferfree, 0); // $$$ errors if (!packetlen || packetlen == bufferfree) { l.bufferlen = 0; goto EGRESS; } #ifdef DEBUG fwrite(bufferp, 1, packetlen, filep); fflush(filep); #endif printf("%s %8d %s recv packet %d\n", TimeStamp(), packetlen, remotep, remain); fflush(stdout); bufferp += packetlen; l.bufferlen += packetlen; bufferfree -= packetlen; if (!l.headerlen) // if no header length { for // find line break ( l.bodyp = l.buffer; l.bodyp <= bufferp-4 && !memeq(l.bodyp, "\r\n\r\n", 4); l.bodyp++ ); if (l.bodyp > bufferp-4) // if no line break continue; // get next packet l.bodyp += 4; // get start of body l.headerlen = l.bodyp - l.buffer; // get header length printf("%s %8d %s recv header %d\n", TimeStamp(), l.headerlen, remotep, remain); fflush(stdout); packetlen = l.bufferlen - l.headerlen; // discount header from packet printf("%s %8d %s adj. packet %d\n", TimeStamp(), packetlen, remotep, remain); fflush(stdout); memcpy(l.header, l.buffer, l.headerlen); // make local copy Parse(l.header, l.headerlen, l.lines, &l.nlines); for (line = 0; line < l.nlines; line++) // find content length spec { linep = l.lines[line]; // "1234567890123456" if (memeq(linep, "Content-Length: ", 16)) break; } if (line >= l.nlines) // if no content length goto EGRESS; remain = atoi(&linep[16]); // get content length printf("%s %8d %s recv body spec %d\n", TimeStamp(), remain, remotep, remain); fflush(stdout); } // if (!l.headerlen) remain -= packetlen; // discount remainder if (!remain) // if read all data break; // break out of loop } // for (packet = 0; ; packet++) EGRESS: l.bodylen = l.bufferlen - l.headerlen; if (l.bodylen) { printf("%s %8d %s recv body %d\n", TimeStamp(), l.bodylen, remotep, remain); fflush(stdout); } #ifdef DEBUG if (filep) fclose(filep); #endif return(rv); } /**---------------------------------------------------------------------------*/ void Append(char **dstpp, char *srcp) { int nbytes; char *dstp; dstp = *dstpp; nbytes = strlen(srcp); memcpy(dstp, srcp, nbytes); dstp += nbytes; *dstpp = dstp; return; } /**---------------------------------------------------------------------------*/ void AppendF(char **dstpp, char *formatp, ...) { va_list valist; static char text[1024]; va_start(valist, formatp); vsprintf(text, formatp, valist); va_end(valist); Append(dstpp, text); return; } /**---------------------------------------------------------------------------*/ int main(void) { int rv = 0; int rc; int n; int len; int line; int nposts; int nnuked; int header2len; int port; int nfilters; int filter; double score; double nonescore; double sievescore; char *p; char *q; char *parenp; char *posterendp; char *linep; char *commentp; char *commentendp; char *scanp; char *scorep; char *posterp; char *bodyendp; char *versionp; FILE *filep; FILTER *filterp; FILTER *starfilterp; struct hostent *hostentp; static struct sockaddr_in sockaddrbrowser; static struct sockaddr_in sockaddrlocal; static struct sockaddr_in sockaddrserver; static SOCKET socketlisten; static SOCKET socketbrowser; static SOCKET socketserver; #ifdef WINDOWS static WSADATA wsadata; #endif static char hostname[256]; static char servername[256]; static char header2[65536]; static char filterlink[256]; static FILTER filters[NFILTERS]; /**---------------------------------------------------------------------------*/ printf("Sieve v0.05 2005-03-23\n"); printf("\n"); /**---------------------------------------------------------------------------*/ // initialize networking subsystem #ifdef WINDOWS rc = WSAStartup(MAKEWORD(1,1), &wsadata); // version 1.1 if (rc) { rc = WSAGetLastError(); printf("Unable to start socket service: rc = %d.\n", rc); rv = 1; goto EGRESS; } #endif /**---------------------------------------------------------------------------*/ // parse configuration file // get default values strcpy(servername, "www.ip-wars.net"); gethostname(hostname, sizeof(hostname)); port = 4004; nonescore = 0.0; nfilters = 0; starfilterp = 0; sievescore = 0.0; // read file filep = fopen("sieve.conf", "r"); if (!filep) goto NOCONFIG; l.bufferlen = fread(l.buffer, 1, sizeof(l.buffer), filep); fclose(filep); if (!l.bufferlen || l.bufferlen == sizeof(l.buffer)) goto NOCONFIG; l.buffer[l.bufferlen] = 0; // terminate buffer // reduce whitespace for (p = l.buffer, q = p+1; *p; p++, q++) { if (*p == '\t') *p = ' '; if (*p != ' ') continue; if (*q == ' ' || *q == '\r' || *q == '\n' || *q == 0) { strcpy(p, q); p--; q--; } } // parse file Parse(l.buffer, l.bufferlen, l.lines, &l.nlines); for (line = 0; line < l.nlines; line++) { linep = l.lines[line]; if (linep[0] == '#') // skip comments continue; if (memeq(linep, "server", 6)) strcpy(servername, &linep[6+1]); else if (memeq(linep, "host", 4)) strcpy(hostname, &linep[4+1]); else if (memeq(linep, "port", 4)) port = atoi(&linep[4+1]); else if (memeq(linep, "sievescore", 10)) sievescore = atof(&linep[10+1]); else if (memeq(linep, "none", 4)) nonescore = atof(&linep[4+1]); else if (isdigit(linep[0])) { score = atof(linep); p = strchr(linep, ' '); if (!p) continue; p++; if (!*p) continue; if (nfilters >= NFILTERS) continue; filterp = &filters[nfilters]; filterp->score = score; strcpy(filterp->name, p); filterp->len = strlen(filterp->name); nfilters++; if (streq(filterp->name, "*")) // note catchall starfilterp = filterp; } } NOCONFIG: // if no PR filter then add one p = "Potential Recruit"; for (filter = 0; filter < nfilters; filter++) { filterp = &filters[filter]; if (streq(filterp->name, p)) break; } if (filter >= nfilters && nfilters < NFILTERS) { filterp = &filters[nfilters]; filterp->score = 3.0; strcpy(filterp->name, p); filterp->len = strlen(filterp->name); nfilters++; } /**---------------------------------------------------------------------------*/ // report configuration printf("servername: \"%s\"\n", servername); printf(" hostname: \"%s\"\n", hostname); printf(" port: %d\n", port); printf(" nonescore: %4.2f\n", nonescore); printf("sievescore: %4.2f\n", sievescore); // "123456#.##: for (filter = 0; filter < nfilters; filter++) { filterp = &filters[filter]; // "123456#.## printf(" %4.2f: \"%s\"\n", filterp->score, filterp->name); } printf("\n"); /**---------------------------------------------------------------------------*/ // get web server address hostentp = gethostbyname(servername); if (!hostentp) { rc = WSAGetLastError(); printf("Unable to resolve server \"%s\": rc = %d.\n", servername, rc); rv = 1; goto EGRESS; } sockaddrserver.sin_family = AF_INET; sockaddrserver.sin_port = htons(80); memcpy(&sockaddrserver.sin_addr, hostentp->h_addr, hostentp->h_length); printf("Server \"%s\" resolves to \"%s\"\n", servername, inet_ntoa(*(struct in_addr *)hostentp->h_addr)); printf("\n"); /**---------------------------------------------------------------------------*/ // allocate listening socket socketlisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #ifdef WINDOWS if (socketlisten == INVALID_SOCKET) #else if (socketlisten < 0) #endif { rc = WSAGetLastError(); printf("Unable to create socket: rc = %d.\n", rc); rv = 1; goto EGRESS; } // allow quick reuse of the socket #ifdef LINUX n = 1; setsockopt(socketlisten, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); #endif // configure listening socket p = strchr(hostname, '.'); if (!p) { printf("Warning: The hostname of this computer \"%s\" does not contain a dot ('.').\n", hostname); printf("Cookies cannot be set on a computer if its hostname does not contain a dot.\n"); printf("I have taken the liberty of appending \".localdomain\" to the hostname.\n"); printf("This probably won't resolve and you may have to add a line to your hosts file.\n"); printf("\n"); strcat(hostname, ".localdomain"); } hostentp = gethostbyname(hostname); if (!hostentp) { rc = WSAGetLastError(); printf("Unable to resolve hostname \"%s\": rc = %d.\n", hostname, rc); rv = 1; goto EGRESS; } sockaddrlocal.sin_family = AF_INET; sockaddrlocal.sin_port = htons((unsigned short)port); memcpy(&sockaddrlocal.sin_addr, hostentp->h_addr, hostentp->h_length); printf("Hostname \"%s\" resolves to \"%s\"\n", hostname, inet_ntoa(*(struct in_addr *)hostentp->h_addr)); printf("\n"); // bind local address to listening socket rc = bind(socketlisten, (struct sockaddr *)&sockaddrlocal, sizeof(sockaddrlocal)); if (rc) { rc = WSAGetLastError(); printf("Unable to bind address to socket: rc = %d.\n", rc); rv = 1; goto EGRESS; } // listen for connections rc = listen(socketlisten, 1); if (rc) { rc = WSAGetLastError(); printf("Unable to listen to socket: rc = %d.\n", rc); rv = 1; goto EGRESS; } for (;;) // infinite loop of connections { /**---------------------------------------------------------------------------*/ // accept connection from browser n = sizeof(sockaddrbrowser); socketbrowser = accept(socketlisten, (struct sockaddr *)&sockaddrbrowser, &n); #ifdef WINDOWS if (socketbrowser == SOCKET_ERROR) #else if (socketbrowser < 0) #endif { rc = WSAGetLastError(); printf("Unable to accept connection: rc = %d.\n", rc); rv = 1; goto EGRESS; } // receive request from browser rv = Receive(socketbrowser, "browser", "request.txt"); if (rv) { printf("Error receiving data from browser.\n"); goto EGRESS; } if (!l.nlines) // header parsed by Receive() { header2len = l.headerlen; memcpy(header2, l.buffer, header2len); // copy original header goto SKIP; } /**---------------------------------------------------------------------------*/ // local request processing // "012345678901234" posterp = strstr(l.lines[0], "/sieve/filter/"); if (posterp) { if (sievescore == 0.0) goto NOLOCAL; posterp += 14; posterendp = strchr(posterp, '/'); if (!posterendp) goto NOLOCAL; *posterendp = 0; for (;;) { p = strchr(posterp, '%'); if (!p) break; *p = ' '; p++; memcpy(p, p+2, posterendp - (p+2) + 1); } l.bodyp = l.buffer; p = l.bodyp; Append(&p, "\r\n"); Append(&p, "\r\n"); Append(&p, "\r\n"); Append(&p, "Sieve: A proxy server filter for the Scoop web log system\r\n"); Append(&p, "\r\n"); Append(&p, "\r\n"); Append(&p, "\r\n"); Append(&p, "

\r\n"); Append(&p, "Sieve: A proxy server filter for the Scoop web log system\r\n"); Append(&p, "

\r\n"); for (filter = 0; filter < nfilters; filter++) { filterp = &filters[filter]; if (streq(filterp->name, posterp)) break; } if (filter < nfilters) AppendF(&p, "Username \"%s\" is already listed in sieve.conf.\r\n", posterp); else { filep = fopen("sieve.conf", "a"); // append to end if (filep) { fprintf(filep, "%4.2f %s\n", sievescore, posterp); fclose(filep); } if (nfilters < NFILTERS) // activate { filterp = &filters[nfilters]; filterp->score = sievescore; strcpy(filterp->name, posterp); filterp->len = strlen(filterp->name); nfilters++; } AppendF(&p, "Username \"%s\" has been added to sieve.conf.\r\n", posterp); } Append (&p, "\r\n"); Append (&p, "\r\n"); l.bodylen = p - l.bodyp; versionp = posterendp + 2; // .../sieve/filter/heurtley/ HTTP/1.1 p = header2; AppendF(&p, "%s 200 OK\r\n", versionp); Append (&p, "Content-type: text/html\r\n"); AppendF(&p, "Content-length: %d\r\n", l.bodylen); Append (&p, "\r\n"); header2len = p - header2; goto SNEAK; } NOLOCAL: /**---------------------------------------------------------------------------*/ // build modified header p = header2; for (line = 0; line < l.nlines; line++) { linep = l.lines[line]; // "123456789012" if (memeq(linep, "Keep-Alive: ", 12)) // nuke keep alive headers continue; if (memeq(linep, "Connection: ", 12)) continue; // "123456" if (memeq(linep, "Host: ", 6)) AppendF(&p, "Host: %s", servername); // replace Host: else // "123456789" if (memeq(linep, "Referer: ", 9)) // restore Referer: { q = strstr(linep, "//"); if (q) { q += 2; q = strchr(q, '/'); } if (!q) Append(&p, linep); else { AppendF(&p, "Referer: http://%s", servername); Append(&p, q); // append rest of original } } else Append(&p, linep); Append(&p, "\r\n"); } header2len = p - header2; SKIP: /**---------------------------------------------------------------------------*/ // create network socket socketserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #ifdef WINDOWS if (socketserver == INVALID_SOCKET) #else if (socketserver < 0) #endif { rc = WSAGetLastError(); printf("Unable to create socket: rc = %d.\n", rc); rv = 1; goto EGRESS; } // connect to server rc = connect(socketserver, (struct sockaddr *)&sockaddrserver, sizeof(sockaddrserver)); #ifdef WINDOWS if (rc == SOCKET_ERROR) #else if (rc) #endif { rc = WSAGetLastError(); printf("Unable to connect to web server: rc = %d.\n", rc); rv = 1; goto EGRESS; } /**---------------------------------------------------------------------------*/ // send request to server in two parts rv = BigSend(socketserver, header2, header2len, &n); if (rv) { printf("Error while sending header to server.\n"); rv = 1; goto EGRESS; } if (n != header2len) { printf("Error while sending header to server.\n"); printf("Tried to send %d bytes, sent %d bytes.\n", header2len, n); rv = 1; goto EGRESS; } printf("%s %8d server xmit header\n", TimeStamp(), header2len); fflush(stdout); if (l.bodylen) { rv = BigSend(socketserver, l.bodyp, l.bodylen, &n); if (rv) { printf("Error while sending body to server.\n"); rv = 1; goto EGRESS; } if (n != l.bodylen) { printf("Error while sending body to server.\n"); printf("Tried to send %d bytes, sent %d bytes.\n", l.bodylen, n); rv = 1; goto EGRESS; } printf("%s %8d server xmit body\n", TimeStamp(), l.bodylen); fflush(stdout); } #ifdef DEBUG filep = fopen("request.txt", "ab"); fwrite("\r\n--------\r\n", 1, 2+8+2, filep); fwrite(header2, 1, header2len, filep); fwrite(l.bodyp, 1, l.bodylen, filep); fclose(filep); #endif /**---------------------------------------------------------------------------*/ // get reply from server rv = Receive(socketserver, "server", "reply.txt"); if (rv) { printf("Error while receiving from server.\n"); goto EGRESS; } closesocket(socketserver); /**---------------------------------------------------------------------------*/ // massage reply body nposts = 0; nnuked = 0; bodyendp = &l.bodyp[l.bodylen]; *bodyendp = 0; // treat body like one big string scanp = l.bodyp; for (;;) { commentp = strstr(scanp, ""); if (!commentp) break; scanp = commentp + 1; // "12345678901234567890" commentendp = strstr(commentp, ""); if (!commentendp) continue; commentendp += 20; posterp = strstr(commentp, ">by "); if (!posterp) continue; posterp += 4; posterendp = strstr(posterp, " on"); if (!posterendp) continue; parenp = strstr(posterp, " ("); if (parenp && parenp < posterendp) posterendp = parenp; for (; *posterendp == ' '; posterendp--); posterendp++; len = posterendp - posterp; for (filter = 0; filter < nfilters; filter++) { filterp = &filters[filter]; if (filterp->len == len && memeq(posterp, filterp->name, filterp->len)) break; } if (filter >= nfilters) // if no exact match { if (starfilterp) // if catchall exists filterp = starfilterp; // use it else // otherwise filterp = 0; // no filter } scorep = strstr(commentp, "showrate"); if (!scorep) continue; scorep = strchr(scorep, '>'); if (!scorep) continue; scorep++; // point to score if (memeq(scorep, "none", 4)) // if no score yet score = nonescore; // use nonescore else // otherwise score = atof(scorep); // get score if (filterp) // if a filter is in effect { nposts++; // count post if (score <= filterp->score) // if low score { memcpy // nuke post, keep null ( commentp, commentendp, bodyendp - commentendp + 1 ); len = commentendp - commentp; l.bodylen -= len; // adjust body length bodyendp -= len; // adjust body endp nnuked++; // count nuked post continue; // test next post } } if // if ( filter >= nfilters // no exact match && // and sievescore != 0.0 // sievescore is defined && // and score <= sievescore // post would have been nuked ) { *posterendp = 0; // delimit posterp sprintf ( filterlink, " (sieve)", hostname, port, posterp ); *posterendp = ' '; // restore char len = strlen(filterlink); memmove // make room, keep null ( posterendp + len, posterendp, bodyendp - posterendp + 1 ); l.bodylen += len; // adjust body length bodyendp += len; // adjust body endp memcpy(posterendp, filterlink, len); // insert filter link } } // for (;;) if (nposts) printf("%s %8d posts tested, %d removed\n", TimeStamp(), nposts, nnuked); /**---------------------------------------------------------------------------*/ // build modified header p = header2; for (line = 0; line < l.nlines; line++) { linep = l.lines[line]; // "1234567890123456" if (memeq(linep, "Content-Length: ", 16)) AppendF(&p, "Content-Length: %d", l.bodylen); else // "123456789012" if (memeq(linep, "Set-Cookie: ", 12)) { // "1234567" q = strstr(linep, "domain="); if (!q) Append(&p, linep); else { q += 7; *q = 0; Append(&p, linep); Append(&p, hostname); } } else Append(&p, linep); Append(&p, "\r\n"); } header2len = p - header2; /**---------------------------------------------------------------------------*/ // send reply to browser in two parts SNEAK: rv = BigSend(socketbrowser, header2, header2len, &n); if (rv) { printf("Error while sending header to browser.\n"); rv = 1; goto EGRESS; } if (n != header2len) { printf("Error while sending header to browser.\n"); printf("Tried to send %d bytes, sent %d bytes.\n", header2len, n); rv = 1; goto EGRESS; } printf("%s %8d browser xmit header\n", TimeStamp(), header2len); fflush(stdout); if (l.bodylen) { rv = BigSend(socketbrowser, l.bodyp, l.bodylen, &n); if (rv) { printf("Error while sending body to browser.\n"); rv = 1; goto EGRESS; } if (n != l.bodylen) { printf("Error while sending body to browser.\n"); printf("Tried to send %d bytes, sent %d bytes.\n", l.bodylen, n); rv = 1; goto EGRESS; } printf("%s %8d browser xmit body\n", TimeStamp(), l.bodylen); fflush(stdout); } #ifdef DEBUG filep = fopen("reply.txt", "ab"); fwrite("\r\n--------\r\n", 1, 2+8+2, filep); fwrite(header2, 1, header2len, filep); fwrite(l.bodyp, 1, l.bodylen, filep); fclose(filep); #endif /**---------------------------------------------------------------------------*/ closesocket(socketbrowser); } // for (;;) listen loop EGRESS: closesocket(socketbrowser); closesocket(socketlisten); closesocket(socketserver); // deinitialize networking #ifdef WINDOWS if (wsadata.wVersion) WSACleanup(); #endif return(rv); }