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