mlsvc_client.c revision 10717:fe0545fc3cdd
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
47typedef 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
55typedef struct ndr_svlist {
56	list_t		svl_list;
57	mutex_t		svl_mtx;
58	boolean_t	svl_init;
59} ndr_svlist_t;
60
61static ndr_svlist_t ndr_svlist;
62
63static int ndr_xa_init(ndr_client_t *, ndr_xa_t *);
64static int ndr_xa_exchange(ndr_client_t *, ndr_xa_t *);
65static int ndr_xa_read(ndr_client_t *, ndr_xa_t *);
66static void ndr_xa_preserve(ndr_client_t *, ndr_xa_t *);
67static void ndr_xa_destruct(ndr_client_t *, ndr_xa_t *);
68static void ndr_xa_release(ndr_client_t *);
69
70static int ndr_svinfo_lookup(char *, char *, srvsvc_server_info_t *);
71static boolean_t ndr_svinfo_match(const char *, const char *, const
72    ndr_svinfo_t *);
73static boolean_t ndr_svinfo_expired(ndr_svinfo_t *);
74
75/*
76 * Initialize the RPC client interface: create the server info cache.
77 */
78void
79ndr_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 */
96void
97ndr_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 */
131int
132ndr_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 */
216void
217ndr_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 */
240int
241ndr_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 */
262uint32_t
263ndr_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 */
279int
280ndr_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
293void *
294ndr_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
304ndr_heap_t *
305ndr_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 */
321void
322ndr_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 */
338boolean_t
339ndr_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 */
356boolean_t
357ndr_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 */
365void
366ndr_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
372void
373ndr_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 */
401static int
402ndr_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 */
430static int
431ndr_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 */
460static int
461ndr_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 */
490static void
491ndr_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 */
503static void
504ndr_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 */
519static void
520ndr_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 */
534static int
535ndr_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
579static boolean_t
580ndr_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 */
598static boolean_t
599ndr_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