/*
 * File...........: qetharp24.c
 * Author(s)......: Frank Pavlic <pavlic@de.ibm.com>
 * Bugreports.to..: <Linux390@de.ibm.com>
 * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2001
 *
 * History of changes (starts February 2002)
 *
 * - 02/26/2002: Frank Pavlic
 *      fix in function getARPfromHiperSockets:
 *         it only works correctly if there were only one reply.
 *         if there were more replies the calculation for the following entries 
 *         wasn't correct,so that the following entries couldn't be found and 
 *         as a result some strange addresses were shown.
 * - 08/15/2002: Frank Pavlic        
 *         - moved all struct and macro definitions to qetharp.h
 *         - also copied necessary structs from kernel include files
 *           to avoid linking them
 * - 04/19/2003: Utz Bacher
 *         - added add and delete functions (-> version 0.02)
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include "qetharp24.h"

/*****************************************************
 *            Function implementation                *
 *****************************************************/

static void show_Header() 
{
	printf("%-40.40s%-20.20s%-10.10s%-10.10s\n", 
	       "Address","HWaddress","HWType","Iface");
}

char* show_Entry(char *buff,unsigned char ip_type,
		 int struct_size,int is_osa,option_info_t *opin)
{

	char *ip;
	char *mac;
	char tmpbuff[32];
	struct in_addr inadr;
	struct hostent *machine=NULL;

	if (ip_type == IP_VERSION_4) { 
		ip = ( char *) (buff + struct_size);
		sprintf(tmpbuff,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]);
		if (!opin->host_resolution) {
			if (inet_aton(tmpbuff,&inadr))
				machine = gethostbyaddr((char *)   
    					  &inadr,sizeof(inadr),AF_INET);
			else
				machine = gethostbyname(tmpbuff);
		}
		if (opin->compact_output==OPTION_INFO_COMPACT_OUTPUT) {
			printf("%s\n",tmpbuff);
		} else if (is_osa) {
			mac = (char *) (buff + struct_size - MAC_LENGTH);
			if (machine)
				printf("%-40.40s",machine->h_name);
			else
				printf("%-40.40s",tmpbuff);
			sprintf(tmpbuff,"%02x:%02x:%02x:%02x:%02x:%02x",
				mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
			printf("%-20.20s%-10.10s%-10.10s\n",
			       tmpbuff,"ether",opin->dev_name);
			
		} else {
			if (machine)
				printf("%-40.40s",machine->h_name);
			else
				printf("%-40.40s",tmpbuff);
			printf("%-20.20s%-10.10s%-10.10s\n",
			       "","hiper",opin->dev_name);
		}
		buff = buff + IPV4_LENGTH + struct_size;

	}
	else {
		ip = ( char *) (buff + struct_size);
		sprintf(tmpbuff,"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
			*((unsigned short*)&ip[0]),*((unsigned short*)&ip[2]),
			*((unsigned short*)&ip[4]),*((unsigned short*)&ip[6]),
			*((unsigned short*)&ip[8]),*((unsigned short*)&ip[10]),
			*((unsigned short*)&ip[12]),*((unsigned short*)&ip[14]));
		if (opin->compact_output==OPTION_INFO_COMPACT_OUTPUT) {
			printf("%s\n",tmpbuff);
		} else if (is_osa) {
			mac = (char *) (buff + struct_size - MAC_LENGTH);
			printf("%-40.40s",tmpbuff);
			sprintf(tmpbuff,"%02x:%02x:%02x:%02x:%02x:%02x",
				mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
			printf("%-20.20s%-10.10s%-10.10s\n",
			       tmpbuff,"ether",opin->dev_name);
			
		} else {
			printf("%-40.40s",tmpbuff);
			printf("%-20.20s%-10.10s%-10.10s\n",
			       "","hiper",opin->dev_name);
		}

		buff = buff + IPV6_LENGTH + struct_size;
	}
	return buff;
}


int getARPfromHiperSockets(char *buff,option_info_t *opin) 
{

	char *astr;
	int entries,i,bytecounter;
	hsi_queryarp_data_t *arpdata;

	astr = buff;
	entries      = 0;
	i            = 0;
	bytecounter  = 0;
	astr = astr + sizeof(unsigned short);
	memcpy(&entries,astr,sizeof(int));
	astr = astr + sizeof(int);	
	for (i = 1;i <= entries;i++)
	{ 	
	        arpdata = (hsi_queryarp_data_t *) astr;
		astr = show_Entry(astr,arpdata->ip_type,
				  sizeof(hsi_queryarp_data_t),
				  0,opin);

		if (arpdata->ip_type == IP_VERSION_4 ) 
		         bytecounter = bytecounter  + 
				       (IPV4_LENGTH +
					sizeof(hsi_queryarp_data_t));
		else
		         bytecounter = bytecounter  + 
				       (IPV6_LENGTH +
					sizeof(hsi_queryarp_data_t));
		
	        if (!astr) 
			return -ENOMEM;

		if ((i % HSI_ENTRIES_IN_ONE_REPLY)==0 && i>0) {

     		     astr = astr+(ARP_NEXT_REPLY_CONSTANT-bytecounter);
		     bytecounter=0;
		}

	}			
	return 0;
}

int getARPfromOSACard(char * buff,option_info_t *opin) 
{

	char *astr;
	int entries,i;
	osa_queryarp_data_t *arpdata;

	astr = buff;
	entries = 0;
	i = 0;
	astr = astr + sizeof(unsigned short);
	memcpy(&entries,astr,sizeof(int));
	astr = astr + sizeof(int);	
	for (i = 0;i < entries;i++)
	{ 	
		arpdata = (osa_queryarp_data_t *) astr;
		astr = show_Entry(astr,arpdata->ip_type,
				  sizeof(osa_queryarp_data_t),
				  1,opin);
		if (!astr)
			return -ENOMEM;
	}			
	return 0;
}

static void option_info_init(option_info_t *opin)
{
	memset(opin,0,sizeof(option_info_t));
}

static int qetharp_purge(option_info_t *opin) {

	int sd;
 	struct ifreq ifr;

	if (!opin->dev_name) {
		printf("\nError: no interface specified!\n");
		return -1;
	}
		
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("Socket failed: %m\n");
		return -1;
	}
	strcpy(ifr.ifr_name, opin->dev_name);
	if (ioctl(sd, ARP_PURGE, &ifr) < 0) {
		close(sd);
		perror("\nUnsuccessful");
		return -1;
	}

	return 0;

}

