pax_global_header00006660000000000000000000000064143635267110014522gustar00rootroot0000000000000052 comment=1e72205ade000384e212e67df0ebe0386fd90001 htpdate-1.3.7/000077500000000000000000000000001436352671100131635ustar00rootroot00000000000000htpdate-1.3.7/.github/000077500000000000000000000000001436352671100145235ustar00rootroot00000000000000htpdate-1.3.7/.github/workflows/000077500000000000000000000000001436352671100165605ustar00rootroot00000000000000htpdate-1.3.7/.github/workflows/main.yml000066400000000000000000000004041436352671100202250ustar00rootroot00000000000000name: main on: push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: build run: make - name: build with https run: make https - name: test run: ./htpdate www.example.com htpdate-1.3.7/Changelog000066400000000000000000000222411436352671100147760ustar00rootroot00000000000000Changes in 1.3.7 ---------------- - bugfix: wrong default portnumber for proxy was used - bugfix: https://bugs.launchpad.net/ubuntu/+source/htpdate/+bug/1850740 - improvement: Avoid bouncing between upper/lower limit when (almost) in sync - improvement: Set SSL server hostname on SSL object 1.3.6 ----- - Fix when correcting large time offsets (bug introduced in 1.3.5) 1.3.5 ----- - Feature: set kernel synchronized (e.g. timedatectl will report 'System clock synchronized: yes') - Improved time tuning accuracy/stability 1.3.4 ----- - fix small memory leak in strdup - fix free in case of DNS lookup failure - other minor updates 1.3.3 ----- - fix compatibility issue with uClibc - fix for time offset > 1s - additional time offset fine tuning 1.3.2 ----- - fine tuning time adjustment - added missing '-f' from usage - fix ntp_adjtime/adjtimex 1.3.1 ----- - Read/write clock driftfile (-f) has been added. - Multiple small bug fixes 1.3.0 ----- - This release is the biggest update in a decade - accuracy has improved with a new algorithm (bisect) - usage of '-p' option has change, '-p 9' allows accuracy in the range of ~10 ms - removed '-b' option, burst mode - HTTPS support has been added (no certificate validation!) 1.2.4 ----- - Several updates/fixes from fork maintained by Angelo Compagnucci - https://github.com/angeloc/htpdate - Added foreground '-F' (thanks to larsluthman) - An incorrect drop privileges fix - Support for multiple verbose/debug levels with -dd and -ddd - Use http_proxy environment variable - Added '-n' to ignore this environment variable - Replaced deprecated asctime(), gettimeofday(), timegm() - Removed the use of mktime() - Removed the vim modeline formatting 1.2.3 ----- - Support URL path to be included, hostname/path, e.g. htpdate www.example.com/htpdate.html - Included several fixes as used in Chromium OS (thanks to Mike Frysinger) - Release on github - Bug fix: dropping privileges, when not specified 1.2.2 ----- - Doc change only (removed reference to FreeBSD and Linux) 1.2.1 ----- - Fix initialization of strptime() 1.2.0 ----- - Fix for port numbers that were not used in daemon mode (thanks to Matthew Jones) - Discard the use of local time offset, because it causes incorrect synchronisation when TZDATA isn't correct, https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=876687 (thanks for Mert Dirik) 1.1.3 ----- - Fixed a year 2038 issue on 32-bit systems (thanks to Ansel Consulting) 1.1.2 ----- - Security fix: setegid wasn't always applied - Fixed compiler warning 'ignoring return value' for recent gcc versions 1.1.1 ----- - Fixed out of bound issue and a missing null-terminated string (thanks to Tobias Stöckmann) 1.1.0 ----- - Removed Debian and RPM specific information - Fixed manpage issues (thanks to Eriberto) 1.0.7 ----- - Fixed an incorrect bug fix in 1.0.6. 1.0.6 ----- - Bug fix for https://bugs.gentoo.org/show_bug.cgi?id=350685 (Thanks Diego Augusto Molina for the bug report and Pavel Kazakov for debugging) 1.0.5 ----- - Change of e-mail/website - Fix for debian build 1.0.4 ----- - Fixed a memory leak (reported and fixed by Andreas Bohne-Lang) 1.0.3 ----- - Fixed logic error... 1.0.2 is broken :( 1.0.2 ----- - Fixed a buffer overflow when time offset gets too large https://dev.openwrt.org/cgi-bin/trac.fcgi/ticket/3940 1.0.1 ----- - Added "burst mode" (-b) to enhance accuracy. - Extended debug output - Removed potential buffer overflows vulnerabilities. - Replaced usleep by nanosleep (which is more portable). - Included debian package. 1.0.0 ----- - Cleanup/simplified the code. - Again a more robust implementation of the "-p" (precision) switch. 0.9.3 ----- - Bug fixes: poll loop could be become close to zero in case of a "connection failed". Sleeptime wasn't correct in case all hosts fail. - Use more "sane" minsleep and maxsleep values. 0.9.2 ----- - The use of the ntp_adjtime system call is now optional. The clock frequency will be adjusted when using the "-x" switch and the systematic drift will be compensated. Works only in daemon mode. 0.9.1 ----- - Htpdate can drop root privileges and run as a restricted user. - Drift calculation starts now after first time correction. 0.9.0 ----- - Mostly code cleanup - Changed "char" variables into "int", which should be more efficient - Changed the qsort routine, into an insertion sort. Quicksort is kind of overkill for such small lists. - Debug mode is allowed in daemon mode. 0.8.8 ----- - Speeded up the poll cycle loop, once a time offset has been detected. - Added systematic drift to the syslog logging (daemon only). The "adjtimex_parameters.sh" script may help to reduce the drift of your system clock. - Changed "precision" from micro into milliseconds. - Changed manpage directory from /usr/man into /usr/share/man. - HTTP/1.1 has become the default 0.8.7 ----- - Bug fix: with precision set, the time could only be adjusted negative (slow down). - Fixed undesired effects in (rare) cases when using only a few servers. - Exit code changed to 1 if no server was found (requested by Supernaut). 0.8.6 ----- - Reintroduction of the "-p" switch. The "precision" determines more accurate when a time adjustment is needed. The implementation of precision is much beter than the one in the past. 0.8.5 ----- - Changed the variable type of "param" from char into int (thanks to Arnaud Mazin). GCC 3.4.x is less forgiving than older versions. - Made compare function for qsort more robust (avoiding overflow). 0.8.4 ----- - Finaly added IPv6 support! - If you would lose internet connectivity, htpdate would flood the system (CPU and log). A patch submitted by Peter Surda, has been included. - Removed -0 switch (HTTP/1.0), since this is the default anyway. 0.8.3 ----- - Fixed that "when" wasn't properly initialized in every poll cycle. This bug fix reduces jitter when system time is close to the correct time. - Added missing "-t" switch to the manpage. - Added warning message if more than 16 servers are specified. - Htpdate double forks now, like a proper daemon :) - vim setting are added to the source (set ts=4) 0.8.2 ----- - Fixed a major bug in offset calculation (caused by changing the rtt type from double to unsigned long) 0.8.1 (broken) -------------- - Correction install paths in Makefile, added uninstall option - Added "-t" switch, which disables time sanity checking (requested for devices, like supported by openwrt, which boot/startup at "epoch" time) 0.8.0 ----- - Lots of code clean up, eg.: - compiles with the -ansi switch (for better portability, but only tested on Linux and FreeBSD) - Compiles with Tiny C Compiler (tcc) and Intel C compiler (icc) - Restored compatibility with FreeBSD (timezone calculation). - A second poll is made if a time offset has been detected. - Removed the switches "-t" and "-x". In daemon mode htpdate will only adjust time. With the "-s" switch htpdate will set the time once at startup and after that only smooth adjusts are made. - Added "-l" switch, which enables logging to syslog in non-daemon mode. Convinient if htpdate is used from cron. - Added "-0" to make an HTTP/1.0 request and "-1" for HTTP/1.1 0.7.2 ----- - Minor bug fix. When running in daemon mode TCP connections weren't cleaned up properly (sockets remained in CLOSE_WAIT status). 0.7.1 ----- - Bug fix. Poll cycle could become very very short (seconds...) in some cases. 0.7.0 ----- - The polling mechanisme has been improved once more :) As of this version polls are also spread within the polling cycle. This way a time offset will be detected earlier, without increasing the polling frequency. - Sleeptime isn't writen to syslog anymore, only time adjustments - Previous versions of htpdate tried to close a already closed HTTP/1.0 session. - The rpm version doesn't override the init-script anymore 0.6.2 ----- - Bug fix, time wasn't correct if 'timeavg' was negative... 0.6.1 ----- - Code clean up, use global variable 'timezone' 0.6 --- - New poll schedule mechanisme has been introduced! This results in a better spreading of the polls in time to gain some extra accuracy. - Removed the -p (precision) flag, because it has become obsolete with the new poll scheduling mechanisme. - Extended 'debug mode' output with round trip time (rtt) information. - Sleeptime parameters are no longer in seconds, but in 2^n seconds. 0.5 --- - Added relevant header files, so gcc -Wall compiles without warnings. - Added -p switch, to set the precision of htpdate. The polling rate is affected by this switch (high precision -> more frequent polling). 0.4 --- - Spread the individual time polls better, to gain accuracy with a small number of web servers as source - Added debug mode, so you can value the quality of the timestamps from web servers - Minor correction on HEAD request (removed max-age=0) - pid file is created - Added htpdate init script 0.3 --- - Run htpdate as a daemon - automatic set/adjust time, based upon offset - automatic poll interval - Added manpage - Added RPM - Minor bug fixes 0.2 --- - Htpdate can set or adjust time smoothly now, without calling external programs 0.1 --- - Initial release. Htpdate extracts the raw timestamp from a webserver. htpdate-1.3.7/LICENSE000066400000000000000000000005161436352671100141720ustar00rootroot00000000000000Copyright (C) 2004-2021 Eddy Vervest This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. http://www.gnu.org/copyleft/gpl.html htpdate-1.3.7/Makefile000066400000000000000000000021271436352671100146250ustar00rootroot00000000000000prefix = $(DESTDIR)/usr bindir = ${prefix}/sbin mandir = ${prefix}/share/man CC ?= gcc CFLAGS += -Wall -std=c11 -pedantic -O2 SSL_LIBS ?= -lssl INSTALL ?= install -c STRIP ?= strip -s all: htpdate htpdate: htpdate.c $(CC) $(CFLAGS) $(LDFLAGS) -o htpdate htpdate.c https: htpdate.c $(CC) $(CFLAGS) $(LDFLAGS) -DENABLE_HTTPS -o htpdate htpdate.c $(SSL_LIBS) install: all $(STRIP) htpdate mkdir -p $(bindir) $(INSTALL) -m 755 htpdate $(bindir)/htpdate mkdir -p $(mandir)/man8 $(INSTALL) -m 644 htpdate.8 $(mandir)/man8/htpdate.8 gzip -f -9 $(mandir)/man8/htpdate.8 test: ./htpdate -v ./htpdate -p 1 www.example.com http://www.example.com https://example.com ./htpdate -p 1 -d www.example.com www.example.com:80/htpdate.html ./htpdate -p 2 -04q www.example.com/ ./htpdate -6q www.example.com ./htpdate -t -dd www.example.com ./htpdate -p1 https://93.184.216.34 https://93.184.216.34:443 ./htpdate -p1 93.184.216.34:80 ./htpdate -p1 [2606:2800:220:1:248:1893:25c8:1946] ./htpdate -h clean: rm -rf htpdate uninstall: rm -rf $(bindir)/htpdate rm -rf $(mandir)/man8/htpdate.8.gz htpdate-1.3.7/README.md000066400000000000000000000043541436352671100144500ustar00rootroot00000000000000# htpdate [![main](https://github.com/twekkel/htpdate/actions/workflows/main.yml/badge.svg)](https://github.com/twekkel/htpdate/actions/workflows/main.yml) ### Time synchronization... quick and dirty ``` date -s "`curl --head -s https://example.com | grep -i "Date: " | cut -d' ' -f2-`" ``` ### Better solution... htpdate The above one-liner might result in unexpected behavior, * site is not reachable * site has wrong time * steps/jumps (backwards!) in time htpdate solves that by allowing multiple URLs as time source, eliminating 'false tickers', gradually adjusts time and run indefinitely as daemon. The HTTP Time Protocol (HTP) is used to synchronize a computer's time with web servers as reference time source. Htpdate will synchronize your computer's time by extracting timestamps from HTTP headers found in web server responses. Htpdate can be used as a daemon, to keep your computer synchronized. The accuracy of htpdate is at least -+0.5 seconds, but can be in the range of ~10 ms (see -p option). If this is not good enough for you, use a ntp package. Install the htpdate package if you need tools for keeping your system's time synchronized via the HTP protocol. Htpdate works also through proxy servers. ### Installation build: ``` make ``` or for HTTPS support (OpenSSL is required) ``` make https ``` install: ``` make install ``` ### Usage Htpdate can be used to query the time of one of more web servers, ``` htpdate www.example.com http://www.example.com https://example.com ``` Htpdate can run as daemon, ``` htpdate -D http://www.example.com ``` Another option is to run htpdate periodically from cron. For a daily time synchronization, ``` 5 3 * * * /usr/sbin/htpdate -a www.example.com ``` All htpdate options, ``` Usage: htpdate [-046acdhlnqstvxDF] [-f driftfile] [-i pidfile] [-m minpoll] [-M maxpoll] [-p precision] [-P [:port]] [-u user[:group]] ... ``` See manpage for more details. ### See also * https://www.vervest.org/htp, home of HTTP Time Protocol * https://www.ntppool.org/scores/ntp6.vervest.org, htpdate as part of NTP pool * https://github.com/twekkel/httpdate, non daemon version using libcurl * https://github.com/angeloc/htpdate, forked from htpdate 1.2.2 * http://www.rkeene.org/oss/htp/ htpdate-1.3.7/htpdate.8000066400000000000000000000113761436352671100147150ustar00rootroot00000000000000.TH "HTPDATE" "8" "version 1.3.7" "htpdate" .SH "NAME" htpdate \- Time synchronization (daemon) .SH "SYNOPSIS" .B htpdate [\-046acdhlnqstvxDF] [\-f driftfile] [\-i pidfile] [\-m minpoll] [\-M maxpoll] [\-p precision] [\-P [:port]] [\-u user[:group]] ... .SH "DESCRIPTION" The HTTP Time Protocol (HTP) is used to synchronize a computer's time with web servers as reference time source. Htp will synchronize your computer's time using the Greenwich Mean Time (GMT) HTTP headers timestamp from web servers. HTTP and HTTPS are both supported. The htpdate package includes a program for retrieving the date and time from remote machines via a network. Htpdate works through proxy servers. Accuracy of htpdate will be usually within 0.5 seconds (better with multiple servers). If this is not good enough for you, use a ntp package like ntpd, OpenNTPD or chrony. .fi .SH OPTIONS .TP .I \-0 HTTP/1.0 request (default is HTTP/1.1). .TP .I \-4 Force IPv4 name resolution only. Default behaviour is to try IPv6 first and fall back to IPv4. .TP .I \-6 Force IPv6 name resolution only. .TP .I \-a Adjust time smoothly (default in daemon mode). .TP .I \-c Verify server certificate (default no verification). .TP .I \-d Turn debug on. Shows the "raw" timestamp, round trip time, time delta and and basic statistics of web server responses. Useful to determining the quality of a specific web server as time source. Multiple -d options increase verbosity. The maximum is 3. .TP .I \-f Read/write the systematic drift of the system clock. See also -x. .TP .I \-h Show help. .TP .I \-i Set the pid file (default /var/run/htpdate.pid). .TP .I \-l Use syslog for output (levels LOG_WARNING and LOG_INFO). Convenient if you use htpdate from cron. .TP .I \-m \-M These options specify the minimum (\-m) and maximum (\-M) polling intervals for HTP requests, in seconds. The default range is between 30 minutes and 32 hours. Htpdate calculates the optimal polling frequency between minimum and maximum values. Only applicable when running in daemon mode. .TP .I \-n Don't use a proxy, even if the appropriate http_proxy environment variable is defined. .TP .I \-p Precision determines the operating accuracy of htpdate. Precision specifies the number of steps (default 4, maximum of 9) for htpdate to determine the second boundary. .TP .I \-q Query web server and display time, but do not change time (default in interactive mode). .TP .I \-s Set time immediate. In daemon mode \-s only applies the first poll. .TP .I \-t Turn off sanity time check. By default a time offset larger than a year, compared to current localtime, is rejected. With \-t set, any time stamp will be accepted. .TP .I \-u Set the user and group that the server normally runs at (default is root). .TP .I \-v Show version. .TP .I \-x Let htpdate compensate for the systematisch clock drift by adjusting system clock frequency. .TP .I \-D Run as daemon. This option requires root privileges. .TP .I \-F Run daemon in foreground. Daemon will not fork or write PID file. This option requires root privileges. .TP .I \-P Proxy server hostname or IP address. .TP .I host Web server hostname or IP address. Up to 16 hosts may be specified, but in general 3 to 5 hosts should be enough for a redundant and accurate setup. .TP .I port Port number (default 80 and 8080 for proxy server). .TP .I path Path to resource (e.g. /index.html). .SH "ENVIRONMENT" Htpdate supports proxies for HTTP connections. The standard way to specify the proxy location, which htpdate recognizes, is using the following environment variable: .IP "\fBhttp_proxy\fR" 4 .IX Item "http_proxy" .PD If set, the http_proxy variable should contain the URL of the proxy for HTTP connections. .SH "EXAMPLES" Request time from web server (don't update local clock): .br \& htpdate www.example.com .P Request time from multiple web servers: .br \& htpdate www.example.com https://example.com http://example.com:80 .P Debug output (don't update local clock): .br \& htpdate \-d www.example.com .P Adjust time smoothly and log output to syslog (eg. cron): .br \& htpdate \-al www.example.com:80/htpdate.html .P HTTP/1.0 request in IPv6 literal format (RFC 2732): .br \& htpdate \-0 [2001:db8:1af6::123]:80 .P Run htpdate as daemon: .br \& htpdate \-D https://www.example.com .P Run htpdate in the foreground with all output going to the terminal: .br \& htpdate \-F www.example.com .P Read clock drift during start of htpdate and update when a new value has been established: .br \& htpdate \-Dx -f /etc/htpdate.drift www.example.com .P Daemon mode for the security minded: .br \& htpdate \-D \-u nobody:nogroup www.example.com .SH "AUTHOR" Eddy Vervest , http://www.vervest.org/htp .SH "SEE ALSO" .BR rdate, .BR timed, .BR ntpd, .BR OpenNTPD, .BR chrony, .BR adjtimex(8), .BR ntp_adjtime, htpdate-1.3.7/htpdate.c000066400000000000000000000731421436352671100147670ustar00rootroot00000000000000/* htpdate v1.3.7 Eddy Vervest http://www.vervest.org/htp Synchronize local system with time offered by remote web servers This program works with the timestamps return by web servers, formatted as specified by HTTP/1.1 (RFC 2616, RFC 1123). Example usage: Debug mode (shows raw timestamps, round trip time (RTT) and time difference): htpdate -d www.example.com Adjust time smoothly: htpdate -a www.example.com ...see man page for more details This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. http://www.gnu.org/copyleft/gpl.html */ /* Needed to avoid implicit warnings from strptime */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined __NetBSD__ || defined __FreeBSD__ || defined __APPLE__ #define adjtimex ntp_adjtime #endif #ifdef ENABLE_HTTPS #include #endif #define VERSION "1.3.7" #define MAX_HTTP_HOSTS 16 /* 16 web servers */ #define DEFAULT_HTTP_PORT "80" #define DEFAULT_PROXY_PORT "8080" #define DEFAULT_IP_VERSION PF_UNSPEC /* IPv6 and IPv4 */ #define DEFAULT_HTTP_VERSION "1" /* HTTP/1.1 */ #define DEFAULT_TIME_LIMIT 31536000 /* 1 year */ #define NO_TIME_LIMIT -1 #define ERR_TIMESTAMP DBL_MAX /* Err fetching date in getHTTPdate */ #define DEFAULT_PRECISION 4 /* 4 request per host */ #define DEFAULT_MIN_SLEEP 1800 /* 30 minutes */ #define DEFAULT_MAX_SLEEP 115200 /* 32 hours */ #define MAX_DRIFT 32768000 /* 500 PPM */ #define DEFAULT_PID_FILE "/var/run/htpdate.pid" #define HEADREQUESTSIZE 1024 #define URLSIZE 128 #define BUFFERSIZE 8192 #define PRINTBUFFERSIZE BUFFERSIZE #define sign(x) (x < 0 ? (-1) : 1) /* By default turn off "debug" and "log" mode */ static int debug = 0; static int logmode = 0; static int verifycert = 0; /* Insertion sort is more efficient (and smaller) than qsort for small lists */ static void insertsort(double a[], int length) { long i, j; for (i = 1; i < length; i++) { double value = a[i]; for (j = i - 1; j >= 0 && a[j] > value; j--) a[j+1] = a[j]; a[j+1] = value; } } /* Printlog is a slighty modified version from the one used in rdate */ static void printlog(int is_error, char *format, ...) { va_list args; char buf[PRINTBUFFERSIZE]; va_start(args, format); (void) vsnprintf(buf, sizeof(buf), format, args); va_end(args); if (logmode) syslog(is_error?LOG_WARNING:LOG_INFO, "%s", buf); else fprintf(is_error?stderr:stdout, "%s\n", buf); } /* Split argument in hostname/IP-address and TCP port Supports IPv6 literal addresses, RFC 2732. */ static void splitURL(char **scheme, char **host, char **port, char **path) { char *rb, *rc, *lb, *lc, *ps; *path = ""; *scheme = NULL; if ((ps = strcasestr(*host, "https://")) != NULL) { #ifndef ENABLE_HTTPS printlog(1, "HTTPS not supported, %s", *host); return; #endif *scheme = "https://"; *port = "443"; *host = ps + 8; } if ((ps = strcasestr(*host, "http://")) != NULL) { *host = ps + 7; } lb = strchr(*host, '['); rb = strrchr(*host, ']'); lc = strchr(*host, ':'); rc = strrchr(*host, ':'); ps = strchr(*host, '/'); /* Extract URL path */ if (ps != NULL) { ps[0] = '\0'; *path = ps + 1; } /* A (literal) IPv6 address with portnumber */ if (rb < rc && lb != NULL && rb != NULL) { rb[0] = '\0'; *port = rc + 1; *host = lb + 1; return; } /* A (literal) IPv6 address without portnumber */ if (rb != NULL && lb != NULL) { rb[0] = '\0'; *host = lb + 1; return; } /* A IPv4 address or hostname with portnumber */ if (rc != NULL && lc == rc) { rc[0] = '\0'; *port = rc + 1; return; } } static void swuid(int id) { if (seteuid(id)) { printlog(1, "seteuid() %i", id); exit(1); } } static void swgid(int id) { if (setegid(id)) { printlog(1, "setegid() %i", id); exit(1); } } static long long getoffset(char remote_time[25]) { struct timeval timevalue = {0, 0}; struct timespec now; struct tm tm; clock_gettime(CLOCK_REALTIME, &now); memset(&tm, 0, sizeof(struct tm)); if (strptime(remote_time, "%d %b %Y %T", &tm) != NULL) { timevalue.tv_sec = timegm(&tm); } else { printlog(1, "unknown time format"); } return now.tv_sec - timevalue.tv_sec; } static int sendHEAD(int server_s, char *headrequest, char *buffer) { int ret = send(server_s, headrequest, strlen(headrequest), 0); if (ret < 0) { printlog(1, "Error sending"); return -1; } /* Receive data from the web server The return code from recv() is the number of bytes received */ ret = recv(server_s, buffer, BUFFERSIZE - 1, 0) > 0; return ret; } #ifdef ENABLE_HTTPS static int sendHEADTLS(SSL *conn, char *headrequest, char *buffer) { int ret = SSL_write(conn, headrequest, strlen(headrequest)); if (ret < 0) { printlog(1, "Error sending: %i", ret); return -1; } /* Receive data from the web server The return code is the number of bytes received */ ret = SSL_read(conn, buffer, BUFFERSIZE - 1) > 0; return ret; } static int proxyCONNECT( int server_s, char *host, char *port, char *proxy, char *proxyport, char *httpversion) { char buffer[BUFFERSIZE] = {'\0'}; char connectrequest[URLSIZE] = {'\0'}; snprintf(connectrequest, URLSIZE, "CONNECT %s:%s HTTP/1.%s\r\n\r\n", host, port, httpversion); send(server_s, connectrequest, strlen(connectrequest), 0); int ret = recv(server_s, buffer, BUFFERSIZE - 1, 0) > 0; if (strstr(buffer, " 200 ") == NULL) { printlog(1, "Proxy error: %s:%s\r\n%s", proxy, proxyport, buffer); } return ret; } #endif static double getHTTPdate( char *scheme, char *host, char *port, char *path, char *proxy, char *proxyport, char *httpversion, int ipversion, int precision) { int server_s; int rc; int polls = 0; struct addrinfo hints, *res; struct timespec sleepspec, now; char headrequest[HEADREQUESTSIZE] = {'\0'}; char buffer[BUFFERSIZE] = {'\0'}; char url[URLSIZE] = {'\0'}; char *pdate = NULL; /* Connect to web server via proxy server or directly */ memset(&hints, 0, sizeof(hints)); switch(ipversion) { case 4: /* IPv4 only */ hints.ai_family = AF_INET; break; case 6: /* IPv6 only */ hints.ai_family = AF_INET6; break; default: /* Support IPv6 and IPv4 name resolution */ hints.ai_family = PF_UNSPEC; } hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; if (proxy == NULL) { rc = getaddrinfo(host, port, &hints, &res); } else { snprintf(url, URLSIZE, "http://%s:%s", host, port); rc = getaddrinfo(proxy, proxyport, &hints, &res); } /* Was the hostname and service resolvable? */ if (rc) { printlog(1, "%s host or service unavailable", host); return(ERR_TIMESTAMP); } /* Build a combined HTTP/1.0 and 1.1 HEAD request Pragma: no-cache, "forces" an HTTP/1.0 and 1.1 compliant web server to return a fresh timestamp Connection: keep-alive, for multiple requests */ snprintf(headrequest, HEADREQUESTSIZE, "HEAD %s/%s HTTP/1.%s\r\n" "Host: %s\r\n" "User-Agent: htpdate/"VERSION"\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Connection: keep-alive\r\n\r\n", url, path, httpversion, host); /* Loop through the available canonical names */ do { server_s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (server_s < 0) { continue; } rc = connect(server_s, res->ai_addr, res->ai_addrlen); if (rc) { close(server_s); server_s = -1; continue; } break; } while ((res = res->ai_next)); freeaddrinfo(res); if (rc) { printlog(1, "%s connection failed", host); return(ERR_TIMESTAMP); } #ifdef ENABLE_HTTPS SSL_CTX *tls_ctx = SSL_CTX_new(TLS_method()); SSL_CTX_set_default_verify_paths(tls_ctx); SSL_CTX_set_verify_depth(tls_ctx, 4); if (verifycert) SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_PEER, NULL); SSL *conn = SSL_new(tls_ctx); SSL_set_tlsext_host_name(conn, host); if (scheme) { if (proxy) { rc = proxyCONNECT(server_s, host, port, proxy, proxyport, httpversion); if (rc != 1) { printlog(1, "Proxy error: %i", rc); return(ERR_TIMESTAMP); } } if (! SSL_set_fd(conn, server_s)) { printlog(1, "TLS error1"); return(ERR_TIMESTAMP); } else { if (SSL_connect(conn) != 1) { printlog(1, "TLS error2"); return(ERR_TIMESTAMP); } } } #endif long long offset = 0, first_offset = 0, prev_offset = 0; long nap = 1e9L; long when = nap >> precision; do { if (debug > 1) printlog(0, "bisect: %i, when: %09li", polls, when); /* Initialize timer */ clock_gettime(CLOCK_REALTIME, &now); /* Initialize RTT (start of measurement) */ long rtt = now.tv_sec; /* Wait till we reach the desired time, "when" */ sleepspec.tv_sec = 0; if (when >= now.tv_nsec) { sleepspec.tv_nsec = when - now.tv_nsec; } else { sleepspec.tv_nsec = 1e9 + when - now.tv_nsec; rtt++; } nanosleep(&sleepspec, NULL); /* Send HEAD request */ #ifdef ENABLE_HTTPS if (scheme) rc = sendHEADTLS(conn, headrequest, buffer); else #endif rc = sendHEAD(server_s, headrequest, buffer); if (!rc) { printlog(1, "error from %s:%s", host, port ); offset = LLONG_MAX; break; } else { clock_gettime(CLOCK_REALTIME, &now); /* rtt contains round trip time in nanoseconds */ rtt = (now.tv_sec - rtt) * 1e9 + now.tv_nsec - when; /* Look for the line that contains [dD]ate: */ if ((pdate = strcasestr(buffer, "date: ")) != NULL && strlen(pdate) >= 35) { if (debug > 2) printlog(0, "%s", buffer); char remote_time[25] = {'\0'}; strncpy(remote_time, pdate + 11, 24); polls++; offset = getoffset(remote_time); nap >>= 1; if (polls > 1) { if (offset != prev_offset) nap = -nap; } else { first_offset=offset; } prev_offset=offset; /* Print host, raw timestamp, round trip time */ if (debug) printlog(0, "%-25s %s, %s (%li ms) => %li", host, port, remote_time, rtt / (long)1e6, offset); } else { printlog(1, "%s no timestamp", host); offset = LLONG_MAX; break; } } /* bytes received */ precision--; when += nap; } while (precision >= 1); close(server_s); #ifdef ENABLE_HTTPS if (scheme) SSL_shutdown(conn); SSL_CTX_free(tls_ctx); SSL_free(conn); #endif /* Rounding */ if (debug) printlog(0, "when: %ld, nap: %ld", when, nap); if (offset == LLONG_MAX) return(ERR_TIMESTAMP); if (when + nap == 1e9 && offset == 0) return 0; /* Return the time delta between web server time (timevalue) and system time (now) */ if (first_offset < 0) { return(-first_offset + (1e9L-when)/(double)1e9L); } else { return(-first_offset + 1 - when/(double)1e9L); } } static int setstatus(int precision) { struct timex txc = {0}; txc.modes = MOD_STATUS | (1000000 >> precision) | MOD_MAXERROR; txc.status &= ~STA_UNSYNC; printlog(0, "Set clock synchronized"); return(adjtimex(&txc)); } static int setclock(double timedelta, int setmode) { struct timespec now; struct timeval timeofday; char buffer[32] = {'\0'}; if (timedelta == 0) { printlog(0, "No time correction needed"); return(0); } switch (setmode) { case 0: /* No time adjustment, just print time */ printlog(0, "Offset %.3f seconds", timedelta); return(0); case 1: /* Adjust time smoothly */ timeofday.tv_sec = (long)timedelta; timeofday.tv_usec = (long)((timedelta - timeofday.tv_sec) * 1e6); printlog(0, "Adjusting %.3f seconds", timedelta); /* Become root */ swuid(0); return(adjtime(&timeofday, NULL)); case 2: /* Set time */ printlog(0, "Setting %.3f seconds", timedelta); clock_gettime(CLOCK_REALTIME, &now); timedelta += (now.tv_sec + now.tv_nsec * 1e-9); now.tv_sec = (long)timedelta; now.tv_nsec = (long)(timedelta - now.tv_sec) * 1e9; strftime(buffer, sizeof(buffer), "%c", localtime(&now.tv_sec)); printlog(0, "Set time: %s", buffer); /* Become root */ swuid(0); return(clock_settime(CLOCK_REALTIME, &now)); case 3: /* Set frequency, but first an adjust */ return(setclock(timedelta, 1)); default: return(-1); } } static int init_frequency(char *driftfile) { struct timex tmx; FILE *fp; fp = fopen(driftfile, "r"); if (fp != NULL) { if (fscanf(fp, "%li", &tmx.freq)) { printlog(0, "Reading frequency from file %li", tmx.freq); fclose(fp); } else { printlog(1, "Error reading frequency from %s", driftfile); fclose(fp); return -1; } } else { printlog(1, "Error reading frequency from %s", driftfile); return -1; } if ((tmx.freq < -MAX_DRIFT) || (tmx.freq > MAX_DRIFT)) tmx.freq = sign(tmx.freq) * MAX_DRIFT; printlog(0, "Set frequency: %li", tmx.freq); tmx.modes = MOD_FREQUENCY; /* Become root */ swuid(0); return(adjtimex(&tmx)); } static int htpdate_adjtimex(double drift, char *driftfile, float confidence) { struct timex tmx; long freq; FILE *fp; /* Read current clock frequency */ tmx.modes = 0; adjtimex(&tmx); /* Calculate new frequency */ freq = (long)(65536e6 * drift); /* Weighted average of current and new frequency */ tmx.freq = tmx.freq + freq * confidence; if ((tmx.freq < -MAX_DRIFT) || (tmx.freq > MAX_DRIFT)) tmx.freq = sign(tmx.freq) * MAX_DRIFT; printlog(0, "Set frequency %li", tmx.freq); tmx.modes = MOD_FREQUENCY; if (driftfile) { fp = fopen(driftfile, "w"); if (fp != NULL) { printlog(0, "Update %s", driftfile); fprintf(fp, "%li", tmx.freq); fclose(fp); } else { printlog(1, "Error writing frequency to %s", driftfile); } } /* Become root */ swuid(0); return(adjtimex(&tmx)); } static void showhelp() { puts("htpdate version "VERSION"\n\ Usage: htpdate [-046acdhlnqstvxDF] [-f driftfile] [-i pidfile] [-m minpoll]\n\ [-M maxpoll] [-p precision] [-P [:port]]\n\ [-u user[:group]] ...\n\n\ -0 HTTP/1.0 request\n\ -4 Force IPv4 name resolution only\n\ -6 Force IPv6 name resolution only\n\ -a adjust time smoothly\n\ -c verify server certificate\n\ -d debug mode\n\ -D daemon mode\n\ -f drift/frequency file\n\ -F run daemon in foreground\n\ -h help\n\ -i pidfile\n\ -l use syslog for output\n\ -m minimum poll interval\n\ -M maximum poll interval\n\ -n no proxy (ignore http_proxy environment variable)\n\ -p precision (1..9, default 4)\n\ -P proxy server\n\ -q query only, don't make time changes (default)\n\ -s set time\n\ -t turn off sanity time check\n\ -u run daemon as user\n\ -v version\n\ -x adjust system clock frequency\n\ URL one of more URLs (max. 16), e.g. www.example.com\n"); return; } /* Run htpdate in daemon mode */ static void runasdaemon(char *pidfile) { FILE *pid_file; pid_t pid; /* Check if htpdate is already running (pid exists)*/ pid_file = fopen(pidfile, "r"); if (pid_file) { fputs("htpdate already running\n", stderr); exit(1); } pid = fork(); if (pid < 0) { fputs("fork()\n", stderr); exit(1); } if (pid > 0) exit(0); /* Create a new SID for the child process */ if (setsid () < 0) exit(1); /* Close out the standard file descriptors */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); signal(SIGHUP, SIG_IGN); /* Change the file mode mask */ umask(0); /* Change the current working directory */ if (chdir("/") < 0) { printlog(1, "chdir()"); exit(1); } /* Second fork, to become the grandchild */ pid = fork(); if (pid < 0) { printlog(1, "fork()"); exit(1); } if (pid > 0) { /* Write a pid file */ pid_file = fopen(pidfile, "w"); if (!pid_file) { printlog(1, "Error writing pid file"); exit(1); } else { fprintf(pid_file, "%d\n", pid); fclose(pid_file); } exit(0); } } int main(int argc, char *argv[]) { char *host = NULL, *proxy = NULL, *proxyport = NULL; char *port = NULL; char *path = NULL; char *scheme = NULL; char *httpversion = DEFAULT_HTTP_VERSION; char *pidfile = DEFAULT_PID_FILE; char *user = NULL, *userstr = NULL, *group = NULL; double timeavg, drift = 0; double timedelta[MAX_HTTP_HOSTS-1]; int numservers; int precision = DEFAULT_PRECISION; int setmode = 0; int i, param; int daemonize = 0, foreground = 0; int noproxyenv = 0; int ipversion = DEFAULT_IP_VERSION; long long timelimit = DEFAULT_TIME_LIMIT; int minsleep = DEFAULT_MIN_SLEEP; int maxsleep = DEFAULT_MAX_SLEEP; int sleeptime = minsleep; int sw_uid = 0, sw_gid = 0; time_t starttime = 0; struct passwd *pw; struct group *gr; extern char *optarg; extern int optind; char *driftfile = NULL; /* Parse the command line switches and arguments */ while ((param = getopt(argc, argv, "046acdf:hi:lm:np:qstu:vxDFM:P:")) != -1) switch(param) { case '0': /* HTTP/1.0 */ httpversion = "0"; break; case '4': /* IPv4 only */ ipversion = 4; break; case '6': /* IPv6 only */ ipversion = 6; break; case 'a': /* adjust time */ setmode = 1; break; case 'c': /* server certificat verification */ verifycert = 1; break; case 'd': /* turn debug on */ if (debug <= 3) debug++; break; case 'f': /* drift file */ driftfile = (char *)optarg; init_frequency(driftfile); break; case 'h': /* show help */ showhelp(); exit(0); case 'i': /* pid file */ pidfile = (char *)optarg; break; case 'l': /* log mode */ logmode = 1; break; case 'm': /* minimum poll interval */ if ((minsleep = atoi(optarg)) <= 0) { fputs("Invalid sleep time\n", stderr); exit(1); } sleeptime = minsleep; break; case 'n': /* don't get proxy from environment */ noproxyenv = 1; break; case 'p': /* precision */ precision = atoi(optarg) ; if ((precision < 1) || (precision > 9)) { fputs("Invalid precision\n", stderr); exit(1); } break; case 'q': /* query only (default) */ break; case 's': /* set time */ setmode = 2; break; case 't': /* disable "sanity" time check */ timelimit = NO_TIME_LIMIT; break; case 'u': /* drop root privileges and run as user */ user = (char *)optarg; userstr = strchr(user, ':'); if (userstr != NULL) { userstr[0] = '\0'; group = userstr + 1; } if ((pw = getpwnam(user)) != NULL) { sw_uid = pw->pw_uid; sw_gid = pw->pw_gid; } else { printf("Unknown user %s\n", user); exit(1); } if (group != NULL) { if ((gr = getgrnam(group)) != NULL) { sw_gid = gr->gr_gid; } else { printf("Unknown group %s\n", group); exit(1); } } break; case 'v': /* print version */ printf("htpdate version %s\n", VERSION); exit(0); case 'x': /* adjust time and clock frequency */ setmode = 3; if (maxsleep > 14400) maxsleep = 14400; if (precision < 7) precision = 7; break; case 'D': /* run as daemon */ daemonize = 1; logmode = 1; break; case 'F': /* run daemon in foreground, don't fork */ foreground = 1; break; case 'M': /* maximum poll interval */ if ((maxsleep = atoi(optarg)) <= 0) { fputs("Invalid sleep time\n", stderr); exit(1); } break; case 'P': proxyport = DEFAULT_PROXY_PORT; char *proxywithport = strdup((char *)optarg); proxy = proxywithport; splitURL(&scheme, &proxy, &proxyport, &path); break; case '?': return 1; default: abort(); } /* Display help page, if no servers are specified */ if (argv[optind] == NULL) { showhelp(); exit(1); } /* Exit if too many servers are specified */ numservers = argc - optind; if (numservers > MAX_HTTP_HOSTS) { fputs("Too many servers\n", stderr); exit(1); } /* Use http_proxy environment variable */ if (getenv("http_proxy") && !noproxyenv) { if ((proxy = strstr(getenv("http_proxy"), "http://")) == NULL) { printlog(1, "Invalid proxy specified: %s", getenv("http_proxy")); exit(1); } if (debug) printlog(0, "Proxy: %s", proxy); proxy += 7; proxyport = DEFAULT_PROXY_PORT; splitURL(&scheme, &proxy, &proxyport, &path); } /* One must be "root" to change the system time */ if ((getuid() != 0) && (setmode || daemonize || foreground)) { fputs("Only root can change time\n", stderr); exit(1); } /* Run as a daemonize when -D is set */ if (daemonize) { runasdaemon(pidfile); } /* Query only mode doesn't exist in daemon or foreground mode */ if (daemonize || foreground) { printlog(0, "htpdate version "VERSION" started"); if (!setmode) setmode = 1; } /* Now we are root, we drop the privileges (if specified) */ if (sw_gid) swgid(sw_gid); if (sw_uid) swuid(sw_uid); #ifdef ENABLE_HTTPS SSL_library_init(); #endif /* Infinite poll cycle loop in daemonize or foreground mode */ do { /* Initialize number of received valid timestamps, good timestamps and the average of the good timestamps */ int validtimes = 0, goodtimes = 0; double sumtimes = 0, mean = 0; /* Loop through the time sources (web servers); poll cycle */ for (i = optind; i < argc; i++) { /* host:port is stored in argv[i] */ char *hostport = strdup(argv[i]); host = hostport; port = DEFAULT_HTTP_PORT; splitURL(&scheme, &host, &port, &path); double offset = getHTTPdate(scheme, host, port, path, proxy, proxyport, httpversion, ipversion, precision); free(hostport); if (debug && offset != ERR_TIMESTAMP) { printlog(0, "offset: %.6f", offset); } /* Only include valid responses in timedelta[] */ if ((timelimit == NO_TIME_LIMIT && offset != ERR_TIMESTAMP) || (offset < timelimit && offset > -timelimit)) { timedelta[validtimes] = offset; validtimes++; } } /* Sort the timedelta results */ insertsort(timedelta, validtimes); /* Mean time value */ mean = timedelta[validtimes/2]; /* Filter out the bogus timevalues. A timedelta which is more than 1 second off from mean, is considered a 'false ticker'. NTP synced web servers can never be more off than a second. */ for (i = 0; i < validtimes; i++) { if ((timedelta[i]-mean) < .5 && (timedelta[i]-mean) > -.5) { sumtimes += timedelta[i]; goodtimes++; } } /* Check if we have at least one valid response */ if (goodtimes) { timeavg = sumtimes / goodtimes; /* Avoid bouncing between upper/lower limit when (almost) in sync */ if (timeavg < 1 && timeavg > -1) timeavg /= 2; if (debug > 1) printlog(0, "#: %d, mean: %.3f, average: %.3f", goodtimes, mean, timeavg); /* Do I really need to change the time? */ if (sumtimes || !(daemonize || foreground)) { if (setclock(timeavg, setmode) < 0) printlog(1, "Time change failed"); /* Drop root privileges again */ if (sw_uid) swuid(sw_uid); if (daemonize || foreground) { if (starttime) { /* Calculate systematic clock drift */ drift = timeavg / (time(NULL) - starttime); printlog(0, "Drift %.2f PPM, %.2f s/day", drift*1e6, drift*86400); /* Adjust system clock */ if (setmode == 3) { starttime = time(NULL); /* Adjust the clock frequency */ if (htpdate_adjtimex(drift, driftfile, (float)sleeptime / (float)maxsleep) < 0) printlog(1, "Frequency change failed"); /* Drop root privileges again */ if (sw_uid) swuid(sw_uid); } } else { starttime = time(NULL); } /* Decrease polling interval to minimum */ sleeptime = minsleep; /* Sleep for 30 minutes after a time adjust or set */ sleep(DEFAULT_MIN_SLEEP); } } else { /* Increase polling interval */ if (sleeptime < maxsleep) sleeptime <<= 1; if (setmode == 3) setstatus(precision); } if (daemonize || foreground) { printlog(0, "sleep for %ld s", sleeptime); sleep(sleeptime); } /* After first successful poll cycle do not step through time, only adjust */ if (setmode != 3) setmode = 1; } else { printlog(1, "No server suitable for synchronization found"); /* Sleep for minsleep to avoid flooding */ if (daemonize || foreground) sleep(minsleep); else exit(1); } } while (daemonize || foreground); /* end of infinite while loop */ exit(0); } htpdate-1.3.7/scripts/000077500000000000000000000000001436352671100146525ustar00rootroot00000000000000htpdate-1.3.7/scripts/adjtimex_parameters.sh000077500000000000000000000014151436352671100212420ustar00rootroot00000000000000#! /bin/sh # # In daemon mode htpdate writes the systematic drift of the clock to syslog. # With this script you can convert the PPM drift values into adjtimex # parameters. Only use this script when you know what you are doing. # # Consult adjtimex man page for more information. if [ ! $1 ]; then echo "Usage: $0 [current TICK] [current FREQ]" echo "By default TICK=10000 and FREQ=0" echo exit 1 fi PPM=$1 TICK=10000 FREQ=0 if [ $2 ]; then TICK=$2 fi if [ $3 ]; then FREQ=$3 fi FREQTOT=`echo "$PPM * 65536 + $TICK * 6553600 + $FREQ" | bc` TICK=`echo "$FREQTOT / 6553600" | bc` FREQ=`echo "$FREQTOT % 6553600" | bc | awk -F. '{print $1}'` echo "TICK=$TICK" echo "FREQ=$FREQ" echo "Suggested command: adjtimex -tick $TICK -frequency $FREQ" htpdate-1.3.7/scripts/htpdate.init000077500000000000000000000022621436352671100171750ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: htpdate # Required-Start: $network $remote_fs $syslog # Required-Stop: $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: htpdate time synchronization # Description: htpdate is a time synchronization daemon ### END INIT INFO NAME="htpdate" DAEMON="/usr/sbin/${NAME}" PIDFILE="/var/run/${NAME}.pid" HTPDATE_ARGS="-s -t https://www.example.com" test -r "/etc/default/$NAME" && . "/etc/default/$NAME" start() { printf 'Starting %s: ' "$NAME" # shellcheck disable=SC2086 # we need the word splitting start-stop-daemon -S -q -x "$DAEMON" \ -- -D -i "$PIDFILE" $HTPDATE_ARGS status=$? if [ "$status" -eq 0 ]; then echo "OK" else echo "FAIL" fi return "$status" } stop() { printf 'Stopping %s: ' "$NAME" start-stop-daemon -K -q -p "$PIDFILE" status=$? if [ "$status" -eq 0 ]; then rm -f "$PIDFILE" echo "OK" else echo "FAIL" fi return "$status" } restart() { stop start } case "$1" in start|stop|restart) "$1";; reload) # Restart, since there is no true "reload" feature. restart;; *) echo "Usage: $0 {start|stop|restart|reload}" exit 1 esac htpdate-1.3.7/scripts/htpdate.service000066400000000000000000000004551436352671100176710ustar00rootroot00000000000000[Unit] Description=HTTP Time Protocol daemon (htpdate) Documentation=man:htpdate(8) After=network-online.target [Service] Type=exec Environment=HTPDATE_ARGS="-st www.example.com" EnvironmentFile=-/etc/default/htpdate ExecStart=/usr/sbin/htpdate -F $HTPDATE_ARGS [Install] WantedBy=multi-user.target