/*
 *      MIPL Diagnostics utility
 *
 *      Authors:
 *      Antti Tuominen          <ajtuomin@tml.hut.fi>
 *
 *      $Id: mipdiag.c,v 1.44 2002/05/15 16:07:30 antti Exp $
 *
 *      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.
 *
 *      Changes:
 *      Alexandru Petrescu,
 *      Miguel Catalina-Gallego   :    Support for configuration of keys 
 *                                     via /proc/sys/... 
 */


#define EINVDIGIT       1
#define ESRCTOOLONG     2
#define ESRCTOOSHORT    3

#define UID0 0

#include <errno.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <net/if.h>
#include <ctype.h>

#include <fcntl.h>
#include <sys/ioctl.h>

#include "mip6.h"
#include "diag.h"
#include "conf.h"

static char miplversion[] = "MIPL Mobile IPv6 0.9.3";
static char progversion[] = "mipdiag 0.23 (2002-04-09) Antti Tuominen";

static char base64_digits [] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz- ";
static struct in6_ifreq hinfo[2];
static int ha_addr_set = 0;
static int haddr_set = 0;
static struct sa_ioctl sa;

static int sa_command_set = 0;
static int key_in_hex = 0;
static char if_name[IFNAMSIZ] = "";

void usage()
{
	fprintf(stderr, 
		"Usage: mipdiag [-acdlsIL]     Show Mobile IP information\n"
		"  mipdiag [-hHdrtRMSCP value] Modify Mobile IP runtime parameters\n\n"
		"  mipdiag [-?|--help]       Detailed usage syntax\n"
		"  mipdiag [-V|--version]    Display version/author and exit\n\n"
		"   -a, --access             show access control list\n"
		"   -c, --bcache             show binding cache\n"
		"   -l, --bulist             show bingding update list\n"
		"   -m, --mninfo             show MN information\n"
		"   -s, --statistics         show statistics\n"
		"   -I, --ifaces             show current interfaces and priorities\n"
		"   -h, --homeaddress        set MN's home address and prefix length\n"
		"   -H, --homeagent          set MN's home agent's address\n"
		"   -i, --if_name            set interface for which preference is adjusted with -P\n"
		"   -P, --set_if_pref        set priority for an interface\n"
		"   -d, --debuglevel         get/set module's debug level\n"
		"   -r, --rule               set access control rule\n"
		"   -R  --rulelist           read access control list from file\n"
		"   -t, --tunnel_sitelocal   set tunnel sitelocal option\n"
		"   -L, --sethomelink           set home link interface\n"
		"   -X, --delhomelink           delete home link interface\n"
		"   -S, --addsa              add security association\n"
		"   -p, --spi                set SPI for SA\n"
		"   -A, --algorithm          set algorithm for SA\n"
		"   -D, --direction          set direction for SA\n"
		"   -x, --key_in_hex         use hexadecimal key\n" 
		"   -k, --key                set key for SA\n"
		"   --showsa                 show SA for address or all SAs\n"
		"   --delsa                  delete SA for address\n"
		"   --safile                 add SAs from a file\n"    
		);
}

void bcache_do_one(bc_t *entry)
{
	char haddr[INET6_ADDRSTRLEN], coaddr[INET6_ADDRSTRLEN];

	inet_ntop(AF_INET6, &entry->homeaddr, haddr, INET6_ADDRSTRLEN);
	inet_ntop(AF_INET6, &entry->careofaddr, coaddr, INET6_ADDRSTRLEN);
	printf("%-44s%-44s%-10ld%-5d\n", haddr, coaddr, 
	       entry->expires, entry->type);
}

