root/lib/libalias/alias_nbt.c
/*-
 * Written by Atsushi Murai <amurai@spec.co.jp>
 * Copyright (c) 1998, System Planning and Engineering Co.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: src/lib/libalias/alias_nbt.c,v 1.4.2.3 2001/08/01 09:52:26 obrien Exp $
 * $DragonFly: src/lib/libalias/alias_nbt.c,v 1.4 2006/03/18 19:43:18 swildner Exp $
 *
 *  TODO:
 *       oClean up. 
 *       oConsidering for word alignment for other platform.
 */
/*
    alias_nbt.c performs special processing for NetBios over TCP/IP
    sessions by UDP.

    Initial version:  May, 1998  (Atsushi Murai <amurai@spec.co.jp>)

    See HISTORY file for record of revisions.
*/

/* Includes */
#include <sys/param.h>
#include <ctype.h>
#include <stdio.h> 
#include <string.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>

#include "alias_local.h"

typedef struct {
        struct in_addr          oldaddr;
        u_short                         oldport;
        struct in_addr          newaddr;
        u_short                         newport;
        u_short                         *uh_sum;
} NBTArguments;

typedef struct {
        unsigned char   type;
        unsigned char   flags;
        u_short                 id;
        struct in_addr  source_ip;
        u_short                 source_port;
        u_short                 len;
        u_short                 offset;
} NbtDataHeader;

#define OpQuery         0
#define OpUnknown       4
#define OpRegist        5
#define OpRelease       6
#define OpWACK          7
#define OpRefresh       8
typedef struct {
        u_short                 nametrid;
        u_short                 dir:1, opcode:4, nmflags:7, rcode:4;
        u_short                 qdcount;
        u_short                 ancount;
        u_short                 nscount;
        u_short                 arcount;
} NbtNSHeader;

#define FMT_ERR         0x1
#define SRV_ERR         0x2
#define IMP_ERR         0x4
#define RFS_ERR         0x5
#define ACT_ERR         0x6
#define CFT_ERR         0x7


#ifdef DEBUG
static void PrintRcode( u_char rcode )  {

        switch (rcode) {
                case FMT_ERR:
                        printf("\nFormat Error.");
                case SRV_ERR:
                        printf("\nSever failure.");
                case IMP_ERR:
                        printf("\nUnsupported request error.\n");
                case RFS_ERR:
                        printf("\nRefused error.\n");
                case ACT_ERR:
                        printf("\nActive error.\n");
                case CFT_ERR:
                        printf("\nName in conflict error.\n");
                default:
                        printf("\n??\?=%0x\n", rcode );

        }       
}
#endif


/* Handling Name field */
static u_char *AliasHandleName ( u_char *p, char *pmax ) {

        u_char *s;
#ifdef DEBUG
        u_char c;
#endif
        int             compress;

        /* Following length field */

        if (p == NULL || (char *)p >= pmax)
                return(NULL);

        if (*p & 0xc0 ) {
                p = p + 2;
                if ((char *)p > pmax)
                        return(NULL);
                return ((u_char *)p);
        }
        while ( ( *p & 0x3f) != 0x00 ) {
                s = p + 1;
                if ( *p == 0x20 )
                        compress = 1;
                else
                        compress = 0;
                
                /* Get next length field */
                p = (u_char *)(p + (*p & 0x3f) + 1);
                if ((char *)p > pmax) {
                        p = NULL;
                        break;
                }
#ifdef DEBUG
                printf(":");
#endif
                while (s < p) {
                        if ( compress == 1 ) {
#ifdef DEBUG
                                c = (u_char )(((((*s & 0x0f) << 4) | (*(s+1) & 0x0f)) - 0x11));
                                if (isprint( c ) )
                                        printf("%c", c );
                                else
                                        printf("<0x%02x>", c );
#endif
                                s +=2;
                        } else {
#ifdef DEBUG
                                printf("%c", *s);
#endif
                                s++;
                        }
                }
#ifdef DEBUG
                printf(":");
#endif
                fflush(stdout);
    }

        /* Set up to out of Name field */
        if (p == NULL || (char *)p >= pmax)
            p = NULL;
        else
            p++;
        return ((u_char *)p);
}

