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 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 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 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 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 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 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 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 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 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 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 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 * 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