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
door_server_init(void)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
door_server_fini(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
door_server(void * cookie,char * argp,size_t sz,door_desc_t * dp,uint_t ndesc)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 if (args != NULL)
257 nvlist_free(args);
258 if (results != NULL)
259 nvlist_free(results);
260 (void) door_return((char *)&rv, sizeof (int), NULL, 0);
261 }
262
263 /*
264 * check_auth()
265 *
266 * Perform an RBAC authorization check.
267 */
268 static int
check_auth(ucred_t * ucred,const char * auth)269 check_auth(ucred_t *ucred, const char *auth)
270 {
271 struct passwd pwd;
272 uid_t euid;
273 char buf[MAXPATHLEN];
274
275 euid = ucred_geteuid(ucred);
276
277 if ((getpwuid_r(euid, &pwd, buf, sizeof (buf)) == NULL) ||
278 (chkauthattr(auth, pwd.pw_name) == 0)) {
279 log_info("Unauthorized door call.\n");
280 return (-1);
281 }
282
283 return (0);
284 }
285
286 /*
287 * cmd_getinfo()
288 *
289 * Implements the door command to get a hotplug information snapshot.
290 */
291 static int
cmd_getinfo(nvlist_t * args,nvlist_t ** resultsp)292 cmd_getinfo(nvlist_t *args, nvlist_t **resultsp)
293 {
294 hp_node_t root;
295 nvlist_t *results;
296 char *path;
297 char *connection;
298 char *buf = NULL;
299 size_t len = 0;
300 uint_t flags;
301 int rv;
302
303 dprintf("cmd_getinfo:\n");
304
305 /* Get arguments */
306 if (nvlist_lookup_string(args, HPD_PATH, &path) != 0) {
307 dprintf("cmd_getinfo: invalid arguments.\n");
308 return (EINVAL);
309 }
310 if (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0)
311 connection = NULL;
312 if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0)
313 flags = 0;
314
315 /* Get and pack the requested snapshot */
316 if ((rv = getinfo(path, connection, flags, &root)) == 0) {
317 rv = hp_pack(root, &buf, &len);
318 hp_fini(root);
319 }
320 dprintf("cmd_getinfo: getinfo(): rv = %d, buf = %p.\n", rv,
321 (void *)buf);
322
323 /*
324 * If the above failed or there is no snapshot,
325 * then only return a status code.
326 */
327 if (rv != 0)
328 return (rv);
329 if (buf == NULL)
330 return (EFAULT);
331
332 /* Allocate nvlist for results */
333 if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
334 dprintf("cmd_getinfo: nvlist_alloc() failed.\n");
335 free(buf);
336 return (ENOMEM);
337 }
338
339 /* Add snapshot and successful status to results */
340 if ((nvlist_add_int32(results, HPD_STATUS, 0) != 0) ||
341 (nvlist_add_byte_array(results, HPD_INFO,
342 (uchar_t *)buf, len) != 0)) {
343 dprintf("cmd_getinfo: nvlist add failure.\n");
344 nvlist_free(results);
345 free(buf);
346 return (ENOMEM);
347 }
348
349 /* Packed snapshot no longer needed */
350 free(buf);
351
352 /* Success */
353 *resultsp = results;
354 return (0);
355 }
356
357 /*
358 * cmd_changestate()
359 *
360 * Implements the door command to initate a state change operation.
361 *
362 * NOTE: requires 'modify' authorization.
363 */
364 static int
cmd_changestate(nvlist_t * args,nvlist_t ** resultsp)365 cmd_changestate(nvlist_t *args, nvlist_t **resultsp)
366 {
367 hp_node_t root = NULL;
368 nvlist_t *results = NULL;
369 char *path, *connection;
370 ucred_t *uc = NULL;
371 uint_t flags;
372 int rv, state, old_state, status;
373
374 dprintf("cmd_changestate:\n");
375
376 /* Get arguments */
377 if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) ||
378 (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) ||
379 (nvlist_lookup_int32(args, HPD_STATE, &state) != 0)) {
380 dprintf("cmd_changestate: invalid arguments.\n");
381 return (EINVAL);
382 }
383 if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0)
384 flags = 0;
385
386 /* Get caller's credentials */
387 if (door_ucred(&uc) != 0) {
388 log_err("Cannot get door credentials (%s)\n", strerror(errno));
389 return (EACCES);
390 }
391
392 /* Check authorization */
393 if (check_auth(uc, HP_MODIFY_AUTH) != 0) {
394 dprintf("cmd_changestate: access denied.\n");
395 audit_changestate(uc, HP_MODIFY_AUTH, path, connection,
396 state, -1, ADT_FAIL_VALUE_AUTH);
397 ucred_free(uc);
398 return (EACCES);
399 }
400
401 /* Perform the state change operation */
402 status = changestate(path, connection, state, flags, &old_state, &root);
403 dprintf("cmd_changestate: changestate() == %d\n", status);
404
405 /* Audit the operation */
406 audit_changestate(uc, HP_MODIFY_AUTH, path, connection, state,
407 old_state, status);
408
409 /* Caller's credentials no longer needed */
410 ucred_free(uc);
411
412 /*
413 * Pack the results into an nvlist if there is an error snapshot.
414 *
415 * If any error occurs while packing the results, the original
416 * error code from changestate() above is still returned.
417 */
418 if (root != NULL) {
419 char *buf = NULL;
420 size_t len = 0;
421
422 dprintf("cmd_changestate: results nvlist required.\n");
423
424 /* Pack and discard the error snapshot */
425 rv = hp_pack(root, &buf, &len);
426 hp_fini(root);
427 if (rv != 0) {
428 dprintf("cmd_changestate: hp_pack() failed (%s).\n",
429 strerror(rv));
430 return (status);
431 }
432
433 /* Allocate nvlist for results */
434 if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
435 dprintf("cmd_changestate: nvlist_alloc() failed.\n");
436 free(buf);
437 return (status);
438 }
439
440 /* Add the results into the nvlist */
441 if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) ||
442 (nvlist_add_byte_array(results, HPD_INFO, (uchar_t *)buf,
443 len) != 0)) {
444 dprintf("cmd_changestate: nvlist add failed.\n");
445 nvlist_free(results);
446 free(buf);
447 return (status);
448 }
449
450 *resultsp = results;
451 }
452
453 return (status);
454 }
455
456 /*
457 * cmd_private()
458 *
459 * Implementation of the door command to set or get bus private options.
460 *
461 * NOTE: requires 'modify' authorization for the 'set' command.
462 */
463 static int
cmd_private(hp_cmd_t cmd,nvlist_t * args,nvlist_t ** resultsp)464 cmd_private(hp_cmd_t cmd, nvlist_t *args, nvlist_t **resultsp)
465 {
466 nvlist_t *results = NULL;
467 ucred_t *uc = NULL;
468 char *path, *connection, *options;
469 char *values = NULL;
470 int status;
471
472 dprintf("cmd_private:\n");
473
474 /* Get caller's credentials */
475 if ((cmd == HP_CMD_SETPRIVATE) && (door_ucred(&uc) != 0)) {
476 log_err("Cannot get door credentials (%s)\n", strerror(errno));
477 return (EACCES);
478 }
479
480 /* Get arguments */
481 if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) ||
482 (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) ||
483 (nvlist_lookup_string(args, HPD_OPTIONS, &options) != 0)) {
484 dprintf("cmd_private: invalid arguments.\n");
485 return (EINVAL);
486 }
487
488 /* Check authorization */
489 if ((cmd == HP_CMD_SETPRIVATE) &&
490 (check_auth(uc, HP_MODIFY_AUTH) != 0)) {
491 dprintf("cmd_private: access denied.\n");
492 audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options,
493 ADT_FAIL_VALUE_AUTH);
494 ucred_free(uc);
495 return (EACCES);
496 }
497
498 /* Perform the operation */
499 status = private_options(path, connection, cmd, options, &values);
500 dprintf("cmd_private: private_options() == %d\n", status);
501
502 /* Audit the operation */
503 if (cmd == HP_CMD_SETPRIVATE) {
504 audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options,
505 status);
506 ucred_free(uc);
507 }
508
509 /* Construct an nvlist if values were returned */
510 if (values != NULL) {
511
512 /* Allocate nvlist for results */
513 if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
514 dprintf("cmd_private: nvlist_alloc() failed.\n");
515 free(values);
516 return (ENOMEM);
517 }
518
519 /* Add values and status to the results */
520 if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) ||
521 (nvlist_add_string(results, HPD_OPTIONS, values) != 0)) {
522 dprintf("cmd_private: nvlist add failed.\n");
523 nvlist_free(results);
524 free(values);
525 return (ENOMEM);
526 }
527
528 /* The values string is no longer needed */
529 free(values);
530
531 *resultsp = results;
532 }
533
534 return (status);
535 }
536
537 /*
538 * get_seqnum()
539 *
540 * Allocate the next unique sequence number for a results buffer.
541 */
542 static uint64_t
get_seqnum(void)543 get_seqnum(void)
544 {
545 uint64_t seqnum;
546
547 (void) pthread_mutex_lock(&buffer_lock);
548
549 seqnum = buffer_seqnum++;
550
551 (void) pthread_mutex_unlock(&buffer_lock);
552
553 return (seqnum);
554 }
555
556 /*
557 * add_buffer()
558 *
559 * Link a results buffer into the list containing all buffers.
560 */
561 static void
add_buffer(uint64_t seqnum,char * buf)562 add_buffer(uint64_t seqnum, char *buf)
563 {
564 i_buffer_t *node;
565
566 if ((node = (i_buffer_t *)malloc(sizeof (i_buffer_t))) == NULL) {
567 /* The consequence is a memory leak. */
568 log_err("Cannot allocate results buffer: %s\n",
569 strerror(errno));
570 return;
571 }
572
573 node->seqnum = seqnum;
574 node->buffer = buf;
575
576 (void) pthread_mutex_lock(&buffer_lock);
577
578 node->next = buffer_list;
579 buffer_list = node;
580
581 (void) pthread_mutex_unlock(&buffer_lock);
582 }
583
584 /*
585 * free_buffer()
586 *
587 * Remove a results buffer from the list containing all buffers.
588 */
589 static void
free_buffer(uint64_t seqnum)590 free_buffer(uint64_t seqnum)
591 {
592 i_buffer_t *node, *prev;
593
594 (void) pthread_mutex_lock(&buffer_lock);
595
596 prev = NULL;
597 node = buffer_list;
598
599 while (node) {
600 if (node->seqnum == seqnum) {
601 dprintf("Free buffer %lld\n", seqnum);
602 if (prev) {
603 prev->next = node->next;
604 } else {
605 buffer_list = node->next;
606 }
607 free(node->buffer);
608 free(node);
609 break;
610 }
611 prev = node;
612 node = node->next;
613 }
614
615 (void) pthread_mutex_unlock(&buffer_lock);
616 }
617
618 /*
619 * audit_session()
620 *
621 * Initialize an audit session.
622 */
623 static int
audit_session(ucred_t * ucred,adt_session_data_t ** sessionp)624 audit_session(ucred_t *ucred, adt_session_data_t **sessionp)
625 {
626 adt_session_data_t *session;
627
628 if (adt_start_session(&session, NULL, 0) != 0) {
629 log_err("Cannot start audit session.\n");
630 return (-1);
631 }
632
633 if (adt_set_from_ucred(session, ucred, ADT_NEW) != 0) {
634 log_err("Cannot set audit session from ucred.\n");
635 (void) adt_end_session(session);
636 return (-1);
637 }
638
639 *sessionp = session;
640 return (0);
641 }
642
643 /*
644 * audit_changestate()
645 *
646 * Audit a 'changestate' door command.
647 */
648 static void
audit_changestate(ucred_t * ucred,char * auth,char * path,char * connection,int new_state,int old_state,int result)649 audit_changestate(ucred_t *ucred, char *auth, char *path, char *connection,
650 int new_state, int old_state, int result)
651 {
652 adt_session_data_t *session;
653 adt_event_data_t *event;
654 int pass_fail, fail_reason;
655
656 if (audit_session(ucred, &session) != 0)
657 return;
658
659 if ((event = adt_alloc_event(session, ADT_hotplug_state)) == NULL) {
660 (void) adt_end_session(session);
661 return;
662 }
663
664 if (result == 0) {
665 pass_fail = ADT_SUCCESS;
666 fail_reason = ADT_SUCCESS;
667 } else {
668 pass_fail = ADT_FAILURE;
669 fail_reason = result;
670 }
671
672 event->adt_hotplug_state.auth_used = auth;
673 event->adt_hotplug_state.device_path = path;
674 event->adt_hotplug_state.connection = connection;
675 event->adt_hotplug_state.new_state = state_str(new_state);
676 event->adt_hotplug_state.old_state = state_str(old_state);
677
678 /* Put the event */
679 if (adt_put_event(event, pass_fail, fail_reason) != 0)
680 log_err("Cannot put audit event.\n");
681
682 adt_free_event(event);
683 (void) adt_end_session(session);
684 }
685
686 /*
687 * audit_setprivate()
688 *
689 * Audit a 'set private' door command.
690 */
691 static void
audit_setprivate(ucred_t * ucred,char * auth,char * path,char * connection,char * options,int result)692 audit_setprivate(ucred_t *ucred, char *auth, char *path, char *connection,
693 char *options, int result)
694 {
695 adt_session_data_t *session;
696 adt_event_data_t *event;
697 int pass_fail, fail_reason;
698
699 if (audit_session(ucred, &session) != 0)
700 return;
701
702 if ((event = adt_alloc_event(session, ADT_hotplug_set)) == NULL) {
703 (void) adt_end_session(session);
704 return;
705 }
706
707 if (result == 0) {
708 pass_fail = ADT_SUCCESS;
709 fail_reason = ADT_SUCCESS;
710 } else {
711 pass_fail = ADT_FAILURE;
712 fail_reason = result;
713 }
714
715 event->adt_hotplug_set.auth_used = auth;
716 event->adt_hotplug_set.device_path = path;
717 event->adt_hotplug_set.connection = connection;
718 event->adt_hotplug_set.options = options;
719
720 /* Put the event */
721 if (adt_put_event(event, pass_fail, fail_reason) != 0)
722 log_err("Cannot put audit event.\n");
723
724 adt_free_event(event);
725 (void) adt_end_session(session);
726 }
727
728 /*
729 * state_str()
730 *
731 * Convert a state from integer to string.
732 */
733 static char *
state_str(int state)734 state_str(int state)
735 {
736 switch (state) {
737 case DDI_HP_CN_STATE_EMPTY:
738 return ("EMPTY");
739 case DDI_HP_CN_STATE_PRESENT:
740 return ("PRESENT");
741 case DDI_HP_CN_STATE_POWERED:
742 return ("POWERED");
743 case DDI_HP_CN_STATE_ENABLED:
744 return ("ENABLED");
745 case DDI_HP_CN_STATE_PORT_EMPTY:
746 return ("PORT-EMPTY");
747 case DDI_HP_CN_STATE_PORT_PRESENT:
748 return ("PORT-PRESENT");
749 case DDI_HP_CN_STATE_OFFLINE:
750 return ("OFFLINE");
751 case DDI_HP_CN_STATE_ATTACHED:
752 return ("ATTACHED");
753 case DDI_HP_CN_STATE_MAINTENANCE:
754 return ("MAINTENANCE");
755 case DDI_HP_CN_STATE_ONLINE:
756 return ("ONLINE");
757 default:
758 return ("UNKNOWN");
759 }
760 }
761