/*
 * -----------------------------------------------------------------------------
 *
 * Author: Markus Moeller (markus_moeller at compuserve.com)
 *
 * Copyright (C) 2007 Markus Moeller. All rights reserved.
 *
 *   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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 *
 * -----------------------------------------------------------------------------
 */

#include "squid.h"
#include "util.h"

#ifdef HAVE_LDAP

#include "support.h"

struct gdstruct *init_gd(void);

struct gdstruct *
init_gd(void) {
    struct gdstruct *gdsp;
    gdsp = (struct gdstruct *) xmalloc(sizeof(struct gdstruct));
    gdsp->group = NULL;
    gdsp->domain = NULL;
    gdsp->next = NULL;
    return gdsp;
}

char *utf8dup(struct main_args *margs);

char *
utf8dup(struct main_args *margs)
{
    int c = 0, s;
    size_t n;
    char *src;
    unsigned char *p, *dupp;

    src = margs->glist;
    if (!src)
        return NULL;
    for (n = 0; n < strlen(src); ++n)
        if ((unsigned char) src[n] > 127)
            c++;
    if (c != 0) {
        p = (unsigned char *) xmalloc(strlen(src) + c);
        dupp = p;
        for (n = 0; n < strlen(src); ++n) {
            s = (unsigned char) src[n];
            if (s > 127 && s < 192) {
                *p = 194;
                p++;
                *p = s;
            } else if (s > 191 && s < 256) {
                *p = 195;
                p++;
                *p = s - 64;
            } else
                *p = s;
            p++;
        }
        *p = '\0';
        debug((char *) "%s| %s: INFO: Group %s as UTF-8: %s\n", LogTime(), PROGRAM, src, dupp);
        return (char *) dupp;
    } else
        return xstrdup(src);
}

char *hex_utf_char(struct main_args *margs, int flag);
/*
 * UTF8    = UTF1 / UTFMB
 * UTFMB   = UTF2 / UTF3 / UTF4
 *
 * UTF0    = %x80-BF
 * UTF1    = %x00-7F
 * UTF2    = %xC2-DF UTF0
 * UTF3    = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) /
 * %xED %x80-9F UTF0 / %xEE-EF 2(UTF0)
 * UTF4    = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) /
 * %xF4 %x80-8F 2(UTF0)
 *
 * http://www.utf8-chartable.de/unicode-utf8-table.pl
 */

