root/usr/src/cmd/cmd-crypto/cryptoadm/adm_kef_util.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <locale.h>
#include <sys/types.h>
#include <zone.h>
#include <sys/stat.h>
#include "cryptoadm.h"

static int err; /* To store errno which may be overwritten by gettext() */
static int build_entrylist(entry_t *pent, entrylist_t **pplist);
static entry_t *dup_entry(entry_t *pent1);
static mechlist_t *dup_mechlist(mechlist_t *plist);
static entry_t *getent(char *provname, entrylist_t *entrylist);
static int interpret(char *buf, entry_t **ppent);
static int parse_sup_dis_list(const char *buf, entry_t *pent);


/*
 * Duplicate the mechanism list.  A null pointer is returned if the storage
 * space available is insufficient or the input argument is NULL.
 */
static mechlist_t *
dup_mechlist(mechlist_t *plist)
{
        mechlist_t      *pres = NULL;
        mechlist_t      *pcur;
        mechlist_t      *ptmp;
        int             rc = SUCCESS;

        while (plist != NULL) {
                if (!(ptmp = create_mech(plist->name))) {
                        rc = FAILURE;
                        break;
                }

                if (pres == NULL) {
                        pres = pcur = ptmp;
                } else {
                        pcur->next = ptmp;
                        pcur = pcur->next;
                }
                plist = plist->next;
        }

        if (rc != SUCCESS) {
                free_mechlist(pres);
                return (NULL);
        }

        return (pres);
}


/*
 * Get the number of mechanisms in the mechanism list.
 */
int
get_mech_count(mechlist_t *plist)
{
        int count = 0;

        while (plist != NULL) {
                count++;
                plist = plist->next;
        }
        return (count);
}

/*
 * Create one item of type entry_t with the provider name.
 * Return NULL if there's not enough memory or provname is NULL.
 */
entry_t *
create_entry(char *provname)
{
        entry_t         *pent = NULL;

        if (provname == NULL) {
                return (NULL);
        }

        pent = calloc(1, sizeof (entry_t));
        if (pent == NULL) {
                cryptodebug("out of memory.");
                return (NULL);
        }

        (void) strlcpy(pent->name, provname, MAXNAMELEN);
        pent->suplist = NULL;
        pent->sup_count = 0;
        pent->dislist = NULL;
        pent->dis_count = 0;
        pent->load = B_TRUE;

        return (pent);
}

/*
 * Duplicate an entry for a provider from kcf.conf.
 * Return NULL if memory is insufficient or the input argument is NULL.
 * Called by getent().
 */
static entry_t *
dup_entry(entry_t *pent1)
{
        entry_t *pent2 = NULL;

        if (pent1 == NULL) {
                return (NULL);
        }

        if ((pent2 = create_entry(pent1->name)) == NULL) {
                cryptodebug("out of memory.");
                return (NULL);
        }

        pent2->sup_count = pent1->sup_count;
        pent2->dis_count = pent1->dis_count;
        pent2->load = pent1->load;
        if (pent1->suplist != NULL) {
                pent2->suplist = dup_mechlist(pent1->suplist);
                if (pent2->suplist == NULL) {
                        free_entry(pent2);
                        return (NULL);
                }
        }
        if (pent1->dislist != NULL) {
                pent2->dislist = dup_mechlist(pent1->dislist);
                if (pent2->dislist == NULL) {
                        free_entry(pent2);
                        return (NULL);
                }
        }

        return (pent2);
}


/*
 * This routine parses the disabledlist or the supportedlist of an entry
 * in the kcf.conf configuration file.
 *
 * Arguments:
 *      buf: an input argument which is a char string with the format of
 *           "disabledlist=m1,m2,..." or "supportedlist=m1,m2,..."
 *      pent: the entry for the disabledlist.  This is an IN/OUT argument.
 *
 * Return value: SUCCESS or FAILURE.
 */
static int
parse_sup_dis_list(const char *buf, entry_t *pent)
{
        mechlist_t      *pmech = NULL;
        mechlist_t      *phead = NULL;
        char            *next_token;
        char            *value;
        int             count;
        int             supflag = B_FALSE;
        int             disflag = B_FALSE;
        int             rc = SUCCESS;

        if (strncmp(buf, EF_SUPPORTED, strlen(EF_SUPPORTED)) == 0) {
                supflag = B_TRUE;
        } else if (strncmp(buf, EF_DISABLED, strlen(EF_DISABLED)) == 0) {
                disflag = B_TRUE;
        } else {
                /* should not come here */
                return (FAILURE);
        }

        if (value = strpbrk(buf, SEP_EQUAL)) {
                value++; /* get rid of = */
        } else {
                cryptodebug("failed to parse the kcf.conf file.");
                return (FAILURE);
        }

        if ((next_token = strtok(value, SEP_COMMA)) == NULL) {
                cryptodebug("failed to parse the kcf.conf file.");
                return (FAILURE);
        }

        if ((pmech = create_mech(next_token)) == NULL) {
                return (FAILURE);
        }

        if (supflag) {
                        if (pent->suplist != NULL) {
                                cryptodebug("multiple supportedlist entries "
                                    "for a mechanism in file kcf.conf.");
                                return (FAILURE);
                        } else {
                                pent->suplist = phead = pmech;
                        }
        } else if (disflag) {
                        if (pent->dislist != NULL) {
                                cryptodebug("multiple disabledlist entries "
                                    "for a mechanism in file kcf.conf.");
                                return (FAILURE);
                        } else {
                                pent->dislist = phead = pmech;
                        }
        }

        count = 1;
        while (next_token) {
                if (next_token = strtok(NULL, SEP_COMMA)) {
                        if ((pmech = create_mech(next_token)) == NULL) {
                                rc = FAILURE;
                                break;
                        }
                        count++;
                        phead->next = pmech;
                        phead = phead->next;
                }
        }

        if (rc == SUCCESS) {
                if (supflag) {
                        pent->sup_count = count;
                } else if (disflag) {
                        pent->dis_count = count;
                }
        } else {
                free_mechlist(phead);
        }

        return (rc);
}


