/*
 *  libmodem library, a modem control facility.
 *
 *  line_manage.c - modem line control
 *
 *  Copyright (C) 1994, 1995  Riccardo Facchetti
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  You can contact the author at this e-mail address:
 *
 *  riccardo@cdc8g5.cdc.polimi.it
 */

/*
 * Some termios stuff is stolen from Gert Doering's mgetty-0.21 code.
 * Here is his (C) for mgetty-0.21 package:
 * --------------------------------------------------------------------------
 * The mgetty+sendfax package is Copyright (c) 1993 Gert Doering. You are
 * permitted to do nearly anything you want with this program - redistribute
 * it, use parts of the code in your own programs, ..., but you have to give
 * me credit - do not remove my name.
 * 
 * If you make money by selling mgetty, I want a share.
 * 
 * For more details, see the chapter "copying conditions" in mgetty.texi
 * --------------------------------------------------------------------------
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <syslog.h>
#include <ctype.h>
/*
 * Use POSIX termios
 */
#include <termios.h>

#include <linux/fs.h>
#include <linux/major.h>

#include <dial/modems.h>
#include <dial/mdmerrno.h>

#include "line_managePriv.h"
#include "termios_cc.h"

#define BUFS    128
#define BIGBUF    1024

char *__line_manage = "line_manage.c (C) Riccardo Facchetti, version " LIBMODEM_VER;

static char buffer[BUFS];
static char bigbuffer[BIGBUF];
static char cmdbuffer[BIGBUF];
static jmp_buf not_opened;
#if 1
struct termios savetty;
#endif

struct modems *mdmopendevice;

char *mdm_reply[] = {
"OK",
"ERROR",
"CONNECT",
"NO CARRIER",
"BUSY",
"NO DIALTONE",
NULL
};

static void alarm_jump(int sig) {
    longjmp(not_opened, 1);
}

int dial(char *phonenum)  {
    struct modems *mdl;
    int ret;

#if defined(DEBUG)
    open_mdm_log();
#endif /* DEBUG */
    mdmerrno = 0;
    setmdms();

    while ((mdl = getnextmdm()) != NULL) {
/*
 * if not locked, use this modem
 */
        if (mdm_chklock(mdl) == FAILURE) {
/*
 * Try to dial...
 */
            ret = bldial(phonenum, mdl->line, mdl->bd);
/*
 * If the modem is alredy used, skip to the next modem else return the error
 */
            if (ret != EMDMOPEN)
                return ret;
        }
    }

    return (mdmerrno = -ENOMDMDEV);
}

int ldial(char *phonenum, char *line) {
    struct modems *mdl;

#if defined(DEBUG)
    open_mdm_log();
#endif /* DEBUG */
    mdmerrno = 0;

    if ((mdl = getmdmnam(line)) == NULL)
        return mdmerrno;

/*
 * dial at the maximum baud rate
 */
    return bldial(phonenum, line, mdl->bd);
}

int bdial(char *phonenum, int baud)  {
    struct modems *mdl;
    int ret;

#if defined(DEBUG)
    open_mdm_log();
#endif /* DEBUG */
    mdmerrno = 0;
    setmdms();

    while ((mdl = getnextmdm()) != NULL) {
        if (baud <= mdl->bd) {
/*
 * if not locked, use this modem
 */
            if (mdm_chklock(mdl) == FAILURE) {
/*
 * Try to dial...
 */
                ret = bldial(phonenum, mdl->line, baud);
/*
 * If the modem is alredy used, skip to the next modem else return the error
 */
                if (ret != EMDMOPEN)
                    return ret;
            }
        }
    }

    return (mdmerrno = -ENOMDMDEV);
}