char *
hex_utf_char(struct main_args *margs, int flag)
{
    char *up;
    char *upd;
    char *ul;
    int a, n, nl, ival, ichar;
    int iUTF2, iUTF3, iUTF4;

    if (flag) {
        up = margs->ulist;
    } else {
        up = margs->tlist;
    }

    if (!up)
        return NULL;

    upd = strrchr(up, '@');
    if (upd)
        a = upd - up;
    else
        a = strlen(up);

    ul = (char *) xmalloc(strlen(up));
    n = 0;
    nl = 0;
    iUTF2 = 0;
    iUTF3 = 0;
    iUTF4 = 0;

    while (n < (int) strlen(up)) {
        if (flag && n == a)
            break;
        if (up[n] == '@') {
            ul[nl] = '@';
            nl++;
            n++;
            continue;
        }
        ival = up[n];
        if (ival > 64 && ival < 71)
            ichar = (ival - 55) * 16;
        else if (ival > 96 && ival < 103)
            ichar = (ival - 87) * 16;
        else if (ival > 47 && ival < 58)
            ichar = (ival - 48) * 16;
        else {
            debug((char *) "%s| %s: WARNING: Invalid Hex value %c\n", LogTime(), PROGRAM, ival);
            if (ul)
                xfree(ul);
            return NULL;
        }


        if (n == a - 1) {
            debug((char *) "%s| %s: WARNING: Invalid Hex UTF-8 string %s\n", LogTime(), PROGRAM, up);
            if (ul)
                xfree(ul);
            return NULL;
        }
        n++;
        ival = up[n];
        if (ival > 64 && ival < 71)
            ichar = ichar + ival - 55;
        else if (ival > 96 && ival < 103)
            ichar = ichar + ival - 87;
        else if (ival > 47 && ival < 58)
            ichar = ichar + ival - 48;
        else {
            debug((char *) "%s| %s: WARNING: Invalid Hex value %c\n", LogTime(), PROGRAM, ival);
            if (ul)
                xfree(ul);
            return NULL;
        }

        if (iUTF2) {
            if (iUTF2 == 0xC2 && ichar > 0x7F && ichar < 0xC0) {
                iUTF2 = 0;
                ul[nl - 1] = ichar;
            } else if (iUTF2 == 0xC3 && ichar > 0x7F && ichar < 0xC0) {
                iUTF2 = 0;
                ul[nl - 1] = ichar + 64;
            } else if (iUTF2 > 0xC3 && iUTF2 < 0xE0 && ichar > 0x7F && ichar < 0xC0) {
                iUTF2 = 0;
                ul[nl] = ichar;
                nl++;
            } else {
                iUTF2 = 0;
                ul[nl] = ichar;
                ul[nl + 1] = '\0';
                debug((char *) "%s| %s: WARNING: Invalid UTF-8 sequence for Unicode %s\n", LogTime(), PROGRAM, ul);
                if (ul)
                    xfree(ul);
                return NULL;
            }
        } else if (iUTF3) {
            if (iUTF3 == 0xE0 && ichar > 0x9F && ichar < 0xC0) {
                iUTF3 = 1;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF3 > 0xE0 && iUTF3 < 0xED && ichar > 0x7F && ichar < 0xC0) {
                iUTF3 = 2;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF3 == 0xED && ichar > 0x7F && ichar < 0xA0) {
                iUTF3 = 3;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF3 > 0xED && iUTF3 < 0xF0 && ichar > 0x7F && ichar < 0xC0) {
                iUTF3 = 4;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF3 > 0 && iUTF3 < 5 && ichar > 0x7F && ichar < 0xC0) {
                iUTF3 = 0;
                ul[nl] = ichar;
                nl++;
            } else {
                iUTF3 = 0;
                ul[nl] = ichar;
                ul[nl + 1] = '\0';
                debug((char *) "%s| %s: WARNING: Invalid UTF-8 sequence for Unicode %s\n", LogTime(), PROGRAM, ul);
                if (ul)
                    xfree(ul);
                return NULL;
            }
        } else if (iUTF4) {
            if (iUTF4 == 0xF0 && ichar > 0x8F && ichar < 0xC0) {
                iUTF4 = 1;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF4 > 0xF0 && iUTF3 < 0xF4 && ichar > 0x7F && ichar < 0xC0) {
                iUTF4 = 2;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF4 == 0xF4 && ichar > 0x7F && ichar < 0x90) {
                iUTF4 = 3;
                ul[nl] = ichar;
                nl++;
            } else if (iUTF4 > 0 && iUTF4 < 5 && ichar > 0x7F && ichar < 0xC0) {
                if (iUTF4 == 4)
                    iUTF4 = 0;
                else
                    iUTF4 = 4;
                ul[nl] = ichar;
                nl++;
            } else {
                iUTF4 = 0;
                ul[nl] = ichar;
                ul[nl + 1] = '\0';
                debug((char *) "%s| %s: WARNING: Invalid UTF-8 sequence for Unicode %s\n", LogTime(), PROGRAM, ul);
                if (ul)
                    xfree(ul);
                return NULL;
            }
        } else if (ichar < 0x80) {
            /* UTF1 */
            ul[nl] = ichar;
            nl++;
        } else if (ichar > 0xC1 && ichar < 0xE0) {
            /* UTF2 (Latin) */
            iUTF2 = ichar;
            ul[nl] = ichar;
            nl++;
        } else if (ichar > 0xDF && ichar < 0xF0) {
            /* UTF3 */
            iUTF3 = ichar;
            ul[nl] = ichar;
            nl++;
        } else if (ichar > 0xEF && ichar < 0xF5) {
            /* UTF4 */
            iUTF4 = ichar;
            ul[nl] = ichar;
            nl++;
        } else {
            ul[nl] = ichar;
            ul[nl + 1] = '\0';
            debug((char *) "%s| %s: WARNING: Invalid UTF-8 sequence for Unicode %s\n", LogTime(), PROGRAM, ul);
            if (ul)
                xfree(ul);
            return NULL;
        }
        n++;
    }

    ul[nl] = '\0';
    if (iUTF2 || iUTF3 || iUTF4) {
        debug((char *) "%s| %s: INFO: iUTF2: %d iUTF3: %d iUTF4: %d\n", LogTime(), PROGRAM, iUTF2, iUTF3, iUTF4);
        debug((char *) "%s| %s: WARNING: Invalid UTF-8 sequence for Unicode %s\n", LogTime(), PROGRAM, ul);
        if (ul)
            xfree(ul);
        return NULL;
    }
    if (flag && upd)
        ul = strcat(ul, upd);
    return ul;
}


