xref: /illumos-gate/usr/src/cmd/hotplugd/hotplugd_door.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <strings.h>
33 #include <alloca.h>
34 #include <door.h>
35 #include <pthread.h>
36 #include <synch.h>
37 #include <pwd.h>
38 #include <auth_list.h>
39 #include <auth_attr.h>
40 #include <bsm/adt.h>
41 #include <bsm/adt_event.h>
42 #include <sys/sunddi.h>
43 #include <sys/ddi_hp.h>
44 #include <libnvpair.h>
45 #include <libhotplug.h>
46 #include <libhotplug_impl.h>
47 #include "hotplugd_impl.h"
48 
49 /*
50  * Buffer management for results.
51  */
52 typedef struct i_buffer {
53 	uint64_t	seqnum;
54 	char		*buffer;
55 	struct i_buffer	*next;
56 } i_buffer_t;
57 
58 static uint64_t		buffer_seqnum = 1;
59 static i_buffer_t	*buffer_list = NULL;
60 static pthread_mutex_t	buffer_lock = PTHREAD_MUTEX_INITIALIZER;
61 
62 /*
63  * Door file descriptor.
64  */
65 static int	door_fd = -1;
66 
67 /*
68  * Function prototypes.
69  */
70 static void	door_server(void *, char *, size_t, door_desc_t *, uint_t);
71 static int	check_auth(ucred_t *, const char *);
72 static int	cmd_getinfo(nvlist_t *, nvlist_t **);
73 static int	cmd_changestate(nvlist_t *, nvlist_t **);
74 static int	cmd_private(hp_cmd_t, nvlist_t *, nvlist_t **);
75 static void	add_buffer(uint64_t, char *);
76 static void	free_buffer(uint64_t);
77 static uint64_t	get_seqnum(void);
78 static char	*state_str(int);
79 static int	audit_session(ucred_t *, adt_session_data_t **);
80 static void	audit_changestate(ucred_t *, char *, char *, char *, int, int,
81 		    int);
82 static void	audit_setprivate(ucred_t *, char *, char *, char *, char *,
83 		    int);
84 
85 /*
86  * door_server_init()
87  *
88  *	Create the door file, and initialize the door server.
89  */
90 boolean_t
91 door_server_init(void)
92 {
93 	int	fd;
94 
95 	/* Create the door file */
96 	if ((fd = open(HOTPLUGD_DOOR, O_CREAT|O_EXCL|O_RDONLY, 0644)) == -1) {
97 		if (errno == EEXIST) {
98 			log_err("Door service is already running.\n");
99 		} else {
100 			log_err("Cannot open door file '%s': %s\n",
101 			    HOTPLUGD_DOOR, strerror(errno));
102 		}
103 		return (B_FALSE);
104 	}
105 	(void) close(fd);
106 
107 	/* Initialize the door service */
108 	if ((door_fd = door_create(door_server, NULL,
109 	    DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
110 		log_err("Cannot create door service: %s\n", strerror(errno));
111 		return (B_FALSE);
112 	}
113 
114 	/* Cleanup stale door associations */
115 	(void) fdetach(HOTPLUGD_DOOR);
116 
117 	/* Associate door service with door file */
118 	if (fattach(door_fd, HOTPLUGD_DOOR) != 0) {
119 		log_err("Cannot attach to door file '%s': %s\n", HOTPLUGD_DOOR,
120 		    strerror(errno));
121 		(void) door_revoke(door_fd);
122 		(void) fdetach(HOTPLUGD_DOOR);
123 		door_fd = -1;
124 		return (B_FALSE);
125 	}
126 
127 	return (B_TRUE);
128 }
129 
130 /*
131  * door_server_fini()
132  *
133  *	Terminate and cleanup the door server.
134  */
135 void
136 door_server_fini(void)
137 {
138 	if (door_fd != -1) {
139 		(void) door_revoke(door_fd);
140 		(void) fdetach(HOTPLUGD_DOOR);
141 	}
142 
143 	(void) unlink(HOTPLUGD_DOOR);
144 }
145 
146 /*
147  * door_server()
148  *
149  *	This routine is the handler which responds to each door call.
150  *	Each incoming door call is expected to send a packed nvlist
151  *	of arguments which describe the requested action.  And each
152  *	response is sent back as a packed nvlist of results.
153  *
154  *	Results are always allocated on the heap.  A global list of
155  *	allocated result buffers is managed, and each one is tracked
156  *	by a unique sequence number.  The final step in the protocol
157  *	is for the caller to send a short response using the sequence
158  *	number when the buffer can be released.
159  */
160 /*ARGSUSED*/
161 static void
162 door_server(void *cookie, char *argp, size_t sz, door_desc_t *dp, uint_t ndesc)
163 {
164 	nvlist_t	*args = NULL;
165 	nvlist_t	*results = NULL;
166 	hp_cmd_t	cmd;
167 	int		rv;
168 
169 	dprintf("Door call: cookie=%p, argp=%p, sz=%d\n", cookie, (void *)argp,
170 	    sz);
171 
172 	/* Special case to free a results buffer */
173 	if (sz == sizeof (uint64_t)) {
174 		free_buffer(*(uint64_t *)(uintptr_t)argp);
175 		(void) door_return(NULL, 0, NULL, 0);
176 		return;
177 	}
178 
179 	/* Unpack the arguments nvlist */
180 	if (nvlist_unpack(argp, sz, &args, 0) != 0) {
181 		log_err("Cannot unpack door arguments.\n");
182 		rv = EINVAL;
183 		goto fail;
184 	}
185 
186 	/* Extract the requested command */
187 	if (nvlist_lookup_int32(args, HPD_CMD, (int32_t *)&cmd) != 0) {
188 		log_err("Cannot decode door command.\n");
189 		rv = EINVAL;
190 		goto fail;
191 	}
192 
193 	/* Implement the command */
194 	switch (cmd) {
195 	case HP_CMD_GETINFO:
196 		rv = cmd_getinfo(args, &results);
197 		break;
198 	case HP_CMD_CHANGESTATE:
199 		rv = cmd_changestate(args, &results);
200 		break;
201 	case HP_CMD_SETPRIVATE:
202 	case HP_CMD_GETPRIVATE:
203 		rv = cmd_private(cmd, args, &results);
204 		break;
205 	default:
206 		rv = EINVAL;
207 		break;
208 	}
209 
210 	/* The arguments nvlist is no longer needed */
211 	nvlist_free(args);
212 	args = NULL;
213 
214 	/*
215 	 * If an nvlist was constructed for the results,
216 	 * then pack the results nvlist and return it.
217 	 */
218 	if (results != NULL) {
219 		uint64_t	seqnum;
220 		char		*buf = NULL;
221 		size_t		len = 0;
222 
223 		/* Add a sequence number to the results */
224 		seqnum = get_seqnum();
225 		if (nvlist_add_uint64(results, HPD_SEQNUM, seqnum) != 0) {
226 			log_err("Cannot add sequence number.\n");
227 			rv = EFAULT;
228 			goto fail;
229 		}
230 
231 		/* Pack the results nvlist */
232 		if (nvlist_pack(results, &buf, &len,
233 		    NV_ENCODE_NATIVE, 0) != 0) {
234 			log_err("Cannot pack door results.\n");
235 			rv = EFAULT;
236 			goto fail;
237 		}
238 
239 		/* Link results buffer into list */
240 		add_buffer(seqnum, buf);
241 
242 		/* The results nvlist is no longer needed */
243 		nvlist_free(results);
244 
245 		/* Return the results */
246 		(void) door_return(buf, len, NULL, 0);
247 		return;
248 	}
249 
250 	/* Return result code (when no nvlist) */
251 	(void) door_return((char *)&rv, sizeof (int), NULL, 0);
252 	return;
253 
254 fail:
255 	log_err("Door call failed (%s)\n", strerror(rv));
256 	nvlist_free(args);
257 	nvlist_free(results);
258 	(void) door_return((char *)&rv, sizeof (int), NULL, 0);
259 }
260 
261 /*
262  * check_auth()
263  *
264  *	Perform an RBAC authorization check.
265  */
266 static int
267 check_auth(ucred_t *ucred, const char *auth)
268 {
269 	struct passwd	pwd;
270 	uid_t		euid;
271 	char		buf[MAXPATHLEN];
272 
273 	euid = ucred_geteuid(ucred);
274 
275 	if ((getpwuid_r(euid, &pwd, buf, sizeof (buf)) == NULL) ||
276 	    (chkauthattr(auth, pwd.pw_name) == 0)) {
277 		log_info("Unauthorized door call.\n");
278 		return (-1);
279 	}
280 
281 	return (0);
282 }
283 
284 /*
285  * cmd_getinfo()
286  *
287  *	Implements the door command to get a hotplug information snapshot.
288  */
289 static int
290 cmd_getinfo(nvlist_t *args, nvlist_t **resultsp)
291 {
292 	hp_node_t	root;
293 	nvlist_t	*results;
294 	char		*path;
295 	char		*connection;
296 	char		*buf = NULL;
297 	size_t		len = 0;
298 	uint_t		flags;
299 	int		rv;
300 
301 	dprintf("cmd_getinfo:\n");
302 
303 	/* Get arguments */
304 	if (nvlist_lookup_string(args, HPD_PATH, &path) != 0) {
305 		dprintf("cmd_getinfo: invalid arguments.\n");
306 		return (EINVAL);
307 	}
308 	if (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0)
309 		connection = NULL;
310 	if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0)
311 		flags = 0;
312 
313 	/* Get and pack the requested snapshot */
314 	if ((rv = getinfo(path, connection, flags, &root)) == 0) {
315 		rv = hp_pack(root, &buf, &len);
316 		hp_fini(root);
317 	}
318 	dprintf("cmd_getinfo: getinfo(): rv = %d, buf = %p.\n", rv,
319 	    (void *)buf);
320 
321 	/*
322 	 * If the above failed or there is no snapshot,
323 	 * then only return a status code.
324 	 */
325 	if (rv != 0)
326 		return (rv);
327 	if (buf == NULL)
328 		return (EFAULT);
329 
330 	/* Allocate nvlist for results */
331 	if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
332 		dprintf("cmd_getinfo: nvlist_alloc() failed.\n");
333 		free(buf);
334 		return (ENOMEM);
335 	}
336 
337 	/* Add snapshot and successful status to results */
338 	if ((nvlist_add_int32(results, HPD_STATUS, 0) != 0) ||
339 	    (nvlist_add_byte_array(results, HPD_INFO,
340 	    (uchar_t *)buf, len) != 0)) {
341 		dprintf("cmd_getinfo: nvlist add failure.\n");
342 		nvlist_free(results);
343 		free(buf);
344 		return (ENOMEM);
345 	}
346 
347 	/* Packed snapshot no longer needed */
348 	free(buf);
349 
350 	/* Success */
351 	*resultsp = results;
352 	return (0);
353 }
354 
355 /*
356  * cmd_changestate()
357  *
358  *	Implements the door command to initate a state change operation.
359  *
360  *	NOTE: requires 'modify' authorization.
361  */
362 static int
363 cmd_changestate(nvlist_t *args, nvlist_t **resultsp)
364 {
365 	hp_node_t	root = NULL;
366 	nvlist_t	*results = NULL;
367 	char		*path, *connection;
368 	ucred_t		*uc = NULL;
369 	uint_t		flags;
370 	int		rv, state, old_state, status;
371 
372 	dprintf("cmd_changestate:\n");
373 
374 	/* Get arguments */
375 	if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) ||
376 	    (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) ||
377 	    (nvlist_lookup_int32(args, HPD_STATE, &state) != 0)) {
378 		dprintf("cmd_changestate: invalid arguments.\n");
379 		return (EINVAL);
380 	}
381 	if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0)
382 		flags = 0;
383 
384 	/* Get caller's credentials */
385 	if (door_ucred(&uc) != 0) {
386 		log_err("Cannot get door credentials (%s)\n", strerror(errno));
387 		return (EACCES);
388 	}
389 
390 	/* Check authorization */
391 	if (check_auth(uc, HP_MODIFY_AUTH) != 0) {
392 		dprintf("cmd_changestate: access denied.\n");
393 		audit_changestate(uc, HP_MODIFY_AUTH, path, connection,
394 		    state, -1, ADT_FAIL_VALUE_AUTH);
395 		ucred_free(uc);
396 		return (EACCES);
397 	}
398 
399 	/* Perform the state change operation */
400 	status = changestate(path, connection, state, flags, &old_state, &root);
401 	dprintf("cmd_changestate: changestate() == %d\n", status);
402 
403 	/* Audit the operation */
404 	audit_changestate(uc, HP_MODIFY_AUTH, path, connection, state,
405 	    old_state, status);
406 
407 	/* Caller's credentials no longer needed */
408 	ucred_free(uc);
409 
410 	/*
411 	 * Pack the results into an nvlist if there is an error snapshot.
412 	 *
413 	 * If any error occurs while packing the results, the original
414 	 * error code from changestate() above is still returned.
415 	 */
416 	if (root != NULL) {
417 		char	*buf = NULL;
418 		size_t	len = 0;
419 
420 		dprintf("cmd_changestate: results nvlist required.\n");
421 
422 		/* Pack and discard the error snapshot */
423 		rv = hp_pack(root, &buf, &len);
424 		hp_fini(root);
425 		if (rv != 0) {
426 			dprintf("cmd_changestate: hp_pack() failed (%s).\n",
427 			    strerror(rv));
428 			return (status);
429 		}
430 
431 		/* Allocate nvlist for results */
432 		if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
433 			dprintf("cmd_changestate: nvlist_alloc() failed.\n");
434 			free(buf);
435 			return (status);
436 		}
437 
438 		/* Add the results into the nvlist */
439 		if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) ||
440 		    (nvlist_add_byte_array(results, HPD_INFO, (uchar_t *)buf,
441 		    len) != 0)) {
442 			dprintf("cmd_changestate: nvlist add failed.\n");
443 			nvlist_free(results);
444 			free(buf);
445 			return (status);
446 		}
447 
448 		*resultsp = results;
449 	}
450 
451 	return (status);
452 }
453 
454 /*
455  * cmd_private()
456  *
457  *	Implementation of the door command to set or get bus private options.
458  *
459  *	NOTE: requires 'modify' authorization for the 'set' command.
460  */
461 static int
462 cmd_private(hp_cmd_t cmd, nvlist_t *args, nvlist_t **resultsp)
463 {
464 	nvlist_t	*results = NULL;
465 	ucred_t		*uc = NULL;
466 	char		*path, *connection, *options;
467 	char		*values = NULL;
468 	int		status;
469 
470 	dprintf("cmd_private:\n");
471 
472 	/* Get caller's credentials */
473 	if ((cmd == HP_CMD_SETPRIVATE) && (door_ucred(&uc) != 0)) {
474 		log_err("Cannot get door credentials (%s)\n", strerror(errno));
475 		return (EACCES);
476 	}
477 
478 	/* Get arguments */
479 	if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) ||
480 	    (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) ||
481 	    (nvlist_lookup_string(args, HPD_OPTIONS, &options) != 0)) {
482 		dprintf("cmd_private: invalid arguments.\n");
483 		return (EINVAL);
484 	}
485 
486 	/* Check authorization */
487 	if ((cmd == HP_CMD_SETPRIVATE) &&
488 	    (check_auth(uc, HP_MODIFY_AUTH) != 0)) {
489 		dprintf("cmd_private: access denied.\n");
490 		audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options,
491 		    ADT_FAIL_VALUE_AUTH);
492 		ucred_free(uc);
493 		return (EACCES);
494 	}
495 
496 	/* Perform the operation */
497 	status = private_options(path, connection, cmd, options, &values);
498 	dprintf("cmd_private: private_options() == %d\n", status);
499 
500 	/* Audit the operation */
501 	if (cmd == HP_CMD_SETPRIVATE) {
502 		audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options,
503 		    status);
504 		ucred_free(uc);
505 	}
506 
507 	/* Construct an nvlist if values were returned */
508 	if (values != NULL) {
509 
510 		/* Allocate nvlist for results */
511 		if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
512 			dprintf("cmd_private: nvlist_alloc() failed.\n");
513 			free(values);
514 			return (ENOMEM);
515 		}
516 
517 		/* Add values and status to the results */
518 		if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) ||
519 		    (nvlist_add_string(results, HPD_OPTIONS, values) != 0)) {
520 			dprintf("cmd_private: nvlist add failed.\n");
521 			nvlist_free(results);
522 			free(values);
523 			return (ENOMEM);
524 		}
525 
526 		/* The values string is no longer needed */
527 		free(values);
528 
529 		*resultsp = results;
530 	}
531 
532 	return (status);
533 }
534 
535 /*
536  * get_seqnum()
537  *
538  *	Allocate the next unique sequence number for a results buffer.
539  */
540 static uint64_t
541 get_seqnum(void)
542 {
543 	uint64_t seqnum;
544 
545 	(void) pthread_mutex_lock(&buffer_lock);
546 
547 	seqnum = buffer_seqnum++;
548 
549 	(void) pthread_mutex_unlock(&buffer_lock);
550 
551 	return (seqnum);
552 }
553 
554 /*
555  * add_buffer()
556  *
557  *	Link a results buffer into the list containing all buffers.
558  */
559 static void
560 add_buffer(uint64_t seqnum, char *buf)
561 {
562 	i_buffer_t	*node;
563 
564 	if ((node = (i_buffer_t *)malloc(sizeof (i_buffer_t))) == NULL) {
565 		/* The consequence is a memory leak. */
566 		log_err("Cannot allocate results buffer: %s\n",
567 		    strerror(errno));
568 		return;
569 	}
570 
571 	node->seqnum = seqnum;
572 	node->buffer = buf;
573 
574 	(void) pthread_mutex_lock(&buffer_lock);
575 
576 	node->next = buffer_list;
577 	buffer_list = node;
578 
579 	(void) pthread_mutex_unlock(&buffer_lock);
580 }
581 
582 /*
583  * free_buffer()
584  *
585  *	Remove a results buffer from the list containing all buffers.
586  */
587 static void
588 free_buffer(uint64_t seqnum)
589 {
590 	i_buffer_t	*node, *prev;
591 
592 	(void) pthread_mutex_lock(&buffer_lock);
593 
594 	prev = NULL;
595 	node = buffer_list;
596 
597 	while (node) {
598 		if (node->seqnum == seqnum) {
599 			dprintf("Free buffer %lld\n", seqnum);
600 			if (prev) {
601 				prev->next = node->next;
602 			} else {
603 				buffer_list = node->next;
604 			}
605 			free(node->buffer);
606 			free(node);
607 			break;
608 		}
609 		prev = node;
610 		node = node->next;
611 	}
612 
613 	(void) pthread_mutex_unlock(&buffer_lock);
614 }
615 
616 /*
617  * audit_session()
618  *
619  *	Initialize an audit session.
620  */
621 static int
622 audit_session(ucred_t *ucred, adt_session_data_t **sessionp)
623 {
624 	adt_session_data_t	*session;
625 
626 	if (adt_start_session(&session, NULL, 0) != 0) {
627 		log_err("Cannot start audit session.\n");
628 		return (-1);
629 	}
630 
631 	if (adt_set_from_ucred(session, ucred, ADT_NEW) != 0) {
632 		log_err("Cannot set audit session from ucred.\n");
633 		(void) adt_end_session(session);
634 		return (-1);
635 	}
636 
637 	*sessionp = session;
638 	return (0);
639 }
640 
641 /*
642  * audit_changestate()
643  *
644  *	Audit a 'changestate' door command.
645  */
646 static void
647 audit_changestate(ucred_t *ucred, char *auth, char *path, char *connection,
648     int new_state, int old_state, int result)
649 {
650 	adt_session_data_t	*session;
651 	adt_event_data_t	*event;
652 	int			pass_fail, fail_reason;
653 
654 	if (audit_session(ucred, &session) != 0)
655 		return;
656 
657 	if ((event = adt_alloc_event(session, ADT_hotplug_state)) == NULL) {
658 		(void) adt_end_session(session);
659 		return;
660 	}
661 
662 	if (result == 0) {
663 		pass_fail = ADT_SUCCESS;
664 		fail_reason = ADT_SUCCESS;
665 	} else {
666 		pass_fail = ADT_FAILURE;
667 		fail_reason = result;
668 	}
669 
670 	event->adt_hotplug_state.auth_used = auth;
671 	event->adt_hotplug_state.device_path = path;
672 	event->adt_hotplug_state.connection = connection;
673 	event->adt_hotplug_state.new_state = state_str(new_state);
674 	event->adt_hotplug_state.old_state = state_str(old_state);
675 
676 	/* Put the event */
677 	if (adt_put_event(event, pass_fail, fail_reason) != 0)
678 		log_err("Cannot put audit event.\n");
679 
680 	adt_free_event(event);
681 	(void) adt_end_session(session);
682 }
683 
684 /*
685  * audit_setprivate()
686  *
687  *	Audit a 'set private' door command.
688  */
689 static void
690 audit_setprivate(ucred_t *ucred, char *auth, char *path, char *connection,
691     char *options, int result)
692 {
693 	adt_session_data_t	*session;
694 	adt_event_data_t	*event;
695 	int			pass_fail, fail_reason;
696 
697 	if (audit_session(ucred, &session) != 0)
698 		return;
699 
700 	if ((event = adt_alloc_event(session, ADT_hotplug_set)) == NULL) {
701 		(void) adt_end_session(session);
702 		return;
703 	}
704 
705 	if (result == 0) {
706 		pass_fail = ADT_SUCCESS;
707 		fail_reason = ADT_SUCCESS;
708 	} else {
709 		pass_fail = ADT_FAILURE;
710 		fail_reason = result;
711 	}
712 
713 	event->adt_hotplug_set.auth_used = auth;
714 	event->adt_hotplug_set.device_path = path;
715 	event->adt_hotplug_set.connection = connection;
716 	event->adt_hotplug_set.options = options;
717 
718 	/* Put the event */
719 	if (adt_put_event(event, pass_fail, fail_reason) != 0)
720 		log_err("Cannot put audit event.\n");
721 
722 	adt_free_event(event);
723 	(void) adt_end_session(session);
724 }
725 
726 /*
727  * state_str()
728  *
729  *	Convert a state from integer to string.
730  */
731 static char *
732 state_str(int state)
733 {
734 	switch (state) {
735 	case DDI_HP_CN_STATE_EMPTY:
736 		return ("EMPTY");
737 	case DDI_HP_CN_STATE_PRESENT:
738 		return ("PRESENT");
739 	case DDI_HP_CN_STATE_POWERED:
740 		return ("POWERED");
741 	case DDI_HP_CN_STATE_ENABLED:
742 		return ("ENABLED");
743 	case DDI_HP_CN_STATE_PORT_EMPTY:
744 		return ("PORT-EMPTY");
745 	case DDI_HP_CN_STATE_PORT_PRESENT:
746 		return ("PORT-PRESENT");
747 	case DDI_HP_CN_STATE_OFFLINE:
748 		return ("OFFLINE");
749 	case DDI_HP_CN_STATE_ATTACHED:
750 		return ("ATTACHED");
751 	case DDI_HP_CN_STATE_MAINTENANCE:
752 		return ("MAINTENANCE");
753 	case DDI_HP_CN_STATE_ONLINE:
754 		return ("ONLINE");
755 	default:
756 		return ("UNKNOWN");
757 	}
758 }
759