#include #include #include #include #include #include #include #include #include #include #include "server.h" #include "multipart.h" #include "findfile.h" #include "parsecfgfile.h" #include "grouplist.h" #include "screen.h" #include "MessageIdStore.h" #include #include /* 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); }