/* 
 * $Id: divertctrl.c,v 1.4 2001/06/10 17:38:25 werner Exp $
 *
 * Control program for the dss1 diversion supplementary services. (User side)
 *
 * Copyright 1999       by Werner Cornelius (werner@ikt.de)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 * $Log: divertctrl.c,v $
 * Revision 1.4  2001/06/10 17:38:25  werner
 *
 * Updated tool and manpage with new remote dial feature
 *
 * Revision 1.3  2001/01/09 19:27:55  werner
 *
 * Added Output for interrogate and description for services.
 * Thanks for help from Wolfram Joost.
 *
 * Revision 1.2  1999/09/02 13:24:14  paul
 * fixed some compile warnings
 *
 * Revision 1.1  1999/05/07 21:33:01  werner
 * Initial release of divertctrl
 *
 *
 */

#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include "isdn_divert.h"

static char number_types[7] = {
       'U',
       'I',
       'N',
       'S',
       'M',
       '?',
       'A' };

static char cf_types[4][5] = {
        "CFU", "CFB", "CFNR", "?" };

static struct {
       unsigned   type;
       char       *name;
} service_types[18] = {
       {  0, "all services" },
       {  1, "speech" },
       {  2, "unrestricted digital information" },
       {  3, "audio 3.1 kHz" },
       {  4, "unrestricted digital information wtaa" },
       {  5, "multirate" },
       { 32, "telephony 3.1 kHz" },
       { 33, "teletex" },
       { 34, "telefax group 4 class 1" },
       { 35, "videotex syntax based" },
       { 36, "videotelephony" },
       { 37, "telefax group 2/3" },
       { 38, "telephony 7 kHz" },
       { 39, "eurofiletransfer" },
       { 40, "filetransfer and access management" },
       { 41, "videoconference" },
       { 42, "audio graphic conference" } };

char *progname; /* name of program */ 
ulong  drvid; /* ids of driver */
int  fd; /* file descriptor for read and ioctl */
divert_ioctl dioctl; /* structure exchanging ioctl data */
char **argp; /* pointer to actual parameter */
int argrest; /* remaining args */
int waitreq; /* wait requested */
int exitcode;/* exitcode for program */

/****************************/
/* print a short usage help */
/****************************/
void usage(void)
{ close(fd);
  fprintf(stderr,"usage: %s [wait] <Command> [parms]\n\n",progname);
  fprintf(stderr,"[wait]    : wait optional keyword until -> wait until command executed \n");
  fprintf(stderr,"<Command> : desired command to execute\n");
  fprintf(stderr,"   activate    <drivers> <cfu,cfb,cfnr> <msn> <service/0> <dest_no>\n");  
  fprintf(stderr,"   deactivate  <drivers> <cfu,cfb,cfnr> <msn> <service/0> \n");  
  fprintf(stderr,"   interrogate <drivers> <cfu,cfb,cfnr> <msn> <service/0> \n");  
  fprintf(stderr,"   listrules   <drivers> -> list all rules for drivers\n");
  fprintf(stderr,"   ignore      <callid> [uus1_string] -> ignore call with callid/send uus1\n");
  fprintf(stderr,"   alert       <callid> [uus1_string] -> alert call with callid/send uus1\n");
  fprintf(stderr,"   deflect     <callid> [to_nr] -> deflect call with callid to to_nr \n");
  fprintf(stderr,"   appendrule  <drivers> -> append a rule at list end\n");
  fprintf(stderr,"   insertrule  <drivers> -> insert a rule at list start\n");
  fprintf(stderr,"   flushrules  [drivers] -> flush all rules for specified drivers\n");
  fprintf(stderr,"   append- and insertrule take the following arguments:\n");
  fprintf(stderr,"   <drivers> <action> [msn] [si1] [si2] [caller] [screen] [delay] [callopt] [destnr/if]\n");
  fprintf(stderr,"   drivers -> driver names for D-chans, %% separated, - = all D-chan\n"); 
  fprintf(stderr,"   action -> 0/ignore 1/report 2/proceed 3/alert 4/reject\n");
  fprintf(stderr,"   msn -> - = wildcard or explicit selected msn.subaddress\n");
  fprintf(stderr,"   when action = 5 then if specifies a network interface to dial\n");
  fprintf(stderr,"   si1 -> 0 = all services, else bitmask for services 1..7\n");
  fprintf(stderr,"   si2 -> 0 = ignore, else specified value\n");
  fprintf(stderr,"   caller -> - = wildcard, 0 = unknown, else number.subaddress\n");
  fprintf(stderr,"             a - trailing number digits is handled as wildcard\n");
  fprintf(stderr,"   screen -> 0 = noscreen 1 = divertonly 2 = divertto\n");
  fprintf(stderr,"   delay -> time in seconds until hangup or divert in auto mode\n");
  fprintf(stderr,"   callopt -> 0 = all calls, 1 = only non waiting, 2 = only waiting\n");
  fprintf(stderr,"   destnr -> destination number and subaddress\n");
  fprintf(stderr,"   defaults: allmsn, si1=0, si2=0, all callers, noscreen, delay=10\n");
  fprintf(stderr,"\n");  
  exit(-1);
} /* usage */


