root/usr/src/lib/libsec/common/aclutils.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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
 *
 * Copyright 2022 RackTop Systems, Inc.
 */


#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#include <strings.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/varargs.h>
#include <locale.h>
#include <aclutils.h>
#include <sys/avl.h>
#include <acl_common.h>
#include <idmap.h>

#define ACL_PATH        0
#define ACL_FD          1


typedef union {
        const char *file;
        int  fd;
} acl_inp;


/*
 * Determine whether a file has a trivial ACL
 * returns:     0 = trivial
 *              1 = nontrivial
 *              <0 some other system failure, such as ENOENT or EPERM
 */
int
acl_trivial(const char *filename)
{
        int acl_flavor;
        int aclcnt;
        int cntcmd;
        int val = 0;
        ace_t *acep;

        acl_flavor = pathconf(filename, _PC_ACL_ENABLED);

        if (acl_flavor == _ACL_ACE_ENABLED)
                cntcmd = ACE_GETACLCNT;
        else
                cntcmd = GETACLCNT;

        aclcnt = acl(filename, cntcmd, 0, NULL);
        if (aclcnt > 0) {
                if (acl_flavor == _ACL_ACE_ENABLED) {
                        acep = malloc(sizeof (ace_t) * aclcnt);
                        if (acep == NULL)
                                return (-1);
                        if (acl(filename, ACE_GETACL,
                            aclcnt, acep) < 0) {
                                free(acep);
                                return (-1);
                        }

                        val = ace_trivial(acep, aclcnt);
                        free(acep);

                } else if (aclcnt > MIN_ACL_ENTRIES)
                        val = 1;
        }
        return (val);
}


static int
cacl_get(acl_inp inp, int get_flag, int type, acl_t **aclp)
{
        const char *fname;
        int fd;
        int ace_acl = 0;
        int error;
        int getcmd, cntcmd;
        acl_t *acl_info;
        int     save_errno;
        int     stat_error;
        struct stat64 statbuf;

        *aclp = NULL;
        if (type == ACL_PATH) {
                fname = inp.file;
                ace_acl = pathconf(fname, _PC_ACL_ENABLED);
        } else {
                fd = inp.fd;
                ace_acl = fpathconf(fd, _PC_ACL_ENABLED);
        }

        /*
         * if acl's aren't supported then
         * send it through the old GETACL interface
         */
        if (ace_acl == 0 || ace_acl == -1) {
                ace_acl = _ACL_ACLENT_ENABLED;
        }

        if (ace_acl & _ACL_ACE_ENABLED) {
                cntcmd = ACE_GETACLCNT;
                getcmd = ACE_GETACL;
                acl_info = acl_alloc(ACE_T);
        } else {
                cntcmd = GETACLCNT;
                getcmd = GETACL;
                acl_info = acl_alloc(ACLENT_T);
        }

        if (acl_info == NULL)
                return (-1);

        if (type == ACL_PATH) {
                acl_info->acl_cnt = acl(fname, cntcmd, 0, NULL);
        } else {
                acl_info->acl_cnt = facl(fd, cntcmd, 0, NULL);
        }

        save_errno = errno;
        if (acl_info->acl_cnt < 0) {
                acl_free(acl_info);
                errno = save_errno;
                return (-1);
        }

        if (acl_info->acl_cnt == 0) {
                acl_free(acl_info);
                errno = save_errno;
                return (0);
        }

        acl_info->acl_aclp =
            malloc(acl_info->acl_cnt * acl_info->acl_entry_size);
        save_errno = errno;

        if (acl_info->acl_aclp == NULL) {
                acl_free(acl_info);
                errno = save_errno;
                return (-1);
        }

        if (type == ACL_PATH) {
                stat_error = stat64(fname, &statbuf);
                error = acl(fname, getcmd, acl_info->acl_cnt,
                    acl_info->acl_aclp);
        } else {
                stat_error = fstat64(fd, &statbuf);
                error = facl(fd, getcmd, acl_info->acl_cnt,
                    acl_info->acl_aclp);
        }

        save_errno = errno;
        if (error == -1) {
                acl_free(acl_info);
                errno = save_errno;
                return (-1);
        }


        if (stat_error == 0) {
                acl_info->acl_flags =
                    (S_ISDIR(statbuf.st_mode) ? ACL_IS_DIR : 0);
        } else
                acl_info->acl_flags = 0;

        switch (acl_info->acl_type) {
        case ACLENT_T:
                if (acl_info->acl_cnt <= MIN_ACL_ENTRIES)
                        acl_info->acl_flags |= ACL_IS_TRIVIAL;
                break;
        case ACE_T:
                if (ace_trivial(acl_info->acl_aclp, acl_info->acl_cnt) == 0)
                        acl_info->acl_flags |= ACL_IS_TRIVIAL;
                break;
        default:
                errno = EINVAL;
                acl_free(acl_info);
                return (-1);
        }

        if ((acl_info->acl_flags & ACL_IS_TRIVIAL) &&
            (get_flag & ACL_NO_TRIVIAL)) {
                acl_free(acl_info);
                errno = 0;
                return (0);
        }

        *aclp = acl_info;
        return (0);
}

