/*
 * This is a partial implementation of RFC 3338
 *
 * Dual Stack Hosts Using BIA
 * ftp://ftp.rfc-editor.org/in-notes/rfc3338.txt
 *
 * ultimately, it should be possible to LD_PRELOAD this library in front
 * of an application, allowing it to communicate over ipv6 even though it
 * is not aware of it.
 *
 * Notez bien subsection 2.2 of that rfc:
 * BIA SHOULD NOT be used for an IPv4 application for which source code
 * is available.  We strongly recommend that application programmers
 * SHOULD NOT use this mechanism when application source code is
 * available.  As well, it SHOULD NOT be used as an excuse not to port
 * software or delay porting.
 *
 * This software currently supports:
 * - intercepts socket() (NO_IPv4)
 * - intercepts gethostbyname() 
 * - intercepts connect() (SHOULD)
 *
 * Known differences to the RFC specification:
 * - Additionaly, if 'NO_IPV4' is true, it will be attempted to 'convert' all
 *   IPv4 calls to IPv6 using a NAT-PT (TRT) prefix. In other words, also
 *   when the application makes a 'normal' ipv4 connection, it will be 
 *   translated to ipv6 by adding a prefix to the address.
 * - The RFC suggests we use 0.0.0.0/24 range. I think that's too small, but
 *   we'll use this for the time being.
 * - We don't do any content-level filtering, so e.g. FTP won't work.
 *
 * Problems:
 * - when an AF_INET socket is created, we also create a second AF_INET6 
 *   'mirror' socket for it. When it turns out the socket is a 'mapped'
 *   connection, for example in connect(), the mirror socket is dup()'ed
 *   onto the origional one, so e.g. write() works mostly transparently.
 *   Notez bien that any fcntl operations on the origional socket will have
 *   been lost.
 * - ipv6-aware applications that use gethostbyname will recieve the
 *   'mapped' addresses. If a program uses gethostbyname2, we assume it
 *   knows about ipv6, thus the behavior of gethostbyname2 isn't changed.
 * - bind() translates a bind() to 0.0.0.0 to a bind() to ::. This is only
 *   correct on linux in the first place (since on linux :: also accepts
 *   ipv4 packets), and even then debatable. We assume that, if a program
 *   ever binds to 0.0.0.0, it's non-ipv6-aware, and we have to translate
 *   packets recieved by recvmsg() et al to ipv4 structures.
 *
 * This software currently does not support:
 * - does not intercept gethostbyname_r() yet (not listed in RFC, but reported
 *     to be in popular use)
 * - does not intercept bind() yet (SHOULD)
 * - does not intercept sendmsg() yet (SHOULD)
 * - does not intercept sendto() yet (SHOULD)
 * - does not intercept accept() yet (SHOULD)
 * - does not intercept recvfrom() yet (SHOULD)
 * - does not intercept recvmsg() yet (SHOULD)
 * - does not intercept getpeername() yet (SHOULD)
 * - does not intercept setpeername() yet (SHOULD)
 * - does not intercept getsocketopt() yet (SHOULD)
 * - does not intercept setsocketopt() yet (SHOULD)
 * - does not intercept recv() yet (SHOULD)
 * - does not intercept read() yet (SHOULD)
 * - the rest :)
 */

#define NO_IPV4 0
#define TRACE_OVERRIDES 1
#define LOGTO stderr

/* general includes */
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>
#include <unistd.h> // dup2, fcntl
#include <fcntl.h>  // fcntl

/* being able to recognise addresses */
#include <sys/types.h>
#include <regex.h>

/* LD_PRELOAD includes (dlopen(), dlsym()) */
#include <dlfcn.h>

/* networking includes */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> // struct hostent, gethostbyname
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int (*orig_socket) (int, int, int) = NULL;
int (*orig_connect) (int, const struct sockaddr *, socklen_t) = NULL;
struct hostent * (*orig_gethostbyname) (const char *) = NULL;
int (*orig_sendto) (int  s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) = NULL;
int (*orig_sendmsg) (int s, const struct msghdr *msg, int flags);
int (*orig_recvmsg) (int s, struct msghdr *msg, int flags);
int (*orig_bind) (int, const struct sockaddr *, socklen_t) = NULL;
int (*orig_fcntl1) (int fd, int cmd);
int (*orig_fcntl2) (int fd, int cmd, long arg);
int (*orig_fcntl3) (int fd, int cmd, struct flock *lock);

