/*
 * $Id$
 *
 * Layer Two Tunnelling Protocol Daemon
 * Copyright (C) 1998 Adtran, Inc.
 * Copyright (C) 2002 Jeff McAdams
 *
 * Mark Spencer
 *
 * This software is distributed under the terms
 * of the GPL, which you should have received
 * along with this source.
 *
 * Authorization, Accounting, and Access control
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include "l2tp.h"

extern void bufferDump (char *, int);

/* FIXME: Accounting? */

static struct addr_ent *uaddr[ADDR_HASH_SIZE];

void init_addr ()
{
    int x;
    for (x = 0; x < ADDR_HASH_SIZE; x++)
        uaddr[x] = NULL;
}

static int ip_used (unsigned int addr)
{
    struct addr_ent *tmp;
    tmp = uaddr[addr % ADDR_HASH_SIZE];
    while (tmp)
    {
        if (tmp->addr == addr)
            return -1;
        tmp = tmp->next;
    }
    return 0;
}

void mk_challenge (char *c, int length)
{
    get_entropy(c, length);

    /* int x;
    int *s = (int *) c;
    for (x = 0; x < length / sizeof (int); x++)
        s[x] = rand (); */
}

void reserve_addr (unsigned int addr)
{
    /* Mark this address as in use */
    struct addr_ent *tmp, *tmp2;
    addr = ntohl (addr);
    if (ip_used (addr))
        return;
    tmp = uaddr[addr % ADDR_HASH_SIZE];
    tmp2 = (struct addr_ent *) malloc (sizeof (struct addr_ent));
    uaddr[addr % ADDR_HASH_SIZE] = tmp2;
    tmp2->next = tmp;
    tmp2->addr = addr;
}

void unreserve_addr (unsigned int addr)
{
    struct addr_ent *tmp, *last = NULL, *z;
    addr = ntohl (addr);
    tmp = uaddr[addr % ADDR_HASH_SIZE];
    while (tmp)
    {
        if (tmp->addr == addr)
        {
            if (last)
            {
                last->next = tmp->next;
            }
            else
            {
                uaddr[addr % ADDR_HASH_SIZE] = tmp->next;
            }
            z = tmp;
            tmp = tmp->next;
            free (z);
        }
        else
        {
            last = tmp;
            tmp = tmp->next;
        }
    }
}

unsigned int get_addr (struct iprange *ipr)
{
    unsigned int x, y;
    int status;
    struct iprange *ipr2;
    while (ipr)
    {
        if (ipr->sense == SENSE_ALLOW)
            for (x = ntohl (ipr->start); x <= ntohl (ipr->end); x++)
            {
                /* Found an IP in an ALLOW range, check to be sure it is
                   consistant through the remaining regions */
                if (!ip_used (x))
                {
                    status = SENSE_ALLOW;
                    ipr2 = ipr->next;
                    while (ipr2)
                    {
                        if ((x >= ntohl (ipr2->start))
                            && (x <= ntohl (ipr2->end)))
                            status = ipr2->sense;
                        ipr2 = ipr2->next;
                    }
                    y = htonl (x);
                    if (status == SENSE_ALLOW)
                        return y;
                }
            };
        ipr = ipr->next;
    }
    return 0;
}