/*
 * return -1 on failure, otherwise the number of acl
 * entries is returned
 */
int
acl_get(const char *path, int get_flag, acl_t **aclp)
{
        acl_inp acl_inp;
        acl_inp.file = path;

        return (cacl_get(acl_inp, get_flag, ACL_PATH, aclp));
}

int
facl_get(int fd, int get_flag, acl_t **aclp)
{

        acl_inp acl_inp;
        acl_inp.fd = fd;

        return (cacl_get(acl_inp, get_flag, ACL_FD, aclp));
}

/*
 * Set an ACL, translates acl to ace_t when appropriate.
 */
static int
cacl_set(acl_inp *acl_inp, acl_t *aclp, int type)
{
        int error = 0;
        int acl_flavor_target;
        struct stat64 statbuf;
        int stat_error;
        int isdir;


        if (type == ACL_PATH) {
                stat_error = stat64(acl_inp->file, &statbuf);
                if (stat_error)
                        return (-1);
                acl_flavor_target = pathconf(acl_inp->file, _PC_ACL_ENABLED);
        } else {
                stat_error = fstat64(acl_inp->fd, &statbuf);
                if (stat_error)
                        return (-1);
                acl_flavor_target = fpathconf(acl_inp->fd, _PC_ACL_ENABLED);
        }

        /*
         * If target returns an error or 0 from pathconf call then
         * fall back to UFS/POSIX Draft interface.
         * In the case of 0 we will then fail in either acl(2) or
         * acl_translate().  We could erroneously get 0 back from
         * a file system that is using fs_pathconf() and not answering
         * the _PC_ACL_ENABLED question itself.
         */
        if (acl_flavor_target == 0 || acl_flavor_target == -1)
                acl_flavor_target = _ACL_ACLENT_ENABLED;

        isdir = S_ISDIR(statbuf.st_mode);

        if ((error = acl_translate(aclp, acl_flavor_target, isdir,
            statbuf.st_uid, statbuf.st_gid)) != 0) {
                return (error);
        }

        if (type == ACL_PATH) {
                error = acl(acl_inp->file,
                    (aclp->acl_type == ACE_T) ? ACE_SETACL : SETACL,
                    aclp->acl_cnt, aclp->acl_aclp);
        } else {
                error = facl(acl_inp->fd,
                    (aclp->acl_type == ACE_T) ? ACE_SETACL : SETACL,
                    aclp->acl_cnt, aclp->acl_aclp);
        }

        return (error);
}

int
acl_set(const char *path, acl_t *aclp)
{
        acl_inp acl_inp;

        acl_inp.file = path;

        return (cacl_set(&acl_inp, aclp, ACL_PATH));
}

int
facl_set(int fd, acl_t *aclp)
{
        acl_inp acl_inp;

        acl_inp.fd = fd;

        return (cacl_set(&acl_inp, aclp, ACL_FD));
}

