/* ifconfig
 *
 * Similar to the standard Unix ifconfig, but with only the necessary
 * parts for AF_INET, and without any printing of if info (for now).
 *
 * Bjorn Wesen, Axis Communications AB
 *
 *
 * Authors of the original ifconfig was:      
 *              Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
 *
 * 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.
 *
 * $Id: ifconfig.c,v 1.4 2001/03/06 00:48:59 andersen Exp $
 *
 * Majorly hacked up by Larry Doolittle <ldoolitt@recycle.lbl.gov> 
 *
 */

#include "busybox.h"
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>   // strcmp and friends
#include <ctype.h>    // isdigit and friends
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <linux/if_ether.h>

static int sockfd;  /* socket fd we use to manipulate stuff with */

#define TESTME 0
#if TESTME
#define ioctl test_ioctl
char *saddr_to_a(struct sockaddr *s)
{
	if (s->sa_family == ARPHRD_ETHER) {
		static char hw[18];
		sprintf(hw, "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
			s->sa_data[0], s->sa_data[1], s->sa_data[2],
			s->sa_data[3], s->sa_data[4], s->sa_data[5]);
		return hw;
	} else if (s->sa_family == AF_INET) {
		struct sockaddr_in *ss = (struct sockaddr_in *) s;
		return inet_ntoa(ss->sin_addr);
	} else {
		return NULL;
	}
}

int test_ioctl(int __fd, unsigned long int __request, void *param)
{
	struct ifreq *i=(struct ifreq *)param;
	printf("ioctl fd=%d, request=%ld\n", __fd, __request);
	
	switch(__request) {
		case SIOCGIFFLAGS:   printf("  SIOCGIFFLAGS\n");       i->ifr_flags = 0;   break;
		case SIOCSIFFLAGS:   printf("  SIOCSIFFLAGS, %x\n",    i->ifr_flags);     break;
		case SIOCSIFMETRIC:  printf("  SIOCSIFMETRIC, %d\n",   i->ifr_metric);    break;
		case SIOCSIFMTU:     printf("  SIOCSIFMTU, %d\n",      i->ifr_mtu);       break;
		case SIOCSIFBRDADDR: printf("  SIOCSIFBRDADDR, %s\n",  saddr_to_a(&(i->ifr_broadaddr))); break;
		case SIOCSIFDSTADDR: printf("  SIOCSIFDSTADDR, %s\n",  saddr_to_a(&(i->ifr_dstaddr  ))); break;
		case SIOCSIFNETMASK: printf("  SIOCSIFNETMASK, %s\n",  saddr_to_a(&(i->ifr_netmask  ))); break;
		case SIOCSIFADDR:    printf("  SIOCSIFADDR, %s\n",     saddr_to_a(&(i->ifr_addr     ))); break;
		case SIOCSIFHWADDR:  printf("  SIOCSIFHWADDR, %s\n",   saddr_to_a(&(i->ifr_hwaddr   ))); break;  /* broken */
		default:
	}
	return 0;
}
#endif


/* print usage and exit */

#define _(x) x

/* Set a certain interface flag. */
static int
set_flag(char *ifname, short flag)
{
	struct ifreq ifr;
	
	strcpy(ifr.ifr_name, ifname);
	if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
		perror("SIOCGIFFLAGS"); 
		return -1;
	}
	strcpy(ifr.ifr_name, ifname);
	ifr.ifr_flags |= flag;
	if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
		perror("SIOCSIFFLAGS");
		return -1;
	}
	return 0;
}


/* Clear a certain interface flag. */
static int
clr_flag(char *ifname, short flag)
{
	struct ifreq ifr;
	
	strcpy(ifr.ifr_name, ifname);
	if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
		perror("SIOCGIFFLAGS");
		return -1;
	}
	strcpy(ifr.ifr_name, ifname);
	ifr.ifr_flags &= ~flag;
	if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
		perror("SIOCSIFFLAGS");
		return -1;
	}
	return 0;
}

/* which element in struct ifreq to frob */
enum frob {
	L_METRIC,
	L_MTU,
	L_DATA,
	L_BROAD,
	L_DEST,
	L_MASK,
	L_HWAD,
};


struct flag_map {
	char *name;
	enum frob frob;
	int flag;
	int sflag;
	int action;
};