int bldial(char *phonenum, char *line, int baud) {
    struct modems *mdl;
    int fd, ret;

#if defined(DEBUG)
    open_mdm_log();
#endif /* DEBUG */
    mdmerrno = 0;

    if ((mdl = getmdmnam(line)) == NULL)
        return mdmerrno;

/*
 * lock and open the modem line and set it up for communications
 * remember to unlock the mdl device when returning an error.
 */
    if ((fd = open_modem_setup_line(mdl, baud)) < 0)
        return fd;

/*
 * Now we must save the actual device to have it in hands when hangup
 */
    mdmopendevice = mdl;

/*
 * set up the modem: init string et al
 */
    if ((ret = initialize_modem(fd, mdl)) < 0) {
        mdm_unlock(mdl);
        hangup(fd);
        return ret;
    }

/*
 * finally dial to phonenum
 */
    if ((ret = dial_to_num(fd, mdl, phonenum)) < 0) {
        mdm_unlock(mdl);
        hangup(fd);
        return ret;
    }

/*
 * dial sequence terminated with success, returning fd
 */
#if defined(DEBUG)
    close_mdm_log();
#endif /* DEBUG */
    return fd;
}

static int dial_to_num(int fd, struct modems *device, char *num) {
/*
 * DIAL string
 */
    sprintf(buffer, "%s%s%s%s%s%c", device->cp, device->ds, num,
                                device->de, device->ce, MODEM_RETURN);
    return talk_to_modem(fd, device, buffer, 1);
}

static int initialize_modem(int fd, struct modems *device) {
/*
 * INIT string
 */
    sprintf(buffer, "%s%s%s%c", device->cp, device->im, device->ce, MODEM_RETURN);
    return talk_to_modem(fd, device, buffer, 0);
}

static int open_modem_setup_line(struct modems *device, int baud) {
    struct termios trm;
    int fd = -1;
    struct stat st;
    void (*old_alarm)(int);
    int old_alarm_value = 0;

#if defined(MDM_CHECK_DEVICES)
reopen_line:
#endif

    if (mdm_lock(device) == FAILURE)
        return (mdmerrno = -EMDMLOCK);

    if (setjmp(not_opened) == 0) {
/*
 * build the modem device filename
 */
        sprintf(buffer, "/dev/%s", device->line);

/*
 * set up the alarm for jump if timeout
 */
        old_alarm_value = alarm(0);
        old_alarm = signal(SIGALRM, alarm_jump);
        alarm(2);

        fd = open(buffer, O_RDWR|O_NOCTTY);
    }
/*
 * Restore the old alarm handler and scheduled timeout
 */
    alarm(0);
    signal(SIGALRM, old_alarm);
    alarm(old_alarm_value);
    if (fd < 0)
        return (mdmerrno = -EMDMOPEN);

/*
 * fstat it to get informations
 */
    if (fstat(fd, &st) == -1) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMSTAT);
    }
/*
 * Now check if it is a character device.
 */
    if (!S_ISCHR(st.st_mode)) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMNOCHR);
    }

#if defined(MDM_CHECK_DEVICES)
/*
 * Check if we are using a callout device. If not, close the ttyS device and
 * change it with the cua one.
 */

/*
 * Now check the minor device (is better to check the major one after minor)
 *
 * Warning: Linux specific.
 */
#define MDM_MINOR_START    64
#define MDM_MINOR_END    127

    if ((MINOR(st.st_rdev) < MDM_MINOR_START) ||
        (MINOR(st.st_rdev) > MDM_MINOR_END)) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMBADMIN);
    }

/*
 * Now check if major is okay:
 * major == TTYAUX_MAJOR (callout device). If == TTY_MAJOR, reopen callout one.
 */
     if ((MAJOR(st.st_rdev) != TTY_MAJOR) &&
         (MAJOR(st.st_rdev) != TTYAUX_MAJOR)) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMBADMAJ);
    }

