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