root/usr/src/lib/libipmi/common/ipmi_user.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <libipmi.h>
#include <string.h>

#include "ipmi_impl.h"

typedef struct ipmi_user_impl {
        ipmi_list_t     iu_list;
        ipmi_user_t     iu_user;
} ipmi_user_impl_t;

/*
 * Get User Access.  See section 22.27.
 *
 * See libipmi.h for a complete description of IPMI reference material.
 */

typedef struct ipmi_get_user_access_req {
        DECL_BITFIELD2(
            igua_channel                :4,
            __reserved1                 :4);
        DECL_BITFIELD2(
            igua_uid                    :2,
            __reserved2                 :6);
} ipmi_get_user_access_req_t;

#define IPMI_CMD_GET_USER_ACCESS        0x44

typedef struct ipmi_get_user_access {
        DECL_BITFIELD2(
            igua_max_uid                :4,
            __reserved1                 :4);
        DECL_BITFIELD2(
            igua_enable_status          :4,
            igua_enabled_uid            :4);
        DECL_BITFIELD2(
            __reserved2                 :4,
            igua_fixed_uid              :4);
        DECL_BITFIELD5(
            __reserved3                 :1,
            igua_only_callback          :1,
            igua_link_auth_enable       :1,
            igua_ipmi_msg_enable        :1,
            igua_privilege_level        :4);
} ipmi_get_user_access_t;

#define IPMI_USER_ENABLE_UNSPECIFIED    0x00
#define IPMI_USER_ENABLE_SETPASSWD      0x01
#define IPMI_USER_DISABLE_SETPASSWD     0x02

#define IPMI_USER_CHANNEL_CURRENT       0xe

/*
 * Get User Name.  See section 22.29
 */

#define IPMI_CMD_GET_USER_NAME          0x46

/*
 * Set User Password.  See section 22.30
 */

#define IPMI_CMD_SET_USER_PASSWORD      0x47

typedef struct ipmi_set_user_password {
        DECL_BITFIELD3(
            isup_uid            :6,
            __reserved1         :1,
            isup_len20          :1);
        DECL_BITFIELD2(
            isup_op             :2,
            __reserved2         :6);
        char            isup_passwd[20];
} ipmi_set_user_password_t;

#define IPMI_PASSWORD_OP_DISABLE        0x0
#define IPMI_PASSWORD_OP_ENABLE         0x1
#define IPMI_PASSWORD_OP_SET            0x2
#define IPMI_PASSWORD_OP_TEST           0x3

static ipmi_get_user_access_t *
ipmi_get_user_access(ipmi_handle_t *ihp, uint8_t channel, uint8_t uid)
{
        ipmi_cmd_t cmd, *resp;
        ipmi_get_user_access_req_t req = { 0 };

        req.igua_channel = channel;
        req.igua_uid = uid;

        cmd.ic_netfn = IPMI_NETFN_APP;
        cmd.ic_cmd = IPMI_CMD_GET_USER_ACCESS;
        cmd.ic_lun = 0;
        cmd.ic_data = &req;
        cmd.ic_dlen = sizeof (req);

        if ((resp = ipmi_send(ihp, &cmd)) == NULL) {
                /*
                 * If sessions aren't supported on the current channel, some
                 * service processors (notably Sun's ILOM) will return an
                 * invalid request completion code (0xCC).  For these SPs, we
                 * translate this to the more appropriate EIPMI_INVALID_COMMAND.
                 */
                if (ipmi_errno(ihp) == EIPMI_INVALID_REQUEST)
                        (void) ipmi_set_error(ihp, EIPMI_INVALID_COMMAND,
                            NULL);
                return (NULL);
        }

        if (resp->ic_dlen < sizeof (ipmi_get_user_access_t)) {
                (void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL);
                return (NULL);
        }

        return (resp->ic_data);
}

static const char *
ipmi_get_user_name(ipmi_handle_t *ihp, uint8_t uid)
{
        ipmi_cmd_t cmd, *resp;

        cmd.ic_netfn = IPMI_NETFN_APP;
        cmd.ic_cmd = IPMI_CMD_GET_USER_NAME;
        cmd.ic_lun = 0;
        cmd.ic_data = &uid;
        cmd.ic_dlen = sizeof (uid);

        if ((resp = ipmi_send(ihp, &cmd)) == NULL)
                return (NULL);

        if (resp->ic_dlen < 16) {
                (void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL);
                return (NULL);
        }

        return (resp->ic_data);
}

void
ipmi_user_clear(ipmi_handle_t *ihp)
{
        ipmi_user_impl_t *uip;

        while ((uip = ipmi_list_next(&ihp->ih_users)) != NULL) {
                ipmi_list_delete(&ihp->ih_users, uip);
                ipmi_free(ihp, uip->iu_user.iu_name);
                ipmi_free(ihp, uip);
        }
}

/*
 * Returns user information in a well-defined structure.
 */
