/* ans.c - Interface for text2atm and atm2text to ANS */

/* Written 1996-2000 by Werner Almesberger, EPFL-LRC/ICA */


/*
 * This stuff is a temporary hack to avoid using gethostbyname_nsap and such
 * without doing the "full upgrade" to getaddrinfo/getnameinfo. This also
 * serves as an exercise for me to get all the details right before I propose
 * a patch that would eventually end up in libc (and that should therefore be
 * as stable as possible).
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <netinet/in.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>

#include "atm.h"
#include "atmres.h"


#define MAX_ANSWER 2048
#define MAX_NAME   1024

#define MAX_LINE		2048	/* in /etc/e164_cc */
#define E164_CC_DEFAULT_LEN	   2
#define E164_CC_FILE		"/etc/e164_cc"

#define GET16(pos) (((pos)[0] << 8) | (pos)[1])


static int ans(const char *text,int wanted,void *result,int res_len)
{
    unsigned char answer[MAX_ANSWER];
    unsigned char name[MAX_NAME];
    unsigned char *pos,*data,*found;
    int answer_len,name_len,data_len,found_len;
    int questions,answers;

    found_len = 0; /* gcc wants it */
    if ((answer_len = res_search(text,C_IN,wanted,answer,MAX_ANSWER)) < 0)
	return TRY_OTHER;
    /*
     * Response header: id, flags, #queries, #answers, #authority,
     * #additional (all 16 bits)
     */
    pos = answer+12;
    if (answer[3] & 15) return TRY_OTHER; /* rcode != 0 */
    questions = GET16(answer+4);
    if (questions != 1) return TRY_OTHER; /* trouble ... */
    answers = GET16(answer+6);
    if (answers < 1) return TRY_OTHER;
    /*
     * Query: name, type (16), class (16)
     */
    if ((name_len = dn_expand(answer,answer+answer_len,pos,name,MAX_NAME)) < 0)
	return TRY_OTHER;
    pos += name_len;
    if (GET16(pos) != wanted || GET16(pos+2) != C_IN) return TRY_OTHER;
    pos += 4;
    /*
     * Iterate over answers until we find something we like, giving priority
     * to ATMA_AESA (until signaling is fixed to work with E.164 too)
     */
    found = NULL;
    while (answers--) {
	/*
	 * RR: name, type (16), class (16), TTL (32), resource_len (16),
	 * resource_data ...
	 */
	if ((name_len = dn_expand(answer,answer+answer_len,pos,name,MAX_NAME))
	  < 0) return TRY_OTHER;
	pos += name_len;
	data_len = GET16(pos+8);
	data = pos+10;
	pos = data+data_len;
	if (GET16(data-10) != wanted || GET16(data-8) != C_IN || !--data_len)
	    continue;
	switch (wanted) {
            case T_NSAP:
                data_len++;
                if (data_len != ATM_ESA_LEN) continue;
                memcpy(((struct sockaddr_atmsvc *) result)->
                  sas_addr.prv,data,ATM_ESA_LEN);
                return 0;
	    case T_ATMA:
		switch (*data++) {
		    case ATMA_AESA:
			if (data_len != ATM_ESA_LEN) continue;
			memcpy(((struct sockaddr_atmsvc *) result)->
			  sas_addr.prv,data,ATM_ESA_LEN);
			return 0;
		    case ATMA_E164:
			if (data_len > ATM_E164_LEN) continue;
			if (!found) {
			    found = data;
			    found_len = data_len;
			}
			break;
		    default:
			continue;
		}
	    case T_PTR:
		    if (dn_expand(answer,answer+answer_len,data,result,
		      res_len) < 0) return FATAL;
		    return 0;
		default:
		    continue;
	}
    }
    if (!found) return TRY_OTHER;
    memcpy(((struct sockaddr_atmsvc *) result)->sas_addr.pub,found,
      found_len);
    ((struct sockaddr_atmsvc *) result)->sas_addr.pub[found_len] = 0;
    return 0;
}