int get_secret (char *us, char *them, char *secret, int size)
{
    FILE *f;
    char buf[STRLEN];
    char *u, *t, *s;
    int num = 0;
    f = fopen (gconfig.authfile, "r");
    if (!f)
    {
        log (LOG_WARN, "%s : Unable to open '%s' for authentication\n",
             __FUNCTION__, gconfig.authfile);
        return 0;
    }
    while (!feof (f))
    {
        num++;
        fgets (buf, sizeof (buf), f);
        if (feof (f))
            break;
        /* Strip comments */
        for (t = buf; *t; t++)
            *t = ((*t == '#') || (*t == ';')) ? 0 : *t;
        /* Strip trailing whitespace */
        for (t = buf + strlen (buf) - 1; (t >= buf) && (*t < 33); t--)
            *t = 0;
        if (!strlen (buf))
            continue;           /* Empty line */
        u = buf;
        while (*u && (*u < 33))
            u++;
        /* us */
        if (!*u)
        {
            log (LOG_WARN,
                 "%s: Invalid authentication info (no us), line %d\n",
                 __FUNCTION__, num);
            continue;
        }
        t = u;
        while (*t > 32)
            t++;
        *(t++) = 0;
        while (*t && (*t < 33))
            t++;
        /* them */
        if (!*t)
        {
            log (LOG_WARN,
                 "%s: Invalid authentication info (nothem), line %d\n",
                 __FUNCTION__, num);
            continue;
        }
        s = t;
        while (*s > 33)
            s++;
        *(s++) = 0;
        while (*s && (*s < 33))
            s++;
        if (!*s)
        {
            log (LOG_WARN,
                 "%s: Invalid authentication info (no secret), line %d\n",
                 __FUNCTION__, num);
            continue;
        }
        if ((!strcasecmp (u, us) || !strcasecmp (u, "*")) &&
            (!strcasecmp (t, them) || !strcasecmp (t, "*")))
        {
#ifdef DEBUG_AUTH
            log (LOG_DEBUG,
                 "%s: we are '%s', they are '%s', secret is '%s'\n",
                 __FUNCTION__, u, t, s);
#endif
            strncpy (secret, s, size);
            return -1;
        }
    }
    return 0;
}

int handle_challenge (struct tunnel *t, struct challenge *chal)
{
    char *us;
    char *them;
    if (!t->lns && !t->lac)
    {
        log (LOG_DEBUG, "%s: No LNS or LAC to handle challenge!\n",
             __FUNCTION__);
        return -1;
    }
#ifdef DEBUG_AUTH
    log (LOG_DEBUG, "%s: making response for tunnel: %d\n", __FUNCTION__,
         t->ourtid);
#endif
    if (t->lns)
    {
        if (t->lns->hostname[0])
            us = t->lns->hostname;
        else
            us = hostname;
        if (t->lns->peername[0])
            them = t->lns->peername;
        else
            them = t->hostname;
    }
    else
    {
        if (t->lac->hostname[0])
            us = t->lac->hostname;
        else
            us = hostname;
        if (t->lac->peername[0])
            them = t->lac->peername;
        else
            them = t->hostname;
    }
    if (!get_secret (us, them, chal->secret, sizeof (chal->secret)))
    {
        log (LOG_DEBUG, "%s: no secret found for us='%s' and them='%s'\n",
             __FUNCTION__, us, them);
        return -1;
    }

#if DEBUG_AUTH
    log (LOG_DEBUG, "*%s: Here comes the chal->ss:\n", __FUNCTION__);
    bufferDump (&chal->ss, 1);

    log (LOG_DEBUG, "%s: Here comes the secret\n", __FUNCTION__);
    bufferDump (chal->secret, strlen (chal->secret));

    log (LOG_DEBUG, "%s: Here comes the challenge\n", __FUNCTION__);
    bufferDump (chal->challenge, strlen (chal->challenge));
#endif

    memset (chal->response, 0, MD_SIG_SIZE);
    MD5Init (&chal->md5);
    MD5Update (&chal->md5, &chal->ss, 1);
    MD5Update (&chal->md5, chal->secret, strlen (chal->secret));
    MD5Update (&chal->md5, chal->challenge, strlen(chal->challenge));
    MD5Final (chal->response, &chal->md5);
#ifdef DEBUG_AUTH
    log (LOG_DEBUG, "response is %X%X%X%X to '%s' and %X%X%X%X, %d\n",
         *((int *) &chal->response[0]),
         *((int *) &chal->response[4]),
         *((int *) &chal->response[8]),
         *((int *) &chal->response[12]),
         chal->secret,
         *((int *) &chal->challenge[0]),
         *((int *) &chal->challenge[4]),
         *((int *) &chal->challenge[8]),
         *((int *) &chal->challenge[12]), chal->ss);
#endif
    chal->state = STATE_CHALLENGED;
    return 0;
}

