/*
 * $Id: ward.c,v 1.4 2002/10/12 14:00:27 raptor Exp $
 *
 * ward v1.9 - Classic War Dialer (optimized for GSM)
 * Copyright (c) 2001 Raptor <raptor@0xdeadbeef.info>
 *
 * WARD is a classic "War Dialer": it scans a list of phone numbers, 
 * finding the ones where a modem is answering the call on the other
 * end and providing a nicely formatted output of the scan results.
 * WARD can generate phone numbers lists based on a user-supplied 
 * mask, in incremental or random order. WARD is one of the fastest 
 * PBX scanners around (and possibly the best for UNIX environment).
 * Remember to change some defines to make it fit your configuration. 
 *
 * Program inspired to the old sordial.c (thanx sorbo!).
 * Many thanx also go to Megat0n for his termios advice.
 *
 * Tested on Linux and OpenBSD. Compile with gcc ward.c -o ward -lm
 * (WARD is also known to compile on Microsoft Windows with cygwin)
 *
 * FOR EDUCATIONAL PURPOSES.
 */


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

/* General info */
#define VERSION         "1.9"
#define AUTHOR          "Raptor"
#define MAIL_SUPPORT    "<raptor@0xdeadbeef.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"
#define MODEM_SPEED 	B9600
#define MODEM_TIMEOUT 	60
#define MAX_NUM_LEN 	15
#define MAX_STATUS_LEN 	80
#define MAX_RETRIES 	2


/* Function prototypes */
void scan(char *nfile);
int dial(char *number, int retry);
void writefile(int last, int incr, char *nfile);
void listgen(char *nmask, int incr, char *nfile);
int initmodem(char *device);
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);
/* Prototypes end */


int fd, timeout = MODEM_TIMEOUT;
char device[254] = MODEM_DEV;
char **numbers;
struct termios newtio, oldtio;



int 
main(int argc, char **argv) 
{
	int incr = 1, opt_line = 0;
	char nmask[MAX_NUM_LEN], nfile[255];

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

/* Print header */
        fprintf(stderr,
		"%s\nWARD v%s - Classic War Dialer (optimized for GSM)\n"
		"Copyright (c) 2001 %s %s\n\n%s", BLUE, VERSION, AUTHOR, 
		MAIL_SUPPORT, 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':
			usage(argv[0]);
			break;	
              	case 'n':
			opt_line |= OPT_NMASK;
              		strncpy(nmask, optarg, MAX_NUM_LEN - 1);
			break;
		case 'r': 
			opt_line |= OPT_RAND;
			incr = 0;
			break;
		case 'g': 
			opt_line |= OPT_GENERATE;
			strncpy(nfile, optarg, 254);
			break;				
 		case 's':
			opt_line |= OPT_SCAN;
 	                strncpy(nfile, optarg, 254);
 	                break;
		case 't':
			opt_line |= OPT_TIMEOUT;
			timeout = atoi(optarg);
			break;
		case 'd':
			opt_line |= OPT_DEVICE;
			strncpy(device, optarg, 254);
			break;
		}			
	}		

/* Input control */
	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 (!*nfile)
		fatalerr("err: please specify a filename to use");


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


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

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

		listgen(nmask, incr, nfile);

/* Scan mode */
     	} else 
		scan(nfile);


	exit(0);
}	 



void 
scan(char *nfile) /* Scanner engine */
{
	FILE * f;
	int size = 1, result, i;
	char n[MAX_NUM_LEN], status[MAX_STATUS_LEN], c[2];

	bzero(n, MAX_NUM_LEN);
	bzero(status, MAX_STATUS_LEN);
	c[1] = 0;

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

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

	fprintf(stdout, "%s[*]%s Using device: %s\n", BRIGHT, NORMAL, device);
	fprintf(stdout, "%s[*]%s Using timeout: %dsecs\n\n", BRIGHT, NORMAL, timeout);
	fprintf(stdout, "%s[*]%s Starting scan engine...\n", BRIGHT, NORMAL);
	fprintf(stdout, "%s[*]%s Resetting modem...\n", BRIGHT, NORMAL);

	sendcmd(fd, 2, "ATZ\r");

	fprintf(stdout, "%s[*]%s Done.                         \n\n", BRIGHT, NORMAL);
	fprintf(stdout, "%s[*]%s Starting scan.                        \n", BRIGHT, NORMAL);

/* Parse the numbers file (FIXME: really minimal parser) */
	while (size) { 

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

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

			if (c[0] != '\n')
				strncat(n, 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")) {

			result = dial(n, MAX_RETRIES);

                	switch (result) {
			case 1:
				fseek(f, -10, SEEK_CUR);
				fwrite("CONNECT  \n", 10, 1, f);
				break;
			case 2:
				fseek(f, -10, SEEK_CUR);
                        	fwrite("BUSY     \n", 10, 1, f);
				break;
			case 3:
				fseek(f, -10, SEEK_CUR);
                        	fwrite("-        \n", 10, 1, f);
				break;
			}			
		}
				
        	bzero(n, MAX_NUM_LEN);
        	bzero(status, MAX_STATUS_LEN);
	}

/* Scan finished */
	fprintf(stdout, "%s[*]%s Scan finished.                      \n\n",
		BRIGHT, NORMAL);
	sendcmd(fd, 1, "+++ATH0\r");
	sendcmd(fd, 1, "ATZ\r");
	closemodem(fd);

	return;
}



