/* emutli1.c 05-May-98 */
/*
 * One short try to emulate TLI with SOCKETS.
 */

/* (C)opyright (C) 1993,1996 Martin Stover, Marburg, Germany
 *
 * 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 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 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.
 */

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * Some of the Code in this module is stolen from the following
 * Programms: ipx_interface, ipx_route, ipx_configure, which were
 * written by Greg Page, Caldera, Inc.
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#if 0
# include <linux/sockios.h>
#endif
#include "net.h"
#if 0
# include <linux/if.h>
# include <linux/route.h>
# include <linux/in.h>
#else
# include <net/if.h>
# include <net/route.h>
# include <netinet/in.h>
#endif
#include <errno.h>

static int    have_ipx_started=0;
static int    auto_interfaces=0;
static int    org_auto_interfaces=0;

static int x_ioctl(int sock, int mode, void *id)
{
  int result;
  int i = 0;
  do {
    result = ioctl(sock, mode, id);
    i++;
  } while ((i < 5) && (result < 0) && (errno == EAGAIN));
  return(result);
}

int read_interface_data(uint8* data, uint32 *rnet, uint8 *node,
                          int *flags, uint8 *name)

/* returns frame or -1 for no frame or -2 for error */
{
  uint32 snet;
  int   frame  =-2;
  int   xflags = 0;
  uint8 buff1[200];
  uint8 buff2[200];
  uint8 buff3[200];
  uint8 buff4[200];
  if (!rnet)  rnet =&snet;
  if (!flags) flags=&xflags;
  else *flags=0;
  if (sscanf((char*)data, "%x %199s %199s %199s %199s",
          rnet, (char*)buff1, (char*)buff2, (char*)buff3, (char*)buff4) == 5  ) {
    int len = strlen((char*)buff4);
    if (!len) return(-2);
    switch (*(buff4+len-1)) {
      case  '2' : frame = IPX_FRAME_8022; break;
      case  '3' : frame = IPX_FRAME_8023; break;
      case  'P' : frame = IPX_FRAME_SNAP; break;
      case  'I' : frame = IPX_FRAME_ETHERII; break;
#ifdef IPX_FRAME_TR_8022
      case  'R' : frame = IPX_FRAME_TR_8022; break;
#endif
      case  'e' :
      case  'E' : frame = -1; break;   /* NONE */
      default   : return(-2);
    }
    if (node) strmaxcpy(node, buff1, 12);

    upstr(buff2);
    if (!strcmp((char*)buff2, "YES")) /* primary */
      *flags |= 1;

    if (name) strmaxcpy(name, buff3, 20);
    upstr(buff3);
    if (!strcmp((char*)buff3, "INTERNAL")) /* internal net */
      *flags |= 2;
  }
  return(frame);
}

int get_interface_frame_name(char *name, uint32 net)
/* returns frame and name of Device of net */
{
  int frame = -1;
  FILE *f=fopen("/proc/net/ipx_interface", "r");
  if (f) {
    char buff[200];
    while (fgets((char*)buff, sizeof(buff), f) != NULL){
      uint32   rnet;
      uint8 dname[25];
      int fframe = read_interface_data((uint8*) buff, &rnet, NULL, NULL, dname);
      if (fframe < 0) continue;
      if (rnet == net) {
        if (name) strcpy((char*)name, (char*)dname);
        frame = fframe;
        break;
      }
    }
    fclose(f);
  }
  return(frame);
}

