/*
 * $Header: /san01/cvs/ashpool/csrc/modradius/Attic/radius.c,v 1.11 2004/05/19 18:34:08 aleigh Exp $
 */

/*
 * Copyright (c) 2004 Alex Leigh, All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 *
 * Use, disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

/* RFC 2865: Radius */

#include "radius.h"

#include <md5.h>

#define RAD_IPADDR 1
#define RAD_STRING 2
#define RAD_INTEGER 3
#define RAD_TEXT 4
#define RAD_TIME 5

/** Protocol Structures **/
struct radSheader {
    uint8_t code;
    uint8_t id;
    uint16_t len;
    unsigned char resp_auth[16];
};
typedef struct radSheader radTheader;

struct radSattrib_header {
    uint8_t type;
    uint8_t len;
};
typedef struct radSattrib_header radTattrib_header;

/** User Structures **/
struct radSattrib_def {
    int id;   // The Radius protocol ID for this attribute.
    int type; // The type for this attrib; RAD_IPADDR, RAD_INTEGER, etc
    const char *name; // The english name for this attribute.
};
typedef struct radSattrib_def radTattrib_def;

struct radSrequest {
    int req_code; // The request protocol packet type. 
    int req_id;   // The request protocol sequence identifier.
    int res_code; // The response protocol packet type
        
    unsigned char auth[16]; // The protocol authenticator 
    lstTset *req_set;
    lstTset *res_set;
};
typedef struct radSrequest radTtrans;

static int radGsock = -1;
static dicTdictionary *radGattribs;

static void radFconfig(void);
static void radFconfig_attribs(const xmlTtag *attribs);
static void radFdump(unsigned char *msg, ssize_t msg_len);
static void radFrunloop(void);
void radF_attrib_free(radTattrib_def *attrib);
static int radFtype_find(const char *type);
static int radFattrib_value_format(unsigned char *buf, size_t buf_len,
                                   unsigned char *attrib, size_t attrib_len,
                                   int type, size_t *value_len);
static void radFcalc_digest(unsigned char *digest,
                     unsigned char *secret,
                     unsigned char *vector);
static int radFdecrypt_password(
    radTtrans *t,
    unsigned char *buf,
    size_t buf_len,
    unsigned char *data,
    size_t data_len);
static radTtrans *radFtrans_init(void);
static void radFtrans_free(radTtrans *r);

/***/

static int radFtype_find(const char *type) {
    if(strcasecmp(type,"ipaddr")==0) return RAD_IPADDR;
    if(strcasecmp(type,"string")==0) return RAD_STRING;
    if(strcasecmp(type,"integer")==0) return RAD_INTEGER;
    if(strcasecmp(type,"text")==0) return RAD_TEXT;
    if(strcasecmp(type,"time")==0) return RAD_TIME;
    return -1;
}

static radTtrans *radFtrans_init(void) {
    radTtrans *r=(radTtrans *)malloc(sizeof(radTtrans));

    assert(r!=NULL);
    
    bzero(r,sizeof(radTtrans));

    r->req_set=lstFset_init();
    r->res_set=lstFset_init();

    assert(r->req_set!=NULL);
    assert(r->res_set!=NULL);

    return r;
}

static void radFtrans_free(radTtrans *r) {
    assert(r!=NULL);
    assert(r->req_set!=NULL);
    assert(r->res_set!=NULL);
    
    lstFset_free(r->req_set);
    lstFset_free(r->res_set);
    
    free(r);
}

/* Take a given attribute value and convert it into string form.
 * This is intended to be used with the raw data from the radius
 * packets, so it does perform network to host byte re-ordering.
 *
 *  Returns 0 on success and -1 on error.
 */
static int radFattrib_value_format(unsigned char *buf, size_t buf_len,
                            unsigned char *attrib, size_t attrib_len,
                                   int type, size_t *value_len)
{
    unsigned int value;

//    utlFhexdump(attrib,attrib_len);
    
    switch(type) {
        case RAD_IPADDR:
            if(buf_len<16) {
                return -1;
            }
            
            if(attrib_len!=4) {
                return -1;
            }

            value=1;
            memFcopy(attrib,&value,4);
            value=htonl(value);

            utlFip_to_str(value,buf,buf_len);

            *value_len=strlen(buf)+1;
            
            return 0;
            
        case RAD_STRING:
                /* This really means a binary value */
            if(buf_len<attrib_len) {
                return -1;
            }
            
            memFcopy(attrib,buf,attrib_len);
            
            *value_len=attrib_len;
            
            return 0;
    
        case RAD_INTEGER:
            value=1;
            memFcopy(attrib,&value,4);
            value=htonl(value);
            snprintf(buf,buf_len,"%u",value);
            
            return 0;
            
        case RAD_TEXT:
                /* This really means a string value, so we
                 * are going to NULL terminate it. */
            
            if(buf_len<attrib_len+1) {
                return -1;
            }
            
            memFcopy(attrib,buf,attrib_len);
            buf[attrib_len+1]=0;

            *value_len=strlen(attrib_len+1);
            
            return 0;
            
        case RAD_TIME:
                /* Time is not supported in any of the default
                 * attribute types so we are going to ignore it
                 * for now. -aleigh */
            return -1;
    }
    return -1;
}