void bulist_do_one(bu_t *e)
{
	char rcpt[INET6_ADDRSTRLEN], haddr[INET6_ADDRSTRLEN], coaddr[INET6_ADDRSTRLEN];

	inet_ntop(AF_INET6, &e->rcptaddr, rcpt, INET6_ADDRSTRLEN);
	inet_ntop(AF_INET6, &e->homeaddr, haddr, INET6_ADDRSTRLEN);
	inet_ntop(AF_INET6, &e->careofaddr, coaddr, INET6_ADDRSTRLEN);
	printf("Recipient CN: %s\n", rcpt);
	printf("BINDING home address: %s care-of address: %s\n", haddr, coaddr);
	printf("        expires: %ld sequence: %d state: %d\n", e->expires, 
	       e->seq, e->state);
	printf("        delay: %d max delay %d callback time: %d\n\n", e->delay, 
	       e->maxdelay, e->cb_time);
}

void stats_do_one(stat_t *stat)
{
	printf("%-25s : %ld\n", stat->name, stat->value);
}

void iface_do_one(struct ma_if_info *info)
{
	char ifname[IFNAMSIZ];

	if (if_indextoname(info->interface_id, ifname) == NULL) {
		fprintf(stderr, "Error mapping index to interface name\n");
		exit(1);
	}
	printf("%-16s %-5d %-4s     %-4s\n", ifname, info->preference,
	       info->status & MA_IFACE_HAS_ROUTER ? "*" : " ",
	       info->status & MA_IFACE_CURRENT ? "*" : " ");
}

int get_ma_current_iface(void) 
{
	FILE *ma_info; 
	char used_if[IFNAMSIZ], name[IFNAMSIZ];
	char type[INTERFACE_TYPE_MAX];
	char addr[INET6_ADDRSTRLEN];
	int id, avail, state, pref;

	ma_info = fopen("/proc/multiaccess/info", "r");

	if (ma_info != NULL) {
		printf ("Currently used interface: ");

		while (!feof(ma_info)) {
			memset(name, 0, IFNAMSIZ);
			memset(type, 0, INTERFACE_TYPE_MAX);
			memset(addr, 0, INET6_ADDRSTRLEN);
			fscanf(ma_info, "%d\t%s\t%d\t%d\t%s\t%d\t%s\n", 
			       &id, name, &avail, &state, type, &pref, addr);
			if (state == MULTIACCESS_IFACE_IS_USED)
				strcpy(used_if, name);
		}
		printf ("%s\n", used_if);
		fclose(ma_info);
	} else {
		fprintf(stderr, "Could not read Multiaccess information.\n");
		exit(ENOENT);
	}

    return 0;
}

int set_ma_set_preference(char *ifname, char *preference) /* MULTIACCESS */
{

	int fd, ret;
	struct ma_if_info ifinfo;

	if (geteuid() != UID0) {
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(EPERM);
	}

	fd = open(CTLFILE, 0);

	if (fd < 0) {
		perror("open:");
		return -ENOENT;
	}

	memset(&ifinfo, 0, sizeof(ifinfo));

	ifinfo.interface_id = if_nametoindex(ifname);

	ifinfo.preference = atoi(preference);

	ret = ioctl(fd, MA_IOCTL_SET_IFACE_PREFERENCE, &ifinfo);

	if (ret < 0) {
		fprintf(stderr, "ioctl failed\n");
		return ret;
	}

	close(fd);

	return 0;
}

/* Access control list stuff */
void access_do_one(char policy, struct in6_addr *addr, int prefix)
{
	char saddr[INET6_ADDRSTRLEN];

	inet_ntop(AF_INET6, addr, saddr, INET6_ADDRSTRLEN);

	printf("%-8s %-44s %d\n", policy == '+' ? "ALLOW" : "DENY", saddr, prefix);
}

void print_access()
{
	FILE *access;
	char policy;
	struct in6_addr addr;
	int prefix;

	access = fopen("/proc/net/mip6_access", "r");
	if (access != NULL) {
		printf("Mobile IPv6 Mobile Node Access Control List\n");
		printf("Policy   Address range                               Prefix length\n");
		while (!feof(access)) {
			fscanf(access, "%c %8x%8x%8x%8x %2x\n", &policy, 
			       &addr.s6_addr32[0], &addr.s6_addr32[1],
			       &addr.s6_addr32[2], &addr.s6_addr32[3],
			       &prefix);
			access_do_one(policy, &addr, prefix);
		}
		fclose(access);
	} else {
		fprintf(stderr, "Could not read access control list.\n");
		exit(ENOENT);
	}
}

