netsukuku/src/andns_lib.c
2014-05-03 12:10:51 -07:00

611 lines
14 KiB
C

/**************************************
* AUTHOR: Federico Tomassini *
* Copyright (C) Federico Tomassini *
* Contact effetom@gmail.com *
***********************************************
******* BEGIN 3/2006 ********
*************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
************************************************************************/
#include "andns_lib.h"
#include "andns_net.h"
#include "log.h"
#include "err_errno.h"
#include "xmalloc.h"
#include <arpa/inet.h>
#include <zlib.h>
int andns_compress(char *src,int srclen)
{
int res;
uLongf space;
src+=ANDNS_HDR_SZ;
srclen-=ANDNS_HDR_SZ;
space=compressBound(srclen);
unsigned char dst[space+ANDNS_HDR_Z];
/* The first four bytes will store
* the uncompressed size */
res=compress2(dst+ANDNS_HDR_Z, &space,(u_char *) src, srclen,
ANDNS_COMPR_LEVEL);
if (res!=Z_OK)
err_ret(ERR_ZLIBCP,-1);
if (space >= srclen-ANDNS_HDR_Z) /* We have to consider the four
bytes too */
err_ret(ERR_ZLIBNU,-1); /* This is a
silent return */
res=htonl(srclen);
memcpy(dst,&res,ANDNS_HDR_Z);
memcpy(src, dst, space);
return (int)space;
}
char* andns_uncompress(char *src,int srclen,int *dstlen)
{
unsigned char *dst;
uLongf space;
int res;
int c_len;
const int hdrsz=ANDNS_HDR_SZ+ANDNS_HDR_Z;
memcpy(&c_len,src+ANDNS_HDR_SZ,ANDNS_HDR_Z);
c_len=ntohl(c_len);
dst=xmalloc(c_len+ANDNS_HDR_SZ);
space=c_len;
res=uncompress(dst+ANDNS_HDR_SZ,&space,(u_char*) src+hdrsz, srclen-hdrsz);
if (res!=Z_OK) {
xfree(dst);
err_ret(ERR_ZLIBUP,NULL);
}
if ((int)space!=c_len) {
xfree(dst);
err_ret(ERR_ANDMAP,NULL);
}
memcpy(dst, src, ANDNS_HDR_SZ);
*dstlen=c_len+ANDNS_HDR_SZ;
return (char*)dst;
}
/*
* Takes the buffer stream and translate headers to
* andns_pkt struct.
* Returns ALWAYS 4. The pkt_len has to be controlled
* elsewhere.
*/
int a_hdr_u(char *buf,andns_pkt *ap)
{
uint8_t c;
uint16_t s;
char *start_buf;
start_buf=buf;
ap->r=*(buf+1)&0x01;
memcpy(&s,buf,2);
ap->id=ntohs(s);
ap->id>>=1;
buf+=2;
memcpy(&c,buf,sizeof(uint8_t));
ap->qr=(c>>7)&0x01;
ap->p=c&0x40?ANDNS_PROTO_UDP:ANDNS_PROTO_TCP;
ap->z=c&0x20;
ap->qtype=(c>>3)&0x03;
ap->ancount=(c<<1)&0x0e;
buf++;
memcpy(&c,buf,sizeof(uint8_t));
if (((*buf)&0x80)) ap->ancount++;
ap->ipv=(c>>6)&0x01;
ap->nk=(c>>4)&0x03;
ap->rcode=c&0x0f;
return ANDNS_HDR_SZ;
}
/*
* Translate the andns_pkt question stream to andns_pkt struct.
* -1 on error. Bytes readed otherwise.
* NOTE: The qst-data size is controlled: apkt won't need
* this control.
*/
int a_qst_u(char *buf,andns_pkt *ap,int limitlen)
{
int ret;
uint16_t s;
if (limitlen<3)
err_ret(ERR_ANDMAP,-1);
switch(ap->qtype) {
case AT_A:
memcpy(&s,buf,2);
ap->service=ntohs(s);
buf+=2;
if (ap->nk==NTK_REALM) {
ap->qstlength=ANDNS_HASH_H;
if (ap->qstlength>limitlen-2)
err_ret(ERR_ANDPLB,-1);
AP_ALIGN(ap);
memcpy(ap->qstdata,buf,ANDNS_HASH_H);
ret=ANDNS_HASH_H+2;
} else if (ap->nk==INET_REALM) {
memcpy(&s,buf,2);
ap->qstlength=ntohs(s);
buf+=2;
if (ap->qstlength>=ANDNS_MAX_QST_LEN ||
ap->qstlength>limitlen-4)
err_ret(ERR_ANDPLB,-1);
AP_ALIGN(ap);
memcpy(ap->qstdata,buf,ap->qstlength);
ret=ap->qstlength+4;
} else
return -1;
break;
case AT_PTR:
ap->qstlength=ap->ipv?16:4;
if (ap->qstlength>limitlen)
err_ret(ERR_ANDMAP,-1)
AP_ALIGN(ap);
memcpy(ap->qstdata,buf,ap->qstlength);
ret=ap->qstlength;
break;
case AT_G:
if (ap->nk!=NTK_REALM)
err_ret(ERR_ANDMAP,-1);
ap->qstlength=ANDNS_HASH_H;
if (ap->qstlength>limitlen)
err_ret(ERR_ANDPLB,-1);
AP_ALIGN(ap);
memcpy(ap->qstdata,buf,ANDNS_HASH_H);
ret=ap->qstlength;
break;
default:
debug(DBG_INSANE,"In a_qst_u: unknow query type.");
err_ret(ERR_ANDMAP,-1)
}
return ret;
}
int a_answ_u(char *buf,andns_pkt *ap,int limitlen)
{
uint16_t alen;
andns_pkt_data *apd;
int limit;
if (limitlen<3)
err_ret(ERR_ANDMAP,-1);
switch (ap->qtype) {
case AT_A:
limit=2;
if (limitlen<limit)
err_ret(ERR_ANDPLB,-1);
apd=andns_add_answ(ap);
if (*buf&0x40) {
apd->m|=APD_IP;
if (*buf&0x80)
apd->m|=APD_MAIN_IP;
limit=ap->ipv?16:4;
} else
limit=ANDNS_HASH_H;
if (limitlen<limit+2)
err_ret(ERR_ANDPLB,-1);
apd->wg=(*buf&0x3f);
apd->prio=(*(buf+1));
apd->rdlength=limit;
APD_ALIGN(apd);
memcpy(apd->rdata,buf+2,limit);
limit+=2;
break;
case AT_PTR:
memcpy(&alen,buf,2);
alen=ntohs(alen);
if (alen+2>limitlen)
err_ret(ERR_ANDPLB,-1);
if (alen>ANDNS_MAX_DATA_LEN)
err_ret(ERR_ANDPLB,-1);
apd=andns_add_answ(ap);
apd->rdlength=alen;
APD_ALIGN(apd);
memcpy(apd->rdata,buf+2,alen);
limit=alen+2;
break;
case AT_G:
if (limitlen<8)
err_ret(ERR_ANDMAP,-1);
apd=andns_add_answ(ap);
if (*buf&0x40) {
apd->m|=APD_IP;
if (*buf&0x80)
apd->m|=APD_MAIN_IP;
}
apd->m|=*buf&0x20?APD_UDP:
APD_TCP;
apd->wg=(*buf&0x1f);
apd->prio=(*(buf+1));
buf+=2;
memcpy(&alen,buf,2);
apd->service=ntohs(alen);
buf+=2;
if (apd->m&APD_IP)
apd->rdlength=(ap->ipv?16:4);
else
apd->rdlength=ANDNS_HASH_H;
limit=4+apd->rdlength;
if (limitlen<limit)
err_ret(ERR_ANDPLB,-1);
APD_ALIGN(apd);
memcpy(apd->rdata,buf,apd->rdlength);
break;
default:
err_ret(ERR_ANDMAP,-1);
}
return limit;
}
int a_answs_u(char *buf,andns_pkt *ap,int limitlen)
{
int ancount,i;
int offset=0,res;
uint16_t alen;
if (ap->qtype==AT_G) {
memcpy(&alen,buf,sizeof(uint16_t));
ap->ancount=ntohs(alen);
offset+=2;
}
ancount=ap->ancount;
for (i=0;i<ancount;i++) {
res=a_answ_u(buf+offset,ap,limitlen-offset);
if (res==-1) {
error(err_str);
err_ret(ERR_ANDMAD,-1);
}
offset+=res;
}
return offset;
}
/*
* This is a main function: takes the pkt-buf and translate
* it in structured data.
* It cares about andns_pkt allocation.
* The apkt is allocate here.
*
* Returns:
* -1 on E_INTRPRT
* 0 if pkt must be discarded.
* Number of bytes readed otherwise
*/
int a_u(char *buf,int pktlen,andns_pkt **app)
{
andns_pkt *ap;
int offset,res;
int limitlen,u_len;
char *u_buf;
if (pktlen<ANDNS_HDR_SZ)
err_ret(ERR_ANDPLB,0);
*app=ap=create_andns_pkt();
offset=a_hdr_u(buf,ap);
if (ap->z) { /* Controls the space to read
uncompressed size */
if (pktlen<ANDNS_HDR_SZ+ANDNS_HDR_Z) {
destroy_andns_pkt(ap);
err_ret(ERR_ANDPLB,0);
}
if (!(u_buf=andns_uncompress(buf,pktlen,&u_len)))
goto andmap;
destroy_andns_pkt(ap);
ANDNS_UNSET_Z(u_buf);
res=a_u(u_buf,u_len,app);
xfree(u_buf);
return res;
}
buf+=offset;
limitlen=pktlen-offset;
if ((res=a_qst_u(buf,ap,limitlen))==-1)
goto andmap;
offset+=res;
if (!ap->ancount) /*No answers */
return offset;
buf+=res;
limitlen-=res;
if ((res=a_answs_u(buf,ap,limitlen))==-1)
goto andmap;
offset+=res;
if (offset!=pktlen)
error("In a_u(): pktlen differs from readed contents: ID query %d.",ap->id);
return offset;
andmap:
destroy_andns_pkt(ap);
error(err_str);
err_ret(ERR_ANDMAP,-1);
}
int a_hdr_p(andns_pkt *ap,char *buf)
{
uint16_t s;
uint8_t an;
ap->id<<=1;
s=htons(ap->id);
memcpy(buf,&s,sizeof(uint16_t));
if (ap->r)
*(buf+1)|=0x01;
else
*(buf+1)&=0xfe;
buf+=2;
if (ap->qr)
(*buf)|=0x80;
if (ap->p)
(*buf)|=0x40;
if (ap->z)
(*buf)|=0x20;
(*buf)|=( (ap->qtype)<<3);
an=ap->ancount;
(*buf++)|=( (an)>>1);
(*buf)|=( (ap->ancount)<<7);
if (ap->ipv)
*buf|=0x40;
(*buf)|=( (ap->nk)<<4);
(*buf)|=( ap->rcode);
return ANDNS_HDR_SZ;
}
int a_qst_p(andns_pkt *ap,char *buf,int limitlen)
{
int ret=0;
uint16_t s;
int limit;
switch(ap->qtype){
case AT_A:
limit=ap->nk==NTK_REALM?ANDNS_HASH_H+2:ap->qstlength+4;
if (limitlen<limit)
err_ret(ERR_ANDMAD,-1);
s=htons(ap->service);
memcpy(buf,&s,2);
buf+=2; /* here INET and NTK REALM change */
if (ap->nk==NTK_REALM) {
memcpy(buf,ap->qstdata,ANDNS_HASH_H);
ret=ANDNS_HASH_H+2;
} else if (ap->nk==INET_REALM) {
s=htons(ap->qstlength);
memcpy(buf,&s,2);
buf+=2;
memcpy(buf,ap->qstdata,ap->qstlength);
ret=ap->qstlength+4;
} else
err_ret(ERR_ANDMAD,-1);
break;
case AT_PTR:
limit=ap->ipv?16:4;
if (limitlen<limit)
err_ret(ERR_ANDMAD,-1);
memcpy(buf,ap->qstdata,limit);
ret=limit;
break;
case AT_G:
limit=ANDNS_HASH_H;
if (limitlen<limit)
err_ret(ERR_ANDMAD,-1);
memcpy(buf,ap->qstdata,ANDNS_HASH_H);
ret=ANDNS_HASH_H;
break;
default:
debug(DBG_INSANE,"In a_qst_p: unknow query type.");
err_ret(ERR_ANDMAD,-1);
break;
}
return ret;
}
int a_answ_p(andns_pkt *ap,andns_pkt_data *apd,char *buf,int limitlen)
{
uint16_t s;
int limit;
int ret;
switch(ap->qtype) {
case AT_A:
limit=ap->ipv?16:4;
if (limitlen<limit+2)
err_ret(ERR_ANDPLB,-1);
if (apd->m)
*buf|=0x80;
*buf++|= (apd->wg&0x7f);
*buf++|=apd->prio;
memcpy(buf,apd->rdata,limit);
ret=limit+2;
break;
case AT_PTR:
if (limitlen<apd->rdlength+2)
err_ret(ERR_ANDPLB,-1);
s=htons(apd->rdlength);
memcpy(buf,&s,sizeof(uint16_t));
buf+=2;
memcpy(buf,apd->rdata,apd->rdlength);
ret=apd->rdlength+2;
break;
case AT_G: /* TODO */
if (limitlen<4)
err_ret(ERR_ANDPLB,-1);
if (apd->m==1)
(*buf)|=0xc0;
else if (apd->m)
(*buf)|=0x40;
*buf++|= (apd->wg&0x3f);
*buf++|=apd->prio;
s=htons(apd->service);
memcpy(buf,&s,2);
if (apd->m) {
limit=ap->ipv?16:4;
if (limitlen<limit+4)
err_ret(ERR_ANDPLB,-1);
memcpy(buf,apd->rdata,limit);
ret=limit+4;
} else {
limit=strlen(apd->rdata);
if (limitlen<limit+6)
err_ret(ERR_ANDPLB,-1);
s=htons(limit);
memcpy(buf,&s,2);
buf+=2;
memcpy(buf,apd->rdata,limit);
ret=limit+6;
}
break;
default:
debug(DBG_INSANE,"In a_answ_p(): unknow query type.");
err_ret(ERR_ANDMAD,-1);
break;
}
return ret;
}
int a_answs_p(andns_pkt *ap,char *buf, int limitlen)
{
andns_pkt_data *apd;
int i;
int offset=0,res;
uint16_t s;
if (ap->qtype==AT_G) {
if (limitlen<2)
err_ret(ERR_ANDPLB,-1);
s=htons(ap->ancount);
memcpy(buf,&s,2);
offset+=2;
}
apd=ap->pkt_answ;
for (i=0;i<ap->ancount && apd;i++) {
if((res=a_answ_p(ap,apd,buf+offset,limitlen-offset))==-1) {
error(err_str);
err_ret(ERR_ANDMAD,-1);
}
offset+=res;
apd=apd->next;
}
return offset;
}
int a_p(andns_pkt *ap, char *buf)
{
int offset,res;
memset(buf,0,ANDNS_MAX_SZ);
offset=a_hdr_p(ap,buf);
buf+=offset;
if ((res=a_qst_p(ap,buf,ANDNS_MAX_SZ-offset))==-1)
goto server_fail;
offset+=res;
buf+=res;
if (ap->ancount) {
if ((res=a_answs_p(ap,buf,ANDNS_MAX_SZ-offset))==-1)
goto server_fail;
offset+=res;
}
destroy_andns_pkt(ap);
/* Compression */
if (offset>ANDNS_COMPR_THRESHOLD) {
res=andns_compress(buf,offset);
if (res==-1)
error(err_str);
else
return res;
}
/* end compression */
return offset;
server_fail:
destroy_andns_pkt(ap);
error(err_str);
err_ret(ERR_ANDMAD,-1);
}
/* MEM */
/* Borning functions */
andns_pkt* create_andns_pkt(void)
{
andns_pkt *ap;
ap=xmalloc(ANDNS_PKT_SZ);
memset(ap,0,ANDNS_PKT_SZ);
return ap;
}
andns_pkt_data* create_andns_pkt_data(void)
{
andns_pkt_data *apd;
apd=xmalloc(ANDNS_PKT_DATA_SZ);
memset(apd,0,ANDNS_PKT_DATA_SZ);
return apd;
}
andns_pkt_data* andns_add_answ(andns_pkt *ap)
{
andns_pkt_data *apd,*a;
apd=create_andns_pkt_data();
a=ap->pkt_answ;
if (!a) {
ap->pkt_answ=apd;
return apd;
}
while (a->next) a=a->next;
a->next=apd;
return apd;
}
/* Death functions */
void destroy_andns_pkt_data(andns_pkt_data *apd)
{
if (apd->rdata)
xfree(apd->rdata);
xfree(apd);
}
void andns_del_answ(andns_pkt *ap)
{
andns_pkt_data *apd,*apdt;
apd=ap->pkt_answ;
if (!apd)
return;
apdt=apd->next;
while (apdt) {
apd=apdt;
apdt=apdt->next;
}
apd->next=NULL;
destroy_andns_pkt_data(apdt);
}
void destroy_andns_pkt_datas(andns_pkt *ap)
{
andns_pkt_data *apd,*apd_t;
apd=ap->pkt_answ;
while(apd) {
apd_t=apd->next;
destroy_andns_pkt_data(apd);
apd=apd_t;
}
}
void destroy_andns_pkt(andns_pkt *ap)
{
if (ap->qstdata)
xfree(ap->qstdata);
destroy_andns_pkt_datas(ap);
xfree(ap);
}