/*
 * modfied ward source: 
 * -logs traffic when CONNECTed.
 * -tries "\r" twice for some reason ;> 
 * -tries "..." for some other reason ;>
 * ...the log will give evidence hrhrhr
 * 
 * btw: raptor`s source smells like b4b0`s 0xd34df337... ;> 
 * dispile with: gcc -o w4rd w4rd.c -lm
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <signal.h>
#include <termios.h>
#include <sys/time.h>
#include <math.h>

/* general disinformation */
#define	NAME		"w4rd.c"
#define VERSION		"v-2.1"
#define DESCRIPTION	"worst wardialer for unix systems (fee/boo/blah)"
#define	COPYRIGHT	"Copyknight (k) 2000-2004"
#define AUTHOR		"Knecht Rootrecht <unx@0xd34df337.info>"

/* getopt() bit masks */
#define OPT_NMASK 	0x01
#define OPT_RAND	0x02
#define OPT_GENERATE	0x04
#define OPT_SCAN	0x08
#define OPT_TIMEOUT	0x10
#define OPT_DEVICE	0x20

/* color definitions */
#define GREEN 		"\E[32m\E[1m"
#define YELLOW		"\E[33m\E[1m"
#define RED		"\E[31m\E[1m"
#define BLUE		"\E[34m\E[1m"
#define BRIGHT		"\E[m\E[1m"
#define NORMAL		"\E[m"

/* local setup, change if needed */
#define MODEM_DEV 	"/dev/modem"	// default modem device
#define MODEM_SPEED 	B19200		// default modem speed (in baud)
#define MODEM_TIMEOUT 	60		// default modem timeout
#define MAX_RETRIES 	2		// max number of dial retries
#define MAX_LEN 	4096			// max length of number, status, out
#define MAX_FILE_LEN	8192			// max length of file, dev, strbuf

/* local functions declaration */
void 	scan(char *file);
int 	dial(char *number, int retry);
void 	writefile(int last, int inc, char *file);
void 	listgen(char *mask, int inc, char *file);
int 	initmodem(char *dev);
void 	closemodem(int fd);
int 	hupmodem(int fd);
void 	sendcmd(int fd, int timewait, char *fmt, ...);
void 	cleanup(int ignored);
void 	fatalerr(char *pattern, ...);
void 	usage(char *name);

/* global variables */
int 	fd, timeout = MODEM_TIMEOUT;
struct 	termios newtio, oldtio;
char 	dev[MAX_FILE_LEN] = MODEM_DEV;
char 	**numbers;

/*
 * main()
 */
int main(int argc, char **argv) 
{
	int 	inc = 1, opt_line = 0;
	char 	mask[MAX_LEN];
	char 	file[MAX_FILE_LEN];

	/* disable buffering for stdout */
	setvbuf(stdout, NULL, _IONBF, 0);

        fprintf(stderr, "%s\n%s %s - %s\n%s %s\n\n%s", BLUE, NAME, VERSION, 
		DESCRIPTION, COPYRIGHT, AUTHOR, NORMAL);

	/* parse command line */
	if (argc < 2) 
		usage(argv[0]);

	{
		int c = 0;

		while ((c = getopt(argc, argv, "rn:g:s:t:hd:")) != EOF)

              		switch (c) {

			case 'h':	/* print usage */
				usage(argv[0]);
				break;	
              		case 'n':	/* set the number mask */
				opt_line |= OPT_NMASK;
              			strncpy(mask, optarg, MAX_LEN - 1);
				break;
			case 'r':	/* use random mode */
				opt_line |= OPT_RAND;
				inc = 0;
				break;
			case 'g': 	/* activate generation mode */
				opt_line |= OPT_GENERATE;
				strncpy(file, optarg, MAX_FILE_LEN - 1);
				break;				
 			case 's':	/* activate scan mode */
				opt_line |= OPT_SCAN;
 	                	strncpy(file, optarg, MAX_FILE_LEN - 1);
 	                	break;
			case 't':	/* set modem timeout */
				opt_line |= OPT_TIMEOUT;
				timeout = atoi(optarg);
				break;
			case 'd':	/* set modem device */
				opt_line |= OPT_DEVICE;
				strncpy(dev, optarg, MAX_FILE_LEN - 1);
				break;
			}			
	}		

	if ( !(opt_line & OPT_GENERATE) && !(opt_line & OPT_SCAN) )
		fatalerr("err: please select an action [generate or scan]");
	
	if ( (opt_line & OPT_GENERATE) && (opt_line & OPT_SCAN) )
		fatalerr("err: select only one action [generate or scan]");

	if ( !*file )
		fatalerr("err: please specify a filename to use");

	/* signals handling */
        signal(SIGINT, cleanup);

	/* enter generation mode */
	if (opt_line & OPT_GENERATE) {

		if ( !(opt_line & OPT_NMASK) || !*mask )
			fatalerr("err: -n <arg> is required with -g");

		listgen(mask, inc, file);

	/* enter scan mode */
     	} else 

		scan(file);

	exit(0);
}	 

