/* Domain header conversion routines
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Additional support for the Domain Name Server
 * by Johan. K. Reinalda, WG7J,
 * based on previous work by Gerard v.d. Grinten, PA0GRI
 */
#include "global.h"
#include "mbuf.h"
#include "domain.h"
  
static int dn_expand __ARGS((char *msg,char *eom,char *compressed,char *full,
int fullen));
static char *getq __ARGS((struct rr **rrpp,char *msg,char *cp));
static char *ntohrr __ARGS((struct rr **rrpp,char *msg,char *cp));
static char *dn_compress __ARGS((char *cp,char *name));
static char *htonrr __ARGS((struct rr *rr,char *buffer));
  
int
ntohdomain(dhdr,bpp)
register struct dhdr *dhdr;
struct mbuf **bpp;
{
    int16 tmp,len;
    register int16 i;
    char *msg,*cp;
    struct rr **rrpp;
  
    len = len_p(*bpp);
    msg = mallocw(len);
    pullup(bpp,msg,len);
    memset((char *)dhdr,0,sizeof(*dhdr));
  
    dhdr->id = get16(&msg[0]);
    tmp = get16(&msg[2]);
    if(tmp & 0x8000)
        dhdr->qr = 1;
    dhdr->opcode = (tmp >> 11) & 0xf;
    if(tmp & 0x0400)
        dhdr->aa = 1;
    if(tmp & 0x0200)
        dhdr->tc = 1;
    if(tmp & 0x0100)
        dhdr->rd = 1;
    if(tmp & 0x0080)
        dhdr->ra = 1;
    dhdr->rcode = tmp & 0xf;
    dhdr->qdcount = get16(&msg[4]);
    dhdr->ancount = get16(&msg[6]);
    dhdr->nscount = get16(&msg[8]);
    dhdr->arcount = get16(&msg[10]);
  
    /* Now parse the variable length sections */
    cp = &msg[12];
  
    /* Question section */
    rrpp = &dhdr->questions;
    for(i=0;i<dhdr->qdcount;i++){
        if((cp = getq(rrpp,msg,cp)) == NULLCHAR){
            free(msg);
            return -1;
        }
        (*rrpp)->source = RR_QUESTION;
        rrpp = &(*rrpp)->next;
    }
    *rrpp = NULLRR;
  
    /* Answer section */
    rrpp = &dhdr->answers;
    for(i=0;i<dhdr->ancount;i++){
        if((cp = ntohrr(rrpp,msg,cp)) == NULLCHAR){
            free(msg);
            return -1;
        }
        (*rrpp)->source = RR_ANSWER;
        rrpp = &(*rrpp)->next;
    }
    *rrpp = NULLRR;
  
    /* Name server (authority) section */
    rrpp = &dhdr->authority;
    for(i=0;i<dhdr->nscount;i++){
        if((cp = ntohrr(rrpp,msg,cp)) == NULLCHAR){
            free(msg);
            return -1;
        }
        (*rrpp)->source = RR_AUTHORITY;
        rrpp = &(*rrpp)->next;
    }
    *rrpp = NULLRR;
  
