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
ndr_rpc_init(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
ndr_rpc_fini(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
ndr_rpc_bind(mlsvc_handle_t * handle,char * server,char * domain,char * username,const char * service)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
ndr_rpc_unbind(mlsvc_handle_t * handle)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
ndr_rpc_call(mlsvc_handle_t * handle,int opnum,void * params)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
ndr_rpc_set_nonull(mlsvc_handle_t * handle)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 *
ndr_rpc_server_info(mlsvc_handle_t * handle)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
ndr_rpc_server_os(mlsvc_handle_t * handle)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
ndr_rpc_get_ssnkey(mlsvc_handle_t * handle,unsigned char * ssn_key,size_t len)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 *
ndr_rpc_malloc(mlsvc_handle_t * handle,size_t size)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 *
ndr_rpc_get_heap(mlsvc_handle_t * handle)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
ndr_rpc_release(mlsvc_handle_t * handle)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
ndr_is_null_handle(mlsvc_handle_t * handle)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
ndr_is_bind_handle(mlsvc_handle_t * handle)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
ndr_inherit_handle(mlsvc_handle_t * child,mlsvc_handle_t * parent)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
ndr_rpc_status(mlsvc_handle_t * handle,int opnum,DWORD status)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
ndr_xa_init(ndr_client_t * clnt,ndr_xa_t * mxa)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
ndr_xa_exchange(ndr_client_t * clnt,ndr_xa_t * mxa)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
ndr_xa_read(ndr_client_t * clnt,ndr_xa_t * mxa)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
ndr_xa_preserve(ndr_client_t * clnt,ndr_xa_t * mxa)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
ndr_xa_destruct(ndr_client_t * clnt,ndr_xa_t * mxa)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
ndr_xa_release(ndr_client_t * clnt)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
ndr_svinfo_lookup(char * server,char * domain,srvsvc_server_info_t * svinfo)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
ndr_svinfo_match(const char * server,const char * domain,const ndr_svinfo_t * svi)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
ndr_svinfo_expired(ndr_svinfo_t * svi)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
ndr_srvsvc_timecheck(char * server,char * domain)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