#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(&regex);
  }
  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(&regex,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(&regex,parser->regex,0)) != 0) {
	screen->addTextToScrollWindow("** REGEX FAILED TO COMPILE !!!\n");
	regerror(regError,&regex,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