xref: /illumos-gate/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_client.c (revision d8a7fe16f62711cdc5c4267da8b34ff24a6b668c)
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