/*
 * scan(): scanner engine
 *
 * The parser is really minimal!
 */
void scan(char *file)
{
	FILE 	*f;
	int 	size = 1;
	char 	number[MAX_LEN], status[MAX_LEN], c[2];

	/* open numbers file */
	if ( (f = fopen(file, "r+")) == NULL )
		fatalerr("err: unable to open file %s", file);

	/* open and init modem device */
	if ( (fd = initmodem(dev)) < 0 )
		fatalerr("err: unable to open %s", dev);

	fprintf(stdout, "Using modem device\t: %s\n", dev);
	fprintf(stdout, "Using modem timeout\t: %dsecs\n\n", timeout);
	fprintf(stdout, "Starting scan.\n");

	/* reset modem */
	sendcmd(fd, 2, "ATZ\r");

	/* parse the numbers file (FIXME) */
	while (size) { 

		bzero(number, MAX_LEN);
		bzero(status, MAX_LEN);
		bzero(c, 2);

		/* read phone number */
		while (1) {
			size = fread(c, 1, 1, f);  

			if (c[0] == '\t' || !size)
				break;

			strncat(number, c, 1);
		}					

		/* read phone number status */
		while (1) {
			size = fread(c, 1, 1, f);

			if (c[0] == '\n' || !size)
				break;

			strncat(status, c, 1);
		}

		/* dial the number, if not already scanned */
		if ( !strcmp(status, "UNSCANNED") ) {

                	switch ( dial(number, MAX_RETRIES) ) {

			case 1:		/* CONNECT */
				fseek(f, -10, SEEK_CUR);
				fwrite("CONNECT  \n", 10, 1, f);
				break;
			case 2:		/* BUSY */
				fseek(f, -10, SEEK_CUR);
                        	fwrite("BUSY     \n", 10, 1, f);
				break;
			case 3:		/* NO ANSWER */
				fseek(f, -10, SEEK_CUR);
                        	fwrite("-        \n", 10, 1, f);
				break;
			}			
		}
	}

	fprintf(stdout, "Scan finished.\n\n");

	/* close modem and return */
	closemodem(fd);
	return;
}

/* 
 * dial(): actually, dial a phone number and get modem response
 *
 * 1: CONNECT
 * 2: BUSY
 * 3: NO ANSWER
 * 4: EOF
 */
int dial(char *number, int retry)
{
	int 	i;
	char 	out[MAX_LEN];
	FILE *logfile;
	logfile=fopen("./logfile", "a+");

        bzero(out, sizeof(out));

	if (!strlen(number)) 
		return(4);

	/* modem hangup */
	fprintf(stdout, "Hanging up...                                     \r");

	if (!hupmodem(fd)) { 
		if (!hupmodem(fd))	
			fatalerr("err: %s not responding", dev);
	}

        fprintf(stdout, "Dialing: %s (%i)                 \r", number, timeout);
	
	/* send the dial command string */
	sendcmd(fd, 2, "ATDT%s\r", number); 

	for (i = timeout; i > 0; i--) {
		fprintf(stdout, "Dialing: %s (%i)               \r", number, i);
		/* read modem output */
		if (read(fd, out, MAX_LEN - 1)) {
			fprintf(logfile, out);
			/* CONNECT */
			if ( strstr(out, "CONNECT") != NULL ) {
				fprintf(stdout, "%sCONNECT: %s\n%s", GREEN, 
					number, NORMAL);
				       sleep(3); //some modems need a delay

				       bzero(out, sizeof(out));
				       sendcmd(fd, 1, "\r");
				       if(read(fd, out, MAX_LEN-1))
                                                fprintf(logfile, out);
				       bzero(out, sizeof(out));
				       sendcmd(fd, 1, "\r");
				       if(read(fd, out, MAX_LEN-1))
                                                fprintf(logfile, out);
				       bzero(out, sizeof(out));
				       sendcmd(fd, 1, "...\r");
				       if(read(fd, out, MAX_LEN-1))
				       		fprintf(logfile, out);
				
				sleep(3);
				sendcmd(fd, 2, "+++");
				sendcmd(fd, 2, "+++");
				sendcmd(fd, 2, "ATH");
				return(1);
			}
			/* BUSY */
			if ( strstr(out, "BUSY") != NULL ) {
				fprintf(stdout, "%sBUSY:    %s\n%s", YELLOW, 
					number, NORMAL);
				return(2);
			}
			/* ERROR */
			if ( strstr(out, "ERROR") != NULL ) {
				fatalerr("err: ERROR. SIM problem?");
			}
			/* NO ANSWER (speed hacks) */
			if ( strstr(out, "NO") != NULL ) {
				if (timeout - i < 3) { /* line problem? */
					/* catch the error */
					if (!retry)
						fatalerr("err: NO CARRIER. Line problem?");
					/* retry */
					fprintf(stdout, "RETRY:   %s\n", number);
					return( dial(number, retry - 1) );
				} else 
					return(3);
			}
			/* NO ANSWER */
			if (strstr(out, "OK") != NULL)
				return(3);
		}
		sleep(1);
	}											
	return(3);
}	