/* temporary hack to see if we need to convert ipv6 structures
 * received by readmsg() to ipv4 
 *
 * 0: no
 * 1: yes
 *
 * turned to 0 when bind()-ing to a mapped ipv4 address (also 0.0.0.0). :(.
 */
int bound_ipv6aware = 1;

/******* address mapper  ********/

/* for the time being, we'll use 0.0.0.1 - 0.0.0.255, like the rfc 
 * suggests. This is of course very little, and we're going to increase this 
 * number later.
 */
struct in6_addr * mapping [256] = {NULL};

/* for every ipv4 socket, we also create an ipv6 'mirror' socket, over which
 * 5Q
 * 
 * we'll send ipv6 stuff it it turns out this is a socket for a 'mapped' 
 * address
 * TODO find upper bound
 * TODO turn 'socket_replaced' to 0 again when closed
 */
int socketmirror [255] = {-1};
int socket_replaced [255] = {0};

/**
 * generate the i-th ipv4 address
 */
struct in_addr * makeipv4addr (int i)
{
	unsigned long * address = (long *) malloc (sizeof (long));
	//fprintf (LOGTO, "Making ipv4 addr %d\n", i);
	// TODO this is not correct cross-platformly
	*address = htonl(i);
	return (struct in_addr *) address;
}

/* returns 0 when equivalent */
int in6_addrcmp (struct in6_addr * one, struct in6_addr * two)
{
	int i;
	for (i=0; i<16; i++)
	{
		if (one->s6_addr[i] != two->s6_addr[i])
			return 1;
	}
	return 0;
}

struct in6_addr * getrevmap (struct in_addr * ipv4addr)
{
	long i = ntohl(*((unsigned long *)ipv4addr));
	char * buffer = (char*) malloc (500 * sizeof(char));
	fprintf(LOGTO, "Getting reverse map for %s (%d)\n", inet_ntoa(*ipv4addr), (int)i);
	assert (i < 256);
	// commented out due to ugly 0.0.0.0 hack 
	// assert (i > 0);
	if (mapping[i] == NULL)
	{
		// TODO make (int)i endian-correct
		fprintf(LOGTO, "getrevmap: error: mapping[%d] == NULL\n", (int)i);
		exit(0);
	}
	// note: (int)-typecast isn't endian-safe
	fprintf (LOGTO, "returning for mapping %d: %s\n", (int)i, inet_ntop(AF_INET6, mapping[i], buffer, 500));
	free (buffer);
	return mapping[i];
}

/*
 * get mapping for this address. if neccesary creates the mapping,
 * and returns the ipv4 side.
 */
struct in_addr * getmap (struct in6_addr * ipv6addr)
{
	// see if it's in the list yet
	int lastnull = 0;
	int i;
	char* buffer = (char*) malloc (500 * sizeof(char));
	//fprintf (LOGTO, "setting for mapping %s\n", inet_ntop(AF_INET6, ipv6addr, buffer, 500));
	for (i=255; i >= 1; i--)
	{
		if (mapping[i] == NULL)
			lastnull = i;
		else
			if (in6_addrcmp(ipv6addr, mapping[i]) == 0)
				return makeipv4addr(i);
	}
	assert (lastnull != 0);
	mapping[lastnull] = ipv6addr; // maybe duplicate?
	fprintf (LOGTO, "setting mapping[%d] to %s\n", lastnull, inet_ntop(AF_INET6, mapping[lastnull], buffer, 500));
	free (buffer);

	if (mapping[1] == NULL)
		fprintf(LOGTO, "mapping[1] == NULL\n");
	else
		fprintf(LOGTO, "mapping[1] != NULL\n");
	

	return makeipv4addr(lastnull);
}

/**
 * returns true when this is a 'mapped' (0.0.0.0/24 except 0.0.0.0) address
 */