    /* Additional section */
    rrpp = &dhdr->additional;
    for(i=0;i<dhdr->arcount;i++){
        if((cp = ntohrr(rrpp,msg,cp)) == NULLCHAR){
            free(msg);
            return -1;
        }
        (*rrpp)->source = RR_ADDITIONAL;
        rrpp = &(*rrpp)->next;
    }
    *rrpp = NULLRR;
    free(msg);
    return 0;
}
static char *
getq(rrpp,msg,cp)
struct rr **rrpp;
char *msg;
char *cp;
{
    register struct rr *rrp;
    int len;
    char *name;
  
    *rrpp = rrp = (struct rr *)callocw(1,sizeof(struct rr));
    name = mallocw(512);
    len = dn_expand(msg,NULLCHAR,cp,name,512);
    if(len == -1){
        free(name);
        return NULLCHAR;
    }
    cp += len;
    rrp->name = strdup(name);
    rrp->type = get16(cp);
    cp += 2;
    rrp->class = get16(cp);
    cp += 2;
    rrp->ttl = 0;
    rrp->rdlength = 0;
    free(name);
    return cp;
}
/* Read a resource record from a domain message into a host structure */
static char *
ntohrr(rrpp,msg,cp)
struct rr **rrpp; /* Where to allocate resource record structure */
char *msg;      /* Pointer to beginning of domain message */
char *cp;       /* Pointer to start of encoded RR record */
{
    register struct rr *rrp;
    int len;
    char *name;
  
    *rrpp = rrp = (struct rr *)callocw(1,sizeof(struct rr));
    name = mallocw(512);
    if((len = dn_expand(msg,NULLCHAR,cp,name,512)) == -1){
        free(name);
        return NULLCHAR;
    }
    cp += len;
    rrp->name = strdup(name);
    rrp->type = get16(cp);
    cp += 2;
    rrp->class = get16(cp);
    cp+= 2;
    rrp->ttl = get32(cp);
    cp += 4;
    rrp->rdlength = get16(cp);
    cp += 2;
    switch(rrp->type){
        case TYPE_A:
        /* Just read the address directly into the structure */
            rrp->rdata.addr = get32(cp);
            cp += 4;
            break;
        case TYPE_CNAME:
        case TYPE_MB:
        case TYPE_MG:
        case TYPE_MR:
        case TYPE_NS:
        case TYPE_PTR:
        /* These types all consist of a single domain name;
         * convert it to ascii format
         */
            len = dn_expand(msg,NULLCHAR,cp,name,512);
            if(len == -1){
                free(name);
                return NULLCHAR;
            }
            rrp->rdata.name = strdup(name);
            rrp->rdlength = strlen(name);
            cp += len;
            break;
        case TYPE_HINFO:
            len = *cp++;
            rrp->rdata.hinfo.cpu = mallocw(len+1);
            memcpy( rrp->rdata.hinfo.cpu, cp, len );
            rrp->rdata.hinfo.cpu[len] = '\0';
            cp += len;
  
            len = *cp++;
            rrp->rdata.hinfo.os = mallocw(len+1);
            memcpy( rrp->rdata.hinfo.os, cp, len );
            rrp->rdata.hinfo.os[len] = '\0';
            cp += len;
            break;
        case TYPE_MX:
            rrp->rdata.mx.pref = get16(cp);
            cp += 2;
        /* Get domain name of exchanger */
            len = dn_expand(msg,NULLCHAR,cp,name,512);
            if(len == -1){
                free(name);
                return NULLCHAR;
            }
            rrp->rdata.mx.exch = strdup(name);
            cp += len;
            break;
        case TYPE_SOA:
        /* Get domain name of name server */
            len = dn_expand(msg,NULLCHAR,cp,name,512);
            if(len == -1){
                free(name);
                return NULLCHAR;
            }
            rrp->rdata.soa.mname = strdup(name);
            cp += len;
  
        /* Get domain name of responsible person */
            len = dn_expand(msg,NULLCHAR,cp,name,512);
            if(len == -1){
                free(name);
                return NULLCHAR;
            }
            rrp->rdata.soa.rname = strdup(name);
            cp += len;
  
            rrp->rdata.soa.serial = get32(cp);
            cp += 4;
            rrp->rdata.soa.refresh = get32(cp);
            cp += 4;
            rrp->rdata.soa.retry = get32(cp);
            cp += 4;
            rrp->rdata.soa.expire = get32(cp);
            cp += 4;
            rrp->rdata.soa.minimum = get32(cp);
            cp += 4;
            break;
        case TYPE_TXT:
        /* Just stash */
            rrp->rdata.data = mallocw(rrp->rdlength);
            memcpy(rrp->rdata.data,cp,rrp->rdlength);
            cp += rrp->rdlength;
            break;
        default:
        /* Ignore */
            cp += rrp->rdlength;
            break;
    }
    free(name);
    return cp;
}
  
