/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This module contains the private function __rpc_get_time_offset() * which will return the difference in seconds between the local system's * notion of time and a remote server's notion of time. This must be * possible without calling any functions that may invoke the name * service. (netdir_getbyxxx, getXbyY, etc). The function is used in the * synchronize call of the authdes code to synchronize clocks between * NIS+ clients and their servers. * * Note to minimize the amount of duplicate code, portions of the * synchronize() function were folded into this code, and the synchronize * call becomes simply a wrapper around this function. Further, if this * function is called with a timehost it *DOES* recurse to the name * server so don't use it in that mode if you are doing name service code. * * Side effects : * When called a client handle to a RPCBIND process is created * and destroyed. Two strings "netid" and "uaddr" are malloc'd * and returned. The SIGALRM processing is modified only if * needed to deal with TCP connections. */ #include "mt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef NIS #include extern void __nis_netconfig2ep(struct netconfig *, endpoint *); extern bool_t __nis_netconfig_matches_ep(struct netconfig *, endpoint *); #ifdef TESTING #define msg(x) printf("ERROR: %s\n", x) /* #define msg(x) syslog(LOG_ERR, "%s", x) */ #else #define msg(x) #endif static int saw_alarm = 0; /* ARGSUSED */ static void alarm_hndler(int s) { saw_alarm = 1; } /* * The internet time server defines the epoch to be Jan 1, 1900 * whereas UNIX defines it to be Jan 1, 1970. To adjust the result * from internet time-service time, into UNIX time we subtract the * following offset : */ #define NYEARS (1970 - 1900) #define TOFFSET ((uint_t)60*60*24*(365*NYEARS + (NYEARS/4))) /* * free_eps() * * Free the strings that were strduped into the eps structure. */ static void free_eps(endpoint eps[], int num) { int i; for (i = 0; i < num; i++) { free(eps[i].uaddr); free(eps[i].proto); free(eps[i].family); } } /* * get_server() * * This function constructs a nis_server structure description for the * indicated hostname. */ static nis_server * get_server(char *host, nis_server *srv, endpoint eps[], int maxep) { int num_ep = 0, i; struct netconfig *nc; void *nch; struct nd_hostserv hs; struct nd_addrlist *addrs; if (! host) return (NULL); hs.h_host = host; hs.h_serv = "rpcbind"; nch = setnetconfig(); while (nc = getnetconfig(nch)) { if ((nc->nc_flag & NC_VISIBLE) == 0) continue; if (! netdir_getbyname(nc, &hs, &addrs)) { for (i = 0; (i < (addrs->n_cnt)) && (num_ep < maxep); i++, num_ep++) { eps[num_ep].uaddr = taddr2uaddr(nc, &(addrs->n_addrs[i])); __nis_netconfig2ep(nc, &(eps[num_ep])); } netdir_free((char *)addrs, ND_ADDRLIST); } } (void) endnetconfig(nch); srv->name = (nis_name) host; srv->ep.ep_len = num_ep; srv->ep.ep_val = eps; srv->key_type = NIS_PK_NONE; srv->pkey.n_bytes = NULL; srv->pkey.n_len = 0; return (srv); } #define MEP(ep, prot) (strcasecmp(ep.proto, prot) == 0) #define MAX_ENDPOINTS 32 /* * __rpc_get_time_offset() * * This function uses a nis_server structure to contact the a remote * machine (as named in that structure) and returns the offset in time * between that machine and this one. This offset is returned in seconds * and may be positive or negative. * * The first time through, a lot of fiddling is done with the netconfig * stuff to find a suitable transport. The function is very aggressive * about choosing UDP or at worst TCP if it can. This is because * those transports support both the RCPBIND call and the internet * time service. * * Once through, *uaddr is set to the universal address of * the machine and *netid is set to the local netid for the transport * that uaddr goes with. On the second call, the netconfig stuff * is skipped and the uaddr/netid pair are used to fetch the netconfig * structure and to then contact the machine for the time. * * td = "server" - "client" */ int __rpc_get_time_offset(struct timeval *td, nis_server *srv, char *thost, char **uaddr, char **netid) { CLIENT *clnt; /* Client handle */ struct netbuf *addr = 0; /* address */ void *nc_handle; /* Netconfig "state" */ struct netconfig *nc; /* Various handles */ endpoint *ep; /* useful endpoints */ char *useua = NULL, /* uaddr of selected xp */ *useid = NULL; /* netid of selected xp */ int epl, i; /* counters */ enum clnt_stat status; /* result of clnt_call */ uint_t thetime; ulong_t delta; int needfree = 0; struct timeval tv; int rtime_fd = -1, time_valid, flag = 0; int a1, a2, a3, a4; char ut[INET6_ADDRSTRLEN]; char ipuaddr[INET6_ADDRSTRLEN]; endpoint teps[MAX_ENDPOINTS], *epcand[MAX_ENDPOINTS], *nonipcand[MAX_ENDPOINTS], supplied; uint32_t epc, nonip; nis_server tsrv; void (*oldsig)() = NULL; /* old alarm handler */ char *dot = NULL; /* tmp pointer */ nc = NULL; td->tv_sec = 0; td->tv_usec = 0; /* * First check to see if we need to find and address for this * server. */ if (*uaddr == NULL) { if ((srv != NULL) && (thost != NULL)) { msg("both timehost and srv pointer used!"); return (0); } if (! srv) { srv = get_server(thost, &tsrv, teps, 32); if (! srv) { msg("unable to contruct server data."); return (0); } needfree = 1; /* need to free data in endpoints */ } nc_handle = (void *) setnetconfig(); if (! nc_handle) { msg("unable to get netconfig info."); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } ep = srv->ep.ep_val; epl = srv->ep.ep_len; for (i = 0; i < sizeof (epcand)/sizeof (epcand[0]); i++) { epcand[i] = 0; nonipcand[i] = 0; } epc = 0; nonip = 0; /* * Build the list of endpoint candidates. We prefer transports * that we know are IP, but let /etc/netconfig determine the * ordering among the IP transports. * * Note: We assume that the endpoint 'proto' field contains * the netid of the transport. */ while ((nc = getnetconfig(nc_handle)) != NULL) { /* Is it a visible transport ? */ if ((nc->nc_flag & NC_VISIBLE) == 0) continue; /* Check against the end points */ for (i = 0; i < epl; i++) { if (__nis_netconfig_matches_ep(nc, &(ep[i]))) { if (MEP(ep[i], "udp") || MEP(ep[i], "udp6") || MEP(ep[i], "tcp") || MEP(ep[i], "tcp6")) { epcand[epc++] = &(ep[i]); } else { nonipcand[nonip++] = &ep[i]; } break; } } } (void) endnetconfig(nc_handle); /* * epcand[] now contains the candidate transports. If there * were non-IP transports as well, add them to the end of the * candidate list. */ for (i = 0; i < nonip; i++) { epcand[epc++] = nonipcand[i]; } if (epc == 0) { msg("no acceptable transport endpoints."); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } } else { /* Caller supplied a uaddr. Fake an endpoint. */ if (*netid != 0) { supplied.proto = *netid; /* Is it one of the known IP transports ? */ if (strcmp("udp", supplied.proto) && strcmp("udp6", supplied.proto) && strcmp("tcp", supplied.proto) && strcmp("tcp6", supplied.proto)) { /* No, it's not */ nonip = 1; } else { nonip = 0; } } else { supplied.proto = (strchr(*uaddr, ':') != 0) ? "udp6" : "udp"; nonip = 0; } supplied.uaddr = *uaddr; supplied.family = (strchr(*uaddr, ':') != 0) ? "inet6" : "inet"; epcand[0] = &supplied; epc = 1; nonip = 0; } nc = 0; clnt = 0; status = RPC_FAILED; /* Anything except RPC_SUCCESS */ /* * Loop over the endpoint candidates. Defer error reporting (except * for the netconfig entry) until we've looked at all candidates. */ for (i = 0; i < epc; i++) { if (nc != 0) freenetconfigent(nc); nc = getnetconfigent(epcand[i]->proto); if (nc == 0) { msg("unable to locate netconfig info for netid."); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } /* * Add the appropriate port number to the uaddr */ useua = epcand[i]->uaddr; useid = epcand[i]->proto; if (strcasecmp(nc->nc_protofmly, NC_INET) == 0) { (void) sscanf(useua, "%d.%d.%d.%d.", &a1, &a2, &a3, &a4); (void) sprintf(ipuaddr, "%d.%d.%d.%d.0.111", a1, a2, a3, a4); useua = &ipuaddr[0]; } else if (strcasecmp(nc->nc_protofmly, NC_INET6) == 0) { size_t len; char *port = ".0.111"; if (strlen(useua) >= sizeof (ipuaddr)) { freenetconfigent(nc); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } (void) strcpy(ipuaddr, useua); /* get the IPv6 address out of the uaddr */ if ((dot = strrchr(ipuaddr, '.')) != 0) { *dot = '\0'; if ((dot = strrchr(ipuaddr, '.')) != 0) *dot = '\0'; } if (dot == 0 || (len = strlen(ipuaddr))+strlen(port) >= sizeof (ipuaddr)) { freenetconfigent(nc); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } /* now put in 0.111 */ (void) strcat(ipuaddr + len, port); useua = ipuaddr; } /* * Create the client handle to rpcbind. Note we always try * version 3 since that is the earliest version that supports * the RPCB_GETTIME call. Also it is the version that comes * standard with SVR4. Since most everyone supports TCP/IP * we could consider trying the rtime call first. */ if (clnt != 0) clnt_destroy(clnt); clnt = __nis_clnt_create(RPC_ANYFD, nc, useua, 0, 0, RPCBPROG, RPCBVERS, 0, 0); if (! clnt) continue; tv.tv_sec = 5; tv.tv_usec = 0; time_valid = 0; status = clnt_call(clnt, RPCBPROC_GETTIME, xdr_void, NULL, xdr_u_int, (char *)&thetime, tv); /* * The only error we check for is anything but success. In * fact we could have seen PROGMISMATCH if talking to a 4.1 * machine (pmap v2) or TIMEDOUT if the net was busy. */ if (status == RPC_SUCCESS) break; } if (status == RPC_SUCCESS) { time_valid = 1; } else if (clnt == 0) { msg("unable to create client handle to rpcbind."); freenetconfigent(nc); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } else { /* * Try the timeservice port. This presumably only exists * for IP transports, so we ignore the non-IP ones. */ for (i = 0; i < epc-nonip; i++) { /* * Convert PMAP address into timeservice address * We take advantage of the fact that we "know" what * a universal address looks like for inet transports. * * We also know that the internet timeservice is always * listening on port 37. */ if (nc != 0) freenetconfigent(nc); nc = getnetconfigent(epcand[i]->proto); if (nc == 0) { msg("no netconfig info for netid."); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } useua = epcand[i]->uaddr; useid = epcand[i]->proto; if (strcasecmp(nc->nc_protofmly, NC_INET) == 0) { (void) sscanf(useua, "%d.%d.%d.%d.", &a1, &a2, &a3, &a4); (void) sprintf(ut, "%d.%d.%d.%d.0.37", a1, a2, a3, a4); } else if (strcasecmp(nc->nc_protofmly, NC_INET6) == 0) { size_t len; char *port = ".0.37"; if (strlen(useua) >= sizeof (ut)) { goto error; } (void) strcpy(ut, useua); /* get the IPv6 address out of the uaddr */ if ((dot = strrchr(ut, '.')) != 0) { *dot = '\0'; if ((dot = strrchr(ut, '.')) != 0) *dot = '\0'; } if (dot == 0) { goto error; } if ((len = strlen(ut))+strlen(port) >= sizeof (ut)) { goto error; } (void) strcat(ut + len, port); } addr = uaddr2taddr(nc, ut); if (! addr) { msg("timeservice uaddr to taddr failed."); goto error; } rtime_fd = t_open(nc->nc_device, O_RDWR, NULL); if (rtime_fd == -1) { msg("unable to open fd to network."); goto error; } if (t_bind(rtime_fd, NULL, NULL) < 0) { msg("unable to bind an endpoint to fd."); goto error; } /* * Now depending on whether or not we're talking to * UDP we set a timeout or not. */ if (nc->nc_semantics == NC_TPI_CLTS) { struct t_unitdata tu_data; struct pollfd pfd; int res; tu_data.addr = *addr; tu_data.udata.buf = (char *)&thetime; tu_data.udata.len = (uint_t)sizeof (thetime); tu_data.udata.maxlen = tu_data.udata.len; tu_data.opt.len = 0; tu_data.opt.maxlen = 0; if (t_sndudata(rtime_fd, &tu_data) == -1) { msg("udp : t_sndudata failed."); goto error; } pfd.fd = rtime_fd; pfd.events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND; do { res = poll(&pfd, 1, 10000); } while (res < 0); if ((res <= 0) || (pfd.revents & POLLNVAL)) goto error; if (t_rcvudata(rtime_fd, &tu_data, &flag) < 0) { msg("t_rvcdata failed on udp trpt."); goto error; } time_valid = 1; } else { struct t_call sndcall; sndcall.addr = *addr; sndcall.opt.len = sndcall.opt.maxlen = 0; sndcall.udata.len = sndcall.udata.maxlen = 0; oldsig = (void (*)())signal(SIGALRM, alarm_hndler); saw_alarm = 0; /* global tracking the alarm */ (void) alarm(20); /* only wait 20 seconds */ if (t_connect(rtime_fd, &sndcall, NULL) == -1) { msg("connect tcp endpoint failedd."); goto error; } if (saw_alarm) { msg("alarm caught it; unreachable."); goto error; } if (t_rcv(rtime_fd, (char *)&thetime, (uint_t)sizeof (thetime), &flag) != (uint_t)sizeof (thetime)) { if (saw_alarm) { /*EMPTY*/ msg("timed out TCP call."); } else { /*EMPTY*/ msg("wrong size results"); } goto error; } time_valid = 1; } if (time_valid) { thetime = ntohl(thetime); /* adjust to UNIX time */ thetime = thetime - TOFFSET; } else thetime = 0; } } error: /* * clean up our allocated data structures. */ if (addr) netdir_free((char *)(addr), ND_ADDR); if (rtime_fd != -1) (void) t_close(rtime_fd); if (clnt) clnt_destroy(clnt); if (nc) freenetconfigent(nc); if (oldsig) { (void) alarm(0); /* reset that alarm if its outstanding */ (void) signal(SIGALRM, oldsig); } /* * note, don't free uaddr strings until after we've made a * copy of them. */ if (time_valid) { if (! *netid) { *netid = strdup(useid); if (! *netid) { msg("__rpc_get_time_offset: strdup failed."); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } *uaddr = strdup(useua); if (! *uaddr) { msg("__rpc_get_time_offset: strdup failed."); if (*netid) free(*netid); if (needfree) free_eps(teps, tsrv.ep.ep_len); return (0); } } (void) gettimeofday(&tv, 0); /* Round to the nearest second */ tv.tv_sec += (tv.tv_sec > 500000) ? 1 : 0; delta = (thetime > tv.tv_sec) ? thetime - tv.tv_sec : tv.tv_sec - thetime; td->tv_sec = (thetime < tv.tv_sec) ? - delta : delta; td->tv_usec = 0; } else { /*EMPTY*/ msg("unable to get the server's time."); } if (needfree) free_eps(teps, tsrv.ep.ep_len); return (time_valid); }