int ismappedaddr (struct in_addr * addr)
{
	unsigned long adr = ntohl(*((unsigned long *)addr));

	//fprintf (LOGTO, "Checking if %s is a mapped address... ", inet_ntoa(*addr));

	return (adr<256); 

	if (adr < 256)
	{
		fprintf (LOGTO, "yes.\n");
		return 1;
	}
	else
	{
		fprintf (LOGTO, "no.\n");
		return 0;
	}
}

void getmapper ()
{
	if (mapping[1] == NULL)
		fprintf(LOGTO, "mapping[1] == NULL\n");
	else
		fprintf(LOGTO, "mapping[1] != NULL\n");
}

/******* function mapper ********/

/**
 * If an AF_INET-socket is created, silently create an AF_INET6-socket instead.
 * Else, just call the origional socket() with the same arguments.
 */
int socket (int domain, int type, int protocol)
{
	if (TRACE_OVERRIDES)
		fprintf(LOGTO, "Using alternative socket() call\n");

	getmapper();

	if (domain == AF_INET)
	{
#if NO_IPV4
		return orig_socket(AF_INET6, type, protocol);
#else
		int retval = orig_socket(domain, type, protocol);
		if (retval > -1)
		{
			socket_replaced[retval] = 0;
			socketmirror[retval] = orig_socket(AF_INET6, type, protocol);
			/*
			fprintf (LOGTO, "Mirror for %d is %d\n", retval, socketmirror[retval]);
			*/
		}
		return retval;
#endif
	}
	else
		return orig_socket(domain, type, protocol);
}

/**
 * returns 1 if the name is an ipv6 address, 0 otherwise
 */
regex_t * ipv6addr = NULL;
int isipv6addr (const char * name)
{
	if (ipv6addr == NULL)
	{
		fprintf (LOGTO, "compiling regexp\n");
		/* remains allocated until end of program */
		ipv6addr = (regex_t *) malloc (sizeof(regex_t));
		//if (regcomp (ipv6addr, "\\d.*", REG_NOSUB | REG_EXTENDED) != 0)
		if (regcomp (ipv6addr, "^([A-Fa-f0-9]{0,4}:)+[A-Fa-f0-9]{0,4}$", REG_NEWLINE | REG_NOSUB | REG_EXTENDED) != 0)
		{
			perror ("ipv6addr: failed to compile regex\n");
			exit(0);
		}
	}

	return (regexec(ipv6addr, name, 0, NULL, 0) != REG_NOMATCH);
}

/**
 * returns 1 if the name is an ipv4 address, 0 otherwise
 */
regex_t * ipv4addr = NULL;
int isipv4addr (const char * name)
{
	if (ipv4addr == NULL)
	{
		fprintf (LOGTO, "compiling regexp\n");
		/* remains allocated until end of program */
		ipv4addr = (regex_t *) malloc (sizeof(regex_t));
		//if (regcomp (ipv4addr, "\\d.*", REG_NOSUB | REG_EXTENDED) != 0)
		if (regcomp (ipv4addr, "^[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+$", REG_NEWLINE | REG_NOSUB | REG_EXTENDED) != 0)
		{
			perror ("ipv4addr: failed to compile regex\n");
			exit(0);
		}
	}

	return (regexec(ipv4addr, name, 0, NULL, 0) != REG_NOMATCH);
}

/**
 * Takes a struct addrinfo and adds it to the hostent list. If the
 * addrinfo host is ipv6, it's mapped to ipv4.
 *
 * Note that, if a host is reachable over both ipv4 and ipv6, we're
 * thus returning 2 ipv4 structs (namely, the 'real' one and the mapped
 * ipv6 address). This is, however, needed, since a service we're connecting
 * to might run only on ipv6 or only on ipv4. (TODO: is this true? not for linux...)
 */
