/*
    SSSD

    ID-mapping library - conversion utilities

    Authors:
        Sumit Bose <sbose@redhat.com>

    Copyright (C) 2012 Red Hat

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>

#include "lib/idmap/sss_idmap.h"
#include "lib/idmap/sss_idmap_private.h"
#include "util/util.h"
#include "util/sss_endian.h"

#define SID_ID_AUTHS 6
#define SID_SUB_AUTHS 15
struct sss_dom_sid {
        uint8_t sid_rev_num;
        int8_t num_auths;                  /* [range(0,15)] */
        uint8_t id_auth[SID_ID_AUTHS];     /* highest order byte has index 0 */
        uint32_t sub_auths[SID_SUB_AUTHS]; /* host byte-order */
};

enum idmap_error_code sss_idmap_bin_sid_to_dom_sid(struct sss_idmap_ctx *ctx,
                                                   const uint8_t *bin_sid,
                                                   size_t length,
                                                   struct sss_dom_sid **_dom_sid)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid;
    size_t i = 0;
    size_t p = 0;
    uint32_t val;

    CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID);

    if (length > sizeof(struct sss_dom_sid)) return IDMAP_SID_INVALID;

    dom_sid = ctx->alloc_func(sizeof(struct sss_dom_sid), ctx->alloc_pvt);
    if (dom_sid == NULL) {
        return IDMAP_OUT_OF_MEMORY;
    }
    memset(dom_sid, 0, sizeof(struct sss_dom_sid));

    /* Safely copy in the SID revision number */
    dom_sid->sid_rev_num = (uint8_t) *(bin_sid + p);
    p++;

    /* Safely copy in the number of sub auth values */
    dom_sid->num_auths = (uint8_t) *(bin_sid + p);
    p++;

    /* Make sure we aren't being told to read more bin_sid
     * than can fit in the structure
     */
    if (dom_sid->num_auths > SID_SUB_AUTHS) {
        err = IDMAP_SID_INVALID;
        goto done;
    }

    /* Safely copy in the id_auth values */
    for (i = 0; i < SID_ID_AUTHS; i++) {
        dom_sid->id_auth[i] = (uint8_t) *(bin_sid + p);
        p++;
    }

    /* Safely copy in the sub_auths values */
    for (i = 0; i < dom_sid->num_auths; i++) {
        /* SID sub auth values in Active Directory are stored little-endian,
         * we store them in host order */
        SAFEALIGN_COPY_UINT32(&val, bin_sid + p, &p);
        dom_sid->sub_auths[i] = le32toh(val);
    }

    *_dom_sid = dom_sid;
    err = IDMAP_SUCCESS;

done:
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(dom_sid, ctx->alloc_pvt);
    }
    return err;
}

enum idmap_error_code sss_idmap_dom_sid_to_bin_sid(struct sss_idmap_ctx *ctx,
                                                   struct sss_dom_sid *dom_sid,
                                                   uint8_t **_bin_sid,
                                                   size_t *_length)
{
    enum idmap_error_code err;
    uint8_t *bin_sid;
    size_t length;
    size_t i = 0;
    size_t p = 0;
    uint32_t val;

    CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID);

    if (dom_sid->num_auths > SID_SUB_AUTHS) {
        return IDMAP_SID_INVALID;
    }

    length = 2 + SID_ID_AUTHS + dom_sid->num_auths * 4;

    bin_sid = ctx->alloc_func(length, ctx->alloc_pvt);
    if (bin_sid == NULL) {
        return IDMAP_OUT_OF_MEMORY;
    }

    bin_sid[p] = dom_sid->sid_rev_num;
    p++;

    bin_sid[p] = dom_sid->num_auths;
    p++;

    for (i = 0; i < SID_ID_AUTHS; i++) {
        bin_sid[p] = dom_sid->id_auth[i];
        p++;
    }

    for (i = 0; i < dom_sid->num_auths; i++) {
        if (p + sizeof(uint32_t) > length) {
            err = IDMAP_SID_INVALID;
            goto done;
        }
        val = htole32(dom_sid->sub_auths[i]);
        SAFEALIGN_COPY_UINT32(bin_sid + p, &val, &p);
    }

    *_bin_sid = bin_sid;
    *_length = length;

    err = IDMAP_SUCCESS;