/* 
 * NetBios Datagram Handler (IP/UDP)
 */
#define DGM_DIRECT_UNIQ         0x10
#define DGM_DIRECT_GROUP        0x11
#define DGM_BROADCAST           0x12
#define DGM_ERROR                       0x13
#define DGM_QUERY                       0x14
#define DGM_POSITIVE_RES        0x15
#define DGM_NEGATIVE_RES        0x16

int AliasHandleUdpNbt(
        struct ip                       *pip,    /* IP packet to examine/patch */
        struct alias_link       *link,
        struct in_addr          *alias_address,
    u_short             alias_port
) {
    struct udphdr *     uh;
    NbtDataHeader       *ndh;
    u_char              *p = NULL;
    char                *pmax;
        
    /* Calculate data length of UDP packet */
    uh =  (struct udphdr *) ((char *) pip + (pip->ip_hl << 2));
    pmax = (char *)uh + ntohs( uh->uh_ulen );

        ndh = (NbtDataHeader *)((char *)uh + (sizeof (struct udphdr)));
    if ((char *)(ndh + 1) > pmax)
            return(-1);
#ifdef DEBUG
        printf("\nType=%02x,", ndh->type );
#endif
        switch ( ndh->type ) {
                case DGM_DIRECT_UNIQ:
                case DGM_DIRECT_GROUP:
                case DGM_BROADCAST:
                        p = (u_char *)ndh + 14;
                    p = AliasHandleName ( p, pmax ); /* Source Name */
                    p = AliasHandleName ( p, pmax ); /* Destination Name */
                        break;
                case DGM_ERROR:
                        p = (u_char *)ndh + 11;
                        break;
                case DGM_QUERY:
                case DGM_POSITIVE_RES:
                case DGM_NEGATIVE_RES:
                        p = (u_char *)ndh + 10;
                    p = AliasHandleName ( p, pmax ); /* Destination Name */
                        break;
        }
    if (p == NULL || (char *)p > pmax)
            p = NULL;
#ifdef DEBUG
        printf("%s:%d-->", inet_ntoa(ndh->source_ip), ntohs(ndh->source_port) );
#endif
        /* Doing a IP address and Port number Translation */
        if ( uh->uh_sum != 0 ) {
                int                             acc;
                u_short                 *sptr;
                acc  = ndh->source_port;
                acc -= alias_port;
                sptr = (u_short *) &(ndh->source_ip);
                acc += *sptr++;
                acc += *sptr;
                sptr = (u_short *) alias_address;
                acc -= *sptr++;
                acc -= *sptr;
                ADJUST_CHECKSUM(acc, uh->uh_sum);
        }
    ndh->source_ip = *alias_address;
    ndh->source_port = alias_port;
#ifdef DEBUG
        printf("%s:%d\n", inet_ntoa(ndh->source_ip), ntohs(ndh->source_port) );
        fflush(stdout);
#endif
    return((p == NULL) ? -1 : 0);
}
/* Question Section */
#define QS_TYPE_NB              0x0020
#define QS_TYPE_NBSTAT  0x0021
#define QS_CLAS_IN              0x0001
typedef struct {
        u_short type;   /* The type of Request */
        u_short class;  /* The class of Request */
} NBTNsQuestion;

static u_char *
AliasHandleQuestion(
    u_short count,
                                                        NBTNsQuestion *q,
    char *pmax,
                                                        NBTArguments  *nbtarg)
{

        while ( count != 0 ) {
                /* Name Filed */
                q = (NBTNsQuestion *)AliasHandleName((u_char *)q, pmax);

                if (q == NULL || (char *)(q + 1) > pmax) {
                        q = NULL;
                        break;
                }

                /* Type and Class filed */
                switch ( ntohs(q->type) ) {
                        case QS_TYPE_NB:
                        case QS_TYPE_NBSTAT:
                                q= q+1;
                        break;
                        default:
#ifdef DEBUG
                                printf("\nUnknown Type on Question %0x\n", ntohs(q->type) );
#endif
                        break;
                }
                count--;
        }

        /* Set up to out of Question Section */
        return ((u_char *)q);
}