/******************************************/
/* get the driver parameter and set drvid */
/******************************************/
void getdrvid(void)
{ char *p,*p1;

  argp++; argrest--;

  if (argrest < 1) 
   { drvid = DEFLECT_ALL_IDS;
     return;
   }

  if ((*argp)[0] == '-')
    drvid = DEFLECT_ALL_IDS;
   else
    { drvid = 0; /* first no device */
      p = *argp; 
      while (p)
       { p1 = p;
         p = strchr(p1,'%');
         if (p) *p++ = '\0'; 
         strcpy(dioctl.getid.drvnam,p1); /* set string to query */
         if (ioctl(fd,IIOCGETDRV,&dioctl))
          { fprintf(stderr,"driver %s not found \n",p1);
            close(fd);
            exit(-1);
          } 
         drvid |= (1L << dioctl.getid.drvid);
       }
    }
  argp++; argrest--;
} /* getdrvid */ 

/***********************************/
/* set the rule pars from cmd line */
/***********************************/
void setrulepar(void)
{ divert_rule *dr = &dioctl.getsetrule.rule;
  unsigned u;

  /* first set the defaults */
  dr->drvid = 0; /* no driver */
  dr->action= DEFLECT_IGNORE; 
  strcpy(dr->my_msn,"0");
  dr->si1   = 0;
  dr->si2   = 0;
  strcpy(dr->caller,"-");
  dr->screen= 0; /* no screening */
  dr->waittime = 0;
  dr->callopt = 0;
  dr->to_nr[0] = '\0';

  if (!argrest) return; 

  /* action */
  if ((sscanf(*(argp),"%u",&u) <= 0) || (u > 5))
   { fprintf(stderr,"invalid action value %s\n",*argp);
     usage();
   }
  dr->action = u & 0xFF;
  if (((dr->action == DEFLECT_ALERT) || (dr->action == NETWORK_DIAL)) && (argrest != 9))
   { fprintf(stderr,"alerting/dialing action must be supplied with all parms\n");
     usage();
   }    
  if (!--argrest) return;

  /* copy msn */
  strcpy(dr->my_msn, *(++argp));
  if (!--argrest) return; 

  /* si1 */
  if (sscanf(*(++argp),"%u",&u) <= 0)
   { fprintf(stderr,"invalid si1 value %s\n",*argp);
     usage();
   }
  dr->si1 = u & 0xFF;   
  if (!--argrest) return;
  
  /* si2 */
  if (sscanf(*(++argp),"%u",&u) <= 0)
   { fprintf(stderr,"invalid si2 value %s\n",*argp);
     usage();
   }
  dr->si2 = u & 0xFF;   
  if (!--argrest) return;
  
  /* caller */
  strcpy(dr->caller,*(++argp));
  if (!--argrest) return;
  
  /* screen */
  if ((sscanf(*(++argp),"%u",&u) <= 0) || (u > 2))
   { fprintf(stderr,"invalid screen value %s\n",*argp);
     usage();
   }
  dr->screen = u & 0xFF;   
  if (!--argrest) return;

  /* delay */
  if ((sscanf(*(++argp),"%u",&u) <= 0) || (u > 99))
   { fprintf(stderr,"invalid delay value %s\n",*argp);
     usage();
   }
  dr->waittime = u & 0xFF;   
  if (!--argrest) return;

  /* callopt */
  if ((sscanf(*(++argp),"%u",&u) <= 0) || (u > 2))
   { fprintf(stderr,"invalid callopt value %s\n",*argp);
     usage();
   }
  dr->callopt = u & 0xFF;   
  if (!--argrest) return;

  /* to_nr */
  strcpy(dr->to_nr,*(++argp));
  if (!--argrest) return;  
  
  fprintf(stderr,"to many cmd line parms\n");
  usage();
} /* setrulepar */

