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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * Client NDR RPC interface. 28 */ 29 30 #include <sys/types.h> 31 #include <sys/errno.h> 32 #include <time.h> 33 #include <strings.h> 34 #include <assert.h> 35 #include <thread.h> 36 #include <synch.h> 37 #include <smbsrv/libsmb.h> 38 #include <smbsrv/libsmbrdr.h> 39 #include <smbsrv/libmlrpc.h> 40 #include <smbsrv/libmlsvc.h> 41 42 /* 43 * Server info cache entry expiration in seconds. 44 */ 45 #define NDR_SVINFO_TIMEOUT 1800 46 47 typedef struct ndr_svinfo { 48 list_node_t svi_lnd; 49 time_t svi_tcached; 50 char svi_server[MAXNAMELEN]; 51 char svi_domain[MAXNAMELEN]; 52 srvsvc_server_info_t svi_svinfo; 53 } ndr_svinfo_t; 54 55 typedef struct ndr_svlist { 56 list_t svl_list; 57 mutex_t svl_mtx; 58 boolean_t svl_init; 59 } ndr_svlist_t; 60 61 static ndr_svlist_t ndr_svlist; 62 63 static int ndr_xa_init(ndr_client_t *, ndr_xa_t *); 64 static int ndr_xa_exchange(ndr_client_t *, ndr_xa_t *); 65 static int ndr_xa_read(ndr_client_t *, ndr_xa_t *); 66 static void ndr_xa_preserve(ndr_client_t *, ndr_xa_t *); 67 static void ndr_xa_destruct(ndr_client_t *, ndr_xa_t *); 68 static void ndr_xa_release(ndr_client_t *); 69 70 static int ndr_svinfo_lookup(char *, char *, srvsvc_server_info_t *); 71 static boolean_t ndr_svinfo_match(const char *, const char *, const 72 ndr_svinfo_t *); 73 static boolean_t ndr_svinfo_expired(ndr_svinfo_t *); 74 75 /* 76 * Initialize the RPC client interface: create the server info cache. 77 */ 78 void 79 ndr_rpc_init(void) 80 { 81 (void) mutex_lock(&ndr_svlist.svl_mtx); 82 83 if (!ndr_svlist.svl_init) { 84 list_create(&ndr_svlist.svl_list, sizeof (ndr_svinfo_t), 85 offsetof(ndr_svinfo_t, svi_lnd)); 86 ndr_svlist.svl_init = B_TRUE; 87 } 88 89 (void) mutex_unlock(&ndr_svlist.svl_mtx); 90 } 91 92 /* 93 * Terminate the RPC client interface: flush and destroy the server info 94 * cache. 95 */ 96 void 97 ndr_rpc_fini(void) 98 { 99 ndr_svinfo_t *svi; 100 101 (void) mutex_lock(&ndr_svlist.svl_mtx); 102 103 if (ndr_svlist.svl_init) { 104 while ((svi = list_head(&ndr_svlist.svl_list)) != NULL) { 105 list_remove(&ndr_svlist.svl_list, svi); 106 free(svi->svi_svinfo.sv_name); 107 free(svi->svi_svinfo.sv_comment); 108 free(svi); 109 } 110 111 list_destroy(&ndr_svlist.svl_list); 112 ndr_svlist.svl_init = B_FALSE; 113 } 114 115 (void) mutex_unlock(&ndr_svlist.svl_mtx); 116 } 117 118 /* 119 * This call must be made to initialize an RPC client structure and bind 120 * to the remote service before any RPCs can be exchanged with that service. 121 * 122 * The mlsvc_handle_t is a wrapper that is used to associate an RPC handle 123 * with the client context for an instance of the interface. The handle 124 * is zeroed to ensure that it doesn't look like a valid handle - 125 * handle content is provided by the remove service. 126 * 127 * The client points to this top-level handle so that we know when to 128 * unbind and teardown the connection. As each handle is initialized it 129 * will inherit a reference to the client context. 130 */ 131 int 132 ndr_rpc_bind(mlsvc_handle_t *handle, char *server, char *domain, 133 char *username, const char *service) 134 { 135 ndr_client_t *clnt; 136 ndr_service_t *svc; 137 srvsvc_server_info_t svinfo; 138 int remote_os; 139 int fid; 140 int rc; 141 142 if (handle == NULL || server == NULL || 143 domain == NULL || username == NULL) 144 return (-1); 145 146 if ((svc = ndr_svc_lookup_name(service)) == NULL) 147 return (-1); 148 149 /* 150 * Set the default based on the assumption that most 151 * servers will be Windows 2000 or later. 152 * Don't lookup the svinfo if this is a SRVSVC request 153 * because the SRVSVC is used to get the server info. 154 * None of the SRVSVC calls depend on the remote OS. 155 */ 156 remote_os = NATIVE_OS_WIN2000; 157 158 if (strcasecmp(service, "SRVSVC") != 0) { 159 if (ndr_svinfo_lookup(server, domain, &svinfo) == 0) 160 remote_os = svinfo.sv_os; 161 } 162 163 if ((clnt = malloc(sizeof (ndr_client_t))) == NULL) 164 return (-1); 165 166 fid = smbrdr_open_pipe(server, domain, username, svc->endpoint); 167 if (fid < 0) { 168 free(clnt); 169 return (-1); 170 } 171 172 bzero(clnt, sizeof (ndr_client_t)); 173 clnt->handle = &handle->handle; 174 clnt->fid = fid; 175 176 ndr_svc_binding_pool_init(&clnt->binding_list, 177 clnt->binding_pool, NDR_N_BINDING_POOL); 178 179 clnt->xa_init = ndr_xa_init; 180 clnt->xa_exchange = ndr_xa_exchange; 181 clnt->xa_read = ndr_xa_read; 182 clnt->xa_preserve = ndr_xa_preserve; 183 clnt->xa_destruct = ndr_xa_destruct; 184 clnt->xa_release = ndr_xa_release; 185 186 bzero(&handle->handle, sizeof (ndr_hdid_t)); 187 handle->clnt = clnt; 188 handle->remote_os = remote_os; 189 190 if (ndr_rpc_get_heap(handle) == NULL) { 191 free(clnt); 192 return (-1); 193 } 194 195 rc = ndr_clnt_bind(clnt, service, &clnt->binding); 196 if (NDR_DRC_IS_FAULT(rc)) { 197 (void) smbrdr_close_pipe(fid); 198 ndr_heap_destroy(clnt->heap); 199 free(clnt); 200 handle->clnt = NULL; 201 return (-1); 202 } 203 204 return (0); 205 } 206 207 /* 208 * Unbind and close the pipe to an RPC service. 209 * 210 * If the heap has been preserved we need to go through an xa release. 211 * The heap is preserved during an RPC call because that's where data 212 * returned from the server is stored. 213 * 214 * Otherwise we destroy the heap directly. 215 */ 216 void 217 ndr_rpc_unbind(mlsvc_handle_t *handle) 218 { 219 ndr_client_t *clnt = handle->clnt; 220 221 if (clnt->heap_preserved) 222 ndr_clnt_free_heap(clnt); 223 else 224 ndr_heap_destroy(clnt->heap); 225 226 (void) smbrdr_close_pipe(clnt->fid); 227 free(handle->clnt); 228 bzero(handle, sizeof (mlsvc_handle_t)); 229 } 230 231 /* 232 * Call the RPC function identified by opnum. The remote service is 233 * identified by the handle, which should have been initialized by 234 * ndr_rpc_bind. 235 * 236 * If the RPC call is successful (returns 0), the caller must call 237 * ndr_rpc_release to release the heap. Otherwise, we release the 238 * heap here. 239 */ 240 int 241 ndr_rpc_call(mlsvc_handle_t *handle, int opnum, void *params) 242 { 243 ndr_client_t *clnt = handle->clnt; 244 int rc; 245 246 if (ndr_rpc_get_heap(handle) == NULL) 247 return (-1); 248 249 rc = ndr_clnt_call(clnt->binding, opnum, params); 250 251 if (NDR_DRC_IS_FAULT(rc)) { 252 ndr_rpc_release(handle); 253 return (-1); 254 } 255 256 return (0); 257 } 258 259 /* 260 * Returns the Native-OS of the RPC server. 261 */ 262 uint32_t 263 ndr_rpc_server_os(mlsvc_handle_t *handle) 264 { 265 return (handle->remote_os); 266 } 267 268 /* 269 * Get the session key from a bound RPC client handle. 270 * 271 * The key returned is the 16-byte "user session key" 272 * established by the underlying authentication protocol 273 * (either Kerberos or NTLM). This key is needed for 274 * SAM RPC calls such as SamrSetInformationUser, etc. 275 * See [MS-SAMR] sections: 2.2.3.3, 2.2.7.21, 2.2.7.25. 276 * 277 * Returns zero (success) or an errno. 278 */ 279 int 280 ndr_rpc_get_ssnkey(mlsvc_handle_t *handle, 281 unsigned char *ssn_key, size_t len) 282 { 283 ndr_client_t *clnt = handle->clnt; 284 int rc; 285 286 if (clnt == NULL) 287 return (EINVAL); 288 289 rc = smbrdr_get_ssnkey(clnt->fid, ssn_key, len); 290 return (rc); 291 } 292 293 void * 294 ndr_rpc_malloc(mlsvc_handle_t *handle, size_t size) 295 { 296 ndr_heap_t *heap; 297 298 if ((heap = ndr_rpc_get_heap(handle)) == NULL) 299 return (NULL); 300 301 return (ndr_heap_malloc(heap, size)); 302 } 303 304 ndr_heap_t * 305 ndr_rpc_get_heap(mlsvc_handle_t *handle) 306 { 307 ndr_client_t *clnt = handle->clnt; 308 309 if (clnt->heap == NULL) 310 clnt->heap = ndr_heap_create(); 311 312 return (clnt->heap); 313 } 314 315 /* 316 * Must be called by RPC clients to free the heap after a successful RPC 317 * call, i.e. ndr_rpc_call returned 0. The caller should take a copy 318 * of any data returned by the RPC prior to calling this function because 319 * returned data is in the heap. 320 */ 321 void 322 ndr_rpc_release(mlsvc_handle_t *handle) 323 { 324 ndr_client_t *clnt = handle->clnt; 325 326 if (clnt->heap_preserved) 327 ndr_clnt_free_heap(clnt); 328 else 329 ndr_heap_destroy(clnt->heap); 330 331 clnt->heap = NULL; 332 } 333 334 /* 335 * Returns true if the handle is null. 336 * Otherwise returns false. 337 */ 338 boolean_t 339 ndr_is_null_handle(mlsvc_handle_t *handle) 340 { 341 static ndr_hdid_t zero_handle; 342 343 if (handle == NULL || handle->clnt == NULL) 344 return (B_TRUE); 345 346 if (!memcmp(&handle->handle, &zero_handle, sizeof (ndr_hdid_t))) 347 return (B_TRUE); 348 349 return (B_FALSE); 350 } 351 352 /* 353 * Returns true if the handle is the top level bind handle. 354 * Otherwise returns false. 355 */ 356 boolean_t 357 ndr_is_bind_handle(mlsvc_handle_t *handle) 358 { 359 return (handle->clnt->handle == &handle->handle); 360 } 361 362 /* 363 * Pass the client reference from parent to child. 364 */ 365 void 366 ndr_inherit_handle(mlsvc_handle_t *child, mlsvc_handle_t *parent) 367 { 368 child->clnt = parent->clnt; 369 child->remote_os = parent->remote_os; 370 } 371 372 void 373 ndr_rpc_status(mlsvc_handle_t *handle, int opnum, DWORD status) 374 { 375 ndr_service_t *svc; 376 char *name = "NDR RPC"; 377 char *s = "unknown"; 378 379 if (status == 0) 380 s = "success"; 381 else if (NT_SC_IS_ERROR(status)) 382 s = "error"; 383 else if (NT_SC_IS_WARNING(status)) 384 s = "warning"; 385 else if (NT_SC_IS_INFO(status)) 386 s = "info"; 387 388 if (handle) { 389 svc = handle->clnt->binding->service; 390 name = svc->name; 391 } 392 393 smb_tracef("%s[0x%02x]: %s: %s (0x%08x)", 394 name, opnum, s, xlate_nt_status(status), status); 395 } 396 397 /* 398 * The following functions provide the client callback interface. 399 * If the caller hasn't provided a heap, create one here. 400 */ 401 static int 402 ndr_xa_init(ndr_client_t *clnt, ndr_xa_t *mxa) 403 { 404 ndr_stream_t *recv_nds = &mxa->recv_nds; 405 ndr_stream_t *send_nds = &mxa->send_nds; 406 ndr_heap_t *heap = clnt->heap; 407 408 if (heap == NULL) { 409 if ((heap = ndr_heap_create()) == NULL) 410 return (-1); 411 412 clnt->heap = heap; 413 } 414 415 mxa->heap = heap; 416 417 nds_initialize(send_nds, 0, NDR_MODE_CALL_SEND, heap); 418 nds_initialize(recv_nds, NDR_PDU_SIZE_HINT_DEFAULT, 419 NDR_MODE_RETURN_RECV, heap); 420 return (0); 421 } 422 423 /* 424 * This is the entry pointy for an RPC client call exchange with 425 * a server, which will result in an smbrdr SmbTransact request. 426 * 427 * SmbTransact should return the number of bytes received, which 428 * we record as the PDU size, or a negative error code. 429 */ 430 static int 431 ndr_xa_exchange(ndr_client_t *clnt, ndr_xa_t *mxa) 432 { 433 ndr_stream_t *recv_nds = &mxa->recv_nds; 434 ndr_stream_t *send_nds = &mxa->send_nds; 435 int nbytes; 436 437 nbytes = smbrdr_transact(clnt->fid, 438 (char *)send_nds->pdu_base_offset, send_nds->pdu_size, 439 (char *)recv_nds->pdu_base_offset, recv_nds->pdu_max_size); 440 441 if (nbytes < 0) { 442 recv_nds->pdu_size = 0; 443 return (-1); 444 } 445 446 recv_nds->pdu_size = nbytes; 447 return (nbytes); 448 } 449 450 /* 451 * This entry point will be invoked if the xa-exchange response contained 452 * only the first fragment of a multi-fragment response. The RPC client 453 * code will then make repeated xa-read requests to obtain the remaining 454 * fragments, which will result in smbrdr SmbReadX requests. 455 * 456 * SmbReadX should return the number of bytes received, in which case we 457 * expand the PDU size to include the received data, or a negative error 458 * code. 459 */ 460 static int 461 ndr_xa_read(ndr_client_t *clnt, ndr_xa_t *mxa) 462 { 463 ndr_stream_t *nds = &mxa->recv_nds; 464 int len; 465 int nbytes; 466 467 if ((len = (nds->pdu_max_size - nds->pdu_size)) < 0) 468 return (-1); 469 470 nbytes = smbrdr_readx(clnt->fid, 471 (char *)nds->pdu_base_offset + nds->pdu_size, len); 472 473 if (nbytes < 0) 474 return (-1); 475 476 nds->pdu_size += nbytes; 477 478 if (nds->pdu_size > nds->pdu_max_size) { 479 nds->pdu_size = nds->pdu_max_size; 480 return (-1); 481 } 482 483 return (nbytes); 484 } 485 486 /* 487 * Preserve the heap so that the client application has access to data 488 * returned from the server after an RPC call. 489 */ 490 static void 491 ndr_xa_preserve(ndr_client_t *clnt, ndr_xa_t *mxa) 492 { 493 assert(clnt->heap == mxa->heap); 494 495 clnt->heap_preserved = B_TRUE; 496 mxa->heap = NULL; 497 } 498 499 /* 500 * Dispose of the transaction streams. If the heap has not been 501 * preserved, we can destroy it here. 502 */ 503 static void 504 ndr_xa_destruct(ndr_client_t *clnt, ndr_xa_t *mxa) 505 { 506 nds_destruct(&mxa->recv_nds); 507 nds_destruct(&mxa->send_nds); 508 509 if (!clnt->heap_preserved) { 510 ndr_heap_destroy(mxa->heap); 511 mxa->heap = NULL; 512 clnt->heap = NULL; 513 } 514 } 515 516 /* 517 * Dispose of a preserved heap. 518 */ 519 static void 520 ndr_xa_release(ndr_client_t *clnt) 521 { 522 if (clnt->heap_preserved) { 523 ndr_heap_destroy(clnt->heap); 524 clnt->heap = NULL; 525 clnt->heap_preserved = B_FALSE; 526 } 527 } 528 529 /* 530 * Lookup platform, type and version information about a server. 531 * If the cache doesn't already contain the data, contact the server and 532 * cache the response before returning the server info to the caller. 533 */ 534 static int 535 ndr_svinfo_lookup(char *server, char *domain, srvsvc_server_info_t *svinfo) 536 { 537 ndr_svinfo_t *svi; 538 539 (void) mutex_lock(&ndr_svlist.svl_mtx); 540 assert(ndr_svlist.svl_init == B_TRUE); 541 542 svi = list_head(&ndr_svlist.svl_list); 543 while (svi != NULL) { 544 if (ndr_svinfo_expired(svi)) { 545 svi = list_head(&ndr_svlist.svl_list); 546 continue; 547 } 548 549 if (ndr_svinfo_match(server, domain, svi)) { 550 bcopy(&svi->svi_svinfo, svinfo, 551 sizeof (srvsvc_server_info_t)); 552 (void) mutex_unlock(&ndr_svlist.svl_mtx); 553 return (0); 554 } 555 556 svi = list_next(&ndr_svlist.svl_list, svi); 557 } 558 559 if ((svi = malloc(sizeof (ndr_svinfo_t))) == NULL) { 560 (void) mutex_unlock(&ndr_svlist.svl_mtx); 561 return (-1); 562 } 563 564 if (srvsvc_net_server_getinfo(server, domain, &svi->svi_svinfo) < 0) { 565 (void) mutex_unlock(&ndr_svlist.svl_mtx); 566 free(svi); 567 return (-1); 568 } 569 570 (void) time(&svi->svi_tcached); 571 (void) strlcpy(svi->svi_server, server, MAXNAMELEN); 572 (void) strlcpy(svi->svi_domain, domain, MAXNAMELEN); 573 list_insert_tail(&ndr_svlist.svl_list, svi); 574 bcopy(&svi->svi_svinfo, svinfo, sizeof (srvsvc_server_info_t)); 575 (void) mutex_unlock(&ndr_svlist.svl_mtx); 576 return (0); 577 } 578 579 static boolean_t 580 ndr_svinfo_match(const char *server, const char *domain, 581 const ndr_svinfo_t *svi) 582 { 583 if ((utf8_strcasecmp(server, svi->svi_server) == 0) && 584 (utf8_strcasecmp(domain, svi->svi_domain) == 0)) { 585 return (B_TRUE); 586 } 587 588 return (B_FALSE); 589 } 590 591 /* 592 * If the server info in the cache has expired, discard it and return true. 593 * Otherwise return false. 594 * 595 * This is a private function to support ndr_svinfo_lookup() that assumes 596 * the list mutex is held. 597 */ 598 static boolean_t 599 ndr_svinfo_expired(ndr_svinfo_t *svi) 600 { 601 time_t tnow; 602 603 (void) time(&tnow); 604 605 if (difftime(tnow, svi->svi_tcached) > NDR_SVINFO_TIMEOUT) { 606 list_remove(&ndr_svlist.svl_list, svi); 607 free(svi->svi_svinfo.sv_name); 608 free(svi->svi_svinfo.sv_comment); 609 free(svi); 610 return (B_TRUE); 611 } 612 613 return (B_FALSE); 614 } 615