/* 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.3 2001/02/20 06:14:07 andersen Exp $
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>   // strcmp and friends
#include <ctype.h>    // isdigit and friends
#include <sys/types.h>
#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>
#include "busybox.h"

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

/* 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);
}

/* 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")) {
		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.. */
	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
			fprintf(stderr,
				_("in_ether(%s): invalid ether address!\n"),
				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
			fprintf(stderr,
				_("in_ether(%s): invalid ether address!\n"),
				orig);
#endif
			errno = EINVAL;
			return (-1);
		}
		if (c != 0)
			bufp++;
		*ptr++ = (unsigned char) (val & 0377);
		i++;
		
		/* We might get a semicolon here - not required. */
		if (*bufp == ':')
			bufp++;
		
	}

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

	return 0;
}

int ifconfig_main(int argc, char **argv)
{
	struct ifreq ifr;
	struct sockaddr_in sa;
	struct sockaddr sa2;
	char **spp;
	int goterr = 0;
	int r, didnetmask = 0;
	char host[128];

	if(argc < 2) {
		show_usage();
	}

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

	/* 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) {
		if (!strcmp(*spp, "arp")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_NOARP);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "-arp")) {
			goterr |= set_flag(ifr.ifr_name, IFF_NOARP);
			spp++;
			continue;
		}
		
		if (!strcmp(*spp, "trailers")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_NOTRAILERS);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "-trailers")) {
			goterr |= set_flag(ifr.ifr_name, IFF_NOTRAILERS);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "promisc")) {
			goterr |= set_flag(ifr.ifr_name, IFF_PROMISC);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "-promisc")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_PROMISC);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "multicast")) {
			goterr |= set_flag(ifr.ifr_name, IFF_MULTICAST);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "-multicast")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_MULTICAST);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "allmulti")) {
			goterr |= set_flag(ifr.ifr_name, IFF_ALLMULTI);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "-allmulti")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_ALLMULTI);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "up")) {
			goterr |= set_flag(ifr.ifr_name, (IFF_UP | IFF_RUNNING));
			spp++;
			continue;
		}
		if (!strcmp(*spp, "down")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_UP);
			spp++;
			continue;
		}

		if (!strcmp(*spp, "metric")) {
			if (*++spp == NULL)
				show_usage();
			ifr.ifr_metric = atoi(*spp);
			if (ioctl(sockfd, SIOCSIFMETRIC, &ifr) < 0) {
				fprintf(stderr, "SIOCSIFMETRIC: %s\n", strerror(errno));
				goterr++;
			}
			spp++;
			continue;
		}
		if (!strcmp(*spp, "mtu")) {
			if (*++spp == NULL)
				show_usage();
			ifr.ifr_mtu = atoi(*spp);
			if (ioctl(sockfd, SIOCSIFMTU, &ifr) < 0) {
				fprintf(stderr, "SIOCSIFMTU: %s\n", strerror(errno));
				goterr++;
			}
			spp++;
			continue;
		}
#ifdef SIOCSKEEPALIVE
		if (!strcmp(*spp, "keepalive")) {
			if (*++spp == NULL)
				show_usage();
			ifr.ifr_data = (caddr_t) atoi(*spp);
			if (ioctl(sockfd, SIOCSKEEPALIVE, &ifr) < 0) {
				fprintf(stderr, "SIOCSKEEPALIVE: %s\n", strerror(errno));
				goterr++;
			}
			spp++;
			continue;
		}
#endif

#ifdef SIOCSOUTFILL
		if (!strcmp(*spp, "outfill")) {
			if (*++spp == NULL)
				show_usage();
			ifr.ifr_data = (caddr_t) atoi(*spp);
			if (ioctl(sockfd, SIOCSOUTFILL, &ifr) < 0) {
				fprintf(stderr, "SIOCSOUTFILL: %s\n", strerror(errno));
				goterr++;
			}
			spp++;
			continue;
		}
#endif

		if (!strcmp(*spp, "-broadcast")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_BROADCAST);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "broadcast")) {
			if (*++spp != NULL) {
				safe_strncpy(host, *spp, (sizeof host));
				if (INET_resolve(host, &sa) < 0) {
					goterr++;
					spp++;
					continue;
				}
				memcpy((char *) &ifr.ifr_broadaddr,
				       (char *) &sa,
				       sizeof(struct sockaddr));
				if (ioctl(sockfd, SIOCSIFBRDADDR, &ifr) < 0) {
					perror("SIOCSIFBRDADDR");
					goterr++;
				}
				spp++;
			}
			goterr |= set_flag(ifr.ifr_name, IFF_BROADCAST);
			continue;
		}
		if (!strcmp(*spp, "dstaddr")) {
			if (*++spp == NULL)
				show_usage();
			safe_strncpy(host, *spp, (sizeof host));
			if (INET_resolve(host, &sa) < 0) {
				goterr++;
				spp++;
				continue;
			}
			memcpy((char *) &ifr.ifr_dstaddr, (char *) &sa,
			       sizeof(struct sockaddr));
			if (ioctl(sockfd, SIOCSIFDSTADDR, &ifr) < 0) {
				fprintf(stderr, "SIOCSIFDSTADDR: %s\n",
					strerror(errno));
				goterr++;
			}
			spp++;
			continue;
		}
		if (!strcmp(*spp, "netmask")) {
			if (*++spp == NULL || didnetmask)
				show_usage();
			safe_strncpy(host, *spp, (sizeof host));
			if (INET_resolve(host, &sa) < 0) {
				goterr++;
				spp++;
				continue;
			}
			didnetmask++;
			memcpy((char *) &ifr.ifr_netmask, (char *) &sa,
			       sizeof(struct sockaddr));
			if (ioctl(sockfd, SIOCSIFNETMASK, &ifr) < 0) {
				perror("SIOCSIFNETMASK");
				goterr++;
			}
			spp++;
			continue;
		}

		if (!strcmp(*spp, "-pointopoint")) {
			goterr |= clr_flag(ifr.ifr_name, IFF_POINTOPOINT);
			spp++;
			continue;
		}
		if (!strcmp(*spp, "pointopoint")) {
			if (*(spp + 1) != NULL) {
				spp++;
				safe_strncpy(host, *spp, (sizeof host));
				if (INET_resolve(host, &sa)) {
					goterr++;
					spp++;
					continue;
				}
				memcpy((char *) &ifr.ifr_dstaddr, (char *) &sa,
				       sizeof(struct sockaddr));
				if (ioctl(sockfd, SIOCSIFDSTADDR, &ifr) < 0) {
					perror("SIOCSIFDSTADDR");
					goterr++;
				}
			}
			goterr |= set_flag(ifr.ifr_name, IFF_POINTOPOINT);
			spp++;
			continue;
		};

		if (!strcmp(*spp, "hw")) {
			if (*++spp == NULL || strcmp("ether", *spp)) {
				show_usage();
			}
				
			if (*++spp == NULL) {
				/* silently ignore it if no address */
				continue;
			}

			safe_strncpy(host, *spp, (sizeof host));
			if (in_ether(host, &sa2) < 0) {
				fprintf(stderr, "invalid hw-addr %s\n", host);
				goterr++;
				spp++;
				continue;
			}
			memcpy((char *) &ifr.ifr_hwaddr, (char *) &sa2,
			       sizeof(struct sockaddr));
			if (ioctl(sockfd, SIOCSIFHWADDR, &ifr) < 0) {
				perror("SIOCSIFHWADDR");
				goterr++;
			}
			spp++;
			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 */

        exit(0);
}