/* Resource Record */
#define RR_TYPE_A               0x0001
#define RR_TYPE_NS              0x0002
#define RR_TYPE_NULL    0x000a
#define RR_TYPE_NB              0x0020
#define RR_TYPE_NBSTAT  0x0021
#define RR_CLAS_IN              0x0001
#define SizeOfNsResource        8
typedef struct {
        u_short type;
        u_short class;
        unsigned int ttl;
        u_short rdlen;
} NBTNsResource;

#define SizeOfNsRNB                     6
typedef struct {
        u_short g:1, ont:2, resv:13;
        struct  in_addr addr;
} NBTNsRNB;

static u_char *
AliasHandleResourceNB( 
    NBTNsResource *q,
    char *pmax, 
                                                           NBTArguments  *nbtarg)
{
        NBTNsRNB        *nb;
        u_short bcount;

        if (q == NULL || (char *)(q + 1) > pmax)
                return(NULL);
        /* Check out a length */
        bcount = ntohs(q->rdlen);

        /* Forward to Resource NB position */
        nb = (NBTNsRNB *)((u_char *)q + SizeOfNsResource);

        /* Processing all in_addr array */
#ifdef DEBUG
        printf("NB rec[%s", inet_ntoa(nbtarg->oldaddr));
            printf("->%s, %dbytes] ",inet_ntoa(nbtarg->newaddr ), bcount);
#endif
        while ( nb != NULL && bcount != 0 )  {
                if ((char *)(nb + 1) > pmax) {
                        nb = NULL;
                        break;
                }
#ifdef DEBUG
                printf("<%s>", inet_ntoa(nb->addr) );
#endif
                if (!bcmp(&nbtarg->oldaddr,&nb->addr, sizeof(struct in_addr) ) ) {
                        if ( *nbtarg->uh_sum != 0 ) {
                int acc;
                u_short *sptr;

                sptr = (u_short *) &(nb->addr);
                acc = *sptr++;
                acc += *sptr;
                sptr = (u_short *) &(nbtarg->newaddr);
                acc -= *sptr++;
                acc -= *sptr;
                ADJUST_CHECKSUM(acc, *nbtarg->uh_sum);
                        }

                        nb->addr = nbtarg->newaddr;
#ifdef DEBUG
                        printf("O");
#endif
                }
#ifdef DEBUG
                 else {
                        printf(".");
                }
#endif
                nb=(NBTNsRNB *)((u_char *)nb + SizeOfNsRNB);
                bcount -= SizeOfNsRNB;
        }
        if (nb == NULL || (char *)(nb + 1) > pmax) {
                nb = NULL;
        }

        return ((u_char *)nb);
}

#define SizeOfResourceA         6
typedef struct {
        struct  in_addr addr;
} NBTNsResourceA;

static u_char *
AliasHandleResourceA( 
    NBTNsResource *q,
    char *pmax,
                                                          NBTArguments  *nbtarg)
{
        NBTNsResourceA  *a;
        u_short bcount;

        if (q == NULL || (char *)(q + 1) > pmax)
                return(NULL);

        /* Forward to Resource A position */
        a = (NBTNsResourceA *)( (u_char *)q + sizeof(NBTNsResource) );

        /* Check out of length */
        bcount = ntohs(q->rdlen);

        /* Processing all in_addr array */
#ifdef DEBUG
        printf("Arec [%s", inet_ntoa(nbtarg->oldaddr));
        printf("->%s]",inet_ntoa(nbtarg->newaddr ));
#endif
        while ( bcount != 0 )  {
                if (a == NULL || (char *)(a + 1) > pmax)
                        return(NULL);
#ifdef DEBUG
                printf("..%s", inet_ntoa(a->addr) );
#endif
                if ( !bcmp(&nbtarg->oldaddr, &a->addr, sizeof(struct in_addr) ) ) {
                        if ( *nbtarg->uh_sum != 0 ) {
                int acc;
                u_short *sptr;

                sptr = (u_short *) &(a->addr);           /* Old */
                acc = *sptr++;
                acc += *sptr;
                sptr = (u_short *) &nbtarg->newaddr; /* New */
                acc -= *sptr++;
                acc -= *sptr;
                ADJUST_CHECKSUM(acc, *nbtarg->uh_sum);
                        }

                        a->addr = nbtarg->newaddr;
                }
                a++;    /*XXXX*/
                bcount -= SizeOfResourceA;
        }
        if (a == NULL || (char *)(a + 1) > pmax)
                a =  NULL;
        return ((u_char *)a);
}