int acl_add_rule(char *policy, struct in6_addr *addr, int prefix_len)
{
	int fd;
	int ret = 0;
	struct mipv6_acl_record rule;

	memset(&rule, 0, sizeof(rule));

	if (!strcasecmp(policy, "DENY"))
		rule.action = ACL_DENY;
	else if (!strcasecmp(policy, "ALLOW"))
		rule.action = ACL_ALLOW;
	else if (!strcasecmp(policy, "FLUSH"))
		rule.action = ACL_FLUSH;
	else {
		fprintf(stderr, "Access list format error: unknown policy \"%s\".\n",
			policy);
		exit(1);
	}

	if (rule.action != ACL_FLUSH) {
		memcpy(&rule.mask, addr, sizeof(struct in6_addr));
		rule.prefix_len = prefix_len;
	}

	if ((fd = open(CTLFILE, 0)) < 0 ) {
		perror("open:");
		return -ENOENT;
	}

	if ((ret = ioctl(fd, IOCTL_ADD_ACL_RULE, &rule)) < 0) {
		fprintf(stderr, "ioctl failed\n");
	}

	close(fd);

	return ret;
}

void set_access(char *rule) 
{
	int plen = 0;
	char *policy, *addr, *n, *p;
	struct in6_addr saddr;

	if (geteuid() != UID0) {
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(EPERM);
	}

	if (strchr(rule, '#')) {
		return;
	}
	policy = (char *) malloc(128 * sizeof(char));
	addr = (char *) malloc(128 * sizeof(char));

	sscanf(rule, "%s %s", policy, addr); 
	
	if (strncasecmp(policy, "FLUSH", 5)) {
		n = strchr(addr, '/');
		if (n) {
			p = n + 1;
			*n = '\0';
			plen = strtoul(p, &p, 10);
			if (*p) exit(1);
		} else {
			fprintf(stderr, "Access list format error: no prefix length.\n");
			exit(1);
		}
		
		if(!inet_pton(AF_INET6, addr, &saddr)) {
			fprintf(stderr, "Access list format error: invalid IPv6 address.\n");
			exit(1);
		}
	} 
	acl_add_rule(policy, &saddr, plen);
}

void acl_file(char *path)
{
	FILE *acl;
	char buffer[128];

	if (!strcmp(path, "-"))
		acl = stdin;
	else
		acl = fopen(path, "r");

	if (acl != NULL) {
		while (!feof(acl)) {
			if (fgets(buffer, 128, acl) != NULL)
				set_access(buffer);
		}
		fclose(acl);
	}
	
}

/* Security stuff */
void delsa(char *addr_c)
{
	struct in6_addr addr;
	int mipv6_fd = open(CTLFILE, 0);
	if (mipv6_fd < 0) {
		perror("open:");
		return;
	}
	if(addr_c != NULL) {
		if (inet_pton(AF_INET6, addr_c, &addr) < 0) {
			fprintf(stderr, "invalid address for peer\n");
			return;
		}
		ioctl(mipv6_fd, IOCTL_DEL_SA_BUNDLE, &addr);	
	}
	close(mipv6_fd);
}	

void showsa(char *addr_c)
{
	struct in6_addr addr;
	int mipv6_fd = open(CTLFILE, 0);
	if (mipv6_fd < 0) {
		perror("open:");
		return;
	}
	if(addr_c != NULL) {
		if (inet_pton(AF_INET6, addr_c, &addr) < 0) {
			fprintf(stderr, "invalid address for peer\n");
			close(mipv6_fd);
			return;
		}

		ioctl(mipv6_fd, IOCTL_PRINT_SA, &addr);	
	}
	else {
		memset(&addr, 0, sizeof(struct in6_addr));
		ioctl(mipv6_fd, IOCTL_PRINT_SA, &addr);
	}
	close(mipv6_fd);
	
}	

/*
 * dst: pointer to destination buffer
 * src: pointer to base64 string
 * size: size (in BYTES) of destination buffer
 */
