/*
 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/*
 * -----------------------------------------------------------------------------
 *
 * 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"

#if HAVE_LDAP

#include "support.h"

struct gdstruct *init_gd(void);
void free_gd(struct gdstruct *gdsp);

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

void
free_gd(struct gdstruct *gdsp)
{
    while (gdsp) {
        struct gdstruct *gdspn = gdsp->next;
        xfree(gdsp->group);
        xfree(gdsp->domain);
        xfree(gdsp);
        gdsp = gdspn;
    }
}

char *utf8dup(struct main_args *margs);

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

    src = margs->glist;
    if (!src)
        return nullptr;
    for (n = 0; n < strlen(src); ++n)
        if ((unsigned char) src[n] > 127)
            ++c;
    if (c != 0) {
        unsigned char *dupp;
        p = (unsigned char *) xmalloc(strlen(src) + c);
        dupp = p;
        for (n = 0; n < strlen(src); ++n) {
            unsigned char s;
            s = (unsigned char) src[n];
            if (s > 127 && s < 192) {
                *p = 194;
                ++p;
                *p = s;
            } else if (s > 191) {
                *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)
{
    int ival, ichar;
    int iUTF2, iUTF3, iUTF4;

    char *up = (flag ? margs->ulist : margs->tlist);
    if (!up)
        return nullptr;

    char *upd = strrchr(up, '@');
    size_t a = (upd ? (size_t)(upd - up) : strlen(up) );

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

    while (n < 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);
            xfree(ul);
            return nullptr;
        }

        if (n == a - 1) {
            debug((char *) "%s| %s: WARNING: Invalid Hex UTF-8 string %s\n", LogTime(), PROGRAM, up);
            xfree(ul);
            return nullptr;
        }
        ++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);
            xfree(ul);
            return nullptr;
        }

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

int
create_gd(struct main_args *margs)
{
    char *gp, *dp;
    char *p;
    struct gdstruct *gdsp = nullptr, *gdspn = nullptr;
    /*
     *  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
     *
     *
     */
    char *hp1 = hex_utf_char(margs, 0);
    char *hp2 = hex_utf_char(margs, 1);
    char *up = utf8dup(margs);

    // NP: will point to the start of a temporary assembly buffer used by 'p' and 'gp'
    //     for catenation of the hp1, hp2, and up buffer contents from above.
    //     necessary for xfree() because both p and gp move over the assembly area
    char *gpbuf = nullptr;

    // release the allocated UTF decoding buffers
#define cleanup() { \
    xfree(gpbuf); \
    xfree(hp1); \
    xfree(hp2); \
    xfree(up); \
    free_gd(gdsp); \
 }

    p = up;
    if (hp1) {
        if (hp2) {
            if (up) {
                gpbuf = p = (char *) xmalloc(strlen(up) + strlen(hp1) + strlen(hp2) + 2);
                strcpy(p, up);
                strcat(p, ":");
                strcat(p, hp1);
                strcat(p, ":");
                strcat(p, hp2);
            } else {
                gpbuf = p = (char *) xmalloc(strlen(hp1) + strlen(hp2) + 1);
                strcpy(p, hp1);
                strcat(p, ":");
                strcat(p, hp2);
            }
        } else {
            if (up) {
                gpbuf = p = (char *) xmalloc(strlen(up) + strlen(hp1) + 1);
                strcpy(p, up);
                strcat(p, ":");
                strcat(p, hp1);
            } else
                p = hp1;
        }
    } else {
        if (hp2) {
            if (up) {
                gpbuf = 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 = nullptr;

    if (!p) {
        debug((char *) "%s| %s: ERROR: No groups defined.\n", LogTime(), PROGRAM);
        cleanup();
        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);
                cleanup();
                return (1);
            }
            if (dp) {  /* end of domain name - twice */
                debug((char *) "%s| %s: @ is not allowed in group name %s@%s\n",LogTime(), PROGRAM,gp,dp);
                cleanup();
                return(1);
            }
            *p = '\0';
            ++p;
            gdsp = init_gd();
            gdsp->group = xstrdup(gp);
            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);
                cleanup();
                return (1);
            }
            *p = '\0';
            ++p;
            if (dp) {       /* end of domain name */
                gdsp->domain = xstrdup(dp);
                dp = nullptr;
            } else {        /* end of group name and no domain name */
                gdsp = init_gd();
                gdsp->group = xstrdup(gp);
                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);
        cleanup();
        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 = xstrdup(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;
    gdsp = nullptr; // prevent the cleanup() deallocating it.
    cleanup();
    return (0);
}
#endif

