/* Copyright (c) 2008-2016 Natanael Copa * * Licencend under GPL-2 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "ntp.h" #include "util.h" static int allow_backward = 1; static int background = 0; static int dryrun = 0; static int interval = 0; static int verbosity = 0; static const char* ntphost; static const char *usage_str = "usage: sntpc [-bdhnvV] [-i INTERVAL] NTPHOST\n" "options:\n" " -b Do not allow time jump backwards\n" " -d Fork to background (daemonize)\n" " -h Show this help\n" " -n Do not set time, just show the offset and delay\n" " -i Poll every INTERVAL seconds\n" " -v Verbose logging\n" " -V Show version and exit\n" "\n"; int init_conn(const char *host, const char *service) { struct addrinfo hints; struct addrinfo *res, *res0; int fd, error; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; error = getaddrinfo(host, service, &hints, &res0); if (error != 0) { log_error("getaddrinfo: %s", gai_strerror(error)); return -1; } for (res = res0; res != NULL; res = res->ai_next) { fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd == -1) continue; if (connect(fd, res->ai_addr, res->ai_addrlen) != -1) break; log_perror("connect"); close(fd); } freeaddrinfo(res0); if (res == NULL) { return -1; } return fd; } long int get_random(void) { long int r; int fd = open("/dev/urandom", O_RDONLY); if (fd == -1) open("/dev/random", O_RDONLY); if (fd == -1) return random(); /* not very secure... */ read(fd, &r, sizeof(r)); close(fd); return r; } void ntp_msg_init_query(struct ntp_msg *msg) { memset(msg, 0, sizeof(struct ntp_msg)); msg->status = MODE_CLIENT | (NTP_VERSION << 3); /* we dont really want to tell what our time is */ msg->xmttime.int_partl = get_random(); msg->xmttime.fractionl = get_random(); } int ntp_msg_send(int fd, struct ntp_msg *msg) { int ret = send(fd, msg, sizeof(struct ntp_msg), 0); if (ret == -1) log_perror("send"); return ret; } int ntp_msg_wait(int fd, int timeout) { struct pollfd fds; int ret; fds.fd = fd; fds.events = POLLIN; ret = poll(&fds, 1, timeout); if (ret < 0) log_perror("poll"); return ret; } int ntp_msg_recv(int fd, struct ntp_msg *msg) { char buf[NTP_MSGSIZE]; ssize_t size = recv(fd, &buf, sizeof(buf), 0); if (size < 0) { log_perror("recv"); return -1; } if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { log_warning("malformated packet recieved"); return -1; } memcpy(msg, buf, sizeof(*msg)); return 0; } int ntp_msg_query(int fd, struct ntp_msg *response, double *T1, double *T4) { int retry = 3; static struct ntp_msg query; ntp_msg_init_query(&query); while (retry--) { *T1 = gettime(); if (ntp_msg_send(fd, &query) < 0) return -1; if (ntp_msg_wait(fd, 3000) <= 0) continue; *T4 = gettime(); if (ntp_msg_recv(fd, response) == 0) break; } if (retry == 0) return -1; if (query.xmttime.int_partl != response->orgtime.int_partl || query.xmttime.fractionl != response->orgtime.fractionl) return -1; return 0; } #if DEBUG void ntp_msg_dump(struct ntp_msg *msg) { printf("status: %x\n",msg->status); printf("stratum: %u\n", msg->stratum); printf("poll: %u\n", msg->ppoll); } #endif int settime(double offset) { struct timeval o, n; if (gettimeofday(&n, NULL) < 0) { log_perror("gettimeofday"); return -1; } d_to_tv(offset, &o); n.tv_usec += o.tv_usec + 1000000; n.tv_sec += o.tv_sec - 1 + (n.tv_usec / 1000000); n.tv_usec %= 1000000; if (settimeofday(&n, NULL) < 0) { log_perror("settimeofday"); return -1; } return 0; } int sync_time(const char *host) { static struct ntp_msg response; int ret; int fd; double T1, T2, T3, T4, delay, offset; fd = init_conn(host, "ntp"); if (fd < 0) return -1; ret = ntp_msg_query(fd, &response, &T1, &T4); close(fd); if (ret < 0) return -1; #if DEBUG // ntp_msg_dump(&response); #endif T2 = lfp_to_d(response.rectime); T3 = lfp_to_d(response.xmttime); offset = ((T2 - T1) + (T3 - T4)) / 2; delay = (T4 - T1) - (T3 - T2); log_debug("offset=%f, delay=%f", offset, delay); if (dryrun || (offset < 0 && !allow_backward)) return 0; return settime(offset); } void daemonize(void) { int pid = fork(); if (pid < 0) fatal("fork"); if (pid > 0) exit(0); freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); } int main(int argc, char * const argv[]) { int c; while ((c = getopt(argc, argv, "bdhi:nVv")) > 0) { switch (c) { case 'b': allow_backward = 0; break; case 'd': background++; break; case 'h': printf("%s", usage_str); return(0); case 'i': interval = atoi(optarg); break; case 'n': dryrun++; verbosity++; break; case 'V': puts("sntpc " SNTPC_VERSION); return(0); case 'v': verbosity++; break; } } if (optind != (argc - 1)) { printf("%s", usage_str); return 1; } ntphost = argv[optind]; log_init(verbosity); /* set time once before daemonize. This will make sure that we lock til we have set the time correctly. */ c = sync_time(ntphost); if (background) daemonize(); while (interval) { sleep(interval); sync_time(ntphost); } return c; }