int base64_to_bin(uint8_t *dst,char *src,int size)
{
	int i,j;
	char *p;
	int offset,count;
	uint32_t digitval;
	uint8_t buf[4];
	
	offset = 18;
	*(uint32_t *)buf = 0;
	count = size;
	memset(dst,0,size);
  
	for (i = 0; src[i] != '\0'&& src[i] != '\n'; i++) {
		p = strchr(base64_digits,src[i]);
		if (p == NULL)
			return (-EINVDIGIT);
		digitval = p - base64_digits;

		*(uint32_t *)buf |= htonl(digitval << offset);

		if (offset == 0) {
			for (j = 0; (j < 3) && (count > 0); j++,count--)
				dst[j] = buf[j + 1];

			offset = 18;
			dst += 3;
			*(uint32_t *)buf = 0;
		}
		else
			offset -= 6;
	}

	if (offset != 18) 
		for (j = 0; (j < 3) && (count > 0); j++,count--)
			dst[j] = buf[j + 1];

	if (i < ((8*size + 5) / 6)) 
		return (-ESRCTOOSHORT);
	else if (i > ((8*size + 5) / 6)) 
		return (-ESRCTOOLONG);
	
	return (0);
}

int validate_input(char *input, int max_length)
{
	if(!input) {
		fprintf(stderr, "mipdiag: NULL input\n");
		return 0;
	}
	if(strlen(input) > max_length) {
		fprintf(stderr, "mipdiag: input too long\n");
		return 0;
	}
	return 1;
}

/* Format for SAs in conf. file:  
 * peer address 
 * SPI 
 * Authent. algorithm
 * key for SA
 * hard lifetime
 * soft lifetime
 * direction
 */
#define MAX_SPILEN 16
#define MAX_ALG_LEN 32

void set_algorithm(char *optarg) {
	if (optarg && (strncmp(optarg, "ALG_AUTH_HMAC_SHA1", strlen(optarg)) == 0)){ 
		sa.auth_alg = ALG_AUTH_HMAC_SHA1;
		sa.key_auth_len = 160/8;
	} else {
		sa.auth_alg = ALG_AUTH_HMAC_MD5 ;
		sa.key_auth_len = 128/8;

	}
}

int set_spi(char *optarg) {
	char *err_ptr;
	sa.spi = strtoul(optarg, &err_ptr, 0);
	if(*err_ptr != '\0') {
		fprintf(stderr, "Incorrect spi %s", err_ptr);
		return -1;
		printf("spi %d\n",sa.spi);
	}
	return 0;
}

void set_key_in_hex(void) {
	key_in_hex = 1;
}

void set_sa_direct(char *optarg){
	if (strncmp(optarg, "inbound", sizeof("inbound")) == 0) 
		sa.direction = INBOUND;
	else
		sa.direction = OUTBOUND;
}

/* Sets key and adds the SA into the kernel */
int set_key(char *optarg) {
	int ret, fd, i, tmp, j=0;
	/* trim the last newline code */
	if (optarg[strlen(optarg)-1] == 0xa)
		optarg[strlen(optarg)-1] = 0x0;

	if (optarg && key_in_hex && validate_input(optarg, sa.key_auth_len*2)) {
		for (i=0; i < (strlen(optarg)); i +=2) {
			if(sscanf(optarg+i, "%2x", &tmp)<=0) {
				fprintf(stderr, "An invalid digit was used in the key %s\n", optarg);
				return -EINVDIGIT;
			}
			else {
				
				sa.key_auth[j] = (u_int8_t)tmp; 
				j++;
			}
		}	
		printf("\n");

	}
	else if (optarg && !key_in_hex){ 
		ret = base64_to_bin (sa.key_auth, optarg, 
				     sa.key_auth_len);
		
		if (ret == -EINVDIGIT)
		{
			fprintf(stderr, "An invalid digit was used in the key %s\n", optarg);
			return -EINVDIGIT;
		}
		else if (ret == -ESRCTOOLONG)
		{
			fprintf(stderr, "The key was longer than expected\n");
			return -ESRCTOOLONG;
		}		
		else if (ret == -ESRCTOOSHORT)
		{
			fprintf(stderr, "The key was shorter than expected\n");
			return -ESRCTOOSHORT;
		}	
		
	}
	else
		return -1;
	
	fd = open(CTLFILE, 0);
	if (fd < 0) {
		perror("open:");
		return -1;
	}
	if(sa.spi && sa.auth_alg && sa.direction && sa_command_set) {

		if(sa.direction == INBOUND)
			ret = ioctl(fd, IOCTL_ADD_IB_SA, &sa);
		else
			ret = ioctl(fd, IOCTL_ADD_OB_SA, &sa);
	}
	else
		ret = -1;
	close(fd);
	return ret;
}	