int ans_byname(const char *text,struct sockaddr_atmsvc *addr,int length,
  int flags)
{
    if (!(flags & T2A_SVC) || length != sizeof(*addr)) return TRY_OTHER; 
    memset(addr,0,sizeof(*addr));
    addr->sas_family = AF_ATMSVC;
    if (!ans(text,T_ATMA,addr,length)) return 0;
    return ans(text,T_NSAP,addr,length);
}


static int encode_nsap(char *buf,const unsigned char *addr)
{
    static int fmt_dcc[] = { 2,12,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      4,2,0 };
    static int fmt_e164[] = { 2,12,1,1,1,1,1,1,1,1,16,2,0 };
    int *fmt;
    int pos,i,j;

    switch (*addr) {
	case ATM_AFI_DCC:
	case ATM_AFI_ICD:
	case ATM_AFI_LOCAL:
	case ATM_AFI_DCC_GROUP:
	case ATM_AFI_ICD_GROUP:
	case ATM_AFI_LOCAL_GROUP:
	    fmt = fmt_dcc;
	    break;
	case ATM_AFI_E164:
	case ATM_AFI_E164_GROUP:
	    fmt = fmt_e164;
	    break;
	default:
	    return TRY_OTHER;
    }
    pos = 2*ATM_ESA_LEN;
    for (i = 0; fmt[i]; i++) {
	pos -= fmt[i];
	for (j = 0; j < fmt[i]; j++)
	    sprintf(buf++,"%x",
	      (addr[(pos+j) >> 1] >> 4*(1-((pos+j) & 1))) & 0xf);
	*buf++ = '.';
    }
    strcpy(buf,"AESA.ATMA.INT.");
    return 0;
}


static int encode_nsap_new(char *buf,const unsigned char *addr)
{
    int i;
    int digit;

    for (i = 20; i; ) {
        i--;
        digit = addr[i] & 0x0F;
        *(buf++) = digit + (digit >= 10 ? '7' : '0');
        *(buf++) = '.';
        digit = ((unsigned char) (addr[i])) >> 4;
        *(buf++) = digit + (digit >= 10 ? '7' : '0');
        *(buf++) = '.';
    }
    strcpy (buf, "NSAP.INT.");
    return 0;
}


static int cc_len(int p0,int p1)
{
    static char *cc_table = NULL;
    FILE *file;
    char buffer[MAX_LINE];
    char *here;
    int cc;

    if (!cc_table) {
	if (!(cc_table = malloc(100))) {
	    perror("malloc");
	    return E164_CC_DEFAULT_LEN;
	}
	memset(cc_table,E164_CC_DEFAULT_LEN,100);
	if (!(file = fopen(E164_CC_FILE,"r")))
	    perror(E164_CC_FILE);
	else {
	    while (fgets(buffer,MAX_LINE,file)) {
		here = strchr(buffer,'#');
		if (here) *here = 0;
		if (sscanf(buffer,"%d",&cc) == 1) {
		    if (cc < 10) cc_table[cc] = 1;
		    else if (cc < 100) cc_table[cc] = 2;
			else cc_table[cc/10] = 3;
		}
	    }
	    fclose(file);
	}
    }
    if (cc_table[p0] == 1) return 1;
    return cc_table[p0*10+p1];
}


static int encode_e164(char *buf,const char *addr)
{
    const char *prefix,*here;

    prefix = addr+cc_len(addr[0]-48,addr[1]-48);
    here = strchr(addr,0);
    while (here > prefix) {
	*buf++ = *--here;
	*buf++ = '.';
    }
    while (here > addr) *buf++ = *addr++;
    strcpy(buf,".E164.ATMA.INT.");
    return 0;
}


int ans_byaddr(char *buffer,int length,const struct sockaddr_atmsvc *addr,
  int flags)
{
    char tmp[MAX_NAME]; /* could be smaller ... */
    int res;

    if (addr->sas_addr.prv) {
        res = encode_nsap(tmp,addr->sas_addr.prv);
        if (!res && !ans(tmp,T_PTR,buffer,length)) return 0;
	res = encode_nsap_new(tmp,addr->sas_addr.prv);
        if (res < 0) return res;
	return ans(tmp,T_PTR,buffer,length);
    } else {
        res = encode_e164(tmp,addr->sas_addr.pub);
        if (res < 0) return res;
        return ans(tmp,T_PTR,buffer,length);
    }
}