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

/*LINTLIBRARY*/

#include <grp.h>
#include <pwd.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/acl.h>
#include <aclutils.h>
#include <idmap.h>
#include <synch.h>

/*
 * Unix names returned for UID and GID values can actually be longer than
 * the traditional 8 characters, or even 32 characters, etc.  When using
 * "ad" in nsswitch.conf they can be quite long.  Let's use a reasonable
 * compromise value here (80) which is large enough to hold SIDs in the
 * numeric form (eg. "S-1-5-21-wwwwwwwww-xxxxxxxxx-yyyyyyyyy-zzzzzzzzz")
 * but not necessarily every crazy long user name.  In addition, places
 * below that might truncate a long name should detect truncation and
 * fall-back to a more compact value (SID or UID/GID et).
 */
#define ID_STR_MAX      80

#define APPENDED_ID_MAX ID_STR_MAX + 1          /* id + colon */
/*
 * yyinteractive controls whether yyparse should print out
 * error messages to stderr, and whether or not id's should be
 * allowed from acl_fromtext().
 */
int     yyinteractive;
acl_t   *yyacl;
char    *yybuf;
mutex_t yymutex;

extern acl_t *acl_alloc(enum acl_type);

/*
 * dynamic string that will increase in size on an
 * as needed basis.
 */
typedef struct dynaclstr {
        size_t d_bufsize;               /* current size of aclexport */
        char *d_aclexport;
        int d_pos;
} dynaclstr_t;

static int str_append(dynaclstr_t *, char *);
static int aclent_perm_txt(dynaclstr_t *, o_mode_t);

static void
aclent_perms(int perm, char *txt_perms)
{
        if (perm & S_IROTH)
                txt_perms[0] = 'r';
        else
                txt_perms[0] = '-';
        if (perm & S_IWOTH)
                txt_perms[1] = 'w';
        else
                txt_perms[1] = '-';
        if (perm & S_IXOTH)
                txt_perms[2] = 'x';
        else
                txt_perms[2] = '-';
        txt_perms[3] = '\0';
}

/*
 * Get the user name into the passed buffer.  If it can't be found,
 * or if the noresolve flag is set, or if what we found will not fit
 * in the buffer without truncation, just put a numeric ID.
 */
static char *
pruname(uid_t uid, char *uidp, size_t buflen, int noresolve)
{
        struct passwd   *passwdp = NULL;

        if (noresolve == 0)
                passwdp = getpwuid(uid);
        if (passwdp != NULL && strlen(passwdp->pw_name) < buflen) {
                (void) strlcpy(uidp, passwdp->pw_name, buflen);
        } else {
                /* display uid instead */
                (void) snprintf(uidp, buflen, "%u", uid);
        }
        return (uidp);
}

/*
 * Get the group name into the passed buffer.  If it can't be found,
 * or if the noresolve flag is set, or if what we found will not fit
 * in the buffer without truncation, just put a numeric ID.
 */
static char *
prgname(gid_t gid, char *gidp, size_t buflen, int noresolve)
{
        struct group    *groupp = NULL;

        if (noresolve == 0)
                groupp = getgrgid(gid);
        if (groupp != NULL && strlen(groupp->gr_name) < buflen) {
                (void) strlcpy(gidp, groupp->gr_name, buflen);
        } else {
                /* display gid instead */
                (void) snprintf(gidp, buflen, "%u", gid);
        }
        return (gidp);
}

static int
getsidname(uid_t who, boolean_t user, char **sidp, boolean_t noresolve)
{
        idmap_get_handle_t *get_hdl = NULL;
        idmap_stat status;
        idmap_rid_t rid;
        int error = IDMAP_ERR_NORESULT;
        int len;
        char *domain = NULL;

        *sidp = NULL;

        /*
         * First try and get windows name
         */

        if (!noresolve) {
                if (user)
                        error = idmap_getwinnamebyuid(who,
                            IDMAP_REQ_FLG_USE_CACHE, sidp, NULL);
                else
                        error = idmap_getwinnamebygid(who,
                            IDMAP_REQ_FLG_USE_CACHE, sidp, NULL);
        }
        if (error != IDMAP_SUCCESS) {
                if (idmap_get_create(&get_hdl) == IDMAP_SUCCESS) {
                        if (user)
                                error = idmap_get_sidbyuid(get_hdl, who,
                                    IDMAP_REQ_FLG_USE_CACHE, &domain, &rid,
                                    &status);
                        else
                                error = idmap_get_sidbygid(get_hdl, who,
                                    IDMAP_REQ_FLG_USE_CACHE, &domain, &rid,
                                    &status);
                        if (error == IDMAP_SUCCESS &&
                            idmap_get_mappings(get_hdl) == 0) {
                                if (status == IDMAP_SUCCESS) {
                                        len = snprintf(NULL, 0,
                                            "%s-%d", domain, rid);
                                        if (*sidp = malloc(len + 1)) {
                                                (void) snprintf(*sidp, len + 1,
                                                    "%s-%d", domain, rid);
                                        }
                                }
                        }
                }
                if (get_hdl)
                        idmap_get_destroy(get_hdl);
        }

        free(domain);

        return (*sidp ? 0 : 1);
}

/*
 * sid_string_by_id() is an exposed interface via -lsec
 */