int
acl_cnt(acl_t *aclp)
{
        return (aclp->acl_cnt);
}

int
acl_type(acl_t *aclp)
{
        return (aclp->acl_type);
}

acl_t *
acl_dup(acl_t *aclp)
{
        acl_t *newaclp;

        newaclp = acl_alloc(aclp->acl_type);
        if (newaclp == NULL)
                return (NULL);

        newaclp->acl_aclp = malloc(aclp->acl_entry_size * aclp->acl_cnt);
        if (newaclp->acl_aclp == NULL) {
                acl_free(newaclp);
                return (NULL);
        }

        (void) memcpy(newaclp->acl_aclp,
            aclp->acl_aclp, aclp->acl_entry_size * aclp->acl_cnt);
        newaclp->acl_cnt = aclp->acl_cnt;

        return (newaclp);
}

int
acl_flags(acl_t *aclp)
{
        return (aclp->acl_flags);
}

void *
acl_data(acl_t *aclp)
{
        return (aclp->acl_aclp);
}

/*
 * Take an acl array and build an acl_t.
 */
acl_t *
acl_to_aclp(enum acl_type type, void *acl, int count)
{
        acl_t *aclp;


        aclp = acl_alloc(type);
        if (aclp == NULL)
                return (aclp);

        aclp->acl_aclp = acl;
        aclp->acl_cnt = count;

        return (aclp);
}

/*
 * Remove an ACL from a file and create a trivial ACL based
 * off of the mode argument.  After acl has been set owner/group
 * are updated to match owner,group arguments
 */
int
acl_strip(const char *file, uid_t owner, gid_t group, mode_t mode)
{
        int     error = 0;
        aclent_t min_acl[MIN_ACL_ENTRIES];
        ace_t   *min_ace_acl;
        int     acl_flavor;
        int     aclcnt;
        struct stat64 statbuf;

        acl_flavor = pathconf(file, _PC_ACL_ENABLED);

        if (stat64(file, &statbuf) != 0) {
                error = 1;
                return (error);
        }

        /*
         * force it through aclent flavor when file system doesn't
         * understand question
         */
        if (acl_flavor == 0 || acl_flavor == -1)
                acl_flavor = _ACL_ACLENT_ENABLED;

        if (acl_flavor & _ACL_ACLENT_ENABLED) {
                min_acl[0].a_type = USER_OBJ;
                min_acl[0].a_id   = owner;
                min_acl[0].a_perm = ((mode & 0700) >> 6);
                min_acl[1].a_type = GROUP_OBJ;
                min_acl[1].a_id   = group;
                min_acl[1].a_perm = ((mode & 0070) >> 3);
                min_acl[2].a_type = CLASS_OBJ;
                min_acl[2].a_id   = (uid_t)-1;
                min_acl[2].a_perm = ((mode & 0070) >> 3);
                min_acl[3].a_type = OTHER_OBJ;
                min_acl[3].a_id   = (uid_t)-1;
                min_acl[3].a_perm = (mode & 0007);
                aclcnt = 4;
                error = acl(file, SETACL, aclcnt, min_acl);
        } else if (acl_flavor & _ACL_ACE_ENABLED) {
                if ((error = acl_trivial_create(mode, S_ISDIR(statbuf.st_mode),
                    &min_ace_acl, &aclcnt)) != 0)
                        return (error);
                error = acl(file, ACE_SETACL, aclcnt, min_ace_acl);
                free(min_ace_acl);
        } else {
                errno = EINVAL;
                error = 1;
        }

        if (error == 0)
                error = chown(file, owner, group);
        return (error);
}

static int
ace_match(void *entry1, void *entry2)
{
        ace_t *p1 = (ace_t *)entry1;
        ace_t *p2 = (ace_t *)entry2;
        ace_t ace1, ace2;

        ace1 = *p1;
        ace2 = *p2;

        /*
         * Need to fixup who field for abstrations for
         * accurate comparison, since field is undefined.
         */
        if (ace1.a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE))
                ace1.a_who = (uid_t)-1;
        if (ace2.a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE))
                ace2.a_who = (uid_t)-1;
        return (memcmp(&ace1, &ace2, sizeof (ace_t)));
}