int 
dial(char *number, int retry) /* Actually dial a phone number */
{

/* 
 * Return values:
 *
 * 1) CONNECT
 * 2) BUSY
 * 3) NO ANSWER
 * 4) EOF
 */

	int i;
	char buf[24];

        bzero(buf, sizeof(buf));

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

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

	if (!hupmodem(fd)) { /* FIXME: ugly hack for a better error handling */
		if (!hupmodem(fd))	
			fatalerr("err: %s not responding", device);
	}

        fprintf(stdout, "Dialing: %s (%i)        \r", number, timeout);
	
/* Create dial command string */
	sendcmd(fd, 2, "ATM0DT%s\r", number); /* modem volume set to 0 */

	for (i = timeout; i > 0; i--) {

		fprintf(stdout, "Dialing: %s (%i)    \r", number, i);

/* Read modem output */
		if (read(fd, buf, 23)) {


			if (strstr(buf, "CONNECT") != NULL) {
				fprintf(stdout, "%sCONNECT: %s\n%s", 
					GREEN, number, NORMAL);

				sleep(3); /* some modems require a delay */
				sendcmd(fd, 2, "+++");

				return(1); /* CONNECT */
			}


			if (strstr(buf, "BUSY") != NULL) {
				fprintf(stdout, "%sBUSY:    %s\n%s", 
					YELLOW, number, NORMAL);

				return(2); /* BUSY */
			}


			if (strstr(buf, "ERROR") != NULL) {
				fatalerr("err: ERROR. SIM problem?");
			}


/* Speed hacks */
			if (strstr(buf, "NO") != NULL) {

				if (timeout - i < 3) { /* Line problems */

					/* 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(buf, "OK") != NULL)

				return(3); /* NO ANSWER */

		}
		sleep(1);
	}											
	return(3); /* NO ANSWER */
}	



void 
writefile(int last, int incr, char *nfile) /* Write list to file */
{
	int i;
	FILE * f;

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

/* Use incremental mode writing to file */
	if (incr)
		for (i = 0; i < last; i++) {

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

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

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

/* Use random mode (pHEAR) */
	else {
		int j;
		struct timeval rnd;

		while (last) {

/* Strong pseudo-random numbers generator */
			gettimeofday(&rnd, NULL);
			srand(rnd.tv_usec);

/* Some 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", nfile);

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

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

	}				
	fprintf(stdout, "%s[*]%s Done.                 \n\n", BRIGHT, NORMAL);

	return;
}		

	

void 
listgen(char *nmask, int incr, char *nfile) /* List generator engine */
{
	int i, j, tot_numbers, nextx = 0, nextn = 0, xpos[MAX_NUM_LEN];
	char n[MAX_NUM_LEN]; 
		

/* Find the position of the 'x' chars and substitute with '0' */
	for (i = 0; i < strlen(nmask); i++) {

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

	tot_numbers = pow(10, nextx);
	numbers = (char **)malloc(tot_numbers * sizeof(char *));

/* Fill the numbers array with all possibilities */
	fprintf(stdout, "%s[*]%s Generating numbers list...\n", BRIGHT, NORMAL);

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

		snprintf(n, MAX_NUM_LEN, "%d", i);

/* Use POW operation properties in decimal notation */
	 	if (strlen(n) == nextx) {
	
	 		for (j = 0; j < nextx; j++) 
				nmask[xpos[j]] = n[j];
        
			numbers[nextn] = strdup((const char *)nmask);
                        nextn++;

		} else {

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

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

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

	}						
		
/* Write to file and free() the memory */
	writefile(nextn, incr, nfile);		

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

	return;
}	



int 
initmodem(char *device) /* Open modem device and init serial port */
{
	int flags;

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

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

/* Set up the new struct and init serial port */
        bzero(&newtio, sizeof(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);
}



void 
closemodem(int fd) /* Close modem device */
{

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

        close(fd);
}



int 
hupmodem(int fd) /* Hangup line and check modem response */
{
	char buf[24];

	bzero(buf, sizeof(buf));

	sendcmd(fd, 2, "ATH0\r");
	read(fd, buf, 23);

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

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



void 
sendcmd(int fd, int timewait, char *fmt, ...) /* Send command to modem */
{
	char strbuf[128];
        va_list ap;

        bzero(strbuf, sizeof(strbuf));

        /* Flushes I/O */
        tcflush(fd, TCIOFLUSH);

        /* Transmits the command */
        va_start(ap, fmt);

        vsnprintf(strbuf, sizeof(strbuf) - 1, fmt, ap);
        write(fd, strbuf, strlen(strbuf));
        va_end(ap);

        /* Waits for whatever character has been transmitted */
        tcdrain(fd);
        sleep(timewait);

        return;
}



void 
cleanup(int ignored) /* SIGINT handler, close modem */
{
	if (fd) {
		sendcmd(fd, 1, "+++ATH0\r");
		sendcmd(fd, 1, "ATZ\r");
		closemodem(fd);
	}

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



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

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

        va_end(ap);

        exit(-1);
}



void 
usage(char *name) /* Print usage */
{
        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, device, BRIGHT, NORMAL);

        exit (0);
}