static void del_special_net(int special, char *devname, int frame)
/* specials =       */
/* IPX_SPECIAL_NONE */
/* IPX_INTERNAL     */
/* IPX_PRIMARY      */
/* devname + frame only if not IPX_INTERNAL */
{
  int sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
  if (sock > -1) {
    struct ifreq  id;
    struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)&id.ifr_addr;
    memset(&id, 0, sizeof(struct ifreq));

    sipx->sipx_network = 0L;
    sipx->sipx_special = special;
    sipx->sipx_family  = AF_IPX;
    if (special == IPX_PRIMARY) {
      FILE *f=fopen("/proc/net/ipx_interface", "r");
      if (f) {
        char  buff[200];
        uint8 name[25];
        while (fgets((char*)buff, sizeof(buff), f) != NULL){
          int flags = 0;
          int frame = read_interface_data((uint8*) buff, NULL, NULL,
                                     &flags, name);
          if (frame < 0) continue;
          sipx->sipx_type = frame;
          if (flags & 1) { /* primary */
            if (flags & 2){ /* primary == internal net */
              sipx->sipx_special = IPX_INTERNAL;
            } else
              strcpy(id.ifr_name, (char*)name);
            break;
          }
        }
        fclose(f);
      }
    } else if (special != IPX_INTERNAL) {
      if (devname && *devname) strcpy(id.ifr_name, (char*)devname);
      sipx->sipx_type    = frame;
    }
    sipx->sipx_action  = IPX_DLTITF;
    x_ioctl(sock, SIOCSIFADDR, &id);
    close(sock);
  }
}

static void del_all_interfaces_nets(void)
/* removes all ipx_interfaces */
{
  int sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
  if (sock > -1) {
    FILE *f=fopen("/proc/net/ipx_interface", "r");
    if (f) {
      char  buff[200];
      uint8 name[25];
      struct ifreq  id;
      struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)&id.ifr_addr;

      while (fgets((char*)buff, sizeof(buff), f) != NULL){
        int flags = 0;
        int frame = read_interface_data((uint8*) buff, NULL, NULL,
                                   &flags, name);
        if (frame < -1) continue;

        memset(&id, 0, sizeof(struct ifreq));
        sipx->sipx_network = 0L;
        sipx->sipx_family  = AF_IPX;

        if (flags & 2) {     /* internal */
          sipx->sipx_special = IPX_INTERNAL;
        } else {
          sipx->sipx_type = frame;
          strcpy(id.ifr_name, (char*)name);
          if (flags & 1) /* primary  */
            sipx->sipx_special = IPX_PRIMARY;
          else
            sipx->sipx_special = IPX_SPECIAL_NONE;
        }
        sipx->sipx_action  = IPX_DLTITF;
        x_ioctl(sock, SIOCSIFADDR, &id);
      }
      fclose(f);
    }
    close(sock);
  }
}

#define del_internal_net() \
  del_special_net(IPX_INTERNAL, NULL, 0)
#define del_interface(devname, frame) \
  del_special_net(IPX_SPECIAL_NONE, (devname), (frame))
#define del_primary_net() \
  del_special_net(IPX_PRIMARY, NULL, 0)

static int add_special_net(int special,
  char *devname, int frame, uint32 netnum, uint32 node)
{
  int result = -1;
  int sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
  if (sock > -1) {
    struct ifreq  id;
    struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)&id.ifr_addr;
    memset(&id, 0, sizeof(struct ifreq));
    if (special != IPX_INTERNAL){
      if (devname && *devname) strcpy(id.ifr_name, (char*)devname);
      sipx->sipx_type    =  frame;
    } else {
      uint32 xx=htonl(node);
      memcpy(sipx->sipx_node+2, &xx, 4);
    }
    sipx->sipx_network = htonl(netnum);
    sipx->sipx_special = special;
    sipx->sipx_family  = AF_IPX;
    sipx->sipx_action  = IPX_CRTITF;
    result = x_ioctl(sock, SIOCSIFADDR, &id);
    close(sock);
  }
  return(result);
}
#define add_internal_net(netnum, node) \
  add_special_net(IPX_INTERNAL, NULL, 0, (netnum), (node))

#define add_primary_net(devname, frame, netnum) \
  add_special_net(IPX_PRIMARY, (devname), (frame), (netnum), 0)