done:
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(bin_sid, ctx->alloc_pvt);
    }
    return err;
}

enum idmap_error_code sss_idmap_dom_sid_to_sid(struct sss_idmap_ctx *ctx,
                                               struct sss_dom_sid *dom_sid,
                                               char **_sid)
{
    enum idmap_error_code err;
    char *sid_buf;
    size_t sid_buf_len;
    char *p;
    int nc;
    int8_t i;
    uint32_t id_auth_val = 0;

    if (dom_sid->num_auths > SID_SUB_AUTHS) {
        return IDMAP_SID_INVALID;
    }

    sid_buf_len = 25 + dom_sid->num_auths * 11;
    sid_buf = ctx->alloc_func(sid_buf_len, ctx->alloc_pvt);
    if (sid_buf == NULL) {
        return IDMAP_OUT_OF_MEMORY;
    }
    memset(sid_buf, 0, sid_buf_len);

    /* Only 32bits are used for the string representation */
    id_auth_val = (dom_sid->id_auth[2] << 24) +
                  (dom_sid->id_auth[3] << 16) +
                  (dom_sid->id_auth[4] << 8) +
                  (dom_sid->id_auth[5]);

    nc = snprintf(sid_buf, sid_buf_len, "S-%u-%lu", dom_sid->sid_rev_num,
                                                    (unsigned long) id_auth_val);
    if (nc < 0 || nc >= sid_buf_len) {
        err = IDMAP_SID_INVALID;
        goto done;
    }


    /* Loop through the sub-auths, if any, prepending a hyphen
     * for each one.
     */
    p = sid_buf;
    for (i = 0; i < dom_sid->num_auths ; i++) {
        p += nc;
        sid_buf_len -= nc;

        nc = snprintf(p, sid_buf_len, "-%lu",
                                      (unsigned long) dom_sid->sub_auths[i]);
        if (nc < 0 || nc >= sid_buf_len) {
            err = IDMAP_SID_INVALID;
            goto done;
        }
    }

    *_sid = sid_buf;
    err = IDMAP_SUCCESS;

done:
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(sid_buf, ctx->alloc_pvt);
    }

    return err;
}

enum idmap_error_code sss_idmap_sid_to_dom_sid(struct sss_idmap_ctx *ctx,
                                               const char *sid,
                                               struct sss_dom_sid **_dom_sid)
{
    enum idmap_error_code err;
    unsigned long ul;
    char *r;
    char *end;
    struct sss_dom_sid *dom_sid;

    CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID);

    if (sid == NULL || (sid[0] != 'S' && sid[0] != 's') || sid[1] != '-') {
            return IDMAP_SID_INVALID;
    }

    dom_sid = ctx->alloc_func(sizeof(struct sss_dom_sid), ctx->alloc_pvt);
    if (dom_sid == NULL) {
        return IDMAP_OUT_OF_MEMORY;
    }
    memset(dom_sid, 0, sizeof(struct sss_dom_sid));


    if (!isdigit(sid[2])) {
        err = IDMAP_SID_INVALID;
        goto done;
    }
    errno = 0;
    ul = strtoul(sid + 2, &r, 10);
    if (errno != 0 || r == NULL || *r != '-' || ul > UINT8_MAX) {
        err = IDMAP_SID_INVALID;
        goto done;
    }
    dom_sid->sid_rev_num = (uint8_t) ul;
    r++;

    if (!isdigit(*r)) {
        err = IDMAP_SID_INVALID;
        goto done;
    }
    errno = 0;
    ul = strtoul(r, &r, 10);
    if (errno != 0 || r == NULL || ul > UINT32_MAX) {
        err = IDMAP_SID_INVALID;
        goto done;
    }

    /* id_auth in the string should always be <2^32 in decimal */
    /* store values in the same order as the binary representation */
    dom_sid->id_auth[0] = 0;
    dom_sid->id_auth[1] = 0;
    dom_sid->id_auth[2] = (ul & 0xff000000) >> 24;
    dom_sid->id_auth[3] = (ul & 0x00ff0000) >> 16;
    dom_sid->id_auth[4] = (ul & 0x0000ff00) >> 8;
    dom_sid->id_auth[5] = (ul & 0x000000ff);

    if (*r == '\0') {
        /* no sub auths given */
        err = IDMAP_SUCCESS;
        goto done;
    }

    if (*r != '-') {
        err = IDMAP_SID_INVALID;
        goto done;
    }

    do {
        if (dom_sid->num_auths >= SID_SUB_AUTHS) {
            err = IDMAP_SID_INVALID;
            goto done;
        }

        r++;
        if (!isdigit(*r)) {
            err = IDMAP_SID_INVALID;
            goto done;
        }

        errno = 0;
        ul = strtoul(r, &end, 10);
        if (errno != 0 || ul > UINT32_MAX || end == NULL ||
            (*end != '\0' && *end != '-')) {
            err = IDMAP_SID_INVALID;
            goto done;
        }

        dom_sid->sub_auths[dom_sid->num_auths++] = ul;

        r = end;
    } while (*r != '\0');

    err = IDMAP_SUCCESS;