/*
 * Now check if major == TTY_MAJOR, if so, close, change device and reopen.
 */
    if (MAJOR(st.st_rdev) == TTY_MAJOR) {
        mdm_unlock(device);
        close(fd);
/*
 * Now we are going to change the device from ttyS one to cua one.
 * The problem is that we are going to lock /dev/cua?? so if another person
 * will attempt to open the same /dev/ttyS?? line, we will have a timeout
 * error on open, and only on the second (right) open, (open of /dev/cua??)
 * we will find the lock of the line.
 */
        sprintf(device->line, "cua%d", MINOR(st.st_rdev));
        goto reopen_line;
    }
#endif /* MDM_CHECK_DEVICES */
/*
 * Set up the line for working with modem (POSIX termios)
 */
    if (tcgetattr(fd, &trm) == -1) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMGETA);
    }

#if 1
/*
 * save tty state
 */
    memcpy(&savetty, &trm, sizeof(struct termios));
#endif

/*
 * raw mode
 */
    trm.c_iflag &= ~(IGNBRK | IGNCR | INLCR | ICRNL | IUCLC |
                     IXANY | IXON | IXOFF | INPCK | ISTRIP);
    trm.c_iflag = BRKINT|IGNPAR|IXON|IXANY;
    trm.c_oflag = OPOST|TAB3;
    trm.c_lflag = ~(ICANON | ISIG | ECHO | ECHONL | ECHOE | ECHOK);
    trm.c_cflag &= ~(CSIZE|CSTOPB|PARENB|PARODD
#if 0
/*
 * Do touch use CLOCAL (we are calling out).
 */
|CLOCAL);
#else
                                                );
#endif
    trm.c_cflag |= CS8|CREAD|HUPCL;
    trm.c_lflag = ECHOK|ECHOE|ECHO|ISIG|ICANON;
    trm.c_cc[VEOF] = 0x04;
#if defined(VEOL) && VEOL < TIONCC
    trm.c_cc[VEOL] = 0;
#endif
#if defined(VSWTCH)
    trm.c_cc[VSWTCH] = 0;
#endif

/*
 * set speed
 */
    if (device->bd < baud) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMBAUD);
    }
     cfsetospeed(&trm, baud);
     cfsetispeed(&trm, baud);

/*
 * set default c_cc flags
 */
    trm.c_cc[VQUIT]  = CQUIT;
    trm.c_cc[VKILL]  = CKILL;
    trm.c_cc[VEOF]   = CEOF;
#if defined(VEOL) && VEOL < TIONCC
    trm.c_cc[VEOL] = CEOL;
#endif
#if defined(VSTART) && VSTART < TIONCC
    trm.c_cc[VSTART] = CSTART;
#endif
#if defined(VSTOP) && VSTOP < TIONCC
    trm.c_cc[VSTOP] = CSTOP;
#endif
#if defined(VSUSP) && VSUSP < TIONCC
    trm.c_cc[VSUSP] = CSUSP;
#endif
#if defined(VSWTCH) && VSWTCH < TIONCC
    trm.c_cc[VSWTCH] = CSWTCH;
#endif
    /* the following are for SVR4.2 (and higher) */
#if defined(VDSUSP) && VDSUSP < TIONCC
    trm.c_cc[VDSUSP] = CDSUSP;
#endif
#if defined(VREPRINT) && VREPRINT < TIONCC
    trm.c_cc[VREPRINT] = CRPRNT;
#endif
#if defined(VDISCARD) && VDISCARD < TIONCC
    trm.c_cc[VDISCARD] = CFLUSH;
#endif
#if defined(VWERASE) && VWERASE < TIONCC
    trm.c_cc[VWERASE] = CWERASE;
#endif
#if defined(VLNEXT) && VLNEXT < TIONCC
    trm.c_cc[VLNEXT] = CLNEXT;
#endif

/*
 * set mode raw
 */
    trm.c_iflag &= ( IXON | IXOFF | IXANY );    /* clear all flags except */
                        /* xon / xoff handshake */
    trm.c_oflag  = 0;                /* no output processing */
    trm.c_lflag  = 0;                /* no signals, no echo */
    trm.c_cc[VMIN]  = 1;                /* disable line buffering */
    trm.c_cc[VTIME] = 0;