void addtohostent (struct hostent * orig, struct addrinfo * add)
{
	if (strcmp (orig->h_name, add->ai_canonname) != 0)
	{
		char ** curalias = orig->h_aliases;
		// so, sure, we waste a little memory. 
		if (curalias == NULL)
		{
			curalias = (char **) malloc (20 * sizeof (char *));
			curalias[0] = strdup(add->ai_canonname);
			curalias[1] = NULL;
		}
		else
		{
			// is this a duplicate entry?
			int duplicate = 0;
			int i = 0;
			while ((curalias[i] != NULL) && (duplicate == 0))
			{
				if (strcmp (orig->h_name, curalias[i]) == 0)
					duplicate = 1;
				i++;
			}
			if (duplicate == 0)
			{
				assert (i < 19); // we allocated only 20,
					// and need the 19th for the last NULL
				curalias[i] = strdup(orig->h_name);
				curalias[i+1] = NULL;
			}
		}
	}

	if (orig->h_addr_list == NULL)
	{
		// jep, only 30 
		orig->h_addr_list = (char **) malloc (30 * (sizeof (char*)));
		
		// only if type is AF_INET, else make mapping
		if (add->ai_family == AF_INET6)
		{
			//in_addr_t * toadd = (in_addr_t *) malloc (sizeof (in_addr_t));
			//*toadd = inet_addr("1.2.3.4");

			orig->h_addr_list[0] = (char*) getmap(&((struct sockaddr_in6 *)add->ai_addr)->sin6_addr);
		}
		else
			orig->h_addr_list[0] = (char*) (&(((struct sockaddr_in *)(add->ai_addr))->sin_addr));
		orig->h_addr_list[1] = NULL;
	}
	else
	{
		// TODO don't add duplicates
		int i = 0;
		while (orig->h_addr_list[i] != NULL) 
			i++;

		assert (i < 29); // we allocated only 30,
			// and need the 29th for the last NULL
			
		// only if type is AF_INET, else make mapping
		if (add->ai_family == AF_INET6)
		{
			//in_addr_t * toadd = (in_addr_t *) malloc (sizeof (in_addr_t));
			//*toadd = inet_addr("1.2.3.4");

			//orig->h_addr_list[i] = (char*) toadd;
			//orig->h_addr_list[i] = (char*) getmap((struct in6_addr *)add->ai_addr);
			orig->h_addr_list[i] = (char*) getmap(&((struct sockaddr_in6 *)add->ai_addr)->sin6_addr);
		}
		else
			orig->h_addr_list[i] = (char*) (&(((struct sockaddr_in *)(add->ai_addr))->sin_addr));
		orig->h_addr_list[i+1] = NULL;
	}
}

/**
 * Tries to copy all fcntl options from fd 'old' to fd 'new'
 */
void fcntlcpy (int old, int new)
{
	// TODO also copy the rest
	int value;
	value = fcntl (old, F_GETFL);
	fcntl (new, F_SETFL, value);
}

/**
 * connect()
 */
int connect(int sockfd, const struct sockaddr *serv_sockaddr, socklen_t addrlen)
{
	struct sockaddr_in * serv_sockaddr_in = (struct sockaddr_in *)serv_sockaddr;
	struct sockaddr_in6 * serv_sockaddr_in6 = NULL;

	if (TRACE_OVERRIDES)
		fprintf (LOGTO, "alt_connect: Using alternative connect() call\n");

	if ((serv_sockaddr->sa_family != AF_INET) 
		|| (! ismappedaddr (&serv_sockaddr_in->sin_addr)))
		return orig_connect(sockfd, serv_sockaddr, addrlen);

	if (TRACE_OVERRIDES)
		fprintf (LOGTO, "alt_connect: mapped ipv4 address\n");

	serv_sockaddr_in6 = (struct sockaddr_in6 *) malloc (sizeof(struct sockaddr_in6));
	//serv_addr_in6->sin6_len = SIN6_LEN;
	serv_sockaddr_in6->sin6_family = AF_INET6;
	serv_sockaddr_in6->sin6_port = serv_sockaddr_in->sin_port;
	serv_sockaddr_in6->sin6_flowinfo = 0; 
	serv_sockaddr_in6->sin6_addr = *getrevmap(&serv_sockaddr_in->sin_addr);

	//serv_sockaddr_in6 = (struct 

	assert (socketmirror[sockfd] != -1);

	if (socket_replaced[sockfd] == 0)
	{
		// copy fcntl settings to the ipv6 mirror
		fcntlcpy(sockfd, socketmirror[sockfd]);
		// replace the socket by its ipv6 mirror
		if (dup2 (socketmirror[sockfd], sockfd) == -1)
		{
			fprintf(LOGTO, "connect: couldn't dup2 socket\n");
			exit(0);
		}
		socket_replaced[sockfd]=1;
	}

	return orig_connect(sockfd, (struct sockaddr *)serv_sockaddr_in6, sizeof(*serv_sockaddr_in6));
}

