/* $Id$ */
/* Copyright (c) 2011-2015 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Unix others */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */



#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <netinet/in.h>
#if defined(__DeforaOS__)
#elif defined(__FreeBSD__)
# include <ifaddrs.h>
# include <net/if_var.h>
# include <netinet6/in6_var.h>
#elif defined(__NetBSD__)
# include <ifaddrs.h>
# include <net/if.h>
# include <netinet6/in6_var.h>
#endif

#ifndef PROGNAME
# define PROGNAME "ifconfig"
#endif


/* ifconfig */
/* private */
/* types */
typedef int Prefs;
#define PREFS_a	0x1
#define PREFS_m	0x2

typedef struct _idxstr
{
	unsigned int idx;
	char const * str;
} idxstr;


/* prototypes */
static int _ifconfig(Prefs prefs, int argc, char * argv[]);

static int _ifconfig_error(char const * message, int ret);
static int _ifconfig_usage(void);

static char const * _inet_str(struct sockaddr * addr);

static char const * _mac_media_str(int type);
static char const * _mac_status_str(int state);


/* functions */
/* ifconfig */
static int _ifconfig_all(Prefs prefs);
static int _ifconfig_do(Prefs prefs, char const * name, int argc,
		char * argv[]);
static int _ifconfig_show(Prefs prefs, char const * name);
static int _show_mac(Prefs prefs, int fd, struct ifreq * ifr);
#ifdef SIOCGIFFLAGS
static void _show_mac_flags(unsigned short flags);
#endif
static int _mac_media(Prefs prefs, int fd, struct ifreq * ifr);
#ifdef SIOCGIFDATA
static int _mac_status(struct if_data * ifd);
#endif
static int _show_inet(Prefs prefs, int fd, struct ifreq * ifr);
static int _show_inet6(Prefs prefs, char const * name);
#ifdef SIOCGIFDSTADDR_IN6
static int _inet6_do(Prefs prefs, char const * name, int fd,
		struct ifaddrs * ifa);
#else
static int _inet6_do(Prefs prefs, char const * name, int fd, void * ifa);
#endif
static void _inet6_print_addr(struct in6_addr * addr);

static int _ifconfig(Prefs prefs, int argc, char * argv[])
{
	if(prefs & PREFS_a)
		return _ifconfig_all(prefs);
	if(argc == 1)
		return _ifconfig_show(prefs, argv[0]);
	if(argc > 1)
		return _ifconfig_do(prefs, argv[0], argc - 1, &argv[1]);
	return 0;
}

static int _ifconfig_all(Prefs prefs)
{
	struct if_nameindex * ifni;
	struct if_nameindex * i;
	char const * sep = "";

	if((ifni = if_nameindex()) == NULL)
		return -_ifconfig_error("if_nameindex", 1);
	for(i = ifni; i != NULL && i->if_index != 0; i++)
	{
		fputs(sep, stderr);
		_ifconfig_show(prefs, i->if_name);
		sep = "\n";
	}
	if_freenameindex(ifni);
	return 0;
}

static int _ifconfig_do(Prefs prefs, char const * name, int argc,
		char * argv[])
{
	/* FIXME implement */
	return 0;
}

static int _ifconfig_show(Prefs prefs, char const * name)
{
	int ret;
	int fd;
	struct ifreq ifr;

	if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		return _ifconfig_error("socket", 1);
	snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", name);
	if((ret = _show_mac(prefs, fd, &ifr)) == 0
			&& (ret |= _show_inet(prefs, fd, &ifr)) == 0)
		ret |= _show_inet6(prefs, name);
	close(fd);
	return ret;
}

static int _show_mac(Prefs prefs, int fd, struct ifreq * ifr)
{
#ifdef SIOCGIFDATA
	struct ifdatareq ifi;
#endif

	printf("%s:", ifr->ifr_name);
#ifdef SIOCGIFFLAGS
	if(ioctl(fd, SIOCGIFFLAGS, ifr) != 0)
	{
		_ifconfig_error(ifr->ifr_name, 1);
		if(errno == ENXIO)
			return -1;
	}
	else
		_show_mac_flags(ifr->ifr_flags);
#endif
#ifdef SIOCGIFDATA
	memcpy(ifi.ifdr_name, ifr->ifr_name, sizeof(ifi.ifdr_name));
	if(ioctl(fd, SIOCGIFDATA, &ifi) != 0)
	{
		_ifconfig_error(ifr->ifr_name, 1);
		if(errno == ENXIO)
			return -1;
		_mac_media(prefs, fd, ifr);
	}
	else
	{
		printf(" mtu %lu\n", ifi.ifdr_data.ifi_mtu);
		_mac_media(prefs, fd, ifr);
		_mac_status(&ifi.ifdr_data);
	}
#endif
	return 0;
}