/************************************************/
/* getcallid fetches the callid for id commands */
/************************************************/
void getcallid(void)
{
  argp++; argrest--;

  if (argrest < 1) 
   { fprintf(stderr,"callid missing\n");
     usage();  
   }

  if (sscanf(*argp,"%lu",&dioctl.fwd_ctrl.callid) != 1)
    { fprintf(stderr,"invalid callid value %s\n",*argp);
      usage();
    }
  argp++; argrest--;
} /* getcallid */

/***************************************************************************/
/* getcfpar fetches the parms for de/activating and interrogating services */
/***************************************************************************/
#define CFP dioctl.cf_ctrl
void getcfpar(void)
{ unsigned u;

  /* first set the defaults */
  CFP.service = 0; /* all services */
  CFP.msn[0] = '\0'; /* all msns */
  CFP.fwd_nr[0] = '\0';
  
  if (!argrest)
   { fprintf(stderr,"no procedure specified \n");
     usage();
   } 
  if (!strcmp(*argp,"cfu")) 
    CFP.cfproc = 0;
     else 
      if (!strcmp(*argp,"cfb"))
        CFP.cfproc = 1;
         else
          if (!strcmp(*argp,"cfnr"))
            CFP.cfproc = 2;
             else
	      { fprintf(stderr,"invalid procedure %s\n",*argp);
                usage();
              }
  if (waitreq) CFP.cfproc |= 0x80;
  argp++; argrest--; 

  strcpy(CFP.msn,*(argp++));
  if (!(--argrest)) return;

  if ((sscanf(*argp,"%u",&u) != 1) ||
      (u > 255))
   { fprintf(stderr,"invalid service [0-255] %s\n",*argp);
     usage();
   }    
  CFP.service = u & 0xFF;
  argrest--; argp++;
} /* getcfpar */ 
#undef CFP

