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 */
52typedef struct i_buffer {
53	uint64_t	seqnum;
54	char		*buffer;
55	struct i_buffer	*next;
56} i_buffer_t;
57
58static uint64_t		buffer_seqnum = 1;
59static i_buffer_t	*buffer_list = NULL;
60static pthread_mutex_t	buffer_lock = PTHREAD_MUTEX_INITIALIZER;
61
62/*
63 * Door file descriptor.
64 */
65static int	door_fd = -1;
66
67/*
68 * Function prototypes.
69 */
70static void	door_server(void *, char *, size_t, door_desc_t *, uint_t);
71static int	check_auth(ucred_t *, const char *);
72static int	cmd_getinfo(nvlist_t *, nvlist_t **);
73static int	cmd_changestate(nvlist_t *, nvlist_t **);
74static int	cmd_private(hp_cmd_t, nvlist_t *, nvlist_t **);
75static void	add_buffer(uint64_t, char *);
76static void	free_buffer(uint64_t);
77static uint64_t	get_seqnum(void);
78static char	*state_str(int);
79static int	audit_session(ucred_t *, adt_session_data_t **);
80static void	audit_changestate(ucred_t *, char *, char *, char *, int, int,
81		    int);
82static 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 */
90boolean_t
91door_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 */
135void
136door_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*/
161static void
162door_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
254fail:
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 */
266static int
267check_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 */
289static int
290cmd_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 */
362static int
363cmd_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 */
461static int
462cmd_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 */
540static uint64_t
541get_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 */
559static void
560add_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 */
587static void
588free_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 */
621static int
622audit_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 */
646static void
647audit_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 */
689static void
690audit_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 */
731static char *
732state_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