htpdate/0000755000175000017500000000000014114426072012266 5ustar andradeandradehtpdate/Changelog0000644000175000017500000001701414114426072014103 0ustar andradeandradeChanges in 1.2.6 ---------------- - Fix https://github.com/angeloc/htpdate/issues/3 Changes in 1.2.5 ---------------- - Fix crosscompiling using pkg-config for openssl LDFLAGS 1.2.4 ----- - Adding https support 1.2.3 ----- - Fix crosscompiling allowing overwrite CC variable - Adding GPLv2 LICENSE file 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/htpdate.80000644000175000017500000001001614114426072014006 0ustar andradeandrade.TH "HTPDATE" "8" "version 1.2.6" "htpdate" .SH "NAME" htpdate \- Time synchronization (daemon) .SH "SYNOPSIS" .B htpdate [\-046abdhlqstxDF] [\-i pid file] [\-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 to Greenwich Mean Time (GMT) via HTTP headers from web servers. 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, try the ntpd package. .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 \-b Burst mode uses multiple polls for each web server to enhance accuracy. .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. .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 \-p Precision (in milliseconds) specifies the operating accuracy of htpdate. Internally htpdate uses a different algorithm to detect a time offset, when precision is specified. Precision only has effect in daemon mode. Use with causion. .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 \-x Let htpdate compensate for the systematisch clock drift. .TP .I \-D Run as daemon (requires root privileges). .TP .I \-F Run in the foreground (requires root privileges). This is the same as \-D but will not fork or write a PID file. .TP .I \-P Proxy server hostname or ip-address. .TP .I host Web server hostname or ip-address. Upto 16 hosts may be specified, but in general 3 to 5 hosts should be enough for a redundant and accurate setup. .TP .I port Portnumber (default 80 and 8080 for proxy server), accept prefix like http:// or https:// .SH "EXAMPLES" Request time from web server (don't update local clock): .br \& htpdate \-q www.example.com .P Request time from https web server: .br \& htpdate \-q https://www.example.com .P Verbose output (don't update local clock): .br \& htpdate \-d \-q www.example.com .P Adjust time smoothly and log output to syslog (eg. cron): .br \& htpdate \-a \-l www.example.com .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 www.example.com .P Run htpdate in the foreground with all output going to the terminal: .br \& htpdate \-F 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 adjtimex, .BR ntp_adjtime, .br HTP implementation by Roy Keene (http://www.rkeene.org/oss/htp/) htpdate/LICENSE0000644000175000017500000000134114114426072013272 0ustar andradeandradeCopyright (C) 2020 Hong Shen 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. htpdate/htpdate.c0000644000175000017500000005536614114426072014102 0ustar andradeandrade/* htpdate v1.2.6 Author: Angelo Compagnucci https://github.com/angeloc/htpdate Original author: Eddy Vervest http://www.vervest.org/htp Synchronize local workstation 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 #ifdef ENABLE_HTTPS #include #endif #define VERSION "1.2.6" #define MAX_HTTP_HOSTS 15 /* 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 DEFAULT_MIN_SLEEP 1800 /* 30 minutes */ #define DEFAULT_MAX_SLEEP 115200 /* 32 hours */ #define MAX_DRIFT 32768000 /* 500 PPM */ #define MAX_ATTEMPT 2 /* Poll attempts */ #define DEFAULT_PID_FILE "/var/run/htpdate.pid" #define URLSIZE 128 #define BUFFERSIZE 1024 #define sign(x) (x < 0 ? (-1) : 1) /* By default we turn off "debug" and "log" mode */ static int debug = 0; static int logmode = 0; /* Make mktime timezone agnostic, see manpage timegm */ time_t gmtmktime (struct tm *tm) { char *tz; time_t result; /* Temporarily set timezone to UTC for conversion */ tz = getenv("TZ"); if (tz) { tz = strdup (tz); } setenv("TZ", "", 1); tzset(); result = mktime (tm); /* Restore timezone */ if (tz) { setenv("TZ", tz, 1); free (tz); } else { unsetenv("TZ"); } tzset(); return result; } /* Insertion sort is more efficient (and smaller) than qsort for small lists */ static void insertsort( int a[], int length ) { int i, j, value; for ( i = 1; i < length; i++ ) { value = a[i]; for ( j = i - 1; j >= 0 && a[j] > value; j-- ) a[j+1] = a[j]; a[j+1] = value; } } /* Split argument in hostname/IP-address and TCP port Supports IPv6 literal addresses, RFC 2732. */ static void splithostport( char **host, char **port ) { char *rb, *rc, *lb, *lc; lb = strchr( *host, '[' ); rb = strrchr( *host, ']' ); lc = strchr( *host, ':' ); rc = strrchr( *host, ':' ); if (strstr(*host, "https://") == *host) { *host = *host + strlen("https://"); *port = "443\0"; return; } if (strstr(*host, "http://") == *host) { *host = *host + strlen("http://"); *port = "80\0"; return; } /* A (litteral) IPv6 address with portnumber */ if ( rb < rc && lb != NULL && rb != NULL ) { rb[0] = '\0'; *port = rc + 1; *host = lb + 1; return; } /* A (litteral) 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; } } /* 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[128]; 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); } /* Drop or elevate privileges */ 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 int getHTTP (int server_s, char *buffer) { int ret; /* Send HEAD request */ ret = send(server_s, buffer, strlen(buffer), 0); if (ret < 0) { printlog( 1, "Error sending" ); return 0; } /* 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) != -1; close( server_s ); return ret; } #ifdef ENABLE_HTTPS static int getHTTPS (int server_s, char *buffer) { int ret; SSL_CTX *ssl_ctx = SSL_CTX_new (TLS_method()); SSL *conn = SSL_new(ssl_ctx); SSL_set_fd(conn, server_s); int err = SSL_connect(conn); if (err != 1) return 0; ret = SSL_write(conn, buffer, BUFFERSIZE -1); if (ret <= 0) { printlog( 1, "Error sending" ); return 0; } ret = SSL_read(conn, buffer, BUFFERSIZE - 1) > 0; SSL_shutdown(conn); return ret; } #endif static long getHTTPdate( char *host, char *port, char *proxy, char *proxyport, char *httpversion, int ipversion, int when ) { int server_s; int rc; struct addrinfo hints, *res, *res0; struct tm tm; struct timeval timevalue = {LONG_MAX, 0}; struct timeval timeofday; struct timespec sleepspec, remainder; long rtt; char buffer[BUFFERSIZE] = { '\0' }; char remote_time[25] = { '\0' }; char url[URLSIZE] = { '\0' }; char *pdate = NULL; #ifdef ENABLE_HTTPS int port_is_https = 0; if (strncmp (port, "443", 3) == 0) port_is_https = 1; #endif /* 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, &res0 ); } else { snprintf( url, URLSIZE, "http://%s:%s", host, port); rc = getaddrinfo( proxy, proxyport, &hints, &res0 ); } /* Was the hostname and service resolvable? */ if ( rc ) { printlog( 1, "%s host or service unavailable", host ); return(0); /* Assume correct time */ } /* 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: close, allows the server the immediately close the connection after sending the response. */ snprintf(buffer, BUFFERSIZE, "HEAD %s/ HTTP/1.%s\r\nHost: %s\r\nUser-Agent: htpdate/"VERSION"\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n", url, httpversion, host); /* Loop through the available canonical names */ res = res0; 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(res0); if ( rc ) { printlog( 1, "%s connection failed", host ); return(0); /* Assume correct time */ } /* Initialize timer */ gettimeofday(&timeofday, NULL); /* Initialize RTT (start of measurement) */ rtt = timeofday.tv_sec; /* Wait till we reach the desired time, "when" */ sleepspec.tv_sec = 0; if ( when >= timeofday.tv_usec ) { sleepspec.tv_nsec = ( when - timeofday.tv_usec ) * 1000; } else { sleepspec.tv_nsec = ( 1000000 + when - timeofday.tv_usec ) * 1000; rtt++; } nanosleep( &sleepspec, &remainder ); #ifdef ENABLE_HTTPS if (port_is_https) rc = getHTTPS(server_s, buffer); else #endif rc = getHTTP(server_s, buffer); if ( !rc ) printlog( 1, "error getting data from %s:%s", host, port ); if ( rc ) { /* Assuming that network delay (server->htpdate) is neglectable, the received web server time "should" match the local time. From RFC 2616 paragraph 14.18 ... It SHOULD represent the best available approximation of the date and time of message generation, unless the implementation has no means of generating a reasonably accurate date and time. ... */ gettimeofday(&timeofday, NULL); /* rtt contains round trip time in micro seconds, now! */ rtt = ( timeofday.tv_sec - rtt ) * 1000000 + \ timeofday.tv_usec - when; /* Look for the line that contains Date: */ if ( ((pdate = strstr(buffer, "Date: ")) != NULL || (pdate = strstr(buffer, "date: ")) != NULL) && strlen( pdate ) >= 35 ) { strncpy(remote_time, pdate + 11, 24); memset(&tm, 0, sizeof(struct tm)); if ( strptime( remote_time, "%d %b %Y %T", &tm) != NULL) { timevalue.tv_sec = gmtmktime(&tm); } else { printlog( 1, "%s unknown time format", host ); } /* Print host, raw timestamp, round trip time */ if ( debug ) printlog( 0, "%-25s %s %s (%.3f) => %li", host, port, remote_time, \ rtt * 1e-6, timevalue.tv_sec - timeofday.tv_sec ); } else { printlog( 1, "%s no timestamp", host ); } } /* bytes received */ /* Return the time delta between web server time (timevalue) and system time (timeofday) */ return( timevalue.tv_sec - timeofday.tv_sec ); } static int setclock( double timedelta, int setmode ) { struct timeval timeofday; 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) * 1000000); printlog( 0, "Adjusting %.3f seconds", timedelta ); /* Become root */ swuid(0); return( adjtime(&timeofday, NULL) ); case 2: /* Set time */ printlog( 0, "Setting %.3f seconds", timedelta ); gettimeofday( &timeofday, NULL ); timedelta += ( timeofday.tv_sec + timeofday.tv_usec*1e-6 ); timeofday.tv_sec = (long)timedelta; timeofday.tv_usec = (long)(timedelta - timeofday.tv_sec) * 1000000; printlog( 0, "Set: %s", asctime(localtime(&timeofday.tv_sec)) ); /* Become root */ swuid(0); return( settimeofday(&timeofday, NULL) ); case 3: /* Set frequency, but first an adjust */ return( setclock( timedelta, 1 ) ); default: return(-1); } } static int htpdate_adjtimex( double drift ) { struct timex tmx; long freq; /* Read current kernel frequency */ tmx.modes = 0; adjtimex(&tmx); /* Calculate new frequency */ freq = (long)(65536e6 * drift); /* Take the average of current and new drift values */ tmx.freq = tmx.freq + (freq >> 1); if ( (tmx.freq < -MAX_DRIFT) || (tmx.freq > MAX_DRIFT) ) tmx.freq = sign(tmx.freq) * MAX_DRIFT; printlog( 0, "Adjusting frequency %li", tmx.freq ); tmx.modes = MOD_FREQUENCY; /* Become root */ swuid(0); return( adjtimex(&tmx) ); } static void showhelp() { puts("htpdate version "VERSION"\n\ Usage: htpdate [-046abdhlqstxD] [-i pid file] [-m minpoll] [-M maxpoll]\n\ [-p precision] [-P [:port]] [-u user[:group]]\n\ ...\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\ -b burst mode\n\ -d debug mode\n\ -D daemon mode\n\ -F foreground mode\n\ -h help\n\ -i pid file\n\ -l use syslog for output\n\ -m minimum poll interval\n\ -M maximum poll interval\n\ -p precision (ms)\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\ -x adjust kernel clock\n\ host web server hostname or ip address (maximum of 16)\n\ port port number (default 80 and 8080 for proxy server)\n"); return; } /* Run htpdate in daemon mode */ static void runasdaemon( char *pidfile ) { FILE *pid_file; pid_t pid; /* If the user has specified a relative path, prepend the curdir */ char *abs_path = malloc( PATH_MAX ); char *ret __attribute__((unused)) = realpath(pidfile, abs_path); if (errno && errno != ENOENT) { printlog( 1, "error getting pid file path" ); exit(1); } /* Check if htpdate is already running (pid exists)*/ pid_file = fopen(abs_path, "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( abs_path, "w" ); free( abs_path ); if ( !pid_file ) { printlog( 1, "Error writing pid file" ); exit(1); } else { fprintf( pid_file, "%u\n", (unsigned int)pid ); fclose( pid_file ); } printlog( 0, "htpdate version "VERSION" started" ); exit(0); } } int main( int argc, char *argv[] ) { char *host = NULL, *proxy = NULL, *proxyport = NULL; char *port = NULL; char *hostport = NULL; char *httpversion = DEFAULT_HTTP_VERSION; char *pidfile = DEFAULT_PID_FILE; char *user = NULL, *userstr = NULL, *group = NULL; long long sumtimes; double timeavg, drift = 0; int timedelta[(MAX_HTTP_HOSTS+1)*(MAX_HTTP_HOSTS+1)-1], timestamp; int numservers, validtimes, goodtimes, mean; int nap = 0, when = 500000, precision = 0; int setmode = 0, burstmode = 0, try, offsetdetect; int i, burst, param; int daemonize = 0; int foreground = 0; int ipversion = DEFAULT_IP_VERSION; int 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; /* Parse the command line switches and arguments */ while ( (param = getopt(argc, argv, "046abdhi:lm:p:qstu:xDFM: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 'b': /* burst mode */ burstmode = 1; break; case 'd': /* turn debug on */ debug = 1; 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 'p': /* precision */ precision = atoi(optarg) ; if ( (precision <= 0) || (precision >= 500) ) { fputs( "Invalid precision\n", stderr ); exit(1); } precision *= 1000; break; case 'q': /* query only */ break; case 's': /* set time */ setmode = 2; break; case 't': /* disable "sanity" time check */ timelimit = 2100000000; 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 'x': /* adjust time and "kernel" */ setmode = 3; break; case 'D': /* run as daemon */ daemonize = 1; logmode = 1; break; case 'F': /* run in the foreground */ foreground = 1; break; case 'M': /* maximum poll interval */ if ( ( maxsleep = atoi(optarg) ) <= 0 ) { fputs( "Invalid sleep time\n", stderr ); exit(1); } break; case 'P': proxy = (char *)optarg; proxyport = DEFAULT_PROXY_PORT; splithostport( &proxy, &proxyport ); 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); } /* 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) && !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 ); /* In case we have more than one web server defined, we spread the polls equal within a second and take a "nap" in between */ if ( numservers > 1 ) if ( precision && (numservers > 2) ) nap = (1000000 - 2*precision) / (numservers - 1); else nap = 1000000 / (numservers + 1); else { precision = 0; nap = 500000; } #ifdef ENABLE_HTTPS SSL_load_error_strings (); 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 */ validtimes = goodtimes = sumtimes = offsetdetect = 0; if ( precision ) when = precision; else when = nap; /* Loop through the time sources (web servers); poll cycle */ for ( i = optind; i < argc; i++ ) { /* host:port is stored in argv[i] */ hostport = (char *)argv[i]; host = strdup(hostport); port = DEFAULT_HTTP_PORT; splithostport( &host, &port ); #ifndef ENABLE_HTTPS if (strncmp (port, "443", 3) == 0) printlog( 1, "HTTPS support not compiled in, " "cannot get timestamp from %s", host); #endif /* if burst mode, reset "when" */ if ( burstmode ) { if ( precision ) when = precision; else when = nap; } burst = 0; do { /* Retry if first poll shows time offset */ try = MAX_ATTEMPT; do { if ( debug ) printlog( 0, "burst: %d try: %d when: %d", \ burst + 1, MAX_ATTEMPT - try + 1, when ); timestamp = getHTTPdate( host, port, proxy, proxyport,\ httpversion, ipversion, when ); try--; } while ( timestamp && try ); /* Only include valid responses in timedelta[] */ if ( timestamp < timelimit && timestamp > -timelimit ) { timedelta[validtimes] = timestamp; validtimes++; } /* If we detected a time offset, set the flag */ if ( timestamp ) offsetdetect = 1; /* Take a nap, to spread polls equally within a second. Example: 2 servers => 0.333, 0.666 3 servers => 0.250, 0.500, 0.750 4 servers => 0.200, 0.400, 0.600, 0.800 ... nap = 1000000 / (#servers + 1) or when "precision" is specified, a different algorithm is used */ when += nap; burst++; } while ( burst < (argc - optind) * burstmode ); /* Sleep for a while, unless we detected a time offset */ if ( (daemonize || foreground) && !offsetdetect ) sleep( sleeptime / numservers ); } /* 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 seconde 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 <= 1 && timedelta[i]-mean >= -1 ) { sumtimes += timedelta[i]; goodtimes++; } } /* Check if we have at least one valid response */ if ( goodtimes ) { timeavg = sumtimes/(double)goodtimes; if ( debug ) { printlog( 0, "#: %d mean: %d average: %.3f", goodtimes, \ mean, timeavg ); } /* Do I really need to change the time? */ if ( sumtimes || !(daemonize || foreground) ) { /* If a precision was specified and the time offset is small (< +-1 second), adjust the time with the value of precision */ if ( precision && sumtimes < goodtimes && sumtimes > -goodtimes ) timeavg = (double)precision / 1000000 * sign(sumtimes); /* Correct the clock, if not in "adjtimex" mode */ if ( setclock( timeavg, setmode ) < 0 ) printlog( 1, "Time change failed" ); /* Drop root privileges again */ 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 kernel clock */ if ( htpdate_adjtimex( drift ) < 0 ) printlog( 1, "Frequency change failed" ); /* Drop root privileges again */ 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 ( debug && (daemonize || foreground) ) printlog( 0, "poll %ld s", sleeptime ); } else { printlog( 1, "No server suitable for synchronization found" ); /* Sleep for minsleep to avoid flooding */ if ( daemonize || foreground ) sleep( minsleep ); else exit(1); } /* After first poll cycle do not step through time, only adjust */ if ( setmode != 3 ) { setmode = 1; } } while ( daemonize || foreground ); /* end of infinite while loop */ exit(0); } /* vim: set ts=4 sw=4: */ htpdate/.gitignore0000644000175000017500000000001014114426072014245 0ustar andradeandradehtpdate htpdate/Makefile0000644000175000017500000000123514114426072013727 0ustar andradeandradeprefix = $(DESTDIR)/usr bindir = ${prefix}/bin mandir = ${prefix}/share/man CC ?= gcc CFLAGS += -Wall -std=c99 -pedantic -O2 PKG_CONFIG ?= pkg-config ifdef ENABLE_HTTPS CFLAGS += -DENABLE_HTTPS LDLIBS = $(shell $(PKG_CONFIG) --libs openssl) endif INSTALL = install -c all: htpdate htpdate: htpdate.c $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o htpdate htpdate.c $(LDLIBS) install: all 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 clean: rm -rf htpdate uninstall: rm -rf $(bindir)/htpdate rm -rf $(mandir)/man8/htpdate.8.gz htpdate/.github/0000755000175000017500000000000014114426072013626 5ustar andradeandradehtpdate/.github/FUNDING.yml0000644000175000017500000000004514114426072015442 0ustar andradeandradecustom: https://paypal.me/angelocomp htpdate/README.md0000644000175000017500000000375114114426072013553 0ustar andradeandradeHtpdate ------- 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 (better with multiple servers). If this is not good enough for you, try the ntpd 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 from source ------------------------ Tested on Linux and FreeBSD only, but should work for most Unix flavors. $ tar zxvf htpdate-x.y.z.zip or $ tar jxvf htpdate-x.y.z.tar.gz $ cd htpdate-X.Y.Z $ make $ make install To compile with HTTPS support, install openssl development files $ make ENABLE_HTTPS=1 An example init script (scripts/htpdate.init) for use in /etc/init.d/ is included, but not installed automatically. This scripts will run htpdate as a daemon. There is also a service file example for systemd (scripts/htpdate.service). Another option is to use htpdate in a cronjob and start it periodically from cron. For a daily time sync it would look something like this: 5 3 * * * /usr/bin/htpdate -s www.example.com Usage ----- Usage: htpdate [-046abdhlqstxD] [-i pid file] [-m minpoll] [-M maxpoll] [-p precision] [-P [:port]] [-u user[:group]] ... Eg. htpdate -q www.example.com Eg. htpdate -a -t https://www.example.com http://www.example.com In general, if more web servers are specified, the accuracy will increase. See manpage for more details. New features ------------ Version v1.2.4 introduced https support. Maintenance ----------- This project is a fork based on htp from Eddy Vervest (http://www.vervest.org/htp). This fork exists only because original project is unmaintained and there are no other active forks. htpdate/scripts/0000755000175000017500000000000014114426072013755 5ustar andradeandradehtpdate/scripts/htpdate.service0000644000175000017500000000042514114426072016771 0ustar andradeandrade[Unit] Description=htpdate daemon After=network.target [Service] Type=exec Environment=HTPDATE_ARGS="-a -s -t http://www.google.com https://www.google.com" EnvironmentFile=-/etc/default/htpdate ExecStart=/usr/bin/htpdate -F $HTPDATE_ARGS [Install] WantedBy=multi-user.target htpdate/scripts/htpdate.init0000755000175000017500000000222414114426072016276 0ustar andradeandrade#!/bin/sh # # A minimal init script for htpdate ### 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: # Short-Description: Start htpdate daemon ### END INIT INFO DAEMON="htpdate" PIDFILE="/var/run/$DAEMON.pid" HTPDATE_ARGS="-a -s -t http://www.google.com https://google.com" test -r "/etc/default/$DAEMON" && . "/etc/default/$DAEMON" start() { printf 'Starting %s: ' "$DAEMON" # shellcheck disable=SC2086 # we need the word splitting start-stop-daemon -S -q -x "/usr/bin/$DAEMON" \ -- -D -i "$PIDFILE" $HTPDATE_ARGS status=$? if [ "$status" -eq 0 ]; then echo "OK" else echo "FAIL" fi return "$status" } stop() { printf 'Stopping %s: ' "$DAEMON" 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 sleep 1 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/scripts/adjtimex_parameters.sh0000755000175000017500000000143714114426072020351 0ustar andradeandrade#! /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. Use it when you know what you are doing... # # The Linux adjtimex manpage gives you more information. # # Feel free to contribute for other OS's. 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"