├── debian ├── compat ├── manpages ├── dirs ├── changelog ├── htpdate-default ├── postinst ├── prerm ├── control ├── init.d ├── copyright └── rules ├── htpdate.8.gz ├── Makefile ├── scripts ├── adjtimex_parameters.sh └── htpdate.init ├── htpdate.spec ├── README ├── Changelog └── htpdate.c /debian/compat: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /debian/manpages: -------------------------------------------------------------------------------- 1 | htpdate.8.gz 2 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | etc/default 2 | etc/init.d 3 | usr/bin 4 | -------------------------------------------------------------------------------- /htpdate.8.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iridium77/htpdate/HEAD/htpdate.8.gz -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | htpdate (1.0.4) stable; urgency=low 2 | 3 | * Fixed a memory leak 4 | 5 | -- Eddy Vervest Mon, 13 Oct 2008 20:20:45 +0200 6 | -------------------------------------------------------------------------------- /debian/htpdate-default: -------------------------------------------------------------------------------- 1 | # Defaults for htpdate initscript 2 | # sourced by /etc/init.d/htpdate 3 | # installed at /etc/default/htpdate by the maintainer scripts 4 | 5 | # Default options that are passed to htpdate 6 | DAEMON_OPTS="-D www.linux.org www.freebsd.org" 7 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for htpdate 3 | 4 | set -e 5 | 6 | case "$1" in 7 | configure) 8 | ;; 9 | 10 | abort-upgrade|abort-remove|abort-deconfigure) 11 | ;; 12 | 13 | *) 14 | echo "postinst called with unknown argument \`$1'" >&2 15 | exit 1 16 | ;; 17 | esac 18 | 19 | #DEBHELPER# 20 | 21 | exit 0 22 | 23 | # vi:set ts=4: 24 | -------------------------------------------------------------------------------- /debian/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # prerm script for htpdate 3 | 4 | set -e 5 | 6 | case "$1" in 7 | remove|upgrade|deconfigure) 8 | if [ -x /etc/init.d/htpdate ]; then 9 | /etc/init.d/htpdate stop 10 | fi 11 | ;; 12 | failed-upgrade) 13 | ;; 14 | *) 15 | echo "prerm called with unknown argument \`$1'" >&2 16 | exit 1 17 | ;; 18 | esac 19 | 20 | #DEBHELPER# 21 | 22 | exit 0 23 | 24 | # vi:set ts=4: 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix = $(DESTDIR)/usr 2 | bindir = ${prefix}/bin 3 | mandir = ${prefix}/share/man 4 | 5 | CC = gcc 6 | CFLAGS += -Wall -O2 7 | #CFLAGS += -Wall -pedantic -ansi -O2 8 | 9 | INSTALL = /usr/bin/install -c 10 | STRIP = /usr/bin/strip -s 11 | 12 | all: htpdate 13 | 14 | htpdate: htpdate.c 15 | $(CC) $(CFLAGS) $(LDFLAGS) -o htpdate htpdate.c 16 | 17 | install: all 18 | $(STRIP) htpdate 19 | mkdir -p $(bindir) 20 | $(INSTALL) -m 755 htpdate $(bindir)/htpdate 21 | mkdir -p $(mandir)/man8 22 | $(INSTALL) -m 644 htpdate.8.gz $(mandir)/man8/htpdate.8.gz 23 | 24 | clean: 25 | rm -rf htpdate 26 | 27 | uninstall: 28 | rm -rf $(bindir)/htpdate 29 | rm -rf $(mandir)/man8/htpdate.8.gz 30 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: htpdate 2 | Section: net 3 | Priority: extra 4 | Maintainer: Eddy Vervest 5 | Build-Depends: debhelper (>= 5) 6 | Standards-Version: 3.7.2 7 | 8 | Package: htpdate 9 | Architecture: any 10 | Depends: ${shlibs:Depends}, ${misc:Depends} 11 | Description: HTTP based time synchronization tool 12 | 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 servers responses. Htpdate can be used as a daemon, to keep your computer synchronized. Accuracy of htpdate is usually better than 0.5 seconds (even better with multiple servers). If this is not good enough for you, try the ntpd package. 13 | Install the htp package if you need tools for keeping your system's time synchronized via the HTP protocol. Htpdate works also through proxy servers. 14 | -------------------------------------------------------------------------------- /scripts/adjtimex_parameters.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # In daemon mode htpdate writes the systematic drift of the clock to syslog. 4 | # With this script you can convert the PPM drift values into adjtimex 5 | # parameters. Use it when you know what you are doing... 6 | # 7 | # The Linux adjtimex manpage gives you more information. 8 | # 9 | # Feel free to contribute for other OS's. 10 | 11 | if [ ! $1 ]; then 12 | echo "Usage: $0 [current TICK] [current FREQ]" 13 | echo "By default TICK=10000 and FREQ=0" 14 | echo 15 | exit 1 16 | fi 17 | 18 | PPM=$1 19 | TICK=10000 20 | FREQ=0 21 | 22 | if [ $2 ]; then 23 | TICK=$2 24 | fi 25 | 26 | if [ $3 ]; then 27 | FREQ=$3 28 | fi 29 | 30 | FREQTOT=`echo "$PPM * 65536 + $TICK * 6553600 + $FREQ" | bc` 31 | 32 | TICK=`echo "$FREQTOT / 6553600" | bc` 33 | FREQ=`echo "$FREQTOT % 6553600" | bc | awk -F. '{print $1}'` 34 | 35 | echo "TICK=$TICK" 36 | echo "FREQ=$FREQ" 37 | echo "Suggested command: adjtimex -tick $TICK -frequency $FREQ" 38 | -------------------------------------------------------------------------------- /debian/init.d: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | DAEMON=/usr/bin/htpdate 6 | NAME=htpdate 7 | DESC="HTTP Time Protocol daemon" 8 | 9 | test -x $DAEMON || exit 0 10 | 11 | # Include htpdate defaults if available 12 | if [ -f /etc/default/htpdate ] ; then 13 | . /etc/default/htpdate 14 | fi 15 | 16 | case "$1" in 17 | start) 18 | echo -n "Starting $DESC: " 19 | start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ 20 | --exec $DAEMON -- $DAEMON_OPTS 21 | echo "$NAME." 22 | ;; 23 | stop) 24 | echo -n "Stopping $DESC: " 25 | start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \ 26 | --exec $DAEMON 27 | rm -f /var/run/$NAME.pid 28 | echo "$NAME." 29 | ;; 30 | restart) 31 | $0 stop 32 | sleep 1 33 | $0 start 34 | ;; 35 | *) 36 | N=/etc/init.d/$NAME 37 | echo "Usage: $N {start|stop|restart}" >&2 38 | exit 1 39 | ;; 40 | esac 41 | 42 | exit 0 43 | 44 | # vi:set ts=4: 45 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This is htpdate, written and maintained by Eddy Vervest 2 | on Thu, 24 Apr 2008 23:05:05 +0200. 3 | 4 | The original source can always be found at: 5 | http://www.clevervest.com/htp/ 6 | 7 | Copyright Holder: Eddy Vervest 8 | 9 | License: 10 | 11 | This program is free software; you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation; either version 2 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this package; if not, write to the Free Software 23 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 24 | 25 | On Debian systems, the complete text of the GNU General 26 | Public License can be found in `/usr/share/common-licenses/GPL'. 27 | -------------------------------------------------------------------------------- /scripts/htpdate.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # A minimal init script for htpdate 4 | 5 | ### BEGIN INIT INFO 6 | # Provides: htpdate 7 | # Required-Start: $network $remote_fs $syslog 8 | # Required-Stop: $network $remote_fs $syslog 9 | # Default-Start: 2 3 4 5 10 | # Default-Stop: 11 | # Short-Description: Start htpdate daemon 12 | ### END INIT INFO 13 | 14 | 15 | test -x /usr/bin/htpdate || exit 0 16 | 17 | PIDFILE="/var/run/htpdate.pid" 18 | SERVERS="www.linux.org www.freebsd.org" 19 | 20 | # See how we were called. 21 | case "$1" in 22 | start) 23 | echo "Starting HTTP Time Protocol daemon: htpdate" 24 | # Set the time first before daemonizing, because the time offset 25 | # might be too big for smooth time adjustment 26 | /usr/bin/htpdate -D -s -i $PIDFILE $SERVERS 27 | ;; 28 | stop) 29 | echo "Stopping HTTP Time Protocol daemon: htpdate" 30 | if [ -f $PIDFILE ] 31 | then 32 | kill `cat $PIDFILE` 33 | else 34 | echo "$PIDFILE not found" 35 | fi 36 | rm -f $PIDFILE 37 | ;; 38 | restart) 39 | $0 stop 40 | $0 start 41 | ;; 42 | *) 43 | echo "Usage: $0 {start|stop|restart}" 44 | exit 1 45 | esac 46 | 47 | exit 0 48 | 49 | # vi:set ts=4: 50 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | 8 | CFLAGS = -Wall 9 | 10 | ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) 11 | CFLAGS += -O0 12 | else 13 | CFLAGS += -O2 14 | endif 15 | 16 | configure: configure-stamp 17 | configure-stamp: 18 | dh_testdir 19 | 20 | 21 | build: build-stamp 22 | 23 | build-stamp: configure-stamp 24 | dh_testdir 25 | 26 | # Add here commands to compile the package. 27 | $(MAKE) 28 | 29 | clean: 30 | dh_testdir 31 | dh_testroot 32 | rm -f build-stamp configure-stamp 33 | 34 | # Add here commands to clean up after the build process. 35 | -$(MAKE) clean 36 | 37 | dh_clean 38 | 39 | install: build 40 | dh_testdir 41 | dh_testroot 42 | dh_clean -k 43 | dh_installdirs 44 | 45 | # Add here commands to install the package into debian/htpdate. 46 | $(MAKE) DESTDIR=$(CURDIR)/debian/htpdate install 47 | install -m0755 htpdate $(CURDIR)/debian/htpdate/usr/bin 48 | install -m0755 debian/init.d $(CURDIR)/debian/htpdate/etc/init.d/htpdate 49 | install -m0644 debian/htpdate-default $(CURDIR)/debian/htpdate/etc/default/htpdate 50 | 51 | 52 | 53 | # Build architecture-independent files here. 54 | binary-indep: build install 55 | # We have nothing to do by default. 56 | 57 | # Build architecture-dependent files here. 58 | binary-arch: build install 59 | dh_testdir 60 | dh_testroot 61 | dh_installchangelogs Changelog 62 | dh_installdocs README 63 | dh_installexamples 64 | dh_installman htpdate.8.gz 65 | dh_installinit 66 | dh_link 67 | dh_strip 68 | dh_compress 69 | dh_fixperms 70 | dh_installdeb 71 | dh_shlibdeps 72 | dh_gencontrol 73 | dh_md5sums 74 | dh_builddeb 75 | 76 | binary: binary-indep binary-arch 77 | .PHONY: build clean binary-indep binary-arch binary install configure 78 | -------------------------------------------------------------------------------- /htpdate.spec: -------------------------------------------------------------------------------- 1 | Summary: HTTP based time synchronization tool 2 | Name: htpdate 3 | Version: 1.0.5 4 | Release: 1 5 | License: GPL 6 | Group: System Environment/Daemons 7 | URL: http://www.vervest.org/htp 8 | Packager: Eddy Vervest 9 | Source: http://www.vervest.org/htp/archive/c/%{name}-%{version}.tar.bz2 10 | BuildRoot: %{_tmppath}/%{name}-%{version}-root 11 | Prereq: /sbin/chkconfig 12 | 13 | 14 | %description 15 | The HTTP Time Protocol (HTP) is used to synchronize a computer's time 16 | with web servers as reference time source. Htpdate will synchronize your 17 | computer's time by extracting timestamps from HTTP headers found 18 | in web servers responses. Htpdate can be used as a daemon, to keep your 19 | computer synchronized. 20 | Accuracy of htpdate is usually better than 0.5 seconds (even better with 21 | multiple servers). If this is not good enough for you, try the ntpd package. 22 | 23 | Install the htp package if you need tools for keeping your system's 24 | time synchronized via the HTP protocol. Htpdate works also through 25 | proxy servers. 26 | 27 | %prep 28 | %setup -q 29 | 30 | %build 31 | make 32 | strip -s htpdate 33 | 34 | %install 35 | mkdir -p %{buildroot}/%{_bindir} 36 | mkdir -p %{buildroot}/%{_mandir} 37 | mkdir -p %{buildroot}/%{_initrddir} 38 | 39 | install -m0755 htpdate %{buildroot}%{_bindir}/htpdate 40 | install -m0644 htpdate.8.gz %{buildroot}%{_mandir}/htpdate.8.gz 41 | install -m0755 scripts/htpdate.init %{buildroot}%{_initrddir}/htpdate 42 | 43 | %post 44 | %{_initrddir}/htpdate stop 45 | /sbin/chkconfig --add htpdate 46 | %{_initrddir}/htpdate start 47 | 48 | %preun 49 | %{_initrddir}/htpdate stop 50 | /sbin/chkconfig --del htpdate 51 | 52 | %clean 53 | [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} 54 | 55 | %files 56 | %defattr(-,root,root) 57 | %doc README Changelog 58 | %config(noreplace) %{_initrddir}/htpdate 59 | %{_bindir}/htpdate 60 | %{_mandir}/htpdate.8.gz 61 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Htpdate 2 | ------- 3 | 4 | The HTTP Time Protocol (HTP) is used to synchronize a computer's time 5 | with web servers as reference time source. Htpdate will synchronize your 6 | computer's time by extracting timestamps from HTTP headers found 7 | in web server responses. Htpdate can be used as a daemon, to keep your 8 | computer synchronized. 9 | The accuracy of htpdate is at least -+0.5 seconds (better with multiple 10 | servers). If this is not good enough for you, try the ntpd package. 11 | 12 | Install the htpdate package if you need tools for keeping your system's 13 | time synchronized via the HTP protocol. Htpdate works also through 14 | proxy servers. 15 | 16 | 17 | Installation from source 18 | ------------------------ 19 | 20 | Tested on Linux and FreeBSD only, but should work for most Unix flavors. 21 | 22 | $ tar zxvf htpdate-x.y.z.tar.gz 23 | or 24 | $ tar jxvf htpdate-x.y.z.tar.bz2 25 | $ cd htpdate-X.Y.Z 26 | $ make 27 | $ make install 28 | 29 | An example init script (scripts/htpdate.init) for use in /etc/init.d/ 30 | is included, but not installed automatically. This scripts with run 31 | htpdate as a daemon. 32 | 33 | Another option is to use htpdate in a cronjob and start it periodically 34 | from cron. For a daily time sync it would look something like this: 35 | 5 3 * * * /usr/bin/htpdate -s www.linux.org www.freebsd.org 36 | 37 | 38 | Installation from RPM 39 | --------------------- 40 | 41 | The easiest way to install (Redhat, SuSE, Mandriva etc..) 42 | 43 | $ rpm -Uvh htpdate-x.y.z.i386.rpm 44 | 45 | By default the htpdate daemon is activated (with chkconfig). 46 | If you only want to run htpdate from cron, disable the htpdate service 47 | with 'chkconfig --del htpdate'. 48 | 49 | 50 | Usage 51 | ----- 52 | 53 | Usage: htpdate [-046abdhlqstxD] [-i pid file] [-m minpoll] [-M maxpoll] 54 | [-p precision] [-P [:port]] [-u user[:group]] 55 | ... 56 | 57 | E.g. htpdate -q www.linux.org www.freebsd.org 58 | 59 | 60 | In general, if more web servers are specified, the accuracy will increase. 61 | 62 | See manpage for more details. 63 | 64 | 65 | To do 66 | ----- 67 | 68 | - I'm open for suggestions :) 69 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | Changes in 1.0.5 2 | ---------------- 3 | 4 | - Change of e-mail/website 5 | - Fix for debian build 6 | 7 | 8 | 1.0.4 9 | ----- 10 | - Fixed a memory leak (reported and fixed by Andreas Bohne-Lang) 11 | 12 | 13 | 1.0.3 14 | ----- 15 | 16 | - Fixed logic error... 1.0.2 is broken :( 17 | 18 | 19 | 1.0.2 20 | ----- 21 | 22 | - Fixed a buffer overflow when time offset gets to large 23 | https://dev.openwrt.org/cgi-bin/trac.fcgi/ticket/3940 24 | 25 | 26 | 1.0.1 27 | ----- 28 | 29 | - Added "burst mode" (-b) to enhance accuracy. 30 | - Extended debug output 31 | - Removed potential buffer overflows vulnerabilities. 32 | - Replaced usleep by nanosleep (which is more portable). 33 | - Included debian package. 34 | 35 | 36 | 1.0.0 37 | ----- 38 | 39 | - Cleanup/simplified the code. 40 | - Again a more robust implementation of the "-p" (precision) switch. 41 | 42 | 43 | 0.9.3 44 | ----- 45 | 46 | - Bug fixes: poll loop could be become close to zero in case of a "connection 47 | failed". Sleeptime wasn't correct in case all hosts fail. 48 | - Use more "sane" minsleep and maxsleep values. 49 | 50 | 51 | 0.9.2 52 | ----- 53 | 54 | - The use of the ntp_adjtime system call is now optional. The clock 55 | frequency will be adjusted when using the "-x" switch and the systematic 56 | drift will be compensated. Works only in daemon mode. 57 | 58 | 59 | 0.9.1 60 | ----- 61 | 62 | - Htpdate can drop root privileges and run as a restricted user. 63 | - Drift calculation starts now after first time correction. 64 | 65 | 66 | 0.9.0 67 | ----- 68 | 69 | - Mostly code cleanup 70 | - Changed "char" variables into "int", which should be more efficient 71 | - Changed the qsort routine, into an insertion sort. Quicksort is 72 | kind of overkill for such small lists. 73 | - Debug mode is allowed in daemon mode. 74 | 75 | 76 | 0.8.8 77 | ----- 78 | 79 | - Speeded up the poll cycle loop, once a time offset has been detected. 80 | - Added systematic drift to the syslog logging (daemon only). 81 | The "adjtimex_parameters.sh" script may help to reduce the drift of your 82 | system clock. 83 | - Changed "precision" from micro into milliseconds. 84 | - Changed manpage directory from /usr/man into /usr/share/man. 85 | - HTTP/1.1 has become the default 86 | 87 | 88 | 0.8.7 89 | ----- 90 | 91 | - Bug fix: with precision set, the time could only be adjusted 92 | negative (slow down). 93 | - Fixed undesired effects in (rare) cases when using only a few servers. 94 | - Exit code changed to 1 if no server was found (requested by Supernaut). 95 | 96 | 97 | 0.8.6 98 | ----- 99 | 100 | - Reintroduction of the "-p" switch. The "precision" determines more accurate 101 | when a time adjustment is needed. The implementation of precision is much 102 | beter than the one in the past. 103 | 104 | 105 | 0.8.5 106 | ----- 107 | 108 | - Changed the variable type of "param" from char into int (thanks to 109 | Arnaud Mazin). GCC 3.4.x is less forgiving than older versions. 110 | - Made compare function for qsort more robust (avoiding overflow). 111 | 112 | 113 | 0.8.4 114 | ----- 115 | 116 | - Finaly added IPv6 support! 117 | - If you would lose internet connectivity, htpdate would flood the system 118 | (CPU and log). A patch submitted by Peter Surda, has been included. 119 | - Removed -0 switch (HTTP/1.0), since this is the default anyway. 120 | 121 | 122 | 0.8.3 123 | ----- 124 | 125 | - Fixed that "when" wasn't properly initialized in every poll cycle. This 126 | bug fix reduces jitter when system time is close to the correct time. 127 | - Added missing "-t" switch to the manpage. 128 | - Added warning message if more than 16 servers are specified. 129 | - Htpdate double forks now, like a proper daemon :) 130 | - vim setting are added to the source (set ts=4) 131 | 132 | 133 | 0.8.2 134 | ----- 135 | 136 | - Fixed a major bug in offset calculation (caused by changing the rtt type 137 | from double to unsigned long) 138 | 139 | 140 | 0.8.1 (broken) 141 | -------------- 142 | 143 | - Correction install paths in Makefile, added uninstall option 144 | - Added "-t" switch, which disables time sanity checking (requested for 145 | devices, like supported by openwrt, which boot/startup at "epoch" time) 146 | 147 | 148 | 0.8.0 149 | ----- 150 | 151 | - Lots of code clean up, eg.: 152 | - compiles with the -ansi switch (for better portability, but only 153 | tested on Linux and FreeBSD) 154 | - Compiles with Tiny C Compiler (tcc) and Intel C compiler (icc) 155 | - Restored compatibility with FreeBSD (timezone calculation). 156 | - A second poll is made if a time offset has been detected. 157 | - Removed the switches "-t" and "-x". In daemon mode htpdate will only adjust 158 | time. With the "-s" switch htpdate will set the time once at startup and 159 | after that only smooth adjusts are made. 160 | - Added "-l" switch, which enables logging to syslog in non-daemon mode. 161 | Convinient if htpdate is used from cron. 162 | - Added "-0" to make an HTTP/1.0 request and "-1" for HTTP/1.1 163 | 164 | 165 | 0.7.2 166 | ----- 167 | 168 | - Minor bug fix. When running in daemon mode TCP connections weren't cleaned up 169 | properly (sockets remained in CLOSE_WAIT status). 170 | 171 | 172 | 0.7.1 173 | ----- 174 | 175 | - Bug fix. Poll cycle could become very very short (seconds...) in some 176 | cases. 177 | 178 | 179 | 0.7.0 180 | ----- 181 | 182 | - The polling mechanisme has been improved once more :) 183 | As of this version polls are also spread within the polling cycle. 184 | This way a time offset will be detected earlier, without increasing the 185 | polling frequency. 186 | - Sleeptime isn't writen to syslog anymore, only time adjustments 187 | - Previous versions of htpdate tried to close a already closed HTTP/1.0 188 | session. 189 | - The rpm version doesn't override the init-script anymore 190 | 191 | 192 | 0.6.2 193 | ----- 194 | 195 | - Bug fix, time wasn't correct if 'timeavg' was negative... 196 | 197 | 198 | 0.6.1 199 | ----- 200 | 201 | - Code clean up, use global variable 'timezone' 202 | 203 | 204 | 0.6 205 | --- 206 | 207 | - New poll schedule mechanisme has been introduced! 208 | This results in a better spreading of the polls in time to 209 | gain some extra accuracy. 210 | - Removed the -p (precision) flag, because it has become obsolete with 211 | the new poll scheduling mechanisme. 212 | - Extended 'debug mode' output with round trip time (rtt) information. 213 | - Sleeptime parameters are no longer in seconds, but in 2^n seconds. 214 | 215 | 216 | 0.5 217 | --- 218 | 219 | - Added relevant header files, so gcc -Wall compiles without warnings. 220 | - Added -p switch, to set the precision of htpdate. The polling rate 221 | is affected by this switch (high precision -> more frequent polling). 222 | 223 | 224 | 0.4 225 | --- 226 | 227 | - Spread the individual time polls better, to gain accuracy with 228 | a small number of web servers as source 229 | - Added debug mode, so you can value the quality of the timestamps 230 | from web servers 231 | - Minor correction on HEAD request (removed max-age=0) 232 | - pid file is created 233 | - Added htpdate init script 234 | 235 | 236 | 0.3 237 | --- 238 | 239 | - Run htpdate as a daemon 240 | - automatic set/adjust time, based upon offset 241 | - automatic poll interval 242 | - Added manpage 243 | - Added RPM 244 | - Minor bug fixes 245 | 246 | 247 | 0.2 248 | --- 249 | 250 | - Htpdate can set or adjust time smoothly now, without calling external 251 | programs 252 | 253 | 254 | 0.1 255 | --- 256 | 257 | - Initial release. Htpdate extracts the raw timestamp from a webserver. 258 | -------------------------------------------------------------------------------- /htpdate.c: -------------------------------------------------------------------------------- 1 | /* 2 | htpdate v1.0.5 3 | 4 | Eddy Vervest 5 | http://www.vervest.org/htp 6 | 7 | Synchronize local workstation with time offered by remote web servers 8 | 9 | This program works with the timestamps return by web servers, 10 | formatted as specified by HTTP/1.1 (RFC 2616, RFC 1123). 11 | 12 | Example usage: 13 | 14 | Debug mode (shows raw timestamps, round trip time (RTT) and 15 | time difference): 16 | 17 | ~# htpdate -d www.linux.org www.freebsd.org 18 | 19 | Adjust time smoothly: 20 | 21 | ~# htpdate -a www.linux.org www.freebsd.org 22 | 23 | ...see man page for more details 24 | 25 | 26 | This program is free software; you can redistribute it and/or 27 | modify it under the terms of the GNU General Public License 28 | as published by the Free Software Foundation; either version 2 29 | of the License, or (at your option) any later version. 30 | http://www.gnu.org/copyleft/gpl.html 31 | */ 32 | 33 | /* Needed to avoid implicit warnings from strptime */ 34 | #define _GNU_SOURCE 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #define VERSION "1.0.5" 58 | #define MAX_HTTP_HOSTS 15 /* 16 web servers */ 59 | #define DEFAULT_HTTP_PORT "80" 60 | #define DEFAULT_PROXY_PORT "8080" 61 | #define DEFAULT_IP_VERSION PF_UNSPEC /* IPv6 and IPv4 */ 62 | #define DEFAULT_HTTP_VERSION "1" /* HTTP/1.1 */ 63 | #define DEFAULT_TIME_LIMIT 31536000 /* 1 year */ 64 | #define DEFAULT_MIN_SLEEP 1800 /* 30 minutes */ 65 | #define DEFAULT_MAX_SLEEP 115200 /* 32 hours */ 66 | #define MAX_DRIFT 32768000 /* 500 PPM */ 67 | #define MAX_ATTEMPT 2 /* Poll attempts */ 68 | #define DEFAULT_PID_FILE "/var/run/htpdate.pid" 69 | #define URLSIZE 128 70 | #define BUFFERSIZE 1024 71 | 72 | #define sign(x) (x < 0 ? (-1) : 1) 73 | 74 | 75 | /* By default we turn off "debug" and "log" mode */ 76 | static int debug = 0; 77 | static int logmode = 0; 78 | static time_t gmtoffset; 79 | 80 | 81 | /* Insertion sort is more efficient (and smaller) than qsort for small lists */ 82 | static void insertsort( int a[], int length ) { 83 | int i, j, value; 84 | 85 | for ( i = 1; i < length; i++ ) { 86 | value = a[i]; 87 | for ( j = i - 1; j >= 0 && a[j] > value; j-- ) 88 | a[j+1] = a[j]; 89 | a[j+1] = value; 90 | } 91 | } 92 | 93 | 94 | /* Split argument in hostname/IP-address and TCP port 95 | Supports IPv6 literal addresses, RFC 2732. 96 | */ 97 | static void splithostport( char **host, char **port ) { 98 | char *rb, *rc, *lb, *lc; 99 | 100 | lb = strchr( *host, '[' ); 101 | rb = strrchr( *host, ']' ); 102 | lc = strchr( *host, ':' ); 103 | rc = strrchr( *host, ':' ); 104 | 105 | /* A (litteral) IPv6 address with portnumber */ 106 | if ( rb < rc && lb != NULL && rb != NULL ) { 107 | rb[0] = '\0'; 108 | *port = rc + 1; 109 | *host = lb + 1; 110 | return; 111 | } 112 | 113 | /* A (litteral) IPv6 address without portnumber */ 114 | if ( rb != NULL && lb != NULL ) { 115 | rb[0] = '\0'; 116 | *host = lb + 1; 117 | return; 118 | } 119 | 120 | /* A IPv4 address or hostname with portnumber */ 121 | if ( rc != NULL && lc == rc ) { 122 | rc[0] = '\0'; 123 | *port = rc + 1; 124 | return; 125 | } 126 | 127 | } 128 | 129 | 130 | /* Printlog is a slighty modified version from the one used in rdate */ 131 | static void printlog( int is_error, char *format, ... ) { 132 | va_list args; 133 | char buf[128]; 134 | 135 | va_start(args, format); 136 | (void) vsnprintf(buf, sizeof(buf), format, args); 137 | va_end(args); 138 | 139 | if ( logmode ) 140 | syslog(is_error?LOG_WARNING:LOG_INFO, "%s", buf); 141 | else 142 | fprintf(is_error?stderr:stdout, "%s\n", buf); 143 | } 144 | 145 | 146 | static long getHTTPdate( char *host, char *port, char *proxy, char *proxyport, char *httpversion, int ipversion, int when, int mintimestamp, int *error ) { 147 | int server_s = -1; 148 | int rc; 149 | struct addrinfo hints, *res, *res0; 150 | struct tm tm; 151 | struct timeval timevalue = {LONG_MAX, 0}; 152 | struct timeval timeofday; 153 | struct timespec sleepspec, remainder; 154 | long rtt; 155 | char buffer[BUFFERSIZE]; 156 | char remote_time[25] = { '\0' }; 157 | char url[URLSIZE] = { '\0' }; 158 | char *pdate = NULL; 159 | struct timeval timeout = {5, 0}; 160 | 161 | 162 | /* Connect to web server via proxy server or directly */ 163 | memset( &hints, 0, sizeof(hints) ); 164 | switch( ipversion ) { 165 | case 4: /* IPv4 only */ 166 | hints.ai_family = AF_INET; 167 | break; 168 | case 6: /* IPv6 only */ 169 | hints.ai_family = AF_INET6; 170 | break; 171 | default: /* Support IPv6 and IPv4 name resolution */ 172 | hints.ai_family = PF_UNSPEC; 173 | } 174 | hints.ai_socktype = SOCK_STREAM; 175 | hints.ai_flags = AI_CANONNAME; 176 | 177 | if ( proxy == NULL ) { 178 | rc = getaddrinfo( host, port, &hints, &res0 ); 179 | } else { 180 | snprintf( url, URLSIZE, "http://%s:%s", host, port); 181 | rc = getaddrinfo( proxy, proxyport, &hints, &res0 ); 182 | } 183 | 184 | /* Was the hostname and service resolvable? */ 185 | if ( rc ) { 186 | printlog( 1, "%s host or service unavailable", host ); 187 | goto error; 188 | } 189 | 190 | /* Build a combined HTTP/1.0 and 1.1 HEAD request 191 | Pragma: no-cache, "forces" an HTTP/1.0 and 1.1 compliant 192 | web server to return a fresh timestamp 193 | Connection: close, allows the server the immediately close the 194 | connection after sending the response. 195 | */ 196 | 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); 197 | 198 | /* Loop through the available canonical names */ 199 | res = res0; 200 | do { 201 | server_s = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); 202 | if ( server_s < 0 ) { 203 | continue; 204 | } 205 | int val = fcntl(server_s, F_GETFL, 0); 206 | fcntl(server_s, F_SETFL, val | O_NONBLOCK); 207 | rc = connect( server_s, res->ai_addr, res->ai_addrlen ); 208 | if ( rc == -1 ) { 209 | if ( errno != EINPROGRESS ) { 210 | printlog( 1, "connect() got error: %d", errno ); 211 | close( server_s ); 212 | server_s = -1; 213 | continue; 214 | } else { 215 | fd_set fdset; 216 | FD_ZERO(&fdset); 217 | FD_SET(server_s, &fdset); 218 | if (select(server_s + 1, NULL, &fdset, NULL, &timeout) < 1) { 219 | printlog( 1, "select() failed: %d", errno ); 220 | printlog( 1, "FD_ISSET: %d", FD_ISSET(server_s, &fdset) ); 221 | close(server_s); 222 | server_s = -1; 223 | continue; 224 | } 225 | } 226 | } 227 | fcntl(server_s, F_SETFL, val); 228 | setsockopt(server_s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); 229 | setsockopt(server_s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); 230 | break; 231 | } while ( ( res = res->ai_next ) ); 232 | 233 | freeaddrinfo(res0); 234 | 235 | if (server_s < 0) { 236 | printlog( 1, "%s connection failed", host ); 237 | goto error; 238 | } 239 | 240 | /* Initialize timer */ 241 | gettimeofday(&timeofday, NULL); 242 | 243 | /* Initialize RTT (start of measurement) */ 244 | rtt = timeofday.tv_sec; 245 | 246 | /* Wait till we reach the desired time, "when" */ 247 | sleepspec.tv_sec = 0; 248 | if ( when >= timeofday.tv_usec ) { 249 | sleepspec.tv_nsec = ( when - timeofday.tv_usec ) * 1000; 250 | } else { 251 | sleepspec.tv_nsec = ( 1000000 + when - timeofday.tv_usec ) * 1000; 252 | rtt++; 253 | } 254 | nanosleep( &sleepspec, &remainder ); 255 | 256 | /* Send HEAD request */ 257 | if ( send(server_s, buffer, strlen(buffer), 0) < 0 ) { 258 | printlog( 1, "Error sending" ); 259 | goto error; 260 | } 261 | 262 | /* Receive data from the web server 263 | The return code from recv() is the number of bytes received 264 | */ 265 | if ( recv(server_s, buffer, BUFFERSIZE, 0) < 0 ) { 266 | printlog(1, "Error receiving"); 267 | goto error; 268 | } 269 | 270 | /* Assuming that network delay (server->htpdate) is neglectable, 271 | the received web server time "should" match the local time. 272 | 273 | From RFC 2616 paragraph 14.18 274 | ... 275 | It SHOULD represent the best available approximation 276 | of the date and time of message generation, unless the 277 | implementation has no means of generating a reasonably 278 | accurate date and time. 279 | ... 280 | */ 281 | 282 | gettimeofday(&timeofday, NULL); 283 | 284 | /* rtt contains round trip time in micro seconds, now! */ 285 | rtt = ( timeofday.tv_sec - rtt ) * 1000000 + \ 286 | timeofday.tv_usec - when; 287 | 288 | /* Look for the line that contains Date: */ 289 | if ( (pdate = strstr(buffer, "Date: ")) == NULL ) { 290 | printlog( 1, "%s no timestamp", host ); 291 | goto error; 292 | } 293 | 294 | strncpy(remote_time, pdate + 11, 24); 295 | 296 | if ( strptime( remote_time, "%d %b %Y %T", &tm) == NULL) { 297 | printlog( 1, "%s unknown time format", host ); 298 | goto error; 299 | } 300 | 301 | /* Web server timestamps are without daylight saving */ 302 | tm.tm_isdst = 0; 303 | timevalue.tv_sec = mktime(&tm); 304 | 305 | if ( timevalue.tv_sec + gmtoffset < mintimestamp) { 306 | printlog( 1, "%s time %d is below lower limit %d", host, \ 307 | timevalue.tv_sec + gmtoffset, mintimestamp); 308 | goto error; 309 | } 310 | 311 | /* Print host, raw timestamp, round trip time */ 312 | if ( debug ) 313 | printlog( 0, "%-25s %s (%.3f) => %li", host, remote_time, \ 314 | rtt * 1e-6, timevalue.tv_sec - timeofday.tv_sec \ 315 | + gmtoffset ); 316 | 317 | /* Return the time delta between web server time (timevalue) 318 | and system time (timeofday) 319 | */ 320 | close( server_s ); 321 | *error = 0; 322 | return( timevalue.tv_sec - timeofday.tv_sec + gmtoffset ); 323 | 324 | error: 325 | if (server_s >= 0) { 326 | close(server_s); 327 | } 328 | *error = 1; 329 | return(0); 330 | } 331 | 332 | 333 | static int setclock( double timedelta, int setmode ) { 334 | struct timeval timeofday; 335 | 336 | if ( timedelta == 0 ) { 337 | printlog( 0, "No time correction needed" ); 338 | return(0); 339 | } 340 | 341 | switch ( setmode ) { 342 | 343 | case 0: /* No time adjustment, just print time */ 344 | printlog( 0, "Offset %.3f seconds", timedelta ); 345 | return(0); 346 | 347 | case 1: /* Adjust time smoothly */ 348 | timeofday.tv_sec = (long)timedelta; 349 | timeofday.tv_usec = (long)((timedelta - timeofday.tv_sec) * 1000000); 350 | 351 | printlog( 0, "Adjusting %.3f seconds", timedelta ); 352 | 353 | /* Become root */ 354 | if ( seteuid(0) ) { 355 | printlog( 1, "seteuid()" ); 356 | exit(1); 357 | } else { 358 | return( adjtime(&timeofday, NULL) ); 359 | } 360 | 361 | case 2: /* Set time */ 362 | printlog( 0, "Setting %.3f seconds", timedelta ); 363 | 364 | gettimeofday( &timeofday, NULL ); 365 | timedelta += ( timeofday.tv_sec + timeofday.tv_usec*1e-6 ); 366 | 367 | timeofday.tv_sec = (long)timedelta; 368 | timeofday.tv_usec = (long)(timedelta - timeofday.tv_sec) * 1000000; 369 | 370 | printlog( 0, "Set: %s", asctime(localtime(&timeofday.tv_sec)) ); 371 | 372 | /* Become root */ 373 | if ( seteuid(0) ) { 374 | printlog( 1, "seteuid()" ); 375 | exit(1); 376 | } else { 377 | return( settimeofday(&timeofday, NULL) ); 378 | } 379 | 380 | case 3: /* Set frequency, but first an adjust */ 381 | return( setclock( timedelta, 1 ) ); 382 | 383 | 384 | default: 385 | return(-1); 386 | 387 | } 388 | 389 | } 390 | 391 | 392 | static int htpdate_adjtimex( double drift ) { 393 | struct timex tmx; 394 | long freq; 395 | 396 | /* Read current kernel frequency */ 397 | tmx.modes = 0; 398 | adjtimex(&tmx); 399 | 400 | /* Calculate new frequency */ 401 | freq = (long)(65536e6 * drift); 402 | 403 | /* Take the average of current and new drift values */ 404 | tmx.freq = tmx.freq + (freq >> 1); 405 | if ( (tmx.freq < -MAX_DRIFT) || (tmx.freq > MAX_DRIFT) ) 406 | tmx.freq = sign(tmx.freq) * MAX_DRIFT; 407 | 408 | printlog( 0, "Adjusting frequency %li", tmx.freq ); 409 | tmx.modes = MOD_FREQUENCY; 410 | 411 | /* Become root */ 412 | if ( seteuid(0) ) { 413 | printlog( 1, "seteuid()" ); 414 | exit(1); 415 | } else { 416 | return( adjtimex(&tmx) ); 417 | } 418 | 419 | } 420 | 421 | 422 | static void showhelp() { 423 | puts("htpdate version "VERSION"\n\ 424 | Usage: htpdate [-046abdhlqstxD] [-i pid file] [-m minpoll] [-M maxpoll]\n\ 425 | [-p precision] [-P [:port]] [-T mintimestamp] [-u user[:group]]\n\ 426 | ...\n\n\ 427 | -0 HTTP/1.0 request\n\ 428 | -4 Force IPv4 name resolution only\n\ 429 | -6 Force IPv6 name resolution only\n\ 430 | -a adjust time smoothly\n\ 431 | -b burst mode\n\ 432 | -d debug mode\n\ 433 | -D daemon mode\n\ 434 | -h help\n\ 435 | -i pid file\n\ 436 | -l use syslog for output\n\ 437 | -m minimum poll interval\n\ 438 | -M maximum poll interval\n\ 439 | -p precision (ms)\n\ 440 | -P proxy server\n\ 441 | -q query only, don't make time changes (default)\n\ 442 | -s set time\n\ 443 | -t turn off sanity time check\n\ 444 | -T minimum timestamp allowed in server response\n\ 445 | -u run daemon as user\n\ 446 | -x adjust kernel clock\n\ 447 | host web server hostname or ip address (maximum of 16)\n\ 448 | port port number (default 80 and 8080 for proxy server)\n"); 449 | 450 | return; 451 | } 452 | 453 | 454 | /* Run htpdate in daemon mode */ 455 | static void runasdaemon( char *pidfile ) { 456 | FILE *pid_file; 457 | pid_t pid; 458 | 459 | /* Check if htpdate is already running (pid exists)*/ 460 | pid_file = fopen(pidfile, "r"); 461 | if ( pid_file ) { 462 | fputs( "htpdate already running\n", stderr ); 463 | exit(1); 464 | } 465 | 466 | pid = fork(); 467 | if ( pid < 0 ) { 468 | fputs( "fork()\n", stderr ); 469 | exit(1); 470 | } 471 | 472 | if ( pid > 0 ) { 473 | exit(0); 474 | } 475 | 476 | /* Create a new SID for the child process */ 477 | if ( setsid () < 0 ) 478 | exit(1); 479 | 480 | /* Close out the standard file descriptors */ 481 | close( STDIN_FILENO ); 482 | close( STDOUT_FILENO ); 483 | close( STDERR_FILENO ); 484 | 485 | signal(SIGHUP, SIG_IGN); 486 | signal(SIGPIPE, SIG_IGN); 487 | 488 | /* Change the file mode mask */ 489 | umask(0); 490 | 491 | /* Change the current working directory */ 492 | if ( chdir("/") < 0 ) { 493 | printlog( 1, "chdir()" ); 494 | exit(1); 495 | } 496 | 497 | /* Second fork, to become the grandchild */ 498 | pid = fork(); 499 | if ( pid < 0 ) { 500 | printlog( 1, "fork()" ); 501 | exit(1); 502 | } 503 | 504 | if ( pid > 0 ) { 505 | /* Write a pid file */ 506 | pid_file = fopen( pidfile, "w" ); 507 | if ( !pid_file ) { 508 | printlog( 1, "Error writing pid file" ); 509 | exit(1); 510 | } else { 511 | fprintf( pid_file, "%u\n", (unsigned short)pid ); 512 | fclose( pid_file ); 513 | } 514 | printlog( 0, "htpdate version "VERSION" started" ); 515 | exit(0); 516 | } 517 | 518 | } 519 | 520 | 521 | int main( int argc, char *argv[] ) { 522 | char *host = NULL, *proxy = NULL, *proxyport = NULL; 523 | char *port; 524 | char *httpversion = DEFAULT_HTTP_VERSION; 525 | char *pidfile = DEFAULT_PID_FILE; 526 | char *user = NULL, *userstr = NULL, *group = NULL; 527 | long long sumtimes; 528 | double timeavg, drift = 0; 529 | int timedelta[MAX_HTTP_HOSTS], timestamp; 530 | int numservers, validtimes, goodtimes, mean; 531 | int nap = 0, when = 500000, precision = 0; 532 | int setmode = 0, burstmode = 0, try, offsetdetect; 533 | int i, burst, param; 534 | int daemonize = 0; 535 | int ipversion = DEFAULT_IP_VERSION; 536 | int timelimit = DEFAULT_TIME_LIMIT; 537 | int minsleep = DEFAULT_MIN_SLEEP; 538 | int maxsleep = DEFAULT_MAX_SLEEP; 539 | int sleeptime = minsleep; 540 | int sw_uid = 0, sw_gid = 0; 541 | time_t starttime = 0; 542 | int mintimestamp = 0; 543 | int error, all_server_error; 544 | 545 | struct passwd *pw; 546 | struct group *gr; 547 | 548 | extern char *optarg; 549 | extern int optind; 550 | 551 | 552 | /* Parse the command line switches and arguments */ 553 | while ( (param = getopt(argc, argv, "046abdhi:lm:p:qstu:xDM:P:T:") ) != -1) 554 | switch( param ) { 555 | 556 | case '0': /* HTTP/1.0 */ 557 | httpversion = "0"; 558 | break; 559 | case '4': /* IPv4 only */ 560 | ipversion = 4; 561 | break; 562 | case '6': /* IPv6 only */ 563 | ipversion = 6; 564 | break; 565 | case 'a': /* adjust time */ 566 | setmode = 1; 567 | break; 568 | case 'b': /* burst mode */ 569 | burstmode = 1; 570 | break; 571 | case 'd': /* turn debug on */ 572 | debug = 1; 573 | break; 574 | case 'h': /* show help */ 575 | showhelp(); 576 | exit(0); 577 | case 'i': /* pid file */ 578 | pidfile = (char *)optarg; 579 | break; 580 | case 'l': /* log mode */ 581 | logmode = 1; 582 | break; 583 | case 'm': /* minimum poll interval */ 584 | if ( ( minsleep = atoi(optarg) ) <= 0 ) { 585 | fputs( "Invalid sleep time\n", stderr ); 586 | exit(1); 587 | } 588 | sleeptime = minsleep; 589 | break; 590 | case 'p': /* precision */ 591 | precision = atoi(optarg) ; 592 | if ( (precision <= 0) || (precision >= 500) ) { 593 | fputs( "Invalid precision\n", stderr ); 594 | exit(1); 595 | } 596 | precision *= 1000; 597 | break; 598 | case 'q': /* query only */ 599 | break; 600 | case 's': /* set time */ 601 | setmode = 2; 602 | break; 603 | case 't': /* disable "sanity" time check */ 604 | timelimit = 2100000000; 605 | break; 606 | case 'u': /* drop root privileges and run as user */ 607 | user = (char *)optarg; 608 | userstr = strchr( user, ':' ); 609 | if ( userstr != NULL ) { 610 | userstr[0] = '\0'; 611 | group = userstr + 1; 612 | } 613 | if ( (pw = getpwnam(user)) != NULL ) { 614 | sw_uid = pw->pw_uid; 615 | sw_gid = pw->pw_gid; 616 | } else { 617 | printf( "Unknown user %s\n", user ); 618 | exit(1); 619 | } 620 | if ( group != NULL ) { 621 | if ( (gr = getgrnam(group)) != NULL ) { 622 | sw_gid = gr->gr_gid; 623 | } else { 624 | printf( "Unknown group %s\n", group ); 625 | exit(1); 626 | } 627 | } 628 | break; 629 | case 'x': /* adjust time and "kernel" */ 630 | setmode = 3; 631 | break; 632 | case 'D': /* run as daemon */ 633 | daemonize = 1; 634 | logmode = 1; 635 | break; 636 | case 'M': /* maximum poll interval */ 637 | if ( ( maxsleep = atoi(optarg) ) <= 0 ) { 638 | fputs( "Invalid sleep time\n", stderr ); 639 | exit(1); 640 | } 641 | break; 642 | case 'P': 643 | proxy = (char *)optarg; 644 | proxyport = DEFAULT_PROXY_PORT; 645 | splithostport( &proxy, &proxyport ); 646 | break; 647 | case 'T': 648 | if ( ( mintimestamp = atoi(optarg) ) <= 0 ) { 649 | fputs( "Invalid min timestamp\n", stderr ); 650 | exit(1); 651 | } 652 | break; 653 | case '?': 654 | return 1; 655 | default: 656 | abort(); 657 | } 658 | 659 | /* Display help page, if no servers are specified */ 660 | if ( argv[optind] == NULL ) { 661 | showhelp(); 662 | exit(1); 663 | } 664 | 665 | /* Exit if too many servers are specified */ 666 | numservers = argc - optind; 667 | if ( numservers > MAX_HTTP_HOSTS ) { 668 | fputs( "Too many servers\n", stderr ); 669 | exit(1); 670 | } 671 | 672 | /* One must be "root" to change the system time */ 673 | if ( (getuid() != 0) && (setmode || daemonize) ) { 674 | fputs( "Only root can change time\n", stderr ); 675 | exit(1); 676 | } 677 | 678 | /* Run as a daemonize when -D is set */ 679 | if ( daemonize ) { 680 | runasdaemon( pidfile ); 681 | /* Query only mode doesn't exist in daemon mode */ 682 | if ( !setmode ) 683 | setmode = 1; 684 | } 685 | 686 | /* Now we are root, we drop the privileges (if specified) */ 687 | if ( sw_uid ) seteuid( sw_uid ); 688 | if ( sw_gid ) setegid( sw_gid ); 689 | 690 | /* Calculate GMT offset from local timezone */ 691 | time(&gmtoffset); 692 | gmtoffset -= mktime(gmtime(&gmtoffset)); 693 | 694 | /* In case we have more than one web server defined, we 695 | spread the polls equal within a second and take a "nap" in between 696 | */ 697 | if ( numservers > 1 ) 698 | if ( precision && (numservers > 2) ) 699 | nap = (1000000 - 2*precision) / (numservers - 1); 700 | else 701 | nap = 1000000 / (numservers + 1); 702 | else { 703 | precision = 0; 704 | nap = 500000; 705 | } 706 | 707 | /* Infinite poll cycle loop in daemonize mode */ 708 | do { 709 | 710 | /* Initialize number of received valid timestamps, good timestamps 711 | and the average of the good timestamps 712 | */ 713 | validtimes = goodtimes = sumtimes = offsetdetect = 0; 714 | all_server_error = 1; 715 | if ( precision ) 716 | when = precision; 717 | else 718 | when = nap; 719 | 720 | /* Loop through the time sources (web servers); poll cycle */ 721 | for ( i = optind; i < argc; i++ ) { 722 | 723 | /* host:port is stored in argv[i] */ 724 | host = (char *)argv[i]; 725 | port = DEFAULT_HTTP_PORT; 726 | splithostport( &host, &port ); 727 | 728 | /* if burst mode, reset "when" */ 729 | if ( burstmode ) { 730 | if ( precision ) 731 | when = precision; 732 | else 733 | when = nap; 734 | } 735 | 736 | burst = 0; 737 | do { 738 | /* Retry if first poll shows time offset */ 739 | try = MAX_ATTEMPT; 740 | do { 741 | if ( debug ) printlog( 0, "burst: %d try: %d when: %d", \ 742 | burst + 1, MAX_ATTEMPT - try + 1, when ); 743 | timestamp = getHTTPdate( host, port, proxy, proxyport,\ 744 | httpversion, ipversion, when, mintimestamp, &error ); 745 | try--; 746 | } while ( timestamp && try ); 747 | 748 | if ( !error ) { 749 | all_server_error = 0; 750 | } 751 | 752 | /* Only include valid responses in timedelta[] */ 753 | if ( !error && timestamp < timelimit && timestamp > -timelimit ) { 754 | timedelta[validtimes] = timestamp; 755 | validtimes++; 756 | } 757 | 758 | /* If we detected a time offset, set the flag */ 759 | if ( timestamp ) 760 | offsetdetect = 1; 761 | 762 | /* Take a nap, to spread polls equally within a second. 763 | Example: 764 | 2 servers => 0.333, 0.666 765 | 3 servers => 0.250, 0.500, 0.750 766 | 4 servers => 0.200, 0.400, 0.600, 0.800 767 | ... 768 | nap = 1000000 / (#servers + 1) 769 | 770 | or when "precision" is specified, a different algorithm is used 771 | */ 772 | when += nap; 773 | 774 | burst++; 775 | } while ( burst < (argc - optind) * burstmode ); 776 | 777 | /* Sleep for a while, unless we detected a time offset */ 778 | if ( daemonize && !offsetdetect && !error ) 779 | sleep( sleeptime / numservers ); 780 | } 781 | 782 | // Retry after 10 seconds when no server returned "Date: " header, 783 | // probably no access to Internet. 784 | if ( all_server_error ) { 785 | sleep(10); 786 | continue; 787 | } 788 | 789 | /* Sort the timedelta results */ 790 | insertsort( timedelta, validtimes ); 791 | 792 | /* Mean time value */ 793 | mean = timedelta[validtimes/2]; 794 | 795 | /* Filter out the bogus timevalues. A timedelta which is more than 796 | 1 seconde off from mean, is considered a 'false ticker'. 797 | NTP synced web servers can never be more off than a second. 798 | */ 799 | for ( i = 0; i < validtimes; i++ ) { 800 | if ( timedelta[i]-mean <= 1 && timedelta[i]-mean >= -1 ) { 801 | sumtimes += timedelta[i]; 802 | goodtimes++; 803 | } 804 | } 805 | 806 | /* Check if we have at least one valid response */ 807 | if ( goodtimes ) { 808 | 809 | timeavg = sumtimes/(double)goodtimes; 810 | 811 | if ( debug ) { 812 | printlog( 0, "#: %d mean: %d average: %.3f", goodtimes, \ 813 | mean, timeavg ); 814 | printlog( 0, "Timezone: GMT%+li (%s,%s)", gmtoffset/3600, tzname[0], tzname[1] ); 815 | } 816 | 817 | /* Do I really need to change the time? */ 818 | if ( sumtimes || !daemonize ) { 819 | /* If a precision was specified and the time offset is small 820 | (< +-1 second), adjust the time with the value of precision 821 | */ 822 | if ( precision && sumtimes < goodtimes && sumtimes > -goodtimes ) 823 | timeavg = (double)precision / 1000000 * sign(sumtimes); 824 | 825 | /* Correct the clock, if not in "adjtimex" mode */ 826 | if ( setclock( timeavg, setmode ) < 0 ) 827 | printlog( 1, "Time change failed" ); 828 | 829 | /* Drop root privileges again */ 830 | if ( sw_uid ) seteuid( sw_uid ); 831 | 832 | if ( daemonize ) { 833 | if ( starttime ) { 834 | /* Calculate systematic clock drift */ 835 | drift = timeavg / ( time(NULL) - starttime ); 836 | printlog( 0, "Drift %.2f PPM, %.2f s/day", \ 837 | drift*1e6, drift*86400 ); 838 | 839 | /* Adjust system clock */ 840 | if ( setmode == 3 ) { 841 | starttime = time(NULL); 842 | /* Adjust the kernel clock */ 843 | if ( htpdate_adjtimex( drift ) < 0 ) 844 | printlog( 1, "Frequency change failed" ); 845 | 846 | /* Drop root privileges again */ 847 | if ( sw_uid ) seteuid( sw_uid ); 848 | } 849 | } else { 850 | starttime = time(NULL); 851 | } 852 | 853 | /* Decrease polling interval to minimum */ 854 | sleeptime = minsleep; 855 | 856 | /* Sleep for 30 minutes after a time adjust or set */ 857 | sleep( DEFAULT_MIN_SLEEP ); 858 | } 859 | } else { 860 | /* Increase polling interval */ 861 | if ( sleeptime < maxsleep ) 862 | sleeptime <<= 1; 863 | } 864 | 865 | } else { 866 | printlog( 1, "No server suitable for synchronization found" ); 867 | /* Sleep for minsleep to avoid flooding */ 868 | if ( daemonize ) 869 | sleep( minsleep ); 870 | else 871 | exit(1); 872 | } 873 | 874 | /* After first poll cycle do not step through time, only adjust */ 875 | if ( setmode != 3 ) { 876 | setmode = 1; 877 | } 878 | 879 | } while ( daemonize ); /* end of infinite while loop */ 880 | 881 | exit(0); 882 | } 883 | 884 | /* vim: set ts=4 sw=4: */ 885 | --------------------------------------------------------------------------------