static int
aclent_match(void *entry1, void *entry2)
{
        aclent_t *aclent1 = (aclent_t *)entry1;
        aclent_t *aclent2 = (aclent_t *)entry2;

        return (memcmp(aclent1, aclent2, sizeof (aclent_t)));
}

/*
 * Find acl entries in acl that correspond to removeacl.  Search
 * is started from slot.  The flag argument indicates whether to
 * remove all matches or just the first match.
 */
int
acl_removeentries(acl_t *acl, acl_t *removeacl, int start_slot, int flag)
{
        int i, j;
        int match;
        int (*acl_match)(void *acl1, void *acl2);
        void *acl_entry, *remove_entry;
        void *start;
        int found = 0;

        if (flag != ACL_REMOVE_ALL && flag != ACL_REMOVE_FIRST)
                flag = ACL_REMOVE_FIRST;

        if (acl == NULL || removeacl == NULL)
                return (EACL_NO_ACL_ENTRY);

        if (acl->acl_type != removeacl->acl_type)
                return (EACL_DIFF_TYPE);

        if (acl->acl_type == ACLENT_T)
                acl_match = aclent_match;
        else
                acl_match = ace_match;

        for (i = 0, remove_entry = removeacl->acl_aclp;
            i != removeacl->acl_cnt; i++) {

                j = 0;
                acl_entry = (char *)acl->acl_aclp +
                    (acl->acl_entry_size * start_slot);
                for (;;) {
                        match = acl_match(acl_entry, remove_entry);
                        if (match == 0)  {
                                found++;

                                /* avoid memmove if last entry */
                                if (acl->acl_cnt == (j + 1)) {
                                        acl->acl_cnt--;
                                        break;
                                }

                                start = (char *)acl_entry +
                                    acl->acl_entry_size;
                                (void) memmove(acl_entry, start,
                                    acl->acl_entry_size *
                                    (acl->acl_cnt-- - (j + 1)));

                                if (flag == ACL_REMOVE_FIRST)
                                        break;
                                /*
                                 * List has changed, just continue so this
                                 * slot gets checked with it's new contents.
                                 */
                                continue;
                        }
                        acl_entry = ((char *)acl_entry + acl->acl_entry_size);
                        if (++j >= acl->acl_cnt) {
                                break;
                        }
                }
                remove_entry = (char *)remove_entry + removeacl->acl_entry_size;
        }

        return ((found == 0) ? EACL_NO_ACL_ENTRY : 0);
}

/*
 * Replace entires entries in acl1 with the corresponding entries
 * in newentries.  The where argument specifies where to begin
 * the replacement.  If the where argument is 1 greater than the
 * number of acl entries in acl1 then they are appended.  If the
 * where argument is 2+ greater than the number of acl entries then
 * EACL_INVALID_SLOT is returned.
 */
int
acl_modifyentries(acl_t *acl1, acl_t *newentries, int where)
{

        int slot;
        int slots_needed;
        int slots_left;
        int newsize;

        if (acl1 == NULL || newentries == NULL)
                return (EACL_NO_ACL_ENTRY);

        if (where < 0 || where >= acl1->acl_cnt)
                return (EACL_INVALID_SLOT);

        if (acl1->acl_type != newentries->acl_type)
                return (EACL_DIFF_TYPE);

        slot = where;

        slots_left = acl1->acl_cnt - slot + 1;
        if (slots_left < newentries->acl_cnt) {
                slots_needed = newentries->acl_cnt - slots_left;
                newsize = (acl1->acl_entry_size * acl1->acl_cnt) +
                    (acl1->acl_entry_size * slots_needed);
                acl1->acl_aclp = realloc(acl1->acl_aclp, newsize);
                if (acl1->acl_aclp == NULL)
                        return (-1);
        }
        (void) memcpy((char *)acl1->acl_aclp + (acl1->acl_entry_size * slot),
            newentries->acl_aclp,
            newentries->acl_entry_size * newentries->acl_cnt);

        /*
         * Did ACL grow?
         */

        if ((slot + newentries->acl_cnt) > acl1->acl_cnt) {
                acl1->acl_cnt = slot + newentries->acl_cnt;
        }

        return (0);
}

