/*
 * Startppp
 *
 * Copyright (C) 1995  Matthias Ott 
 *  (msott@cip.informatik.uni-erlangen.de)
 *
 * This program 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 program 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 program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <stdio.h>
#include <string.h>
#include "tk.h"
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "Modem.h"


char dialerror[]="dialerror\n";
char dialkill[]="dialkill\n";
char dialup[]="dialup\n";
char dialconnect[]="dialconnect\n";
char dialend[]="dialend\n";
int fd=-1;
int childpid=0;
int pipefd[2];
int pipeterminalfd[2];
int timeout;
int timeouttime;
int showtermint=0;
Tcl_Interp *myinterp;

/*
   **	Function name : alarm_handler
   **
   **	Description :
   **	Input :
   **	Output :
*/
void alarm_handler(int x)
{
  char dialtimeout[]="timeout -> closed line";
  write (pipefd[1], dialtimeout, strlen(dialtimeout));
  timeout=1;
}


/*
   **	Function name : modemdial
   **
   **	Description :
   **	Input : init number
   **	Output :
*/
int modemdial(char *modeminit, char *modemconnect)
{
  int len,i,ii;
  int number=0;
  char buf[60];
  char buf2[20];
  char action[200];
  int again = 1;
  struct sigaction sigalarm={
    alarm_handler,0,0};

  timeout=0;
  
  if (sigaction(SIGALRM, &sigalarm,NULL)!=0) {
    perror("SIGALRM: ");
  }
    
  i=0;
  ii=0;
  while (i < strlen (modeminit)) {
    if (modeminit[i]=='~') {
      sleep(1);
      ++i;
      continue;
    }
    ii=0;
    while ((modeminit[i]!='^') && (modeminit[i+1]!='M') && (i < strlen (modeminit))) {
      action[ii]=modeminit[i];
      ++i;
      ++ii;
    }
    i=i+2;
    action[ii]='\0';
    write (pipefd[1], action, strlen(action));
    strncat(action,"\r",1); 
    write (fd, action, strlen(action));
  }
  
  /* magic sleep, should change it */
  sleep(2);
  write (pipefd[1], modemconnect, strlen(modemconnect));
  write (fd, modemconnect, strlen(modemconnect));
  modemwait(modemconnect);

  buf[0]='\0';
  alarm(timeouttime);
  do {
    number=0;
    do {
      number=read (fd, buf2,20);
    } while ((number == 0) && (again == 1) &&(timeout == 0));
    if ((number == -1) && (timeout == 0)) {
      alarm(0);
      printf("read error at modemdial\n");
      return 7;
    }
    if (timeout == 1) {
      sleep (1);
      write (pipefd[1], dialerror, strlen(dialerror));
      while(1) sleep (1000);
    }
    buf2[number]='\0';
    if (showtermint == 1) {
      write (pipeterminalfd[1], buf2, number);
    }
    strcat (buf,buf2);
    
    if (strstr(buf,"BUSY")!=NULL) {
      alarm(0);
      return 6;
    }
    if (strstr(buf,"NO ANSWER")!=NULL) {
      alarm(0);
      write (pipefd[1], "NO ANSWER", 9);
      sleep (1);
      return 7;
    }
    if (strstr(buf,"DELAYED")!=NULL) {
      alarm(0);
      write (pipefd[1], "DELAYED", 7);
      sleep (1);
      return 7;
    }
    if (strstr(buf,"NO DIALTONE")!=NULL) {
      alarm(0);
      write (pipefd[1], "NO DIALTONE", 11);
      sleep (1);
      return 7;
    }
    if (strstr(buf,"ERROR")!=NULL) {
      alarm(0);
      write (pipefd[1], "ERROR", 5);
      sleep (1);
      return 7;
    }
    if (strstr(buf,"NO CARRIER")!=NULL) {
      alarm(0);
      write (pipefd[1], "NO CARRIER", 10);
      sleep (1);
      return 7;
    }
    if (strstr(buf,"CARRIER")!=NULL) {
      alarm(0);
      return 0;
    }
    if (strstr(buf,"CONNECT")!=NULL) {
      alarm(0);
      return 0;
    }
    if (again >= 10) {
      alarm(0);
      write (pipefd[1], "ANY ERROR", 9);
      sleep (1);
      return 7;
    }
    ++again;
    
  } while (1);
}