static int qetharp_add(option_info_t *opin) {

	int sd;
 	struct ifreq ifr;
	osa_addarp_data_t addarp;
	unsigned int i1,i2,i3,i4,i5,i6,r;

	memset(&addarp,0,sizeof(osa_addarp_data_t));
	if (!opin->dev_name) {
		printf("\nError: no interface specified!\n");
		return -1;
	}
	if (!opin->ip_addr) {
		printf("\nError: no ip address specified!\n");
		return -1;
	}
	r=sscanf(opin->ip_addr,"%u.%u.%u.%u",&i1,&i2,&i3,&i4);
	if ( (r!=4) || (i1>255) || (i2>255) || (i3>255) || (i4>255) ) {
		printf("\nError: invalid ip address specified!\n");
		return -1;
	}
	addarp.ip[0]=i1;
	addarp.ip[1]=i2;
	addarp.ip[2]=i3;
	addarp.ip[3]=i4;
	
	if (!opin->mac_addr) {
		printf("\nError: no MAC address specified!\n");
		return -1;
	}
	r=sscanf(opin->mac_addr,"%x:%x:%x:%x:%x:%x",&i1,&i2,&i3,&i4,&i5,&i6);
	if ( (r!=6) || (i1>255) || (i2>255) || (i3>255) || 
	     (i4>255) || (i5>255) || (i6>255) ) {
		printf("\nError: invalid MAC address specified!\n");
		return -1;
	}
	addarp.mac[0]=i1;
	addarp.mac[1]=i2;
	addarp.mac[2]=i3;
	addarp.mac[3]=i4;
	addarp.mac[4]=i5;
	addarp.mac[5]=i6;

	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("Socket failed: %m\n");
		return -1;
	}
	strcpy(ifr.ifr_name, opin->dev_name);
	ifr.ifr_ifru.ifru_data = (void*)&addarp;

	if (ioctl(sd, ARP_ADD, &ifr) < 0) {
		close(sd);
		perror("\nUnsuccessful");
		return -1;
	}

	return 0;
}

static int qetharp_delete(option_info_t *opin) {

	int sd;
 	struct ifreq ifr;
	osa_delarp_data_t delarp;
	unsigned int i1,i2,i3,i4,r;

	memset(&delarp,0,sizeof(osa_delarp_data_t));
	if (!opin->dev_name) {
		printf("\nError: no interface specified!\n");
		return -1;
	}
	if (!opin->ip_addr) {
		printf("\nError: no ip address specified!\n");
		return -1;
	}
	r=sscanf(opin->ip_addr,"%u.%u.%u.%u",&i1,&i2,&i3,&i4);
	if ( (r!=4) || (i1>255) || (i2>255) || (i3>255) || (i4>255) ) {
		printf("\nError: invalid ip address specified!\n");
		return -1;
	}
	delarp.ip[0]=i1;
	delarp.ip[1]=i2;
	delarp.ip[2]=i3;
	delarp.ip[3]=i4;
	
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("Socket failed: %m\n");
		return -1;
	}
	strcpy(ifr.ifr_name, opin->dev_name);
	ifr.ifr_ifru.ifru_data = (void*)&delarp;

	if (ioctl(sd, ARP_DELETE, &ifr) < 0) {
		close(sd);
		perror("\nUnsuccessful");
		return -1;
	}

	return 0;
}

