root/usr/src/common/smbsrv/smb_sid.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
 */

#if !defined(_KERNEL) && !defined(_FAKE_KERNEL)
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <syslog.h>
#else   /* !_KERNEL && !_FAKE_KERNEL */
#include <sys/int_limits.h> /* Needed for _FAKE_KERNEL */
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/sunddi.h>
#endif  /* !_KERNEL && !_FAKE_KERNEL */

#include <smbsrv/smb_sid.h>

static smb_sid_t *smb_sid_alloc(size_t);

/*
 * smb_sid_isvalid
 *
 * Performs a minimal SID validation.
 */
boolean_t
smb_sid_isvalid(smb_sid_t *sid)
{
        if (sid == NULL)
                return (B_FALSE);

        return ((sid->sid_revision == NT_SID_REVISION) &&
            (sid->sid_subauthcnt <= NT_SID_SUBAUTH_MAX));
}

/*
 * smb_sid_len
 *
 * Returns the number of bytes required to hold the sid.
 */
int
smb_sid_len(smb_sid_t *sid)
{
        if (sid == NULL)
                return (0);

        return (sizeof (smb_sid_t) - sizeof (uint32_t)
            + (sid->sid_subauthcnt * sizeof (uint32_t)));
}

/*
 * smb_sid_dup
 *
 * Make a duplicate of the specified sid. The memory for the new sid
 * should be freed by calling smb_sid_free().
 * A pointer to the new sid is returned.
 */
smb_sid_t *
smb_sid_dup(smb_sid_t *sid)
{
        smb_sid_t *new_sid;
        int size;

        if (sid == NULL)
                return (NULL);

        size = smb_sid_len(sid);
        if ((new_sid = smb_sid_alloc(size)) == NULL)
                return (NULL);

        bcopy(sid, new_sid, size);
        return (new_sid);
}


/*
 * smb_sid_splice
 *
 * Make a full sid from a domain sid and a relative id (rid).
 * The memory for the result sid should be freed by calling
 * smb_sid_free(). A pointer to the new sid is returned.
 */
smb_sid_t *
smb_sid_splice(smb_sid_t *domain_sid, uint32_t rid)
{
        smb_sid_t *sid;
        int size;

        if (domain_sid == NULL)
                return (NULL);

        size = smb_sid_len(domain_sid);
        if ((sid = smb_sid_alloc(size + sizeof (rid))) == NULL)
                return (NULL);

        bcopy(domain_sid, sid, size);

        sid->sid_subauth[domain_sid->sid_subauthcnt] = rid;
        ++sid->sid_subauthcnt;

        return (sid);
}

/*
 * smb_sid_getrid
 *
 * Return the Relative Id (RID) of the specified SID. It is the
 * caller's responsibility to ensure that this is an appropriate SID.
 * All we do here is return the last sub-authority from the SID.
 */
int
smb_sid_getrid(smb_sid_t *sid, uint32_t *rid)
{
        if (!smb_sid_isvalid(sid) || (rid == NULL) ||
            (sid->sid_subauthcnt == 0))
                return (-1);

        *rid = sid->sid_subauth[sid->sid_subauthcnt - 1];
        return (0);
}

/*
 * smb_sid_split
 *
 * Take a full sid and split it into a domain sid and a relative id (rid).
 * The domain SID is allocated and a pointer to it will be returned. The
 * RID value is passed back in 'rid' arg if it's not NULL. The allocated
 * memory for the domain SID must be freed by caller.
 */
smb_sid_t *
smb_sid_split(smb_sid_t *sid, uint32_t *rid)
{
        smb_sid_t *domsid;
        int size;

        if (!smb_sid_isvalid(sid) || (sid->sid_subauthcnt == 0))
                return (NULL);

        /*
         * We will reduce sid_subauthcnt by one, because
         * the domain SID does not include the RID.
         */
        size = smb_sid_len(sid) - sizeof (uint32_t);
        if ((domsid = smb_sid_alloc(size)) == NULL)
                return (NULL);

        bcopy(sid, domsid, size);
        domsid->sid_subauthcnt = sid->sid_subauthcnt - 1;

        if (rid)
                *rid = sid->sid_subauth[sid->sid_subauthcnt - 1];

        return (domsid);
}

/*
 * smb_sid_splitstr
 *
 * Takes a full sid in string form and split it into a domain sid and a
 * relative id (rid).
 *
 * IMPORTANT: The original sid is modified in place. This function assumes
 * given SID is in valid string format.
 */