int
ipmi_user_iter(ipmi_handle_t *ihp, int (*func)(ipmi_user_t *, void *),
    void *data)
{
        ipmi_get_user_access_t *resp;
        uint8_t i, uid_max;
        ipmi_user_impl_t *uip;
        ipmi_user_t *up;
        const char *name;
        uint8_t channel;
        ipmi_deviceid_t *devid;

        ipmi_user_clear(ihp);

        channel = IPMI_USER_CHANNEL_CURRENT;

        /*
         * Get the number of active users on the system by requesting the first
         * user ID (1).
         */
        if ((resp = ipmi_get_user_access(ihp, channel, 1)) == NULL) {
                /*
                 * Some versions of the Sun ILOM have a bug which prevent the
                 * GET USER ACCESS command from succeeding over the default
                 * channel.  If this fails and we are on ILOM, then attempt to
                 * use the standard channel (1) instead.
                 */
                if ((devid = ipmi_get_deviceid(ihp)) == NULL)
                        return (-1);

                if (!ipmi_is_sun_ilom(devid))
                        return (-1);

                channel = 1;
                if ((resp = ipmi_get_user_access(ihp, channel, 1)) == NULL)
                        return (-1);
        }

        uid_max = resp->igua_max_uid;
        for (i = 1; i <= uid_max; i++) {
                if (i != 1 && (resp = ipmi_get_user_access(ihp,
                    channel, i)) == NULL)
                        return (-1);

                if ((uip = ipmi_zalloc(ihp, sizeof (ipmi_user_impl_t))) == NULL)
                        return (-1);

                up = &uip->iu_user;

                up->iu_enabled = resp->igua_enabled_uid;
                up->iu_uid = i;
                up->iu_ipmi_msg_enable = resp->igua_ipmi_msg_enable;
                up->iu_link_auth_enable = resp->igua_link_auth_enable;
                up->iu_priv = resp->igua_privilege_level;

                ipmi_list_append(&ihp->ih_users, uip);

                /*
                 * If we are requesting a username that doesn't have a
                 * supported username, we may get an INVALID REQUEST response.
                 * If this is the case, then continue as if there is no known
                 * username.
                 */
                if ((name = ipmi_get_user_name(ihp, i)) == NULL) {
                        if (ipmi_errno(ihp) == EIPMI_INVALID_REQUEST)
                                continue;
                        else
                                return (-1);
                }

                if (*name == '\0')
                        continue;

                if ((up->iu_name = ipmi_strdup(ihp, name)) == NULL)
                        return (-1);
        }

        for (uip = ipmi_list_next(&ihp->ih_users); uip != NULL;
            uip = ipmi_list_next(uip)) {
                if (func(&uip->iu_user, data) != 0)
                        return (-1);
        }

        return (0);
}

typedef struct ipmi_user_cb {
        const char      *uic_name;
        uint8_t         uic_uid;
        ipmi_user_t     *uic_result;
} ipmi_user_cb_t;

static int
ipmi_user_callback(ipmi_user_t *up, void *data)
{
        ipmi_user_cb_t *cbp = data;

        if (cbp->uic_result != NULL)
                return (0);

        if (up->iu_name) {
                if (strcmp(up->iu_name, cbp->uic_name) == 0)
                        cbp->uic_result = up;
        } else if (up->iu_uid == cbp->uic_uid) {
                cbp->uic_result = up;
        }

        return (0);
}

ipmi_user_t *
ipmi_user_lookup_name(ipmi_handle_t *ihp, const char *name)
{
        ipmi_user_cb_t cb = { 0 };

        cb.uic_name = name;
        cb.uic_result = NULL;

        if (ipmi_user_iter(ihp, ipmi_user_callback, &cb) != 0)
                return (NULL);

        if (cb.uic_result == NULL)
                (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT,
                    "no such user");

        return (cb.uic_result);
}

ipmi_user_t *
ipmi_user_lookup_id(ipmi_handle_t *ihp, uint8_t uid)
{
        ipmi_user_cb_t cb = { 0 };

        cb.uic_uid = uid;
        cb.uic_result = NULL;

        if (ipmi_user_iter(ihp, ipmi_user_callback, &cb) != 0)
                return (NULL);

        if (cb.uic_result == NULL)
                (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT,
                    "no such user");

        return (cb.uic_result);
}

int
ipmi_user_set_password(ipmi_handle_t *ihp, uint8_t uid, const char *passwd)
{
        ipmi_set_user_password_t req = { 0 };
        ipmi_cmd_t cmd;

        req.isup_uid = uid;
        req.isup_op = IPMI_PASSWORD_OP_SET;

        if (strlen(passwd) > 19)
                return (ipmi_set_error(ihp, EIPMI_INVALID_REQUEST,
                    "password length must be less than 20 characters"));

        if (strlen(passwd) > 15)
                req.isup_len20 = 1;

        (void) strcpy(req.isup_passwd, passwd);

        cmd.ic_netfn = IPMI_NETFN_APP;
        cmd.ic_cmd = IPMI_CMD_SET_USER_PASSWORD;
        cmd.ic_lun = 0;
        cmd.ic_data = &req;
        if (req.isup_len20)
                cmd.ic_dlen = sizeof (req);
        else
                cmd.ic_dlen = sizeof (req) - 4;

        if (ipmi_send(ihp, &cmd) == NULL)
                return (-1);

        return (0);
}