int add_sa(char *str)
{

	memset(&sa, 0, sizeof(struct sa_ioctl));
	if (inet_pton(AF_INET6, str, &sa.addr) < 0) {
		fprintf(stderr, "invalid address for peer %s\n", str);
		return -1;
	}
	sa_command_set = 1;
	return 0;
}
int sa_add_one(char *sa_in_str)
{
	char *tmp;
	tmp = strtok(sa_in_str, ";");
	if (!tmp) return -1;
	add_sa(tmp);
	tmp = strtok(NULL, ";");
        if (!tmp) return -1;
	set_spi(tmp);
	tmp = strtok(NULL, ";");
	if (!tmp) return -1;
	set_algorithm(tmp); 
	tmp = strtok(NULL, ";");
	if (!tmp) return -1;
	set_sa_direct(tmp); 
	tmp = strtok(NULL, ";");
        if (!tmp) return -1;
	if (*tmp == '1') {
		set_key_in_hex();
		set_key(tmp+2);
	}
	else
		set_key(tmp+2);
	return 0;
}
int add_sas_from_file(char *path)
{
	FILE *sas;
	char sa_buf[255];
	if(!path)
		strcpy(path, "/etc/mipv6_sas.conf");
	if (geteuid() != UID0) {
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(EPERM);
	}
	
	if (!strcmp(path, "-"))
		sas = stdin;
	else
		sas = fopen(path, "r");
	
	if (sas == NULL) {
		fprintf(stderr, "SA file %s missing.\n", path);   
		return -1;
	}
	while (!feof(sas)) {
		if (fgets(sa_buf, 255, sas) != NULL)
			sa_add_one(sa_buf);
	}
	fclose(sas);
	return 0;
}



void debuglevel(char *level)
{
	int ilevel;
	int ret = 0;

	if (level == NULL) {
		ret = get_sysctl_int("net/ipv6/mobility/debuglevel");
		if (ret < 0) {
			fprintf(stderr, "Could not get debug level. Is module loaded?\n");
			exit(ret);
		}
		printf("Debug level is %d.\n", ret);
		exit(0);
	}
	ilevel = atoi(level);

	if (ilevel == 0 && level[0] != '0') {
		fprintf(stderr, "Debug level must be a positive integer value\n");
		exit(1);
	}
	ret = set_sysctl_int("net/ipv6/mobility/debuglevel", ilevel);

	switch (ret) {
	case -EPERM:
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(ret);
		break;
	case -ENOENT:
		fprintf(stderr, "Could not set debug level. Is module loaded?\n");
		exit(ret);
		break;
	case -EIO:
		fprintf(stderr, "Unable to set debug level.\n");
		exit(ret);
		break;
	default:
		if (ret < 0) {
			fprintf(stderr, "Unknown error.\n");
			exit(ret);
		}
	}
}