/* action:
 *  2   set
 *  4   clear
 *  6   set/clear
 *  8   clear/set
 *  10  numeric
 *  12  address
 *  14  address/clear
 */
const static struct flag_map flag_table[] = {
	{"arp",         0,  IFF_NOARP,             0, 6},
	{"trailers",    0,  IFF_NOTRAILERS,        0, 6},
	{"promisc",     0,  IFF_PROMISC,           0, 8},
	{"multicast",   0,  IFF_MULTICAST,         0, 8},
	{"allmulti",    0,  IFF_ALLMULTI,          0, 8},
	{"up",          0, (IFF_UP | IFF_RUNNING), 0, 2},
	{"down",        0,  IFF_UP,                0, 4},
	{"metric",      L_METRIC,              0, SIOCSIFMETRIC,  10},
	{"mtu",         L_MTU,                 0, SIOCSIFMTU,     10},
#ifdef SIOCSKEEPALIVE
	{"keepalive",   L_DATA,                0, SIOCSKEEPALIVE, 10},
#endif
#ifdef SIOCSOUTFILL
	{"outfill",     L_DATA,                0, SIOCSOUTFILL,   10},
#endif
	{"broadcast",   L_BROAD, IFF_BROADCAST,   SIOCSIFBRDADDR, 14},
	{"dstaddr",     L_DEST,                0, SIOCSIFDSTADDR, 12},
	{"netmask",     L_MASK,                0, SIOCSIFNETMASK, 12},
	{"pointopoint", L_DEST,  IFF_POINTOPOINT, SIOCSIFDSTADDR, 14},
	{"hw",          L_HWAD,                0, SIOCSIFHWADDR,  14},
};


/* resolve XXX.YYY.ZZZ.QQQ -> binary */

static int
INET_resolve(char *name, struct sockaddr_in *sin)
{
	sin->sin_family = AF_INET;
	sin->sin_port = 0;

	/* Default is special, meaning 0.0.0.0. */
	if (strcmp(name, "default")==0) {
		sin->sin_addr.s_addr = INADDR_ANY;
		return 1;
	}
	/* Look to see if it's a dotted quad. */
	if (inet_aton(name, &sin->sin_addr)) {
		return 0;
	}
	/* guess not.. */
	errno = EINVAL;
	return -1;
}

/* Input an Ethernet address and convert to binary. */
static int
in_ether(char *bufp, struct sockaddr *sap)
{
	unsigned char *ptr;
	char c, *orig;
	int i;
	unsigned val;
	
	sap->sa_family = ARPHRD_ETHER;
	ptr = sap->sa_data;
	
	i = 0;
	orig = bufp;
	while ((*bufp != '\0') && (i < ETH_ALEN)) {
		val = 0;
		c = *bufp++;
		if (isdigit(c))
			val = c - '0';
		else if (c >= 'a' && c <= 'f')
			val = c - 'a' + 10;
		else if (c >= 'A' && c <= 'F')
			val = c - 'A' + 10;
		else {
#ifdef DEBUG
			error_msg(
				_("in_ether(%s): invalid ether address!"),
				orig);
#endif
			errno = EINVAL;
			return -1;
		}
		val <<= 4;
		c = *bufp;
		if (isdigit(c))
			val |= c - '0';
		else if (c >= 'a' && c <= 'f')
			val |= c - 'a' + 10;
		else if (c >= 'A' && c <= 'F')
			val |= c - 'A' + 10;
		else if (c == ':' || c == 0)
			val >>= 4;
		else {
#ifdef DEBUG
			error_msg(
				_("in_ether(%s): invalid ether address!"),
				orig);
#endif
			errno = EINVAL;
			return -1;
		}
		if (c != 0)
			bufp++;
		*ptr++ = (unsigned char) (val & 0377);
		i++;
		
		/* optional colon already handled, don't swallow a second */
	}

	if(i != ETH_ALEN) {
		errno = EINVAL;
		return -1;
	}

	return 0;
}
		
#ifdef BB_FEATURE_IFCONFIG_STATUS
extern int display_interfaces(void);
#else
int display_interfaces(void)
{
    show_usage();
}
#endif

