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