int
sid_string_by_id(uid_t who, boolean_t user, char **sidp, boolean_t noresolve)
{
        return (getsidname(who, user, sidp, noresolve));
}

static void
aclent_printacl(acl_t *aclp)
{
        aclent_t *tp;
        int aclcnt;
        int mask;
        int slot = 0;
        char perm[4];
        char uidp[ID_STR_MAX];
        char gidp[ID_STR_MAX];

        /* display ACL: assume it is sorted. */
        aclcnt = aclp->acl_cnt;
        for (tp = aclp->acl_aclp; tp && aclcnt--; tp++) {
                if (tp->a_type == CLASS_OBJ)
                        mask = tp->a_perm;
        }
        aclcnt = aclp->acl_cnt;
        for (tp = aclp->acl_aclp; aclcnt--; tp++) {
                (void) printf("     %d:", slot++);
                switch (tp->a_type) {
                case USER:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("user:%s:%s\t\t",
                            pruname(tp->a_id, uidp, sizeof (uidp), 0), perm);
                        aclent_perms((tp->a_perm & mask), perm);
                        (void) printf("#effective:%s\n", perm);
                        break;
                case USER_OBJ:
                        /* no need to display uid */
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("user::%s\n", perm);
                        break;
                case GROUP:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("group:%s:%s\t\t",
                            prgname(tp->a_id, gidp, sizeof (gidp), 0), perm);
                        aclent_perms(tp->a_perm & mask, perm);
                        (void) printf("#effective:%s\n", perm);
                        break;
                case GROUP_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("group::%s\t\t", perm);
                        aclent_perms(tp->a_perm & mask, perm);
                        (void) printf("#effective:%s\n", perm);
                        break;
                case CLASS_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("mask:%s\n", perm);
                        break;
                case OTHER_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("other:%s\n", perm);
                        break;
                case DEF_USER:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("default:user:%s:%s\n",
                            pruname(tp->a_id, uidp, sizeof (uidp), 0), perm);
                        break;
                case DEF_USER_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("default:user::%s\n", perm);
                        break;
                case DEF_GROUP:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("default:group:%s:%s\n",
                            prgname(tp->a_id, gidp, sizeof (gidp), 0), perm);
                        break;
                case DEF_GROUP_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("default:group::%s\n", perm);
                        break;
                case DEF_CLASS_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("default:mask:%s\n", perm);
                        break;
                case DEF_OTHER_OBJ:
                        aclent_perms(tp->a_perm, perm);
                        (void) printf("default:other:%s\n", perm);
                        break;
                default:
                        (void) fprintf(stderr,
                            dgettext(TEXT_DOMAIN, "unrecognized entry\n"));
                        break;
                }
        }
}

static void
split_line(char *str, int cols)
{
        char *ptr;
        int len;
        int i;
        int last_split;
        char *pad = "";
        int pad_len;

        len = strlen(str);
        ptr = str;
        pad_len = 0;

        ptr = str;
        last_split = 0;
        for (i = 0; i != len; i++) {
                if ((i + pad_len + 4) >= cols) {
                        (void) printf("%s%.*s\n", pad, last_split, ptr);
                        ptr = &ptr[last_split];
                        len = strlen(ptr);
                        i = 0;
                        pad_len = 4;
                        pad = "         ";
                } else {
                        if (ptr[i] == '/' || ptr[i] == ':') {
                                last_split = i;
                        }
                }
        }
        if (i == len) {
                (void) printf("%s%s\n", pad, ptr);
        }
}

/*
 * compute entry type string, such as user:joe, group:staff,...
 */
static int
aclent_type_txt(dynaclstr_t *dstr, aclent_t *aclp, int flags)
{
        char idp[ID_STR_MAX];
        int error;

        switch (aclp->a_type) {
        case DEF_USER_OBJ:
        case USER_OBJ:
                if (aclp->a_type == USER_OBJ)
                        error = str_append(dstr, "user::");
                else
                        error = str_append(dstr, "defaultuser::");
                break;

        case DEF_USER:
        case USER:
                if (aclp->a_type == USER)
                        error = str_append(dstr, "user:");
                else
                        error = str_append(dstr, "defaultuser:");
                if (error)
                        break;
                error = str_append(dstr, pruname(aclp->a_id, idp,
                    sizeof (idp), flags & ACL_NORESOLVE));
                if (error == 0)
                        error = str_append(dstr, ":");
                break;

        case DEF_GROUP_OBJ:
        case GROUP_OBJ:
                if (aclp->a_type == GROUP_OBJ)
                        error = str_append(dstr, "group::");
                else
                        error = str_append(dstr, "defaultgroup::");
                break;

        case DEF_GROUP:
        case GROUP:
                if (aclp->a_type == GROUP)
                        error = str_append(dstr, "group:");
                else
                        error = str_append(dstr, "defaultgroup:");
                if (error)
                        break;
                error = str_append(dstr, prgname(aclp->a_id, idp,
                    sizeof (idp), flags & ACL_NORESOLVE));
                if (error == 0)
                        error = str_append(dstr, ":");
                break;

        case DEF_CLASS_OBJ:
        case CLASS_OBJ:
                if (aclp->a_type == CLASS_OBJ)
                        error = str_append(dstr, "mask:");
                else
                        error = str_append(dstr, "defaultmask:");
                break;

        case DEF_OTHER_OBJ:
        case OTHER_OBJ:
                if (aclp->a_type == OTHER_OBJ)
                        error = str_append(dstr, "other:");
                else
                        error = str_append(dstr, "defaultother:");
                break;

        default:
                error = 1;
                break;
        }

        return (error);
}