void tunnel_sitelocal(char *opt)
{
	int iopt = -1, ret = 0;

	if (opt == NULL) {
		ret = get_sysctl_int("net/ipv6/mobility/tunnel_sitelocal");
		if (ret < 0) {
			fprintf(stderr, "Could not get tunnel site-local setting. Is module loaded?\n");
			exit(ret);
		}
		printf("Tunnel site-local is %s.\n", ret ? "ON" : "OFF");
		exit(0);
	}

	if (!strcasecmp(opt, "YES") || !strcasecmp(opt, "ON") || 
	    !strcasecmp(opt, "Y") || !strcmp(opt, "1"))
		iopt = 1;
	else if (!strcasecmp(opt, "NO") || !strcasecmp(opt, "OFF") ||
		 !strcasecmp(opt, "N") || !strcmp(opt, "0"))
		iopt = 0;
	else {
		fprintf(stderr, "Invalid value for option: %s\n", opt);
		exit(EINVAL);
	}

	ret = set_sysctl_int("net/ipv6/mobility/tunnel_sitelocal", iopt);

	switch (ret) {
	case -EPERM:
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(ret);
		break;
	case -ENOENT:
		fprintf(stderr, "Could not set tunnel sitelocal option. Is module loaded?\n");
		exit(ret);
		break;
	case -EIO:
		fprintf(stderr, "Unable to set tunnel sitelocal option.\n");
		exit(ret);
		break;
	default:
		if (ret < 0) {
			fprintf(stderr, "Unknown error.\n");
			exit(ret);
		}
	}
}
int set_homelink(char *ifname)
{
	int fd, ret;
	if (geteuid() != UID0) {
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(EPERM);
	}
	fd = open(CTLFILE, 0);
	if (fd < 0) {
		perror("open");
		return -ENOENT;
	}
	ret = ioctl(fd, IOCTL_SET_HA_ANYCAST, ifname);

	if (ret < 0) {
		fprintf(stderr, "ioctl failed\n");
		return ret;
	}

	close(fd);

	return 0;
}
int del_homelink(char *ifname)
{
	int fd, ret;
	if (geteuid() != UID0) {
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(EPERM);
	}
	fd = open(CTLFILE, 0);
	if (fd < 0) {
		perror("open");
		return -ENOENT;
	}
	ret = ioctl(fd, IOCTL_DEL_HA_ANYCAST, ifname);

	if (ret < 0) {
		fprintf(stderr, "ioctl failed\n");
		return ret;
	}

	close(fd);

	return 0;
}

/* Setting home address and home agent */
int set_mn_info()
{
	int fd, ret;
		
	if (geteuid() != UID0) {
		fprintf(stderr, "You must be root (uid = 0) to do this.\n");
		exit(EPERM);
	}

	fd = open(CTLFILE, 0);
	if (fd < 0) {
		perror("open");
		return -ENOENT;
	}

	ret = ioctl(fd, IOCTL_SET_MN_INFO, hinfo);

	if (ret < 0) {
		fprintf(stderr, "ioctl failed\n");
		return ret;
	}

	close(fd);

	return 0;
}

int set_homeaddress(char *addr)
{
	char *p, *n;

	n = strchr(addr, '/');
	if (n) {
		p = n + 1;
		*n = '\0';
		hinfo[0].ifr6_prefixlen = strtoul(p, &p, 10);
		if (*p) hinfo[0].ifr6_prefixlen = 128;
	} else {
		fprintf(stderr, "Invalid home address: no prefix length\n");
		exit(-1);
	}
	if (inet_pton(AF_INET6, addr, &hinfo[0].ifr6_addr) <= 0) {
		fprintf(stderr, "Invalid home address: parse error\n");
		exit(-1);
	}
	haddr_set = 1;

	if (ha_addr_set)
		set_mn_info();

	return 0;
}

int set_homeagent(char *addr)
{
	char *p, *n;

	if (addr == NULL) {
		memset(&hinfo[0], 0, sizeof(struct in6_ifreq));
		goto success;
	}
	n = strchr(addr, '/');
	if (n) {
		p = n + 1;
		*n = '\0';
		hinfo[0].ifr6_prefixlen = strtoul(p, &p, 10);
		if (*p) hinfo[0].ifr6_prefixlen = 128;
	} else {
		fprintf(stderr, "Invalid home agent: no prefix length\n");
		exit(-1);
	}
	if (inet_pton(AF_INET6, addr, &hinfo[1].ifr6_addr) <= 0) {
		fprintf(stderr, "Invalid home agent address: parse error\n");
		exit(-1);
	}

 success:
	ha_addr_set = 1;
	if (haddr_set)
		set_mn_info();

	return 0;
}

