
/* joe's pet getaddrinfo tester */

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>

#define HINT(c) (opts && strchr(opts, c) != NULL)

#define PRINT(s) do { if (s) printf("\"%s\"", s); else printf("NULL"); } while (0)

int main(int argc, char **argv)
{
    struct addrinfo hints = {0}, *res;
    int ret;
    char *opts = NULL;
    const char *hostname, *service, *family;

    if (argc == 1) {
        printf("Usage: gai [-hints] hostname [service] [family]\n"
               " hints: a=AI_ADDRCONFIG, c=AI_CANONNAME, n=AI_NUMERICHOST, p=AI_PASSIVE\n"
               " family: inet6, inet4, unspec (default)\n");
        return 1;
    }

    if (argv[1][0] == '-') {
        opts = (++argv)[0] + 1;
        argc--;
    }

    hints.ai_socktype = SOCK_STREAM;

    hostname = argc > 1 && strcasecmp(argv[1], "null") ? argv[1] : NULL;
    service = argc > 2 && strcasecmp(argv[2], "null") ? argv[2] : NULL;

    family = argc > 3 ? argv[3] : "unspec";

    if (strcasecmp(family, "inet6") == 0) {
        hints.ai_family = AF_INET6;
        family = "AF_INET6";
    } else if (strcasecmp(family, "inet") == 0 ||
               strcasecmp(family, "inet4") == 0) {
        family = "AF_INET";
        hints.ai_family = AF_INET;
    } else {
        hints.ai_family = AF_UNSPEC;
        family = "AF_UNSPEC";
    }

    printf("getaddrinfo(");
    PRINT(hostname);
    printf(", ");
    PRINT(service);
    printf(", {.family=%s, .hints=0", family ? family : "AF_UNSPEC");

    if (HINT('a')) {
#ifdef AI_ADDRCONFIG
        hints.ai_flags = AI_ADDRCONFIG;
        printf("|AI_ADDRCONFIG");
#else
        printf("[AI_ADDRCONFIG undefined]");
#endif
    }
#ifdef AI_CANONNAME
    if (HINT('c')) {
        hints.ai_flags |= AI_CANONNAME;
        printf("|AI_CANONNAME");
    }
#endif
#ifdef AI_NUMERICHOST
    if (HINT('n')) {
        hints.ai_flags |= AI_NUMERICHOST;
        printf("|AI_NUMERICHOST");
    }
#endif
#ifdef AI_PASSIVE
    if (HINT('p')) {
        hints.ai_flags |= AI_PASSIVE;
        printf("|AI_PASSIVE");
    }
#endif
    printf("})");

    ret = getaddrinfo(hostname, service, &hints, &res);
#ifdef AI_ADDRCONFIG
    if (HINT('a') && ret == EAI_BADFLAGS) {
        printf("=> lookup failed with EAI_BADFLAGS, retrying without AI_ADDRCONFIG...\n");
        hints.ai_flags &= ~AI_ADDRCONFIG;
        ret = getaddrinfo(argv[1], NULL, &hints, &res);
    }
#endif
    if (ret) {
	printf(" = -1:\nerror: %s\n", gai_strerror(ret));
    }
    else {
	char buf[256];
        
        puts(" = 0:");

	for (; res; res = res->ai_next) {
            
#ifdef AF_INET6
            printf(" family=%2d, proto=%2d ", res->ai_family, res->ai_protocol);
	    if (res->ai_family == AF_INET6) {
		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)res->ai_addr;
		printf("inet6: addr=%s, port=%d, flowinfo=%d", 
                       inet_ntop(AF_INET6, &in6->sin6_addr, buf, sizeof buf),
                       ntohs(in6->sin6_port), in6->sin6_flowinfo);
	    } else
#endif
            if (res->ai_family == AF_INET) {
		struct sockaddr_in *in = (struct sockaddr_in *)res->ai_addr;
		printf("inet4: addr=%s, port=%d", 
                       inet_ntop(AF_INET, &in->sin_addr, buf, sizeof buf),
                       ntohs(in->sin_port));
            } else if (res->ai_family == AF_UNIX) {
                struct sockaddr_un *un = (struct sockaddr_un *)res->ai_addr;
                printf("unix: path=%s", un->sun_path);
	    } else {
                printf("unknown");
            }
            if (res->ai_canonname != NULL)
                printf(", canon=%s", res->ai_canonname);
            puts("");
	}
    }    

    return 0;
}

