// $Id: chbind.c,v 1.11 2006/01/22 18:17:24 ensc Exp $

// Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
// based on chbind.cc by Jacques Gelinas
//  
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "vserver.h"
#include "util.h"

#include <lib/internal.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define ENSC_WRAPPERS_PREFIX	"chbind: "
#define ENSC_WRAPPERS_IO	1
#define ENSC_WRAPPERS_UNISTD	1
#define ENSC_WRAPPERS_VSERVER	1
#include "wrappers.h"

#define CMD_HELP	0x1000
#define CMD_VERSION	0x1001

#define CMD_SILENT	0x2000
#define CMD_IP		0x2001
#define CMD_BCAST	0x2002
#define CMD_NID		0x2003

int wrapper_exit_code = 255;


static struct option const
CMDLINE_OPTIONS[] = {
  { "help",     no_argument,  0, CMD_HELP },
  { "version",  no_argument,  0, CMD_VERSION },
  { "silent",   no_argument,  0, CMD_SILENT },
  { "ip",       required_argument, 0, CMD_IP },
  { "bcast",    required_argument, 0, CMD_BCAST },
  { "nid",	required_argument, 0, CMD_NID },
  { 0,0,0,0 }
};

static void
showHelp(int fd, char const *cmd, int res)
{
  WRITE_MSG(fd, "Usage:\n  ");
  WRITE_STR(fd, cmd);
  WRITE_MSG(fd,
	    " [--silent] [--nid <nid>] [--ip <ip_num>[/<mask>]] [--bcast <broadcast>] [--] <commands> <args>*\n\n"
	    "Please report bugs to " PACKAGE_BUGREPORT "\n");

  exit(res);
}

static void
showVersion()
{
  WRITE_MSG(1,
	    "chbind " VERSION " -- bind to an ip and execute a program\n"
	    "This program is part of " PACKAGE_STRING "\n\n"
	    "Copyright (C) 2003,2004 Enrico Scholz\n"
	    VERSION_COPYRIGHT_DISCLAIMER);
  exit(0);
}

/*
	Check if a network device exist in /proc/net/dev.
	This is used because ifconfig_ioctl triggers modprobe if requesting
	information about non existant devices.

	Return != 0 if the device exist.
*/
static bool
existsDevice(char const *dev_raw)
{
  size_t	buf_size=8192;
  char		dev[strlen(dev_raw)+2];

  strcpy(dev, dev_raw);
  strcat(dev, ":");
  for (;;) {
    char	buf[buf_size];
    char *	pos;
    bool	too_small;
    int		fd=open("/proc/net/dev", O_RDONLY);
    
    if (fd==-1) return false;
    too_small = EreadAll(fd, buf, buf_size);
    close(fd);

    if (too_small) {
      buf_size *= 2;
      continue;
    }

    pos = strstr(buf, dev);
    return (pos && (pos==buf || pos[-1]==' ' || pos[-1]=='\n'));
  }
}

static int ifconfig_ioctl(
	int fd,
	const char *ifname,
	int cmd,
	struct ifreq *ifr)
{
	strcpy(ifr->ifr_name, ifname);
	return ioctl(fd, cmd,ifr);
}

/*
	Fetch the IP number of an interface from the kernel.
	Assume the device is already available in the kernel
	Return -1 if any error.
*/
int ifconfig_getaddr (
	const char *ifname,
	uint32_t *addr,
	uint32_t *mask,
	uint32_t *bcast)
{
	int ret = -1;
	if (existsDevice(ifname)){
		int skfd = socket(AF_INET, SOCK_DGRAM, 0);
		*addr = 0;
		*bcast = 0xffffffff;
		if (skfd != -1){
			struct ifreq ifr;
			if (ifconfig_ioctl(skfd,ifname,SIOCGIFADDR, &ifr) >= 0){
				struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
				*addr = sin->sin_addr.s_addr;
				ret = 0;
			}
			if (ifconfig_ioctl(skfd,ifname,SIOCGIFNETMASK, &ifr) >= 0){
				struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
				*mask = sin->sin_addr.s_addr;
				ret = 0;
			}
			if (ifconfig_ioctl(skfd,ifname,SIOCGIFBRDADDR, &ifr) >= 0){
				struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
				*bcast = sin->sin_addr.s_addr;
				ret = 0;
			}
			close (skfd);
		}
	}
	return ret;
}