int sendto (int  s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) 
{
	if (TRACE_OVERRIDES)
		fprintf(LOGTO, "Using alternative sendto() call\n");

	return orig_sendto(s, msg, len, flags, to, tolen);
}
int sendmsg (int s, const struct msghdr *msg, int flags)
{
	if (TRACE_OVERRIDES)
		fprintf(LOGTO, "Using alternative sendmsg() call\n");

	return orig_sendmsg(s, msg, flags);
}
int recvmsg (int s, struct msghdr *msg, int flags)
{
	int retval; 

	if (TRACE_OVERRIDES)
		fprintf(LOGTO, "Using alternative recvmsg() call\n");

	retval = orig_recvmsg(s, msg, flags);

	if ((retval != -1) && (bound_ipv6aware == 0))
	{
		/* convert to ipv4 if this is an ipv6 structure */
		// TODO how?? (test with the snapshot version of 'dig')
	}

	return retval;
}

/******* name resolver   ********/

/**
 * returns a structure of type hostent for the given host name. Name can be
 * either a hostname, an IPv4 address in dot notation, or an IPv6 address
 * in colon (and possibly dot) notation. 
 *
 * If name is an IPv4 address, no lookup is performed and gethostbyname() 
 * simpy copies name into the h_name field and its struct in_addr equivalent 
 * into the h_addr_list[0] field of the returned structure.
 *
 * If name is a name, this function behaves as described in sub-section
 * 3.3 of RFC 3338:
 *
 *   It returns a proper answer in response to the IPv4 application's
 *   request.
 *
 *   When an IPv4 application tries to resolve names via the resolver
 *   library (e.g. gethostbyname()), BIA intercept the function call and
 *   instead call the IPv6 equivalent functions (e.g. getnameinfo()) that
 *   will resolve both A and AAAA records.
 *
 *   If the AAAA record is available, it requests the address mapper to
 *   assign an IPv4 address corresponding to the IPv6 address, then
 *   creates the A record for the assigned IPv4 address, and returns the A
 *   record to the application.
 *
 * Note that we will return 'mapped' addresses 
 */
struct hostent * gethostbyname(const char *name)
{

	int error;
	struct addrinfo * result = NULL;
	struct hostent * retval = NULL;
	struct addrinfo * hints = NULL; 
	int ai_flags = AI_CANONNAME;

	if (TRACE_OVERRIDES)
		fprintf (LOGTO, "Using alternative gethostbyname() call\n");

	if (isipv4addr(name))
		return orig_gethostbyname(name);

	if (isipv6addr(name))
		//return orig_gethostbyname(name);
		ai_flags |= AI_NUMERICHOST;

	if (TRACE_OVERRIDES)
		fprintf (LOGTO, "Resolving name %s our way\n", name);

	hints = (struct addrinfo *) malloc (sizeof(struct addrinfo));

	hints->ai_flags = ai_flags;
	hints->ai_family = PF_UNSPEC;
	hints->ai_socktype = 0;
	hints->ai_protocol = 0;
	hints->ai_addrlen = 0;
	hints->ai_addr = NULL;
	hints->ai_canonname = NULL;
	hints->ai_next = NULL;

	if ((error = getaddrinfo(name, NULL, hints, &result)))
	{
		fprintf (stderr, "gethostbyname: couldn't run getaddrinfo: %s\n", gai_strerror(error));
		h_errno = NO_RECOVERY; // is this the right one?
		free (hints);
		return NULL;
	}
	
	retval = (struct hostent *) malloc (sizeof(struct hostent));
	retval->h_name = strdup(name);
	retval->h_addrtype = AF_INET; // gethostbyname only supports AF_INET
	retval->h_length = 4; // ipv4, remember?
	retval->h_aliases = NULL;   // both of these are allocated and
	retval->h_addr_list = NULL; // filled by addtohostent