typedef struct {
        u_short opcode:4, flags:8, resv:4;
} NBTNsResourceNULL;

static u_char *
AliasHandleResourceNULL( 
    NBTNsResource *q, 
    char *pmax,
                                                             NBTArguments  *nbtarg)
{
        NBTNsResourceNULL       *n;
        u_short bcount;

        if (q == NULL || (char *)(q + 1) > pmax)
                return(NULL);

        /* Forward to Resource NULL position */
        n = (NBTNsResourceNULL *)( (u_char *)q + sizeof(NBTNsResource) );

        /* Check out of length */
        bcount = ntohs(q->rdlen);

        /* Processing all in_addr array */
        while ( bcount != 0 )  {
                if ((char *)(n + 1) > pmax) {
                        n = NULL;
                        break;
                }
                n++;
                bcount -= sizeof(NBTNsResourceNULL);
        }
        if ((char *)(n + 1) > pmax)
                n = NULL;

        return ((u_char *)n);
}

static u_char *
AliasHandleResourceNS( 
    NBTNsResource *q,
    char *pmax,
                                                             NBTArguments  *nbtarg)
{
        NBTNsResourceNULL       *n;
        u_short bcount;

        if (q == NULL || (char *)(q + 1) > pmax)
                return(NULL);

        /* Forward to Resource NULL position */
        n = (NBTNsResourceNULL *)( (u_char *)q + sizeof(NBTNsResource) );

        /* Check out of length */
        bcount = ntohs(q->rdlen);

        /* Resource Record Name Filed */
        q = (NBTNsResource *)AliasHandleName( (u_char *)n, pmax ); /* XXX */

        if (q == NULL || (char *)((u_char *)n + bcount) > pmax)
                return(NULL);
        else
        return ((u_char *)n + bcount);
}

typedef struct {
        u_short numnames;
} NBTNsResourceNBSTAT;

static u_char *
AliasHandleResourceNBSTAT(
    NBTNsResource *q,
    char *pmax,
                                                               NBTArguments  *nbtarg)
{
        NBTNsResourceNBSTAT     *n;
        u_short bcount;

        if (q == NULL || (char *)(q + 1) > pmax)
                return(NULL);

        /* Forward to Resource NBSTAT position */
        n = (NBTNsResourceNBSTAT *)( (u_char *)q + sizeof(NBTNsResource) );

        /* Check out of length */
        bcount = ntohs(q->rdlen);

        if (q == NULL || (char *)((u_char *)n + bcount) > pmax)
                return(NULL);
        else
        return ((u_char *)n + bcount);
}