int get_mn_info(void)
{
	int fd, ret = 0;
	int *iptr;
	int i;
	char address[INET6_ADDRSTRLEN], coaddress[INET6_ADDRSTRLEN];
	struct in6_addr *careof;
	struct mn_info_ext *minfo;
	void *buf;
	fd = open(CTLFILE, 0);
	if (fd < 0) {
		perror("open");
		return -ENOENT;
	}

	careof = malloc(sizeof(struct in6_addr));
	buf = malloc(sizeof(int) + sizeof(struct mn_info_ext));

	memset(buf, 0, sizeof(struct mn_info_ext) + sizeof(int));
	minfo = (struct mn_info_ext *)(buf + sizeof(int));
	iptr = (int *)buf;
	printf("%-44s %-44s %-44s Registered\n", "Home Address", 
	       "Home Agent Address", "Care-of address");

	for (i = 0; i < 100; i++) {
		*iptr = i;
		ret = ioctl(fd, IOCTL_GET_MN_INFO, buf);
		if (ret < 0)
			break;

		memcpy(careof, &minfo->home_addr, sizeof(struct in6_addr));
		ret = ioctl(fd, IOCTL_GET_CAREOFADDR, careof);
		if (ret < 0) {
			perror("ioctl");
			return ret;
		}
		if (!IN6_IS_ADDR_UNSPECIFIED(careof)) {
			inet_ntop(AF_INET6, careof, coaddress, INET6_ADDRSTRLEN);
		}

		inet_ntop(AF_INET6, &minfo->home_addr, address, 
			  INET6_ADDRSTRLEN);
		if (IN6_IS_ADDR_UNSPECIFIED(&minfo->home_addr))
			break;
		else
			printf("%-44s ", address);

		inet_ntop(AF_INET6, &minfo->ha, address, 
			  INET6_ADDRSTRLEN);
		if (IN6_IS_ADDR_UNSPECIFIED(&minfo->ha))
			printf("Home Agent Address has NOT been set.\n");
		else
			printf("%-44s ", address);	
	
		ret = ioctl(fd, IOCTL_GET_CAREOFADDR, &minfo->home_addr);
		if (ret < 0) {
			perror("ioctl");
			return ret;
		}
		if (!IN6_IS_ADDR_UNSPECIFIED(&minfo->home_addr)) {
			inet_ntop(AF_INET6, &minfo->home_addr, coaddress, INET6_ADDRSTRLEN);
			printf("%-44s ", coaddress);
		}
		else
			printf("%-44s ", "No care-of address defined");
		printf("%d\n", minfo->has_home_reg); 
	}
	close(fd);
	return 0;
}

void set_if_name(char *arg)
{
	strncpy(if_name, arg, IFNAMSIZ);
}