done:
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(dom_sid, ctx->alloc_pvt);
    } else {
        *_dom_sid = dom_sid;
    }

    return err;
}

enum idmap_error_code sss_idmap_sid_to_bin_sid(struct sss_idmap_ctx *ctx,
                                               const char *sid,
                                               uint8_t **_bin_sid,
                                               size_t *_length)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid = NULL;
    size_t length;
    uint8_t *bin_sid = NULL;

    err = sss_idmap_sid_to_dom_sid(ctx, sid, &dom_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    err = sss_idmap_dom_sid_to_bin_sid(ctx, dom_sid, &bin_sid, &length);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    *_length = length;
    *_bin_sid = bin_sid;
    err = IDMAP_SUCCESS;

done:
    ctx->free_func(dom_sid, ctx->alloc_pvt);
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(bin_sid, ctx->alloc_pvt);
    }

    return err;
}

enum idmap_error_code sss_idmap_bin_sid_to_sid(struct sss_idmap_ctx *ctx,
                                               const uint8_t *bin_sid,
                                               size_t length,
                                               char **_sid)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid = NULL;
    char *sid = NULL;

    err = sss_idmap_bin_sid_to_dom_sid(ctx, bin_sid, length, &dom_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    err = sss_idmap_dom_sid_to_sid(ctx, dom_sid, &sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    *_sid = sid;
    err = IDMAP_SUCCESS;

done:
    ctx->free_func(dom_sid, ctx->alloc_pvt);
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(sid, ctx->alloc_pvt);
    }

    return err;
}

enum idmap_error_code sss_idmap_sid_to_smb_sid(struct sss_idmap_ctx *ctx,
                                               const char *sid,
                                               struct dom_sid **_smb_sid)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid = NULL;
    struct dom_sid *smb_sid = NULL;

    err = sss_idmap_sid_to_dom_sid(ctx, sid, &dom_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    err = sss_idmap_dom_sid_to_smb_sid(ctx, dom_sid, &smb_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    *_smb_sid = smb_sid;
    err = IDMAP_SUCCESS;

done:
    ctx->free_func(dom_sid, ctx->alloc_pvt);
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(smb_sid, ctx->alloc_pvt);
    }

    return err;
}

enum idmap_error_code sss_idmap_smb_sid_to_sid(struct sss_idmap_ctx *ctx,
                                               struct dom_sid *smb_sid,
                                               char **_sid)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid = NULL;
    char *sid = NULL;

    err = sss_idmap_smb_sid_to_dom_sid(ctx, smb_sid, &dom_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    err = sss_idmap_dom_sid_to_sid(ctx, dom_sid, &sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    *_sid = sid;
    err = IDMAP_SUCCESS;

done:
    ctx->free_func(dom_sid, ctx->alloc_pvt);
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(sid, ctx->alloc_pvt);
    }

    return err;
}