#ifdef SIOCGIFFLAGS
static void _show_mac_flags(unsigned short flags)
{
	struct
	{
		int flag;
		char const * name;
	} names[] =
	{
# ifdef IFF_UP
		{ IFF_UP,		"UP"		},
# endif
# ifdef IFF_BROADCAST
		{ IFF_BROADCAST,	"BROADCAST"	},
# endif
# ifdef IFF_DEBUG
		{ IFF_DEBUG,		"DEBUG"		},
# endif
# ifdef IFF_LOOPBACK
		{ IFF_LOOPBACK,		"LOOPBACK"	},
# endif
# ifdef IFF_POINTOPOINT
		{ IFF_POINTOPOINT,	"POINTOPOINT"	},
# endif
# ifdef IFF_RUNNING
		{ IFF_RUNNING,		"RUNNING"	},
# endif
# ifdef IFF_NOARP
		{ IFF_NOARP,		"NOARP"		},
# endif
# ifdef IFF_PROMISC
		{ IFF_PROMISC,		"PROMISC"	},
# endif
# ifdef IFF_OACTIVE
		{ IFF_OACTIVE,		"OACTIVE"	},
# endif
# ifdef IFF_SIMPLEX
		{ IFF_SIMPLEX,		"SIMPLEX"	},
# endif
# ifdef IFF_MULTICAST
		{ IFF_MULTICAST,	"MULTICAST"	},
# endif
	};
	size_t i;
	char const * sep = "";

	printf(" flags=%x", flags);
	if(sizeof(names) == 0 || flags == 0)
		return;
	putchar('<');
	for(i = 0; i < sizeof(names) / sizeof(*names); i++)
		if((flags & names[i].flag) == names[i].flag)
		{
			printf("%s%s", sep, names[i].name);
			sep = ",";
		}
	putchar('>');
}
#endif

static int _mac_media(Prefs prefs, int fd, struct ifreq * ifr)
{
#ifdef SIOCGIFMEDIA
	struct ifmediareq ifm;

	memset(&ifm, 0, sizeof(ifm));
	memcpy(ifm.ifm_name, ifr->ifr_name, sizeof(ifm.ifm_name));
	if(ioctl(fd, SIOCGIFMEDIA, &ifm) != 0)
		return (errno != ENOTTY && errno != EINVAL)
			? _ifconfig_error("SIOCGIFMEDIA", 1) : 0;
	printf("\tmedia: %s\n", _mac_media_str(ifm.ifm_current));
#endif
	return 0;
}

#ifdef SIOCGIFDATA
static int _mac_status(struct if_data * ifd)
{
	char const * status;

	if((status = _mac_status_str(ifd->ifi_link_state)) == NULL)
		return 0;
	printf("\tstatus: %s\n", status);
	return 0;
}
#endif

static int _show_inet(Prefs prefs, int fd, struct ifreq * ifr)
{
	if(ioctl(fd, SIOCGIFADDR, ifr) != 0)
	{
#ifdef EADDRNOTAVAIL
		if(errno == EADDRNOTAVAIL)
			return 0;
#endif
#ifdef EAFNOSUPPORT
		if(errno == EAFNOSUPPORT)
			return 0;
#endif
		return -_ifconfig_error("SIOCGIFADDR", 1);
	}
	printf("%s%s", "\tinet: ", _inet_str(&ifr->ifr_addr));
#ifdef SIOCGIFDSTADDR
	if(ioctl(fd, SIOCGIFDSTADDR, ifr) == 0)
		printf(" -> %s", _inet_str(&ifr->ifr_dstaddr));
#endif
#ifdef SIOCGIFBRDADDR
	if(ioctl(fd, SIOCGIFBRDADDR, ifr) == 0)
		printf(" broadcast %s", _inet_str(&ifr->ifr_broadaddr));
#endif
	putchar('\n');
	return 0;
}