static u_char *
AliasHandleResource(
    u_short count, 
                                                        NBTNsResource *q,
    char *pmax,
    NBTArguments  
    *nbtarg)
{
        while ( count != 0 ) {
                /* Resource Record Name Filed */
                q = (NBTNsResource *)AliasHandleName( (u_char *)q, pmax );

                if (q == NULL || (char *)(q + 1) > pmax)
                        break;
#ifdef DEBUG
                printf("type=%02x, count=%d\n", ntohs(q->type), count );
#endif

                /* Type and Class filed */
                switch ( ntohs(q->type) ) {
                        case RR_TYPE_NB:
                                q = (NBTNsResource *)AliasHandleResourceNB( 
                                    q,
                                    pmax,
                                    nbtarg 
                                );
                                break;
                        case RR_TYPE_A: 
                                q = (NBTNsResource *)AliasHandleResourceA( 
                                    q, 
                                    pmax, 
                                    nbtarg
                                );
                                break;
                        case RR_TYPE_NS:
                                q = (NBTNsResource *)AliasHandleResourceNS( 
                                    q,
                                    pmax, 
                                    nbtarg 
                                );
                                break;
                        case RR_TYPE_NULL:
                                q = (NBTNsResource *)AliasHandleResourceNULL( 
                                    q, 
                                    pmax, 
                                    nbtarg 
                                );
                                break;
                        case RR_TYPE_NBSTAT:
                                q = (NBTNsResource *)AliasHandleResourceNBSTAT(
                                    q,
                                    pmax, 
                                    nbtarg
                                );
                                break;
                        default: 
#ifdef DEBUG
                                printf(
                                    "\nUnknown Type of Resource %0x\n", 
                                    ntohs(q->type) 
                                );
#endif
                                break;
                }
                count--;
        }
        fflush(stdout);
        return ((u_char *)q);
}

int AliasHandleUdpNbtNS(
        struct ip                       *pip,    /* IP packet to examine/patch */
        struct alias_link       *link,
        struct in_addr          *alias_address,
        u_short                         *alias_port,
        struct in_addr          *original_address,
        u_short                         *original_port )
{
    struct udphdr *     uh;
        NbtNSHeader       * nsh;
        u_char            * p;
        char            *pmax;
        NBTArguments    nbtarg;

        /* Set up Common Parameter */   
        nbtarg.oldaddr  =       *alias_address;
        nbtarg.oldport  =       *alias_port;
        nbtarg.newaddr  =       *original_address;
        nbtarg.newport  =       *original_port;

    /* Calculate data length of UDP packet */
    uh =  (struct udphdr *) ((char *) pip + (pip->ip_hl << 2));
        nbtarg.uh_sum   =       &(uh->uh_sum);
        nsh = (NbtNSHeader *)((char *)uh + (sizeof(struct udphdr)));
        p = (u_char *)(nsh + 1);
    pmax = (char *)uh + ntohs( uh->uh_ulen );

    if ((char *)(nsh + 1) > pmax)
        return(-1);

#ifdef DEBUG
    printf(" [%s] ID=%02x, op=%01x, flag=%02x, rcode=%01x, qd=%04x"
           ", an=%04x, ns=%04x, ar=%04x, [%d]-->", 
                nsh->dir ? "Response": "Request",
                nsh->nametrid,
                nsh->opcode,
                nsh->nmflags,
                nsh->rcode,
                ntohs(nsh->qdcount),
                ntohs(nsh->ancount),
                ntohs(nsh->nscount),
                ntohs(nsh->arcount),
        (u_char *)p -(u_char *)nsh
    );
#endif

        /* Question Entries */
        if (ntohs(nsh->qdcount) !=0 ) {
        p = AliasHandleQuestion(
            ntohs(nsh->qdcount),
            (NBTNsQuestion *)p,
            pmax, 
            &nbtarg 
        );
        }

        /* Answer Resource Records */
        if (ntohs(nsh->ancount) !=0 ) {
        p = AliasHandleResource(
            ntohs(nsh->ancount),
            (NBTNsResource *)p,
            pmax, 
            &nbtarg 
        );
        }

        /* Authority Resource Recodrs */
        if (ntohs(nsh->nscount) !=0 ) {
        p = AliasHandleResource(
            ntohs(nsh->nscount), 
            (NBTNsResource *)p,
            pmax, 
            &nbtarg 
        );
        }

        /* Additional Resource Recodrs */
        if (ntohs(nsh->arcount) !=0 ) {
        p = AliasHandleResource(
            ntohs(nsh->arcount),
            (NBTNsResource *)p,
            pmax, 
            &nbtarg 
        );
        }

#ifdef DEBUG
                PrintRcode(nsh->rcode);
#endif
    return ((p == NULL) ? -1 : 0);
}