enum idmap_error_code sss_idmap_dom_sid_to_smb_sid(struct sss_idmap_ctx *ctx,
                                                   struct sss_dom_sid *dom_sid,
                                                   struct dom_sid **_smb_sid)
{
    struct dom_sid *smb_sid;
    size_t c;

    smb_sid = ctx->alloc_func(sizeof(struct dom_sid), ctx->alloc_pvt);
    if (smb_sid == NULL) {
        return IDMAP_OUT_OF_MEMORY;
    }
    memset(smb_sid, 0, sizeof(struct dom_sid));

    smb_sid->sid_rev_num = dom_sid->sid_rev_num;
    smb_sid->num_auths = dom_sid->num_auths;
    for (c = 0; c < SID_ID_AUTHS; c++) {
        smb_sid->id_auth[c] = dom_sid->id_auth[c];
    }
    for (c = 0; c < SID_SUB_AUTHS; c++) {
        smb_sid->sub_auths[c] = dom_sid->sub_auths[c];
    }

    *_smb_sid = smb_sid;

    return IDMAP_SUCCESS;
}

enum idmap_error_code sss_idmap_smb_sid_to_dom_sid(struct sss_idmap_ctx *ctx,
                                                   struct dom_sid *smb_sid,
                                                   struct sss_dom_sid **_dom_sid)
{
    struct sss_dom_sid *dom_sid;
    size_t c;

    dom_sid = ctx->alloc_func(sizeof(struct sss_dom_sid), ctx->alloc_pvt);
    if (dom_sid == NULL) {
        return IDMAP_OUT_OF_MEMORY;
    }
    memset(dom_sid, 0, sizeof(struct sss_dom_sid));

    dom_sid->sid_rev_num = smb_sid->sid_rev_num;
    dom_sid->num_auths = smb_sid->num_auths;
    for (c = 0; c < SID_ID_AUTHS; c++) {
        dom_sid->id_auth[c] = smb_sid->id_auth[c];
    }
    for (c = 0; c < SID_SUB_AUTHS; c++) {
        dom_sid->sub_auths[c] = smb_sid->sub_auths[c];
    }

    *_dom_sid = dom_sid;

    return IDMAP_SUCCESS;
}

enum idmap_error_code sss_idmap_bin_sid_to_smb_sid(struct sss_idmap_ctx *ctx,
                                                   const uint8_t *bin_sid,
                                                   size_t length,
                                                   struct dom_sid **_smb_sid)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid = NULL;
    struct dom_sid *smb_sid = NULL;

    err = sss_idmap_bin_sid_to_dom_sid(ctx, bin_sid, length, &dom_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    err = sss_idmap_dom_sid_to_smb_sid(ctx, dom_sid, &smb_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    *_smb_sid = smb_sid;
    err = IDMAP_SUCCESS;

done:
    ctx->free_func(dom_sid, ctx->alloc_pvt);
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(smb_sid, ctx->alloc_pvt);
    }

    return err;
}

enum idmap_error_code sss_idmap_smb_sid_to_bin_sid(struct sss_idmap_ctx *ctx,
                                                   struct dom_sid *smb_sid,
                                                   uint8_t **_bin_sid,
                                                   size_t *_length)
{
    enum idmap_error_code err;
    struct sss_dom_sid *dom_sid = NULL;
    uint8_t *bin_sid = NULL;
    size_t length;

    err = sss_idmap_smb_sid_to_dom_sid(ctx, smb_sid, &dom_sid);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    err = sss_idmap_dom_sid_to_bin_sid(ctx, dom_sid, &bin_sid, &length);
    if (err != IDMAP_SUCCESS) {
        goto done;
    }

    *_bin_sid = bin_sid;
    *_length = length;
    err = IDMAP_SUCCESS;

done:
    ctx->free_func(dom_sid, ctx->alloc_pvt);
    if (err != IDMAP_SUCCESS) {
        ctx->free_func(bin_sid, ctx->alloc_pvt);
    }

    return err;
}
