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 /*
23  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
24  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
25  */
26 
27 /*
28  * Client NDR RPC interface.
29  */
30 
31 #include <sys/types.h>
32 #include <sys/errno.h>
33 #include <sys/fcntl.h>
34 #include <sys/tzfile.h>
35 #include <time.h>
36 #include <strings.h>
37 #include <assert.h>
38 #include <errno.h>
39 #include <thread.h>
40 #include <unistd.h>
41 #include <syslog.h>
42 #include <synch.h>
43 
44 #include <netsmb/smbfs_api.h>
45 #include <smbsrv/libsmb.h>
46 #include <smbsrv/libmlrpc.h>
47 #include <smbsrv/libmlsvc.h>
48 #include <smbsrv/ndl/srvsvc.ndl>
49 #include <libsmbrdr.h>
50 #include <mlsvc.h>
51 
52 /*
53  * Server info cache entry expiration in seconds.
54  */
55 #define	NDR_SVINFO_TIMEOUT	1800
56 
57 typedef struct ndr_svinfo {
58 	list_node_t		svi_lnd;
59 	time_t			svi_tcached;
60 	char			svi_server[MAXNAMELEN];
61 	char			svi_domain[MAXNAMELEN];
62 	srvsvc_server_info_t	svi_svinfo;
63 } ndr_svinfo_t;
64 
65 typedef struct ndr_svlist {
66 	list_t		svl_list;
67 	mutex_t		svl_mtx;
68 	boolean_t	svl_init;
69 } ndr_svlist_t;
70 
71 static ndr_svlist_t ndr_svlist;
72 
73 static int ndr_xa_init(ndr_client_t *, ndr_xa_t *);
74 static int ndr_xa_exchange(ndr_client_t *, ndr_xa_t *);
75 static int ndr_xa_read(ndr_client_t *, ndr_xa_t *);
76 static void ndr_xa_preserve(ndr_client_t *, ndr_xa_t *);
77 static void ndr_xa_destruct(ndr_client_t *, ndr_xa_t *);
78 static void ndr_xa_release(ndr_client_t *);
79 
80 static int ndr_svinfo_lookup(char *, char *, srvsvc_server_info_t *);
81 static boolean_t ndr_svinfo_match(const char *, const char *, const
82     ndr_svinfo_t *);
83 static boolean_t ndr_svinfo_expired(ndr_svinfo_t *);
84 
85 /*
86  * Initialize the RPC client interface: create the server info cache.
87  */
88 void
89 ndr_rpc_init(void)
90 {
91 	(void) mutex_lock(&ndr_svlist.svl_mtx);
92 
93 	if (!ndr_svlist.svl_init) {
94 		list_create(&ndr_svlist.svl_list, sizeof (ndr_svinfo_t),
95 		    offsetof(ndr_svinfo_t, svi_lnd));
96 		ndr_svlist.svl_init = B_TRUE;
97 	}
98 
99 	(void) mutex_unlock(&ndr_svlist.svl_mtx);
100 }
101 
102 /*
103  * Terminate the RPC client interface: flush and destroy the server info
104  * cache.
105  */
106 void
107 ndr_rpc_fini(void)
108 {
109 	ndr_svinfo_t *svi;
110 
111 	(void) mutex_lock(&ndr_svlist.svl_mtx);
112 
113 	if (ndr_svlist.svl_init) {
114 		while ((svi = list_head(&ndr_svlist.svl_list)) != NULL) {
115 			list_remove(&ndr_svlist.svl_list, svi);
116 			free(svi->svi_svinfo.sv_name);
117 			free(svi->svi_svinfo.sv_comment);
118 			free(svi);
119 		}
120 
121 		list_destroy(&ndr_svlist.svl_list);
122 		ndr_svlist.svl_init = B_FALSE;
123 	}
124 
125 	(void) mutex_unlock(&ndr_svlist.svl_mtx);
126 }
127 
128 /*
129  * This call must be made to initialize an RPC client structure and bind
130  * to the remote service before any RPCs can be exchanged with that service.
131  *
132  * The mlsvc_handle_t is a wrapper that is used to associate an RPC handle
133  * with the client context for an instance of the interface.  The handle
134  * is zeroed to ensure that it doesn't look like a valid handle -
135  * handle content is provided by the remove service.
136  *
137  * The client points to this top-level handle so that we know when to
138  * unbind and teardown the connection.  As each handle is initialized it
139  * will inherit a reference to the client context.
140  */
141 int
142 ndr_rpc_bind(mlsvc_handle_t *handle, char *server, char *domain,
143     char *username, const char *service)
144 {
145 	struct smb_ctx		*ctx = NULL;
146 	ndr_client_t		*clnt = NULL;
147 	ndr_service_t		*svc;
148 	srvsvc_server_info_t	svinfo;
149 	int			fd = -1;
150 	int			rc;
151 
152 	if (handle == NULL || server == NULL ||
153 	    domain == NULL || username == NULL)
154 		return (-1);
155 
156 	if ((svc = ndr_svc_lookup_name(service)) == NULL)
157 		return (-1);
158 
159 	/*
160 	 * Set the default based on the assumption that most
161 	 * servers will be Windows 2000 or later.
162 	 * Don't lookup the svinfo if this is a SRVSVC request
163 	 * because the SRVSVC is used to get the server info.
164 	 * None of the SRVSVC calls depend on the server info.
165 	 */
166 	bzero(&svinfo, sizeof (srvsvc_server_info_t));
167 	svinfo.sv_platform_id = SV_PLATFORM_ID_NT;
168 	svinfo.sv_version_major = 5;
169 	svinfo.sv_version_minor = 0;
170 	svinfo.sv_type = SV_TYPE_DEFAULT;
171 	svinfo.sv_os = NATIVE_OS_WIN2000;
172 
173 	if (strcasecmp(service, "SRVSVC") != 0)
174 		(void) ndr_svinfo_lookup(server, domain, &svinfo);
175 
176 	/*
177 	 * Setup smbfs library handle, authenticate, connect to
178 	 * the IPC$ share.  This will reuse an existing connection
179 	 * if the driver already has one for this combination of
180 	 * server, user, domain.
181 	 */
182 	if ((rc = smbrdr_ctx_new(&ctx, server, domain, username)) != 0) {
183 		syslog(LOG_ERR, "ndr_rpc_bind: "
184 		    "smbrdr_ctx_new(S=%s, D=%s, U=%s), err=%d",
185 		    server, domain, username, rc);
186 		goto errout;
187 	}
188 
189 	/*
190 	 * Open the named pipe.
191 	 */
192 	fd = smb_fh_open(ctx, svc->endpoint, O_RDWR);
193 	if (fd < 0) {
194 		syslog(LOG_DEBUG, "ndr_rpc_bind: "
195 		    "smb_fh_open, err=%d", errno);
196 		goto errout;
197 	}
198 
199 	/*
200 	 * Setup the RPC client handle.
201 	 */
202 	if ((clnt = malloc(sizeof (ndr_client_t))) == NULL)
203 		return (-1);
204 	bzero(clnt, sizeof (ndr_client_t));
205 
206 	clnt->handle = &handle->handle;
207 	clnt->xa_init = ndr_xa_init;
208 	clnt->xa_exchange = ndr_xa_exchange;
209 	clnt->xa_read = ndr_xa_read;
210 	clnt->xa_preserve = ndr_xa_preserve;
211 	clnt->xa_destruct = ndr_xa_destruct;
212 	clnt->xa_release = ndr_xa_release;
213 	clnt->xa_private = ctx;
214 	clnt->xa_fd = fd;
215 
216 	ndr_svc_binding_pool_init(&clnt->binding_list,
217 	    clnt->binding_pool, NDR_N_BINDING_POOL);
218 
219 	if ((clnt->heap = ndr_heap_create()) == NULL)
220 		goto errout;
221 
222 	/*
223 	 * Fill in the caller's handle.
224 	 */
225 	bzero(&handle->handle, sizeof (ndr_hdid_t));
226 	handle->clnt = clnt;
227 	bcopy(&svinfo, &handle->svinfo, sizeof (srvsvc_server_info_t));
228 
229 	/*
230 	 * Do the OtW RPC bind.
231 	 */
232 	rc = ndr_clnt_bind(clnt, service, &clnt->binding);
233 	if (NDR_DRC_IS_FAULT(rc)) {
234 		syslog(LOG_DEBUG, "ndr_rpc_bind: "
235 		    "ndr_clnt_bind, rc=0x%x", rc);
236 		goto errout;
237 	}
238 
239 	/* Success! */
240 	return (0);
241 
242 errout:
243 	handle->clnt = NULL;
244 	if (clnt != NULL) {
245 		ndr_heap_destroy(clnt->heap);
246 		free(clnt);
247 	}
248 	if (ctx != NULL) {
249 		if (fd != -1)
250 			(void) smb_fh_close(fd);
251 		smbrdr_ctx_free(ctx);
252 	}
253 
254 	return (-1);
255 }
256 
257 /*
258  * Unbind and close the pipe to an RPC service.
259  *
260  * If the heap has been preserved we need to go through an xa release.
261  * The heap is preserved during an RPC call because that's where data
262  * returned from the server is stored.
263  *
264  * Otherwise we destroy the heap directly.
265  */
266 void
267 ndr_rpc_unbind(mlsvc_handle_t *handle)
268 {
269 	ndr_client_t *clnt = handle->clnt;
270 	struct smb_ctx *ctx = clnt->xa_private;
271 
272 	if (clnt->heap_preserved)
273 		ndr_clnt_free_heap(clnt);
274 	else
275 		ndr_heap_destroy(clnt->heap);
276 
277 	(void) smb_fh_close(clnt->xa_fd);
278 	smbrdr_ctx_free(ctx);
279 	free(clnt);
280 	bzero(handle, sizeof (mlsvc_handle_t));
281 }
282 
283 /*
284  * Call the RPC function identified by opnum.  The remote service is
285  * identified by the handle, which should have been initialized by
286  * ndr_rpc_bind.
287  *
288  * If the RPC call is successful (returns 0), the caller must call
289  * ndr_rpc_release to release the heap.  Otherwise, we release the
290  * heap here.
291  */
292 int
293 ndr_rpc_call(mlsvc_handle_t *handle, int opnum, void *params)
294 {
295 	ndr_client_t *clnt = handle->clnt;
296 	int rc;
297 
298 	if (ndr_rpc_get_heap(handle) == NULL)
299 		return (-1);
300 
301 	rc = ndr_clnt_call(clnt->binding, opnum, params);
302 
303 	/*
304 	 * Always clear the nonull flag to ensure
305 	 * it is not applied to subsequent calls.
306 	 */
307 	clnt->nonull = B_FALSE;
308 
309 	if (NDR_DRC_IS_FAULT(rc)) {
310 		ndr_rpc_release(handle);
311 		return (-1);
312 	}
313 
314 	return (0);
315 }
316 
317 /*
318  * Outgoing strings should not be null terminated.
319  */
320 void
321 ndr_rpc_set_nonull(mlsvc_handle_t *handle)
322 {
323 	handle->clnt->nonull = B_TRUE;
324 }
325 
326 /*
327  * Return a reference to the server info.
328  */
329 const srvsvc_server_info_t *
330 ndr_rpc_server_info(mlsvc_handle_t *handle)
331 {
332 	return (&handle->svinfo);
333 }
334 
335 /*
336  * Return the RPC server OS level.
337  */
338 uint32_t
339 ndr_rpc_server_os(mlsvc_handle_t *handle)
340 {
341 	return (handle->svinfo.sv_os);
342 }
343 
344 /*
345  * Get the session key from a bound RPC client handle.
346  *
347  * The key returned is the 16-byte "user session key"
348  * established by the underlying authentication protocol
349  * (either Kerberos or NTLM).  This key is needed for
350  * SAM RPC calls such as SamrSetInformationUser, etc.
351  * See [MS-SAMR] sections: 2.2.3.3, 2.2.7.21, 2.2.7.25.
352  *
353  * Returns zero (success) or an errno.
354  */
355 int
356 ndr_rpc_get_ssnkey(mlsvc_handle_t *handle,
357 	unsigned char *ssn_key, size_t len)
358 {
359 	ndr_client_t *clnt = handle->clnt;
360 	int rc;
361 
362 	if (clnt == NULL)
363 		return (EINVAL);
364 
365 	rc = smb_fh_getssnkey(clnt->xa_fd, ssn_key, len);
366 	return (rc);
367 }
368 
369 void *
370 ndr_rpc_malloc(mlsvc_handle_t *handle, size_t size)
371 {
372 	ndr_heap_t *heap;
373 
374 	if ((heap = ndr_rpc_get_heap(handle)) == NULL)
375 		return (NULL);
376 
377 	return (ndr_heap_malloc(heap, size));
378 }
379 
380 ndr_heap_t *
381 ndr_rpc_get_heap(mlsvc_handle_t *handle)
382 {
383 	ndr_client_t *clnt = handle->clnt;
384 
385 	if (clnt->heap == NULL)
386 		clnt->heap = ndr_heap_create();
387 
388 	return (clnt->heap);
389 }
390 
391 /*
392  * Must be called by RPC clients to free the heap after a successful RPC
393  * call, i.e. ndr_rpc_call returned 0.  The caller should take a copy
394  * of any data returned by the RPC prior to calling this function because
395  * returned data is in the heap.
396  */
397 void
398 ndr_rpc_release(mlsvc_handle_t *handle)
399 {
400 	ndr_client_t *clnt = handle->clnt;
401 
402 	if (clnt->heap_preserved)
403 		ndr_clnt_free_heap(clnt);
404 	else
405 		ndr_heap_destroy(clnt->heap);
406 
407 	clnt->heap = NULL;
408 }
409 
410 /*
411  * Returns true if the handle is null.
412  * Otherwise returns false.
413  */
414 boolean_t
415 ndr_is_null_handle(mlsvc_handle_t *handle)
416 {
417 	static ndr_hdid_t zero_handle;
418 
419 	if (handle == NULL || handle->clnt == NULL)
420 		return (B_TRUE);
421 
422 	if (!memcmp(&handle->handle, &zero_handle, sizeof (ndr_hdid_t)))
423 		return (B_TRUE);
424 
425 	return (B_FALSE);
426 }
427 
428 /*
429  * Returns true if the handle is the top level bind handle.
430  * Otherwise returns false.
431  */
432 boolean_t
433 ndr_is_bind_handle(mlsvc_handle_t *handle)
434 {
435 	return (handle->clnt->handle == &handle->handle);
436 }
437 
438 /*
439  * Pass the client reference from parent to child.
440  */
441 void
442 ndr_inherit_handle(mlsvc_handle_t *child, mlsvc_handle_t *parent)
443 {
444 	child->clnt = parent->clnt;
445 	bcopy(&parent->svinfo, &child->svinfo, sizeof (srvsvc_server_info_t));
446 }
447 
448 void
449 ndr_rpc_status(mlsvc_handle_t *handle, int opnum, DWORD status)
450 {
451 	ndr_service_t *svc;
452 	char *name = "NDR RPC";
453 	char *s = "unknown";
454 
455 	switch (NT_SC_SEVERITY(status)) {
456 	case NT_STATUS_SEVERITY_SUCCESS:
457 		s = "success";
458 		break;
459 	case NT_STATUS_SEVERITY_INFORMATIONAL:
460 		s = "info";
461 		break;
462 	case NT_STATUS_SEVERITY_WARNING:
463 		s = "warning";
464 		break;
465 	case NT_STATUS_SEVERITY_ERROR:
466 		s = "error";
467 		break;
468 	}
469 
470 	if (handle) {
471 		svc = handle->clnt->binding->service;
472 		name = svc->name;
473 	}
474 
475 	smb_tracef("%s[0x%02x]: %s: %s (0x%08x)",
476 	    name, opnum, s, xlate_nt_status(status), status);
477 }
478 
479 /*
480  * The following functions provide the client callback interface.
481  * If the caller hasn't provided a heap, create one here.
482  */
483 static int
484 ndr_xa_init(ndr_client_t *clnt, ndr_xa_t *mxa)
485 {
486 	ndr_stream_t	*recv_nds = &mxa->recv_nds;
487 	ndr_stream_t	*send_nds = &mxa->send_nds;
488 	ndr_heap_t	*heap = clnt->heap;
489 	int		rc;
490 
491 	if (heap == NULL) {
492 		if ((heap = ndr_heap_create()) == NULL)
493 			return (-1);
494 
495 		clnt->heap = heap;
496 	}
497 
498 	mxa->heap = heap;
499 
500 	rc = nds_initialize(send_nds, 0, NDR_MODE_CALL_SEND, heap);
501 	if (rc == 0)
502 		rc = nds_initialize(recv_nds, NDR_PDU_SIZE_HINT_DEFAULT,
503 		    NDR_MODE_RETURN_RECV, heap);
504 
505 	if (rc != 0) {
506 		nds_destruct(&mxa->recv_nds);
507 		nds_destruct(&mxa->send_nds);
508 		ndr_heap_destroy(mxa->heap);
509 		mxa->heap = NULL;
510 		clnt->heap = NULL;
511 		return (-1);
512 	}
513 
514 	if (clnt->nonull)
515 		NDS_SETF(send_nds, NDS_F_NONULL);
516 
517 	return (0);
518 }
519 
520 /*
521  * This is the entry pointy for an RPC client call exchange with
522  * a server, which will result in an smbrdr SmbTransact request.
523  *
524  * SmbTransact should return the number of bytes received, which
525  * we record as the PDU size, or a negative error code.
526  */
527 static int
528 ndr_xa_exchange(ndr_client_t *clnt, ndr_xa_t *mxa)
529 {
530 	ndr_stream_t *recv_nds = &mxa->recv_nds;
531 	ndr_stream_t *send_nds = &mxa->send_nds;
532 	int err, more, nbytes;
533 
534 	nbytes = recv_nds->pdu_max_size;
535 	err = smb_fh_xactnp(clnt->xa_fd,
536 	    send_nds->pdu_size, (char *)send_nds->pdu_base_offset,
537 	    &nbytes, (char *)recv_nds->pdu_base_offset, &more);
538 	if (err) {
539 		recv_nds->pdu_size = 0;
540 		return (-1);
541 	}
542 
543 	recv_nds->pdu_size = nbytes;
544 	return (0);
545 }
546 
547 /*
548  * This entry point will be invoked if the xa-exchange response contained
549  * only the first fragment of a multi-fragment response.  The RPC client
550  * code will then make repeated xa-read requests to obtain the remaining
551  * fragments, which will result in smbrdr SmbReadX requests.
552  *
553  * SmbReadX should return the number of bytes received, in which case we
554  * expand the PDU size to include the received data, or a negative error
555  * code.
556  */
557 static int
558 ndr_xa_read(ndr_client_t *clnt, ndr_xa_t *mxa)
559 {
560 	ndr_stream_t *nds = &mxa->recv_nds;
561 	int len;
562 	int nbytes;
563 
564 	if ((len = (nds->pdu_max_size - nds->pdu_size)) < 0)
565 		return (-1);
566 
567 	nbytes = smb_fh_read(clnt->xa_fd, 0, len,
568 	    (char *)nds->pdu_base_offset + nds->pdu_size);
569 
570 	if (nbytes < 0)
571 		return (-1);
572 
573 	nds->pdu_size += nbytes;
574 
575 	if (nds->pdu_size > nds->pdu_max_size) {
576 		nds->pdu_size = nds->pdu_max_size;
577 		return (-1);
578 	}
579 
580 	return (nbytes);
581 }
582 
583 /*
584  * Preserve the heap so that the client application has access to data
585  * returned from the server after an RPC call.
586  */
587 static void
588 ndr_xa_preserve(ndr_client_t *clnt, ndr_xa_t *mxa)
589 {
590 	assert(clnt->heap == mxa->heap);
591 
592 	clnt->heap_preserved = B_TRUE;
593 	mxa->heap = NULL;
594 }
595 
596 /*
597  * Dispose of the transaction streams.  If the heap has not been
598  * preserved, we can destroy it here.
599  */
600 static void
601 ndr_xa_destruct(ndr_client_t *clnt, ndr_xa_t *mxa)
602 {
603 	nds_destruct(&mxa->recv_nds);
604 	nds_destruct(&mxa->send_nds);
605 
606 	if (!clnt->heap_preserved) {
607 		ndr_heap_destroy(mxa->heap);
608 		mxa->heap = NULL;
609 		clnt->heap = NULL;
610 	}
611 }
612 
613 /*
614  * Dispose of a preserved heap.
615  */
616 static void
617 ndr_xa_release(ndr_client_t *clnt)
618 {
619 	if (clnt->heap_preserved) {
620 		ndr_heap_destroy(clnt->heap);
621 		clnt->heap = NULL;
622 		clnt->heap_preserved = B_FALSE;
623 	}
624 }
625 
626 /*
627  * Lookup platform, type and version information about a server.
628  * If the cache doesn't already contain the data, contact the server and
629  * cache the response before returning the server info to the caller.
630  *
631  * We don't provide the name or comment for now, which avoids the need
632  * to deal with unnecessary memory management.
633  */
634 static int
635 ndr_svinfo_lookup(char *server, char *domain, srvsvc_server_info_t *svinfo)
636 {
637 	static boolean_t	timechecked = B_FALSE;
638 	ndr_svinfo_t *svi;
639 
640 	(void) mutex_lock(&ndr_svlist.svl_mtx);
641 	if (!ndr_svlist.svl_init)
642 		return (-1);
643 
644 	svi = list_head(&ndr_svlist.svl_list);
645 	while (svi != NULL) {
646 		if (ndr_svinfo_expired(svi)) {
647 			svi = list_head(&ndr_svlist.svl_list);
648 			continue;
649 		}
650 
651 		if (ndr_svinfo_match(server, domain, svi)) {
652 			bcopy(&svi->svi_svinfo, svinfo,
653 			    sizeof (srvsvc_server_info_t));
654 			svinfo->sv_name = NULL;
655 			svinfo->sv_comment = NULL;
656 			(void) mutex_unlock(&ndr_svlist.svl_mtx);
657 			return (0);
658 		}
659 
660 		svi = list_next(&ndr_svlist.svl_list, svi);
661 	}
662 
663 	if ((svi = malloc(sizeof (ndr_svinfo_t))) == NULL) {
664 		(void) mutex_unlock(&ndr_svlist.svl_mtx);
665 		return (-1);
666 	}
667 
668 	if (srvsvc_net_server_getinfo(server, domain, &svi->svi_svinfo) < 0) {
669 		(void) mutex_unlock(&ndr_svlist.svl_mtx);
670 		free(svi);
671 		return (-1);
672 	}
673 
674 	(void) time(&svi->svi_tcached);
675 	(void) strlcpy(svi->svi_server, server, MAXNAMELEN);
676 	(void) strlcpy(svi->svi_domain, domain, MAXNAMELEN);
677 	list_insert_tail(&ndr_svlist.svl_list, svi);
678 	bcopy(&svi->svi_svinfo, svinfo, sizeof (srvsvc_server_info_t));
679 	svinfo->sv_name = NULL;
680 	svinfo->sv_comment = NULL;
681 
682 	if (!timechecked) {
683 		timechecked = B_TRUE;
684 		ndr_srvsvc_timecheck(server, domain);
685 	}
686 
687 	(void) mutex_unlock(&ndr_svlist.svl_mtx);
688 	return (0);
689 }
690 
691 static boolean_t
692 ndr_svinfo_match(const char *server, const char *domain,
693     const ndr_svinfo_t *svi)
694 {
695 	if ((smb_strcasecmp(server, svi->svi_server, 0) == 0) &&
696 	    (smb_strcasecmp(domain, svi->svi_domain, 0) == 0)) {
697 		return (B_TRUE);
698 	}
699 
700 	return (B_FALSE);
701 }
702 
703 /*
704  * If the server info in the cache has expired, discard it and return true.
705  * Otherwise return false.
706  *
707  * This is a private function to support ndr_svinfo_lookup() that assumes
708  * the list mutex is held.
709  */
710 static boolean_t
711 ndr_svinfo_expired(ndr_svinfo_t *svi)
712 {
713 	time_t	tnow;
714 
715 	(void) time(&tnow);
716 
717 	if (difftime(tnow, svi->svi_tcached) > NDR_SVINFO_TIMEOUT) {
718 		list_remove(&ndr_svlist.svl_list, svi);
719 		free(svi->svi_svinfo.sv_name);
720 		free(svi->svi_svinfo.sv_comment);
721 		free(svi);
722 		return (B_TRUE);
723 	}
724 
725 	return (B_FALSE);
726 }
727 
728 /*
729  * Compare the time here with the remote time on the server
730  * and report clock skew.
731  */
732 void
733 ndr_srvsvc_timecheck(char *server, char *domain)
734 {
735 	char			hostname[MAXHOSTNAMELEN];
736 	struct timeval		dc_tv;
737 	struct tm		dc_tm;
738 	struct tm		*tm;
739 	time_t			tnow;
740 	time_t			tdiff;
741 	int			priority;
742 
743 	if (srvsvc_net_remote_tod(server, domain, &dc_tv, &dc_tm) < 0) {
744 		syslog(LOG_DEBUG, "srvsvc_net_remote_tod failed");
745 		return;
746 	}
747 
748 	tnow = time(NULL);
749 
750 	if (tnow > dc_tv.tv_sec)
751 		tdiff = (tnow - dc_tv.tv_sec) / SECSPERMIN;
752 	else
753 		tdiff = (dc_tv.tv_sec - tnow) / SECSPERMIN;
754 
755 	if (tdiff != 0) {
756 		(void) strlcpy(hostname, "localhost", MAXHOSTNAMELEN);
757 		(void) gethostname(hostname, MAXHOSTNAMELEN);
758 
759 		priority = (tdiff > 2) ? LOG_NOTICE : LOG_DEBUG;
760 		syslog(priority, "DC [%s] clock skew detected: %u minutes",
761 		    server, tdiff);
762 
763 		tm = gmtime(&dc_tv.tv_sec);
764 		syslog(priority, "%-8s  UTC: %s", server, asctime(tm));
765 		tm = gmtime(&tnow);
766 		syslog(priority, "%-8s  UTC: %s", hostname, asctime(tm));
767 	}
768 }
769