/*
 * set flow control
 */
    trm.c_cflag &= ~CRTSCTS;
    trm.c_iflag &= ~(IXON|IXOFF|IXANY);
    if ( device->fc == FLOW_HARD )
        trm.c_cflag |= CRTSCTS;
    if ( device->fc == FLOW_XON_XOFF )
        trm.c_iflag |= IXOFF|IXON|IXANY;

/*
 * set the new attributes, flush output, discard all not read input
 */
    if (tcsetattr(fd, TCSAFLUSH, &trm) == -1) {
        mdm_unlock(device);
        close(fd);
        return (mdmerrno = -EMDMSETA);
    }

    return fd;
}

static
int talk_to_modem(int fd, struct modems *device, char *command, int isdial) {
    fd_set readfds;
    struct timeval tmout;
    int ret;
    int cmd_found = FAILURE;
    int cnt = 0;
    int retries = SYNC_RTRY;

#if defined(DEBUG)
    mdm_log(LOG_DEBUG, "writing >> %s << to modem\n", command);
#endif /* DEBUG */

/*
 * write the command to modem
 */

    if (write(fd, command, strlen(command)) != strlen(command))
        return (mdmerrno = -EMDMWRITE);

#if defined(DEBUG)
    mdm_log(LOG_DEBUG, "written >> %s << to modem\n", command);
#endif /* DEBUG */

    memset(cmdbuffer, 0, BIGBUF);

    while (!cmd_found) {
        char *pp, *pq;

        memset(bigbuffer, 0, BIGBUF);
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        tmout.tv_sec = device->dl;

        ret = select(FD_SETSIZE, &readfds, NULL, NULL, &tmout);

        if (ret < 0)
            return (mdmerrno = -EMDMSEL);

        if (!FD_ISSET(fd, &readfds))
            return (mdmerrno = -EMDMREAD);
        
        if ((ret = read(fd, bigbuffer, BIGBUF)) < 0)
            return (mdmerrno = -EMDMREAD);
        
        bigbuffer[ret] = '\0';

#if defined(DEBUG)
        mdm_log(LOG_DEBUG, "read >> %s << from modem\n", bigbuffer);
#endif /* DEBUG */

        strcat(cmdbuffer+cnt, bigbuffer);

        if (!strncmp(command, cmdbuffer, strlen(command))) {
/*
 * local echo enabled!!! strip out the command from modem answer!!!!
 * that is "command\r\nOK\r" or something like that
 */

             strcpy(bigbuffer, (char *)&cmdbuffer[strlen(command)]);
            strcpy(cmdbuffer, bigbuffer);
        }

/*
 * strip cmd from cmdbuffer taking only alphanums
 */

        pp = bigbuffer;
        pq = cmdbuffer;

        while (*pq) {
            if (!isspace(*pq))
                *pp++ = *pq;
            pq++;
        }
        *pp = '\0';
        strcpy(cmdbuffer, bigbuffer);
        cnt = strlen(cmdbuffer);

#if defined(DEBUG)
        mdm_log(LOG_DEBUG, "checking >> %s << from modem\n", cmdbuffer);
#endif /* DEBUG */

        if (check_for_string(cmdbuffer, buffer) == SUCCESS)
            cmd_found = 1;
        if (!(retries--))
            return (mdmerrno = -EMDMSYNC);
    }

/*
 * If modem return ERROR, we must return the error now
 */
    if (!strcmp(buffer, "ERROR"))
        return (mdmerrno = -EMDMERROR);

    if (isdial) {
/*
 * check the answer against 'CONNECT' ? No err : Unk err
 */
        if (!strcmp(buffer, "CONNECT"))
            return (mdmerrno = -ENOMDMERR);
        if (!strcmp(buffer, "BUSY"))
            return (mdmerrno = -EMDMBUSY);
    }
    else {
/*
 * check the answer against 'OK' ? No err : Unk err
 */
        if (!strcmp(buffer, "OK"))
            return (mdmerrno = -ENOMDMERR);
    }
    return (mdmerrno = -EUNMDMERR);
}