struct lns *get_lns (struct tunnel *t)
{
    /*
     * Look through our list of LNS's and
     * find a reasonable LNS for this call
     * if one is available
     */
    struct lns *lns;
    struct iprange *ipr;
    int allow, checkdefault = 0;
    /* If access control is disabled, we give the default
       otherwise, we give nothing */
    allow = 0;
    lns = lnslist;
    if (!lns)
    {
        lns = deflns;
        checkdefault = -1;
    }
    while (lns)
    {
        ipr = lns->lacs;
        while (ipr)
        {
            if ((ntohl (t->peer.sin_addr.s_addr) >= ntohl (ipr->start)) &&
                (ntohl (t->peer.sin_addr.s_addr) <= ntohl (ipr->end)))
            {
#ifdef DEBUG_AAA
                log (LOG_DEBUG,
                     "get_lns: Rule %s to %s, sense %s matched %s\n",
                     IPADDY (ipr->start), IPADDY (ipr->end),
                     (ipr->sense ? "allow" : "deny"), IPADDY (t->addr));
#endif
                allow = ipr->sense;
            }
            ipr = ipr->next;
        }
        if (allow)
            return lns;
        lns = lns->next;
        if (!lns && !checkdefault)
        {
            lns = deflns;
            checkdefault = -1;
        }
    }
    if (gconfig.accesscontrol)
        return NULL;
    else
        return deflns;
}

#ifdef DEBUG_HIDDEN
void print_md5 (void *md5)
{
    int *i = (int *) md5;
    log (LOG_DEBUG, "%X%X%X%X\n", i[0], i[1], i[2], i[3], i[4]);
}

inline void print_challenge (struct challenge *chal)
{
    log (LOG_DEBUG, "vector: ");
    print_md5 (chal->vector);
    log (LOG_DEBUG, "secret: %s\n", chal->secret);
}
#endif
void encrypt_avp (struct buffer *buf, _u16 len, struct tunnel *t)
{
    /* Encrypts an AVP of len, at data.  We assume there
       are two "spare bytes" before the data pointer,l but otherwise
       this is just a normal AVP that is about to be returned from
       an avpsend routine */
    struct avp_hdr *new_hdr =
        (struct avp_hdr *) (buf->start + buf->len - len);
    struct avp_hdr *old_hdr =
        (struct avp_hdr *) (buf->start + buf->len - len + 2);
    _u16 length, flags, attr;   /* New length, old flags */
    char *ptr, *end;
    int cnt;
    unsigned char digest[MD_SIG_SIZE];
    unsigned char *previous_segment;

    /* FIXME: Should I pad more randomly? Right now I pad to nearest 16 bytes */
    length =
        ((len - sizeof (struct avp_hdr) + 1) / 16 + 1) * 16 +
        sizeof (struct avp_hdr);
    flags = htons (old_hdr->length) & 0xF000;
    new_hdr->length = htons (length | flags | HBIT);
    new_hdr->vendorid = old_hdr->vendorid;
    new_hdr->attr = attr = old_hdr->attr;
    /* This is really the length field of the hidden sub-format */
    old_hdr->attr = htons (len - sizeof (struct avp_hdr));
    /* Okay, now we've rewritten the header, as it should be.  Let's start
       encrypting the actual data now */
    buf->len -= len;
    buf->len += length;
    /* Back to the beginning of real data, including the original length AVP */

    MD5Init (&t->chal_them.md5);
    MD5Update (&t->chal_them.md5, (void *) &attr, 2);
    MD5Update (&t->chal_them.md5, t->chal_them.secret,
               strlen (t->chal_them.secret));
    MD5Update (&t->chal_them.md5, t->chal_them.vector, VECTOR_SIZE);
    MD5Final (digest, &t->chal_them.md5);

    /* Though not a "MUST" in the spec, our subformat length is always a multiple of 16 */
    ptr = ((char *) new_hdr) + sizeof (struct avp_hdr);
    end = ((char *) new_hdr) + length;
    previous_segment = ptr;
    while (ptr < end)
    {
#if DEBUG_HIDDEN
        log (LOG_DEBUG, "%s: The digest to be XOR'ed\n", __FUNCTION__);
        bufferDump (digest, MD_SIG_SIZE);
        log (LOG_DEBUG, "%s: The plaintext to be XOR'ed\n", __FUNCTION__);
        bufferDump (ptr, MD_SIG_SIZE);
#endif
        for (cnt = 0; cnt < MD_SIG_SIZE; cnt++, ptr++)
        {
            *ptr = *ptr ^ digest[cnt];
        }
#if DEBUG_HIDDEN
        log (LOG_DEBUG, "%s: The result of XOR\n", __FUNCTION__);
        bufferDump (previous_segment, MD_SIG_SIZE);
#endif
        if (ptr < end)
        {
            MD5Init (&t->chal_them.md5);
            MD5Update (&t->chal_them.md5, t->chal_them.secret,
                       strlen (t->chal_them.secret));
            MD5Update (&t->chal_them.md5, previous_segment, MD_SIG_SIZE);
            MD5Final (digest, &t->chal_them.md5);
        }
        previous_segment = ptr;
    }
}