/*
   **	Function name : ModemClose
   **
   **	Description :
   **	Input : modemdevicetail
   **	Output : UNLINK DENIED | CLOSED
*/
int ModemClose(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
  char buf[256];
  struct termios t;
  char *modemdevice;
  char *modemdevicetail;
  char *modemspeed;
  char *modemparity;
  char *modemstopbits;
  int result;
  
  if (argc != 2) {
    interp->result = "wrong # args";
    return TCL_ERROR;
  }
  if (fd==-1) {
    modemdevice=Tcl_GetVar(interp, "modemdevice", TCL_GLOBAL_ONLY);
    strcpy(modemdevicetail,argv[1]);
    modemspeed=Tcl_GetVar(interp, "modemspeed", TCL_GLOBAL_ONLY);
    modemparity=Tcl_GetVar(interp, "modemparity", TCL_GLOBAL_ONLY);
    modemstopbits=Tcl_GetVar(interp, "modemstopbits", TCL_GLOBAL_ONLY);
    result=modemopen(modemdevice,modemdevicetail,modemspeed,modemparity,modemstopbits);
    if (result==1) {
      interp->result = "can't open LCK.. for reading";
      return TCL_OK;
    }
    if (result==2) {
      interp->result = "LCK process active";
      return TCL_OK;
    }
    if (result==3) {
      interp->result = "can't delete LCK..";
      return TCL_OK;
    }
    if (result==4) {
      interp->result = "can't open LCK.. for writing";
      return TCL_OK;
    }
    if (result==5) {
      interp->result = "can't open modemdevice";
      return TCL_OK;
    }
    if (result==6) {
      interp->result = "tcget|setattr error";
      return TCL_OK;
    }
  }

  if (tcgetattr(fd,&t) < 0) {
    Tcl_SetVar(myinterp, "message", "tcsetattr is not working", TCL_GLOBAL_ONLY);
  } else {
    t.c_cflag=B0;
    if (tcsetattr(fd,TCSANOW,&t) < 0) {
      Tcl_SetVar(myinterp, "message", "tcgetattr is not working", TCL_GLOBAL_ONLY);
    }
  }

  close (fd);
  fd = -1;
  sprintf(buf,"/usr/spool/uucp/LCK..%s",argv[1]);
  if (unlink (buf) != 0) {
    interp->result = "unlink denied";
    return TCL_OK;
  }
  interp->result = "closed line";
  return TCL_OK;
}
/*
   **	Function name : modemtalk
   **
   **	Description :
   **	Input : file
   **	Output : 1 | 0 
*/
int modemtalk(char *expectfile)
{
  char name[100];
  int len;
  FILE *fd2;
  char dialfile[]="no expectfile";
  
  fd2=fopen(expectfile,"r");
  if (fd2==NULL) {
    write (pipefd[1], dialfile, strlen(dialfile));
    return 1;
  }
  while (fgets(name,100,fd2)!=NULL) {
    len = strlen(name);
    name[len-1]='\0';
    modemwait (name);
    fgets(name,100,fd2);
    write (fd, name, strlen(name));
  }
  fclose (fd2);
  
  return 0;
}

/*
   **	Function name : modemwait
   **
   **	Description :
   **	Input :
   **	Output :
*/
int modemwait(char *name)
{
  int r;
  int i;
  int number=0;
  char buf[5000];
  char myname[100];
  struct sigaction sigalarm={
    alarm_handler,0,0};
  strcpy(myname,name);
  r=0;
  
  timeout=0;
  if (sigaction(SIGALRM, &sigalarm,NULL)!=0) {
    perror("SIGALRM: ");
  }
  alarm(timeouttime);
  do {
    number=0;
    while ((number == 0) && (timeout == 0)) {
      number=read (fd, &buf[r],1);
    }
			     
/*
   if ((number == -1) && (timeout == 0)) {
   alarm(0);
   printf("read error at modemwait\n");
   write (pipefd[1], dialerror, strlen(dialerror));
   while(1) sleep (1000);
   }
*/
    if (timeout == 1) {
      write (pipefd[1], dialerror, strlen(dialerror));
      while(1) sleep (1000);
    }
    buf[r+1]='\0';
    
    if (showtermint == 1) {
      write (pipeterminalfd[1], &buf[r], 1);
    }
    if (buf[r]=='\0') {
      buf[r]==' ';
    }
    r++;
    if (r >= 5000) {
      r = 0;
    }
  } while ((strstr(buf, myname)==NULL) && (timeout == 0));
  alarm(0);
  return 0;
}