/*
 * writefile(): write numbers to file
 *
 * Weird strncpy() bug on Linux reported by <circut@hackthisbox.org> now fixed.
 */
void writefile(int last, int inc, char *file)
{
	FILE 	*f;
	int 	i;

	if ( (f = fopen(file, "a")) == NULL )
		fatalerr("err: unable to open file %s", file);
	
	fprintf(stdout, "Writing numbers to file...\n");

	/* use incremental mode */
	if (inc)
		for (i = 0; i < last; i++) {

			if ( !fwrite(numbers[i], 1, strlen(numbers[i]), f) )
				fatalerr("err: unable to write to file %s", file);

			fwrite("\tUNSCANNED\n", 1, 11, f); /* mark as new */

                        fprintf(stdout, "%d numbers left         \r", last - i);
		}

	/* use random mode */
	else {
		int j;
		struct timeval rnd;

		while (last) {

			gettimeofday(&rnd, NULL);
			srand(rnd.tv_usec);

			/* some deep voodoo magic */
	                j = 0 + (int)((last + 0.0) * rand()/(RAND_MAX + 1.0));
			
                        if( !fwrite(numbers[j], 1, strlen(numbers[j]), f) )
				fatalerr("err: unable to write to file %s", file);

			fwrite("\tUNSCANNED\n", 1, 11, f); /* mark as new */

			strncpy(numbers[j], numbers[last - 1], strlen(numbers[j]));
			last--;
			
                        fprintf(stdout, "%d numbers left             \r", last);
		}		  
	}				

	/* close file */
	fclose(f);

	fprintf(stdout, "Done.                                           \n\n");
	return;
}		

/*
 * listgen(): list generator engine
 */
void listgen(char *mask, int inc, char *file)
{
	int 	i, j, tot_numbers;
	int	nextx = 0, nextn = 0, xpos[MAX_LEN];
	char 	n[MAX_LEN]; 
		
	/* parse the number mask */
	for (i = 0; i < strlen(mask); i++) {

		if (mask[i] == 'x') {
			xpos[nextx] = i;
			mask[i] = '0';
			nextx++;
		}
	}
	
	if ( (!nextx) || (nextx > 4) )
		fatalerr("err: please specify 1 to 4 x's");

	/* allocate the needed amount of memory */
	tot_numbers = pow(10, nextx);
	numbers = (char **)malloc(tot_numbers * sizeof(char *));

	/* fill the numbers array with all possibilities */
	fprintf(stdout, "Generating numbers list...\n");

	for (i = 0; i < tot_numbers; i++) {

		snprintf(n, MAX_LEN - 1, "%d", i);

	 	if (strlen(n) == nextx) {
	
	 		for (j = 0; j < nextx; j++) 
				mask[xpos[j]] = n[j];
        
			numbers[nextn] = strdup((const char *)mask);
                        nextn++;

		} else {

			for (j = 0 ; j < nextx - strlen(n); j++) 
				mask[xpos[j]] = '0';

			for (j = nextx - strlen(n); j < nextx; j++)
				mask[xpos[j]] = n[j + strlen(n) - nextx];

			numbers[nextn] = strdup((const char *)mask);
                        nextn++;
		}

	}						
		
	/* write to file and free() the memory */
	writefile(nextn, inc, file);		

	for (i = 0; i < nextn; i++)
       		free(numbers[i]);
	free(numbers);

	return;
}	