int ifconfig_main(int argc, char **argv)
{
	struct ifreq ifr;
	struct sockaddr_in sa;
	char **spp, *cmd;
	int goterr = 0;
	int r;
	/* int didnetmask = 0;   special case input error detection no longer implemented */
	char host[128];
	const struct flag_map *ft;
	int i, sense;
	int a, ecode;
	struct sockaddr *d;

	if(argc < 2) {
		return(display_interfaces());
	}

	/* Create a channel to the NET kernel. */
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror_msg_and_die("socket");
	}

	/* skip argv[0] */

	argc--;
	argv++;

	spp = argv;

	/* get interface name */

	safe_strncpy(ifr.ifr_name, *spp++, IFNAMSIZ);

	/* Process the remaining arguments. */
	while (*spp != (char *) NULL) {
		cmd = *spp;
		sense=0;
		if (*cmd=='-') {
			sense=1;
			cmd++;
		}
		ft = NULL;
		for (i=0; i<(sizeof(flag_table)/sizeof(struct flag_map)); i++) {
			if (strcmp(cmd, flag_table[i].name)==0) {
				ft=flag_table+i;
				spp++;
				break;
			}
		}
		if (ft) {
			switch (ft->action+sense) {
			case 4:
			case 7:
			case 8:
			case 15:
				goterr |= clr_flag(ifr.ifr_name, ft->flag);
				break;
			case 2:
			case 6:
			case 9:
				goterr |= set_flag(ifr.ifr_name, ft->flag);
				break;
			case 10:
				if (*spp == NULL)
					show_usage();
				a = atoi(*spp++);
				switch (ft->frob) {
					case L_METRIC: ifr.ifr_metric = a; break;
					case L_MTU:    ifr.ifr_mtu    = a; break;
					case L_DATA:   ifr.ifr_data   = (caddr_t) a; break;
					default: error_msg_and_die("bugaboo");
				}

				if (ioctl(sockfd, ft->sflag, &ifr) < 0) {
					perror(ft->name);  /* imperfect */
					goterr++;
				}
				break;
			case 12:
			case 14:
				if (ft->action+sense==10 && *spp == NULL) {
					show_usage();
					break;
				}
				if (*spp != NULL) {
					safe_strncpy(host, *spp, (sizeof host));
					spp++;
					if (ft->frob == L_HWAD) {
						ecode = in_ether(host, &ifr.ifr_hwaddr);
					} else {
						switch (ft->frob) {
							case L_BROAD: d = &ifr.ifr_broadaddr; break;
							case L_DEST:  d = &ifr.ifr_dstaddr;   break;
							case L_MASK:  d = &ifr.ifr_netmask;   break;
							default: error_msg_and_die("bugaboo");
						}
						ecode = INET_resolve(host, (struct sockaddr_in *) d);
					}
					if (ecode < 0 || ioctl(sockfd, ft->sflag, &ifr) < 0) {
						perror(ft->name);  /* imperfect */
						goterr++;
					}
				}
				if (ft->flag != 0) {
					goterr |= set_flag(ifr.ifr_name, ft->flag);
				}
				break;
			default:
				show_usage();
			} /* end of switch */
			continue;
		}
		
		/* If the next argument is a valid hostname, assume OK. */
		safe_strncpy(host, *spp, (sizeof host));

		if (INET_resolve(host, &sa) < 0) {
			show_usage();
		}
		memcpy((char *) &ifr.ifr_addr,
		       (char *) &sa, sizeof(struct sockaddr));

		r = ioctl(sockfd, SIOCSIFADDR, &ifr);

		if (r < 0) {
			perror("SIOCSIFADDR");
			goterr++;
		}

		/*
		 * Don't do the set_flag() if the address is an alias with a - at the
		 * end, since it's deleted already! - Roman
		 *
		 * Should really use regex.h here, not sure though how well it'll go
		 * with the cross-platform support etc. 
		 */
		{
			char *ptr;
			short int found_colon = 0;
			for (ptr = ifr.ifr_name; *ptr; ptr++ )
				if (*ptr == ':') found_colon++;
			
			if (!(found_colon && *(ptr - 1) == '-'))
				goterr |= set_flag(ifr.ifr_name, (IFF_UP | IFF_RUNNING));
		}
		
		spp++;

	} /* end of while-loop */

        return goterr;
}