/*
 * compute entry type string such as, owner@:, user:joe, group:staff,...
 */
static int
ace_type_txt(dynaclstr_t *dynstr, ace_t *acep, int flags)
{
        char idp[ID_STR_MAX];
        int error;
        char *sidp = NULL;

        switch (acep->a_flags & ACE_TYPE_FLAGS) {
        case ACE_OWNER:
                error = str_append(dynstr, OWNERAT_TXT);
                break;

        case ACE_GROUP|ACE_IDENTIFIER_GROUP:
                error = str_append(dynstr, GROUPAT_TXT);
                break;

        case ACE_IDENTIFIER_GROUP:
                if ((flags & ACL_SID_FMT) && acep->a_who > MAXUID) {
                        if (error = str_append(dynstr,
                            GROUPSID_TXT))
                                break;
                        if (error = getsidname(acep->a_who, B_FALSE,
                            &sidp, flags & ACL_NORESOLVE))
                                break;
                        error = str_append(dynstr, sidp);
                } else {
                        if (error = str_append(dynstr, GROUP_TXT))
                                break;
                        error = str_append(dynstr, prgname(acep->a_who, idp,
                            sizeof (idp), flags & ACL_NORESOLVE));
                }
                if (error == 0)
                        error = str_append(dynstr, ":");
                break;

        case ACE_EVERYONE:
                error = str_append(dynstr, EVERYONEAT_TXT);
                break;

        case 0:
                if ((flags & ACL_SID_FMT) && acep->a_who > MAXUID) {
                        if (error = str_append(dynstr, USERSID_TXT))
                                break;
                        if (error = getsidname(acep->a_who, B_TRUE,
                            &sidp, flags & ACL_NORESOLVE))
                                break;
                        error = str_append(dynstr, sidp);
                } else {
                        if (error = str_append(dynstr, USER_TXT))
                                break;
                        error = str_append(dynstr, pruname(acep->a_who, idp,
                            sizeof (idp), flags & ACL_NORESOLVE));
                }
                if (error == 0)
                        error = str_append(dynstr, ":");
                break;
        default:
                error = 0;
                break;
        }

        if (sidp)
                free(sidp);
        return (error);
}

/*
 * compute string of permissions, such as read_data/write_data or
 * rwxp,...
 * The format depends on the flags field which indicates whether the compact
 * or verbose format should be used.
 */
