/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hotplugd_impl.h" /* * Buffer management for results. */ typedef struct i_buffer { uint64_t seqnum; char *buffer; struct i_buffer *next; } i_buffer_t; static uint64_t buffer_seqnum = 1; static i_buffer_t *buffer_list = NULL; static pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER; /* * Door file descriptor. */ static int door_fd = -1; /* * Function prototypes. */ static void door_server(void *, char *, size_t, door_desc_t *, uint_t); static int check_auth(ucred_t *, const char *); static int cmd_getinfo(nvlist_t *, nvlist_t **); static int cmd_changestate(nvlist_t *, nvlist_t **); static int cmd_private(hp_cmd_t, nvlist_t *, nvlist_t **); static void add_buffer(uint64_t, char *); static void free_buffer(uint64_t); static uint64_t get_seqnum(void); static char *state_str(int); static int audit_session(ucred_t *, adt_session_data_t **); static void audit_changestate(ucred_t *, char *, char *, char *, int, int, int); static void audit_setprivate(ucred_t *, char *, char *, char *, char *, int); /* * door_server_init() * * Create the door file, and initialize the door server. */ boolean_t door_server_init(void) { int fd; /* Create the door file */ if ((fd = open(HOTPLUGD_DOOR, O_CREAT|O_EXCL|O_RDONLY, 0644)) == -1) { if (errno == EEXIST) { log_err("Door service is already running.\n"); } else { log_err("Cannot open door file '%s': %s\n", HOTPLUGD_DOOR, strerror(errno)); } return (B_FALSE); } (void) close(fd); /* Initialize the door service */ if ((door_fd = door_create(door_server, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { log_err("Cannot create door service: %s\n", strerror(errno)); return (B_FALSE); } /* Cleanup stale door associations */ (void) fdetach(HOTPLUGD_DOOR); /* Associate door service with door file */ if (fattach(door_fd, HOTPLUGD_DOOR) != 0) { log_err("Cannot attach to door file '%s': %s\n", HOTPLUGD_DOOR, strerror(errno)); (void) door_revoke(door_fd); (void) fdetach(HOTPLUGD_DOOR); door_fd = -1; return (B_FALSE); } return (B_TRUE); } /* * door_server_fini() * * Terminate and cleanup the door server. */ void door_server_fini(void) { if (door_fd != -1) { (void) door_revoke(door_fd); (void) fdetach(HOTPLUGD_DOOR); } (void) unlink(HOTPLUGD_DOOR); } /* * door_server() * * This routine is the handler which responds to each door call. * Each incoming door call is expected to send a packed nvlist * of arguments which describe the requested action. And each * response is sent back as a packed nvlist of results. * * Results are always allocated on the heap. A global list of * allocated result buffers is managed, and each one is tracked * by a unique sequence number. The final step in the protocol * is for the caller to send a short response using the sequence * number when the buffer can be released. */ /*ARGSUSED*/ static void door_server(void *cookie, char *argp, size_t sz, door_desc_t *dp, uint_t ndesc) { nvlist_t *args = NULL; nvlist_t *results = NULL; hp_cmd_t cmd; int rv; dprintf("Door call: cookie=%p, argp=%p, sz=%d\n", cookie, (void *)argp, sz); /* Special case to free a results buffer */ if (sz == sizeof (uint64_t)) { free_buffer(*(uint64_t *)(uintptr_t)argp); (void) door_return(NULL, 0, NULL, 0); return; } /* Unpack the arguments nvlist */ if (nvlist_unpack(argp, sz, &args, 0) != 0) { log_err("Cannot unpack door arguments.\n"); rv = EINVAL; goto fail; } /* Extract the requested command */ if (nvlist_lookup_int32(args, HPD_CMD, (int32_t *)&cmd) != 0) { log_err("Cannot decode door command.\n"); rv = EINVAL; goto fail; } /* Implement the command */ switch (cmd) { case HP_CMD_GETINFO: rv = cmd_getinfo(args, &results); break; case HP_CMD_CHANGESTATE: rv = cmd_changestate(args, &results); break; case HP_CMD_SETPRIVATE: case HP_CMD_GETPRIVATE: rv = cmd_private(cmd, args, &results); break; default: rv = EINVAL; break; } /* The arguments nvlist is no longer needed */ nvlist_free(args); args = NULL; /* * If an nvlist was constructed for the results, * then pack the results nvlist and return it. */ if (results != NULL) { uint64_t seqnum; char *buf = NULL; size_t len = 0; /* Add a sequence number to the results */ seqnum = get_seqnum(); if (nvlist_add_uint64(results, HPD_SEQNUM, seqnum) != 0) { log_err("Cannot add sequence number.\n"); rv = EFAULT; goto fail; } /* Pack the results nvlist */ if (nvlist_pack(results, &buf, &len, NV_ENCODE_NATIVE, 0) != 0) { log_err("Cannot pack door results.\n"); rv = EFAULT; goto fail; } /* Link results buffer into list */ add_buffer(seqnum, buf); /* The results nvlist is no longer needed */ nvlist_free(results); /* Return the results */ (void) door_return(buf, len, NULL, 0); return; } /* Return result code (when no nvlist) */ (void) door_return((char *)&rv, sizeof (int), NULL, 0); return; fail: log_err("Door call failed (%s)\n", strerror(rv)); nvlist_free(args); nvlist_free(results); (void) door_return((char *)&rv, sizeof (int), NULL, 0); } /* * check_auth() * * Perform an RBAC authorization check. */ static int check_auth(ucred_t *ucred, const char *auth) { struct passwd pwd; uid_t euid; char buf[MAXPATHLEN]; euid = ucred_geteuid(ucred); if ((getpwuid_r(euid, &pwd, buf, sizeof (buf)) == NULL) || (chkauthattr(auth, pwd.pw_name) == 0)) { log_info("Unauthorized door call.\n"); return (-1); } return (0); } /* * cmd_getinfo() * * Implements the door command to get a hotplug information snapshot. */ static int cmd_getinfo(nvlist_t *args, nvlist_t **resultsp) { hp_node_t root; nvlist_t *results; char *path; char *connection; char *buf = NULL; size_t len = 0; uint_t flags; int rv; dprintf("cmd_getinfo:\n"); /* Get arguments */ if (nvlist_lookup_string(args, HPD_PATH, &path) != 0) { dprintf("cmd_getinfo: invalid arguments.\n"); return (EINVAL); } if (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) connection = NULL; if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0) flags = 0; /* Get and pack the requested snapshot */ if ((rv = getinfo(path, connection, flags, &root)) == 0) { rv = hp_pack(root, &buf, &len); hp_fini(root); } dprintf("cmd_getinfo: getinfo(): rv = %d, buf = %p.\n", rv, (void *)buf); /* * If the above failed or there is no snapshot, * then only return a status code. */ if (rv != 0) return (rv); if (buf == NULL) return (EFAULT); /* Allocate nvlist for results */ if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) { dprintf("cmd_getinfo: nvlist_alloc() failed.\n"); free(buf); return (ENOMEM); } /* Add snapshot and successful status to results */ if ((nvlist_add_int32(results, HPD_STATUS, 0) != 0) || (nvlist_add_byte_array(results, HPD_INFO, (uchar_t *)buf, len) != 0)) { dprintf("cmd_getinfo: nvlist add failure.\n"); nvlist_free(results); free(buf); return (ENOMEM); } /* Packed snapshot no longer needed */ free(buf); /* Success */ *resultsp = results; return (0); } /* * cmd_changestate() * * Implements the door command to initate a state change operation. * * NOTE: requires 'modify' authorization. */ static int cmd_changestate(nvlist_t *args, nvlist_t **resultsp) { hp_node_t root = NULL; nvlist_t *results = NULL; char *path, *connection; ucred_t *uc = NULL; uint_t flags; int rv, state, old_state, status; dprintf("cmd_changestate:\n"); /* Get arguments */ if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) || (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) || (nvlist_lookup_int32(args, HPD_STATE, &state) != 0)) { dprintf("cmd_changestate: invalid arguments.\n"); return (EINVAL); } if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0) flags = 0; /* Get caller's credentials */ if (door_ucred(&uc) != 0) { log_err("Cannot get door credentials (%s)\n", strerror(errno)); return (EACCES); } /* Check authorization */ if (check_auth(uc, HP_MODIFY_AUTH) != 0) { dprintf("cmd_changestate: access denied.\n"); audit_changestate(uc, HP_MODIFY_AUTH, path, connection, state, -1, ADT_FAIL_VALUE_AUTH); ucred_free(uc); return (EACCES); } /* Perform the state change operation */ status = changestate(path, connection, state, flags, &old_state, &root); dprintf("cmd_changestate: changestate() == %d\n", status); /* Audit the operation */ audit_changestate(uc, HP_MODIFY_AUTH, path, connection, state, old_state, status); /* Caller's credentials no longer needed */ ucred_free(uc); /* * Pack the results into an nvlist if there is an error snapshot. * * If any error occurs while packing the results, the original * error code from changestate() above is still returned. */ if (root != NULL) { char *buf = NULL; size_t len = 0; dprintf("cmd_changestate: results nvlist required.\n"); /* Pack and discard the error snapshot */ rv = hp_pack(root, &buf, &len); hp_fini(root); if (rv != 0) { dprintf("cmd_changestate: hp_pack() failed (%s).\n", strerror(rv)); return (status); } /* Allocate nvlist for results */ if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) { dprintf("cmd_changestate: nvlist_alloc() failed.\n"); free(buf); return (status); } /* Add the results into the nvlist */ if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) || (nvlist_add_byte_array(results, HPD_INFO, (uchar_t *)buf, len) != 0)) { dprintf("cmd_changestate: nvlist add failed.\n"); nvlist_free(results); free(buf); return (status); } *resultsp = results; } return (status); } /* * cmd_private() * * Implementation of the door command to set or get bus private options. * * NOTE: requires 'modify' authorization for the 'set' command. */ static int cmd_private(hp_cmd_t cmd, nvlist_t *args, nvlist_t **resultsp) { nvlist_t *results = NULL; ucred_t *uc = NULL; char *path, *connection, *options; char *values = NULL; int status; dprintf("cmd_private:\n"); /* Get caller's credentials */ if ((cmd == HP_CMD_SETPRIVATE) && (door_ucred(&uc) != 0)) { log_err("Cannot get door credentials (%s)\n", strerror(errno)); return (EACCES); } /* Get arguments */ if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) || (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) || (nvlist_lookup_string(args, HPD_OPTIONS, &options) != 0)) { dprintf("cmd_private: invalid arguments.\n"); return (EINVAL); } /* Check authorization */ if ((cmd == HP_CMD_SETPRIVATE) && (check_auth(uc, HP_MODIFY_AUTH) != 0)) { dprintf("cmd_private: access denied.\n"); audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options, ADT_FAIL_VALUE_AUTH); ucred_free(uc); return (EACCES); } /* Perform the operation */ status = private_options(path, connection, cmd, options, &values); dprintf("cmd_private: private_options() == %d\n", status); /* Audit the operation */ if (cmd == HP_CMD_SETPRIVATE) { audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options, status); ucred_free(uc); } /* Construct an nvlist if values were returned */ if (values != NULL) { /* Allocate nvlist for results */ if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) { dprintf("cmd_private: nvlist_alloc() failed.\n"); free(values); return (ENOMEM); } /* Add values and status to the results */ if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) || (nvlist_add_string(results, HPD_OPTIONS, values) != 0)) { dprintf("cmd_private: nvlist add failed.\n"); nvlist_free(results); free(values); return (ENOMEM); } /* The values string is no longer needed */ free(values); *resultsp = results; } return (status); } /* * get_seqnum() * * Allocate the next unique sequence number for a results buffer. */ static uint64_t get_seqnum(void) { uint64_t seqnum; (void) pthread_mutex_lock(&buffer_lock); seqnum = buffer_seqnum++; (void) pthread_mutex_unlock(&buffer_lock); return (seqnum); } /* * add_buffer() * * Link a results buffer into the list containing all buffers. */ static void add_buffer(uint64_t seqnum, char *buf) { i_buffer_t *node; if ((node = (i_buffer_t *)malloc(sizeof (i_buffer_t))) == NULL) { /* The consequence is a memory leak. */ log_err("Cannot allocate results buffer: %s\n", strerror(errno)); return; } node->seqnum = seqnum; node->buffer = buf; (void) pthread_mutex_lock(&buffer_lock); node->next = buffer_list; buffer_list = node; (void) pthread_mutex_unlock(&buffer_lock); } /* * free_buffer() * * Remove a results buffer from the list containing all buffers. */ static void free_buffer(uint64_t seqnum) { i_buffer_t *node, *prev; (void) pthread_mutex_lock(&buffer_lock); prev = NULL; node = buffer_list; while (node) { if (node->seqnum == seqnum) { dprintf("Free buffer %lld\n", seqnum); if (prev) { prev->next = node->next; } else { buffer_list = node->next; } free(node->buffer); free(node); break; } prev = node; node = node->next; } (void) pthread_mutex_unlock(&buffer_lock); } /* * audit_session() * * Initialize an audit session. */ static int audit_session(ucred_t *ucred, adt_session_data_t **sessionp) { adt_session_data_t *session; if (adt_start_session(&session, NULL, 0) != 0) { log_err("Cannot start audit session.\n"); return (-1); } if (adt_set_from_ucred(session, ucred, ADT_NEW) != 0) { log_err("Cannot set audit session from ucred.\n"); (void) adt_end_session(session); return (-1); } *sessionp = session; return (0); } /* * audit_changestate() * * Audit a 'changestate' door command. */ static void audit_changestate(ucred_t *ucred, char *auth, char *path, char *connection, int new_state, int old_state, int result) { adt_session_data_t *session; adt_event_data_t *event; int pass_fail, fail_reason; if (audit_session(ucred, &session) != 0) return; if ((event = adt_alloc_event(session, ADT_hotplug_state)) == NULL) { (void) adt_end_session(session); return; } if (result == 0) { pass_fail = ADT_SUCCESS; fail_reason = ADT_SUCCESS; } else { pass_fail = ADT_FAILURE; fail_reason = result; } event->adt_hotplug_state.auth_used = auth; event->adt_hotplug_state.device_path = path; event->adt_hotplug_state.connection = connection; event->adt_hotplug_state.new_state = state_str(new_state); event->adt_hotplug_state.old_state = state_str(old_state); /* Put the event */ if (adt_put_event(event, pass_fail, fail_reason) != 0) log_err("Cannot put audit event.\n"); adt_free_event(event); (void) adt_end_session(session); } /* * audit_setprivate() * * Audit a 'set private' door command. */ static void audit_setprivate(ucred_t *ucred, char *auth, char *path, char *connection, char *options, int result) { adt_session_data_t *session; adt_event_data_t *event; int pass_fail, fail_reason; if (audit_session(ucred, &session) != 0) return; if ((event = adt_alloc_event(session, ADT_hotplug_set)) == NULL) { (void) adt_end_session(session); return; } if (result == 0) { pass_fail = ADT_SUCCESS; fail_reason = ADT_SUCCESS; } else { pass_fail = ADT_FAILURE; fail_reason = result; } event->adt_hotplug_set.auth_used = auth; event->adt_hotplug_set.device_path = path; event->adt_hotplug_set.connection = connection; event->adt_hotplug_set.options = options; /* Put the event */ if (adt_put_event(event, pass_fail, fail_reason) != 0) log_err("Cannot put audit event.\n"); adt_free_event(event); (void) adt_end_session(session); } /* * state_str() * * Convert a state from integer to string. */ static char * state_str(int state) { switch (state) { case DDI_HP_CN_STATE_EMPTY: return ("EMPTY"); case DDI_HP_CN_STATE_PRESENT: return ("PRESENT"); case DDI_HP_CN_STATE_POWERED: return ("POWERED"); case DDI_HP_CN_STATE_ENABLED: return ("ENABLED"); case DDI_HP_CN_STATE_PORT_EMPTY: return ("PORT-EMPTY"); case DDI_HP_CN_STATE_PORT_PRESENT: return ("PORT-PRESENT"); case DDI_HP_CN_STATE_OFFLINE: return ("OFFLINE"); case DDI_HP_CN_STATE_ATTACHED: return ("ATTACHED"); case DDI_HP_CN_STATE_MAINTENANCE: return ("MAINTENANCE"); case DDI_HP_CN_STATE_ONLINE: return ("ONLINE"); default: return ("UNKNOWN"); } }