radTattrib_def *radFattrib_find_idx(int idx) {
    return (radTattrib_def *)dicFuserdata_find(radGattribs,idx);
}

void radF_attrib_free(radTattrib_def *attrib) {
        /* We don't free the names because they are just borrowed
         * from the XML configuration. This will probably be important
         * to remember. -aleigh */
    free(attrib);
}

static void radFconfig(void)
{
    const xmlTtag *global;
    const xmlTtag *tag;
    const xmlTtag *child;

    radGattribs=dicFchar_init();
    assert(radGattribs!=NULL);
    
    dicFuserdata_destructor_set(radGattribs,radF_attrib_free);
    
    global=conFget_config_basetag();
    
    assert(global!=NULL);

    tag=xmlFfind_first_tag_children(global,"modradius_config");

    logFmsg(3,"mod/radius: tag is 0x%x", tag);
    
    if(tag==NULL) {
        logFmsg(2,"mod/radius: Unable to find <modradius_config> in config!");
        exit(5);
    }

    child=xmlFfind_first_tag_children(tag,"attribs");
    if(child==NULL) {
        logFmsg(2,"mod/radius: Unable to find <attribs> section!");
        exit(5);
    }

    radFconfig_attribs(child);
}

static void radFconfig_attribs(const xmlTtag *attribs)
{
    xmlTiterator *it;
    const xmlTtag *tag;
    
    assert(attribs!=NULL);
    
    logFmsg(3,"mod/radius: radFconfig_attribs: Called.");
    
    it=xmlFiterator_init(attribs);
    
    assert(it!=NULL);
        
    while((tag=xmlFiterator_next_name(it,"attrib"))!=NULL) {
        const char *id;
        const char *name;
        size_t name_len;
        const char *type;
        size_t type_len;
        
        id=xmlFtag_get_attrib_value(tag,"id");
        
        name=xmlFfirst_child_value(tag,"name",&name_len);
        type=xmlFfirst_child_value(tag,"type",&type_len);
        
        if(id!=NULL && name!=NULL && type!=NULL) {
            radTattrib_def *def;

            def=(radTattrib_def *)malloc(sizeof(radTattrib_def));
            assert(def!=NULL);

            def->name=name;
            def->type=radFtype_find(type);
            def->id=atoi(id);

            dicFuserdata_update(radGattribs,def->id,(void *)def);

            logFmsg(3,"mod/radius: %s - %s", id, name);
        }
    }
    
    xmlFiterator_free(it);
}

static radTtrans *radFparse(unsigned char *msg, ssize_t msg_len)
{
    int code;
    int id;
    int len;
    int type;
    int i;
    radTheader *header = (radTheader *) msg;
    radTattrib_header *ah;
    size_t attrib_offset=0;
    radTtrans *req = radFtrans_init();

    code = header->code;
    id = header->id;
    len = header->len;

    memFcopy(header->resp_auth,req->auth,16);
    
    utlFhexdump(msg,msg_len);
    
    logFmsg(3, "header is %d", sizeof(radTheader));
    
    logFmsg(3,"my size was 0x%x", msg_len);
    
    logFmsg(3, "code was: %d", code);
    logFmsg(3, "id was: %d", id);
    logFmsg(3, "len was: %d", len);
    
    logFmsg(3,"head %d", sizeof(radTattrib_header));
    
    for(attrib_offset=0;attrib_offset<header->len-20;) {
        logFmsg(3,"Offset is %d", attrib_offset);
        
        ah=(msg+20)+attrib_offset;

        logFmsg(3,"attrib: type %u len %u", ah->type, ah->len);
        
        radTattrib_def *ad=radFattrib_find_idx(ah->type);
        
        if(ad==NULL) {
            logFmsg(3,"Unable to find attribute defenition.");
        } else {
            unsigned char buf[1024];
            size_t buf_len;
            
            if(radFattrib_value_format(buf,1024,(unsigned char *)ah+2,
                                       ah->len-2,ad->type,&buf_len)==0) {
                logFmsg(3,"value: %s len: %d", buf, buf_len);

                lstFset_badd(req->req_set, ad->name, strlen(ad->name), buf, buf_len);
            } else {
                logFmsg(3,"radFattrib_value_format() failed.");
            }
            logFmsg(3,"Attrib %s", ad->name);
        }
        attrib_offset+=ah->len;
    }

    return req;
}