static int
ace_perm_txt(dynaclstr_t *dstr, uint32_t mask,
    uint32_t iflags, int isdir, int flags)
{
        int error = 0;

        if (flags & ACL_COMPACT_FMT) {
                char buf[16];

                if (mask & ACE_READ_DATA)
                        buf[0] = 'r';
                else
                        buf[0] = '-';
                if (mask & ACE_WRITE_DATA)
                        buf[1] = 'w';
                else
                        buf[1] = '-';
                if (mask & ACE_EXECUTE)
                        buf[2] = 'x';
                else
                        buf[2] = '-';
                if (mask & ACE_APPEND_DATA)
                        buf[3] = 'p';
                else
                        buf[3] = '-';
                if (mask & ACE_DELETE)
                        buf[4] = 'd';
                else
                        buf[4] = '-';
                if (mask & ACE_DELETE_CHILD)
                        buf[5] = 'D';
                else
                        buf[5] = '-';
                if (mask & ACE_READ_ATTRIBUTES)
                        buf[6] = 'a';
                else
                        buf[6] = '-';
                if (mask & ACE_WRITE_ATTRIBUTES)
                        buf[7] = 'A';
                else
                        buf[7] = '-';
                if (mask & ACE_READ_NAMED_ATTRS)
                        buf[8] = 'R';
                else
                        buf[8] = '-';
                if (mask & ACE_WRITE_NAMED_ATTRS)
                        buf[9] = 'W';
                else
                        buf[9] = '-';
                if (mask & ACE_READ_ACL)
                        buf[10] = 'c';
                else
                        buf[10] = '-';
                if (mask & ACE_WRITE_ACL)
                        buf[11] = 'C';
                else
                        buf[11] = '-';
                if (mask & ACE_WRITE_OWNER)
                        buf[12] = 'o';
                else
                        buf[12] = '-';
                if (mask & ACE_SYNCHRONIZE)
                        buf[13] = 's';
                else
                        buf[13] = '-';
                buf[14] = ':';
                buf[15] = '\0';
                error = str_append(dstr, buf);
        } else {
                /*
                 * If ACE is a directory, but inheritance indicates its
                 * for a file then print permissions for file rather than
                 * dir.
                 */
                if (isdir) {
                        if (mask & ACE_LIST_DIRECTORY) {
                                if (iflags == ACE_FILE_INHERIT_ACE) {
                                        error = str_append(dstr,
                                            READ_DATA_TXT);
                                } else {
                                        error =
                                            str_append(dstr, READ_DIR_TXT);
                                }
                        }
                        if (error == 0 && (mask & ACE_ADD_FILE)) {
                                if (iflags == ACE_FILE_INHERIT_ACE) {
                                        error =
                                            str_append(dstr, WRITE_DATA_TXT);
                                } else {
                                        error =
                                            str_append(dstr, ADD_FILE_TXT);
                                }
                        }
                        if (error == 0 && (mask & ACE_ADD_SUBDIRECTORY)) {
                                if (iflags == ACE_FILE_INHERIT_ACE) {
                                        error = str_append(dstr,
                                            APPEND_DATA_TXT);
                                } else {
                                        error = str_append(dstr,
                                            ADD_DIR_TXT);
                                }
                        }
                } else {
                        if (mask & ACE_READ_DATA) {
                                error = str_append(dstr, READ_DATA_TXT);
                        }
                        if (error == 0 && (mask & ACE_WRITE_DATA)) {
                                error = str_append(dstr, WRITE_DATA_TXT);
                        }
                        if (error == 0 && (mask & ACE_APPEND_DATA)) {
                                error = str_append(dstr, APPEND_DATA_TXT);
                        }
                }
                if (error == 0 && (mask & ACE_READ_NAMED_ATTRS)) {
                        error = str_append(dstr, READ_XATTR_TXT);
                }
                if (error == 0 && (mask & ACE_WRITE_NAMED_ATTRS)) {
                        error = str_append(dstr, WRITE_XATTR_TXT);
                }
                if (error == 0 && (mask & ACE_EXECUTE)) {
                        error = str_append(dstr, EXECUTE_TXT);
                }
                if (error == 0 && (mask & ACE_DELETE_CHILD)) {
                        error = str_append(dstr, DELETE_CHILD_TXT);
                }
                if (error == 0 && (mask & ACE_READ_ATTRIBUTES)) {
                        error = str_append(dstr, READ_ATTRIBUTES_TXT);
                }
                if (error == 0 && (mask & ACE_WRITE_ATTRIBUTES)) {
                        error = str_append(dstr, WRITE_ATTRIBUTES_TXT);
                }
                if (error == 0 && (mask & ACE_DELETE)) {
                        error = str_append(dstr, DELETE_TXT);
                }
                if (error == 0 && (mask & ACE_READ_ACL)) {
                        error = str_append(dstr, READ_ACL_TXT);
                }
                if (error == 0 && (mask & ACE_WRITE_ACL)) {
                        error = str_append(dstr, WRITE_ACL_TXT);
                }
                if (error == 0 && (mask & ACE_WRITE_OWNER)) {
                        error = str_append(dstr, WRITE_OWNER_TXT);
                }
                if (error == 0 && (mask & ACE_SYNCHRONIZE)) {
                        error = str_append(dstr, SYNCHRONIZE_TXT);
                }
                if (error == 0 && dstr->d_aclexport[dstr->d_pos-1] == '/') {
                        dstr->d_aclexport[--dstr->d_pos] = '\0';
                }
                if (error == 0)
                        error = str_append(dstr, ":");
        }
        return (error);
}

/*
 * compute string of access type, such as allow, deny, ...
 */
static int
ace_access_txt(dynaclstr_t *dstr, int type)
{
        int error;

        if (type == ACE_ACCESS_ALLOWED_ACE_TYPE)
                error = str_append(dstr, ALLOW_TXT);
        else if (type == ACE_ACCESS_DENIED_ACE_TYPE)
                error = str_append(dstr, DENY_TXT);
        else if (type == ACE_SYSTEM_AUDIT_ACE_TYPE)
                error = str_append(dstr, AUDIT_TXT);
        else if (type == ACE_SYSTEM_ALARM_ACE_TYPE)
                error = str_append(dstr, ALARM_TXT);
        else
                error = str_append(dstr, UNKNOWN_TXT);

        return (error);
}

static int
ace_inherit_txt(dynaclstr_t *dstr, uint32_t iflags, int flags)
{
        int error = 0;

        if (flags & ACL_COMPACT_FMT) {
                char buf[9];

                if (iflags & ACE_FILE_INHERIT_ACE)
                        buf[0] = 'f';
                else
                        buf[0] = '-';
                if (iflags & ACE_DIRECTORY_INHERIT_ACE)
                        buf[1] = 'd';
                else
                        buf[1] = '-';
                if (iflags & ACE_INHERIT_ONLY_ACE)
                        buf[2] = 'i';
                else
                        buf[2] = '-';
                if (iflags & ACE_NO_PROPAGATE_INHERIT_ACE)
                        buf[3] = 'n';
                else
                        buf[3] = '-';
                if (iflags & ACE_SUCCESSFUL_ACCESS_ACE_FLAG)
                        buf[4] = 'S';
                else
                        buf[4] = '-';
                if (iflags & ACE_FAILED_ACCESS_ACE_FLAG)
                        buf[5] = 'F';
                else
                        buf[5] = '-';
                if (iflags & ACE_INHERITED_ACE)
                        buf[6] = 'I';
                else
                        buf[6] = '-';
                buf[7] = ':';
                buf[8] = '\0';
                error = str_append(dstr, buf);
        } else {
                if (iflags & ACE_FILE_INHERIT_ACE) {
                        error = str_append(dstr, FILE_INHERIT_TXT);
                }
                if (error == 0 && (iflags & ACE_DIRECTORY_INHERIT_ACE)) {
                        error = str_append(dstr, DIR_INHERIT_TXT);
                }
                if (error == 0 && (iflags & ACE_NO_PROPAGATE_INHERIT_ACE)) {
                        error = str_append(dstr, NO_PROPAGATE_TXT);
                }
                if (error == 0 && (iflags & ACE_INHERIT_ONLY_ACE)) {
                        error = str_append(dstr, INHERIT_ONLY_TXT);
                }
                if (error == 0 && (iflags & ACE_SUCCESSFUL_ACCESS_ACE_FLAG)) {
                        error = str_append(dstr, SUCCESSFUL_ACCESS_TXT);
                }
                if (error == 0 && (iflags & ACE_FAILED_ACCESS_ACE_FLAG)) {
                        error = str_append(dstr, FAILED_ACCESS_TXT);
                }
                if (error == 0 && (iflags & ACE_INHERITED_ACE)) {
                        error = str_append(dstr, INHERITED_ACE_TXT);
                }
                if (error == 0 && dstr->d_aclexport[dstr->d_pos-1] == '/') {
                        dstr->d_aclexport[--dstr->d_pos] = '\0';
                        error = str_append(dstr, ":");
                }
        }

        return (error);
}