/*
 * Convert a char string containing a line about a provider
 * from kcf.conf into an entry_t structure.
 *
 * Note: the input string, buf, may be modified by this function.
 *
 * See ent2str(), the reverse of this function, for the format of
 * kcf.conf lines.
 */
static int
interpret(char *buf, entry_t **ppent)
{
        entry_t *pent = NULL;
        char    *token1;
        char    *token2;
        char    *token3;
        int     rc;

        /* Get provider name */
        if ((token1 = strtok(buf, SEP_COLON)) == NULL) { /* buf is NULL */
                return (FAILURE);
        };

        pent = create_entry(token1);
        if (pent == NULL) {
                cryptodebug("out of memory.");
                return (FAILURE);
        }

        if ((token2 = strtok(NULL, SEP_SEMICOLON)) == NULL) {
                /* The entry contains a provider name only */
                free_entry(pent);
                return (FAILURE);
        }

        if (strncmp(token2, EF_UNLOAD, strlen(EF_UNLOAD)) == 0) {
                pent->load = B_FALSE; /* cryptoadm unload */
                token2 = strtok(NULL, SEP_SEMICOLON);
                /*
                 * If token2 is NULL, the entry contains a
                 * provider name:unload only
                 */
        }

        if (token2 != NULL) {
                /*
                 * Either supportedlist or disabledlist or both are present.
                 * Need to call strtok() to get token3 first, as function
                 * parse_sup_dis_list() makes strtok() calls on the
                 * token2 substring.
                 */
                token3 = strtok(NULL, SEP_SEMICOLON); /* optional */

                /* parse supportedlist (or disabledlist if no supportedlist) */
                if ((rc = parse_sup_dis_list(token2, pent)) != SUCCESS) {
                        free_entry(pent);
                        return (rc);
                }

                /* parse disabledlist (if there's a supportedlist) */
                if ((token3 != NULL) && ((rc = parse_sup_dis_list(token3,
                    pent)) != SUCCESS)) {
                        free_entry(pent);
                        return (rc);
                }
        }

        *ppent = pent;
        return (SUCCESS);
}


/*
 * Add an entry about a provider from kcf.conf to the end of an entry list.
 * If the entry list pplist is NULL, create the linked list with pent as the
 * first element.
 */
static int
build_entrylist(entry_t *pent, entrylist_t **pplist)
{
        entrylist_t     *pentlist;
        entrylist_t     *pcur = NULL;

        pentlist = malloc(sizeof (entrylist_t));
        if (pentlist == NULL) {
                cryptodebug("out of memory.");
                return (FAILURE);
        }
        pentlist->pent = pent;
        pentlist->next = NULL;

        if (*pplist) {
                pcur = *pplist;
                while (pcur->next != NULL)
                        pcur = pcur->next;
                pcur->next = pentlist;
        } else { /* empty list */
                *pplist = pentlist;
        }

        return (SUCCESS);
}



/*
 * Find the entry with the "provname" name from the entry list and duplicate
 * it.  Called by getent_kef().
 */
static entry_t *
getent(char *provname, entrylist_t *entrylist)
{
        boolean_t       found = B_FALSE;
        entry_t         *pent1 = NULL;

        if ((provname == NULL) || (entrylist == NULL)) {
                return (NULL);
        }

        while (!found && entrylist) {
                if (strcmp(entrylist->pent->name, provname) == 0) {
                        found = B_TRUE;
                        pent1 = entrylist->pent;
                } else {
                        entrylist = entrylist->next;
                }
        }

        if (!found) {
                return (NULL);
        }

        /* duplicate the entry to be returned */
        return (dup_entry(pent1));
}


/*
 * Free memory in entry_t.
 * That is, the supported and disabled lists for a provider
 * from kcf.conf.
 */
void
free_entry(entry_t  *pent)
{
        if (pent == NULL) {
                return;
        } else {
                free_mechlist(pent->suplist);
                free_mechlist(pent->dislist);
                free(pent);
        }
}