static int check_for_string(char *from, char *to) {
    int i;

    for (i=0; mdm_reply[i]; i++)
        if (!strncmp(from, mdm_reply[i], strlen(mdm_reply[i]))) {
            strcpy(to, mdm_reply[i]);
            return SUCCESS;
        }
    return FAILURE;
}

int close_nohangup(int fd) {
    struct termios trm;

    tcgetattr(fd, &trm);
    trm.c_cflag &= ~HUPCL;
    tcsetattr(fd, TCSANOW, &trm);

    close(fd);
/*
 * fd closed, but modem still on-line
 */
    return (mdmerrno = -ENOMDMERR);
}

int hangup(int fd) {
    struct termios trm;

    if (fd < 0)
        return (mdmerrno = -EMDMBADFD);

    if (mdmopendevice == NULL)
        return hangup_nodev(fd);
/*
 * ATTENTION+HANGUP string
 */
    sprintf(buffer, "%s%s%s%s%c", mdmopendevice->at,  mdmopendevice->cp,
                                  mdmopendevice->hu, mdmopendevice->ce,
                                  MODEM_RETURN);
    write (fd, buffer, strlen(buffer));

/*
 * hangup with termios (speed to B0). just to be sure :)
 */
    if (tcgetattr(fd, &trm) != -1) {
        cfsetospeed(&trm, B0);
        cfsetispeed(&trm, B0);
/*
 * hangup NOW lowering DTR (0 speed) and sleep 2 seconds to allow modem
 * to hangup
 */
        tcsetattr(fd, TCSANOW, &trm);
        sleep(2);
    }

#if 1
/*
 * reset line stat
 */
    tcsetattr(fd, TCSANOW, &savetty);
#endif

/*
 * close and unlock
 */
    close(fd);
    mdm_unlock(mdmopendevice);

    mdmopendevice = NULL;

    if (mdmerrno < 0)
        return mdmerrno;
    return (mdmerrno = -ENOMDMERR);
}

int hangup_nodev(int fd) {
    struct termios trm;

/*
 * hangup with termios (speed to B0). just to be sure :)
 */
    if (tcgetattr(fd, &trm) != -1) {
        cfsetospeed(&trm, B0);
        cfsetispeed(&trm, B0);
/*
 * hangup NOW lowering DTR (0 speed) and sleep 2 seconds to allow modem
 * to hangup
 */
        tcsetattr(fd, TCSANOW, &trm);
        sleep(2);
    }

#if 1
/*
 * reset line stat
 */
    tcsetattr(fd, TCSANOW, &savetty);
#endif

/*
 * close and unlock
 */
    close(fd);

    if (mdmerrno < 0)
        return mdmerrno;
    return (mdmerrno = -ENOMDMERR);
}

struct modems *getopenmodem(void) {
    static struct modems openmdm;

    if (mdmopendevice != NULL) {
        memcpy(&openmdm, mdmopendevice, sizeof(struct modems));
        return &openmdm;
    }
    else
        return NULL;
}

int clear_line(int fd, int waittime) {
    char buffer[2];
    int bytes;

    struct termios tty, savetty;

    if (tcgetattr(fd, &tty) == -1)
        return (mdmerrno = -EMDMGETA);
    
    savetty = tty;

    tty.c_lflag &= ~ICANON;
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = waittime;

    tcsetattr(fd, TCSANOW, &tty);

    while ( read( fd, buffer, 1 ) > 0 && bytes < 10000 )
        bytes++;

/* reset terminal settings */
    tcsetattr(fd, TCSANOW, &savetty);
    
    return SUCCESS;
}