/*
 * Convert internal acl representation to external representation.
 *
 * The length of a non-owning user name or non-owning group name ie entries
 * of type DEF_USER, USER, DEF_GROUP or GROUP, can exceed LOGNAME_MAX.  We
 * thus check the length of these entries, and if greater than LOGNAME_MAX,
 * we realloc() via increase_length().
 *
 * The LOGNAME_MAX, ENTRYTYPELEN and PERMS limits are otherwise always
 * adhered to.
 */

/*
 * acltotext() converts each ACL entry to look like this:
 *
 *    entry_type:uid^gid^name:perms[:id]
 *
 * The maximum length of entry_type is 14 ("defaultgroup::" and
 * "defaultother::") hence ENTRYTYPELEN is set to 14.
 *
 * The max length of a uid^gid^name entry (in theory) is 8, hence we use,
 * however the ID could be a number so we therefore use ID_STR_MAX
 *
 * The length of a perms entry is 4 to allow for the comma appended to each
 * to each acl entry.  Hence PERMS is set to 4.
 */

#define ENTRYTYPELEN    14
#define PERMS           4
#define ACL_ENTRY_SIZE  (ENTRYTYPELEN + ID_STR_MAX + PERMS + APPENDED_ID_MAX)

char *
aclent_acltotext(aclent_t  *aclp, int aclcnt, int flags)
{
        dynaclstr_t     *dstr;
        char            *aclexport = NULL;
        int             i;
        int             error = 0;

        if (aclp == NULL)
                return (NULL);
        if ((dstr = malloc(sizeof (dynaclstr_t))) == NULL)
                return (NULL);
        dstr->d_bufsize = aclcnt * ACL_ENTRY_SIZE;
        if ((dstr->d_aclexport = malloc(dstr->d_bufsize)) == NULL) {
                free(dstr);
                return (NULL);
        }
        *dstr->d_aclexport = '\0';
        dstr->d_pos = 0;

        for (i = 0; i < aclcnt; i++, aclp++) {
                if (error = aclent_type_txt(dstr, aclp, flags))
                        break;
                if (error = aclent_perm_txt(dstr, aclp->a_perm))
                        break;

                if ((flags & ACL_APPEND_ID) && ((aclp->a_type == USER) ||
                    (aclp->a_type == DEF_USER) || (aclp->a_type == GROUP) ||
                    (aclp->a_type == DEF_GROUP))) {
                        char id[ID_STR_MAX], *idstr;

                        if (error = str_append(dstr, ":"))
                                break;
                        id[ID_STR_MAX - 1] = '\0'; /* null terminate buffer */
                        idstr = lltostr(aclp->a_id, &id[ID_STR_MAX - 1]);
                        if (error = str_append(dstr, idstr))
                                break;
                }
                if (i < aclcnt - 1)
                        if (error = str_append(dstr, ","))
                                break;
        }
        if (error) {
                if (dstr->d_aclexport)
                        free(dstr->d_aclexport);
        } else {
                aclexport = dstr->d_aclexport;
        }
        free(dstr);
        return (aclexport);
}

char *
acltotext(aclent_t *aclp, int aclcnt)
{
        return (aclent_acltotext(aclp, aclcnt, 0));
}


aclent_t *
aclfromtext(char *aclstr, int *aclcnt)
{
        acl_t *aclp;
        aclent_t *aclentp;
        int error;

        error = acl_fromtext(aclstr, &aclp);
        if (error)
                return (NULL);

        aclentp = aclp->acl_aclp;
        aclp->acl_aclp = NULL;
        *aclcnt = aclp->acl_cnt;

        acl_free(aclp);
        return (aclentp);
}


/*
 * Append string onto dynaclstr_t.
 *
 * Return 0 on success, 1 for failure.
 */
static int
str_append(dynaclstr_t *dstr, char *newstr)
{
        size_t len = strlen(newstr);

        if ((len + dstr->d_pos) >= dstr->d_bufsize) {
                dstr->d_aclexport = realloc(dstr->d_aclexport,
                    dstr->d_bufsize + len + 1);
                if (dstr->d_aclexport == NULL)
                        return (1);
                dstr->d_bufsize += len;
        }
        (void) strcat(&dstr->d_aclexport[dstr->d_pos], newstr);
        dstr->d_pos += len;
        return (0);
}