int
smb_sid_splitstr(char *strsid, uint32_t *rid)
{
        char *p;

        if ((p = strrchr(strsid, '-')) == NULL)
                return (-1);

        *p++ = '\0';
        if (rid) {
#if defined(_KERNEL) || defined(_FAKE_KERNEL)
                unsigned long sua = 0;
                (void) ddi_strtoul(p, NULL, 10, &sua);
                *rid = (uint32_t)sua;
#else
                *rid = strtoul(p, NULL, 10);
#endif
        }

        return (0);
}

/*
 * smb_sid_cmp
 *
 * Compare two SIDs and return a boolean result. The checks are ordered
 * such that components that are more likely to differ are checked
 * first. For example, after checking that the SIDs contain the same
 * sid_subauthcnt, we check the sub-authorities in reverse order because
 * the RID is the most likely differentiator between two SIDs, i.e.
 * they are probably going to be in the same domain.
 */
boolean_t
smb_sid_cmp(smb_sid_t *sid1, smb_sid_t *sid2)
{
        int i;

        if (sid1 == NULL || sid2 == NULL)
                return (B_FALSE);

        if (sid1->sid_subauthcnt != sid2->sid_subauthcnt ||
            sid1->sid_revision != sid2->sid_revision)
                return (B_FALSE);

        for (i = sid1->sid_subauthcnt - 1; i >= 0; --i)
                if (sid1->sid_subauth[i] != sid2->sid_subauth[i])
                        return (B_FALSE);

        if (bcmp(&sid1->sid_authority, &sid2->sid_authority, NT_SID_AUTH_MAX))
                return (B_FALSE);

        return (B_TRUE);
}

/*
 * smb_sid_indomain
 *
 * Check if given SID is in given domain.
 */
boolean_t
smb_sid_indomain(smb_sid_t *domain_sid, smb_sid_t *sid)
{
        int i;

        if (sid == NULL || domain_sid == NULL)
                return (B_FALSE);

        if (domain_sid->sid_revision != sid->sid_revision ||
            sid->sid_subauthcnt < domain_sid->sid_subauthcnt)
                return (B_FALSE);

        for (i = domain_sid->sid_subauthcnt - 1; i >= 0; --i)
                if (domain_sid->sid_subauth[i] != sid->sid_subauth[i])
                        return (B_FALSE);

        if (bcmp(&domain_sid->sid_authority, &sid->sid_authority,
            NT_SID_AUTH_MAX))
                return (B_FALSE);

        return (B_TRUE);
}

/*
 * smb_sid_tostr
 *
 * Fill in the passed buffer with the string form of the given
 * binary sid.
 */
void
smb_sid_tostr(const smb_sid_t *sid, char *strsid)
{
        char *p = strsid;
        int i;

        if (sid == NULL || strsid == NULL)
                return;

        (void) sprintf(p, "S-%d-", sid->sid_revision);
        while (*p)
                p++;

        for (i = 0; i < NT_SID_AUTH_MAX; ++i) {
                if (sid->sid_authority[i] != 0 || i == NT_SID_AUTH_MAX - 1) {
                        (void) sprintf(p, "%d", sid->sid_authority[i]);
                        while (*p)
                                p++;
                }
        }

        for (i = 0; i < sid->sid_subauthcnt && i < NT_SID_SUBAUTH_MAX; ++i) {
                (void) sprintf(p, "-%u", sid->sid_subauth[i]);
                while (*p)
                        p++;
        }
}

/*
 * smb_sid_fromstr
 *
 * Converts a SID in string form to a SID structure. There are lots of
 * simplifying assumptions in here. The memory for the SID is allocated
 * as if it was the largest possible SID; the caller is responsible for
 * freeing the memory when it is no longer required. We assume that the
 * string starts with "S-1-" and that the authority is held in the last
 * byte, which should be okay for most situations. It also assumes the
 * sub-authorities are in decimal format.
 *
 * On success, a pointer to a SID is returned. Otherwise a null pointer
 * is returned.
 */