/*
 * Add acl2 entries into acl1.  The where argument specifies where
 * to add the entries.
 */
int
acl_addentries(acl_t *acl1, acl_t *acl2, int where)
{

        int newsize;
        int len;
        void *start;
        void *to;

        if (acl1 == NULL || acl2 == NULL)
                return (EACL_NO_ACL_ENTRY);

        if (acl1->acl_type != acl2->acl_type)
                return (EACL_DIFF_TYPE);

        /*
         * allow where to specify 1 past last slot for an append operation
         * but anything greater is an error.
         */
        if (where < 0 || where > acl1->acl_cnt)
                return (EACL_INVALID_SLOT);

        newsize = (acl2->acl_entry_size * acl2->acl_cnt) +
            (acl1->acl_entry_size * acl1->acl_cnt);
        acl1->acl_aclp = realloc(acl1->acl_aclp, newsize);
        if (acl1->acl_aclp == NULL)
                return (-1);

        /*
         * first push down entries where new ones will be inserted
         */

        to = (void *)((char *)acl1->acl_aclp +
            ((where + acl2->acl_cnt) * acl1->acl_entry_size));

        start = (void *)((char *)acl1->acl_aclp +
            where * acl1->acl_entry_size);

        if (where < acl1->acl_cnt) {
                len = (acl1->acl_cnt - where) * acl1->acl_entry_size;
                (void) memmove(to, start, len);
        }

        /*
         * now stick in new entries.
         */

        (void) memmove(start, acl2->acl_aclp,
            acl2->acl_cnt * acl2->acl_entry_size);

        acl1->acl_cnt += acl2->acl_cnt;
        return (0);
}

/*
 * return text for an ACL error.
 */
char *
acl_strerror(int errnum)
{
        switch (errnum) {
        case EACL_GRP_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "There is more than one group or default group entry"));
        case EACL_USER_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "There is more than one user or default user entry"));
        case EACL_OTHER_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "There is more than one other entry"));
        case EACL_CLASS_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "There is more than one mask entry"));
        case EACL_DUPLICATE_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Duplicate user or group entries"));
        case EACL_MISS_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Missing user/group owner, other, mask entry"));
        case EACL_MEM_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Memory error"));
        case EACL_ENTRY_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Unrecognized entry type"));
        case EACL_INHERIT_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Invalid inheritance flags"));
        case EACL_FLAGS_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Unrecognized entry flags"));
        case EACL_PERM_MASK_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Invalid ACL permissions"));
        case EACL_COUNT_ERROR:
                return (dgettext(TEXT_DOMAIN,
                    "Invalid ACL count"));
        case EACL_INVALID_SLOT:
                return (dgettext(TEXT_DOMAIN,
                    "Invalid ACL entry number specified"));
        case EACL_NO_ACL_ENTRY:
                return (dgettext(TEXT_DOMAIN,
                    "ACL entry doesn't exist"));
        case EACL_DIFF_TYPE:
                return (dgettext(TEXT_DOMAIN,
                    "Different file system ACL types cannot be merged"));
        case EACL_INVALID_USER_GROUP:
                return (dgettext(TEXT_DOMAIN, "Invalid user or group"));
        case EACL_INVALID_STR:
                return (dgettext(TEXT_DOMAIN, "ACL string is invalid"));
        case EACL_FIELD_NOT_BLANK:
                return (dgettext(TEXT_DOMAIN, "Field expected to be blank"));
        case EACL_INVALID_ACCESS_TYPE:
                return (dgettext(TEXT_DOMAIN, "Invalid access type"));
        case EACL_UNKNOWN_DATA:
                return (dgettext(TEXT_DOMAIN, "Unrecognized entry"));
        case EACL_MISSING_FIELDS:
                return (dgettext(TEXT_DOMAIN,
                    "ACL specification missing required fields"));
        case EACL_INHERIT_NOTDIR:
                return (dgettext(TEXT_DOMAIN,
                    "Inheritance flags are only allowed on directories"));
        case -1:
                return (strerror(errno));
        default:
                errno = EINVAL;
                return (dgettext(TEXT_DOMAIN, "Unknown error"));
        }
}