static int
aclent_perm_txt(dynaclstr_t *dstr, o_mode_t perm)
{
        char buf[4];

        if (perm & S_IROTH)
                buf[0] = 'r';
        else
                buf[0] = '-';
        if (perm & S_IWOTH)
                buf[1] = 'w';
        else
                buf[1] = '-';
        if (perm & S_IXOTH)
                buf[2] = 'x';
        else
                buf[2] = '-';
        buf[3] = '\0';
        return (str_append(dstr, buf));
}

/*
 * ace_acltotext() convert each ace formatted acl to look like this:
 *
 * entry_type:uid^gid^name:perms[:flags]:<allow|deny>[:id][,]
 *
 * The maximum length of entry_type is 5 ("group")
 *
 * The max length of a uid^gid^name entry (in theory) is 8,
 * however id could be a number so we therefore use ID_STR_MAX
 *
 * The length of a perms entry is 144 i.e read_data/write_data...
 * to each acl entry.
 *
 * iflags: file_inherit/dir_inherit/inherit_only/no_propagate/successful_access
 *         /failed_access
 *
 */

#define ACE_ENTRYTYPLEN         6
#define IFLAGS_STR "file_inherit/dir_inherit/inherit_only/no_propagate/" \
        "successful_access/failed_access/inherited"
#define IFLAGS_SIZE             (sizeof (IFLAGS_STR) - 1)
#define ACCESS_TYPE_SIZE        7       /* if unknown */
#define COLON_CNT               3
#define PERMS_LEN               216
#define ACE_ENTRY_SIZE  (ACE_ENTRYTYPLEN + ID_STR_MAX + PERMS_LEN + \
    ACCESS_TYPE_SIZE + IFLAGS_SIZE + COLON_CNT + APPENDED_ID_MAX)

static char *
ace_acltotext(acl_t *aceaclp, int flags)
{
        ace_t           *aclp = aceaclp->acl_aclp;
        int             aclcnt = aceaclp->acl_cnt;
        int             i;
        int             error = 0;
        int             isdir = (aceaclp->acl_flags & ACL_IS_DIR);
        dynaclstr_t     *dstr;
        char            *aclexport = NULL;
        char            *rawsidp = NULL;

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

        if ((dstr = malloc(sizeof (dynaclstr_t))) == NULL)
                return (NULL);
        dstr->d_bufsize = aclcnt * ACL_ENTRY_SIZE;
        if ((dstr->d_aclexport = malloc(dstr->d_bufsize)) == NULL) {
                free(dstr);
                return (NULL);
        }
        *dstr->d_aclexport = '\0';
        dstr->d_pos = 0;

        for (i = 0; i < aclcnt; i++, aclp++) {

                if (error = ace_type_txt(dstr, aclp, flags))
                        break;
                if (error = ace_perm_txt(dstr, aclp->a_access_mask,
                    aclp->a_flags, isdir, flags))
                        break;
                if (error = ace_inherit_txt(dstr, aclp->a_flags, flags))
                        break;
                if (error = ace_access_txt(dstr, aclp->a_type))
                        break;

                if ((flags & ACL_APPEND_ID) &&
                    (((aclp->a_flags & ACE_TYPE_FLAGS) == 0) ||
                    ((aclp->a_flags & ACE_TYPE_FLAGS) ==
                    ACE_IDENTIFIER_GROUP))) {
                        char id[ID_STR_MAX], *idstr;

                        if (error = str_append(dstr, ":"))
                                break;

                        rawsidp = NULL;
                        id[ID_STR_MAX -1] = '\0'; /* null terminate */
                        if (aclp->a_who > MAXUID && (flags & ACL_SID_FMT)) {

                                error = getsidname(aclp->a_who,
                                    ((aclp->a_flags & ACE_TYPE_FLAGS) == 0) ?
                                    B_TRUE : B_FALSE, &idstr, 1);
                                rawsidp = idstr;
                                if (error)
                                        break;
                        } else if (aclp->a_who > MAXUID &&
                            !(flags & ACL_NORESOLVE)) {
                                idstr = lltostr(UID_NOBODY,
                                    &id[ID_STR_MAX - 1]);
                        } else {
                                idstr = lltostr(aclp->a_who,
                                    &id[ID_STR_MAX - 1]);
                        }
                        if (error = str_append(dstr, idstr))
                                break;
                        if (rawsidp) {
                                free(rawsidp);
                                rawsidp = NULL;
                        }
                }
                if (i < aclcnt - 1) {
                        if (error = str_append(dstr, ","))
                                break;
                }
        }

        if (rawsidp)
                free(rawsidp);
        if (error) {
                if (dstr->d_aclexport)
                        free(dstr->d_aclexport);
        } else {
                aclexport = dstr->d_aclexport;
        }
        free(dstr);
        return (aclexport);
}

char *
acl_totext(acl_t *aclp, int flags)
{
        char *txtp;

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

        switch (aclp->acl_type) {
        case ACE_T:
                txtp = ace_acltotext(aclp, flags);
                break;
        case ACLENT_T:
                txtp = aclent_acltotext(aclp->acl_aclp, aclp->acl_cnt, flags);
                break;
        }

        return (txtp);
}