int
create_gd(struct main_args *margs)
{
    char *gp, *dp;
    char *hp1, *hp2, *up;
    char *p;
    struct gdstruct *gdsp = NULL, *gdspn = NULL;
    /*
     *  Group list format:
     *
     *     glist=Pattern1[:Pattern2]
     *
     *     Pattern=Group           Group for all domains(including non Kerberos domains using ldap url options) if no
     *                             other group definition for domain exists or users without
     *                             domain information.
     *                             gdstruct.domain=NULL, gdstruct.group=Group
     *
     *  or Pattern=Group@          Group for all Kerberos domains if no other group definition
     *                             exists
     *                             gdstruct.domain="", gdstruct.group=Group
     *
     *  or Pattern=Group@Domain    Group for a specific Kerberos domain
     *                             gdstruct.domain=Domain, gdstruct.group=Group
     *
     *
     */
    hp1 = hex_utf_char(margs, 0);
    hp2 = hex_utf_char(margs, 1);
    up = utf8dup(margs);
    p = up;
    if (hp1) {
        if (hp2) {
            if (up) {
                p = (char *) xmalloc(strlen(up) + strlen(hp1) + strlen(hp2) + 2);
                strcpy(p, up);
                strcat(p, ":");
                strcat(p, hp1);
                strcat(p, ":");
                strcat(p, hp2);
            } else {
                p = (char *) xmalloc(strlen(hp1) + strlen(hp2) + 1);
                strcpy(p, hp1);
                strcat(p, ":");
                strcat(p, hp2);
            }
        } else {
            if (up) {
                p = (char *) xmalloc(strlen(up) + strlen(hp1) + 1);
                strcpy(p, up);
                strcat(p, ":");
                strcat(p, hp1);
            } else
                p = hp1;
        }
    } else {
        if (hp2) {
            if (up) {
                p = (char *) xmalloc(strlen(up) + strlen(hp2) + 1);
                strcpy(p, up);
                strcat(p, ":");
                strcat(p, hp2);
            } else
                p = hp2;
        } else
            p = up;
    }
    gp = p;
    debug((char *) "%s| %s: INFO: Group list %s\n", LogTime(), PROGRAM, p ? p : "NULL");
    dp = NULL;

    if (!p) {
        debug((char *) "%s| %s: ERROR: No groups defined.\n", LogTime(), PROGRAM);
        return (1);
    }
    while (*p) {		/* loop over group list */
        if (*p == '\n' || *p == '\r') {		/* Ignore CR and LF if exist */
            p++;
            continue;
        }
        if (*p == '@') {	/* end of group name - start of domain name */
            if (p == gp) {	/* empty group name not allowed */
                debug((char *) "%s| %s: ERROR: No group defined for domain %s\n", LogTime(), PROGRAM, p);
                return (1);
            }
            *p = '\0';
            p++;
            gdsp = init_gd();
            gdsp->group = gp;
            if (gdspn)		/* Have already an existing structure */
                gdsp->next = gdspn;
            dp = p;		/* after @ starts new domain name */
        } else if (*p == ':') {	/* end of group name or end of domain name */
            if (p == gp) {	/* empty group name not allowed */
                debug((char *) "%s| %s: ERROR: No group defined for domain %s\n", LogTime(), PROGRAM, p);
                return (1);
            }
            *p = '\0';
            p++;
            if (dp) {		/* end of domain name */
                gdsp->domain = xstrdup(dp);
                dp = NULL;
            } else {		/* end of group name and no domain name */
                gdsp = init_gd();
                gdsp->group = gp;
                if (gdspn)	/* Have already an existing structure */
                    gdsp->next = gdspn;
            }
            gdspn = gdsp;
            gp = p;		/* after : starts new group name */
            debug((char *) "%s| %s: INFO: Group %s  Domain %s\n", LogTime(), PROGRAM, gdsp->group, gdsp->domain ? gdsp->domain : "NULL");
        } else
            p++;
    }
    if (p == gp) {		/* empty group name not allowed */
        debug((char *) "%s| %s: ERROR: No group defined for domain %s\n", LogTime(), PROGRAM, p);
        return (1);
    }
    if (dp) {			/* end of domain name */
        gdsp->domain = xstrdup(dp);
    } else {			/* end of group name and no domain name */
        gdsp = init_gd();
        gdsp->group = gp;
        if (gdspn)		/* Have already an existing structure */
            gdsp->next = gdspn;
    }
    debug((char *) "%s| %s: INFO: Group %s  Domain %s\n", LogTime(), PROGRAM, gdsp->group, gdsp->domain ? gdsp->domain : "NULL");

    margs->groups = gdsp;
    return (0);
}
#endif