/*
   **	Function name : modemopen
   **
   **	Description :
   **	Input : device lock
   **	Output : 
*/
int modemopen(char *modemdevice, char *modemdevicetail, char *modemspeed, char *modemparity, char *modemstopbits)
{
  FILE *fp;
  char buf[256];
  int pid=0;
  struct termios t;
  
  sprintf(buf,"/usr/spool/uucp/LCK..%s",modemdevicetail);
  fp = fopen(buf, "r");
  if (fp == NULL) {
    if (errno != ENOENT) {
      return 1;
    }
  } 
  else {
    fscanf(fp, "%d", &pid);
    fclose (fp);
    if (getpid()!=pid) {
      if (kill(pid,0)==0) {
	return 2;
      }
    }
    else {
      if (unlink (buf) != 0) {
	return 3;
      }
    }
  }
  fp = fopen(buf,"w");
  if (fp == NULL) {
    return 4;
  }
  fprintf(fp,"%d\n",getpid());
  fclose(fp);
  
  fd = open(modemdevice, O_RDWR | O_NDELAY);
  if (fd<0) {
    printf("MODEM open error\n");
    return 5;
  }
  fcntl(fd,F_SETFL,O_RDWR);
  if (tcgetattr(fd,&t)<0) {
    perror("tcgetattr not working");
    close(fd);
    fd = -1;
    return 6;
  }
  t.c_iflag=t.c_oflag=t.c_lflag=0;
  t.c_cc[VMIN]=1;
  t.c_cc[VTIME]=0;
  
  /* Setzen der Flags */
  if (strcmp(modemparity, "CS8")==0) t.c_cflag = CREAD | CS8;
  if (strcmp(modemparity, "CS8|PARENB ")==0) t.c_cflag = CREAD | CS8 | PARENB;
  if (strcmp(modemparity, "CS8|PARENB|PARODD")==0) t.c_cflag = CREAD | CS8 | PARENB | PARODD;
  
  if (strcmp(modemspeed, "38400")==0) t.c_cflag |= B38400;
  if (strcmp(modemspeed, "19200")==0) t.c_cflag |= B19200;
  if (strcmp(modemspeed, "9600")==0) t.c_cflag |= B9600;
  if (strcmp(modemspeed, "1200")==0) t.c_cflag |= B1200;
  
  if (strcmp(modemstopbits, "CSTOPB")==0) t.c_cflag |= CSTOPB;
  t.c_cflag |= HUPCL;
  /*  t.c_cflag=CREAD | CS8 | B38400 | HUPCL;*/
  if (tcsetattr(fd,TCSANOW,&t)<0) {
    perror("tcsetattr not working");
    close(fd);
    fd = -1;
    return 6;
  }
  return 0;
  
}



/*
   **	Function name : TalkTerminal
   **
   **	Description :
   **	Input :
   **	Output :
*/
void TalkTerminal(ClientData clientData, int mask)
{
  int count,i;
  char input[1000];
  char order[1000];
  char order2[]=".sm.input.text yview -pickplace end\n";

  count=read(pipeterminalfd[0], input, 1000);
  if (count <=0) {
    printf("ERROR reading PIPE");
    return;
  }
  input[count]='\0';
  for (i=0;i<count;++i) {
    if (input[i]=='\r') {
      input[i]=' ';
    }
  }
  sprintf(order,".sm.input.text insert end {%s}",input);
  Tcl_Eval (myinterp, order);
  Tcl_Eval (myinterp, order2);
}

