/*
    zasyncdns.c - asynchronous DNS queries
    Copyright (C) 2011-2022 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <zasyncdns.h>
#include <zdebug.h>
#include <zerror.h>
#include <zerror.h>
#include <eprintf.h>
#include <zselect.h>
#include <zstr.h>
#include <zthread.h>


#ifdef Z_HAVE_NETDB_H
#include <netdb.h>
#endif

#ifdef Z_HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#ifdef Z_HAVE_WS2TCPIP_H
#include <ws2tcpip.h>
#endif


#ifdef Z_HAVE_WIN32API_WS2TCPIP_H
#include <win32api/ws2tcpip.h>
#endif

static GPtrArray *gadns_list = NULL;
gpointer zasyncdns_thread_func(gpointer arg);

struct zasyncdns *zasyncdns_init(void){
    struct zasyncdns *adns;

	if (gadns_list == NULL) gadns_list = g_ptr_array_new();	

    adns = g_new0(struct zasyncdns, 1);
	g_ptr_array_add(gadns_list, adns);
    return adns;
}

void zasyncdns_free(struct zasyncdns *adns){
	if (adns == NULL) return;

	g_ptr_array_remove(gadns_list, adns);

    if (adns->thread != NULL){
		//dbg("zasyncdns_free: adns=%p\n", adns);
        g_thread_join(adns->thread);
        adns->thread = NULL;
    }
    g_free(adns->hostname);
    g_free(adns);
}

struct zasyncdns *zasyncdns_getaddrinfo(
        struct zasyncdns *adns, 
        struct zselect *zsel, 
        void (*callback)(struct zasyncdns *adns, int n, int *family, int *socktype, int *protocol, int *addrlen, union zsockaddr *addr, char *errorstr), 
        char *hostname,
        int socktype,
        void *arg)
{

    if (adns->thread != NULL){
        g_thread_join(adns->thread);
        adns->thread = NULL;
    }

	//dbg("zasyncdns_getaddrinfo('%s')\n", hostname);

    adns->zsel = zsel;
    adns->hostname = g_strdup(hostname);
	adns->callback = callback;
    adns->arg = arg;
    adns->socktype = socktype;
    //adns->thread = g_thread_create(zasyncdns_thread_func, adns, TRUE, NULL);
    adns->thread = g_thread_try_new("zasyncdns", zasyncdns_thread_func, adns, NULL);
	return adns;
}

gpointer zasyncdns_thread_func(gpointer arg){
#ifdef Z_HAVE_GETADDRINFO
    struct zasyncdns *adns;
    int ret;
    struct addrinfo *ai, *result, src;
    GString *msg;
    
    adns = (struct zasyncdns *)arg;
	zg_thread_set_name("Libzia zasyncdns");

    memset(&src, 0, sizeof(src));
	src.ai_family = AF_INET6;
	
    ret = getaddrinfo(adns->hostname, NULL, NULL/*&src*/, &result);
    if (ret){
		char *e = g_strdup(gai_strerror(ret));
        zselect_msg_send(adns->zsel, "ZASYNCDNS;E;%p;%s", adns, z_1250_to_8859_2(e));
		g_free(e);
        dbg("zasyncdns: error\n");
        return NULL;
    }

    msg = g_string_new("ZASYNCDNS;A");
	g_string_append_printf(msg, ";%p", adns);
    for (ai = result; ai != NULL; ai = ai->ai_next){
        zg_string_eprintfa("e", msg, ";%d;%d;%d;%d;",
            ai->ai_family, ai->ai_socktype, ai->ai_protocol, ai->ai_addrlen);
        zg_string_eprintfa("b", msg, "%y", ai->ai_addr, ai->ai_addrlen);
    }
        
    g_string_append(msg, "\n");
    //dbg("zasyncdns: %s -> %s", adns->hostname, msg->str);
    zselect_msg_send_raw(adns->zsel, msg->str);
    freeaddrinfo(result);
    g_string_free(msg, TRUE); 
	return NULL;
#else
    struct zasyncdns *adns;
    struct hostent *he;
    GString *msg;
    struct sockaddr_in sin;

    adns = (struct zasyncdns *)arg;
    he = gethostbyname(adns->hostname);
    if (!he){
        zselect_msg_send(adns->zsel, "ZASYNCDNS;E;0xp;%s", adns, z_host_error());
        return NULL;
    }

    memset(&sin, 0, sizeof(struct sockaddr_in));
	memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length);
    sin.sin_family = AF_INET;

    msg = g_string_new("ZASYNCDNS;A");
	g_string_append_printf(msg, ";%p", adns);
    zg_string_eprintfa("e", msg, ";%d;%d;%d;%d;", AF_INET, SOCK_STREAM, IPPROTO_TCP, sizeof(struct sockaddr_in));
    zg_string_eprintfa("b", msg, "%y", &sin, sizeof(struct sockaddr_in));
    g_string_append(msg, "\n");
    //dbg("zasyncdns: '%s'", msg->str);
    zselect_msg_send_raw(adns->zsel, msg->str);
    g_string_free(msg, TRUE); 
    return NULL;
#endif
}


void zasyncdns_read_handler(int n, char *items[]){
	struct zasyncdns *adns;
	int i, len;
	int *family, *socktype, *protocol, *addrlen;
	union zsockaddr *addr; 

    if (n < 4) return; // malformed 

	adns = (struct zasyncdns *)z_strtop(items[2]);
   // dbg("zasyncdns_read_handler(%p)\n", adns);


	for (i = 0; i < (int)gadns_list->len; i++){
		struct zasyncdns *a = (struct zasyncdns *)g_ptr_array_index(gadns_list, i);
		if (a == adns) goto found;
	}
	dbg("zasyncdns_read_handler - unknown adns=%p\n", adns);
	return;
found:;
	g_thread_join(adns->thread);
	adns->thread = NULL;

    if (n == 4){
        adns->callback(adns, 0, NULL, NULL, NULL, NULL, NULL, items[3]);
        return;
    }

	len = (n - 3) / 5;
	family = g_new(int, len);
	socktype = g_new(int, len);
	protocol = g_new(int, len);
	addrlen = g_new(int, len);
	addr = g_new(union zsockaddr, len);

	for (i = 0; i < len; i++){
		family[i] = atoi(items[5 * i + 3]);
		socktype[i] = atoi(items[5 * i + 4]);
		protocol[i] = atoi(items[5 * i + 5]);
		addrlen[i] = atoi(items[5 * i + 6]);
		//z_base64dec((char *)(addr + i), addrlen[i], NULL, items[5 * i + 7]);
		z_hexadec((char *)(addr + i), addrlen[i], NULL, items[5 * i + 7]);
	}

    adns->callback(adns, len, family, socktype, protocol, addrlen, addr, NULL);
	g_free(family);
	g_free(socktype);
	g_free(protocol);
	g_free(addrlen);
	g_free(addr);
}