#if defined(_KERNEL) || defined(_FAKE_KERNEL)
smb_sid_t *
smb_sid_fromstr(const char *sidstr)
{
        smb_sid_t *sid;
        smb_sid_t *retsid = NULL;
        const char *p;
        int size;
        uint8_t i;
        unsigned long sua;

        if (sidstr == NULL)
                return (NULL);

        if (strncmp(sidstr, "S-1-", 4) != 0)
                return (NULL);

        sua = 0;
        (void) ddi_strtoul(&sidstr[4], (char **)&p, 10, &sua);

        /*
         * If ddi_strtoul() did the right thing, *p will point at the first '-'
         * after the identifier authority.
         * The IdentifierAuthority can be up to 2^48, but all known ones
         * currently fit into a uint8_t.
         * TODO: support IdentifierAuthorities > 255 (those over UINT32_MAX are
         * hex-formatted).
         */
        if (sua > UINT8_MAX || (*p != '-' && *p != '\0'))
                return (NULL);

        size = sizeof (smb_sid_t) + (NT_SID_SUBAUTH_MAX * sizeof (uint32_t));
        sid = kmem_zalloc(size, KM_SLEEP);
        sid->sid_revision = NT_SID_REVISION;
        sid->sid_authority[5] = (uint8_t)sua;

        for (i = 0; i < NT_SID_SUBAUTH_MAX && *p; ++i) {
                while (*p == '-')
                        ++p;

                if (*p < '0' || *p > '9')
                        goto out;

                sua = 0;
                (void) ddi_strtoul(p, (char **)&p, 10, &sua);
                if (sua > UINT32_MAX)
                        goto out;
                sid->sid_subauth[i] = (uint32_t)sua;

                if (*p != '\0' && *p != '-')
                        goto out;
        }

        sid->sid_subauthcnt = i;
        retsid = smb_sid_dup(sid);

out:
        kmem_free(sid, size);
        return (retsid);
}
#else /* _KERNEL */
smb_sid_t *
smb_sid_fromstr(const char *sidstr)
{
        smb_sid_t *sid;
        const char *p;
        int size;
        uint8_t i;
        unsigned long sua;

        if (sidstr == NULL)
                return (NULL);

        if (strncmp(sidstr, "S-1-", 4) != 0)
                return (NULL);

        sua = strtoul(&sidstr[4], (char **)&p, 10);

        /*
         * If strtoul() did the right thing, *p will point at the first '-'
         * after the identifier authority.
         * The IdentifierAuthority can be up to 2^48, but all known ones
         * currently fit into a uint8_t.
         * TODO: support IdentifierAuthorities > 255 (those over UINT32_MAX are
         * hex-formatted).
         */
        if (sua > UINT8_MAX || (*p != '-' && *p != '\0'))
                return (NULL);

        size = sizeof (smb_sid_t) + (NT_SID_SUBAUTH_MAX * sizeof (uint32_t));

        if ((sid = calloc(size, 1)) == NULL)
                return (NULL);

        sid->sid_revision = NT_SID_REVISION;
        sid->sid_authority[5] = (uint8_t)sua;

        for (i = 0; i < NT_SID_SUBAUTH_MAX && *p; ++i) {
                while (*p == '-')
                        ++p;

                if (*p < '0' || *p > '9') {
                        free(sid);
                        return (NULL);
                }

                sid->sid_subauth[i] = strtoul(p, (char **)&p, 10);
                if (*p != '\0' && *p != '-') {
                        free(sid);
                        return (NULL);
                }
        }

        sid->sid_subauthcnt = i;
        return (sid);
}
#endif /* _KERNEL */

/*
 * smb_sid_type2str
 *
 * Returns the text name for a SID_NAME_USE value. The SID_NAME_USE
 * provides the context for a SID, i.e. the type of resource to which
 * it refers.
 */
char *
smb_sid_type2str(uint16_t snu_id)
{
        static char *snu_name[] = {
                "SidTypeSidPrefix",
                "SidTypeUser",
                "SidTypeGroup",
                "SidTypeDomain",
                "SidTypeAlias",
                "SidTypeWellKnownGroup",
                "SidTypeDeletedAccount",
                "SidTypeInvalid",
                "SidTypeUnknown",
                "SidTypeComputer",
                "SidTypeLabel"
        };

        if (snu_id < ((sizeof (snu_name)/sizeof (snu_name[0]))))
                return (snu_name[snu_id]);

        return (snu_name[SidTypeUnknown]);
}

static smb_sid_t *
smb_sid_alloc(size_t size)
{
        smb_sid_t *sid;
#if defined(_KERNEL) || defined(_FAKE_KERNEL)
        sid = kmem_alloc(size, KM_SLEEP);
#else
        sid = malloc(size);
#endif
        return (sid);
}

void
smb_sid_free(smb_sid_t *sid)
{
#if defined(_KERNEL) || defined(_FAKE_KERNEL)
        if (sid == NULL)
                return;

        kmem_free(sid, smb_sid_len(sid));
#else
        free(sid);
#endif
}