int decrypt_avp (char *buf, struct tunnel *t)
{
    /* Decrypts a hidden AVP pointed to by buf.  The
       new header will be exptected to be two characters
       offset from the old */
    int cnt = 0;
    int len, olen, flags;
    char digest[MD_SIG_SIZE];
    char *ptr, *end;
    _u16 attr;
    struct avp_hdr *old_hdr = (struct avp_hdr *) buf;
    struct avp_hdr *new_hdr = (struct avp_hdr *) (buf + 2);
    int saved_segment_len;      /* maybe less 16; may be used if the cipher is longer than 16 octets */
    char saved_segment[MD_SIG_SIZE];
    ptr = ((char *) old_hdr) + sizeof (struct avp_hdr);
    olen = old_hdr->length & 0x0FFF;
    end = buf + olen;
    if (!t->chal_us.vector)
    {
        log (LOG_DEBUG,
             "decrypt_avp: Hidden bit set, but no random vector specified!\n");
        return -EINVAL;
    }
    /* First, let's decrypt all the data.  We're not guaranteed
       that it will be padded to a 16 byte boundary, so we
       have to be more careful than when encrypting */
    attr = ntohs (old_hdr->attr);
    MD5Init (&t->chal_us.md5);
    MD5Update (&t->chal_us.md5, (void *) &attr, 2);
    MD5Update (&t->chal_us.md5, t->chal_us.secret,
               strlen (t->chal_us.secret));
    MD5Update (&t->chal_us.md5, t->chal_us.vector, t->chal_us.vector_len);
    MD5Final (digest, &t->chal_us.md5);
#ifdef DEBUG_HIDDEN
    log (LOG_DEBUG, "attribute is %d and challenge is: ", attr);
    print_challenge (&t->chal_us);
    log (LOG_DEBUG, "md5 is: ");
    print_md5 (digest);
#endif
    while (ptr < end)
    {
        if (cnt >= MD_SIG_SIZE)
        {
            MD5Init (&t->chal_us.md5);
            MD5Update (&t->chal_us.md5, t->chal_us.secret,
                       strlen (t->chal_us.secret));
            MD5Update (&t->chal_us.md5, saved_segment, MD_SIG_SIZE);
            MD5Final (digest, &t->chal_us.md5);
            cnt = 0;
        }
        /* at the beginning of each segment, we save the current segment (16 octets or less) of cipher 
         * so that the next round of MD5 (if there is a next round) hash could use it 
         */
        if (cnt == 0)
        {
            saved_segment_len =
                (end - ptr < MD_SIG_SIZE) ? (end - ptr) : MD_SIG_SIZE;
            memcpy (saved_segment, ptr, saved_segment_len);
        }
        *ptr = *ptr ^ digest[cnt++];
        ptr++;
    }
    /* Hopefully we're all nice and decrypted now.  Let's rewrite the header. 
       First save the old flags, and get the new stuff */
    flags = old_hdr->length & 0xF000 & ~HBIT;
    len = ntohs (new_hdr->attr) + sizeof (struct avp_hdr);
    if (len > olen - 2)
    {
        log (LOG_DEBUG,
             "decrypt_avp: Decrypted length is too long (%d > %d)\n", len,
             olen - 2);
        return -EINVAL;
    }
    new_hdr->attr = old_hdr->attr;
    new_hdr->vendorid = old_hdr->vendorid;
    new_hdr->length = len | flags;
    return 0;
}