/*
 * Free elements in a entrylist_t linked list,
 * which lists providers in kcf.conf.
 */
void
free_entrylist(entrylist_t *entrylist)
{
        entrylist_t *pnext;

        while (entrylist != NULL) {
                pnext = entrylist->next;
                free_entry(entrylist->pent);
                entrylist = pnext;
        }
}


/*
 * Convert an entry_t to a kcf.conf line string.  Build a string to insert
 * as a line in file kcf.conf.  Based on the content of an entry_t,
 * the result string is one of these 8 forms:
 *  - name:supportedlist=m1,m2,...,mj
 *  - name:disabledlist=m1,m2,...,mj
 *  - name:supportedlist=m1,...,mj;disabledlist=m1,m2,...,mk
 *
 *  - name:unload
 *  - name:unload;supportedlist=m1,m2,...,mj
 *  - name:unload;disabledlist=m1,m2,...,mj
 *  - name:unload;supportedlist=m1,...,mj;disabledlist=m1,m2,...,mk
 *
 *  - (NUL character or 0-length string)
 *
 * Return a 0-length empty string if no keyword is present (that is,
 * supportedlist, disabledlist, or unload).  A kcf.conf line with just the
 * provider name with no keyword is invalid.
 *
 * Note that the caller is responsible for freeing the returned string
 * (with free_entry()).
 * See interpret() for the reverse of this function: converting a string
 * to an entry_t.
 */
char *
ent2str(entry_t *pent)
{
        char            *buf;
        mechlist_t      *pcur = NULL;
        boolean_t       keyword_already_present = B_FALSE;

        if (pent == NULL) {
                return (NULL);
        }

        if ((buf = malloc(BUFSIZ)) == NULL) {
                return (NULL);
        }

        /* convert the provider name */
        if (strlcpy(buf, pent->name, BUFSIZ) >= BUFSIZ) {
                free(buf);
                return (NULL);
        }

        if (!pent->load) { /* add "unload" keyword */
                if (strlcat(buf, SEP_COLON, BUFSIZ) >= BUFSIZ) {
                        free(buf);
                        return (NULL);
                }

                if (strlcat(buf, EF_UNLOAD, BUFSIZ) >= BUFSIZ) {
                        free(buf);
                        return (NULL);
                }

                keyword_already_present = B_TRUE;
        }

        /* convert the supported list if any */
        pcur = pent->suplist;
        if (pcur != NULL) {
                if (strlcat(buf,
                    keyword_already_present ? SEP_SEMICOLON : SEP_COLON,
                    BUFSIZ) >= BUFSIZ) {
                        free(buf);
                        return (NULL);
                }

                if (strlcat(buf, EF_SUPPORTED, BUFSIZ) >= BUFSIZ) {
                        free(buf);
                        return (NULL);
                }

                while (pcur != NULL) {
                        if (strlcat(buf, pcur->name, BUFSIZ) >= BUFSIZ) {
                                free(buf);
                                return (NULL);
                        }

                        pcur = pcur->next;
                        if (pcur != NULL) {
                                if (strlcat(buf, SEP_COMMA, BUFSIZ)
                                    >= BUFSIZ) {
                                        free(buf);
                                        return (NULL);
                                }
                        }
                }
                keyword_already_present = B_TRUE;
        }

        /* convert the disabled list if any */
        pcur = pent->dislist;
        if (pcur != NULL) {
                if (strlcat(buf,
                    keyword_already_present ? SEP_SEMICOLON : SEP_COLON,
                    BUFSIZ) >= BUFSIZ) {
                        free(buf);
                        return (NULL);
                }

                if (strlcat(buf, EF_DISABLED, BUFSIZ) >= BUFSIZ) {
                        free(buf);
                        return (NULL);
                }

                while (pcur != NULL) {
                        if (strlcat(buf, pcur->name, BUFSIZ) >= BUFSIZ) {
                                free(buf);
                                return (NULL);
                        }

                        pcur = pcur->next;
                        if (pcur != NULL) {
                                if (strlcat(buf, SEP_COMMA, BUFSIZ)
                                    >= BUFSIZ) {
                                        free(buf);
                                        return (NULL);
                                }
                        }
                }
                keyword_already_present = B_TRUE;
        }

        if (strlcat(buf, "\n", BUFSIZ) >= BUFSIZ) {
                free(buf);
                return (NULL);
        }

        if (!keyword_already_present) {
                /* Only the provider name, without a keyword, is on the line */
                buf[0] = '\0';
        }
        return (buf);
}


/*
 * Enable the mechanisms for the provider pointed by *ppent.  If allflag is
 * TRUE, enable all.  Otherwise, enable the mechanisms specified in the 3rd
 * argument "mlist".  The result will be stored in ppent also.
 */
