/*	ringd.c by Adam Dace <thekind@tezcat.com>
	Enjoy... */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "config.h"

int tty_open(void);
int modem_reset(void);
int get_rings(int);
void shut_down(int);

int fd_modem, rings=0;
char buf[32];
time_t timer1;

int main(void)
{
        int got_rings;
        pid_t pid;

	if ((pid = fork()) < 0)
	{
		perror("Couldn't fork() daemon process");
		return(-1);
	}
	else
		if (pid != 0)
			exit(0);

	/* From here on out, it's daemon all the way... */

	setsid();
	chdir("/");
	umask(0);
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	if (signal(SIGTERM, shut_down) == SIG_ERR)
	{
		syslog(LOG_ERR,strerror(errno));
		shut_down(-1);
	}

	openlog("ringd", LOG_PID, LOG_DAEMON);
	syslog(LOG_NOTICE, "ringd 0.8 monitoring modem");

	while (1)
	{
		got_rings = 0;

		while (tty_open() < 0)
		{
			close (fd_modem);
			sleep (RECYCLE_TIME);
		}

		if (modem_reset() < 0)
			shut_down(-1);

		while (!got_rings)
		{
			timer1 = 0;
			time(&timer1);

			if (get_rings(3) < 0)
			{
				rings=1;
				continue;
			}
			else
				rings=0;

			if (get_rings(2) < 0)
			{
				rings=1;
				continue;
			}
			else
				rings=0;

			got_rings=1;
		}

		if (close(fd_modem) < 0)
		{
			syslog(LOG_ERR,strerror(errno));
			shut_down(-1);
		}

		sleep(2);

		if ((pid=fork()) < 0)	/* Be a man and fork, he says... */
		{			/* What utter crap and pain. */
			syslog(LOG_ERR,strerror(errno));
			shut_down(-1);
		}
		else
			if (pid == 0)	
					execl(PROGRAM,PROGRAM,ARG,NULL);
			else
				wait(NULL);

		sleep(2);
	}
}


int tty_open(void) /* Opens the modem, and sets terminal attributes. */
{
	struct termios term;

	if ((fd_modem = open("/dev/modem", O_RDWR | O_NOCTTY)) < 0)
	{
		syslog(LOG_ERR,strerror(errno));
		shut_down(-1);
	}

	if (isatty(fd_modem) == 0)
	{
		syslog(LOG_ERR,strerror(errno));
		shut_down(-1);
	}

	if (flock(fd_modem,LOCK_EX) < 0)
	{
		syslog(LOG_ERR,strerror(errno));
		shut_down(-1);
	}

	/* This right here is such a hack, even I have a hard time believing
	 * it.  I stole these values from kermit and stty -a.  I tried a
	 * number of other combinations, which didn't work, some help here
	 * would be greatly appreciated. */

	term.c_cflag = CS8 | CREAD | HUPCL | CLOCAL;
	term.c_iflag = IGNBRK | IGNPAR;
	term.c_lflag = 0;
	term.c_oflag = 0;

	if (cfsetispeed(&term,BPS_RATE) < 0)
		return(-1);
	if (cfsetospeed(&term,BPS_RATE) < 0)
		return(-1);

	if (tcsetattr(fd_modem, TCSANOW, &term) < 0)
		return(-1);

	return(0);
}

int modem_reset(void) /* Resets the modem, and checks for additional rings. */
{
        char command[]=RESET_STR;         /* Modem reset. */
	int number;

        if (write(fd_modem, &command, 4) < 0)
	{
                syslog(LOG_ERR,strerror(errno));
                shut_down(-1);
	}

        sleep(RING_TIME);

        number=read(fd_modem, &buf, 32);
	buf[number]='\0';

        if (strstr(buf, OK) == NULL)
	{
                syslog(LOG_ERR,strerror(errno));
                shut_down(-1);
	}

        if (strstr(buf, RING) != NULL )
                return (1);

	return (0);
}

int get_rings(int rings_wanted) /* Waits for a number of specified rings. */
{
	int counter=0, done=0;
	time_t time1=0, time2=0;
	fd_set myset;

	FD_ZERO(&myset);

	while (!done)  /* BTW, read() is slightly broken, so select() we go */
	{
		FD_SET(fd_modem, &myset);
		select(2,&myset,NULL,&myset,NULL);

		counter = read(fd_modem, &buf, 256);
		buf[counter]='\0';

		if ((counter > 0) && (strstr(buf, RING) != NULL))
		{
                	time(&time1);

			if ((time2 == 0) && ((abs(difftime(timer1, time1)) < TOTAL_TIME)))
			{
				rings++;
				time2 = time1;
			}
			else
			{
				if (((abs(difftime(time1, time2))) < RING_TIME) &&
				    ((abs(difftime(timer1, time2))) < TOTAL_TIME))
				{
					rings++;
					time2 = time1;
				}
				else
					return(-1);
			}
		}

		if (rings == rings_wanted)
		{
			if (modem_reset() == 0)
				done=1;
			else
				return(-1);
		}
	}

	return(0);
}

void shut_down(int status)	/* Shut down somewhat gracefully. */
{
	if (status != SIGTERM)
		syslog(LOG_NOTICE,"internal error occured, shutting down");
	else
		syslog(LOG_NOTICE,"SIGTERM recieved, shutting down");

	closelog();
	flock(fd_modem,LOCK_UN);
	close(fd_modem);
	exit(status);
}