/* Convert a compressed domain name to the human-readable form */
static int
dn_expand(msg,eom,compressed,full,fullen)
char *msg;              /* Complete domain message */
char *eom;
char *compressed;       /* Pointer to compressed name */
char *full;             /* Pointer to result buffer */
int fullen;             /* Length of same */
{
    unsigned int slen;      /* Length of current segment */
    register char *cp;
    int clen = 0;   /* Total length of compressed name */
    int indirect = 0;       /* Set if indirection encountered */
    int nseg = 0;           /* Total number of segments in name */
  
    cp = compressed;
    for(;;){
        slen = uchar(*cp++);    /* Length of this segment */
        if(!indirect)
            clen++;
        if((slen & 0xc0) == 0xc0){
            if(!indirect)
                clen++;
            indirect = 1;
            /* Follow indirection */
            cp = &msg[((slen & 0x3f)<<8) + uchar(*cp)];
            slen = uchar(*cp++);
        }
        if(slen == 0)   /* zero length == all done */
            break;
        fullen -= slen + 1;
        if(fullen < 0)
            return -1;
        if(!indirect)
            clen += slen;
        while(slen-- != 0)
            *full++ = *cp++;
        *full++ = '.';
        nseg++;
    }
    if(nseg == 0){
        /* Root name; represent as single dot */
        *full++ = '.';
        fullen--;
    }
    *full++ = '\0';
    fullen--;
    return clen;    /* Length of compressed message */
}
  
  
  
#ifdef DOMAINSERVER
  
/* Most of this code is based on the DNS server in PA0GRI's 910828
 * Ported to the current NOS code by Johan. K. Reinalda, WG7J
 * for version and bug/feature info, see domain.c
 */
  
static char *
dn_compress(cp,name)
char *cp, *name;
{
    int len,dlen;
    char *cp1;
    dlen = strlen(name);
    for(;;){
        /* Look for next dot */
        cp1 = strchr(name,'.');
        if(cp1 != NULLCHAR)
            len = (int)(cp1-name); /* More to come */
        else
            len = dlen;     /* Last component */
        *cp++ = len;            /* Write length of component */
        if(len == 0)
            return cp;
        /* Copy component up to (but not including) dot */
        strncpy(cp,name,len);
        cp += len;
        if(cp1 == NULLCHAR){
            *cp++ = 0;      /* Last one; write null and finish */
            return cp;
        }
        name += len+1;
        dlen -= len+1;
    }
}
  