static int _show_inet6(Prefs prefs, char const * name)
{
	int ret = 0;
	int fd;
#if defined(__FreeBSD__) || defined(__NetBSD__)
	struct ifaddrs * ifa;
	struct ifaddrs * i;
#endif

	if((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
		return -_ifconfig_error("socket", 1);
#if !defined(__DeforaOS__) && (defined(__FreeBSD__) || defined(__NetBSD__))
	if(getifaddrs(&ifa) != 0)
		ret = -_ifconfig_error("getifaddrs", 1);
	else
		for(i = ifa; i != NULL; i = i->ifa_next)
			if(strcmp(i->ifa_name, name) != 0)
				continue;
			else if(i->ifa_addr->sa_family != AF_INET6)
				continue;
			else
				ret |= _inet6_do(prefs, name, fd, i);
#else
	/* FIXME implement */
#endif
	close(fd);
	return ret;
}

#ifdef SIOCGIFDSTADDR_IN6
static int _inet6_do(Prefs prefs, char const * name, int fd,
		struct ifaddrs * ifa)
{
	struct in6_ifreq ifr;

	if(ifa->ifa_addr->sa_len != sizeof(ifr.ifr_addr))
		return -1;
	memcpy(&ifr.ifr_addr, ifa->ifa_addr, ifa->ifa_addr->sa_len);
	snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", name);
	printf("%s", "\tinet6: ");
	_inet6_print_addr(&ifr.ifr_ifru.ifru_addr.sin6_addr);
	if(ioctl(fd, SIOCGIFDSTADDR_IN6, &ifr) == 0)
	{
		fputs(" -> ", stdout);
		_inet6_print_addr(&ifr.ifr_ifru.ifru_dstaddr.sin6_addr);
	}
	putchar('\n');
#else
static int _inet6_do(Prefs prefs, char const * name, int fd, void * ifa)
{
	/* FIXME implement */
#endif
	return 0;
}

static void _inet6_print_addr(struct in6_addr * addr)
{
	size_t i;
	char const * sep = "";

	for(i = 0; i < 16; i+=2)
	{
		printf("%s%02x%02x", sep, addr->s6_addr[i],
				addr->s6_addr[i + 1]);
		sep = ":";
	}
}


/* ifconfig_error */
static int _ifconfig_error(char const * message, int ret)
{
	fputs(PROGNAME ": ", stderr);
	perror(message);
	return ret;
}


/* inet_str */
char const * _inet_str(struct sockaddr * addr)
{
	static char buf[16];

	if(addr->sa_family != AF_INET)
		snprintf(buf, sizeof(buf), "%s", "UNKNOWN");
	else
		/* FIXME understand why this is so */
		snprintf(buf, sizeof(buf), "%u.%u.%u.%u",
				(unsigned char)addr->sa_data[2],
				(unsigned char)addr->sa_data[3],
				(unsigned char)addr->sa_data[4],
				(unsigned char)addr->sa_data[5]);
	return buf;
}


/* mac_media_str */
static char const * _mac_media_str(int type)
{
	static char buf[32];
	idxstr is[] =
	{
		{ 0x20,	"Ethernet"	},
		{ 0x80,	"IEEE802.11"	},
		{ 0,	NULL		}
	};
	unsigned int i;

	for(i = 0; is[i].str != NULL; i++)
		if(is[i].idx == (type & 0xe0))
			break;
	if(is[i].str == NULL)
	{
		snprintf(buf, sizeof(buf), "%s (%u)", "UNKNOWN", type);
		return buf;
	}
	snprintf(buf, sizeof(buf), "%s%s", is[i].str, (type & 0x1f) == 0
			? " autoselect" : "");
	return buf;
}


/* mac_status_str */
static char const * _mac_status_str(int state)
{
	switch(state)
	{
#ifdef LINK_STATE_DOWN
		case LINK_STATE_DOWN:		return "inactive";
#endif
#ifdef LINK_STATE_UP
		case LINK_STATE_UP:		return "active";
#endif
#ifdef LINK_STATE_UNKNOWN
		case LINK_STATE_UNKNOWN:
#endif
		default:			return NULL;
	}
}


/* ifconfig_usage */
static int _ifconfig_usage(void)
{
	fputs("Usage: " PROGNAME " [-m] interface [argument...]\n"
"       " PROGNAME " -a\n", stderr);
	return 1;
}


/* public */
/* functions */
/* main */
int main(int argc, char * argv[])
{
	int o;
	Prefs prefs;

	memset(&prefs, 0, sizeof(prefs));
	while((o = getopt(argc, argv, "am")) != -1)
		switch(o)
		{
			case 'a':
				prefs |= PREFS_a;
				break;
			case 'm':
				prefs |= PREFS_m;
				break;
			default:
				return _ifconfig_usage();
		}
	if(prefs & PREFS_a)
	{
		if(optind != argc)
			return _ifconfig_usage();
	}
	else if(optind == argc)
		return _ifconfig_usage();
	return (_ifconfig(prefs, argc - optind, &argv[optind]) == 0) ? 0 : 2;
}