static void
readIP(char const *str, struct vc_ip_mask_pair *ip, uint32_t *bcast)
{
  if (ifconfig_getaddr(str, &ip->ip, &ip->mask, bcast)==-1) {
    char		*pt;
    char		tmpopt[strlen(str)+1];
    struct hostent	*h;

    strcpy(tmpopt,str);
    pt = strchr(tmpopt,'/');
    
    if (pt==0)
      ip->mask = ntohl(0xffffff00);
    else {
      *pt++ = '\0';

      // Ok, we have a network size, not a netmask
      if (strchr(pt,'.')==0) {
	int		sz = atoi(pt);
	;
	for (ip->mask = 0; sz>0; --sz) {
	  ip->mask >>= 1;
	  ip->mask  |= 0x80000000;
	}
	ip->mask = ntohl(ip->mask);
      }
      else { 
	struct hostent *h = gethostbyname (pt);
	if (h==0) {
	  WRITE_MSG(2, "Invalid netmask '");
	  WRITE_STR(2, pt);
	  WRITE_MSG(2, "'\n");
	  exit(wrapper_exit_code);
	}

	memcpy (&ip->mask, h->h_addr, sizeof(ip->mask));
      }
    }

    h = gethostbyname (tmpopt);
    if (h==0) {
      WRITE_MSG(2, "Invalid IP number or host name '");
      WRITE_STR(2, tmpopt);
      WRITE_MSG(2, "'\n");
      exit(wrapper_exit_code);
    }

    memcpy (&ip->ip, h->h_addr,sizeof(ip->ip));
  }
}

static void
readBcast(char const *str, uint32_t *bcast)
{
  uint32_t	tmp;
  if (ifconfig_getaddr(str, &tmp, &tmp, bcast)==-1){
    struct hostent *h = gethostbyname (str);
    if (h==0){
      WRITE_MSG(2, "Invalid broadcast number '");
      WRITE_STR(2, optarg);
      WRITE_MSG(2, "'\n");
      exit(wrapper_exit_code);
    }
    memcpy (bcast, h->h_addr,sizeof(*bcast));
  }
}

#if defined(VC_ENABLE_API_NET)
static void
make_nx(nid_t nid, uint32_t bcast, size_t nbaddrs, struct vc_ip_mask_pair *ips)
{
  size_t i;
  struct vc_net_nx addr;

  if (nid == VC_DYNAMIC_NID) {
    nid = vc_net_create(VC_DYNAMIC_NID);
    if (nid == (nid_t) -1) {
      perror("chbind: vc_net_create()");
      exit(wrapper_exit_code);
    }
  }
  else {
    if (vc_net_create(nid) == (nid_t) -1) {
      if (errno == EEXIST) {
        if (vc_net_migrate(nid) != 0) {
          perror("chbind: vc_net_migrate()");
          exit(wrapper_exit_code);
        }
        else
          return;
      }
      else {
        perror("chbind: vc_net_create()");
        exit(wrapper_exit_code);
      }
    }
  }

  addr.type = vcNET_IPV4B;
  addr.count = 1;
  addr.ip[0] = bcast;
  addr.mask[0] = 0;

  if (vc_net_add(nid, &addr) != 1) {
    perror("chbind: vc_net_add()");
    exit(wrapper_exit_code);
  }

  for (i = 0; i < nbaddrs; i++) {
    addr.type = vcNET_IPV4;
    addr.count = 1;
    addr.ip[0] = ips[i].ip;
    addr.mask[0] = ips[i].mask;

    if (vc_net_add(nid, &addr) != 1) {
      perror("chbind: vc_net_add()");
      exit(wrapper_exit_code);
    }
  }
}
#endif