/*
   **	Function name : TalkProc
   **
   **	Description :
   **	Input :
   **	Output :
*/
void TalkProc(ClientData clientData, int mask)
{
  int i;
  int count;
  char *buf; 
  int bufint;
  char up2[]="up2";
  char startonlinetime[]="startonlinetime";
  char input[100];
  char order [100];
  char *modemdevicetail;
  char *startcommand;
  
  count=read(pipefd[0], input, 100);
 
  if (count <=0) {
    printf("ERROR reading PIPE");
    return;
  }
  input[count]='\0';
  
  /* increase dialtry +1 */
  if (strstr(input, dialup)!=NULL) {
    buf=Tcl_GetVar(myinterp, "dialtry", TCL_GLOBAL_ONLY);
    Tcl_GetInt(myinterp, buf, &bufint);
    ++bufint;
    sprintf(buf,"%d",bufint); 
    Tcl_SetVar(myinterp, "dialtry", buf, TCL_GLOBAL_ONLY);
    return;
  }
  
  /* error while dialing, close modemline */
  if (strstr(input, dialerror)!=NULL) {
    Tcl_SetVar(myinterp, "dialtry", "0", TCL_GLOBAL_ONLY);
    modemdevicetail=Tcl_GetVar(myinterp, "modemdevicetail", TCL_GLOBAL_ONLY);
    sprintf(order,"modemclose %s",modemdevicetail);
    Tcl_Eval (myinterp, order);
    pidkill();
    return;
  }
  
  /* error, only kill childprocess */
  if (strstr(input, dialkill)!=NULL) {
    Tcl_SetVar(myinterp, "dialtry", "0", TCL_GLOBAL_ONLY);
    pidkill();
    return;
  }
  
  /* got a connect, set LINE up, start onlinetime */
  if (strstr(input, dialconnect)!=NULL) {
    Tcl_SetVar(myinterp, "message", "connected", TCL_GLOBAL_ONLY);
    Tcl_SetVar(myinterp, "dialtry", "0", TCL_GLOBAL_ONLY);
    Tcl_SetVar(myinterp, "linestatus", "LINE up", TCL_GLOBAL_ONLY);
    Tcl_Eval (myinterp, startonlinetime);
    return;
  }
  
  /* sucessfull dialing, start <up2> */
  if (strstr(input, dialend)!=NULL) {
    Tcl_Eval (myinterp, up2);
    Tcl_SetVar(myinterp, "pid", "0", TCL_GLOBAL_ONLY);
    pidkill();
    return;
  }
  
  Tcl_SetVar(myinterp, "message", input, TCL_GLOBAL_ONLY);
  return;
}