/*
 * initmodem(): open modem device and initialize serial port
 *
 * retval: modem file descriptor
 */
int initmodem(char *dev)
{
	int 	flags;

        if ( (fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0 )
                return(fd);

	/* save old terminal settings */	
        tcgetattr(fd, &oldtio);

	/* set up the new struct and init serial port */
        tcgetattr(fd, &newtio);

        newtio.c_cflag |= 	MODEM_SPEED | CS8 | CLOCAL | CREAD;
        newtio.c_iflag |= 	IGNPAR;
        newtio.c_oflag = 	0;
        newtio.c_lflag = 	0;
        newtio.c_cc[VTIME] = 	0;
        newtio.c_cc[VMIN] = 	0;

        tcflush(fd, TCIFLUSH);
        tcsetattr(fd, TCSANOW, &newtio);

	/* we no longer want to have the serial port non-blocking */
        flags = fcntl(fd, F_GETFL);
        if (flags == -1)
                 fatalerr("err: failed to get serial tty flags"); 

        flags &= ~O_NONBLOCK;
        if (fcntl(fd, F_SETFL, flags) == -1)
                 fatalerr("err: failed to set serial port ~O_NONBLOCK");

        return(fd);
}

/*
 * closemodem(): reset serial port and close modem device
 */
void closemodem(int fd)
{
	/* hangup and reset modem */
	sendcmd(fd, 1, "+++ATH0\r");
	sendcmd(fd, 1, "ATZ\r");

	/* set old attributes for the serial port */
        tcsetattr(fd, TCSANOW, &oldtio);
        close(fd);

	/* re-enable line buffering for stdout */
	setvbuf(stdout, NULL, _IOLBF, 0);
}

/*
 * hupmodem(): hangup line and get modem response
 *
 * 1: error, modem not responding
 * 2: ok, modem connected
 */
int hupmodem(int fd)
{
	char 	out[16];

	bzero(out, sizeof(out));

	sendcmd(fd, 2, "ATH0\r");
	read(fd, out, 15);

	/* modem is not responding */
	if (strstr(out, "OK") == NULL)
		return(0);

	/* modem connected */
	return(1);
}

/*
 * sendcmd(): send a command to modem
 */
void sendcmd(int fd, int timewait, char *fmt, ...)
{
	char 	strbuf[MAX_FILE_LEN];
        va_list	ap;

        bzero(strbuf, sizeof(strbuf));

        /* flush i/o */
        tcflush(fd, TCIOFLUSH);

        /* send the command */
        va_start(ap, fmt);
        vsnprintf(strbuf, sizeof(strbuf) - 1, fmt, ap);
        write(fd, strbuf, strlen(strbuf));
        va_end(ap);

        /* wait for whatever char has been transmitted */
        tcdrain(fd);
        sleep(timewait);

        return;
}

/*
 * cleanup(): SIGINT handler
 */
void cleanup(int ignored)
{
	if (fd)
		closemodem(fd);

	fprintf(stderr, "\n");
	fatalerr("err: program interrupted... cleanup done");	
}

/*
 * fatalerr(): error handling routine
 */
void fatalerr(char *pattern, ...)
{
        va_list ap;
        va_start(ap, pattern);

        fprintf(stderr, "%sw4rd-", RED);
        vfprintf(stderr, pattern, ap);
        fprintf(stderr, " (exit forced).\n\n%s", NORMAL);

        va_end(ap);

        exit(-1);
}

/*
 * usage(): print usage
 */
void usage(char *name)
{
        fprintf (stderr, 
		"%susage%s:\n"
		"\t%s -g <file> -n <numbers_mask> [-r]   (generation mode)\n"
        	"\t%s -s <file> [-t <timeout> -d <dev>]  (scan mode)\n\n", 
		BRIGHT, NORMAL, name, name);

        fprintf (stderr,
		"%sgeneration mode%s:\n"
                "\t-g  generate numbers list and save it to file\n"
                "\t-n  number mask to be used in generation mode\n"
                "\t-r  toggle random mode ON\n\n"
		"%sscan mode%s:\n"
                "\t-s  scan a list of phone numbers from file\n"
                "\t-t  set the modem timeout (default=%dsecs)\n"
		"\t-d  use this device (default=%s)\n\n"
		"%shelp%s:\n"
                "\t-h  print this help\n\n", BRIGHT, NORMAL, BRIGHT, NORMAL, 
		timeout, dev, BRIGHT, NORMAL);

        exit (0);
}