/* Main program */
int main (int argc, char **argv)
{
	int c;
     	
	while (1)
	{
		int option_index = 0;
		static struct option long_options[] =
		{
			{"if_name", 1, 0, 'i'}, /* MULTIACCESS */
			{"ifaces", 0, 0, 'I'}, /* MULTIACCESS */
			{"set_if_pref", 1, 0, 'P'}, /* MULTIACCESS */
			{"mninfo", 0, 0, 'm'},
			{"access", 0, 0, 'a'},
			{"rule", 1, 0, 'r'},
			{"rulelist", 1, 0, 'R'},
			{"statistics", 0, 0, 's'},
			{"bulist", 0, 0, 'l'},
			{"bcache", 0, 0, 'c'},
			{"help", 0, 0, '?'},
			{"version", 0, 0, 'V'},
			{"debuglevel", 2, 0, 'd'},
			{"tunnel_sitelocal", 2, 0, 't'},
			{"sethomelink", 1, 0, 'L'},
			{"delhomelink", 1, 0, 'X'},
			{"homeagent", 2, 0, 'H'},
			{"homeaddress", 1, 0, 'h'},
			{"addsa", 1, 0, 'S'},
			{"showsa", 2, 0, 0},
			{"delsa", 1, 0, 0},
			{"safile", 1, 0, 0},
			{"algorithm", 2, 0, 'A'},
			{"key", 2, 0, 'k'},
			{"hex", 0, 0, 'x'},
			{"spi", 1, 0, 'p'},
			{"direction", 1, 0, 'D'},
			{0, 0, 0, 0}
		};
		

		c = getopt_long (argc, argv,
				 "i:IP:mar:R:slc?Vd::t::H::h:S:A::k::xp:D:L:X:",
				 long_options, &option_index);
		if (c == -1)
			break;
		
		switch (c)
		{
		case 0:

			if (strcmp(long_options[option_index].name, 
				   "showsa") == 0) {
				showsa(optarg);
				break;
			}
			if (strcmp(long_options[option_index].name,"delsa") 
			    == 0) {
				delsa(optarg);
				break;
			}
			if (strcmp(long_options[option_index].name,"safile")
                            == 0) {
				add_sas_from_file(optarg);
				break;
                        }
			break;
		case 'c':
			printf("Mobile IPv6 Binding cache\n");
			printf("%-44s%-44s%-10s%-5s\n", "Home Address", "Care-of Address", 
			       "Lifetime", "Type");
			if (dump_bc(bcache_do_one) < 0)
				fprintf(stderr, "Could not read binding cache information.\n");
			break;
		case 'l':
			printf ("Mobile IPv6 Binding update list\n");
			if (dump_bu(bulist_do_one) < 0) {
				fprintf(stderr, "Could not read binding update list information.\n");
				exit(1);
			}
			break;
		case 's':
			printf("Mobile IPv6 Statistics\n");
			if (dump_stat(stats_do_one) < 0) {
				fprintf(stderr, "Could not read statistics.\n");
				exit(1);
			}
			break;
		case 'd':
			debuglevel(optarg);
			break;
		case 't':
			tunnel_sitelocal(optarg);
			break;
		case 'L':
			set_homelink(optarg);
			break;
		case 'X':
			del_homelink(optarg);
			break;
		case 'i': /* MULTIACCESS */
			set_if_name(optarg);
			break;
		case 'I':
			printf("Interfaces Preference List\n");
			printf("%-16s %-5s HAS RTR  CURRENT\n",
			       "Interface", "Pref");
			if (dump_iface(iface_do_one) < 0) {
				fprintf(stderr, "Could not read interface information.\n");
				exit(1);
			}
		case 'P': /* MULTIACCESS */
			set_ma_set_preference(if_name, optarg);
			break;
		case 'r':
			set_access(optarg);
			break;
		case 'H':
			set_homeagent(optarg);
			break;
		case 'm':
			get_mn_info();
			break;
		case 'a':
			print_access();
			break;
		case 'R':
			acl_file(optarg);
			break;
		case 'S':
			add_sa(optarg);
			break;
		case 'h':
			set_homeaddress(optarg);
			break;
		case 'V':
			fprintf (stderr, "%s\n", miplversion);
			fprintf (stderr, "%s\n", progversion);
			break;
		case 'A':
			set_algorithm(optarg);
			break;
		case 'p': 
			set_spi(optarg);
			break;
		case 'k':
			set_key(optarg);
			break;
		case 'D':
			set_sa_direct(optarg);
			break;
		case 'x': 
			set_key_in_hex();
			break;
		case '?':
			usage();
			break;
			
		default:
			printf ("mipdiag: invalid option -- %c. Try -? for help.\n", c);
		}
	}
	if (argc == 1)
		usage();
	if (optind < argc)
	{
		printf ("non-option ARGV-elements: ");
		while (optind < argc)
			printf ("%s ", argv[optind++]);
		printf ("\n");
	}
	if (!!ha_addr_set ^ !!haddr_set) {
		fprintf(stderr, "Options -h and -H must be used together.\n");
	}
	exit (0);
}