int
acl_fromtext(const char *acltextp, acl_t **ret_aclp)
{
        int error;
        char *buf;

        buf = malloc(strlen(acltextp) + 2);
        if (buf == NULL)
                return (EACL_MEM_ERROR);
        strcpy(buf, acltextp);
        strcat(buf, "\n");

        (void) mutex_lock(&yymutex);
        yybuf = buf;
        yyreset();
        error = yyparse();
        free(buf);

        if (yyacl) {
                if (error == 0)
                        *ret_aclp = yyacl;
                else {
                        acl_free(yyacl);
                }
                yyacl = NULL;
        }
        (void) mutex_unlock(&yymutex);

        return (error);
}

int
acl_parse(const char *acltextp, acl_t **aclp)
{
        int error;

        yyinteractive = 1;
        error = acl_fromtext(acltextp, aclp);
        yyinteractive = 0;
        return (error);
}

static void
ace_compact_printacl(acl_t *aclp, int flgs)
{
        int cnt;
        ace_t *acep;
        dynaclstr_t *dstr;
        int len;

        if ((dstr = malloc(sizeof (dynaclstr_t))) == NULL)
                return;
        dstr->d_bufsize = ACE_ENTRY_SIZE;
        if ((dstr->d_aclexport = malloc(dstr->d_bufsize)) == NULL) {
                free(dstr);
                return;
        }
        *dstr->d_aclexport = '\0';

        dstr->d_pos = 0;
        for (cnt = 0, acep = aclp->acl_aclp;
            cnt != aclp->acl_cnt; cnt++, acep++) {
                dstr->d_aclexport[0] = '\0';
                dstr->d_pos = 0;

                if (ace_type_txt(dstr, acep, flgs))
                        break;
                len = strlen(&dstr->d_aclexport[0]);
                if (ace_perm_txt(dstr, acep->a_access_mask, acep->a_flags,
                    aclp->acl_flags & ACL_IS_DIR, ACL_COMPACT_FMT))
                        break;
                if (ace_inherit_txt(dstr, acep->a_flags, ACL_COMPACT_FMT))
                        break;
                if (ace_access_txt(dstr, acep->a_type) == -1)
                        break;
                (void) printf("    %20.*s%s\n", len, dstr->d_aclexport,
                    &dstr->d_aclexport[len]);
        }

        if (dstr->d_aclexport)
                free(dstr->d_aclexport);
        free(dstr);
}

static void
ace_printacl(acl_t *aclp, int cols, int flgs)
{
        int  slot = 0;
        char *token;
        char *acltext;

        if (flgs & ACL_COMPACT_FMT) {
                ace_compact_printacl(aclp, flgs);
                return;
        }

        acltext = acl_totext(aclp, flgs);

        if (acltext == NULL)
                return;

        token = strtok(acltext, ",");
        if (token == NULL) {
                free(acltext);
                return;
        }

        do {
                (void) printf("     %d:", slot++);
                split_line(token, cols - 5);
        } while (token = strtok(NULL, ","));
        free(acltext);
}

/*
 * pretty print an ACL.
 * For aclent_t ACL's the format is
 * similar to the old format used by getfacl,
 * with the addition of adding a "slot" number
 * before each entry.
 *
 * for ace_t ACL's the cols variable will break up
 * the long lines into multiple lines and will also
 * print a "slot" number.
 */
void
acl_printacl2(acl_t *aclp, int cols, int flgs)
{

        switch (aclp->acl_type) {
        case ACLENT_T:
                aclent_printacl(aclp);
                break;
        case ACE_T:
                ace_printacl(aclp, cols, flgs);
                break;
        }
}

/*
 * Historical, compatibility version of the above.
 */
void
acl_printacl(acl_t *aclp, int cols, int compact)
{
        int flgs = compact ? ACL_COMPACT_FMT : 0;

        switch (aclp->acl_type) {
        case ACLENT_T:
                aclent_printacl(aclp);
                break;
        case ACE_T:
                ace_printacl(aclp, cols, flgs);
                break;
        }
}

typedef struct value_table {
        char            p_letter; /* perm letter such as 'r' */
        uint32_t        p_value; /* value for perm when pletter found */
} value_table_t;

/*
 * The permission tables are laid out in positional order
 * a '-' character will indicate a permission at a given
 * position is not specified.  The '-' is not part of the
 * table, but will be checked for in the permission computation
 * routine.
 */
value_table_t ace_perm_table[] = {
        { 'r', ACE_READ_DATA},
        { 'w', ACE_WRITE_DATA},
        { 'x', ACE_EXECUTE},
        { 'p', ACE_APPEND_DATA},
        { 'd', ACE_DELETE},
        { 'D', ACE_DELETE_CHILD},
        { 'a', ACE_READ_ATTRIBUTES},
        { 'A', ACE_WRITE_ATTRIBUTES},
        { 'R', ACE_READ_NAMED_ATTRS},
        { 'W', ACE_WRITE_NAMED_ATTRS},
        { 'c', ACE_READ_ACL},
        { 'C', ACE_WRITE_ACL},
        { 'o', ACE_WRITE_OWNER},
        { 's', ACE_SYNCHRONIZE}
};