int add_device_net(char *devname, int frame, uint32 netnum) 
{
  return(add_special_net(IPX_SPECIAL_NONE, (devname), (frame), (netnum), 0));
}

int get_frame_name(uint8 *framename, int frame)
{
  char *frname=0;
  switch (frame) {
    case  -1               : frname = "AUTO";       break;
#ifdef IPX_FRAME_TR_8022
    case IPX_FRAME_TR_8022 : frname = "TOKEN";      break;
#endif
    case IPX_FRAME_8022    : frname = "802.2";      break;
    case IPX_FRAME_8023    : frname = "802.3";      break;
    case IPX_FRAME_SNAP    : frname = "SNAP";       break;
    case IPX_FRAME_ETHERII : frname = "ETHERNET_II";break;
    default                : framename[0] = '\0';
                             return(-1);
  } /* switch */
  strcpy((char*)framename, frname);
  return(0);
}

int ipx_inuse(int mode)
/* returns 0 if ipx is idle */
{
  FILE *f=fopen("/proc/net/ipx", "r");
  int idle=0;
  if (f) {
    char buff[200];
    while (fgets((char*)buff, sizeof(buff), f) != NULL){
      uint32 network;
      int     sock;
      if (2==sscanf((char*)buff, "%x:%x", &network, &sock)) {
        if (mode == 0) {
          if (sock >= 0x4000) { /* user socket */
            idle=sock;
            break;
          }
        } else if (sock==mode) {
          idle=sock;
          break;
        }
      }
    }
    fclose(f);
  }
  return(idle);
}

static void ipx_in_use_abort()
{
  errorp(11, "!! IPX IN USE ERROR !!",
       "mars_nwe would kill existing IPX programs if starting\n"
       "because it must reinit ipx devices.\n"
       "Please stop other IPX programs e.g. ncpmount,\n"
       "or change mars_nwe's configuration.\n"
       );
  exit(1);
}

int init_ipx(uint32 network, uint32 node, int ipx_debug, int flags)
{
  int result=-1;
  int sock;
  uint32 primary_net=0L;
#if INTERNAL_RIP_SAP
# ifdef CONFIG_IPX_INTERN
  errorp(11, "!! configuration error !!",
       "mars_nwe don't run with kernel 'full internal net'.\n"
       "Change kernel option CONFIG_IPX_INTERN=NO (nobody needs it)\n"
       "or use 'ipxd' and change mars_nwe INTERNAL_RIP_SAP=0.");
  exit(1);
# endif
#endif

  if ((sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX)) < 0) {
    errorp(1,  "EMUTLI:init_ipx", NULL);
    errorp(10, "Problem", "probably kernel-IPX is not setup correctly");
    exit(1);
  } else {
    ipx_config_data cfgdata;
    struct sockaddr_ipx ipxs;
    ioctl(sock, SIOCIPXCFGDATA, &cfgdata);
    org_auto_interfaces =
        auto_interfaces = cfgdata.ipxcfg_auto_create_interfaces;
    set_sock_debug(sock);
    result=0;
    
    memset((char*)&ipxs, 0, sizeof(struct sockaddr_ipx));
    ipxs.sipx_port   = htons(SOCK_NCP);
    ipxs.sipx_family = AF_IPX;
    if (bind(sock, (struct sockaddr*)&ipxs,
          sizeof(struct sockaddr_ipx))==-1) {
      if (errno == EEXIST || errno == EADDRINUSE) {
        result = -1;
        errorp(1, "EMUTLI:init_ipx socket 0x451", NULL);
        exit(1);
      }
    } else {
      socklen_t maxplen=sizeof(struct sockaddr_ipx);
      if (getsockname(sock, (struct sockaddr*)&ipxs, &maxplen) != -1)
        primary_net= ntohl(ipxs.sipx_network);
      if (primary_net)
        have_ipx_started++;
    }
    /* build new internal net */
    if (network) {
      int diffs = (primary_net && (network != primary_net));
      if (diffs) {
        XDPRINTF((1,0,"Existing primary network will be reinit from %x to %x", 
           primary_net, network));
        if (ipx_inuse(0) && !(flags&4)) 
          ipx_in_use_abort();
      }
      if ((flags&4) || diffs || !primary_net) {  /* if complete reinit or diffs */
        del_internal_net();
        add_internal_net(network, node);
      }
      have_ipx_started++;
    }
    if ((flags & 2) && !auto_interfaces) { /* set auto interfaces */
      auto_interfaces = 1;
      ioctl(sock, SIOCAIPXITFCRT, &auto_interfaces);
    }
    close(sock);
  }
  return(result);
}

