#include <fstream.h>
#include <iostream.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <signal.h>
#include "server.h"
#include "multipart.h"
#include "findfile.h"
#include "parsecfgfile.h"
#include "grouplist.h"
#include "screen.h"
#include "MessageIdStore.h"
#include <strings.h>
#include <regex.h>
/* Prototype for signal handler */
void cleanUp(int);
int NNTP_PORT = 119;
char *NNTP_SERVER = NULL;
int minLines = -1; // minimum amount of lines; -1 disables check
int maxLines = -1; // maximum amount of lines; -1 disables check
groupListPointer NNTP_GROUP = new groupList();
int spamThreshold = -1;
char *authUser = NULL, *authPass = NULL;
Screen *screen = NULL;
bool quiet = false;
bool regexp = false;
regex_t regex;
Parser *parser = NULL; //CFG file parser. Defined in parsecfgfile.h
char *cfgfilename=NULL; // name of cfg file if its something else that default
char *logfilename=NULL; // name of log file, if its NULL then no log file
bool checkSpamSenders = false;
bool checkSpamSubjects = false;
bool printMessageProcess = false;
void
cleanUp(int sig)
{
int i;
delete screen;
ofstream tfile;
if(logfilename != NULL) {
tfile.open(logfilename,ios::app);
tfile << "\n!! Caught Signal "<< sig << ", cleaning up\n";
}
fprintf(stderr,"Caught Signal %d, cleaning up\n\n",sig);
tfile.close();
if(regexp) {
regfree(®ex);
}
exit(0);
}
void missing_helper() {
fprintf(stderr,"Error! This program requires mmencode (MIME) and ");
fprintf(stderr,"uudecode (Unix-to-Unix)\n\tto be present on your ");
fprintf(stderr,"system and in your path.\n");
exit(-1);
}
void need_authentication() {
delete screen;
fprintf(stderr,"The news server you are attempting to connect to requires\n");
fprintf(stderr,"authentication. Please use the -a parameter.\n");
exit(-1);
}
void bad_authentication() {
fprintf(stderr,"The username/password you are trying to use are invalid.\n");
exit(-1);
}
//int outputname=0; // only for debugging
void fork_uudecode(char *outfile, int multiPart) {
/* if outputfile is NULL, then return */
if(outfile == NULL) {
screen->addTextToScrollWindow("Malformed message, skipping...\n");
return;
}
screen->lastDecode(outfile);
if (multiPart)
screen->encodedAs("UUEncode (Multipart)");
else
screen->encodedAs("UUEncode");
screen->addTextToScrollWindow("UUDecoding: ");
screen->addTextToScrollWindow(outfile);
screen->addTextToScrollWindow("\n");
//outputname++;
if (fork()) {
// This is the parent
wait(NULL);
unlink("outfile.txt");
} else {
// This is the child
/* FILE *devnull = fopen("/dev/null","w");
dup2(fileno(devnull),fileno(stdout));
dup2(fileno(devnull),fileno(stderr));
dup2(fileno(devnull),fileno(stdin));
char name[200];
sprintf(name,"outfile.txt%d\0", outputname);
execlp("cp","cp","outfile.txt",name,NULL);*/
FILE *msgfile;
msgfile = fopen("outfile.txt","r");
dup2(fileno(msgfile),fileno(stdin));
FILE *devnull = fopen("/dev/null","w");
dup2(fileno(devnull),fileno(stdout));
dup2(fileno(devnull),fileno(stderr));
execlp("uudecode","uudecode",NULL);
}
unlink("core"); // if it fucked up
}
void fork_mime_decode(char *outputfile) {
/* if outputfile is NULL, then return */
if(outputfile == NULL) {
screen->addTextToScrollWindow("Malformed message, skipping...\n");
return;
}
screen->lastDecode(outputfile);
screen->encodedAs("Base64 MIME");
screen->addTextToScrollWindow("MIME decoding: ");
screen->addTextToScrollWindow(outputfile);
screen->addTextToScrollWindow("\n");
if (fork()) {
// This is the parent
wait(NULL);
unlink("outfile.txt");
} else {
// This is the child
FILE *devnull = fopen("/dev/null","w");
dup2(fileno(devnull),fileno(stdout));
dup2(fileno(devnull),fileno(stderr));
dup2(fileno(devnull),fileno(stdin));
execlp("mmencode","mmencode","-u","outfile.txt","-o",outputfile,NULL);
}
unlink("core"); // if it fucked up
}
bool check_spam(bool subject, char *actline) {
char *actspam;
char *tmpspam;
if (subject)
actspam = strdup(parser->spamsubjects);
else
actspam = strdup(parser->spamsenders);
tmpspam = strtok(actspam,"\377");
while (tmpspam !=NULL) {
if (strstr(actline,tmpspam) != NULL) {
free(actspam);
return TRUE;
}
/* tmpspam = strsep(&actspam,"\377"); */
tmpspam = strtok(actspam,NULL);
}
free(actspam);
return FALSE;
}
messageP process_message_header(Server *s, int *isSpam, int *iLines) {
char *tline;
messageP myMessage = NULL;
s->writeport("HEAD\n");
char tmpstring[80];
tline = s->nextline();
if ((tline[0] == '4') && (tline[3] == ' ')) { // A 4xx error message
screen->addTextToScrollWindow("Message HEADER missing\n");
free(tline);
return NULL; // Return error
}
char *msgID = strtok(tline," "); msgID = strdup(strtok(NULL," "));
screen->msgID(msgID);
// Loop until the first blank line.
// First line after blank is first message text line.
do {
if (tline) free(tline);
tline = s->nextline();
if (strncmp(tline,"Subject:",8)==0) {
myMessage = new message(tline);
if (!(*isSpam)) {
ofstream tfile;
if(logfilename != NULL) {
tfile.open(logfilename,ios::app);
tfile << "MsgID-" << msgID << "::" << (char *)tline+9 << "\n";
}
screen->subject((char *)(tline+9));
tfile.close();
}
if (checkSpamSubjects && check_spam(TRUE,tline+9)) {
*isSpam = TRUE;
sprintf(tmpstring,"Spam: %s",tline+9);
screen->subject(tmpstring);
}
// Check if regexs are to be used.
if( regexp && parser->regex != NULL) {
//screen->addTextToScrollWindow("Checking regex Match.\n");
screen->line("checking regex ");
int match;
if((match = regexec(®ex,tline+9,0,NULL,0)) == 0) {
//screen->addTextToScrollWindow("Regex Match.\n");
// NOP
}
else {
screen->line("No regex match");
//screen->addTextToScrollWindow("Regex didn't match.\n");
*isSpam = TRUE;
}
}
}
else if (strncmp(tline,"From:",5)==0) {
if (checkSpamSenders && check_spam(FALSE,tline+6)) {
*isSpam = TRUE;
sprintf(tmpstring,"Spam: %s",tline);
screen->subject(tmpstring);
}
}
else if (strncmp(tline,"Lines:",6)==0) {
char *temp;
temp = strstr(tline," ");
temp++;
*iLines = atoi(temp);
if ((minLines != -1)&&(minLines > *iLines)) {
*isSpam = TRUE;
sprintf(tmpstring,"Skipping small message (%d lines)...",*iLines);
screen->subject(tmpstring);
}
if ((maxLines != -1)&&(maxLines < *iLines)) {
*isSpam = TRUE;
sprintf(tmpstring,"Skipping large message (%d lines)...",*iLines);
screen->subject(tmpstring);
}
} else if ((spamThreshold != -1)&&(strncmp(tline,"Newsgroups:",11))) {
int tmp1 = 1;
for (int w = 0; w < strlen(tline); w++)
if (tline[w] == ',') tmp1++;
if (tmp1 > spamThreshold) {
*isSpam = TRUE;
sprintf(tmpstring,"Skipping spam message (%d cross-posts)...",tmp1);
screen->subject(tmpstring);
}
}
} while (!(tline[0]=='.'));
free(tline);
return myMessage;
}
void read_article(Server *s) {
char *outfile;
messageP myMessage;
int isSpam = FALSE;
int iLines = 0;
myMessage = process_message_header(s, &isSpam, &iLines);
if( myMessage == NULL )
return;
if ((!isSpam)&&(myMessage)) {
s->writeport("BODY\n");
ofstream tfile;
if (myMessage->isMultiPart())
tfile.open(myMessage->getOutFName(),ios::app); // ios::app for append
else
tfile.open("outfile.txt",ios::app);
int done = FALSE;
int isBase64 = FALSE;
int isUUEncode = FALSE;
int isBase64Data = FALSE;
int iCurrentLine = 0;
if (myMessage->isMultiPart()) {
outfile = strdup(myMessage->getFileName());
isUUEncode = TRUE; // Assume it's a UUEncoded multipart
// change later if see MIME signature
} else outfile = NULL;
do {
if ( printMessageProcess )
screen->messageProgress(iCurrentLine, iLines);
iCurrentLine++;
char *nline = s->nextline();
if ((iCurrentLine == 1) &&
(nline[0] == '4') &&
(nline[3] == ' ')) {
screen->addTextToScrollWindow("Message BODY unavailable\n");
done = TRUE; // causes us to move on to the next message
}
if (strstr(nline,"This is a multipart message in MIME format.") && isUUEncode)
isUUEncode = FALSE;
if ((!isBase64)&&(strncmp(nline,"Content-Transfer-Encoding:",26)==0))
isBase64 = TRUE; // Identifies MIME decodes
if (strncmp(nline, "/9", 2) == 0)
isBase64Data = TRUE;
if (strcmp(nline,".")==0) // End of message
done = TRUE;
else if (strncmp(nline,"begin ",6)==0) {
// This part gets the filename and the start of a UUDecode
isUUEncode = TRUE;
outfile = index(nline,' '); outfile++;
outfile = index(outfile,' '); outfile++;
outfile = strdup(outfile);
// Filenames that start with dots and filenames
// with no dots are invalidated.
if ((outfile[0]=='.')||(index(outfile,'.')==NULL)) {
free(outfile);
isUUEncode = FALSE;
}
if (isUUEncode) {
// Change spaces in filename to underscores and remove leading
// dashes that make it impossible to "rm" the file from the shell
for (int i = 0; i < strlen(outfile); i++)
if (outfile[i] == ' ') outfile[i] = '_';
while (outfile[0] == '-')
outfile++;
tfile << "begin 644 " << outfile << "\n";
}
// } else if (strncmp(nline,"Content-Disposition: ",21)==0) {
} else if (strncmp(nline," filename=",10)==0) {
// This parts get the filename for a MIME decode
outfile = strstr(nline,"\"");
if (outfile) {
outfile++;
outfile[strlen(outfile)-1] = 0;
outfile = strdup(outfile);
}
} else if ((isBase64&&isBase64Data)||isUUEncode) {
if (strncmp(nline,"222 ",4)==0) {} // Discard header
else if ( nline[0] == 0 ) {} // Discard null line
else {
tfile << nline << "\n";
}
}
free(nline);
} while (!done);
tfile.close();
if (myMessage->isMultiPart()&&myMessage->haveAllParts()) {
myMessage->joinParts();
if (isUUEncode) fork_uudecode(myMessage->getFileName(),TRUE);
else fork_mime_decode(outfile);
} else if ( !myMessage->isMultiPart() ) {
if ( isUUEncode ) fork_uudecode(outfile,FALSE);
else if (isBase64) fork_mime_decode(outfile);
else unlink("outfile.txt");
} else unlink("outfile.txt");
} // isSpam
return;
}
void usage_error(char *progname) {
fprintf(stderr,"Usage: %s [OPTION]... grouplist\n\n", progname);
fprintf(stderr,"grouplist newsgroups to read\n");
fprintf(stderr,"-S name name of the news server\n");
fprintf(stderr," (if not specified, environ. var. NNTPSERVER is used )\n");
fprintf(stderr,"-P port news server port (default 119)\n");
fprintf(stderr,"-n don't output to stdout\n");
fprintf(stderr,"-f fork to background. use only with -n\n");
fprintf(stderr,"-s num consider messages posted to more than\n");
fprintf(stderr," 'num' groups to be spam and don't read\n");
fprintf(stderr," them. 'num' must be greater than 0.\n");
fprintf(stderr,"-a user pass Authenticate the login to the server with\n");
fprintf(stderr," 'user' as username and 'pass' as password.\n");
fprintf(stderr," Both username and password must be present.\n");
fprintf(stderr,"-c filename Specify a configuration file other that the default one (.bgrabrc)\n");
fprintf(stderr,"-l filename Specify a log file if one is desired\n");
fprintf(stderr,"-r Match against a regex (in .bgrabrc)\n");
fprintf(stderr,"-p Display message downloading progress\n");
fprintf(stderr,"-m num Minimum message's lines count\n");
fprintf(stderr,"-M num Maximum message's lines count\n");
exit(-1);
}
void process_command_line(int argc, char **argv) {
int output_off = FALSE;
int want_fork = FALSE;
spamThreshold = -1;
minLines = -1;
maxLines = -1;
for (int i = 1; i < argc; i++) {
char *parm = argv[i];
if (parm[0] == '-') {
switch (parm[1]) {
case 'n':
quiet = TRUE;
break;
case 'f':
quiet = TRUE;
want_fork = TRUE;
break;
case 'r':
regexp = TRUE;
break;
case 'P':
i++;
if (i > argc) {
usage_error(argv[0]);
break;
}
NNTP_PORT = atoi(argv[i]);
break;
case 'S':
i++;
if (i > argc) {
usage_error(argv[0]);
break;
}
NNTP_SERVER = argv[i];
break;
case 's': i++;
if (i == argc) {
usage_error(argv[0]);
break;
}
spamThreshold = atoi(argv[i]);
if (spamThreshold < 1)
spamThreshold = -1;
break;
case 'a':
i++;
if (i+1 > argc)
usage_error(argv[0]);
authUser = strdup(argv[i]);
i++;
authPass = strdup(argv[i]);
break;
case 'l':
i++;
if (i == argc) {
usage_error(argv[0]);
break;
}
logfilename = argv[i];
break;
case 'c':
i++;
if (i == argc) {
usage_error(argv[0]);
break;
}
cfgfilename = argv[i];
break;
case 'p':
printMessageProcess = TRUE;
break;
case 'm':
i++;
if (i == argc) {
usage_error(argv[0]);
break;
}
minLines = atoi(argv[i]);
if (minLines < 1)
minLines = -1;
break;
case 'M':
i++;
if (i == argc) {
usage_error(argv[0]);
break;
}
maxLines = atoi(argv[i]);
if (maxLines < 1)
maxLines = -1;
break;
default:
usage_error(argv[0]);
break;
}
}
else if (parm[0] != 0) NNTP_GROUP->addGroup(parm);
}
if (NNTP_GROUP->numGroups() == 0) usage_error(argv[0]); // No group name
if (want_fork&&output_off) {
if (fork()) exit(-1);
} else if (want_fork) usage_error(argv[0]);
}
int main(int argc, char **argv) {
char tmpstring[256];
int regError;
if ( (!findfile("mmencode")) || (!findfile("uudecode")) )
missing_helper();
/* install signal handlers */
signal(SIGHUP,cleanUp);
signal(SIGINT,cleanUp);
signal(SIGQUIT,cleanUp);
signal(SIGABRT,cleanUp);
signal(SIGKILL,cleanUp);
signal(SIGTERM,cleanUp);
if (getenv("NNTPGROUP")) NNTP_GROUP->addGroup( getenv("NNTPGROUP") ); // Check env for group to be checked
process_command_line(argc, argv);
if ( cfgfilename ) // Parse the config file
parser = new Parser(cfgfilename);
else
parser = new Parser();
if ( !NNTP_SERVER ) // If NNTP_SERVER hasn't been set on command line
{
if ( parser->nntpserver ) // from config file
NNTP_SERVER = strdup( parser->nntpserver );
else if (getenv("NNTPSERVER")) // from env. variable
NNTP_SERVER = strdup(getenv("NNTPSERVER"));
else
NNTP_SERVER = strdup("news"); // default
}
if( !authUser && parser->user )
authUser = strdup( parser->user );
if( !authPass && parser->passwd )
authPass = strdup( parser->passwd );
if (strcmp(parser->spamsenders,"") != 0)
checkSpamSenders = TRUE;
if (strcmp(parser->spamsubjects,"") != 0)
checkSpamSubjects = TRUE;
screen = new Screen( quiet );
screen->newsServer( NNTP_SERVER );
screen->addTextToScrollWindow("Connecting to NNTP server... ");
ServerP s = new Server(NNTP_SERVER, NNTP_PORT);
if( s->sockfd() == -1 )
{
screen->addTextToScrollWindow("Connection failed\n");
screen->addTextToScrollWindow("NNTP Server: ");
screen->addTextToScrollWindow(NNTP_SERVER);
screen->addTextToScrollWindow("\n NNTP Port: ");
char temp[9];
sprintf(temp,"%i\n",NNTP_PORT);
screen->addTextToScrollWindow(temp);
sleep(5);
exit(99);
}
else
screen->addTextToScrollWindow("done\n");
char *tmp = s->nextline(); // Read welcome line (200)
if (authUser&&authPass) {
s->writeport("AUTHINFO USER %s\n", authUser);
free(s->nextline());
s->writeport("AUTHINFO PASS %s\n", authPass);
char *tmp2 = s->nextline();
if (strncmp(tmp2, "502", 3) == 0) bad_authentication();
free(tmp2);
}
mkdir("unfinished",0755);
int numberOfMessagesInNewsgroup=0;
int lastMessageInNewsgroup=0;
int firstMessageInNewsgroup=0;
int currentMessageId=0;
// If using regexpressions compile the regex.
if(regexp && parser->regex != NULL)
if( (regError = regcomp(®ex,parser->regex,0)) != 0) {
screen->addTextToScrollWindow("** REGEX FAILED TO COMPILE !!!\n");
regerror(regError,®ex,tmpstring,sizeof(tmpstring));
screen->addTextToScrollWindow(" Error is: ");
screen->addTextToScrollWindow(tmpstring);
sleep(5);
delete screen;
ofstream tfile;
if(logfilename != NULL) {
tfile.open(logfilename,ios::app);
tfile << "\n!! REGEX FAILED TO COMPILE, cleaning up\n";
}
fprintf(stderr,"!! REGEX FAILED TO COMPILE, cleaning up\n\n");
fprintf(stderr,"\tError: %d, for \"%s\"\n\t- \"%s\"\n\n",
regError,parser->regex,tmpstring);
tfile.close();
exit(0);
} else {
screen->addTextToScrollWindow("Regex compiled OK.\n");
}
while (NNTP_GROUP->numGroups() > 0) {
// For each group do: specify group, check for good response from server
// (211), make msgid trees, and download all msgs with id not in the trees
char *THIS_GROUP = NNTP_GROUP->nextGroup();
screen->newsgroup(THIS_GROUP);
screen->addTextToScrollWindow("Selecting group: ");
screen->addTextToScrollWindow(THIS_GROUP);
screen->addTextToScrollWindow("\n");
screen->addTextToScrollWindow(" Info: ");
s->writeport("GROUP %s\n", THIS_GROUP);
char *tmp2 = s->nextline(); // Read group ID line
screen->addTextToScrollWindow(tmp2);
screen->addTextToScrollWindow("\n");
char *r = strtok(tmp2," "); // Parse for Last ID
r = strtok(NULL," "); numberOfMessagesInNewsgroup = atoi( r );
r = strtok(NULL," "); firstMessageInNewsgroup = atoi( r );
r = strtok(NULL," "); lastMessageInNewsgroup = atoi( r );
screen->msgIDlast( r ); // Put last ID on output
if (strncmp(tmp2, "480", 3) == 0) need_authentication();
if (strncmp(tmp2, "411", 3) == 0) { // Bad group name
screen->addTextToScrollWindow("The server doesn't have the group ");
screen->addTextToScrollWindow(THIS_GROUP);
screen->addTextToScrollWindow("\n");
continue; // continue with the other groups in que
}
//if (strncmp(tmp2, "211", 3) != 0); // Bad response from server
// Make trees
MessageIdStore myOldTree;
myOldTree.loadSetFromFile(THIS_GROUP);
myOldTree.setFilename("oldTree");
//myOldTree->setGroupName("oldTree");
// myOldTree->printDepth();
MessageIdStore myNewTree;
myNewTree.setFilename(THIS_GROUP);
// printf("\n");
// myNewTree->printDepth();
unlink("outfile.txt");
int done = FALSE;
s->writeport("STAT\n");
while (!done) {
char *reply = s->nextline();
char *msgNum, *msgID, *respnum;
respnum = strtok(reply," ");
if (strncmp(respnum,"421",3)==0 || strncmp(respnum,"423",3)==0)
done = TRUE;
else {
msgNum = strtok(NULL, " ");
msgID = strtok(NULL," ");
currentMessageId = atoi( msgNum );
//screen->percentLeft( (float)currentMessageId );//- (float)firstMessageInNewsgroup );
if (myOldTree.insert(msgID)) { // Check if already read
myNewTree.insert(msgID); // Insert into new database too
read_article(s);
}
else {
myNewTree.insert(msgID); // Insert into new database
screen->msgID(msgNum);
screen->subject("Skipping previously read message...");
if ( printMessageProcess )
screen->messageProgress(0,0);
}
}
free(reply);
s->writeport("NEXT\n");
}
myOldTree.saveSetToFile("oldTree");
myNewTree.saveSetToFile(THIS_GROUP);
// printf("\n");
// myNewTree->printDepth();
//delete myOldTree;
//delete myNewTree;
unlink("outfile.txt");
}
screen->addTextToScrollWindow("Run Complete, system exiting...\n");
sleep(4); // used in debugging
delete screen;
exit(0);
}
syntax highlighted by Code2HTML, v. 0.9.1