static int qetharp_query(option_info_t *opin) {

	int sd;
 	struct ifreq ifr;
	char *data_buff;
	int memsize,result;
	unsigned short retcode;

	if (!opin->dev_name) {
		printf("\nError: no interface specified!\n");
		return -1;
	}
		
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("Socket failed: %m\n");
		return -1;
	}
	strcpy(ifr.ifr_name, opin->dev_name);
	memsize = DATA_SIZE;
	data_buff = (char *) malloc(DATA_SIZE);
	memcpy(data_buff,&memsize,sizeof(int));
	ifr.ifr_ifru.ifru_data = data_buff;

	if (ioctl(sd, ARP_QUERY, &ifr) < 0) {
		close(sd);
		perror("\nUnsuccessful");
		return -1;
	}
	memcpy(&retcode,data_buff,sizeof(unsigned short));
	if (opin->compact_output!=OPTION_INFO_COMPACT_OUTPUT) {
		show_Header();
	}
	if (retcode == IS_HIPERSOCKET_DEVICE) 
	        result = getARPfromHiperSockets(data_buff,opin);
	else
		result = getARPfromOSACard(data_buff,opin);
	free(data_buff);

	return result;
}
static void qetharp_usage(void)
{
	printf("qetharp [-[n]q interface]|[-p interface]|\n" \
	       "\t\t[-a interface -i ip-addr -m MAC-addr]|\n" \
	       "\t\t[-d interface -i ip-addr] [-h] [-v ]\n\n");
	printf("where:\n" \
	       "\tq: prints ARP entries found on the card\n" \
	       "\tn: in conjunction with the -q option it shows\n" \
	       "\t\tnumerical addresses instead of trying to\n" \
	       "\t\tresolve IP addresses to host names.\n" \
	       "\tc: in conjuction with the -q option it shows\n" \
	       "\t\tonly numerical addresses without any\n" \
	       "\t\tother information.\n" \
	       "\tp: flushes the ARP table of the card\n" \
	       "\ta: add static ARP entry\n" \
	       "\td: delete static ARP entry\n" \
	       "\tv: prints version information\n"
	       "\th: prints this usage information\n");
}

static int qetharp_parse_info(option_info_t *opin)
{
	if ((opin->purge_flag+opin->query_flag+
	    opin->add_flag+opin->delete_flag)!=1) {
		printf("\nOnly use one of the options '-a', " \
		       "'-d', '-p' and 'q' at a time.\n");
		return -1;	
	}
	if (opin->purge_flag &&
	    (opin->query_flag || opin->host_resolution)) {
		printf("\nError in using '-p' option:\n" \
			"\tYou can not use '-p' option in conjunction with " \
			"'-q' or '-n'.\n");
		return -1;
	}
	if (opin->purge_flag) {
		return qetharp_purge(opin);
	}
	if ((opin->host_resolution) && 
	    !(opin->query_flag)) {
		printf("\nError in using '-n' option:\n" \
		       "\t'-q' option missing!\n");
		return -1;
	}
	if (opin->query_flag) {
		return qetharp_query(opin);
	}
	if (opin->add_flag) {
		if ((!opin->ip_flag)||(!opin->mac_flag)) {
			printf("\nError in using '-a' option:\n" \
			       "\t'-i' or '-m' option missing!\n");
			return -1;
		}
		return qetharp_add(opin);
	}
	if (opin->delete_flag) {
		if (!opin->ip_flag) {
			printf("\nError in using '-d' option:\n" \
			       "\t'-i' option missing!\n");
			return -1;
		}
		return qetharp_delete(opin);
	}
	return 0;
}

 
int main(int argc, char **argv) 
{
	
	int index,c,result;
	option_info_t info;

	opterr=0;
	result=0;


	option_info_init(&info);
	while (1)
	{
		c = getopt_long(argc, argv,QETHARP_GETOPT_STRING,
				qetharp_options,&index);
		
		if (c==-1)
			break;

		switch (c) 
		{
		case '?':	
		case 'h':
		        qetharp_usage();
			exit(0);
		case 'v':
			printf("\nqetharp version 0.02\n");
			exit(0);
		case 'q':
			info.dev_name = optarg;
			info.query_flag =  OPTION_INFO_QUERY;
			break;
		case 'n':
			info.host_resolution =  OPTION_INFO_HOST_RESOLUTION;
			break;
		case 'p':
			info.dev_name = optarg;
			info.purge_flag = OPTION_INFO_PURGE;
			break;
		case 'c':
			info.compact_output = OPTION_INFO_COMPACT_OUTPUT;
			break;
		case 'a':
			info.dev_name = optarg;
			info.add_flag = OPTION_INFO_ADD;
			break;
		case 'd':
			info.dev_name = optarg;
			info.delete_flag = OPTION_INFO_DELETE;
			break;
		case 'i':
			info.ip_addr = optarg;
			info.ip_flag = OPTION_INFO_IP;
			break;
		case 'm':
			info.mac_addr = optarg;
			info.mac_flag = OPTION_INFO_MAC;
			break;
		default:
			qetharp_usage();
			exit(0);
		}
	}
	result = qetharp_parse_info(&info);
	return result;
}