void exit_ipx(int flags)
{
  int sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
  if (sock > -1) {
    /* Switch DEBUG off */
    set_locipxdebug(0);
    set_sock_debug(sock);
    if (have_ipx_started && !(flags&1))
       org_auto_interfaces = 0;
    if (auto_interfaces != org_auto_interfaces)
      ioctl(sock, SIOCAIPXITFCRT, &org_auto_interfaces);
    close(sock);
  }
  if (flags&4) {
    del_all_interfaces_nets();
  } else if (!ipx_inuse(0)) {
    del_internal_net();
  }
}

int init_dev(char *devname, int frame, uint32 network, int wildmask)
{
   if (!(wildmask & 3))
      del_interface(devname, frame);
   if (!have_ipx_started) {
     if (wildmask) return(-99);
     have_ipx_started++;
     if (!ipx_inuse(0)) {
       del_primary_net(); 
       add_primary_net(devname, frame, network);
     }
   } else {
     if (!wildmask)
       return(add_device_net(devname, frame, network));
     return(1);
   }
   return(0);
}

void exit_dev(char *devname, int frame)
{
  del_interface(devname, frame);
}

void ipx_route_add(uint32  dest_net,
                   uint32  route_net,
                   uint8   *route_node)
{
  struct rtentry  rd;
  int result;
  int sock;
  /* Router */
  struct sockaddr_ipx   *sr = (struct sockaddr_ipx *)&rd.rt_gateway;
  /* Target */
  struct sockaddr_ipx   *st = (struct sockaddr_ipx *)&rd.rt_dst;

  rd.rt_flags = RTF_GATEWAY;

  st->sipx_network = htonl(dest_net);
  sr->sipx_network = htonl(route_net);

  if (route_node)
    memcpy(sr->sipx_node, route_node, IPX_NODE_SIZE);
  else
    memset(sr->sipx_node, 0, IPX_NODE_SIZE);

  if ( (sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX)) < 0){
    errorp(0, "EMUTLI:ipx_route_add", NULL);
    return;
  }
  sr->sipx_family = st->sipx_family = AF_IPX;

  if ( 0 != (result = x_ioctl(sock, SIOCADDRT, &rd))) {
    switch (errno) {
      case ENETUNREACH:
        errorp(0, "ROUTE ADD", "Router network (%08X) not reachable.\n",
                     htonl(sr->sipx_network));
      break;

      case EEXIST:
      case EADDRINUSE:
      break;

      default:
        errorp(0, "ROUTE ADD", NULL);
        break;
    }
  }
  close(sock);
}

void ipx_route_del(uint32 net)
{
  struct rtentry  rd;
  int    sock;
  /* Router */
  struct sockaddr_ipx   *sr = (struct sockaddr_ipx *)&rd.rt_gateway;
  /* Target */
  struct sockaddr_ipx   *st = (struct sockaddr_ipx *)&rd.rt_dst;
  rd.rt_flags = RTF_GATEWAY;
  st->sipx_network = htonl(net);
  if ( (sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX)) < 0){
    errorp(0, "EMUTLI:ipx_route_del", NULL);
    return;
  }
  sr->sipx_family = st->sipx_family = AF_IPX;
  x_ioctl(sock, SIOCDELRT, &rd);
  close(sock);
}