static void radFrunloop(void)
{
    unsigned char msg[1024];
    struct sockaddr_in sa;
    size_t sa_len;
    int ret;
    radTtrans *trans;
    int radius_auth = dlFpipeline_getid("radius_auth");
    
    while (1) {
        ret =
            netFudp_recv(radGsock, (void *) &msg, sizeof(msg), &sa,
                         &sa_len);
        trans=radFparse(msg, ret);

        ret=dlFpipeline_exec(trans,radius_auth,NULL,NULL);

        radFtrans_free(trans);
    }
}

int radFinit(void *p, lstTset * opt)
{
    uint16_t my_port=0;

    radFconfig();
    
    radGsock = netFudp_open(&my_port, 2550, "radius");

    if (radGsock < 0) {
        logFmsg(2, "mod/radius: Unable to open radius UDP socket.");
        exit(1);
    }

    logFmsg(3, "mod/radius: Opened UDP socket %d on port %d", radGsock, my_port);

    radFrunloop();

    exit(1);
}

#define AUTH_VECTOR_LEN         16
#define AUTH_PASS_LEN           16
#define AUTH_MAXPASS_LEN        48


/* Given the authentication identifier in the protocol
 * and the shared-secret, calculates a digest.
 */
static void radFcalc_digest(
    unsigned char *digest,
    unsigned char *secret,
    unsigned char *vector)
{
    unsigned char  buffer[128];
    int     secretlen;

    logFmsg(3,"Vector is:");
    utlFhexdump(vector,AUTH_VECTOR_LEN);
    
        /* Use the secret to setup the decryption digest */
    memset(buffer, 0, sizeof(buffer));
    secretlen = strlen((char *)secret);
    memcpy((char *)buffer, (char *)secret,secretlen);
    memcpy(buffer + secretlen, vector, AUTH_VECTOR_LEN);

    logFmsg(3,"Calculated string to MD5:");
    utlFhexdump(buffer,secretlen+AUTH_VECTOR_LEN);
    
    md5_calc(digest, buffer, secretlen + AUTH_VECTOR_LEN);

    logFmsg(3,"Calculated MD5 value:");
    
    utlFhexdump(digest,16);
    
    memset(buffer, 0, secretlen+AUTH_VECTOR_LEN);
    
    return;
}

/* I am not entirely convinced that this function works properly
 * for passwords that are >16 bytes long.
 */

static int radFdecrypt_password(
    radTtrans *t,
    unsigned char *buf,
    size_t buf_len,
    unsigned char *data,
    size_t data_len)
{

    int i;
    int j;
    char hold_vector[AUTH_VECTOR_LEN];
    char vector[AUTH_VECTOR_LEN];
    char pw_digest[16];
    char *string;
    char *ptr;

    bzero(pw_digest,16);
    
    radFcalc_digest(pw_digest,"foo",t->auth);
    
    if(buf_len<data_len) return -1;
    
    memFcopy(data,buf,data_len);
    ptr=string=buf;
    
    for(i=0;i<data_len;i+=AUTH_PASS_LEN) {
            /* Decrypt from the digest */
        for(j=0;j<AUTH_PASS_LEN;j++) {
            *ptr^=pw_digest[j];
            ptr++;
        }
        
            /* Calculate the next digest if necessary */
        if(i+AUTH_PASS_LEN<data_len) {
            radFcalc_digest(pw_digest,"foo",hold_vector);
        }
    }
    *ptr=0;
    
    logFmsg(3,"Decrypted string: %s", string);
    
    return STATUS_OK;
}

int radiusFdump(radTtrans *t, lstTset *o) {
    const char *un;

    un=lstFset_get(t->req_set,"User-Name");

    logFmsg(3,"Packet received.");

    if(un!=NULL) {
        logFmsg(3,"Authorization request for user: %s", un);
    }

    return STATUS_OK;
}

int radiusFdecrypt_password(radTtrans *t, lstTset *o) {
    const char *pw;
    size_t pw_len;
    char buf[64];

    pw=lstFset_get(t->req_set,"Password");

    if(pw==NULL) {
        logFmsg(3,"Password not found?");
        return STATUS_ERROR;
    }

    pw_len=lstFset_get_vsize(t->req_set,"Password");

    utlFhexdump(pw,pw_len);

    radFdecrypt_password(t,buf,sizeof(buf),pw,pw_len);

    lstFset_update(t->req_set,"Password",buf);

    pw=lstFset_get(t->req_set,"Password");

    logFmsg(3,"Final password is: %s", pw);
    
    return STATUS_OK;
}

int radFsend_response(radTtrans *t) {
    

}



