#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/poll.h>
#include <sys/select.h>

#define MODEM_DEV "/dev/modem"
#define PORT 5001

int mdm, sock;
int ibuflen = 0;
unsigned char ibuf[1024];
unsigned char tmpbuf[1024];
fd_set cfds;

void write2(int fd, unsigned char *buf, int len)
{
    int ret;
    
    while(len)
    {
	ret = write(fd, buf, len);
	if(ret < 0)
	{
	    perror("write");
	    exit(1);
	}
	buf += ret;
	len -= ret;
    }
}

void sendcmd(unsigned char *cmd)
{
    unsigned char *lbuf;
    
    lbuf = strcpy(malloc(strlen(cmd) + 3), cmd);
    strcat(lbuf, "\r\n");
    write2(mdm, lbuf, strlen(cmd) + 2);
    free(lbuf);
}

unsigned char *getline(int block)
{
    int i;
    unsigned char *p;
    int ret, len;
    static unsigned char retbuf[1024];
    struct pollfd pf;
    
    while(1)
    {
	while((p = memchr(ibuf, '\r', ibuflen)) != NULL)
	{
	    memcpy(retbuf, ibuf, len = (p - ibuf));
	    retbuf[len] = 0;
	    memmove(ibuf, p + 1, ibuflen -= (len + 1));
	    for(p = retbuf; *p; p++)
	    {
		while(*p == '\n')
		    memmove(p, p + 1, len--);
	    }
	    if(len && (memcmp(retbuf, "AT", 2)))
		return(retbuf);
	}
	if(!block)
	{
	    pf.fd = mdm;
	    pf.events = POLLIN;
	    pf.revents = 0;
	    if(poll(&pf, 1, 0) == 0)
		return(NULL);
	}
	ret = read(mdm, ibuf + ibuflen, 1024 - ibuflen);
	if(ret < 0)
	{
	    perror("read");
	    exit(1);
	}
	ibuflen += ret;
    }
}

void broadcast(unsigned char *buf, ...)
{
    int i;
    va_list args;
    
    va_start(args, buf);
    vsprintf(tmpbuf, buf, args);
    va_end(args);
    strcat(tmpbuf, "\n");
    for(i = 0; i < FD_SETSIZE; i++)
    {
	if(FD_ISSET(i, &cfds) && (i != mdm) && (i != sock))
	    write2(i, tmpbuf, strlen(tmpbuf));
    }
}

int main(int argc, unsigned char **argv)
{
    int i;
    unsigned char *p;
    int nsock;
    int ret;
    struct sockaddr_in name;
    struct termios tio;
    fd_set rfds;
    
    if((mdm = open(MODEM_DEV, O_RDWR)) < 0)
    {
	perror(MODEM_DEV);
	return(1);
    }
    if(tcgetattr(mdm, &tio) < 0)
    {
	perror("tcgetattr");
	return(1);
    }
    cfmakeraw(&tio);
    cfsetispeed(&tio, B0);
    cfsetospeed(&tio, B38400);
    if(tcsetattr(mdm, TCSANOW, &tio) < 0)
    {
	perror("tcsetattr");
	return(1);
    }
    if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
	perror("socket");
	return(1);
    }
    name.sin_family = AF_INET;
    name.sin_port = htons(PORT);
    name.sin_addr.s_addr = 0;
    if(bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0)
    {
	perror("bind");
	return(1);
    }
    if(listen(sock, 16) < 0)
    {
	perror("listen");
	return(1);
    }
    sendcmd("ATZ");
    if(strcmp(getline(1), "OK"))
    {
	fprintf(stderr, "Could not reset modem\n");
	return(1);
    }
    sendcmd("AT#CID=1");
    if(strcmp(getline(1), "OK"))
    {
	fprintf(stderr, "Could not activate caller ID\n");
	return(1);
    }
    daemon(0, 0);
    FD_ZERO(&cfds);
    FD_SET(mdm, &cfds);
    FD_SET(sock, &cfds);
    while(1)
    {
	rfds = cfds;
	ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
	if(ret < 0)
	{
	    perror("select");
	    return(1);
	}
	if(ret > 0)
	{
	    for(i = 0; i < FD_SETSIZE; i++)
	    {
		if(FD_ISSET(i, &rfds))
		{
		    if(i == mdm)
		    {
			while((p = getline(0)) != NULL)
			{
			    if(!strcmp(p, "RING"))
				broadcast("R");
			    if(!memcmp(p, "NMBR = ", 7))
			    {
				p += 7;
				broadcast("N%s", p);
			    }
			}
		    } else if(i == sock) {
			if((nsock = accept(sock, NULL, 0)) >= 0)
			    FD_SET(nsock, &cfds);
		    } else {
			if(read(i, tmpbuf, 1024) == 0)
			{
			    FD_CLR(i, &cfds);
			    close(i);
			}
		    }
		}
	    }
	}
    }
}