int
enable_mechs(entry_t **ppent, boolean_t allflag, mechlist_t *mlist)
{
        entry_t         *pent;
        mechlist_t      *phead; /* the current and resulting disabled list */
        mechlist_t      *ptr = NULL;
        mechlist_t      *pcur = NULL;
        boolean_t       found;

        pent = *ppent;
        if (pent == NULL) {
                return (FAILURE);
        }

        if (allflag) {
                free_mechlist(pent->dislist);
                pent->dis_count = 0;
                pent->dislist = NULL;
                return (SUCCESS);
        }

        /*
         * for each mechanism in the to-be-enabled mechanism list,
         * -    check if it is in the current disabled list
         * -    if found, delete it from the disabled list
         *      otherwise, give a warning.
         */
        ptr = mlist;
        while (ptr != NULL) {
                found = B_FALSE;
                phead = pcur =  pent->dislist;
                while (!found && pcur) {
                        if (strcmp(pcur->name, ptr->name) == 0) {
                                found = B_TRUE;
                        } else {
                                phead = pcur;
                                pcur = pcur->next;
                        }
                }

                if (found) {
                        if (phead == pcur) {
                                pent->dislist = pent->dislist->next;
                                free(pcur);
                        } else {
                                phead->next = pcur->next;
                                free(pcur);
                        }
                        pent->dis_count--;
                } else {
                        cryptoerror(LOG_STDERR, gettext(
                            "(Warning) %1$s is either enabled already or not "
                            "a valid mechanism for %2$s"), ptr->name,
                            pent->name);
                }
                ptr = ptr->next;
        }

        if (pent->dis_count == 0) {
                pent->dislist = NULL;
        }

        return (SUCCESS);

}


/*
 * Determine if the kernel provider name, path, is a device
 * (that is, it contains a slash character (e.g., "mca/0").
 * If so, it is a hardware provider; otherwise it is a software provider.
 */
boolean_t
is_device(char *path)
{
        if (strchr(path, SEP_SLASH) != NULL) {
                return (B_TRUE);
        } else {
                return (B_FALSE);
        }
}

/*
 * Split a hardware provider name with the "name/inst_num" format into
 * a name and a number (e.g., split "mca/0" into "mca" instance 0).
 */
int
split_hw_provname(char *provname, char *pname, int *inst_num)
{
        char    name[MAXNAMELEN];
        char    *inst_str;

        if (provname == NULL) {
                return (FAILURE);
        }

        (void) strlcpy(name, provname, MAXNAMELEN);
        if (strtok(name, "/") == NULL) {
                return (FAILURE);
        }

        if ((inst_str = strtok(NULL, "/")) == NULL) {
                return (FAILURE);
        }

        (void) strlcpy(pname, name, MAXNAMELEN);
        *inst_num = atoi(inst_str);

        return (SUCCESS);
}


/*
 * Retrieve information from kcf.conf and build a hardware device entry list
 * and a software entry list of kernel crypto providers.
 *
 * This list is usually incomplete, as kernel crypto providers only have to
 * be listed in kcf.conf if a mechanism is disabled (by cryptoadm) or
 * if the kernel provider module is not one of the default kernel providers.
 *
 * The kcf.conf file is available only in the global zone.
 */
int
get_kcfconf_info(entrylist_t **ppdevlist, entrylist_t **ppsoftlist)
{
        FILE    *pfile = NULL;
        char    buffer[BUFSIZ];
        int     len;
        entry_t *pent = NULL;
        int     rc = SUCCESS;

        if ((pfile = fopen(_PATH_KCF_CONF, "r")) == NULL) {
                cryptodebug("failed to open the kcf.conf file for read only");
                return (FAILURE);
        }

        *ppdevlist = NULL;
        *ppsoftlist = NULL;
        while (fgets(buffer, BUFSIZ, pfile) != NULL) {
                if (buffer[0] == '#' || buffer[0] == ' ' ||
                    buffer[0] == '\n'|| buffer[0] == '\t') {
                        continue;   /* ignore comment lines */
                }

                len = strlen(buffer);
                if (buffer[len - 1] == '\n') { /* get rid of trailing '\n' */
                        len--;
                }
                buffer[len] = '\0';

                if ((rc = interpret(buffer,  &pent)) == SUCCESS) {
                        if (is_device(pent->name)) {
                                rc = build_entrylist(pent, ppdevlist);
                        } else {
                                rc = build_entrylist(pent, ppsoftlist);
                        }
                } else {
                        cryptoerror(LOG_STDERR, gettext(
                            "failed to parse configuration."));
                }

                if (rc != SUCCESS) {
                        free_entrylist(*ppdevlist);
                        free_entrylist(*ppsoftlist);
                        free_entry(pent);
                        break;
                }
        }

        (void) fclose(pfile);
        return (rc);
}

/*
 * Retrieve information from admin device and build a device entry list and
 * a software entry list.  This is used where there is no kcf.conf, e.g., the
 * non-global zone.
 */