/********************/
/* execute commands */
/********************/
static void do_command(void)
{ divert_rule *dr = &dioctl.getsetrule.rule;
  divert_ioctl ioc;
  int i;
  ulong u,cmd;
  fd_set rdfs;
  struct timeval tv;
  char read_buf[512];
  ssize_t read_len;
  char *read_ptr;
  ulong read_procid;
  ulong read_counter;
  char read_driver[30];
  u_long read_msn_type;
  char read_msn[35];
  u_long read_cfproc;
  u_long read_service;
  u_long read_tonr_type;
  char read_tonr[35];
  int read_char_count;
  char read_end;
  char read_found;

  if (!strcmp(*argp,"listrules"))
    { getdrvid();
      dioctl.getsetrule.ruleidx = 0; /* get first rule */ 
      while (1)
       { /* fetch all rules */
         if (ioctl(fd,IIOCGETRULE,&dioctl)) return; /* no more rules */
         i = 0; /* first interface */
         dr->drvid = drvid & dr->drvid; /* only requested bits */ 
         while (dr->drvid)  
	  { if (!(dr->drvid & 1))
             { dr->drvid >>= 1; /* next entry */
               i++;
               continue;
             } 
            if (dr->drvid == DEFLECT_ALL_IDS)
             { printf("%10s","-"); /* all if */
               dr->drvid = 0; /* no further output */
             }  
             else
              { ioc.getid.drvid = i++; /* set para */
                dr->drvid >>= 1;
                if (ioctl(fd,IIOCGETNAM,&ioc)) continue; /* invalid driver */
                if (!ioc.getid.drvnam[0]) continue; /* no name */
                printf("%10s ",ioc.getid.drvnam);
              }
            printf("%15s %20s %2d %3d %d %2d %d %s\n",
                   dr->my_msn,  /* msn */ 
                   dr->caller,  /* caller id */
                   dr->si1,     /* si1 0 = all */
                   dr->si2,     /* si2 0 = all */
                   dr->action,  /* desired action */
                   dr->waittime,/* wait until action */
                   dr->callopt, /* option for waiting calls */
                   dr->to_nr);  /* deflect to */
          } /* interface match */ 
         dioctl.getsetrule.ruleidx++; /* next rule */ 
       } /* fetch all rules */
    } /* listrules */
  else
  if (!strcmp(*argp,"deflect"))
    { getcallid();
      dioctl.fwd_ctrl.subcmd = 2;
      if (argrest)
       { strcpy(dioctl.fwd_ctrl.to_nr,*argp);
         argp++; argrest--;
       }
      else
         dioctl.fwd_ctrl.to_nr[0] = '\0';
      exitcode = ioctl(fd,IIOCDODFACT,&dioctl);
    } /* deflect */
  else
  if (!strcmp(*argp,"alert"))
    { getcallid();
      dioctl.fwd_ctrl.subcmd = 1;
      if (argrest)
       { strcpy(dioctl.fwd_ctrl.to_nr,*argp);
         argp++; argrest--;
       }
      else
         dioctl.fwd_ctrl.to_nr[0] = '\0';
      exitcode = ioctl(fd,IIOCDODFACT,&dioctl);
    } /* alert */
  else
  if (!strcmp(*argp,"ignore"))
    { getcallid();
      dioctl.fwd_ctrl.subcmd = 0;
      if (argrest)
       { strcpy(dioctl.fwd_ctrl.to_nr,*argp);
         argp++; argrest--;
       }
      else
         dioctl.fwd_ctrl.to_nr[0] = '\0';
      exitcode = ioctl(fd,IIOCDODFACT,&dioctl);
    } /* ignore */
  else
  if (!strcmp(*argp,"insertrule"))
    { getdrvid();
      setrulepar();
      dioctl.getsetrule.ruleidx = 0; /* start of list */
      dr->drvid = drvid; /* driver id */
      ioctl(fd,IIOCINSRULE,&dioctl);
    } /* insertrule */
  else
  if (!strcmp(*argp,"appendrule"))
    { getdrvid();
      setrulepar();
      dioctl.getsetrule.ruleidx = -1; /* end of list */
      dr->drvid = drvid; /* driver id */
      ioctl(fd,IIOCINSRULE,&dioctl);
    } /* appendrule */
  else
  if (!strcmp(*argp,"flushrules"))
    { getdrvid(); 
      dioctl.getsetrule.ruleidx = 0; /* start of list */
      while (1)
       { /* fetch all rules */
         if (ioctl(fd,IIOCGETRULE,&dioctl)) return; /* no more rules */
         if (drvid & dr->drvid)
	  { dr->drvid &= ~drvid; /* delete drivers */
            if (dr->drvid)
	     { ioctl(fd,IIOCMODRULE,&dioctl);
               dioctl.getsetrule.ruleidx++;
             }       
            else
             ioctl(fd,IIOCDELRULE,&dioctl); 
          } 
         else
          dioctl.getsetrule.ruleidx++;
       } /* fetch all rules */
    } /* flushrules */
  else
  if ((!strcmp(*argp,"activate")) || (!strcmp(*argp,"deactivate"))
      || (!strcmp(*argp,"interrogate")))
    { if (*argp[0] == 'a') 
        cmd = IIOCDOCFACT;
       else if (*argp[0] == 'i')
         cmd = IIOCDOCFINT; 
       else
         cmd = IIOCDOCFDIS; 
      getdrvid();
      getcfpar(); 
      dioctl.cf_ctrl.fwd_nr[0] = '\0';
      if (cmd == IIOCDOCFACT)
       { if (!argrest)
          { fprintf(stderr,"missing diverted to number \n");
            usage();
          }
         strcpy(dioctl.cf_ctrl.fwd_nr,*(argp++));
         argrest--;
       } /* only activate */
      dioctl.cf_ctrl.drvid = 0;
      u = drvid; /* list of drivers */
      while (u)
       { if (u & 1)
	 { if ((exitcode = ioctl(fd,cmd,&dioctl)) > 0) 
              return; /* error executing diversion */
            else
             if ((exitcode < 0) && (drvid != DEFLECT_ALL_IDS))
               return;
                        read_end = 0;
            read_found = 0;
            while ( !read_end && (cmd == IIOCDOCFINT) )
             { FD_ZERO(&rdfs);
               FD_SET(fd,&rdfs);
               tv.tv_sec = 4;
               tv.tv_usec = 0;
               if (select(fd+1,&rdfs,NULL,NULL,&tv) != 1)
                { break;
                }
               read_len = read(fd,read_buf,sizeof(read_buf)-1);
               if (read_len)
               { read_buf[read_len] = 0;
                 read_ptr = read_buf;
                 while (*read_ptr)
                  { if ( ((*read_ptr != (DIVERT_REPORT | 0x30)) ||
                        (read_ptr[1] != ' ')) && strncmp(read_ptr,"128 ",4) )
                    { while (*read_ptr && (*read_ptr != '\r'))
                        read_ptr++;
                      if (*read_ptr)
                        read_ptr++;
                      continue;
                    }
                    if ( !strncmp(read_ptr,"128 ",4)) {
                       sscanf(read_ptr + 4,"%lx",&read_procid);
                       if (read_procid == dioctl.cf_ctrl.procid) {
                          puts("Error. :-(");
                          read_end = 1;
                          break;
                       }
                    }
                    else {
                       read_ptr += 2;
                       sscanf(read_ptr,"%lx %lu %s %lu %s %lx %lu %n",
                              &read_procid,&read_counter,read_driver,
                              &read_msn_type,read_msn,&read_service,&read_cfproc,
                              &read_char_count);
                       read_ptr += read_char_count;
                       read_end = (read_procid == dioctl.cf_ctrl.procid) && !read_counter;
                       if (read_end)
                        { if (!read_found)
                            puts("Nothing active.");
                          break;
                        }
                       if ( read_procid == dioctl.cf_ctrl.procid )
                        { sscanf(read_ptr,"%lu %s",&read_tonr_type,read_tonr);
                          if (read_msn_type > 6)
                             read_msn_type = 5;
                          if (read_tonr_type > 6)
                             read_tonr_type = 5;
                          if (read_cfproc > 3)
                             read_cfproc = 3;
                          read_found = 1;
                          printf("%-8s %c %-8s %-4s %c %-10s ",read_driver,
                                 number_types[read_msn_type],read_msn,
                                 cf_types[read_cfproc],
                                 number_types[read_tonr_type],read_tonr);
                          for (read_char_count = 0; read_char_count < 18; read_char_count++)
                           { if (service_types[read_char_count].type == read_service)
                              { puts(service_types[read_char_count].name);
                                break;
                              }
                           }
                          if (read_char_count == 18)
                             puts("unkown");
                        }
                     }
                     while (*read_ptr && (*read_ptr != '\r'))
                        read_ptr++;
                     if (*read_ptr)
                        read_ptr++;
                 }
               }
             }
            exitcode = 0;
          }
         u >>= 1; /* next driver */  
         dioctl.cf_ctrl.drvid++;    
       } /* for all drivers */
    } /* activate */
  else
    { fprintf(stderr,"unknown command: %s\n",*argp);
      usage();
    }
} /* do_command */