/*
   **	Function name : DialProc
   **
   **	Description :
   **	Input :
   **	Output :
*/
int DialProc(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
  int pid;
  char *modemdevice;
  char *modemdevicetail;
  char *modemspeed;
  char *modemparity;
  char *modemstopbits;
  char *modemtimeout;
  char *showterminal;
  int result;
  if (childpid != 0) {
    interp->result="line already active";
    return TCL_OK;
  }
  
  modemdevice=Tcl_GetVar(interp, "modemdevice", TCL_GLOBAL_ONLY);
  modemdevicetail=Tcl_GetVar(interp, "modemdevicetail", TCL_GLOBAL_ONLY);
  modemspeed=Tcl_GetVar(interp, "modemspeed", TCL_GLOBAL_ONLY);
  modemparity=Tcl_GetVar(interp, "modemparity", TCL_GLOBAL_ONLY);
  modemstopbits=Tcl_GetVar(interp, "modemstopbits", TCL_GLOBAL_ONLY);
  modemtimeout=Tcl_GetVar(interp, "modemtimeout", TCL_GLOBAL_ONLY);
  showterminal=Tcl_GetVar(interp, "showterminal", TCL_GLOBAL_ONLY);
  Tcl_GetInt(interp, showterminal, &showtermint);
  Tcl_GetInt(interp, modemtimeout, &timeouttime);

  /* open the modemdevice */
  result=modemopen(modemdevice,modemdevicetail,modemspeed,modemparity,modemstopbits);
  if (result==1) {
    interp->result = "can't open LCK.. for reading";
    return TCL_OK;
  }
  if (result==2) {
    interp->result = "LCK process active";
    return TCL_OK;
  }
  if (result==3) {
    interp->result = "can't delete LCK..";
    return TCL_OK;
  }
  if (result==4) {
    interp->result = "can't open LCK.. for writing";
    return TCL_OK;
  }
  if (result==5) {
    interp->result = "can't open modemdevice";
    return TCL_OK;
  }
  if (result==6) {
    interp->result = "tcget|setattr error";
    return TCL_OK;
  }
  
  /* Pipes are initiated */
  if (pipe(pipefd) == -1) {
    interp->result="pipe error";
    return TCL_OK;
  }
  if (showtermint == 1) {
    if (pipe(pipeterminalfd) == -1) {
      interp->result="pipe error";
      return TCL_OK;
    }
  }
  
  /* forking */
  if ((pid = fork()) > 0) {  /* parent process  */
    myinterp=interp;
    Tk_CreateFileHandler(pipefd[0],TK_READABLE,TalkProc,(ClientData) NULL); 

    /* show terminal */
    if (showtermint == 1) {
      Tk_CreateFileHandler(pipeterminalfd[0],TK_READABLE,TalkTerminal,(ClientData) NULL); 
    }
    
    /* set childpid on pid */
    childpid=pid;
    sprintf(interp->result,"%d",childpid);
    return TCL_OK;
  }
  else {
    if (pid == 0) {  /* child process */
      
      char *dialtype;
      char *modemnumber;
      char *modeminit;
      char modemconnect[30];
      char *expectfile;
      char *dialtry;
      char *repeatdelay;
      char dialbusy[]="line is busy";
      char dialerror2[]="error while dialing";
      int delay;
      int dialint;
      
      repeatdelay=Tcl_GetVar(interp, "repeatdelay", TCL_GLOBAL_ONLY);
      Tcl_GetInt(interp, repeatdelay, &delay);
      dialtype=Tcl_GetVar(interp, "dialtype", TCL_GLOBAL_ONLY);
      modemnumber=Tcl_GetVar(interp, "modemnumber", TCL_GLOBAL_ONLY);
      modeminit=Tcl_GetVar(interp, "modeminit", TCL_GLOBAL_ONLY);
      expectfile=Tcl_GetVar(interp, "expectfile", TCL_GLOBAL_ONLY);

      sprintf(modemconnect,"ATD%s%s\r",dialtype,modemnumber);
      
      while (1) {
	dialtry=Tcl_GetVar(interp, "dialtry", TCL_GLOBAL_ONLY);
	Tcl_GetInt(interp, dialtry, &dialint);
	++dialint;
	sprintf(dialtry,"%d",dialint);
	write (pipefd[1], dialup, strlen(dialup));
	
	result=modemdial(modeminit, modemconnect);
	if (result == 0) {
	  write (pipefd[1], dialconnect, strlen(dialconnect));
	  if (modemtalk(expectfile)!=0) {
	    write (pipefd[1], dialerror, strlen(dialerror));
	    while(1) sleep (1000);
	  }
	}
	if (result == 6) { /* BUSY */
	  write (pipefd[1], dialbusy, strlen(dialbusy));
	  sleep(delay);
	  continue;
	}  
	if (result == 7) { /* ERROR */
	  write (pipefd[1], dialerror, strlen(dialerror));
	  while(1) sleep (1000);
	}
	break;
      }
      write (pipefd[1], dialend, strlen(dialend));
      while(1) sleep (1000);
    }
    else { /* <fork> error */
      interp->result = "fork error";
      return TCL_OK;
    }
  }
}

/*
   **	Function name : KillPid
   **
   **	Description :
   **	Input :
   **	Output :
*/
int KillPid(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
  if (argc != 1) {
    interp->result = "wrong # args";
    return TCL_ERROR;
  }
  
  if (childpid==0) {
    interp->result = "child not active";
    return TCL_OK;
  }
  /* close the handler */
  Tk_DeleteFileHandler(pipefd[0]);
  close (pipefd[0]);
  close (pipefd[1]);
  if (showtermint == 1) {
    Tk_DeleteFileHandler(pipeterminalfd[0]);
    close (pipeterminalfd[0]);
    close (pipeterminalfd[1]);
  }

  /* kill child */
  if (kill (childpid, 2)==0) {
    waitpid (childpid,NULL,0);
    childpid=0;
    interp->result = "killed child process";
    return TCL_OK;
  }
  interp->result = "failed to kill child";
  return TCL_OK;
}

/*
   **	Function name : pidkill
   **
   **	Description :
   **	Input :
   **	Output :
*/
void pidkill(void)
{
  Tk_DeleteFileHandler(pipefd[0]);
  close (pipefd[0]);
  close (pipefd[1]);
  if (showtermint == 1) {
    Tk_DeleteFileHandler(pipeterminalfd[0]);
    close (pipeterminalfd[0]);
    close (pipeterminalfd[1]);
  }
  if(kill(childpid, 2)==0) {
    waitpid(childpid,NULL,0);
    childpid=0;
  }
}