	while (result != NULL)
	{
		/*
		if (result->ai_family == AF_INET6)
		{
			char * buffer = (char*) malloc (500 * sizeof(char));
			fprintf (LOGTO, "found ipv6 address: %s\n", 
				inet_ntop(AF_INET6, 
					&(((struct sockaddr_in6 *)(result->ai_addr))->sin6_addr), 
				buffer, 500));
		}
		*/
		addtohostent(retval, result);
		result = result->ai_next;
	}
	
	free (hints);

	return retval;
}

/**
 * bind() - not tested by me yet
 */

int bind(int sockfd, const struct sockaddr *serv_sockaddr, socklen_t addrlen)
{
	struct sockaddr_in * serv_sockaddr_in = (struct sockaddr_in *)serv_sockaddr;
	struct sockaddr_in6 * serv_sockaddr_in6 = NULL;
	char * buffer = (char*) malloc (500 * sizeof(char));

	if (TRACE_OVERRIDES)
		fprintf (LOGTO, "Using alternative bind() call\n");

	/* for the time being, we indeed consider 0.0.0.0 'mapped' 
	 * (to '::') 
	 */
	if (serv_sockaddr->sa_family != AF_INET) 
		return orig_bind(sockfd, serv_sockaddr, addrlen);

	bound_ipv6aware=0;

	if (! ismappedaddr (&serv_sockaddr_in->sin_addr))
		return orig_bind(sockfd, serv_sockaddr, addrlen);

	if (TRACE_OVERRIDES)
		fprintf (LOGTO, "Effectively using alternative bind() call ;)\n");

	serv_sockaddr_in6 = (struct sockaddr_in6 *) malloc (sizeof(struct sockaddr_in6));
	//serv_addr_in6->sin6_len = SIN6_LEN;
	serv_sockaddr_in6->sin6_family = AF_INET6;
	serv_sockaddr_in6->sin6_port = serv_sockaddr_in->sin_port;
	serv_sockaddr_in6->sin6_flowinfo = 0; // TODO find out what this is
	serv_sockaddr_in6->sin6_addr = *getrevmap(&serv_sockaddr_in->sin_addr);

	assert (socketmirror[sockfd] != -1);
	fprintf (LOGTO, "Binding to: socket %d, address %s\n", socketmirror[sockfd], inet_ntop(AF_INET6, &(serv_sockaddr_in6->sin6_addr), buffer, 500));
	free (buffer);

	if (socket_replaced[sockfd] == 0)
	{
		// copy fcntl settings to the ipv6 mirror
		fcntlcpy(sockfd, socketmirror[sockfd]);
		// replace the socket by its ipv6 mirror
		if (dup2 (socketmirror[sockfd], sockfd) == -1)
		{
			fprintf(LOGTO, "connect: couldn't dup2 socket\n");
			exit(0);
		}
		socket_replaced[sockfd]=1;
	}

	return orig_bind(sockfd, (struct sockaddr *)serv_sockaddr_in6, sizeof(*serv_sockaddr_in6));
}

void __attribute__ ((constructor)) my_init(void)
{
	void * handle = dlopen("/lib/libc.so.6", RTLD_LAZY);
	//struct in6_addr any6addr;
	//inet_pton(PF_INET6, "::", &any6addr);

	struct in6_addr * any6addrp = (struct in6_addr *) malloc (sizeof(struct in6_addr));
	inet_pton(PF_INET6, "::", any6addrp);
	mapping[0] = any6addrp;

	orig_socket =  dlsym(handle, "socket");
	orig_connect = dlsym(handle, "connect");
	orig_gethostbyname = dlsym(handle, "gethostbyname");
	orig_bind = dlsym(handle, "bind");

	// TODO
	orig_sendto = dlsym(handle, "sendto");
	orig_sendmsg = dlsym(handle, "sendmsg");
	orig_recvmsg = dlsym(handle, "recvmsg");

	/* TODO this is not the right way to 
	 * 'convert' binding to 'any'. But usable
	 * for the time being.
	 */
	//mapping[0] = (struct in6_addr *) malloc (sizeof (struct in6_addr));
	//inet_pton(AF_INET6, "::", mapping[0]);


	assert (mapping[0] != NULL);
}



#if WITHMAIN
#include "transtest.c"
#endif
