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