int
get_admindev_info(entrylist_t **ppdevlist, entrylist_t **ppsoftlist)
{
        crypto_get_dev_list_t   *pdevlist_kernel = NULL;
        crypto_get_soft_list_t  *psoftlist_kernel = NULL;
        char                    *devname;
        int                     inst_num;
        int                     mcount;
        mechlist_t              *pmech = NULL;
        entry_t                 *pent_dev = NULL, *pent_soft = NULL;
        int                     i;
        char                    *psoftname;
        entrylist_t             *tmp_pdev = NULL;
        entrylist_t             *tmp_psoft = NULL;
        entrylist_t             *phardlist = NULL, *psoftlist = NULL;

        /*
         * Get hardware providers
         */
        if (get_dev_list(&pdevlist_kernel) != SUCCESS) {
                cryptodebug("failed to get hardware provider list from kernel");
                return (FAILURE);
        }

        for (i = 0; i < pdevlist_kernel->dl_dev_count; i++) {
                devname = pdevlist_kernel->dl_devs[i].le_dev_name;
                inst_num = pdevlist_kernel->dl_devs[i].le_dev_instance;
                mcount = pdevlist_kernel->dl_devs[i].le_mechanism_count;

                pmech = NULL;
                if (get_dev_info(devname, inst_num, mcount, &pmech) !=
                    SUCCESS) {
                        cryptodebug(
                            "failed to retrieve the mechanism list for %s/%d.",
                            devname, inst_num);
                        goto fail_out;
                }

                if ((pent_dev = create_entry(devname)) == NULL) {
                        cryptodebug("out of memory.");
                        free_mechlist(pmech);
                        goto fail_out;
                }
                pent_dev->suplist = pmech;
                pent_dev->sup_count = mcount;

                if (build_entrylist(pent_dev, &tmp_pdev) != SUCCESS) {
                        goto fail_out;
                }
        }

        free(pdevlist_kernel);
        pdevlist_kernel = NULL;

        /*
         * Get software providers
         */
        if (getzoneid() == GLOBAL_ZONEID) {
                if (get_kcfconf_info(&phardlist, &psoftlist) != SUCCESS) {
                        goto fail_out;
                }
        }

        if (get_soft_list(&psoftlist_kernel) != SUCCESS) {
                cryptodebug("failed to get software provider list from kernel");
                goto fail_out;
        }

        for (i = 0, psoftname = psoftlist_kernel->sl_soft_names;
            i < psoftlist_kernel->sl_soft_count;
            i++, psoftname = psoftname + strlen(psoftname) + 1) {
                pmech = NULL;
                if (get_soft_info(psoftname, &pmech, phardlist, psoftlist) !=
                    SUCCESS) {
                        cryptodebug(
                            "failed to retrieve the mechanism list for %s.",
                            psoftname);
                        goto fail_out;
                }

                if ((pent_soft = create_entry(psoftname)) == NULL) {
                        cryptodebug("out of memory.");
                        free_mechlist(pmech);
                        goto fail_out;
                }
                pent_soft->suplist = pmech;
                pent_soft->sup_count = get_mech_count(pmech);

                if (build_entrylist(pent_soft, &tmp_psoft) != SUCCESS) {
                        goto fail_out;
                }
        }

        free(psoftlist_kernel);
        psoftlist_kernel = NULL;

        *ppdevlist = tmp_pdev;
        *ppsoftlist = tmp_psoft;

        return (SUCCESS);

fail_out:
        if (pent_dev != NULL)
                free_entry(pent_dev);
        if (pent_soft != NULL)
                free_entry(pent_soft);

        free_entrylist(tmp_pdev);
        free_entrylist(tmp_psoft);

        if (pdevlist_kernel != NULL)
                free(pdevlist_kernel);
        if (psoftlist_kernel != NULL)
                free(psoftlist_kernel);

        return (FAILURE);
}

/*
 * Return configuration information for a kernel provider from kcf.conf.
 * For kernel software providers return a enabled list and disabled list.
 * For kernel hardware providers return just a disabled list.
 *
 * Parameters phardlist and psoftlist are supplied by get_kcfconf_info().
 * If NULL, this function calls get_kcfconf_info() internally.
 */
entry_t *
getent_kef(char *provname, entrylist_t *phardlist, entrylist_t *psoftlist)
{
        entry_t         *pent = NULL;
        boolean_t       memory_allocated = B_FALSE;

        if ((phardlist == NULL) || (psoftlist == NULL)) {
                if (get_kcfconf_info(&phardlist, &psoftlist) != SUCCESS) {
                        return (NULL);
                }
                memory_allocated = B_TRUE;
        }

        if (is_device(provname)) {
                pent = getent(provname, phardlist);
        } else {
                pent = getent(provname, psoftlist);
        }

        if (memory_allocated) {
                free_entrylist(phardlist);
                free_entrylist(psoftlist);
        }

        return (pent);
}

/*
 * Print out the provider name and the mechanism list.
 */
void
print_mechlist(char *provname, mechlist_t *pmechlist)
{
        mechlist_t *ptr = NULL;

        if (provname == NULL) {
                return;
        }

        (void) printf("%s: ", provname);
        if (pmechlist == NULL) {
                (void) printf(gettext("No mechanisms presented.\n"));
                return;
        }

        ptr = pmechlist;
        while (ptr != NULL) {
                (void) printf("%s", ptr->name);
                ptr = ptr->next;
                if (ptr == NULL) {
                        (void) printf("\n");
                } else {
                        (void) printf(",");
                }
        }
}