int main (int argc, char *argv[])
{
  size_t const			nb_ipv4root = vc_get_nb_ipv4root();
  bool				is_silent   = false;
  struct vc_ip_mask_pair	ips[nb_ipv4root];
  size_t			nbaddrs = 0;
  uint32_t			bcast   = 0xffffffff;
  nid_t				nid	= VC_DYNAMIC_NID;
  
  while (1) {
    int		c = getopt_long(argc, argv, "+", CMDLINE_OPTIONS, 0);
    if (c==-1) break;

    switch (c) {
      case CMD_HELP		:  showHelp(1, argv[0], 0);
      case CMD_VERSION		:  showVersion();
      case CMD_SILENT		:  is_silent = true; break;
      case CMD_BCAST		:  readBcast(optarg, &bcast); break;
#if defined(VC_ENABLE_API_NET)
      case CMD_NID		:  nid = Evc_xidopt2xid(optarg,true); break;
#else
      case CMD_NID		:  WRITE_MSG(2, "WARNING: --nid is not supported by this version\n"); break;
#endif
      case CMD_IP		:
	if (nbaddrs>=nb_ipv4root) {
	  WRITE_MSG(2, "Too many IP numbers, max 16\n");
	  exit(wrapper_exit_code);
	}
	readIP(optarg, ips+nbaddrs, &bcast);
	++nbaddrs;
	break;
      default		:
	WRITE_MSG(2, "Try '");
	WRITE_STR(2, argv[0]);
	WRITE_MSG(2, " --help\" for more information.\n");
	exit(wrapper_exit_code);
	break;
    }
  }

  if (optind==argc) {
    WRITE_MSG(2, "No command given; try '--help' for more information\n");
    exit(wrapper_exit_code);
  }
  
#if !defined(VC_ENABLE_API_NET) && !defined(VC_ENABLE_API_COMPAT) && !defined(VC_ENABLE_API_LEGACY)
#  error can not build 'chbind' without network virtualization API
#endif
  
#if defined(VC_ENABLE_API_NET)
  if (vc_isSupported(vcFEATURE_VNET)) {
    make_nx(nid, bcast, nbaddrs, ips);
  }
  else
#endif
#if defined(VC_ENABLE_API_COMPAT) || defined(VC_ENABLE_API_LEGACY)
  if (vc_set_ipv4root(bcast,nbaddrs,ips)!=0) {
    perror("chbind: vc_set_ipv4root()");
    exit(wrapper_exit_code);
  }
#else
  {
    WRITE_MSG(2, "chbind: kernel does not provide network virtualization\n");
    exit(wrapper_exit_code);
  }
#endif

  if (!is_silent) {
    size_t		i;
    
    WRITE_MSG(1, "ipv4root is now");
    for (i=0; i<nbaddrs; ++i) {
      WRITE_MSG(1, " ");
      WRITE_STR(1, inet_ntoa(*reinterpret_cast(struct in_addr *)(&ips[i].ip)));
    }
    WRITE_MSG(1, "\n");
  }

  Eexecvp (argv[optind],argv+optind);
  return EXIT_SUCCESS;
}

#ifdef ENSC_TESTSUITE
#include <assert.h>

void test()
{
  struct vc_ip_mask_pair	ip;
  uint32_t			bcast;

  bcast = 0;
  readIP("1.2.3.4", &ip, &bcast);
  assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xffffff00) && bcast==0);

  readIP("1.2.3.4/8", &ip, &bcast);
  assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xff000000) && bcast==0);

  readIP("1.2.3.4/255.255.0.0", &ip, &bcast);
  assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xffff0000) && bcast==0);

  readIP("localhost", &ip, &bcast);
  assert(ip.ip==ntohl(0x7f000001) && ip.mask==ntohl(0xffffff00) && bcast==0);

#if 0
  if (ifconfig_getaddr("lo", &tmp, &tmp, &tmp)!=-1) {
    readIP("lo", &ip, &bcast);
    assert(ip.ip==ntohl(0x7f000001) && ip.mask==ntohl(0xff000000) && bcast==ntohl(0x7fffffff));
  }
#endif
}
#endif