extern int yyinteractive;

/* PRINTFLIKE1 */
void
acl_error(const char *fmt, ...)
{
        va_list va;

        if (yyinteractive == 0)
                return;

        va_start(va, fmt);
        (void) vfprintf(stderr, fmt, va);
        va_end(va);
}

typedef enum id_type {
        UID_TYPE,
        GID_TYPE,
        PID_TYPE
} id_type_t;

static int
sid_to_id_impl(char *sid, id_type_t type, int *is_user, uid_t *id)
{
        idmap_get_handle_t *get_hdl;
        char *rid_start;
        idmap_stat rv;
        idmap_rid_t rid;
        const char *errstr;
        idmap_stat status;
        int error = 1;

        rid_start = strrchr(sid, '-');
        if (rid_start == NULL)
                return (error);

        rid = strtonum(rid_start + 1, 0, UINT32_MAX, &errstr);
        if (errstr != NULL)
                return (error);

        if (idmap_get_create(&get_hdl) != IDMAP_SUCCESS)
                return (error);

        /*
         * When these functions return success, the &status output is
         * indeterminate. We only care about rv==success in this caller,
         * so just ignore &status.
         */
        /* We need sid prefix. Insert NUL on '-', restore it later. */
        *rid_start = '\0';
        switch (type) {
        case UID_TYPE:
                rv = idmap_get_uidbysid(get_hdl,
                    sid, rid, IDMAP_REQ_FLG_USE_CACHE,
                    id, &status);
                break;

        case GID_TYPE:
                rv = idmap_get_gidbysid(get_hdl,
                    sid, rid, IDMAP_REQ_FLG_USE_CACHE,
                    id, &status);
                break;

        case PID_TYPE:
                rv = idmap_get_pidbysid(get_hdl, sid, rid,
                    IDMAP_REQ_FLG_USE_CACHE, id, is_user,
                    &status);
                break;
        }

        *rid_start = '-';       /* putback character removed earlier */
        if (rv == IDMAP_SUCCESS &&
            idmap_get_mappings(get_hdl) == IDMAP_SUCCESS) {
                error = 0;
        }
        idmap_get_destroy(get_hdl);

        return (error);
}

int
sid_to_id(char *sid, boolean_t user, uid_t *id)
{
        char *domain_start;
        int error = 1;

        if ((domain_start = strchr(sid, '@')) == NULL) {
                error = sid_to_id_impl(sid, user ? UID_TYPE : GID_TYPE,
                    NULL, id);
        } else {
                char *name = sid;
                idmap_stat rv;

                *domain_start++ = '\0';

                if (user)
                        rv = idmap_getuidbywinname(name, domain_start,
                            IDMAP_REQ_FLG_USE_CACHE, id);
                else
                        rv = idmap_getgidbywinname(name, domain_start,
                            IDMAP_REQ_FLG_USE_CACHE, id);
                *--domain_start = '@';
                if (rv == IDMAP_SUCCESS)
                        error = 0;
        }

        return (error);
}

/*
 * Variant of sid_to_id() called when we don't know whether the SID
 * is a user or group. 2nd arg gets the type (0:group, 1:user)
 * Returns zero for success, 1 for errors.
 */
int
sid_to_xid(char *sid, int *is_user, uid_t *id)
{
        if ((strchr(sid, '@')) != NULL)
                return (1);

        return (sid_to_id_impl(sid, PID_TYPE, is_user, id));
}