/*++
/* NAME
/*	inet_proto 3
/* SUMMARY
/*	convert protocol names to assorted constants
/* SYNOPSIS
/*	#include <inet_proto.h>
/*
/*	typedef struct {
/* .in +4
/*		unsigned ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */
/*		unsigned *ai_family_list; /* PF_INET and/or PF_INET6 */
/*		unsigned *dns_atype_list;/* TAAAA and/or TA */
/*		unsigned char *sa_family_list;/* AF_INET6 and/or AF_INET */
/* .in -4
/*	} INET_PROTO_INFO;
/*
/*	const INET_PROTO_INFO *inet_proto_init(context, protocols)
/*
/*	const INET_PROTO_INFO *inet_proto_info()
/* DESCRIPTION
/*	inet_proto_init() converts a string with protocol names
/*	into null-terminated lists of appropriate constants used
/*	by Postfix library routines.  The idea is that one should
/*	be able to configure an MTA for IPv4 only, without having
/*	to recompile code (what a concept).
/*
/*	Unfortunately, some compilers won't link initialized data
/*	without a function call into the same source module, so
/*	we invoke inet_proto_info() in order to access the result
/*	from inet_proto_init() from within library routines.
/*	inet_proto_info() also conveniently initializes the data
/*	to built-in defaults.
/*
/*	Arguments:
/* .IP context
/*	Typically, a configuration parameter name.
/* .IP protocols
/*	Null-terminated string with protocol names separated by
/*	whitespace and/or commas:
/* .RS
/* .IP INET_PROTO_NAME_ALL
/*	Enable all available IP protocols.
/* .IP INET_PROTO_NAME_IPV4
/*	Enable IP version 4 support.
/* .IP INET_PROTO_NAME_IPV6
/*	Enable IP version 6 support.
/* .RS
/* .PP
/*	Results:
/* .IP ai_family
/*	Only one of PF_UNSPEC, PF_INET, or PF_INET6. This can be
/*	used as input for the getaddrinfo() and getnameinfo()
/*	routines.
/* .IP ai_family_list
/*	One or more of PF_INET or PF_INET6. This can be used as
/*	input for the inet_addr_local() routine.
/* .IP dns_atype_list
/*	One or more of T_AAAA or T_A. This can be used as input for
/*	the dns_lookup_v() and dns_lookup_l() routines.
/* .IP sa_family_list
/*	One or more of AF_INET6 or AF_INET. This can be used as an
/*	output filter for the results from the getaddrinfo() and
/*	getnameinfo() routines.
/* SEE ALSO
/*	msg(3) diagnostics interface
/* DIAGNOSTICS
/*	This module will warn and turn off support for any protocol
/*	that is requested but unavailable.
/*
/*	Fatal errors: memory allocation problem.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#ifdef RESOLVE_H_NEEDS_STDIO_H
#include <stdio.h>
#endif
#include <resolv.h>
#include <stdarg.h>
#include <unistd.h>

/* Utility library. */

#include <mymalloc.h>
#include <msg.h>
#include <myaddrinfo.h>
#include <name_mask.h>
#include <inet_proto.h>

 /*
  * Application-specific.
  */

 /*
  * Run-time initialization, so we can work around LINUX where IPv6 falls
  * flat on its face because it is not turned on in the kernel.
  */
INET_PROTO_INFO *inet_proto_table = 0;

 /*
  * Infrastructure: lookup table with the protocol names that we support.
  */
#define INET_PROTO_MASK_IPV4	(1<<0)
#define INET_PROTO_MASK_IPV6	(1<<1)

static const NAME_MASK proto_table[] = {
#ifdef HAS_IPV6
    INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4 | INET_PROTO_MASK_IPV6,
    INET_PROTO_NAME_IPV6, INET_PROTO_MASK_IPV6,
#else
    INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4,
#endif
    INET_PROTO_NAME_IPV4, INET_PROTO_MASK_IPV4,
    0,
};

/* make_uchar_vector - create and initialize uchar vector */

static unsigned char *make_uchar_vector(int len,...)
{
    const char *myname = "make_uchar_vector";
    va_list ap;
    int     count;
    unsigned char *vp;

    va_start(ap, len);
    if (len <= 0)
	msg_panic("%s: bad vector length: %d", myname, len);
    vp = (unsigned char *) mymalloc(sizeof(*vp) * len);
    for (count = 0; count < len; count++)
	vp[count] = va_arg(ap, unsigned);
    va_end(ap);
    return (vp);
}

/* make_unsigned_vector - create and initialize integer vector */

static unsigned *make_unsigned_vector(int len,...)
{
    const char *myname = "make_unsigned_vector";
    va_list ap;
    int     count;
    unsigned *vp;

    va_start(ap, len);
    if (len <= 0)
	msg_panic("%s: bad vector length: %d", myname, len);
    vp = (unsigned *) mymalloc(sizeof(*vp) * len);
    for (count = 0; count < len; count++)
	vp[count] = va_arg(ap, unsigned);
    va_end(ap);
    return (vp);
}

/* inet_proto_free - destroy data */

static void inet_proto_free(INET_PROTO_INFO *pf)
{
    myfree((void *) pf->ai_family_list);
    myfree((void *) pf->dns_atype_list);
    myfree((void *) pf->sa_family_list);
    myfree((void *) pf);
}