int main(int argc, char *argv[])
{

  exitcode = 0; /* default no error */

  /* first get our own program name */
  if ((progname = strrchr(argv[0], '/')))
    progname++;
  else
    progname = argv[0];

  /* now open the interface */
  if ((fd = open("/proc/net/isdn/divert", O_RDWR)) < 0)
    { if ((fd = open("/dev/isdndivert", O_RDWR)) < 0)
        { fprintf(stderr,"error opening device (dss1_divert module not loaded ?)\n");
          exit(-1);
        } 
    } 
  
  /* check the interface version */
  dioctl.drv_version  = -1; /* invalidate */
  if (ioctl(fd,IIOCGETVER,&dioctl))
    { fprintf(stderr,"could not execute ioctl call \n");
      close(fd);
      exit(-1);
    }  
  if ( dioctl.drv_version != DIVERT_IIOC_VERSION)
    { fprintf(stderr,"versions of %s and driver incompatible\n",progname);
      close(fd);
      exit(-1);
    } 

  argrest = argc - 1; /* remaining number of parameters */
  argp = argv + 1; /* pointer to next parameter/command */

  waitreq = 0; /* if no wait desired */
  if (argrest >= 1)
   if (!strcmp(*argp,"wait"))
    { waitreq = 1; 
      argp++;
      argrest--;
    }  
  /* a minimum of 1 additional cmd line parameters is needed */
  if (argrest < 1) usage();
  
  do_command(); 
  close(fd);
  exit(exitcode);
} /* main */