/* Translate a resource record from host format to network format */
static char *
htonrr(rr,buffer)
struct rr *rr;
char *buffer;
{
    struct rr *rrp;
    char *cp, *p;
    int i, len;
  
    cp = buffer;
    for(rrp = rr; rrp != NULLRR; rrp = rrp->next) {
#ifdef notdef
        i = strlen(rrp->name);
        if(rrp->name[i-1] != '.'
        && rrp->origin != NULLCHAR) {
            p = mallocw(i + strlen(rrp->origin) + 2);
            sprintf(p,"%s.%s",rrp->name,rrp->origin);
            cp = dn_compress(cp,p);
            free(p);
        } else
#endif
            cp = dn_compress(cp,rrp->name);
        cp = put16(cp,rrp->type);
        cp = put16(cp,rrp->class);
        cp = put32(cp,rrp->ttl);
#ifdef notdef
    /* The length doesn't seem to be right for all types ! */
        cp = put16(cp,rrp->rdlength);
#endif
        p = cp;     /* This is where the length goes ! */
        cp += 2;    /* Save the space for lenght field */
  
        switch(rrp->type) {
            case TYPE_A:
                cp = put32(cp,rrp->rdata.addr);
                break;
            case TYPE_SOA:
                cp = dn_compress(cp,rrp->rdata.soa.mname);
                cp = dn_compress(cp,rrp->rdata.soa.rname);
                cp = put32(cp,rrp->rdata.soa.serial);
                cp = put32(cp,rrp->rdata.soa.refresh);
                cp = put32(cp,rrp->rdata.soa.retry);
                cp = put32(cp,rrp->rdata.soa.expire);
                cp = put32(cp,rrp->rdata.soa.minimum);
                break;
            case TYPE_HINFO:
                *cp++ = len = strlen(rrp->rdata.hinfo.cpu);
                strncpy(cp,rrp->rdata.hinfo.cpu,len);
                cp += len;
                *cp++ = len = strlen(rrp->rdata.hinfo.os);
                strncpy(cp,rrp->rdata.hinfo.os,len);
                cp += len;
                break;
            case TYPE_MX:
                cp = put16(cp,rrp->rdata.mx.pref);
                cp = dn_compress(cp,rrp->rdata.mx.exch);
                break;
            case TYPE_CNAME:
            case TYPE_MB:
            case TYPE_MG:
            case TYPE_MR:
            case TYPE_NS:
            case TYPE_PTR:
                cp = dn_compress(cp,rrp->rdata.data);
                break;
            case TYPE_MINFO:    /* Unsupported type */
                cp = dn_compress(cp,rrp->rdata.minfo.rmailbx);
                cp = dn_compress(cp,rrp->rdata.minfo.emailbx);
            case TYPE_MD:       /* Unsupported type */
            case TYPE_MF:       /* Unsupported type */
            case TYPE_NULL:     /* Unsupported type */
            case TYPE_WKS:      /* Unsupported type */
                cp = dn_compress(cp,rrp->rdata.data);
                break;
            case TYPE_TXT:
            default:
                cp = put16(cp,rrp->rdlength);
                for(i=0 ; i < rrp->rdlength ; i++)
                    *cp++ = rrp->rdata.data[i];
                break;
        }
    /* Calculate the lenght of the RR */
        len = (int) (cp - p - 2);
        put16(p,len);       /* and set it */
    }
    return cp;
}
  
int
htondomain(dhdr,buffer,buflen)
struct dhdr *dhdr;
char *buffer;   /* Area for query */
int16 buflen;   /* Length of same */
{
    char *cp;
    struct rr *rrp;
    int16 parameter;
    int i, count;
  
    cp = buffer;
    cp = put16(cp,dhdr->id);
    if(dhdr->qr)
        parameter = 0x8000;
    else
        parameter = 0;
    parameter |= (dhdr->opcode & 0x0f) << 11;
    if(dhdr->aa)
        parameter |= DOM_AUTHORITY;
    if(dhdr->tc)
        parameter |= DOM_TRUNC;
    if(dhdr->rd)
        parameter |= DOM_DORECURSE;
    if(dhdr->ra)
        parameter |= DOM_CANRECURSE;
    parameter |= (dhdr->rcode & 0x0f);
    cp = put16(cp,parameter);
    cp = put16(cp,dhdr->qdcount);
    cp = put16(cp,dhdr->ancount);
    cp = put16(cp,dhdr->nscount);
    cp = put16(cp,dhdr->arcount);
    if((count = dhdr->qdcount) > 0) {
        rrp = dhdr->questions;
        for(i = 0; i < count; i++) {
            cp = dn_compress(cp,rrp->name);
            cp = put16(cp,rrp->type);
            cp = put16(cp,rrp->class);
            rrp = rrp->next;
        }
    }
    if((count = dhdr->ancount) > 0) {
        rrp = dhdr->answers;
        for(i = 0; i < count; i++) {
            cp = htonrr(rrp,cp);
            rrp = rrp->next;
        }
    }
    if((count = dhdr->nscount) > 0) {
        rrp = dhdr->authority;
        for(i = 0; i < count; i++) {
            cp = htonrr(rrp,cp);
            rrp = rrp->next;
        }
    }
    if((count = dhdr->arcount) > 0) {
        rrp = dhdr->additional;
        for(i = 0; i < count; i++) {
            cp = htonrr(rrp,cp);
            rrp = rrp->next;
        }
    }
    return (int)(cp - buffer);
}
  
  
#endif /* DOMAINSERVER */
  