/* inet_proto_init - convert protocol names to library inputs */

const INET_PROTO_INFO *inet_proto_init(const char *context, const char *protocols)
{
    const char *myname = "inet_proto";
    INET_PROTO_INFO *pf;
    int     inet_proto_mask;
    int     sock;

    /*
     * Avoid run-time errors when all network protocols are disabled. We
     * can't look up interface information, and we can't convert explicit
     * names or addresses.
     */
    inet_proto_mask = name_mask(context, proto_table, protocols);
#ifdef HAS_IPV6
    if (inet_proto_mask & INET_PROTO_MASK_IPV6) {
	if ((sock = socket(PF_INET6, SOCK_STREAM, 0)) >= 0) {
	    close(sock);
	} else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
	    msg_warn("%s: disabling IPv6 name/address support: %m", context);
	    inet_proto_mask &= ~INET_PROTO_MASK_IPV6;
	} else {
	    msg_fatal("socket: %m");
	}
    }
#endif
    if (inet_proto_mask & INET_PROTO_MASK_IPV4) {
	if ((sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0) {
	    close(sock);
	} else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
	    msg_warn("%s: disabling IPv4 name/address support: %m", context);
	    inet_proto_mask &= ~INET_PROTO_MASK_IPV4;
	} else {
	    msg_fatal("socket: %m");
	}
    }

    /*
     * Store address family etc. info as null-terminated vectors. If that
     * breaks because we must be able to store nulls, we'll deal with the
     * additional complexity.
     * 
     * XXX Use compile-time initialized data templates instead of building the
     * reply on the fly.
     */
    switch (inet_proto_mask) {
#ifdef HAS_IPV6
    case INET_PROTO_MASK_IPV6:
	pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
	pf->ai_family = PF_INET6;
	pf->ai_family_list = make_unsigned_vector(2, PF_INET6, 0);
	pf->dns_atype_list = make_unsigned_vector(2, T_AAAA, 0);
	pf->sa_family_list = make_uchar_vector(2, AF_INET6, 0);
	break;
    case (INET_PROTO_MASK_IPV6 | INET_PROTO_MASK_IPV4):
	pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
	pf->ai_family = PF_UNSPEC;
	pf->ai_family_list = make_unsigned_vector(3, PF_INET, PF_INET6, 0);
	pf->dns_atype_list = make_unsigned_vector(3, T_A, T_AAAA, 0);
	pf->sa_family_list = make_uchar_vector(3, AF_INET, AF_INET6, 0);
	break;
#endif
    case INET_PROTO_MASK_IPV4:
	pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
	pf->ai_family = PF_INET;
	pf->ai_family_list = make_unsigned_vector(2, PF_INET, 0);
	pf->dns_atype_list = make_unsigned_vector(2, T_A, 0);
	pf->sa_family_list = make_uchar_vector(2, AF_INET, 0);
	break;
    case 0:
	pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
	pf->ai_family = PF_UNSPEC;
	pf->ai_family_list = make_unsigned_vector(1, 0);
	pf->dns_atype_list = make_unsigned_vector(1, 0);
	pf->sa_family_list = make_uchar_vector(1, 0);
	break;
    default:
	msg_panic("%s: bad inet_proto_mask 0x%x", myname, inet_proto_mask);
    }
    if (inet_proto_table)
	inet_proto_free(inet_proto_table);
    return (inet_proto_table = pf);
}

#ifdef TEST

 /*
  * Small driver for unit tests.
  */

static char *print_unsigned_vector(VSTRING *buf, unsigned *vector)
{
    unsigned *p;

    VSTRING_RESET(buf);
    for (p = vector; *p; p++) {
	vstring_sprintf_append(buf, "%u", *p);
	if (p[1])
	    VSTRING_ADDCH(buf, ' ');
    }
    VSTRING_TERMINATE(buf);
    return (vstring_str(buf));
}

static char *print_uchar_vector(VSTRING *buf, unsigned char *vector)
{
    unsigned char *p;

    VSTRING_RESET(buf);
    for (p = vector; *p; p++) {
	vstring_sprintf_append(buf, "%u", *p);
	if (p[1])
	    VSTRING_ADDCH(buf, ' ');
    }
    VSTRING_TERMINATE(buf);
    return (vstring_str(buf));
}

int     main(int argc, char **argv)
{
    const char *myname = argv[0];
    INET_PROTO_INFO *pf;
    VSTRING *buf;

    if (argc < 2)
	msg_fatal("usage: %s protocol(s)...", myname);

    buf = vstring_alloc(10);
    while (*++argv) {
	msg_info("=== %s ===", *argv);
	inet_proto_init(myname, *argv);
	pf = inet_proto_table;
	msg_info("ai_family = %u", pf->ai_family);
	msg_info("ai_family_list = %s",
		 print_unsigned_vector(buf, pf->ai_family_list));
	msg_info("dns_atype_list = %s",
		 print_unsigned_vector(buf, pf->dns_atype_list));
	msg_info("sa_family_list = %s",
		 print_uchar_vector(buf, pf->sa_family_list));
    }
    vstring_free(buf);
    return (0);
}

#endif