#define ACE_PERM_COUNT (sizeof (ace_perm_table) / sizeof (value_table_t))

value_table_t aclent_perm_table[] = {
        { 'r', S_IROTH},
        { 'w', S_IWOTH},
        { 'x', S_IXOTH}
};

#define ACLENT_PERM_COUNT (sizeof (aclent_perm_table) / sizeof (value_table_t))

value_table_t inherit_table[] = {
        {'f', ACE_FILE_INHERIT_ACE},
        {'d', ACE_DIRECTORY_INHERIT_ACE},
        {'i', ACE_INHERIT_ONLY_ACE},
        {'n', ACE_NO_PROPAGATE_INHERIT_ACE},
        {'S', ACE_SUCCESSFUL_ACCESS_ACE_FLAG},
        {'F', ACE_FAILED_ACCESS_ACE_FLAG},
        {'I', ACE_INHERITED_ACE}
};

#define IFLAG_COUNT (sizeof (inherit_table) / sizeof (value_table_t))
#define IFLAG_COUNT_V1 6 /* Older version compatibility */

/*
 * compute value from a permission table or inheritance table
 * based on string passed in.  If positional is set then
 * string must match order in permtab, otherwise any order
 * is allowed.
 */
int
compute_values(value_table_t *permtab, int count,
    char *permstr, int positional, uint32_t *mask)
{
        uint32_t perm_val = 0;
        char *pstr;
        int i, found;

        if (count < 0)
                return (1);

        if (positional) {
                for (i = 0, pstr = permstr; i != count && pstr &&
                    *pstr; i++, pstr++) {
                        if (*pstr == permtab[i].p_letter) {
                                perm_val |= permtab[i].p_value;
                        } else if (*pstr != '-') {
                                return (1);
                        }
                }
        } else {  /* random order single letters with no '-' */
                for (pstr = permstr; pstr && *pstr; pstr++) {
                        for (found = 0, i = 0; i != count; i++) {
                                if (*pstr == permtab[i].p_letter) {
                                        perm_val |= permtab[i].p_value;
                                        found = 1;
                                        break;
                                }
                        }
                        if (found == 0)
                                return (1);
                }
        }

        *mask = perm_val;
        return (0);
}


int
ace_inherit_helper(char *str, uint32_t *imask, int table_length)
{
        int rc = 0;

        if (strlen(str) == table_length) {
                /*
                 * If the string == table_length then first check to see it's
                 * in positional format.  If that fails then see if it's in
                 * non-positional format.
                 */
                if (compute_values(inherit_table, table_length, str,
                    1, imask) && compute_values(inherit_table,
                    table_length, str, 0, imask)) {
                        rc = 1;
                }
        } else {
                rc = compute_values(inherit_table, table_length, str, 0, imask);
        }

        return (rc ? EACL_INHERIT_ERROR : 0);
}

/*
 * compute value for inheritance flags.
 */
int
compute_ace_inherit(char *str, uint32_t *imask)
{
        int rc = 0;

        rc = ace_inherit_helper(str, imask, IFLAG_COUNT);

        if (rc && strlen(str) != IFLAG_COUNT) {

                /* is it an old formatted inherit string? */
                rc = ace_inherit_helper(str, imask, IFLAG_COUNT_V1);
        }

        return (rc);
}


/*
 * compute value for ACE permissions.
 */
int
compute_ace_perms(char *str, uint32_t *mask)
{
        int positional = 0;
        int error;

        if (strlen(str) == ACE_PERM_COUNT)
                positional = 1;

        error = compute_values(ace_perm_table, ACE_PERM_COUNT,
            str, positional, mask);

        if (error && positional) {
                /*
                 * If positional was set, then make sure permissions
                 * aren't actually valid in non positional case where
                 * all permissions are specified, just in random order.
                 */
                error = compute_values(ace_perm_table,
                    ACE_PERM_COUNT, str, 0, mask);
        }
        if (error)
                error = EACL_PERM_MASK_ERROR;

        return (error);
}



/*
 * compute values for aclent permissions.
 */
int
compute_aclent_perms(char *str, o_mode_t *mask)
{
        int error;
        uint32_t pmask;

        if (strlen(str) != ACLENT_PERM_COUNT)
                return (EACL_PERM_MASK_ERROR);

        *mask = 0;
        error = compute_values(aclent_perm_table, ACLENT_PERM_COUNT,
            str, 1, &pmask);
        if (error == 0) {
                *mask = (o_mode_t)pmask;
        } else
                error = EACL_PERM_MASK_ERROR;
        return (error);
}

/*
 * determine ACE permissions.
 */
int
ace_perm_mask(struct acl_perm_type *aclperm, uint32_t *mask)
{
        int error;

        if (aclperm->perm_style == PERM_TYPE_EMPTY) {
                *mask = 0;
                return (0);
        }

        if (aclperm->perm_style == PERM_TYPE_ACE) {
                *mask = aclperm->perm_val;
                return (0);
        }

        error = compute_ace_perms(aclperm->perm_str, mask);
        if (error) {
                acl_error(dgettext(TEXT_DOMAIN,
                    "Invalid permission(s) '%s' specified\n"),
                    aclperm->perm_str);
                return (EACL_PERM_MASK_ERROR);
        }

        return (0);
}