/*
 * Update the kcf.conf file based on the update mode:
 * - If update_mode is MODIFY_MODE, modify the entry with the same name.
 *   If not found, append a new entry to the kcf.conf file.
 * - If update_mode is DELETE_MODE, delete the entry with the same name.
 * - If update_mode is ADD_MODE, append a new entry to the kcf.conf file.
 */
int
update_kcfconf(entry_t *pent, int update_mode)
{
        boolean_t       add_it = B_FALSE;
        boolean_t       delete_it = B_FALSE;
        boolean_t       this_entry_matches = B_FALSE;
        boolean_t       found_entry = B_FALSE;
        FILE            *pfile = NULL;
        FILE            *pfile_tmp = NULL;
        char            buffer[BUFSIZ];
        char            buffer2[BUFSIZ];
        char            tmpfile_name[MAXPATHLEN];
        char            *name;
        char            *new_str = NULL;
        int             rc = SUCCESS;

        if (pent == NULL) {
                cryptoerror(LOG_STDERR, gettext("internal error."));
                return (FAILURE);
        }

        /* Check the update_mode */
        switch (update_mode) {
        case ADD_MODE:
                add_it = B_TRUE;
                /* FALLTHROUGH */
        case MODIFY_MODE:
                /* Convert the entry a string to add to kcf.conf  */
                if ((new_str = ent2str(pent)) == NULL) {
                        return (FAILURE);
                }
                if (strlen(new_str) == 0) {
                        free(new_str);
                        delete_it = B_TRUE;
                }
                break;
        case DELETE_MODE:
                delete_it = B_TRUE;
                break;
        default:
                cryptoerror(LOG_STDERR, gettext("internal error."));
                return (FAILURE);
        }

        /* Open the kcf.conf file */
        if ((pfile = fopen(_PATH_KCF_CONF, "r+")) == NULL) {
                err = errno;
                cryptoerror(LOG_STDERR,
                    gettext("failed to update the configuration - %s"),
                    strerror(err));
                cryptodebug("failed to open %s for write.", _PATH_KCF_CONF);
                return (FAILURE);
        }

        /* Lock the kcf.conf file */
        if (lockf(fileno(pfile), F_TLOCK, 0) == -1) {
                err = errno;
                cryptoerror(LOG_STDERR,
                    gettext("failed to update the configuration - %s"),
                    strerror(err));
                (void) fclose(pfile);
                return (FAILURE);
        }

        /*
         * Create a temporary file in the /etc/crypto directory to save
         * updated configuration file first.
         */
        (void) strlcpy(tmpfile_name, TMPFILE_TEMPLATE, sizeof (tmpfile_name));
        if (mkstemp(tmpfile_name) == -1) {
                err = errno;
                cryptoerror(LOG_STDERR,
                    gettext("failed to create a temporary file - %s"),
                    strerror(err));
                (void) fclose(pfile);
                return (FAILURE);
        }

        if ((pfile_tmp = fopen(tmpfile_name, "w")) == NULL) {
                err = errno;
                cryptoerror(LOG_STDERR, gettext("failed to open %s - %s"),
                    tmpfile_name, strerror(err));
                (void) fclose(pfile);
                return (FAILURE);
        }

        /*
         * Loop thru the entire kcf.conf file, insert, modify or delete
         * an entry.
         */
        while (fgets(buffer, BUFSIZ, pfile) != NULL) {
                if (add_it) {
                        if (fputs(buffer, pfile_tmp) == EOF) {
                                err = errno;
                                cryptoerror(LOG_STDERR, gettext(
                                    "failed to write to a temp file: %s."),
                                    strerror(err));
                                rc = FAILURE;
                                break;
                        }

                } else { /* modify or delete */
                        this_entry_matches = B_FALSE;

                        if (!(buffer[0] == '#' || buffer[0] == ' ' ||
                            buffer[0] == '\n'|| buffer[0] == '\t')) {
                                /*
                                 * Get the provider name from this line and
                                 * check if this is the entry to be updated
                                 * or deleted. Note: can not use "buffer"
                                 * directly because strtok will change its
                                 * value.
                                 */
                                (void) strlcpy(buffer2, buffer, BUFSIZ);
                                if ((name = strtok(buffer2, SEP_COLON)) ==
                                    NULL) {
                                        rc = FAILURE;
                                        break;
                                }

                                if (strcmp(pent->name, name) == 0) {
                                        this_entry_matches = B_TRUE;
                                        found_entry = B_TRUE;
                                }
                        }


                        if (!this_entry_matches || !delete_it) {
                                /* write this entry */
                                if (this_entry_matches) {
                                        /*
                                         * Modify this entry: get the
                                         * updated string and place into buffer.
                                         */
                                        (void) strlcpy(buffer, new_str, BUFSIZ);
                                        free(new_str);
                                }
                                /* write the (unchanged or modified) entry */
                                if (fputs(buffer, pfile_tmp) == EOF) {
                                        err = errno;
                                        cryptoerror(LOG_STDERR, gettext(
                                            "failed to write to a temp file: "
                                            "%s."), strerror(err));
                                        rc = FAILURE;
                                        break;
                                }
                        }
                }
        }

        if ((!delete_it) && (rc != FAILURE)) {
                if (add_it || !found_entry) {
                        /* append new entry to end of file */
                        if (fputs(new_str, pfile_tmp) == EOF) {
                                err = errno;
                                cryptoerror(LOG_STDERR, gettext(
                                    "failed to write to a temp file: %s."),
                                    strerror(err));
                                rc = FAILURE;
                        }
                        free(new_str);
                }
        }

        (void) fclose(pfile);
        if (fclose(pfile_tmp) != 0) {
                err = errno;
                cryptoerror(LOG_STDERR,
                    gettext("failed to close %s: %s"), tmpfile_name,
                    strerror(err));
                return (FAILURE);
        }

        /* Copy the temporary file to the kcf.conf file */
        if (rename(tmpfile_name, _PATH_KCF_CONF) == -1) {
                err = errno;
                cryptoerror(LOG_STDERR,
                    gettext("failed to update the configuration - %s"),
                    strerror(err));
                cryptodebug("failed to rename %s to %s: %s", tmpfile,
                    _PATH_KCF_CONF, strerror(err));
                rc = FAILURE;
        } else if (chmod(_PATH_KCF_CONF,
            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) {
                err = errno;
                cryptoerror(LOG_STDERR,
                    gettext("failed to update the configuration - %s"),
                    strerror(err));
                cryptodebug("failed to chmod to %s: %s", _PATH_KCF_CONF,
                    strerror(err));
                rc = FAILURE;
        } else {
                rc = SUCCESS;
        }

        if ((rc == FAILURE) && (unlink(tmpfile_name) != 0)) {
                err = errno;
                cryptoerror(LOG_STDERR, gettext(
                    "(Warning) failed to remove %s: %s"),
                    tmpfile_name, strerror(err));
        }

        return (rc);
}


/*
 * Disable the mechanisms for the provider pointed by *ppent.  If allflag is
 * TRUE, disable all.  Otherwise, disable the mechanisms specified in the
 * dislist argument.  The "infolist" argument contains the mechanism list
 * supported by this provider.
 */
int
disable_mechs(entry_t **ppent, mechlist_t *infolist, boolean_t allflag,
mechlist_t *dislist)
{
        entry_t         *pent;
        mechlist_t      *plist = NULL;
        mechlist_t      *phead = NULL;
        mechlist_t      *pmech = NULL;
        int             rc = SUCCESS;

        pent = *ppent;
        if (pent == NULL) {
                return (FAILURE);
        }

        if (allflag) {
                free_mechlist(pent->dislist);
                pent->dis_count = get_mech_count(infolist);
                if (!(pent->dislist = dup_mechlist(infolist))) {
                        return (FAILURE);
                } else {
                        return (SUCCESS);
                }
        }

        /*
         * Not disable all. Now loop thru the mechanisms specified in the
         * dislist.  If the mechanism is not supported by the provider,
         * ignore it with a warning.  If the mechanism is disabled already,
         * do nothing. Otherwise, prepend it to the beginning of the disabled
         * list of the provider.
         */
        plist = dislist;
        while (plist != NULL) {
                if (!is_in_list(plist->name, infolist)) {
                        cryptoerror(LOG_STDERR, gettext("(Warning) "
                            "%1$s is not a valid mechanism for %2$s."),
                            plist->name, pent->name);
                } else if (!is_in_list(plist->name, pent->dislist)) {
                        /* Add this mechanism into the disabled list */
                        if ((pmech = create_mech(plist->name)) == NULL) {
                                rc = FAILURE;
                                break;
                        }

                        if (pent->dislist == NULL) {
                                pent->dislist = pmech;
                        } else {
                                phead = pent->dislist;
                                pent->dislist = pmech;
                                pmech->next = phead;
                        }
                        pent->dis_count++;
                }
                plist = plist->next;
        }

        return (rc);
}

/*
 * Remove the mechanism passed, specified by mech, from the list of
 * mechanisms, if present in the list. Else, do nothing.
 *
 * Returns B_TRUE if mechanism is present in the list.
 */
boolean_t
filter_mechlist(mechlist_t **pmechlist, const char *mech)
{
        int             cnt = 0;
        mechlist_t      *ptr, *pptr;
        boolean_t       mech_present = B_FALSE;

        ptr = pptr = *pmechlist;

        while (ptr != NULL) {
                if (strncmp(ptr->name, mech, sizeof (mech_name_t)) == 0) {
                        mech_present = B_TRUE;
                        if (ptr == *pmechlist) {
                                pptr = *pmechlist = ptr->next;
                                free(ptr);
                                ptr = pptr;
                        } else {
                                pptr->next = ptr->next;
                                free(ptr);
                                ptr = pptr->next;
                        }
                } else {
                        pptr = ptr;
                        ptr = ptr->next;
                        cnt++;
                }
        }

        /* Only one entry is present */
        if (cnt == 0)
                *pmechlist = NULL;

        return (mech_present);
}



/*
 * Print out the mechanism policy for a kernel provider that has an entry
 * in the kcf.conf file.
 *
 * The flag has_random is set to B_TRUE if the provider does random
 * numbers. The flag has_mechs is set by the caller to B_TRUE if the provider
 * has some mechanisms.
 *
 * If pent is NULL, the provider doesn't have a kcf.conf entry.
 */
void
print_kef_policy(char *provname, entry_t *pent, boolean_t has_random,
    boolean_t has_mechs)
{
        mechlist_t      *ptr = NULL;
        boolean_t       rnd_disabled = B_FALSE;

        if (pent != NULL) {
                rnd_disabled = filter_mechlist(&pent->dislist, RANDOM);
                ptr = pent->dislist;
        }

        (void) printf("%s:", provname);

        if (has_mechs == B_TRUE) {
                /*
                 * TRANSLATION_NOTE
                 * This code block may need to be modified a bit to avoid
                 * constructing the text message on the fly.
                 */
                (void) printf(gettext(" all mechanisms are enabled"));
                if (ptr != NULL)
                        (void) printf(gettext(", except "));
                while (ptr != NULL) {
                        (void) printf("%s", ptr->name);
                        ptr = ptr->next;
                        if (ptr != NULL)
                                (void) printf(",");
                }
                if (ptr == NULL)
                        (void) printf(".");
        }

        /*
         * TRANSLATION_NOTE
         * "random" is a keyword and not to be translated.
         */
        if (rnd_disabled)
                (void) printf(gettext(" %s is disabled."), "random");
        else if (has_random)
                (void) printf(gettext(" %s is enabled."), "random");
        (void) printf("\n");
}


/*
 * Check if a kernel software provider is in the kernel.
 *
 * Parameters:
 * provname             Provider name
 * psoftlist_kernel     Optional software provider list.  If NULL, it will be
 *                      obtained from get_soft_list().
 * in_kernel            Set to B_TRUE if device is in the kernel, else B_FALSE
 */
int
check_kernel_for_soft(char *provname, crypto_get_soft_list_t *psoftlist_kernel,
    boolean_t *in_kernel)
{
        char            *ptr;
        int             i;
        boolean_t       psoftlist_allocated = B_FALSE;

        if (provname == NULL) {
                cryptoerror(LOG_STDERR, gettext("internal error."));
                return (FAILURE);
        }

        if (psoftlist_kernel == NULL) {
                if (get_soft_list(&psoftlist_kernel) == FAILURE) {
                        cryptodebug("failed to get the software provider list"
                        " from kernel.");
                        return (FAILURE);
                }
                psoftlist_allocated = B_TRUE;
        }

        *in_kernel = B_FALSE;
        ptr = psoftlist_kernel->sl_soft_names;
        for (i = 0; i < psoftlist_kernel->sl_soft_count; i++) {
                if (strcmp(provname, ptr) == 0) {
                        *in_kernel = B_TRUE;
                        break;
                }
                ptr = ptr + strlen(ptr) + 1;
        }

        if (psoftlist_allocated)
                free(psoftlist_kernel);

        return (SUCCESS);
}


/*
 * Check if a kernel hardware provider is in the kernel.
 *
 * Parameters:
 * provname     Provider name
 * pdevlist     Optional Hardware Crypto Device List.  If NULL, it will be
 *              obtained from get_dev_list().
 * in_kernel    Set to B_TRUE if device is in the kernel, otherwise B_FALSE
 */
int
check_kernel_for_hard(char *provname,
    crypto_get_dev_list_t *pdevlist, boolean_t *in_kernel)
{
        char            devname[MAXNAMELEN];
        int             inst_num;
        int             i;
        boolean_t       dev_list_allocated = B_FALSE;

        if (provname == NULL) {
                cryptoerror(LOG_STDERR, gettext("internal error."));
                return (FAILURE);
        }

        if (split_hw_provname(provname, devname, &inst_num) == FAILURE) {
                return (FAILURE);
        }

        if (pdevlist == NULL) {
                if (get_dev_list(&pdevlist) == FAILURE) {
                        cryptoerror(LOG_STDERR, gettext("internal error."));
                        return (FAILURE);
                }
                dev_list_allocated = B_TRUE;
        }

        *in_kernel = B_FALSE;
        for (i = 0; i < pdevlist->dl_dev_count; i++) {
                if ((strcmp(pdevlist->dl_devs[i].le_dev_name, devname) == 0) &&
                    (pdevlist->dl_devs[i].le_dev_instance == inst_num)) {
                        *in_kernel = B_TRUE;
                        break;
                }
        }

        if (dev_list_allocated)
                free(pdevlist);

        return (SUCCESS);
}